diff --git a/Applications/PluginGenerator/ProjectTemplate/CMakeExternals/MITK.cmake b/Applications/PluginGenerator/ProjectTemplate/CMakeExternals/MITK.cmake index 60fd693999..43155e16cd 100644 --- a/Applications/PluginGenerator/ProjectTemplate/CMakeExternals/MITK.cmake +++ b/Applications/PluginGenerator/ProjectTemplate/CMakeExternals/MITK.cmake @@ -1,211 +1,211 @@ #----------------------------------------------------------------------------- # MITK #----------------------------------------------------------------------------- set(MITK_DEPENDS) set(proj_DEPENDENCIES) set(proj MITK) if(NOT MITK_DIR) #----------------------------------------------------------------------------- # Create CMake options to customize the MITK build #----------------------------------------------------------------------------- option(MITK_USE_SUPERBUILD "Use superbuild for MITK" ON) option(MITK_USE_BLUEBERRY "Build the BlueBerry platform in MITK" ON) option(MITK_BUILD_EXAMPLES "Build the MITK examples" OFF) option(MITK_BUILD_ALL_PLUGINS "Build all MITK plugins" OFF) option(MITK_BUILD_TESTING "Build the MITK unit tests" OFF) - option(MITK_USE_OpenMesh "" OFF) + option(MITK_USE_ACVD "Use Approximated Centroidal Voronoi Diagrams" OFF) option(MITK_USE_CTK "Use CTK in MITK" ${MITK_USE_BLUEBERRY}) option(MITK_USE_DCMTK "Use DCMTK in MITK" ON) option(MITK_USE_Qt5 "Use Qt 5 library in MITK" ON) option(MITK_USE_DCMQI "Use dcmqi in MITK" OFF) option(MITK_USE_OpenCV "Use Intel's OpenCV library" OFF) option(MITK_USE_Python3 "Enable Python wrapping in MITK" OFF) if(MITK_USE_BLUEBERRY AND NOT MITK_USE_CTK) message("Forcing MITK_USE_CTK to ON because of MITK_USE_BLUEBERRY") set(MITK_USE_CTK ON CACHE BOOL "Use CTK in MITK" FORCE) endif() if(MITK_USE_CTK AND NOT MITK_USE_Qt5) message("Forcing MITK_USE_Qt5 to ON because of MITK_USE_CTK") set(MITK_USE_Qt5 ON CACHE BOOL "Use Qt 5 library in MITK" FORCE) endif() set(MITK_USE_CableSwig ${MITK_USE_Python3}) set(MITK_USE_GDCM 1) set(MITK_USE_ITK 1) set(MITK_USE_VTK 1) mark_as_advanced(MITK_USE_SUPERBUILD MITK_BUILD_ALL_PLUGINS MITK_BUILD_TESTING ) set(mitk_cmake_boolean_args MITK_USE_SUPERBUILD MITK_USE_BLUEBERRY MITK_BUILD_EXAMPLES MITK_BUILD_ALL_PLUGINS - MITK_USE_OpenMesh + MITK_USE_ACVD MITK_USE_CTK MITK_USE_DCMTK MITK_USE_Qt5 MITK_USE_DCMQI MITK_USE_OpenCV MITK_USE_Python3 ) if(MITK_USE_Qt5) # Look for Qt at the superbuild level, to catch missing Qt libs early find_package(Qt5Widgets REQUIRED) endif() set(additional_mitk_cmakevars ) # Configure the set of default pixel types set(MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES "int, unsigned int, short, unsigned short, char, unsigned char" CACHE STRING "List of integral pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES "double, float" CACHE STRING "List of floating pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES "" CACHE STRING "List of composite pixel types used in AccessByItk and InstantiateAccessFunction macros") set(MITK_ACCESSBYITK_DIMENSIONS "2,3" CACHE STRING "List of dimensions used in AccessByItk and InstantiateAccessFunction macros") foreach(_arg MITK_ACCESSBYITK_INTEGRAL_PIXEL_TYPES MITK_ACCESSBYITK_FLOATING_PIXEL_TYPES MITK_ACCESSBYITK_COMPOSITE_PIXEL_TYPES MITK_ACCESSBYITK_DIMENSIONS) mark_as_advanced(${_arg}) list(APPEND additional_mitk_cmakevars "-D${_arg}:STRING=${${_arg}}") endforeach() #----------------------------------------------------------------------------- # Create options to inject pre-build dependencies #----------------------------------------------------------------------------- - foreach(proj CTK DCMTK DCMQI GDCM VTK OpenMesh ITK OpenCV CableSwig) + foreach(proj CTK DCMTK DCMQI GDCM VTK ACVD ITK OpenCV CableSwig) if(MITK_USE_${proj}) set(MITK_${proj}_DIR "${${proj}_DIR}" CACHE PATH "Path to ${proj} build directory") mark_as_advanced(MITK_${proj}_DIR) if(MITK_${proj}_DIR) list(APPEND additional_mitk_cmakevars "-D${proj}_DIR:PATH=${MITK_${proj}_DIR}") endif() endif() endforeach() set(MITK_BOOST_ROOT "${BOOST_ROOT}" CACHE PATH "Path to Boost directory") mark_as_advanced(MITK_BOOST_ROOT) if(MITK_BOOST_ROOT) list(APPEND additional_mitk_cmakevars "-DBOOST_ROOT:PATH=${MITK_BOOST_ROOT}") endif() set(MITK_SOURCE_DIR "" CACHE PATH "MITK source code location. If empty, MITK will be cloned from MITK_GIT_REPOSITORY") set(MITK_GIT_REPOSITORY "https://phabricator.mitk.org/source/mitk.git" CACHE STRING "The git repository for cloning MITK") set(MITK_GIT_TAG "origin/master" CACHE STRING "The git tag/hash to be used when cloning from MITK_GIT_REPOSITORY") mark_as_advanced(MITK_SOURCE_DIR MITK_GIT_REPOSITORY MITK_GIT_TAG) #----------------------------------------------------------------------------- # Create the final variable containing superbuild boolean args #----------------------------------------------------------------------------- set(mitk_boolean_args) foreach(mitk_cmake_arg ${mitk_cmake_boolean_args}) list(APPEND mitk_boolean_args -D${mitk_cmake_arg}:BOOL=${${mitk_cmake_arg}}) endforeach() #----------------------------------------------------------------------------- # Additional MITK CMake variables #----------------------------------------------------------------------------- if(MITK_USE_Qt5) list(APPEND additional_mitk_cmakevars "-DCMAKE_PREFIX_PATH:PATH=${CMAKE_PREFIX_PATH}") endif() if(MITK_USE_CTK) list(APPEND additional_mitk_cmakevars "-DGIT_EXECUTABLE:FILEPATH=${GIT_EXECUTABLE}") endif() if(MITK_INITIAL_CACHE_FILE) list(APPEND additional_mitk_cmakevars "-DMITK_INITIAL_CACHE_FILE:INTERNAL=${MITK_INITIAL_CACHE_FILE}") endif() if(MITK_USE_SUPERBUILD) set(MITK_BINARY_DIR ${proj}-superbuild) else() set(MITK_BINARY_DIR ${proj}-build) endif() set(proj_DEPENDENCIES) set(MITK_DEPENDS ${proj}) # Configure the MITK souce code location if(NOT MITK_SOURCE_DIR) set(mitk_source_location SOURCE_DIR ${CMAKE_BINARY_DIR}/${proj} GIT_REPOSITORY ${MITK_GIT_REPOSITORY} GIT_TAG ${MITK_GIT_TAG} ) else() set(mitk_source_location SOURCE_DIR ${MITK_SOURCE_DIR} ) endif() ExternalProject_Add(${proj} ${mitk_source_location} BINARY_DIR ${MITK_BINARY_DIR} PREFIX ${proj}${ep_suffix} INSTALL_COMMAND "" CMAKE_GENERATOR ${gen} CMAKE_ARGS ${ep_common_args} ${mitk_boolean_args} ${additional_mitk_cmakevars} -DBUILD_SHARED_LIBS:BOOL=ON -DBUILD_TESTING:BOOL=${MITK_BUILD_TESTING} CMAKE_CACHE_ARGS ${ep_common_cache_args} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} DEPENDS ${proj_DEPENDENCIES} ) if(MITK_USE_SUPERBUILD) set(MITK_DIR "${CMAKE_CURRENT_BINARY_DIR}/${MITK_BINARY_DIR}/MITK-build") else() set(MITK_DIR "${CMAKE_CURRENT_BINARY_DIR}/${MITK_BINARY_DIR}") endif() else() # The project is provided using MITK_DIR, nevertheless since other # projects may depend on MITK, let's add an 'empty' one MacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") # Further, do some sanity checks in the case of a pre-built MITK set(my_itk_dir ${ITK_DIR}) set(my_vtk_dir ${VTK_DIR}) find_package(MITK REQUIRED) if(my_itk_dir AND NOT my_itk_dir STREQUAL ${ITK_DIR}) message(FATAL_ERROR "ITK packages do not match:\n ${MY_PROJECT_NAME}: ${my_itk_dir}\n MITK: ${ITK_DIR}") endif() if(my_vtk_dir AND NOT my_vtk_dir STREQUAL ${VTK_DIR}) message(FATAL_ERROR "VTK packages do not match:\n ${MY_PROJECT_NAME}: ${my_vtk_dir}\n MITK: ${VTK_DIR}") endif() endif() diff --git a/CMake/BuildConfigurations/Default.cmake b/CMake/BuildConfigurations/Default.cmake index ab92d73684..4faffa7f12 100644 --- a/CMake/BuildConfigurations/Default.cmake +++ b/CMake/BuildConfigurations/Default.cmake @@ -1,23 +1,23 @@ set(MITK_CONFIG_PACKAGES - OpenMesh + ACVD Qt5 BLUEBERRY ) set(MITK_CONFIG_PLUGINS org.mitk.gui.qt.mitkworkbench.intro org.mitk.gui.qt.datamanager org.mitk.gui.qt.stdmultiwidgeteditor org.mitk.gui.qt.dicombrowser org.mitk.gui.qt.imagenavigator org.mitk.gui.qt.measurementtoolbox org.mitk.gui.qt.properties org.mitk.gui.qt.segmentation org.mitk.gui.qt.volumevisualization org.mitk.planarfigure org.mitk.gui.qt.moviemaker org.mitk.gui.qt.pointsetinteraction org.mitk.gui.qt.remeshing org.mitk.gui.qt.viewnavigator org.mitk.gui.qt.imagecropper ) diff --git a/CMake/BuildConfigurations/mitkNavigationModules.cmake b/CMake/BuildConfigurations/mitkNavigationModules.cmake index 5836dc8d04..d6881034ab 100644 --- a/CMake/BuildConfigurations/mitkNavigationModules.cmake +++ b/CMake/BuildConfigurations/mitkNavigationModules.cmake @@ -1,35 +1,35 @@ message(STATUS "Configuring MITK Navigation Modules Build") set(MITK_CONFIG_PACKAGES - OpenMesh + ACVD Qt5 BLUEBERRY ) # Enable open cv and open igt link, which is a necessary configuration set(MITK_USE_OpenCV ON CACHE BOOL "MITK Use OpenCV Library" FORCE) set(MITK_USE_OpenIGTLink ON CACHE BOOL "MITK Use OpenIGTLink Library" FORCE) # Enable default plugins and the navigation modules set(MITK_CONFIG_PLUGINS org.mitk.gui.qt.datamanager org.mitk.gui.qt.stdmultiwidgeteditor org.mitk.gui.qt.dicombrowser org.mitk.gui.qt.imagenavigator org.mitk.gui.qt.measurementtoolbox org.mitk.gui.qt.properties org.mitk.gui.qt.segmentation org.mitk.gui.qt.volumevisualization org.mitk.planarfigure org.mitk.gui.qt.moviemaker org.mitk.gui.qt.pointsetinteraction org.mitk.gui.qt.registration org.mitk.gui.qt.remeshing org.mitk.gui.qt.viewnavigator org.mitk.gui.qt.imagecropper org.mitk.gui.qt.igttracking org.mitk.gui.qt.openigtlink org.mitk.gui.qt.ultrasound org.mitk.gui.qt.tofutil ) diff --git a/CMake/PackageDepends/MITK_ACVD_Config.cmake b/CMake/PackageDepends/MITK_ACVD_Config.cmake new file mode 100644 index 0000000000..0e5e98fee4 --- /dev/null +++ b/CMake/PackageDepends/MITK_ACVD_Config.cmake @@ -0,0 +1,12 @@ +foreach(acvd_component ${ACVD_REQUIRED_COMPONENTS_BY_MODULE}) + if(NOT acvd_component MATCHES "^vtk") + set(acvd_component "vtk${acvd_component}") + endif() + list(APPEND _acvd_required_components_by_module ${acvd_component}) +endforeach() + +find_package(ACVD COMPONENTS ${_acvd_required_components_by_module} REQUIRED) + +foreach(acvd_component ${_acvd_required_components_by_module}) + list(APPEND ALL_LIBRARIES ${acvd_component}) +endforeach() diff --git a/CMake/PackageDepends/MITK_OpenMesh_Config.cmake b/CMake/PackageDepends/MITK_OpenMesh_Config.cmake deleted file mode 100644 index 85d06a71dd..0000000000 --- a/CMake/PackageDepends/MITK_OpenMesh_Config.cmake +++ /dev/null @@ -1,7 +0,0 @@ -find_package(OpenMesh COMPONENTS ${OpenMesh_REQUIRED_COMPONENTS_BY_MODULE} REQUIRED) - -foreach(openmesh_component ${OpenMesh_REQUIRED_COMPONENTS_BY_MODULE}) - list(APPEND ALL_LIBRARIES "OpenMesh${openmesh_component}") -endforeach() - -set(ALL_COMPILE_DEFINITIONS -D_USE_MATH_DEFINES) diff --git a/CMake/mitkFunctionGetLibrarySearchPaths.cmake b/CMake/mitkFunctionGetLibrarySearchPaths.cmake index 21f2436c0e..86725d7193 100644 --- a/CMake/mitkFunctionGetLibrarySearchPaths.cmake +++ b/CMake/mitkFunctionGetLibrarySearchPaths.cmake @@ -1,219 +1,213 @@ #! 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/ACVD.cmake similarity index 62% rename from CMakeExternals/OpenMesh.cmake rename to CMakeExternals/ACVD.cmake index 4e02342298..5409265ccd 100644 --- a/CMakeExternals/OpenMesh.cmake +++ b/CMakeExternals/ACVD.cmake @@ -1,50 +1,50 @@ #----------------------------------------------------------------------------- -# OpenMesh +# ACVD #----------------------------------------------------------------------------- -if(MITK_USE_OpenMesh) +if(MITK_USE_ACVD) # 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") + if(DEFINED ACVD_DIR AND NOT EXISTS ${ACVD_DIR}) + message(FATAL_ERROR "ACVD_DIR variable is defined but corresponds to non-existing directory") endif() - set(proj OpenMesh) - set(proj_DEPENDENCIES ) - set(OpenMesh_DEPENDS ${proj}) + set(proj ACVD) + set(proj_DEPENDENCIES VTK) + set(ACVD_DEPENDS ${proj}) - if(NOT DEFINED OpenMesh_DIR) + if(NOT DEFINED ACVD_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 + GIT_REPOSITORY https://github.com/valette/ACVD.git + GIT_TAG e583e278 CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${additional_args} + -DUSE_MULTITHREADING:BOOL=ON + -DBUILD_EXAMPLES:BOOL=OFF + -DQt5_DIR:PATH=${Qt5_DIR} + -DVTK_DIR:PATH=${VTK_DIR} CMAKE_CACHE_ARGS ${ep_common_cache_args} - -DBUILD_APPS:BOOL=OFF - -DOPENMESH_BUILD_SHARED:BOOL=ON - -DOPENMESH_DOCS:BOOL=OFF CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} DEPENDS ${proj_DEPENDENCIES} ) - set(OpenMesh_DIR "${ep_prefix}") + set(ACVD_DIR ${ep_prefix}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/CMakeExternals/ExternalProjectList.cmake b/CMakeExternals/ExternalProjectList.cmake index afff18b5ec..f35f0a1975 100644 --- a/CMakeExternals/ExternalProjectList.cmake +++ b/CMakeExternals/ExternalProjectList.cmake @@ -1,33 +1,33 @@ mitkFunctionAddExternalProject(NAME Poco ON COMPONENTS Foundation Net Util XML Zip) mitkFunctionAddExternalProject(NAME DCMTK ON DOC "EXPERIMENTAL, superbuild only: Use DCMTK in MITK") mitkFunctionAddExternalProject(NAME OpenIGTLink OFF) mitkFunctionAddExternalProject(NAME tinyxml2 ON ADVANCED) mitkFunctionAddExternalProject(NAME GDCM ON ADVANCED) mitkFunctionAddExternalProject(NAME Eigen ON ADVANCED DOC "Use the Eigen library") mitkFunctionAddExternalProject(NAME ANN ON ADVANCED DOC "Use Approximate Nearest Neighbor Library") mitkFunctionAddExternalProject(NAME CppUnit ON ADVANCED DOC "Use CppUnit for unit tests") mitkFunctionAddExternalProject(NAME HDF5 ON ADVANCED) mitkFunctionAddExternalProject(NAME OpenCV OFF) mitkFunctionAddExternalProject(NAME Vigra OFF DEPENDS HDF5) mitkFunctionAddExternalProject(NAME ITK ON NO_CACHE DEPENDS HDF5) mitkFunctionAddExternalProject(NAME VTK ON NO_CACHE) mitkFunctionAddExternalProject(NAME Boost ON NO_CACHE) mitkFunctionAddExternalProject(NAME ZLIB OFF ADVANCED) mitkFunctionAddExternalProject(NAME lz4 ON ADVANCED) mitkFunctionAddExternalProject(NAME cpprestsdk OFF DEPENDS Boost ZLIB ADVANCED) -mitkFunctionAddExternalProject(NAME OpenMesh OFF) +mitkFunctionAddExternalProject(NAME ACVD OFF DOC "Use Approximated Centroidal Voronoi Diagrams") mitkFunctionAddExternalProject(NAME CTK ON DEPENDS Qt5 DCMTK DOC "Use CTK in MITK") mitkFunctionAddExternalProject(NAME DCMQI ON DEPENDS DCMTK ITK DOC "Use dcmqi in MITK") mitkFunctionAddExternalProject(NAME MatchPoint OFF ADVANCED DEPENDS ITK DOC "Use the MatchPoint translation image registration library") mitkFunctionAddExternalProject(NAME nlohmann_json ON ADVANCED) if(MITK_USE_Qt5) mitkFunctionAddExternalProject(NAME Qwt ON ADVANCED DEPENDS Qt5) endif() if(UNIX AND NOT APPLE) mitkFunctionAddExternalProject(NAME PCRE OFF ADVANCED NO_PACKAGE) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE DEPENDS PCRE) elseif(WIN32) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE) endif() diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox index c9d1bd489a..ff4c803e83 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox @@ -1,115 +1,115 @@ /** \page thirdpartylibs Third-party libraries The following third-party libraries can be used with MITK by default and can, in part, be automatically downloaded during superbuild. +\par ACVD + +https://www.creatis.insa-lyon.fr/~valette/public/project/acvd/ + \par ANN https://www.cs.umd.edu/~mount/ANN/ \par Boost https://www.boost.org/ \par C++ REST SDK https://github.com/Microsoft/cpprestsdk/ \par CppUnit https://sourceforge.net/projects/cppunit/ \par CTK https://commontk.org/ \par DCMTK https://dicom.offis.de/dcmtk \par Eigen http://eigen.tuxfamily.org/index.php?title=Main_Page \par GDCM https://gdcm.sourceforge.net/ \par HDF5 https://support.hdfgroup.org/HDF5/ \par ITK https://itk.org/ \par JSON for Modern C++ https://github.com/nlohmann/json \par lz4 https://github.com/lz4/lz4 \par MatchPoint https://www.dkfz.de/en/sidt/projects/MatchPoint/info.html \par OpenCL https://www.khronos.org/opencl/ \par OpenCV https://opencv.org/ \par OpenIGTLink http://openigtlink.org/ -\par OpenMesh - -https://www.openmesh.org/ - \par PCRE https://www.pcre.org/ \par POCO https://pocoproject.org/ \par Python https://www.python.org/ \par Qt https://www.qt.io/ \par Qwt http://qwt.sourceforge.net/ \par SWIG http://www.swig.org/ \par TinyXML-2 http://www.grinninglizard.com/tinyxml2/ \par VIGRA https://ukoethe.github.io/vigra/ \par VTK https://vtk.org/ \par zlib https://zlib.net/ For copyright information on any of the above toolkits see the corresponding home page or the corresponding source folder. */ diff --git a/Modules/Remeshing/CMakeLists.txt b/Modules/Remeshing/CMakeLists.txt index 91db053ef8..4e658c5ebe 100644 --- a/Modules/Remeshing/CMakeLists.txt +++ b/Modules/Remeshing/CMakeLists.txt @@ -1,4 +1,7 @@ mitk_create_module( - DEPENDS MitkCore - PACKAGE_DEPENDS OpenMesh|Tools + DEPENDS PUBLIC MitkCore + PACKAGE_DEPENDS ACVD|Surface+VolumeProcessing + WARNINGS_NO_ERRORS # ACVD's header files trigger some unused parameter errors ) + +add_subdirectory(Testing) diff --git a/Modules/Remeshing/Testing/CMakeLists.txt b/Modules/Remeshing/Testing/CMakeLists.txt new file mode 100644 index 0000000000..7a1edabe1d --- /dev/null +++ b/Modules/Remeshing/Testing/CMakeLists.txt @@ -0,0 +1,5 @@ +if(MITK_USE_ACVD) + MITK_CREATE_MODULE_TESTS() + + mitkAddCustomModuleTest(mitkRemeshingTest mitkRemeshingTest ${MITK_DATA_DIR}/binary.stl 0 1228 1.0 10 0.0 1 0 0) +endif() diff --git a/Modules/Remeshing/Testing/files.cmake b/Modules/Remeshing/Testing/files.cmake new file mode 100644 index 0000000000..b41ec2ca0c --- /dev/null +++ b/Modules/Remeshing/Testing/files.cmake @@ -0,0 +1,6 @@ +set(MODULE_TESTS +) + +set(MODULE_CUSTOM_TESTS + mitkRemeshingTest.cpp +) diff --git a/Modules/Remeshing/Testing/mitkRemeshingTest.cpp b/Modules/Remeshing/Testing/mitkRemeshingTest.cpp new file mode 100644 index 0000000000..a4fa0343fd --- /dev/null +++ b/Modules/Remeshing/Testing/mitkRemeshingTest.cpp @@ -0,0 +1,138 @@ +/*============================================================================ + +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 +#include +#include +#include +#include +#include +#include + +#define _MITK_TEST_FOR_EXCEPTION(STATEMENT, EXCEPTION, MESSAGE) \ + MITK_TEST_OUTPUT_NO_ENDL(<< MESSAGE) \ + try \ + { \ + STATEMENT; \ + MITK_TEST_OUTPUT(<< " [FAILED]") \ + mitk::TestManager::GetInstance()->TestFailed(); \ + } \ + catch (const EXCEPTION &e) \ + { \ + MITK_TEST_OUTPUT(<< "\n " << e.GetDescription() << " [PASSED]") \ + mitk::TestManager::GetInstance()->TestPassed(); \ + } + +template +static T lexical_cast(const std::string &string) +{ + std::istringstream sstream(string); + T value; + + sstream >> value; + + if (sstream.fail()) + { + MITK_ERROR << "Lexical cast failed for '" << string << "'!"; + exit(EXIT_FAILURE); + } + + return value; +} + +static void Remesh_SurfaceIsNull_ThrowsException() +{ + mitk::Surface::ConstPointer surface; + _MITK_TEST_FOR_EXCEPTION( + mitk::Remesh(surface, 0, 100, 0.0), mitk::Exception, "Remesh_SurfaceIsNull_ThrowsException") +} + +static void Remesh_PolyDataIsNull_ThrowsException() +{ + mitk::Surface::ConstPointer surface = mitk::Surface::New().GetPointer(); + _MITK_TEST_FOR_EXCEPTION( + mitk::Remesh(surface, 0, 100, 0.0), mitk::Exception, "Remesh_PolyDataIsNull_ThrowsException") +} + +static void Remesh_SurfaceDoesNotHaveDataAtTimeStep_ThrowsException() +{ + mitk::Surface::ConstPointer surface = mitk::Surface::New().GetPointer(); + _MITK_TEST_FOR_EXCEPTION(mitk::Remesh(surface, 1, 100, 0.0), + mitk::Exception, + "Remesh_SurfaceDoesNotHaveDataAtTimeStep_ThrowsException") +} + +static void Remesh_SurfaceHasNoPolygons_ThrowsException() +{ + mitk::Surface::Pointer surface = mitk::Surface::New(); + vtkSmartPointer polyData = vtkSmartPointer::New(); + surface->SetVtkPolyData(polyData); + _MITK_TEST_FOR_EXCEPTION(mitk::Remesh(surface.GetPointer(), 0, 100, 0.0), + mitk::Exception, + "Remesh_SurfaceHasNoPolygons_ThrowsException") +} + +static void Remesh_SurfaceIsValid_ReturnsRemeshedSurface(const std::string &filename, + unsigned int t, + int numVertices, + double gradation, + int subsampling, + double edgeSplitting, + int optimizationLevel, + bool forceManifold, + bool boundaryFixing) +{ + auto surface = mitk::IOUtil::Load(filename); + mitk::Surface::Pointer remeshedSurface = mitk::Remesh( + surface.GetPointer(), t, numVertices, gradation, subsampling, edgeSplitting, optimizationLevel, forceManifold, boundaryFixing); + MITK_TEST_CONDITION(remeshedSurface.IsNotNull() && remeshedSurface->GetVtkPolyData() != nullptr && + remeshedSurface->GetVtkPolyData()->GetNumberOfPolys() != 0, + "Remesh_SurfaceIsValid_ReturnsRemeshedSurface") +} + +int mitkRemeshingTest(int argc, char *argv[]) +{ + if (argc != 10) + { + MITK_ERROR << "Invalid argument count!\n" + << "Usage: mitkRemeshingTest \n" + << " \n" + << " \n" + << " See MITK API documentation of mitk::Remesh() for details."; + + return EXIT_FAILURE; + } + + const std::string filename = argv[1]; + const unsigned int t = lexical_cast(argv[2]); + const int numVertices = lexical_cast(argv[3]); + const double gradation = lexical_cast(argv[4]); + const int subsampling = lexical_cast(argv[5]); + const double edgeSplitting = lexical_cast(argv[6]); + const int optimizationLevel = lexical_cast(argv[7]); + const bool forceManifold = lexical_cast(argv[8]); + const bool boundaryFixing = lexical_cast(argv[9]); + + MITK_TEST_BEGIN("mitkRemeshingTest") + + vtkDebugLeaks::SetExitError(0); + + Remesh_SurfaceIsNull_ThrowsException(); + Remesh_PolyDataIsNull_ThrowsException(); + Remesh_SurfaceDoesNotHaveDataAtTimeStep_ThrowsException(); + Remesh_SurfaceHasNoPolygons_ThrowsException(); + + Remesh_SurfaceIsValid_ReturnsRemeshedSurface( + filename, t, numVertices, gradation, subsampling, edgeSplitting, optimizationLevel, forceManifold, boundaryFixing); + + MITK_TEST_END() +} diff --git a/Modules/Remeshing/include/mitkRemeshing.h b/Modules/Remeshing/include/mitkRemeshing.h index ef068d6424..ad92fc666d 100644 --- a/Modules/Remeshing/include/mitkRemeshing.h +++ b/Modules/Remeshing/include/mitkRemeshing.h @@ -1,39 +1,100 @@ /*============================================================================ 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 +#include #include namespace mitk { - namespace Remeshing + /** \brief Remesh a surface and store the result in a new surface. + * + * The %ACVD library is used for remeshing which is based on the paper "Approximated Centroidal Voronoi Diagrams for + * Uniform Polygonal Mesh Coarsening" by S. Valette, and J. M. Chassery. + * There are a few rules of thumbs regarding the ranges of parameters to gain high quality remeshed surfaces: + * + *
    + *
  • numVertices is exact, however, if boundaryFixing is enabled, additional vertices are generated at + * boundaries + *
  • %Set gradation to zero in case you want polygons of roughly the same size all over the remeshed surface; + * start with 1 otherwise + *
  • subsampling has direct influence on the quality of the remeshed surface (higher values take more time) + *
  • edgeSplitting is useful for surfaces that contain long and thin triangles but takes a long time + *
  • Leave optimizationLevel set to 1 as greater values result in degenerated polygons + *
  • Irregular shrinking of boundaries during remeshing can be avoided by boundaryFixing, however this results + * in additional, lower quality polygons at boundaries + *
+ * + * \param[in] surface Input surface. + * \param[in] t Time step of a four-dimensional input surface, zero otherwise. + * \param[in] numVertices Desired number of vertices in the remeshed surface, set to zero to keep original vertex + * count. + * \param[in] gradation Influence of surface curvature on polygon size. + * \param[in] subsampling Subsample input surface until number of vertices exceeds initial count times this + * parameter. + * \param[in] edgeSplitting Recursively split edges that are longer than the average edge length times this + * parameter. + * \param[in] optimizationLevel Minimize distance between input surface and remeshed surface. + * \param[in] forceManifold + * \param[in] boundaryFixing Keep original surface boundaries by adding additional polygons. + * \return Returns the remeshed surface or nullptr if input surface is invalid. + */ + MITKREMESHING_EXPORT Surface::Pointer Remesh(const Surface* surface, + TimeStepType t, + int numVertices, + double gradation, + int subsampling = 10, + double edgeSplitting = 0.0, + int optimizationLevel = 1, + bool forceManifold = false, + bool boundaryFixing = false); + + /** \brief Encapsulates mitk::Remesh function as filter. + */ + class MITKREMESHING_EXPORT RemeshFilter : public mitk::SurfaceToSurfaceFilter { - /** \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); - } + public: + mitkClassMacro(RemeshFilter, SurfaceToSurfaceFilter); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + itkSetMacro(TimeStep, unsigned int); + itkSetMacro(NumVertices, int); + itkSetMacro(Gradation, double); + itkSetMacro(Subsampling, int); + itkSetMacro(EdgeSplitting, double); + itkSetMacro(OptimizationLevel, int); + itkSetMacro(ForceManifold, bool); + itkSetMacro(BoundaryFixing, bool); + + protected: + void GenerateData() override; + + private: + RemeshFilter(); + ~RemeshFilter() override; + + TimeStepType m_TimeStep; + int m_NumVertices; + double m_Gradation; + int m_Subsampling; + double m_EdgeSplitting; + int m_OptimizationLevel; + bool m_ForceManifold; + bool m_BoundaryFixing; + }; } #endif diff --git a/Modules/Remeshing/src/mitkRemeshing.cpp b/Modules/Remeshing/src/mitkRemeshing.cpp index dd531cfaaf..295fd968ba 100644 --- a/Modules/Remeshing/src/mitkRemeshing.cpp +++ b/Modules/Remeshing/src/mitkRemeshing.cpp @@ -1,185 +1,216 @@ /*============================================================================ 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 +#include -#include -#include -#include - -#include #include -#include +#include +#include +#include #include #include #include -#include - -#include -#include +#include -using Mesh = OpenMesh::TriMesh_ArrayKernelT; +#include +#include namespace { - bool IsValidPolyData(vtkPolyData* polyData) + struct ClustersQuadrics final { - return nullptr != polyData && 0 < polyData->GetNumberOfPoints() && - (0 < polyData->GetNumberOfPolys() || 0 < polyData->GetNumberOfStrips()); - } + explicit ClustersQuadrics(size_t size) + : Elements(size), + Size(size) + { + for (auto& array : Elements) + array.fill(0.0); + } - vtkSmartPointer TriangulatePolyData(vtkPolyData* polyData) - { - auto triangleFilter = vtkSmartPointer::New(); - triangleFilter->SetInputData(polyData); - triangleFilter->PassVertsOff(); - triangleFilter->PassLinesOff(); + ~ClustersQuadrics() = default; - triangleFilter->Update(); + ClustersQuadrics(const ClustersQuadrics&) = delete; + ClustersQuadrics& operator=(const ClustersQuadrics&) = delete; - return triangleFilter->GetOutput(); - } + std::vector> Elements; + size_t Size; + }; - vtkSmartPointer CalculateNormals(vtkPolyData* polyData, bool flipNormals) + void ValidateSurface(const mitk::Surface* surface, mitk::TimeStepType t) { - auto polyDataNormals = vtkSmartPointer::New(); - polyDataNormals->SetInputData(polyData); - - if (flipNormals) - polyDataNormals->FlipNormalsOn(); + if (surface == nullptr) + mitkThrow() << "Input surface is nullptr!"; - polyDataNormals->Update(); + if (t >= surface->GetSizeOfPolyDataSeries()) + mitkThrow() << "Input surface doesn't have data at time step " << t << "!"; - return polyDataNormals->GetOutput(); - } + auto* polyData = const_cast(surface)->GetVtkPolyData(t); - Mesh ConvertPolyDataToMesh(vtkPolyData* polyData) - { - Mesh mesh; + if (polyData == nullptr) + mitkThrow() << "PolyData of input surface at time step " << t << " is nullptr!"; - auto* points = polyData->GetPoints(); - const auto numPoints = points->GetNumberOfPoints(); + if (polyData->GetNumberOfPolys() == 0) + mitkThrow() << "Input surface has no polygons at time step " << t << "!"; + } +} - for (std::remove_const_t i = 0; i < numPoints; ++i) - mesh.add_vertex(Mesh::Point(points->GetPoint(i))); +mitk::Surface::Pointer mitk::Remesh(const Surface* surface, + TimeStepType t, + int numVertices, + double gradation, + int subsampling, + double edgeSplitting, + int optimizationLevel, + bool forceManifold, + bool boundaryFixing) +{ + ValidateSurface(surface, t); - auto* polys = polyData->GetPolys(); - const auto numPolys = polys->GetNumberOfCells(); + MITK_INFO << "Start remeshing..."; - auto ids = vtkSmartPointer::New(); - std::array vertexHandles; + auto surfacePolyData = vtkSmartPointer::New(); + surfacePolyData->DeepCopy(const_cast(surface)->GetVtkPolyData(t)); - for (std::remove_const_t i = 0; i < numPolys; ++i) - { - polys->GetCellAtId(i, ids); + auto mesh = vtkSmartPointer::New(); - 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->CreateFromPolyData(surfacePolyData); + mesh->GetCellData()->Initialize(); + mesh->GetPointData()->Initialize(); - mesh.add_face(vertexHandles.data(), 3); - } + mesh->DisplayMeshProperties(); - return mesh; - } + if (numVertices == 0) + numVertices = surfacePolyData->GetNumberOfPoints(); - vtkSmartPointer ConvertMeshToPolyData(const Mesh& mesh) - { - auto polyData = vtkSmartPointer::New(); + if (edgeSplitting != 0.0) + mesh->SplitLongEdges(edgeSplitting); - const auto numVertices = mesh.n_vertices(); - auto points = vtkSmartPointer::New(); - points->SetNumberOfPoints(numVertices); + auto remesher = vtkSmartPointer::New(); - for (std::remove_const_t i = 0; i < numVertices; ++i) - points->SetPoint(i, mesh.point(Mesh::VertexHandle(static_cast(i))).data()); + remesher->GetMetric()->SetGradation(gradation); + remesher->SetBoundaryFixing(boundaryFixing); + remesher->SetConsoleOutput(1); + remesher->SetForceManifold(forceManifold); + remesher->SetInput(mesh); + remesher->SetNumberOfClusters(numVertices); + remesher->SetSubsamplingThreshold(subsampling); - polyData->SetPoints(points); + remesher->Remesh(); - const auto numFaces = mesh.n_faces(); - auto polys = vtkSmartPointer::New(); + // Optimization: Minimize distance between input surface and remeshed surface + if (optimizationLevel != 0) + { + ClustersQuadrics clustersQuadrics(numVertices); - auto ids = vtkSmartPointer::New(); - ids->SetNumberOfIds(3); - Mesh::CFVIter iter; + auto faceList = vtkSmartPointer::New(); + vtkSmartPointer clustering = remesher->GetClustering(); + vtkSmartPointer remesherInput = remesher->GetInput(); + int clusteringType = remesher->GetClusteringType(); + int numItems = remesher->GetNumberOfItems(); + int numMisclassifiedItems = 0; - for (std::remove_const_t i = 0; i < numFaces; ++i) + for (int i = 0; i < numItems; ++i) { - iter = mesh.cfv_iter(Mesh::FaceHandle(static_cast(i))); + int cluster = clustering->GetValue(i); - ids->SetId(0, (iter++)->idx()); - ids->SetId(1, (iter++)->idx()); - ids->SetId(2, iter->idx()); + if (cluster >= 0 && cluster < numVertices) + { + if (clusteringType != 0) + { + remesherInput->GetVertexNeighbourFaces(i, faceList); + int numIds = static_cast(faceList->GetNumberOfIds()); - polys->InsertNextCell(ids); + for (int j = 0; j < numIds; ++j) + vtkQuadricTools::AddTriangleQuadric(clustersQuadrics.Elements[cluster].data(), remesherInput, faceList->GetId(j), false); + } + else + { + vtkQuadricTools::AddTriangleQuadric(clustersQuadrics.Elements[cluster].data(), remesherInput, i, false); + } + } + else + { + ++numMisclassifiedItems; + } } - polyData->SetPolys(polys); + if (numMisclassifiedItems != 0) + MITK_INFO << numMisclassifiedItems << " items with wrong cluster association" << std::endl; - 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; + vtkSmartPointer remesherOutput = remesher->GetOutput(); + double point[3]; - auto output = mitk::Surface::New(); - const auto numTimeSteps = input->GetTimeSteps(); - - for (std::remove_const_t t = 0; t < numTimeSteps; ++t) + for (int i = 0; i < numVertices; ++i) { - vtkSmartPointer polyData = input->GetVtkPolyData(t); + remesherOutput->GetPoint(i, point); + vtkQuadricTools::ComputeRepresentativePoint(clustersQuadrics.Elements[i].data(), point, optimizationLevel); + remesherOutput->SetPointCoordinates(i, point); + } - if (IsValidPolyData(polyData)) - { - polyData = TriangulatePolyData(polyData); + MITK_INFO << "After quadrics post-processing:" << std::endl; + remesherOutput->DisplayMeshProperties(); + } - if (IsValidPolyData(polyData)) - { - auto mesh = ConvertPolyDataToMesh(polyData); - ProcessMesh(mesh); - polyData = ConvertMeshToPolyData(mesh); + auto normals = vtkSmartPointer::New(); - if (calculateNormals) - polyData = CalculateNormals(polyData, flipNormals); + normals->SetInputData(remesher->GetOutput()); + normals->AutoOrientNormalsOn(); + normals->ComputeCellNormalsOff(); + normals->ComputePointNormalsOn(); + normals->ConsistencyOff(); + normals->FlipNormalsOff(); + normals->NonManifoldTraversalOff(); + normals->SplittingOff(); - output->SetVtkPolyData(polyData, t); - continue; - } - } + normals->Update(); - output->SetVtkPolyData(nullptr, t); - } + auto remeshedSurface = Surface::New(); + remeshedSurface->SetVtkPolyData(normals->GetOutput()); - return output; - } + MITK_INFO << "Finished remeshing"; + + return remeshedSurface; } -mitk::Surface::Pointer mitk::Remeshing::Decimate(const Surface* input, double percent, bool calculateNormals, bool flipNormals) +mitk::RemeshFilter::RemeshFilter() + : m_TimeStep(0), + m_NumVertices(0), + m_Gradation(1.0), + m_Subsampling(10), + m_EdgeSplitting(0.0), + m_OptimizationLevel(1), + m_ForceManifold(false), + m_BoundaryFixing(false) { - 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(); + Surface::Pointer output = Surface::New(); + this->SetNthOutput(0, output); +} - decimater.initialize(); - decimater.decimate_to(mesh.n_vertices() * std::max(0.0, std::min(percent, 1.0))); +mitk::RemeshFilter::~RemeshFilter() +{ +} - mesh.garbage_collection(); - }); +void mitk::RemeshFilter::GenerateData() +{ + auto output = Remesh(this->GetInput(), + m_TimeStep, + m_NumVertices, + m_Gradation, + m_Subsampling, + m_EdgeSplitting, + m_OptimizationLevel, + m_ForceManifold, + m_BoundaryFixing); + + this->SetNthOutput(0, output); } diff --git a/Plugins/org.mitk.gui.qt.remeshing/CMakeLists.txt b/Plugins/org.mitk.gui.qt.remeshing/CMakeLists.txt index dcfeabfa2f..083fc99ef8 100644 --- a/Plugins/org.mitk.gui.qt.remeshing/CMakeLists.txt +++ b/Plugins/org.mitk.gui.qt.remeshing/CMakeLists.txt @@ -1,7 +1,9 @@ project(org_mitk_gui_qt_remeshing) +include_directories(${CTK_INCLUDE_DIRS}) + mitk_create_plugin( EXPORT_DIRECTIVE REMESHING_EXPORT EXPORTED_INCLUDE_SUFFIXES src MODULE_DEPENDS MitkQtWidgets MitkRemeshing ) 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 aef064d831..ee6c744303 100644 --- a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp +++ b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp @@ -1,123 +1,164 @@ /*============================================================================ 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 +#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->remeshPushButton->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->vertexCountSlider, SIGNAL(valueChanged(int)), this, SLOT(OnVertexCountChanged(int))); - connect(m_Controls->vertexCountSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnVertexCountChanged(int))); - connect(m_Controls->calculateNormalsCheckBox, SIGNAL(stateChanged(int)), this, SLOT(OnCalculateNormalsChanged(int))); - connect(m_Controls->decimatePushButton, SIGNAL(clicked()), this, SLOT(OnDecimateButtonClicked())); + connect(m_Controls->densitySlider, SIGNAL(valueChanged(int)), this, SLOT(OnDensityChanged(int))); + connect(m_Controls->densitySpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnDensityChanged(int))); + connect(m_Controls->remeshPushButton, SIGNAL(clicked()), this, SLOT(OnRemeshButtonClicked())); this->OnSurfaceChanged(m_Controls->selectionWidget->GetSelectedNodes()); } void QmitkRemeshingView::OnSurfaceChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes) { - this->EnableWidgets(!nodes.empty() && nodes.front().IsNotNull()); + if (!nodes.empty() && nodes.front().IsNotNull()) + { + m_MaxNumberOfVertices = static_cast(static_cast(nodes.front()->GetData())->GetVtkPolyData()->GetNumberOfPoints()); + this->EnableWidgets(true); + } + else + { + m_MaxNumberOfVertices = 0; + this->EnableWidgets(false); + } } -void QmitkRemeshingView::OnVertexCountChanged(int vertexCount) +void QmitkRemeshingView::OnDensityChanged(int density) { - if (vertexCount != m_Controls->vertexCountSlider->value()) - m_Controls->vertexCountSlider->setValue(vertexCount); + if (density != m_Controls->densitySlider->value()) + m_Controls->densitySlider->setValue(density); - if (vertexCount != m_Controls->vertexCountSpinBox->value()) - m_Controls->vertexCountSpinBox->setValue(vertexCount); + if (density != m_Controls->densitySpinBox->value()) + m_Controls->densitySpinBox->setValue(density); } -void QmitkRemeshingView::OnCalculateNormalsChanged(int checkState) +void QmitkRemeshingView::OnRemeshButtonClicked() { - m_Controls->flipNormalsCheckBox->setEnabled(Qt::Unchecked != checkState); -} + auto selectedNode = m_Controls->selectionWidget->GetSelectedNode(); + mitk::Surface::ConstPointer surface = static_cast(selectedNode->GetData()); -void QmitkRemeshingView::OnDecimateButtonClicked() -{ - mitk::DataNode::Pointer selectedNode = m_Controls->selectionWidget->GetSelectedNode(); - mitk::Surface::ConstPointer input = static_cast(selectedNode->GetData()); - mitk::Surface::Pointer output; + int density = m_Controls->densitySpinBox->value(); + int numVertices = std::max(100, static_cast(m_MaxNumberOfVertices * (density * 0.01))); + + double gradation = m_Controls->remeshingComboBox->currentText() == QStringLiteral("Adaptive") + ? 1.0 + : 0.0; + + const QString quality = m_Controls->qualityComboBox->currentText(); + int subsampling; + + if (QStringLiteral("High (slow)") == quality) + { + subsampling = 50; + } + else if (QStringLiteral("Maximum (very slow)") == quality) + { + subsampling = 500; + } + else // The default is "Medium (fast)". + { + subsampling = 10; + } + + bool boundaryFixing = m_Controls->preserveEdgesCheckBox->isChecked(); + + auto remesher = mitk::RemeshFilter::New(); + remesher->SetInput(surface); + remesher->SetTimeStep(0); + remesher->SetNumVertices(numVertices); + remesher->SetGradation(gradation); + remesher->SetSubsampling(subsampling); + remesher->SetEdgeSplitting(0.0); + remesher->SetOptimizationLevel(1.0); + remesher->SetForceManifold(false); + remesher->SetBoundaryFixing(boundaryFixing); try { - output = mitk::Remeshing::Decimate(input, - 0.01 * m_Controls->vertexCountSpinBox->value(), - m_Controls->calculateNormalsCheckBox->isChecked(), - m_Controls->flipNormalsCheckBox->isChecked()); + remesher->Update(); } catch(const mitk::Exception& exception) { MITK_ERROR << exception.GetDescription(); return; } - if (output.IsNull()) - return; + mitk::Surface::Pointer remeshedSurface = remesher->GetOutput(); auto newNode = mitk::DataNode::New(); - newNode->SetName(QString("%1 (decimated)").arg(selectedNode->GetName().c_str()).toStdString()); - newNode->SetData(output); + newNode->SetName(QString("%1 (%2%)").arg(selectedNode->GetName().c_str()).arg(density).toStdString()); + newNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); + newNode->SetData(remeshedSurface); this->GetDataStorage()->Add(newNode, selectedNode); } void QmitkRemeshingView::EnableWidgets(bool enable) { - m_Controls->vertexCountSlider->setEnabled(enable); - m_Controls->vertexCountSpinBox->setEnabled(enable); - m_Controls->calculateNormalsCheckBox->setEnabled(enable); - m_Controls->flipNormalsCheckBox->setEnabled(enable && m_Controls->calculateNormalsCheckBox->isChecked()); - m_Controls->decimatePushButton->setEnabled(enable); + m_Controls->densitySlider->setEnabled(enable); + m_Controls->densitySpinBox->setEnabled(enable); + m_Controls->remeshingComboBox->setEnabled(enable); + m_Controls->qualityComboBox->setEnabled(enable); + m_Controls->preserveEdgesCheckBox->setEnabled(enable); + m_Controls->remeshPushButton->setEnabled(enable); + + m_Controls->explanationLabel->setVisible(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 e0f5b99b83..5cff2bd088 100644 --- a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.h +++ b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.h @@ -1,49 +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 OnVertexCountChanged(int vertexCount); - void OnCalculateNormalsChanged(int checkState); - void OnDecimateButtonClicked(); + void OnDensityChanged(int numVertices); + void OnRemeshButtonClicked(); private: void EnableWidgets(bool enable); Ui::QmitkRemeshingViewControls* m_Controls; + int m_MaxNumberOfVertices; }; #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 78a31dde92..105b8e2ba4 100644 --- a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingViewControls.ui +++ b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingViewControls.ui @@ -1,172 +1,244 @@ QmitkRemeshingViewControls true 0 0 253 573 Remeshing 0 0 Surface - - - - Flip normals + + + + + 0 + 40 + - + 0 0 - Vertex count + Density - + 0 0 1 100 10 - 50 + 100 Qt::Horizontal - - - - - 0 - 40 - - - - - + % 1 100 1 - 50 + 100 + + + + + + + + 0 + 0 + + + + Remeshing - + + + + Adaptive + + + + + Regular + + + + + + + + + 0 + 0 + + - Calculate normals + Quality + + + + + + + 0 + + + + Medium (fast) + + + + + High (slow) + + + + + Maximum (very slow) + + + + + + + + + 0 + 0 + + + + Preserve Edges true - + true - Decimate + Remesh :/Remeshing/RemeshingIcon.svg:/Remeshing/RemeshingIcon.svg 24 24 + + + + <html><head/><body><p><span style=" font-weight:600;">Density:</span> The density of the resulting surface compared to the input surface. For example, a density of 50% will effectively halve the number of vertices. It's not uncommen to choose values as low as 10% for overly dense input surfaces. The minimum number of output vertices is at least 100, though.</p><p><span style=" font-weight:600;">Remeshing:</span> Adaptive remeshing results in higher density in curvy areas and less density in flat areas. This remeshing strategy can preserve fine shape details even when the overall density is heavily reduced. Regular remeshing evenly distributes the density regardless of the local shape of the surface.</p><p><span style=" font-weight:600;">Quality</span>: While medium quality is sufficient for the vast majority of use cases, you can increase this setting to further optimize the mesh quality in terms of uniformly shaped triangles. However, computation time and memory consumption increase dramatically compared to very small improvements.</p><p><span style=" font-weight:600;">Preserve Edges:</span> If the input surface contains holes or edges, they will be preserved very accurately by default at the cost of less uniform triangles at their direct neighborhood.</p></body></html> + + + true + + + Qt::Vertical 20 40 QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
- vertexCountSpinBox - decimatePushButton + densitySpinBox + preserveEdgesCheckBox + remeshPushButton
diff --git a/Plugins/org.mitk.gui.qt.remeshing/target_libraries.cmake b/Plugins/org.mitk.gui.qt.remeshing/target_libraries.cmake new file mode 100644 index 0000000000..6c71bbbf08 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.remeshing/target_libraries.cmake @@ -0,0 +1,3 @@ +set(target_libraries + CTKWidgets +)