diff --git a/CMake/mitkFunctionGetLibrarySearchPaths.cmake b/CMake/mitkFunctionGetLibrarySearchPaths.cmake index 86725d7193..21f2436c0e 100644 --- a/CMake/mitkFunctionGetLibrarySearchPaths.cmake +++ b/CMake/mitkFunctionGetLibrarySearchPaths.cmake @@ -1,213 +1,219 @@ #! Helper function that gets all library search paths. #! #! Usage: #! #! mitkFunctionGetLibrarySearchPaths(search_path intermediate_dir [DEBUG|MINSIZEREL|RELWITHDEBINFO]) #! #! #! The function creates the variable ${search_path}. The variable intermediate_dir contains #! paths that should be added to the search_path but should not be checked for existance, #! because the are not yet created. The option DEBUG, MINSIZEREL or RELWITHDEBINFO can be used to indicate that #! not the paths for release configuration are requested but the debug, min size release or "release with debug info" #! paths. #! function(mitkFunctionGetLibrarySearchPaths search_path intermediate_dir) cmake_parse_arguments(PARSE_ARGV 2 GLS "RELEASE;DEBUG;MINSIZEREL;RELWITHDEBINFO" "" "") set(_dir_candidates "${MITK_CMAKE_RUNTIME_OUTPUT_DIRECTORY}" "${MITK_CMAKE_RUNTIME_OUTPUT_DIRECTORY}/plugins" "${MITK_CMAKE_LIBRARY_OUTPUT_DIRECTORY}" "${MITK_CMAKE_LIBRARY_OUTPUT_DIRECTORY}/plugins" ) if(MITK_EXTERNAL_PROJECT_PREFIX) list(APPEND _dir_candidates "${MITK_EXTERNAL_PROJECT_PREFIX}/bin" "${MITK_EXTERNAL_PROJECT_PREFIX}/lib" ) endif() # Determine the Qt5 library installation prefix set(_qmake_location ) if(MITK_USE_Qt5 AND TARGET ${Qt5Core_QMAKE_EXECUTABLE}) get_property(_qmake_location TARGET ${Qt5Core_QMAKE_EXECUTABLE} PROPERTY IMPORT_LOCATION) endif() if(_qmake_location) if(NOT _qt_install_libs) if(WIN32) execute_process(COMMAND ${_qmake_location} -query QT_INSTALL_BINS OUTPUT_VARIABLE _qt_install_libs OUTPUT_STRIP_TRAILING_WHITESPACE) else() execute_process(COMMAND ${_qmake_location} -query QT_INSTALL_LIBS OUTPUT_VARIABLE _qt_install_libs OUTPUT_STRIP_TRAILING_WHITESPACE) endif() file(TO_CMAKE_PATH "${_qt_install_libs}" _qt_install_libs) set(_qt_install_libs ${_qt_install_libs} CACHE INTERNAL "Qt library installation prefix" FORCE) endif() if(_qt_install_libs) list(APPEND _dir_candidates ${_qt_install_libs}) endif() elseif(MITK_USE_Qt5) message(WARNING "The qmake executable could not be found.") endif() get_property(_additional_paths GLOBAL PROPERTY MITK_ADDITIONAL_LIBRARY_SEARCH_PATHS) if(TARGET OpenSSL::SSL) if(GLS_DEBUG) get_target_property(_openssl_location OpenSSL::SSL IMPORTED_LOCATION_DEBUG) else() get_target_property(_openssl_location OpenSSL::SSL IMPORTED_LOCATION_RELEASE) endif() if(_openssl_location) get_filename_component(_openssl_location ${_openssl_location} DIRECTORY) set(_openssl_location "${_openssl_location}/../../bin") if(EXISTS ${_openssl_location}) get_filename_component(_openssl_location ${_openssl_location} ABSOLUTE) list(APPEND _dir_candidates ${_openssl_location}) endif() endif() endif() if(MITK_USE_HDF5) FIND_PACKAGE(HDF5 COMPONENTS C HL NO_MODULE REQUIRED shared) get_target_property(_location hdf5-shared LOCATION) get_filename_component(_location ${_location} PATH) list(APPEND _additional_paths ${_location}) # This is a work-around. The hdf5-config.cmake file is not robust enough # to be included several times via find_pakcage calls. set(HDF5_LIBRARIES ${HDF5_LIBRARIES} PARENT_SCOPE) endif() if(MITK_USE_Vigra) # we cannot use _find_package(Vigra) here because the vigra-config.cmake file # always includes the target-exports files without using an include guard. This # would lead to errors when another find_package(Vigra) call is processed. The # (bad) assumption here is that for the time being, only the Classification module # is using Vigra. if(UNIX) list(APPEND _additional_paths ${Vigra_DIR}/lib) else() list(APPEND _additional_paths ${Vigra_DIR}/bin) endif() endif() if(_additional_paths) list(APPEND _dir_candidates ${_additional_paths}) endif() # The code below is sub-optimal. It makes assumptions about # the structure of the build directories, pointed to by # the *_DIR variables. Instead, we should rely on package # specific "LIBRARY_DIRS" variables, if they exist. if(WIN32) list(APPEND _dir_candidates "${ITK_DIR}/bin") endif() if(MITK_USE_MatchPoint) if(WIN32) list(APPEND _dir_candidates "${MatchPoint_DIR}/bin") else() list(APPEND _dir_candidates "${MatchPoint_DIR}/lib") endif() endif() # If OpenCV is built within the MITK superbuild set the binary directory # according to the lib path provided by OpenCV. # In the case where an external OpenCV is provided use the binary directory # of this OpenCV directory if(MITK_USE_OpenCV) if(WIN32) if (EXISTS ${OpenCV_LIB_PATH}) list(APPEND _dir_candidates "${OpenCV_LIB_PATH}/../bin") # OpenCV is built in superbuild else() list(APPEND _dir_candidates "${OpenCV_DIR}/bin") # External OpenCV build is used endif() endif() endif() + if(MITK_USE_OpenMesh) + if(WIN32) + list(APPEND _dir_candidates "${MITK_EXTERNAL_PROJECT_PREFIX}") + endif() + endif() + if(MITK_USE_Python3) list(APPEND _dir_candidates "${CTK_DIR}/CMakeExternals/Install/bin") get_filename_component(_python_dir "${Python3_EXECUTABLE}" DIRECTORY) list(APPEND _dir_candidates "${_python_dir}") endif() if(MITK_USE_TOF_PMDO3 OR MITK_USE_TOF_PMDCAMCUBE OR MITK_USE_TOF_PMDCAMBOARD) list(APPEND _dir_candidates "${MITK_PMD_SDK_DIR}/plugins" "${MITK_PMD_SDK_DIR}/bin") endif() if(MITK_USE_CTK) list(APPEND _dir_candidates "${CTK_LIBRARY_DIRS}") foreach(_ctk_library ${CTK_LIBRARIES}) if(${_ctk_library}_LIBRARY_DIRS) list(APPEND _dir_candidates "${${_ctk_library}_LIBRARY_DIRS}") endif() endforeach() endif() if(MITK_USE_BLUEBERRY) if(DEFINED CTK_PLUGIN_RUNTIME_OUTPUT_DIRECTORY) if(IS_ABSOLUTE "${CTK_PLUGIN_RUNTIME_OUTPUT_DIRECTORY}") list(APPEND _dir_candidates "${CTK_PLUGIN_RUNTIME_OUTPUT_DIRECTORY}") else() list(APPEND _dir_candidates "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/${CTK_PLUGIN_RUNTIME_OUTPUT_DIRECTORY}") endif() endif() endif() if(MITK_LIBRARY_DIRS) list(APPEND _dir_candidates ${MITK_LIBRARY_DIRS}) endif() ################################################################### #get the search paths added by the mitkFunctionAddLibrarySearchPath file(GLOB _additional_path_info_files "${MITK_SUPERBUILD_BINARY_DIR}/MITK-AdditionalLibPaths/*.cmake") foreach(_additional_path_info_file ${_additional_path_info_files}) get_filename_component(_additional_info_name ${_additional_path_info_file} NAME_WE) include(${_additional_path_info_file}) if(GLS_DEBUG) list(APPEND _dir_candidates ${${_additional_info_name}_ADDITIONAL_DEBUG_LIBRARY_SEARCH_PATHS}) elseif(GLS_MINSIZEREL) list(APPEND _dir_candidates ${${_additional_info_name}_ADDITIONAL_MINSIZEREL_LIBRARY_SEARCH_PATHS}) elseif(GLS_RELWITHDEBINFO) list(APPEND _dir_candidates ${${_additional_info_name}_ADDITIONAL_RELWITHDEBINFO_LIBRARY_SEARCH_PATHS}) else() #Release list(APPEND _dir_candidates ${${_additional_info_name}_ADDITIONAL_RELEASE_LIBRARY_SEARCH_PATHS}) endif() endforeach(_additional_path_info_file ${_additional_path_info_files}) ############################################### #sanitize all candidates and compile final list list(REMOVE_DUPLICATES _dir_candidates) set(_search_dirs ) foreach(_dir ${_dir_candidates}) if(EXISTS "${_dir}/${intermediate_dir}") list(APPEND _search_dirs "${_dir}/${intermediate_dir}") else() list(APPEND _search_dirs "${_dir}") endif() endforeach() # Special handling for "internal" search dirs. The intermediate directory # might not have been created yet, so we can't check for its existence. # Hence we just add it for Windows without checking. set(_internal_search_dirs "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/plugins") if(WIN32) foreach(_dir ${_internal_search_dirs}) set(_search_dirs "${_dir}/${intermediate_dir}" ${_search_dirs}) endforeach() else() set(_search_dirs ${_internal_search_dirs} ${_search_dirs}) endif() list(REMOVE_DUPLICATES _search_dirs) set(${search_path} ${_search_dirs} PARENT_SCOPE) endfunction() diff --git a/CMakeExternals/OpenMesh.cmake b/CMakeExternals/OpenMesh.cmake index f0636d75c5..4e02342298 100644 --- a/CMakeExternals/OpenMesh.cmake +++ b/CMakeExternals/OpenMesh.cmake @@ -1,50 +1,50 @@ #----------------------------------------------------------------------------- # OpenMesh #----------------------------------------------------------------------------- if(MITK_USE_OpenMesh) # Sanity checks if(DEFINED OpenMesh_DIR AND NOT EXISTS "${OpenMesh_DIR}") message(FATAL_ERROR "OpenMesh_DIR variable is defined but corresponds to non-existing directory") endif() set(proj OpenMesh) set(proj_DEPENDENCIES ) set(OpenMesh_DEPENDS ${proj}) if(NOT DEFINED OpenMesh_DIR) set(additional_args ) if(CTEST_USE_LAUNCHERS) list(APPEND additional_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} URL ${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/OpenMesh-8.1.tar.gz URL_MD5 9e1eb6feeca3882ab95f9fc97681a4da CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${additional_args} + CMAKE_CACHE_ARGS + ${ep_common_cache_args} -DBUILD_APPS:BOOL=OFF -DOPENMESH_BUILD_SHARED:BOOL=ON -DOPENMESH_DOCS:BOOL=OFF - CMAKE_CACHE_ARGS - ${ep_common_cache_args} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} DEPENDS ${proj_DEPENDENCIES} ) set(OpenMesh_DIR "${ep_prefix}") mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/Modules/Core/src/IO/mitkSurfaceStlIO.cpp b/Modules/Core/src/IO/mitkSurfaceStlIO.cpp index e64e9a1634..21aeb441e0 100644 --- a/Modules/Core/src/IO/mitkSurfaceStlIO.cpp +++ b/Modules/Core/src/IO/mitkSurfaceStlIO.cpp @@ -1,160 +1,160 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSurfaceStlIO.h" #include "mitkIOMimeTypes.h" #include "mitkLocaleSwitch.h" #include "mitkSurface.h" #include #include #include #include #include #include #include namespace mitk { - std::string SurfaceStlIO::OPTION_MERGE_POINTS() - { - static std::string s = "Merge points"; - return s; - } + // std::string SurfaceStlIO::OPTION_MERGE_POINTS() + // { + // static std::string s = "Merge points"; + // return s; + // } std::string SurfaceStlIO::OPTION_TAG_SOLIDS() { static std::string s = "Tag solids"; return s; } std::string SurfaceStlIO::OPTION_CLEAN() { static std::string s = "Clean poly data"; return s; } SurfaceStlIO::SurfaceStlIO() : SurfaceVtkIO(Surface::GetStaticNameOfClass(), IOMimeTypes::STEREOLITHOGRAPHY_MIMETYPE(), "Stereolithography") { Options defaultOptions; - defaultOptions[OPTION_MERGE_POINTS()] = us::Any(true); + // defaultOptions[OPTION_MERGE_POINTS()] = us::Any(true); defaultOptions[OPTION_TAG_SOLIDS()] = us::Any(false); defaultOptions[OPTION_CLEAN()] = us::Any(true); this->SetDefaultReaderOptions(defaultOptions); this->RegisterService(); } std::vector> SurfaceStlIO::DoRead() { LocaleSwitch localeSwitch("C"); Options options = this->GetReaderOptions(); mitk::Surface::Pointer output = mitk::Surface::New(); vtkSmartPointer stlReader = vtkSmartPointer::New(); stlReader->SetFileName(this->GetLocalFileName().c_str()); - bool mergePoints = true; + // bool mergePoints = true; bool tagSolids = false; bool cleanData = true; try { - mergePoints = us::any_cast(options[OPTION_MERGE_POINTS()]); + // mergePoints = us::any_cast(options[OPTION_MERGE_POINTS()]); tagSolids = us::any_cast(options[OPTION_TAG_SOLIDS()]); cleanData = us::any_cast(options[OPTION_CLEAN()]); } catch (const us::BadAnyCastException &e) { MITK_WARN << "Unexpected error: " << e.what(); } - stlReader->SetMerging(mergePoints); + // stlReader->SetMerging(mergePoints); stlReader->SetScalarTags(tagSolids); vtkSmartPointer normalsGenerator = vtkSmartPointer::New(); normalsGenerator->SetInputConnection(stlReader->GetOutputPort()); vtkSmartPointer algo = normalsGenerator; if (cleanData) { vtkSmartPointer cleanPolyDataFilter = vtkSmartPointer::New(); cleanPolyDataFilter->SetInputConnection(normalsGenerator->GetOutputPort()); cleanPolyDataFilter->PieceInvariantOff(); cleanPolyDataFilter->ConvertLinesToPointsOff(); cleanPolyDataFilter->ConvertPolysToLinesOff(); cleanPolyDataFilter->ConvertStripsToPolysOff(); - if (mergePoints) - { + // if (mergePoints) + // { cleanPolyDataFilter->PointMergingOn(); - } + // } algo = cleanPolyDataFilter; } algo->Update(); if (algo->GetOutput() != nullptr) { vtkSmartPointer surfaceWithNormals = algo->GetOutput(); output->SetVtkPolyData(surfaceWithNormals); } std::vector result; result.push_back(output.GetPointer()); return result; } void SurfaceStlIO::Write() { LocaleSwitch localeSwitch("C"); ValidateOutputLocation(); const auto *input = dynamic_cast(this->GetInput()); const unsigned int timesteps = input->GetTimeGeometry()->CountTimeSteps(); for (unsigned int t = 0; t < timesteps; ++t) { std::string fileName; vtkSmartPointer polyData = this->GetPolyData(t, fileName); vtkSmartPointer triangleFilter = vtkSmartPointer::New(); triangleFilter->SetInputData(polyData); vtkSmartPointer writer = vtkSmartPointer::New(); writer->SetInputConnection(triangleFilter->GetOutputPort()); // The vtk stl writer cannot write to streams LocalFile localFile(this); writer->SetFileName(localFile.GetFileName().c_str()); if (writer->Write() == 0 || writer->GetErrorCode() != 0) { mitkThrow() << "Error during surface writing" << (writer->GetErrorCode() ? std::string(": ") + vtkErrorCode::GetStringFromErrorCode(writer->GetErrorCode()) : std::string()); } if (this->GetOutputStream() && input->GetTimeGeometry()->CountTimeSteps() > 1) { MITK_WARN << "Writing multiple time-steps to output streams is not supported. " << "Only the first time-step will be written"; break; } } } SurfaceStlIO *SurfaceStlIO::IOClone() const { return new SurfaceStlIO(*this); } } diff --git a/Modules/Core/src/IO/mitkSurfaceStlIO.h b/Modules/Core/src/IO/mitkSurfaceStlIO.h index ffe36e1e60..2e95e8ab2e 100644 --- a/Modules/Core/src/IO/mitkSurfaceStlIO.h +++ b/Modules/Core/src/IO/mitkSurfaceStlIO.h @@ -1,45 +1,47 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _MITK_SURFACE_STL_IO_H_ #define _MITK_SURFACE_STL_IO_H_ #include "mitkSurfaceVtkIO.h" namespace mitk { class SurfaceStlIO : public mitk::SurfaceVtkIO { public: SurfaceStlIO(); // -------------- AbstractFileReader ------------- using AbstractFileReader::Read; // -------------- AbstractFileWriter ------------- void Write() override; protected: std::vector> DoRead() override; private: SurfaceStlIO *IOClone() const override; - static std::string OPTION_MERGE_POINTS(); + // vtkSTLReader crashes with this option + // static std::string OPTION_MERGE_POINTS(); + static std::string OPTION_TAG_SOLIDS(); static std::string OPTION_CLEAN(); }; } #endif //_MITK_SURFACE_STL_IO_H_ diff --git a/Modules/Remeshing/CMakeLists.txt b/Modules/Remeshing/CMakeLists.txt index 3e62ed8d2f..2697e18603 100644 --- a/Modules/Remeshing/CMakeLists.txt +++ b/Modules/Remeshing/CMakeLists.txt @@ -1,4 +1,9 @@ mitk_create_module( DEPENDS MitkCore - PACKAGE_DEPENDS OpenMesh|OpenMeshCore + PACKAGE_DEPENDS OpenMesh|OpenMeshTools ) + +if(TARGET ${MODULE_TARGET}) + target_link_libraries(${MODULE_TARGET} PRIVATE OpenMeshTools) + target_compile_definitions(${MODULE_TARGET} PRIVATE -D_USE_MATH_DEFINES) +endif() diff --git a/Modules/Remeshing/include/mitkRemeshing.h b/Modules/Remeshing/include/mitkRemeshing.h index 74336d8e14..ef068d6424 100644 --- a/Modules/Remeshing/include/mitkRemeshing.h +++ b/Modules/Remeshing/include/mitkRemeshing.h @@ -1,27 +1,39 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkRemeshing_h #define mitkRemeshing_h #include #include namespace mitk { namespace Remeshing { - MITKREMESHING_EXPORT Surface::Pointer Decimate(Surface::ConstPointer input); + /** \brief Reduce the number of vertices of an mitk::Surface. + * + * The decimation is applied separately to all time steps of the input surface. + * The meshes of the resulting surface are guaranteed to consist of triangles only. + * + * \param[in] input Input surface + * \param[in] percent Relative number of vertices after decimation [0, 1] + * \param[in] calculateNormals Calculate normals after decimation (\c true by default) + * \param[in] flipNormals Flip calculated normals (\c false by default) + * + * \return Decimated surface + */ + MITKREMESHING_EXPORT Surface::Pointer Decimate(const Surface* input, double percent, bool calculateNormals = true, bool flipNormals = false); } } #endif diff --git a/Modules/Remeshing/src/mitkRemeshing.cpp b/Modules/Remeshing/src/mitkRemeshing.cpp index 915be3ece4..dd531cfaaf 100644 --- a/Modules/Remeshing/src/mitkRemeshing.cpp +++ b/Modules/Remeshing/src/mitkRemeshing.cpp @@ -1,18 +1,185 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include -mitk::Surface::Pointer mitk::Remeshing::Decimate(Surface::ConstPointer input) +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using Mesh = OpenMesh::TriMesh_ArrayKernelT; + +namespace { - return input->Clone(); + bool IsValidPolyData(vtkPolyData* polyData) + { + return nullptr != polyData && 0 < polyData->GetNumberOfPoints() && + (0 < polyData->GetNumberOfPolys() || 0 < polyData->GetNumberOfStrips()); + } + + vtkSmartPointer TriangulatePolyData(vtkPolyData* polyData) + { + auto triangleFilter = vtkSmartPointer::New(); + triangleFilter->SetInputData(polyData); + triangleFilter->PassVertsOff(); + triangleFilter->PassLinesOff(); + + triangleFilter->Update(); + + return triangleFilter->GetOutput(); + } + + vtkSmartPointer CalculateNormals(vtkPolyData* polyData, bool flipNormals) + { + auto polyDataNormals = vtkSmartPointer::New(); + polyDataNormals->SetInputData(polyData); + + if (flipNormals) + polyDataNormals->FlipNormalsOn(); + + polyDataNormals->Update(); + + return polyDataNormals->GetOutput(); + } + + Mesh ConvertPolyDataToMesh(vtkPolyData* polyData) + { + Mesh mesh; + + auto* points = polyData->GetPoints(); + const auto numPoints = points->GetNumberOfPoints(); + + for (std::remove_const_t i = 0; i < numPoints; ++i) + mesh.add_vertex(Mesh::Point(points->GetPoint(i))); + + auto* polys = polyData->GetPolys(); + const auto numPolys = polys->GetNumberOfCells(); + + auto ids = vtkSmartPointer::New(); + std::array vertexHandles; + + for (std::remove_const_t i = 0; i < numPolys; ++i) + { + polys->GetCellAtId(i, ids); + + vertexHandles[0] = Mesh::VertexHandle(static_cast(ids->GetId(0))); + vertexHandles[1] = Mesh::VertexHandle(static_cast(ids->GetId(1))); + vertexHandles[2] = Mesh::VertexHandle(static_cast(ids->GetId(2))); + + mesh.add_face(vertexHandles.data(), 3); + } + + return mesh; + } + + vtkSmartPointer ConvertMeshToPolyData(const Mesh& mesh) + { + auto polyData = vtkSmartPointer::New(); + + const auto numVertices = mesh.n_vertices(); + auto points = vtkSmartPointer::New(); + points->SetNumberOfPoints(numVertices); + + for (std::remove_const_t i = 0; i < numVertices; ++i) + points->SetPoint(i, mesh.point(Mesh::VertexHandle(static_cast(i))).data()); + + polyData->SetPoints(points); + + const auto numFaces = mesh.n_faces(); + auto polys = vtkSmartPointer::New(); + + auto ids = vtkSmartPointer::New(); + ids->SetNumberOfIds(3); + Mesh::CFVIter iter; + + for (std::remove_const_t i = 0; i < numFaces; ++i) + { + iter = mesh.cfv_iter(Mesh::FaceHandle(static_cast(i))); + + ids->SetId(0, (iter++)->idx()); + ids->SetId(1, (iter++)->idx()); + ids->SetId(2, iter->idx()); + + polys->InsertNextCell(ids); + } + + polyData->SetPolys(polys); + + return polyData; + } + + mitk::Surface::Pointer ProcessEachTimeStep(const mitk::Surface* input, bool calculateNormals, bool flipNormals, const std::function& ProcessMesh) + { + if (nullptr == input || !input->IsInitialized()) + return nullptr; + + auto output = mitk::Surface::New(); + const auto numTimeSteps = input->GetTimeSteps(); + + for (std::remove_const_t t = 0; t < numTimeSteps; ++t) + { + vtkSmartPointer polyData = input->GetVtkPolyData(t); + + if (IsValidPolyData(polyData)) + { + polyData = TriangulatePolyData(polyData); + + if (IsValidPolyData(polyData)) + { + auto mesh = ConvertPolyDataToMesh(polyData); + ProcessMesh(mesh); + polyData = ConvertMeshToPolyData(mesh); + + if (calculateNormals) + polyData = CalculateNormals(polyData, flipNormals); + + output->SetVtkPolyData(polyData, t); + continue; + } + } + + output->SetVtkPolyData(nullptr, t); + } + + return output; + } +} + +mitk::Surface::Pointer mitk::Remeshing::Decimate(const Surface* input, double percent, bool calculateNormals, bool flipNormals) +{ + return ProcessEachTimeStep(input, calculateNormals, flipNormals, [percent](Mesh& mesh) { + using Decimater = OpenMesh::Decimater::DecimaterT; + using HModQuadric = OpenMesh::Decimater::ModQuadricT::Handle; + + Decimater decimater(mesh); + + HModQuadric hModQuadric; + decimater.add(hModQuadric); + decimater.module(hModQuadric).unset_max_err(); + + decimater.initialize(); + decimater.decimate_to(mesh.n_vertices() * std::max(0.0, std::min(percent, 1.0))); + + mesh.garbage_collection(); + }); } diff --git a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp index 7f084beb61..9ed0a2ef58 100644 --- a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp +++ b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp @@ -1,110 +1,123 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkRemeshingView.h" #include #include #include #include #include #include #include #include const std::string QmitkRemeshingView::VIEW_ID = "org.mitk.views.remeshing"; QmitkRemeshingView::QmitkRemeshingView() : m_Controls(new Ui::QmitkRemeshingViewControls) { } QmitkRemeshingView::~QmitkRemeshingView() { } void QmitkRemeshingView::CreateQtPartControl(QWidget* parent) { m_Controls->setupUi(parent); m_Controls->decimatePushButton->setIcon(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/Remeshing/RemeshingIcon.svg"))); m_Controls->selectionWidget->SetDataStorage(this->GetDataStorage()); m_Controls->selectionWidget->SetSelectionIsOptional(true); m_Controls->selectionWidget->SetEmptyInfo(QStringLiteral("Select a surface")); m_Controls->selectionWidget->SetAutoSelectNewNodes(true); m_Controls->selectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateOr::New( mitk::NodePredicateProperty::New("helper object"), mitk::NodePredicateProperty::New("hidden object"))))); connect(m_Controls->selectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkRemeshingView::OnSurfaceChanged); connect(m_Controls->polygonCountSlider, SIGNAL(valueChanged(int)), this, SLOT(OnPolygonCountChanged(int))); connect(m_Controls->polygonCountSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnPolygonCountChanged(int))); + connect(m_Controls->calculateNormalsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnCalculateNormalsChanged(int))); connect(m_Controls->decimatePushButton, SIGNAL(clicked()), this, SLOT(OnDecimateButtonClicked())); this->OnSurfaceChanged(m_Controls->selectionWidget->GetSelectedNodes()); } void QmitkRemeshingView::OnSurfaceChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes) { this->EnableWidgets(!nodes.empty() && nodes.front().IsNotNull()); } void QmitkRemeshingView::OnPolygonCountChanged(int polygonCount) { if (polygonCount != m_Controls->polygonCountSlider->value()) m_Controls->polygonCountSlider->setValue(polygonCount); if (polygonCount != m_Controls->polygonCountSpinBox->value()) m_Controls->polygonCountSpinBox->setValue(polygonCount); } +void QmitkRemeshingView::OnCalculateNormalsChanged(int checkState) +{ + m_Controls->flipNormalsCheckBox->setEnabled(Qt::Unchecked != checkState); +} + void QmitkRemeshingView::OnDecimateButtonClicked() { mitk::DataNode::Pointer selectedNode = m_Controls->selectionWidget->GetSelectedNode(); mitk::Surface::ConstPointer input = static_cast(selectedNode->GetData()); mitk::Surface::Pointer output; try { - output = mitk::Remeshing::Decimate(input); + output = mitk::Remeshing::Decimate(input, + 0.01 * m_Controls->polygonCountSpinBox->value(), + m_Controls->calculateNormalsCheckBox->isChecked(), + m_Controls->flipNormalsCheckBox->isChecked()); } catch(const mitk::Exception& exception) { MITK_ERROR << exception.GetDescription(); return; } + if (output.IsNull()) + return; + auto newNode = mitk::DataNode::New(); - newNode->SetName(QString("%1 (%2%)").arg(selectedNode->GetName().c_str()).arg(m_Controls->polygonCountSpinBox->value()).toStdString()); + newNode->SetName(QString("%1 (decimated)").arg(selectedNode->GetName().c_str()).toStdString()); newNode->SetData(output); this->GetDataStorage()->Add(newNode, selectedNode); } void QmitkRemeshingView::EnableWidgets(bool enable) { m_Controls->polygonCountSlider->setEnabled(enable); m_Controls->polygonCountSpinBox->setEnabled(enable); - m_Controls->polygonDistributionComboBox->setEnabled(enable); + m_Controls->calculateNormalsCheckBox->setEnabled(enable); + m_Controls->flipNormalsCheckBox->setEnabled(enable && m_Controls->calculateNormalsCheckBox->isChecked()); m_Controls->decimatePushButton->setEnabled(enable); } void QmitkRemeshingView::SetFocus() { m_Controls->selectionWidget->setFocus(); } diff --git a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.h b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.h index a2cc446cd7..679f1679ec 100644 --- a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.h +++ b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.h @@ -1,48 +1,49 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkRemeshingView_h #define QmitkRemeshingView_h #include #include namespace Ui { class QmitkRemeshingViewControls; } class QmitkRemeshingView : public QmitkAbstractView { Q_OBJECT public: static const std::string VIEW_ID; QmitkRemeshingView(); ~QmitkRemeshingView() override; void CreateQtPartControl(QWidget* parent) override; void SetFocus() override; private slots: void OnSurfaceChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes); void OnPolygonCountChanged(int polygonCount); + void OnCalculateNormalsChanged(int checkState); void OnDecimateButtonClicked(); private: void EnableWidgets(bool enable); Ui::QmitkRemeshingViewControls* m_Controls; }; #endif diff --git a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingViewControls.ui b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingViewControls.ui index bca912609c..d06d2ff7fc 100644 --- a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingViewControls.ui +++ b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingViewControls.ui @@ -1,182 +1,172 @@ QmitkRemeshingViewControls true 0 0 253 573 Remeshing + + + + + 0 + 0 + + + + Surface + + + + + + + Flip normals + + + + + + + + 0 + 0 + + + + Polygon count + + + 0 0 1 100 10 - 100 + 50 Qt::Horizontal + + + + + 0 + 40 + + + + % 1 100 1 - 100 + 50 - - - - - 0 - 0 - - + + - Polygon distribution - - - - - - - - 0 - 0 - + Calculate normals - - Surface + + true - - - - - 0 - 40 - - - - - - - - - 0 - 0 - - - - Polygon count - - - - - - - - Adaptive - - - - - Uniform - - - - true Decimate :/Remeshing/RemeshingIcon.svg:/Remeshing/RemeshingIcon.svg 24 24 Qt::Vertical 20 40 QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
polygonCountSpinBox decimatePushButton