diff --git a/CMake/MITKDashboardScript.download.cmake b/CMake/MITKDashboardScript.download.cmake index 55111521d7..bab00f2f39 100644 --- a/CMake/MITKDashboardScript.download.cmake +++ b/CMake/MITKDashboardScript.download.cmake @@ -1,702 +1,702 @@ #[============================================================================[ Helper functions ]============================================================================] function(get_macos_codename) set(macos_codename "") set(software_license_file "\ /System/Library/CoreServices/Setup Assistant.app\ /Contents/Resources/en.lproj/OSXSoftwareLicense.rtf") if(EXISTS "${software_license_file}") file(READ "${software_license_file}" software_license) - if(software_license MATCHES "SOFTWARE LICENSE AGREEMENT FOR macOS ([^\n]+)") + if(software_license MATCHES "SOFTWARE LICENSE AGREEMENT FOR macOS ([^\\\n]+)") set(macos_codename "${CMAKE_MATCH_1}") endif() endif() set(${ARGV0} ${macos_codename} PARENT_SCOPE) endfunction() function(get_macos_name) set(macos_name "macOS") get_macos_codename(macos_codename) if(macos_codename) set(macos_name "${macos_name} ${macos_codename}") endif() execute_process( RESULT_VARIABLE error_code OUTPUT_VARIABLE version OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND "sw_vers" "-productVersion") if(0 EQUAL error_code AND version) set(macos_name "${macos_name} ${version}") endif() set(${ARGV0} ${macos_name} PARENT_SCOPE) endfunction() function(get_linux_name) set(linux_name "Linux") set(os_release_file "/etc/os-release") if(EXISTS "${os_release_file}") file(STRINGS "${os_release_file}" os_release) set(name "") set(version_id "") foreach(line ${os_release}) if(NOT name AND line MATCHES "^NAME=[\"]?([^\"]+)") set(name "${CMAKE_MATCH_1}") continue() endif() if(NOT version_id AND line MATCHES "^VERSION_ID=\"?([^\"]+)") set(version_id "${CMAKE_MATCH_1}") continue() endif() if(name AND version_id) break() endif() endforeach() if(name) set(linux_name "${name}") endif() if(version_id) set(linux_name "${linux_name} ${version_id}") endif() endif() set(${ARGV0} ${linux_name} PARENT_SCOPE) endfunction() function(get_windows_name) set(windows_name "Windows") execute_process( RESULT_VARIABLE error_code OUTPUT_VARIABLE version OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND "wmic" "os" "get" "Version" "-value") if(0 EQUAL error_code AND version) if(version MATCHES "Version=([0-9]+)\\.([0-9]+)") set(windows_name "${windows_name} ${CMAKE_MATCH_1}") if(NOT 0 EQUAL CMAKE_MATCH_2) set(windows_name "${windows_name}.${CMAKE_MATCH_2}") endif() endif() endif() set(${ARGV0} ${windows_name} PARENT_SCOPE) endfunction() function(get_os_name) set(os_name "") if(APPLE) get_macos_name(os_name) elseif(UNIX) get_linux_name(os_name) elseif(WIN32) get_windows_name(os_name) endif() set(${ARGV0} ${os_name} PARENT_SCOPE) endfunction() function(download_string) cmake_parse_arguments(PARSE_ARGV 0 ARG "" "URL;OUTPUT_VARIABLE" "") get_temp_directory(temp_dir) string(RANDOM random_filename) while(EXISTS "${temp_dir}/${random_filename}") string(RANDOM random_filename) endwhile() set(temp_output_file "${temp_dir}/${random_filename}") download_file( URL ${ARG_URL} OUTPUT_FILE ${temp_output_file} ERROR_CODE error_code) set(output "") if(EXISTS "${temp_output_file}") file(READ "${temp_output_file}" output) file(REMOVE "${temp_output_file}") endif() set(${ARG_OUTPUT_VARIABLE} ${output} PARENT_SCOPE) endfunction() function(find_vswhere) get_temp_directory(temp_dir) set(latest_vswhere "${temp_dir}/vswhere.exe") set(vswhere "vswhere.exe-NOTFOUND") if(EXISTS "${latest_vswhere}") set(vswhere "${latest_vswhere}") else() set(program_files_x86 "PROGRAMFILES(X86)") set(program_files_x86 "$ENV{${program_files_x86}}") set(system_vswhere "${program_files_x86}/Microsoft Visual Studio/Installer/vswhere.exe") if(EXISTS "${system_vswhere}") set(vswhere "${system_vswhere}") else() download_string( URL "https://api.github.com/repos/microsoft/vswhere/releases/latest" OUTPUT_VARIABLE latest_vswhere_json) if(latest_vswhere_json MATCHES "\"browser_download_url\": *\"([^\"]*)\"") download_file( URL "${CMAKE_MATCH_1}" OUTPUT_FILE "${latest_vswhere}" ERROR_CODE error_code) if (0 EQUAL error_code) set(vswhere "${latest_vswhere}") endif() endif() endif() endif() set(${ARGV0} "${vswhere}" PARENT_SCOPE) endfunction() function(vswhere) cmake_parse_arguments(PARSE_ARGV 0 ARG "" "PROPERTY;OUTPUT_VARIABLE" "") set(property_value "") if(CTEST_CMAKE_GENERATOR MATCHES "^Visual Studio ([0-9]+)") set(major_version "${CMAKE_MATCH_1}") math(EXPR next_major_version "${major_version} + 1") find_vswhere(vswhere) if (vswhere) execute_process( OUTPUT_VARIABLE property_value OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND ${vswhere} "-requires" "Microsoft.VisualStudio.Workload.NativeDesktop" "-version" "[${major_version},${next_major_version})" "-property" "${ARG_PROPERTY}" "-format" "value") endif() endif() set(${ARG_OUTPUT_VARIABLE} ${property_value} PARENT_SCOPE) endfunction() function(get_visual_studio_name) set(visual_studio_name "Visual Studio") if(CTEST_CMAKE_GENERATOR MATCHES "^Visual Studio [0-9]+ ([0-9]+)") set(product_line_version "${CMAKE_MATCH_1}") set(visual_studio_name "${visual_studio_name} ${product_line_version}") vswhere(PROPERTY "catalog_productDisplayVersion" OUTPUT_VARIABLE product_display_version) if(product_display_version) set(visual_studio_name "${visual_studio_name} ${product_display_version}") endif() endif() set(${ARGV0} ${visual_studio_name} PARENT_SCOPE) endfunction() function(get_compiler_name) set(compiler_name "") if(CTEST_CMAKE_GENERATOR MATCHES "^Visual Studio") get_visual_studio_name(compiler_name) else() get_temp_directory(temp_dir) file(WRITE "${temp_dir}/src/CMakeLists.txt" "\ project(probe C) message(STATUS \"CMAKE_C_COMPILER_ID=\\\"\${CMAKE_C_COMPILER_ID}\\\"\") message(STATUS \"CMAKE_C_COMPILER_VERSION=\\\"\${CMAKE_C_COMPILER_VERSION}\\\"\")") file(REMOVE_RECURSE "${temp_dir}/build") unset(options) foreach(option ${MITK_SUPERBUILD_OPTIONS}) list(APPEND options "-D" "${option}") endforeach() execute_process( RESULT_VARIABLE error_code OUTPUT_VARIABLE compiler ERROR_QUIET COMMAND ${CMAKE_COMMAND} "-S" "${temp_dir}/src" "-B" "${temp_dir}/build" "-G" "${CTEST_CMAKE_GENERATOR}" ${options}) if(0 EQUAL error_code) if(compiler MATCHES "CMAKE_C_COMPILER_ID=\"([^\"]*)\"") set(compiler_name "${CMAKE_MATCH_1}") if(compiler MATCHES "CMAKE_C_COMPILER_VERSION=\"([^\"]*)\"") set(version "${CMAKE_MATCH_1}") if(version MATCHES "^([0-9]+\\.[0-9]+\\.[0-9]+)") set(version "${CMAKE_MATCH_1}") endif() string(REGEX REPLACE "\\.0$" "" version "${version}") set(compiler_name "${compiler_name} ${version}") endif() endif() endif() endif() set(${ARGV0} ${compiler_name} PARENT_SCOPE) endfunction() function(get_default_build_name) get_os_name(default_build_name) get_compiler_name(compiler_name) if(compiler_name) set(default_build_name "${default_build_name} ${compiler_name}") endif() set(default_build_name "${default_build_name} ${CTEST_BUILD_CONFIGURATION}") if(MITK_BUILD_CONFIGURATION) set(default_build_name "${default_build_name} ${MITK_BUILD_CONFIGURATION}") endif() if(MITK_BUILD_NAME_SUFFIX) set(default_build_name "${default_build_name} ${MITK_BUILD_NAME_SUFFIX}") endif() set(${ARGV0} ${default_build_name} PARENT_SCOPE) endfunction() function(git) cmake_parse_arguments(PARSE_ARGV 0 ARG "" "WORKING_DIRECTORY;OUTPUT_VARIABLE;RESULT_VARIABLE" "") if(NOT ARG_WORKING_DIRECTORY) set(ARG_WORKING_DIRECTORY "${CTEST_SOURCE_DIRECTORY}") endif() get_filename_component(ARG_WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}" ABSOLUTE) execute_process( WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}" RESULT_VARIABLE error_code OUTPUT_VARIABLE output OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND "${CTEST_GIT_COMMAND}" ${ARG_UNPARSED_ARGUMENTS}) if(ARG_OUTPUT_VARIABLE) set(${ARG_OUTPUT_VARIABLE} ${output} PARENT_SCOPE) endif() if(ARG_RESULT_VARIABLE) set(${ARG_RESULT_VARIABLE} ${error_code} PARENT_SCOPE) endif() endfunction() function(git_log) cmake_parse_arguments(PARSE_ARGV 0 ARG "" "WORKING_DIRECTORY;COMMIT_HASH;COMMITTER;SUBJECT" "") if(NOT ARG_WORKING_DIRECTORY) set(ARG_WORKING_DIRECTORY "${CTEST_SOURCE_DIRECTORY}") endif() get_filename_component(ARG_WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}" ABSOLUTE) set(args WORKING_DIRECTORY "${ARG_WORKING_DIRECTORY}" "log" "-1") if(ARG_COMMIT_HASH) unset(commit_hash) git(OUTPUT_VARIABLE commit_hash ${args} "--pretty=%h" ) if(commit_hash) set(${ARG_COMMIT_HASH} ${commit_hash} PARENT_SCOPE) endif() endif() if(ARG_COMMITTER) unset(committer) git(OUTPUT_VARIABLE committer ${args} "--pretty=%cn" ) if(committer) set(${ARG_COMMITTER} ${committer} PARENT_SCOPE) endif() endif() if(ARG_SUBJECT) unset(subject) git(OUTPUT_VARIABLE subject ${args} "--pretty=%s" ) if(subject) set(${ARG_SUBJECT} ${subject} PARENT_SCOPE) endif() endif() endfunction() function(rm) execute_process(COMMAND ${CMAKE_COMMAND} "-E" "rm" "-rf" ${ARGN}) endfunction() function(submit) cmake_parse_arguments(PARSE_ARGV 0 ARG "" "" "PARTS") if(MITK_SUBMIT_URL) set(submit_url "SUBMIT_URL" "${MITK_SUBMIT_URL}") else() unset(submit_url) endif() if(MITK_AUTH_TOKEN) set(http_header HTTPHEADER "Authorization: Bearer ${MITK_AUTH_TOKEN}") else() unset(http_header) endif() ctest_submit(${submit_url} ${http_header} RETRY_COUNT 3 RETRY_DELAY 5 PARTS ${ARG_PARTS}) endfunction() #[============================================================================[ Actual script ]============================================================================] cmake_minimum_required(VERSION 3.18 FATAL_ERROR) find_program(CTEST_GIT_COMMAND git) if(NOT CTEST_GIT_COMMAND) message(FATAL_ERROR "Git not found") endif() if(NOT CTEST_CMAKE_GENERATOR MATCHES "^Visual Studio") unset(CTEST_CMAKE_GENERATOR_PLATFORM) endif() if(CTEST_CMAKE_GENERATOR MATCHES "^(Unix Makefiles|Ninja)$") set(CTEST_USE_LAUNCHERS ON) endif() if(NOT CTEST_SITE) unset(CTEST_SITE) site_name(CTEST_SITE) endif() if(NOT CTEST_BUILD_NAME) get_default_build_name(CTEST_BUILD_NAME) endif() set(CTEST_SOURCE_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/src/MITK") set(CTEST_BINARY_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/build") set(indent " ") message("CTest settings") if(MITK_SUBMIT_URL) message("${indent}Submit URL: ${MITK_SUBMIT_URL}") endif() message("\ ${indent}Dashboard model: ${CTEST_DASHBOARD_MODEL} ${indent}Build name: ${CTEST_BUILD_NAME} ${indent}Site: ${CTEST_SITE}") if(MITK_CLEAN_SOURCE_DIR OR MITK_CLEAN_BINARY_DIR) message("Clean") if(MITK_CLEAN_SOURCE_DIR) set(clean_dir "${CMAKE_CURRENT_SOURCE_DIR}/src") message("${indent}Source directory: ${clean_dir}") rm("${clean_dir}") endif() if(MITK_CLEAN_BINARY_DIR) set(clean_dir "${CMAKE_CURRENT_SOURCE_DIR}/build") message("${indent}Binary directory: ${clean_dir}") rm("${clean_dir}") endif() endif() message("MITK repository") if(NOT EXISTS "${CTEST_SOURCE_DIRECTORY}") message("\ ${indent}Clone repository: ${MITK_REPOSITORY} ${indent}Branch: ${MITK_BRANCH}") set(CTEST_CHECKOUT_COMMAND "\"${CTEST_GIT_COMMAND}\" clone --branch=${MITK_BRANCH} -- ${MITK_REPOSITORY} MITK") ctest_start(${CTEST_DASHBOARD_MODEL}) else() git_log(COMMIT_HASH old_revision COMMITTER committer SUBJECT subject) message("\ ${indent}Update repository: ${CTEST_SOURCE_DIRECTORY} ${indent}Branch: ${MITK_BRANCH} ${indent}Old revision: ${old_revision} (committed by ${committer}) ${indent}${indent}${subject}") ctest_start(${CTEST_DASHBOARD_MODEL}) set(CTEST_UPDATE_OPTIONS "--tags origin ${MITK_BRANCH}") ctest_update(RETURN_VALUE return_value) if(-1 EQUAL return_value) return() endif() git_log(COMMIT_HASH new_revision) if(new_revision STREQUAL old_revision) message("${indent}New revision: up-to-date") else() git_log(COMMITTER committer SUBJECT subject) message("\ ${indent}New revision: ${new_revision} (committed by ${committer}) ${indent}${indent}${subject}") endif() endif() unset(extension_dirs) if(MITK_EXTENSIONS) list(LENGTH MITK_EXTENSIONS mitk_extensions_size) math(EXPR max_index "${mitk_extensions_size} - 1") foreach(name_index RANGE 0 ${max_index} 3) list(GET MITK_EXTENSIONS ${name_index} name) math(EXPR url_index "${name_index} + 1") list(GET MITK_EXTENSIONS ${url_index} url) math(EXPR branch_index "${name_index} + 2") list(GET MITK_EXTENSIONS ${branch_index} branch) message("${name} repository") set(extension_dir "${CMAKE_CURRENT_SOURCE_DIR}/src/${name}") list(APPEND extension_dirs "${extension_dir}") if(NOT EXISTS "${extension_dir}") message("\ ${indent}Clone repository: ${url} ${indent}Branch: ${branch}") git("clone" "--quiet" "--branch=${branch}" "${url}" "${name}" WORKING_DIRECTORY "${extension_dir}/..") else() git_log(COMMIT_HASH old_revision COMMITTER committer SUBJECT subject WORKING_DIRECTORY "${extension_dir}") message("\ ${indent}Update repository: ${extension_dir} ${indent}Branch: ${branch} ${indent}Old revision: ${old_revision} (committed by ${committer}) ${indent}${indent}${subject}") git("fetch" "--quiet" "--tags" "origin" "${branch}" WORKING_DIRECTORY "${extension_dir}") git("diff" "--quiet" "HEAD" "FETCH_HEAD" WORKING_DIRECTORY "${extension_dir}" RESULT_VARIABLE error_code) if(NOT error_code EQUAL 0) git("reset" "--quiet" "--hard" "FETCH_HEAD" WORKING_DIRECTORY "${extension_dir}") endif() git_log(COMMIT_HASH new_revision WORKING_DIRECTORY "${extension_dir}") if(new_revision STREQUAL old_revision) message("${indent}New revision: up-to-date") else() git_log(COMMITTER committer SUBJECT subject WORKING_DIRECTORY "${extension_dir}") message("\ ${indent}New revision: ${new_revision} (committed by ${committer}) ${indent}${indent}${subject}") endif() endif() endforeach() endif() submit(PARTS Update) set(options "-D" "MITK_CTEST_SCRIPT_MODE:STRING=${CTEST_DASHBOARD_MODEL}" # "-D" "SUPERBUILD_EXCLUDE_MITKBUILD_TARGET:BOOL=TRUE" ) if(NOT WIN32) list(APPEND options "-D" "CMAKE_BUILD_TYPE:STRING=${CTEST_BUILD_CONFIGURATION}") endif() if(extension_dirs) string (REPLACE ";" "\\\;" extension_dirs "${extension_dirs}") list(APPEND options "-D" "MITK_EXTENSION_DIRS:STRING=${extension_dirs}") endif() if(MITK_BUILD_CONFIGURATION) list(APPEND options "-D" "MITK_BUILD_CONFIGURATION:STRING=${MITK_BUILD_CONFIGURATION}") endif() if(MITK_BUILD_OPTIONS) get_temp_directory(temp_dir) set(mitk_initial_cache_file "${temp_dir}/MITKInitialCache.cmake") file(WRITE "${mitk_initial_cache_file}" "") foreach(mitk_option ${MITK_BUILD_OPTIONS}) if(mitk_option MATCHES "^([^:]+):([^=]+)=(.*)") set(var "${CMAKE_MATCH_1}") set(type "${CMAKE_MATCH_2}") set(value "${CMAKE_MATCH_3}") file(APPEND "${mitk_initial_cache_file}" "set(${var} \"${value}\" CACHE ${type} \"\" FORCE)\n") endif() endforeach() list(APPEND options "-D" "MITK_INITIAL_CACHE_FILE:FILEPATH=${mitk_initial_cache_file}") endif() foreach(option ${MITK_SUPERBUILD_OPTIONS}) list(APPEND options "-D" "${option}") endforeach() ctest_configure(OPTIONS "${options}" RETURN_VALUE return_value) submit(PARTS Configure) if(NOT 0 EQUAL return_value) submit(PARTS Done) return() endif() include("${CTEST_BINARY_DIRECTORY}/SuperBuildTargets.cmake") if(NOT SUPERBUILD_TARGETS) submit(PARTS Done) message(FATAL_ERROR "SUPERBUILD_TARGETS variable not set in SuperBuildTargets.cmake") else() set(mitk_targets MITK-Data MITK-Utilities MITK-Configure MITK-build ) list(LENGTH SUPERBUILD_TARGETS n) list(LENGTH mitk_targets num_mitk_targets) math(EXPR n "${n} + ${num_mitk_targets}") set(i 1) unset(append) foreach(target ${SUPERBUILD_TARGETS} ${mitk_targets}) message("MITK superbuild - [${i}/${n}] Build ${target}") if(NOT target IN_LIST mitk_targets) list(APPEND CTEST_CUSTOM_WARNING_EXCEPTION ".*") set(pop_warning_exception TRUE) else() set(pop_warning_exception FALSE) endif() ctest_build(TARGET ${target} NUMBER_ERRORS num_build_errors NUMBER_WARNINGS num_build_warnings RETURN_VALUE return_value ${append}) if(pop_warning_exception) list(POP_BACK CTEST_CUSTOM_WARNING_EXCEPTION) endif() submit(PARTS Build) if(0 LESS num_build_warnings) message("${indent}${num_build_warnings} warning(s)") endif() if(NOT (0 EQUAL return_value AND 0 EQUAL num_build_errors)) submit(PARTS Done) message("${indent}${num_build_errors} error(s)") return() else() message("${indent}${target} was built successfully") endif() if(NOT append) set(append APPEND) endif() math(EXPR i "${i} + 1") endforeach() endif() if(MITK_BUILD_DOCUMENTATION) message("MITK build - Build documentation") list(APPEND CTEST_CUSTOM_WARNING_EXCEPTION ".*") ctest_build(TARGET doc BUILD "${CTEST_BINARY_DIRECTORY}/MITK-build" NUMBER_ERRORS num_doc_errors NUMBER_WARNINGS num_doc_warnings RETURN_VALUE return_value APPEND ) list(POP_BACK CTEST_CUSTOM_WARNING_EXCEPTION) submit(PARTS Build) if(0 LESS num_doc_warnings) message("${indent}${num_doc_warnings} warning(s)") endif() if(NOT (0 EQUAL return_value AND 0 EQUAL num_doc_errors)) submit(PARTS Done) message("${indent}${num_doc_errors} error(s)") return() else() message("${indent}Documentation was built successfully") endif() endif() message("Run unit tests...") set(CTEST_CONFIGURATION_TYPE "${CTEST_BUILD_CONFIGURATION}") ctest_test( BUILD "${CTEST_BINARY_DIRECTORY}/MITK-build" PARALLEL_LEVEL 4) submit(PARTS Test Done) message("Done") diff --git a/CMakeExternals/Boost.cmake b/CMakeExternals/Boost.cmake index 392f597f9f..208c32fcc9 100644 --- a/CMakeExternals/Boost.cmake +++ b/CMakeExternals/Boost.cmake @@ -1,333 +1,337 @@ #----------------------------------------------------------------------------- # Boost #----------------------------------------------------------------------------- include(mitkFunctionGetMSVCVersion) #[[ Sanity checks ]] if(DEFINED BOOST_ROOT AND NOT EXISTS ${BOOST_ROOT}) message(FATAL_ERROR "BOOST_ROOT variable is defined but corresponds to non-existing directory") endif() string(REPLACE "^^" ";" MITK_USE_Boost_LIBRARIES "${MITK_USE_Boost_LIBRARIES}") set(proj Boost) set(proj_DEPENDENCIES ) set(Boost_DEPENDS ${proj}) if(NOT DEFINED BOOST_ROOT AND NOT MITK_USE_SYSTEM_Boost) #[[ Reset variables. ]] set(patch_cmd "") set(configure_cmd "") set(install_cmd "") set(BOOST_ROOT ${ep_prefix}) if(WIN32) set(BOOST_LIBRARYDIR "${BOOST_ROOT}/lib") endif() #[[ If you update Boost, make sure that the FindBoost module of the minimum required version of CMake supports the new version of Boost. In case you are using a higher version of CMake, download at least the source code of the minimum required version of CMake to look into the right version of the FindBoost module: /share/cmake-/Modules/FindBoost.cmake Search for a list called _Boost_KNOWN_VERSIONS. If the new version is not included in this list, you have three options: * Update the minimum required version of CMake. This may require adaptions of other parts of our CMake scripts and has the most impact on other MITK developers. Yet this is the safest and cleanest option. * Set Boost_ADDITIONAL_VERSIONS (see the documentation of the FindBoost module). As Boost libraries and dependencies between them are hard-coded in the FindBoost module only for known versions, this may cause trouble for other MITK developers relying on new components of Boost or components with changed dependencies. * Copy a newer version of the FindBoost module into our CMake directory. Our CMake directory has a higher precedence than the default CMake module directory. Doublecheck if the minimum required version of CMake is able to process the newer version of the FindBoost module. Also, DO NOT FORGET to mention this option right above the call of cmake_minimum_required() in the top-level CMakeLists.txt file AND in this file right above the set(url) command below so if we update the minimum required version of CMake or use another option in the future, we do not forget to remove our copy of the FindBoost module again. ]] set(url "${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/boost_1_74_0.tar.gz") set(md5 3c8fb92ce08b9ad5a5f0b35731ac2c8e) if(MITK_USE_Boost_LIBRARIES) #[[ Boost has a two-step build process. In the first step, a bootstrap script is called to build b2, an executable that is used to actually build Boost in the second step. The bootstrap script expects a toolset (compiler) argument that is used to build the b2 executable. The scripts and their expected argument format differ between Windows and Unix. ]] if(WIN32) mitkFunctionGetMSVCVersion() if(VISUAL_STUDIO_VERSION_MINOR EQUAL 0) #[[ Use just the major version in the toolset name. ]] set(bootstrap_args vc${VISUAL_STUDIO_VERSION_MAJOR}) elseif(VISUAL_STUDIO_VERSION_MAJOR EQUAL 14 AND VISUAL_STUDIO_VERSION_MINOR LESS 20) #[[ Assume Visual Studio 2017. ]] set(bootstrap_args vc${VISUAL_STUDIO_VERSION_MAJOR}1) elseif(VISUAL_STUDIO_VERSION_MAJOR EQUAL 14 AND VISUAL_STUDIO_VERSION_MINOR LESS 30) #[[ Assume Visual Studio 2019. ]] set(bootstrap_args vc${VISUAL_STUDIO_VERSION_MAJOR}2) else() #[[ Fallback to the generic case. Be prepared to add another elseif branch above for future versions of Visual Studio. ]] set(bootstrap_args vc${VISUAL_STUDIO_VERSION_MAJOR}) endif() else() #[[ We support GCC and Clang on Unix. On macOS, the toolset must be set to clang. The actual compiler for all of these toolkits is set further below, after the bootstrap script but before b2. ]] if(CMAKE_CXX_COMPILER_ID STREQUAL GNU) set(toolset gcc) elseif(CMAKE_CXX_COMPILER_ID STREQUAL Clang OR APPLE) set(toolset clang) endif() if(toolset) set(bootstrap_args --with-toolset=${toolset}) endif() #[[ At least give it a shot if the toolset is something else and let the bootstrap script decide on the toolset by not passing any argument. ]] endif() #[[ The call of b2 is more complex. b2 arguments are grouped into options and properties. Options follow the standard format for arguments while properties are plain key-value pairs. ]] set(b2_options --build-dir= --stagedir= --ignore-site-config #[[ Build independent of any site.config file ]] -q #[[ Stop at first error ]] ) if(APPLE AND CMAKE_OSX_SYSROOT) #[[ Specify the macOS platform SDK to be used. ]] list(APPEND b2_options --sysroot=${CMAKE_OSX_SYSROOT}) endif() foreach(lib ${MITK_USE_Boost_LIBRARIES}) list(APPEND b2_options --with-${lib}) endforeach() set(b2_properties threading=multi runtime-link=shared "cxxflags=${MITK_CXX14_FLAG} ${CMAKE_CXX_FLAGS}" ) if(CMAKE_SIZEOF_VOID_P EQUAL 8) list(APPEND b2_properties address-model=64) else() list(APPEND b2_properties address-model=32) endif() if(BUILD_SHARED_LIBS) list(APPEND b2_properties link=shared) else() list(APPEND b2_properties link=static) endif() list(APPEND b2_properties "\ $<$:variant=debug>\ $<$:variant=release>\ $<$:variant=release>\ $<$:variant=release>") if(WIN32) set(bootstrap_cmd if not exist b2.exe \( call bootstrap.bat ${bootstrap_args} \)) set(b2_cmd b2 ${b2_options} ${b2_properties} stage) else() set(bootstrap_cmd #[[ test -e ./b2 || ]] ./bootstrap.sh ${bootstrap_args}) set(b2_cmd ./b2 ${b2_options} ${b2_properties} stage) #[[ We already told Boost if we want to use GCC or Clang but so far we were not able to specify the exact same compiler we set in CMake when configuring the MITK superbuild for the first time. For example, this can be different from the system default when multiple versions of the same compiler are installed at the same time. The bootstrap script creates a configuration file for b2 that should be modified if necessary before b2 is called. We look for a line like using gcc ; and replace it with something more specific like using gcc : : /usr/bin/gcc-7.3 ; - We use the stream editor sed for the replacement but as macOS is + We use the stream editor sed for the replacement but since macOS is based on BSD Unix, we use the limited but portable BSD syntax instead of the more powerful GNU syntax. We also use | instead of the more commonly used / separator for sed because the replacement - contains slashes. ]] + contains slashes. - if(toolset) + 2021/06/15: The custom project-config.jam does not work well with + SDK paths on macOS anymore, so we use a custom project-config.jam + only on Linux for now. ]] + + if(toolset AND NOT APPLE) set(configure_cmd sed -i.backup "\ s|\ using[[:space:]][[:space:]]*${toolset}[[:space:]]*$|\ using ${toolset} : : ${CMAKE_CXX_COMPILER} $|\ g" /project-config.jam ) endif() endif() endif() if(WIN32) set(dummy_cmd cd .) else() set(dummy_cmd true) #[[ "cd ." does not work reliably ]] endif() if(NOT patch_cmd) set(patch_cmd ${dummy_cmd}) #[[ Do nothing ]] endif() if(NOT configure_cmd) set(configure_cmd ${dummy_cmd}) #[[ Do nothing ]] endif() if(WIN32) set(install_cmd if not exist $ \( ${CMAKE_COMMAND} -E copy_directory /boost /include/boost \) ) else() set(install_cmd # test -e /include/boost/config.hpp || ${CMAKE_COMMAND} -E copy_directory /boost /include/boost ) endif() ExternalProject_Add(${proj} URL ${url} URL_MD5 ${md5} PATCH_COMMAND ${patch_cmd} CONFIGURE_COMMAND ${configure_cmd} BUILD_COMMAND "" INSTALL_COMMAND ${install_cmd} ) ExternalProject_Add_Step(${proj} bootstrap COMMAND ${bootstrap_cmd} DEPENDEES patch DEPENDERS configure WORKING_DIRECTORY ) ExternalProject_Add_Step(${proj} b2 COMMAND ${b2_cmd} DEPENDEES bootstrap DEPENDERS build WORKING_DIRECTORY ) if(WIN32) #[[ Reuse already extracted files. ]] set(stamp_dir ${ep_prefix}/src/Boost-stamp) configure_file( ${CMAKE_CURRENT_LIST_DIR}/extract-Boost.replacement.cmake ${stamp_dir}/extract-Boost.replacement.cmake COPYONLY) ExternalProject_Add_Step(${proj} pre_download COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/Boost-pre_download.cmake DEPENDEES mkdir DEPENDERS download WORKING_DIRECTORY ${stamp_dir} ) endif() set(install_manifest_dependees install) if(MITK_USE_Boost_LIBRARIES) if(WIN32) #[[ Move DLLs from lib to bin directory. ]] ExternalProject_Add_Step(${proj} post_install COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/Boost-post_install-WIN32.cmake DEPENDEES install WORKING_DIRECTORY /lib ) set(install_manifest_dependees post_install) elseif(APPLE) #[[ Boost does not follow the common practice of either using rpath or absolute paths for referencing dependencies. We have to use the install_name_tool to fix this. ]] ExternalProject_Add_Step(${proj} post_install COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/Boost-post_install-APPLE.cmake DEPENDEES install WORKING_DIRECTORY /lib ) set(install_manifest_dependees post_install) endif() endif() ExternalProject_Add_Step(${proj} install_manifest COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_LIST_DIR}/Boost-install_manifest.cmake DEPENDEES ${install_manifest_dependees} WORKING_DIRECTORY ${ep_prefix} ) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() diff --git a/CMakeExternals/DCMQI.cmake b/CMakeExternals/DCMQI.cmake index dc0e078434..933a8a4f69 100644 --- a/CMakeExternals/DCMQI.cmake +++ b/CMakeExternals/DCMQI.cmake @@ -1,65 +1,65 @@ #----------------------------------------------------------------------------- # DCMQI #----------------------------------------------------------------------------- if(MITK_USE_DCMQI) # Sanity checks if(DEFINED DCMQI_DIR AND NOT EXISTS ${DCMQI_DIR}) message(FATAL_ERROR "DCMQI_DIR variable is defined but corresponds to non-existing directory") endif() set(proj DCMQI) set(proj_DEPENDENCIES DCMTK ITK) set(DCMQI_DEPENDS ${proj}) if(NOT DEFINED DCMQI_DIR) set(additional_cmake_args) if(CTEST_USE_LAUNCHERS) list(APPEND additional_cmake_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() mitk_query_custom_ep_vars() ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} GIT_REPOSITORY https://github.com/QIICR/dcmqi.git - GIT_TAG 83f498e9 + GIT_TAG v1.2.4 UPDATE_COMMAND "" INSTALL_COMMAND "" CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${additional_cmake_args} #-DCMAKE_INSTALL_PREFIX:PATH= -DBUILD_SHARED_LIBS:BOOL=ON -DDCMQI_BUILD_APPS:BOOL=OFF -DDCMTK_DIR:PATH=${DCMTK_DIR} -DITK_DIR:PATH=${ITK_DIR} -DITK_NO_IO_FACTORY_REGISTER_MANAGER:BOOL=ON -DDCMQI_SUPERBUILD:BOOL=OFF -DDCMQI_CMAKE_CXX_STANDARD:STRING=14 ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) ExternalProject_Get_Property(${proj} binary_dir) set(DCMQI_DIR ${binary_dir}) #set(${proj}_DIR ${ep_prefix}) #message(${proj}_DIR: ${${proj}_DIR}) #mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/CMakeExternals/DCMTK.cmake b/CMakeExternals/DCMTK.cmake index 4429abba21..2f00eea2ef 100644 --- a/CMakeExternals/DCMTK.cmake +++ b/CMakeExternals/DCMTK.cmake @@ -1,70 +1,70 @@ #----------------------------------------------------------------------------- # DCMTK #----------------------------------------------------------------------------- if(MITK_USE_DCMTK) # Sanity checks if(DEFINED DCMTK_DIR AND NOT EXISTS ${DCMTK_DIR}) message(FATAL_ERROR "DCMTK_DIR variable is defined but corresponds to non-existing directory") endif() set(proj DCMTK) set(proj_DEPENDENCIES ) set(DCMTK_DEPENDS ${proj}) if(NOT DEFINED DCMTK_DIR) if(DCMTK_DICOM_ROOT_ID) set(DCMTK_CXX_FLAGS "${DCMTK_CXX_FLAGS} -DSITE_UID_ROOT=\\\"${DCMTK_DICOM_ROOT_ID}\\\"") set(DCMTK_C_FLAGS "${DCMTK_CXX_FLAGS} -DSITE_UID_ROOT=\\\"${DCMTK_DICOM_ROOT_ID}\\\"") endif() set(additional_args ) if(CTEST_USE_LAUNCHERS) list(APPEND additional_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() mitk_query_custom_ep_vars() ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} - URL ${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/dcmtk-3.6.5.tar.gz - URL_MD5 e19707f64ee5695c496b9c1e48e39d07 + URL ${MITK_THIRDPARTY_DOWNLOAD_PREFIX_URL}/dcmtk-3.6.6.tar.gz + URL_MD5 f815879d315b916366a9da71339c7575 CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${additional_args} "-DCMAKE_CXX_FLAGS:STRING=${CMAKE_CXX_FLAGS} ${DCMTK_CXX_FLAGS}" "-DCMAKE_C_FLAGS:STRING=${CMAKE_C_FLAGS} ${DCMTK_C_FLAGS}" -DDCMTK_ENABLE_BUILTIN_DICTIONARY:BOOL=ON -DDCMTK_ENABLE_CXX11:BOOL=ON -DDCMTK_ENABLE_STL:BOOL=ON -DDCMTK_WITH_DOXYGEN:BOOL=OFF -DDCMTK_WITH_ZLIB:BOOL=OFF # see bug #9894 -DDCMTK_WITH_OPENSSL:BOOL=OFF # see bug #9894 -DDCMTK_WITH_PNG:BOOL=OFF # see bug #9894 -DDCMTK_WITH_TIFF:BOOL=OFF # see bug #9894 -DDCMTK_WITH_XML:BOOL=OFF # see bug #9894 -DDCMTK_WITH_ICONV:BOOL=OFF # see bug #9894 -DDCMTK_WITH_ICU:BOOL=OFF # see T26438 ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) set(DCMTK_DIR ${ep_prefix}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() endif() diff --git a/CMakeExternals/ExternalProjectList.cmake b/CMakeExternals/ExternalProjectList.cmake index 7eeb49a07a..67c827c45b 100644 --- a/CMakeExternals/ExternalProjectList.cmake +++ b/CMakeExternals/ExternalProjectList.cmake @@ -1,31 +1,31 @@ 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 cpprestsdk OFF DEPENDS Boost ZLIB ADVANCED) mitkFunctionAddExternalProject(NAME OpenMesh OFF) 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") if(MITK_USE_Qt5) mitkFunctionAddExternalProject(NAME Qwt ON ADVANCED DEPENDS Qt5) endif() -if(UNIX) +if(UNIX AND NOT APPLE) mitkFunctionAddExternalProject(NAME PCRE OFF ADVANCED NO_PACKAGE) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE DEPENDS PCRE) -else() +elseif(WIN32) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE) endif() diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/GettingToKnow/CMakeFAQ.dox b/Documentation/Doxygen/3-DeveloperManual/Starting/GettingToKnow/CMakeFAQ.dox index b63ac30697..de0cfad7a0 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/GettingToKnow/CMakeFAQ.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/GettingToKnow/CMakeFAQ.dox @@ -1,41 +1,44 @@ /** \page CMAKE_FAQ CMake FAQ \section CMAKE_FAQ_General A general comment MITK uses %CMake for configuration. If you want to develop either using MITK as a toolkit or by extending the capabilities of the applications provided by us, we recommend using %CMake for your own project too. While it might be possible to use MITK in conjunction with other options, such as QMake or setting up your own project manually it will invariably involve a lot of work and probably hacks as well. As we do have no experience with this, we will not be able to help you. Be prepared to do a lot of tweaking on by yourself. This guide does not try to give a general introduction to CMake, instead it tries to answer some basic questions that might arise for those new to %CMake, to help you get started on MITK. For a more comprehensive introduction on %CMake see here. We will assume in this guide, that the path to your source is /MITK/. \section CMAKE_FAQ_Question Basic Questions \subsection CMAKE_FAQ_Question_WhereGetIt Where do I get CMake and what version do I need? See \ref BuildInstructions_Prerequisites. \subsection CMAKE_FAQ_Question_NewPluginNothing I coded a new plugin for the Workbench and nothing happened. Why? The correct way to create a new plugin is described in \ref NewPluginOnly. Do note that you need to move the source to the MITK/Plugins directory and you will have to add the plugin to the config file (most likely MITK/Plugins/Plugins.cmake). After that see \ref CMAKE_FAQ_Question_HowDoIActivatePlugin. \subsection CMAKE_FAQ_Question_HowDoIActivatePlugin I want to use a plugin, how do I activate it?
    -
  1. Start %CMake -
  2. Configure -
  3. Set the variable MITK_BUILD_\ to TRUE -
  4. Configure again -
  5. Generate -
  6. Start a build using your development environment +
  7. Start %CMake in the `MITK-build` directory inside your superbuild folder. E.g. + `cd ~//MITK-build` + `ccmake .` or `cmake-gui .` +
  8. Optional: *Configure* to see all %CMake variables. +
  9. Find the variable `MITK_BUILD_` and activate it. The `` refers to the package name of the plugin.
    + E.g. the plugin-id (also, the package name) `org.mitk.core.ext` is the sub-directory of same name in plugins directory (`~/MITK/Plugins/`). +
  10. *Configure*, again +
  11. *Generate* +
  12. Rebuild the `MITK-build` target using your development environment.
\subsection CMAKE_FAQ_Question_HowDoIActivateModule I want to use a module, how do I activate it? Modules are build automatically if a plugin that requires them is activated. See \ref CMAKE_FAQ_Question_HowDoIActivatePlugin. \subsection CMAKE_FAQ_Question_HowOwnToolkits MITK always downloads the toolkits, but I want to use my own. This is covered in \ref HowToNewProjectCustomizingMITKConfigure. \subsection CMAKE_FAQ_Question_HowOwnProjectMITK I want to use an MITK plugin in my own project but I can not find it. See \ref HowToNewProjectAddingMITKFunctionality. */ diff --git a/Modules/Classification/CLUtilities/test/files.cmake b/Modules/Classification/CLUtilities/test/files.cmake index 579c0e0670..a2d8b7b28d 100644 --- a/Modules/Classification/CLUtilities/test/files.cmake +++ b/Modules/Classification/CLUtilities/test/files.cmake @@ -1,18 +1,18 @@ set(MODULE_TESTS - mitkGIFCooc2Test - mitkGIFCurvatureStatisticTest - mitkGIFFirstOrderHistogramStatisticsTest - mitkGIFFirstOrderNumericStatisticsTest - mitkGIFFirstOrderStatisticsTest - mitkGIFGreyLevelDistanceZoneTest - mitkGIFGreyLevelSizeZoneTest - mitkGIFImageDescriptionFeaturesTest - mitkGIFIntensityVolumeHistogramTest - mitkGIFLocalIntensityTest - mitkGIFNeighbourhoodGreyToneDifferenceFeaturesTest - mitkGIFNeighbouringGreyLevelDependenceFeatureTest - mitkGIFVolumetricDensityStatisticsTest - mitkGIFVolumetricStatisticsTest + mitkGIFCooc2Test.cpp + mitkGIFCurvatureStatisticTest.cpp + mitkGIFFirstOrderHistogramStatisticsTest.cpp + mitkGIFFirstOrderNumericStatisticsTest.cpp + mitkGIFFirstOrderStatisticsTest.cpp + mitkGIFGreyLevelDistanceZoneTest.cpp + mitkGIFGreyLevelSizeZoneTest.cpp + mitkGIFImageDescriptionFeaturesTest.cpp + mitkGIFIntensityVolumeHistogramTest.cpp + mitkGIFLocalIntensityTest.cpp + mitkGIFNeighbourhoodGreyToneDifferenceFeaturesTest.cpp + mitkGIFNeighbouringGreyLevelDependenceFeatureTest.cpp + mitkGIFVolumetricDensityStatisticsTest.cpp + mitkGIFVolumetricStatisticsTest.cpp #mitkSmoothedClassProbabilitesTest.cpp #mitkGlobalFeaturesTest.cpp ) diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h index b43386aefb..7c567a697e 100644 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h @@ -1,117 +1,124 @@ /*============================================================================ 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 mitkContourModelUtils_h #define mitkContourModelUtils_h #include #include #include #include namespace mitk { /** * \brief Helpful methods for working with contours and images * * */ class MITKCONTOURMODEL_EXPORT ContourModelUtils : public itk::Object { public: mitkClassMacroItkParent(ContourModelUtils, itk::Object); /** \brief Projects a contour onto an image point by point. Converts from world to index coordinates. \param slice \param contourIn3D \param correctionForIpSegmentation adds 0.5 to x and y index coordinates (difference between ipSegmentation and MITK contours) \param constrainToInside */ static ContourModel::Pointer ProjectContourTo2DSlice(const Image *slice, const ContourModel *contourIn3D, bool correctionForIpSegmentation, bool constrainToInside); /** \brief Projects a slice index coordinates of a contour back into world coordinates. \param sliceGeometry \param contourIn2D \param correctionForIpSegmentation subtracts 0.5 to x and y index coordinates (difference between ipSegmentation and MITK contours) */ static ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, const ContourModel *contourIn2D, bool correctionForIpSegmentation = false); /** \brief Fill a contour in a 2D slice with a specified pixel value. This version always uses the contour of time step 0 and fills the image. \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ static void FillContourInSlice(const ContourModel *projectedContour, Image *sliceImage, const Image* workingImage, int paintingPixelValue = 1); /** \brief Fill a contour in a 2D slice with a specified pixel value. This overloaded version uses the contour at the passed contourTimeStep to fill the passed image slice. \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ static void FillContourInSlice(const ContourModel *projectedContour, TimeStepType contourTimeStep, Image *sliceImage, const Image* workingImage, int paintingPixelValue = 1); /** - \brief Fills a image (filledImage) into another image (resultImage) by incorporating the rules of LabelSet-Images + \brief Fills the paintingPixelValue into every pixel of resultImage as indicated by filledImage. + If a LableSet image is specified it also by incorporating the rules of LabelSet images when filling the content. + \param filledImage Pointer to the image content that should be checked to decied of a pixel in resultImage should + be filled with paintingPixelValue or not. + \param resultImage Pointer to the image content that should be overwritten guided by the content of filledImage. + \param image Pointer to an mitk image that allows to define the LabelSet image which states steer the filling process. + If an LabelSet instance is passed its states (e.g. locked labels etc...) will be used. If nullptr or an normal image + is passed, then simply any pixel position indicated by filledImage will be overwritten. \param paintingPixelValue the pixelvalue/label that should be used in the result image when filling. \param fillForegroundThreshold The threshold value that decides if a pixel in the filled image counts as foreground (>=fillForegroundThreshold) or not. */ static void FillSliceInSlice(vtkSmartPointer filledImage, vtkSmartPointer resultImage, const Image* image, int paintingPixelValue, double fillForegroundThreshold = 1.0); /** \brief Move the contour in time step 0 to to a new contour model at the given time step. */ static ContourModel::Pointer MoveZerothContourTimeStep(const ContourModel *contour, TimeStepType timeStep); /** \brief Retrieves the active pixel value of a (labelset) image. If the image is basic image, the pixel value 1 (one) will be returned. If the image is actually a labelset image, the pixel value of the active label of the active layer will be returned. \param workingImage The (labelset) image to retrieve the active pixel value of. */ static int GetActivePixelValue(const Image* workingImage); protected: ContourModelUtils(); ~ContourModelUtils() override; }; } #endif diff --git a/Modules/ContourModel/IO/mitkContourModelWriter.h b/Modules/ContourModel/IO/mitkContourModelWriter.h index 3a1b587533..0add146a3b 100644 --- a/Modules/ContourModel/IO/mitkContourModelWriter.h +++ b/Modules/ContourModel/IO/mitkContourModelWriter.h @@ -1,171 +1,158 @@ /*============================================================================ 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_CONTOURMODEL_WRITER__H_ #define _MITK_CONTOURMODEL_WRITER__H_ #include #include // DEPRECATED #include namespace mitk { /** * @brief XML-based writer for mitk::ContourModels * * XML-based writer for mitk::ContourModels. Multiple ContourModels can be written in * a single XML file by simply setting multiple inputs to the filter. * * The xml file will look like: * * * * * * * * * * * * * * * * * * * * * @ingroup MitkContourModelModule */ - class TimeSlicedGeometry; - class ContourModelWriter : public mitk::AbstractFileWriter { public: explicit ContourModelWriter(bool writeXMLHeader = true); ~ContourModelWriter() override; using AbstractFileWriter::Write; void Write() override; protected: ContourModelWriter(const ContourModelWriter &other); mitk::ContourModelWriter *Clone() const override; /** * Converts an arbitrary type to a string. The type has to * support the << operator. This works fine at least for integral * data types as float, int, long etc. * @param value the value to convert * @returns the string representation of value */ template std::string ConvertToString(T value); /** * Writes an XML representation of the given point set to * an outstream. The XML-Header an root node is not included! * @param contourModel the point set to be converted to xml * @param out the stream to write to. */ void WriteXML(const mitk::ContourModel *contourModel, std::ostream &out); /** * Writes the geometry information of the TimeGeometry to an outstream. * The root tag is not included. * @param geometry the TimeGeometry of the contour. * @param out the stream to write to. */ void WriteGeometryInformation(const mitk::TimeGeometry *geometry, std::ostream &out); - /** - * Writes the geometry information of the TimeGeometry to an outstream. - * The root tag is not included. - * @param geometry the TimeGeometry of the contour. - * @param out the stream to write to. - * - * \deprecatedSince{2013_09} Please use TimeGeometry instead of TimeSlicedGeometry. For more information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - DEPRECATED(void WriteGeometryInformation(const mitk::TimeSlicedGeometry *geometry, std::ostream &out)); - /** * Writes an standard xml header to the given stream. * @param file the stream in which the header is written. */ void WriteXMLHeader(std::ostream &file); /** Write a start element tag */ void WriteStartElement(const char *const tag, std::ostream &file); void WriteStartElementWithAttribut(const char *const tag, std::vector attributes, std::vector values, std::ostream &file); /** * Write an end element tag * End-Elements following character data should pass indent = false. */ void WriteEndElement(const char *const tag, std::ostream &file, const bool &indent = true); /** Write character data inside a tag. */ void WriteCharacterData(const char *const data, std::ostream &file); /** Write a start element tag */ void WriteStartElement(std::string &tag, std::ostream &file); /** Write an end element tag */ void WriteEndElement(std::string &tag, std::ostream &file, const bool &indent = true); /** Write character data inside a tag. */ void WriteCharacterData(std::string &data, std::ostream &file); /** Writes empty spaces to the stream according to m_IndentDepth and m_Indent */ void WriteIndent(std::ostream &file); bool m_WriteXMLHeader; unsigned int m_IndentDepth; unsigned int m_Indent; public: static const char *XML_CONTOURMODEL; static const char *XML_HEAD; static const char *XML_GEOMETRY_INFO; static const char *XML_DATA; static const char *XML_TIME_STEP; static const char *XML_CONTROL_POINTS; static const char *XML_POINT; static const char *XML_X; static const char *XML_Y; static const char *XML_Z; }; } #endif diff --git a/Modules/Core/include/mitkBaseData.h b/Modules/Core/include/mitkBaseData.h index 539a9e35fd..c43122796f 100644 --- a/Modules/Core/include/mitkBaseData.h +++ b/Modules/Core/include/mitkBaseData.h @@ -1,422 +1,388 @@ /*============================================================================ 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 BASEDATA_H_HEADER_INCLUDED_C1EBB6FA #define BASEDATA_H_HEADER_INCLUDED_C1EBB6FA #include #include "mitkBaseProcess.h" #include "mitkIdentifiable.h" #include "mitkIPropertyOwner.h" #include "mitkOperationActor.h" #include "mitkPropertyList.h" #include "mitkTimeGeometry.h" #include namespace mitk { // class BaseProcess; //##Documentation //## @brief Base of all data objects //## //## Base of all data objects, e.g., images, contours, surfaces etc. Inherits //## from itk::DataObject and thus can be included in a pipeline. //## Inherits also from OperationActor and can be used as a destination for Undo //## @remark Some derived classes may support the persistence of the Identifiable UID. //** but it is no guaranteed feature and also depends on the format the data is stored in //** as not all formats support storing of meta information. Please check the documentation //** of the IFileReader and IFileWriter classes to see if the ID-persistance is supported. //** MITK SceneIO supports the UID persistance for all BaseData derived classes. //## @ingroup Data class MITKCORE_EXPORT BaseData : public itk::DataObject, public OperationActor, public Identifiable, public IPropertyOwner { public: mitkClassMacroItkParent(BaseData, itk::DataObject); // IPropertyProvider BaseProperty::ConstPointer GetConstProperty(const std::string &propertyKey, const std::string &contextName = "", bool fallBackOnDefaultContext = true) const override; std::vector GetPropertyKeys(const std::string &contextName = "", bool includeDefaultContext = false) const override; std::vector GetPropertyContextNames() const override; // IPropertyOwner BaseProperty * GetNonConstProperty(const std::string &propertyKey, const std::string &contextName = "", bool fallBackOnDefaultContext = true) override; void SetProperty(const std::string &propertyKey, BaseProperty *property, const std::string &contextName = "", bool fallBackOnDefaultContext = false) override; void RemoveProperty(const std::string &propertyKey, const std::string &contextName = "", bool fallBackOnDefaultContext = false) override; /** * \brief Return the TimeGeometry of the data as const pointer. * * \warning No update will be called. Use GetUpdatedGeometry() if you cannot * be sure that the geometry is up-to-date. * * Normally used in GenerateOutputInformation of subclasses of BaseProcess. */ const mitk::TimeGeometry *GetTimeGeometry() const { return m_TimeGeometry.GetPointer(); } - /** - * \brief Return the TimeGeometry of the data as const pointer. - * - * \warning No update will be called. Use GetUpdatedGeometry() if you cannot - * be sure that the geometry is up-to-date. - * - * Normally used in GenerateOutputInformation of subclasses of BaseProcess. - * \deprecatedSince{2013_09} Please use GetTimeGeometry instead: For additional information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - DEPRECATED(const mitk::TimeGeometry *GetTimeSlicedGeometry() const) { return GetTimeGeometry(); } /** * @brief Return the TimeGeometry of the data as pointer. * * \warning No update will be called. Use GetUpdatedGeometry() if you cannot * be sure that the geometry is up-to-date. * * Normally used in GenerateOutputInformation of subclasses of BaseProcess. */ mitk::TimeGeometry *GetTimeGeometry() { return m_TimeGeometry.GetPointer(); } /** * @brief Return the TimeGeometry of the data. * * The method does not simply return the value of the m_TimeGeometry * member. Before doing this, it makes sure that the TimeGeometry * is up-to-date (by setting the update extent to largest possible and * calling UpdateOutputInformation). */ const mitk::TimeGeometry *GetUpdatedTimeGeometry(); - /** - * @brief Return the TimeGeometry of the data. - * - * The method does not simply return the value of the m_TimeGeometry - * member. Before doing this, it makes sure that the TimeGeometry - * is up-to-date (by setting the update extent to largest possible and - * calling UpdateOutputInformation). - * \deprecatedSince{2013_09} Please use GetUpdatedTimeGeometry instead: For additional information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - DEPRECATED(const mitk::TimeGeometry *GetUpdatedTimeSliceGeometry()) { return GetUpdatedTimeGeometry(); } /** * \brief Expands the TimeGeometry to a number of TimeSteps. * * The method expands the TimeGeometry to the given number of TimeSteps, * filling newly created elements with empty geometries. Sub-classes should override * this method to handle the elongation of their data vectors, too. * Note that a shrinking is neither possible nor intended. */ virtual void Expand(unsigned int timeSteps); /** * \brief Return the BaseGeometry of the data at time \a t. * * The method does not simply return * m_TimeGeometry->GetGeometry(t). * Before doing this, it makes sure that the BaseGeometry is up-to-date * (by setting the update extent appropriately and calling * UpdateOutputInformation). * * @todo Appropriate setting of the update extent is missing. */ const mitk::BaseGeometry *GetUpdatedGeometry(int t = 0); //##Documentation //## @brief Return the geometry, which is a TimeGeometry, of the data //## as non-const pointer. //## //## \warning No update will be called. Use GetUpdatedGeometry() if you cannot //## be sure that the geometry is up-to-date. //## //## Normally used in GenerateOutputInformation of subclasses of BaseProcess. mitk::BaseGeometry *GetGeometry(int t = 0) const { if (m_TimeGeometry.IsNull()) return nullptr; return m_TimeGeometry->GetGeometryForTimeStep(t); } //##Documentation //## @brief Update the information for this BaseData (the geometry in particular) //## so that it can be used as an output of a BaseProcess. //## //## This method is used in the pipeline mechanism to propagate information and //## initialize the meta data associated with a BaseData. Any implementation //## of this method in a derived class is assumed to call its source's //## BaseProcess::UpdateOutputInformation() which determines modified //## times, LargestPossibleRegions, and any extra meta data like spacing, //## origin, etc. Default implementation simply call's it's source's //## UpdateOutputInformation(). //## \note Implementations of this methods in derived classes must take care //## that the geometry is updated by calling //## GetTimeGeometry()->UpdateInformation() //## \em after calling its source's BaseProcess::UpdateOutputInformation(). void UpdateOutputInformation() override; //##Documentation //## @brief Set the RequestedRegion to the LargestPossibleRegion. //## //## This forces a filter to produce all of the output in one execution //## (i.e. not streaming) on the next call to Update(). void SetRequestedRegionToLargestPossibleRegion() override = 0; //##Documentation //## @brief Determine whether the RequestedRegion is outside of the BufferedRegion. //## //## This method returns true if the RequestedRegion //## is outside the BufferedRegion (true if at least one pixel is //## outside). This is used by the pipeline mechanism to determine //## whether a filter needs to re-execute in order to satisfy the //## current request. If the current RequestedRegion is already //## inside the BufferedRegion from the previous execution (and the //## current filter is up to date), then a given filter does not need //## to re-execute bool RequestedRegionIsOutsideOfTheBufferedRegion() override = 0; //##Documentation //## @brief Verify that the RequestedRegion is within the LargestPossibleRegion. //## //## If the RequestedRegion is not within the LargestPossibleRegion, //## then the filter cannot possibly satisfy the request. This method //## returns true if the request can be satisfied (even if it will be //## necessary to process the entire LargestPossibleRegion) and //## returns false otherwise. This method is used by //## PropagateRequestedRegion(). PropagateRequestedRegion() throws a //## InvalidRequestedRegionError exception if the requested region is //## not within the LargestPossibleRegion. bool VerifyRequestedRegion() override = 0; //##Documentation //## @brief Copy information from the specified data set. //## //## This method is part of the pipeline execution model. By default, a //## BaseProcess will copy meta-data from the first input to all of its //## outputs. See ProcessObject::GenerateOutputInformation(). Each //## subclass of DataObject is responsible for being able to copy //## whatever meta-data it needs from another DataObject. //## The default implementation of this method copies the time sliced geometry //## and the property list of an object. If a subclass overrides this //## method, it should always call its superclass' version. void CopyInformation(const itk::DataObject *data) override; //##Documentation //## @brief Check whether the data has been initialized, i.e., //## at least the Geometry and other header data has been set //## //## \warning Set to \a true by default for compatibility reasons. //## Set m_Initialized=false in constructors of sub-classes that //## support distinction between initialized and uninitialized state. virtual bool IsInitialized() const; //##Documentation //## @brief Calls ClearData() and InitializeEmpty(); //## \warning Only use in subclasses that reimplemented these methods. //## Just calling Clear from BaseData will reset an object to a not initialized, //## invalid state. virtual void Clear(); //##Documentation //## @brief Check whether object contains data (at //## a specified time), e.g., a set of points may be empty //## //## \warning Returns IsInitialized()==false by default for //## compatibility reasons. Override in sub-classes that //## support distinction between empty/non-empty state. virtual bool IsEmptyTimeStep(unsigned int t) const; //##Documentation //## @brief Check whether object contains data (at //## least at one point in time), e.g., a set of points //## may be empty //## //## \warning Returns IsInitialized()==false by default for //## compatibility reasons. Override in sub-classes that //## support distinction between empty/non-empty state. virtual bool IsEmpty() const; //##Documentation //## @brief Set the requested region from this data object to match the requested //## region of the data object passed in as a parameter. //## //## This method is implemented in the concrete subclasses of BaseData. void SetRequestedRegion(const itk::DataObject *data) override = 0; //##Documentation //##@brief overwrite if the Data can be called by an Interactor (StateMachine). //## //## Empty by default. Overwrite and implement all the necessary operations here //## and get the necessary information from the parameter operation. void ExecuteOperation(Operation *operation) override; /** * \brief Set the BaseGeometry of the data, which will be referenced (not copied!). * Assumes the data object has only 1 time step ( is a 3D object ) and creates a * new TimeGeometry which saves the given BaseGeometry. If an TimeGeometry has already * been set for the object, it will be replaced after calling this function. * * @warning This method will normally be called internally by the sub-class of BaseData * during initialization. * \sa SetClonedGeometry */ virtual void SetGeometry(BaseGeometry *aGeometry3D); /** * \brief Set the TimeGeometry of the data, which will be referenced (not copied!). * * @warning This method will normally be called internally by the sub-class of BaseData * during initialization. * \sa SetClonedTimeGeometry */ virtual void SetTimeGeometry(TimeGeometry *geometry); /** * \brief Set a clone of the provided Geometry as Geometry of the data. * Assumes the data object has only 1 time step ( is a 3D object ) and * creates a new TimeGeometry. If an TimeGeometry has already * been set for the object, it will be replaced after calling this function. * * \sa SetGeometry */ virtual void SetClonedGeometry(const BaseGeometry *aGeometry3D); /** * \brief Set a clone of the provided TimeGeometry as TimeGeometry of the data. * * \sa SetGeometry */ virtual void SetClonedTimeGeometry(const TimeGeometry *geometry); //##Documentation //## @brief Set a clone of the provided geometry as BaseGeometry of a given time step. //## //## \sa SetGeometry virtual void SetClonedGeometry(const BaseGeometry *aGeometry3D, unsigned int time); //##Documentation //## @brief Get the data's property list //## @sa GetProperty //## @sa m_PropertyList mitk::PropertyList::Pointer GetPropertyList() const; //##Documentation //## @brief Set the data's property list //## @sa SetProperty //## @sa m_PropertyList void SetPropertyList(PropertyList *propertyList); //##Documentation //## @brief Get the property (instance of BaseProperty) with key @a propertyKey from the PropertyList, //## and set it to this, respectively; //## @sa GetPropertyList //## @sa m_PropertyList //## @sa m_MapOfPropertyLists mitk::BaseProperty::Pointer GetProperty(const char *propertyKey) const; void SetProperty(const char *propertyKey, BaseProperty *property); //##Documentation //## @brief Convenience method for setting the origin of //## the BaseGeometry instances of all time steps //## //## \warning Geometries contained in the BaseGeometry will //## \em not be changed, e.g. in case the BaseGeometry is a //## SlicedGeometry3D the origin will \em not be propagated //## to the contained slices. The sub-class SlicedData //## does this for the case that the SlicedGeometry3D is //## evenly spaced. virtual void SetOrigin(const Point3D &origin); /** \brief Get the process object that generated this data object. * * If there is no process object, then the data object has * been disconnected from the pipeline, or the data object * was created manually. (Note: we cannot use the GetObjectMacro() * defined in itkMacro because the mutual dependency of * DataObject and ProcessObject causes compile problems. Also, * a forward reference smart pointer is returned, not a smart pointer, * because of the circular dependency between the process and data object.) * * GetSource() returns a SmartPointer and not a WeakPointer * because it is assumed the code calling GetSource() wants to hold a * long term reference to the source. */ itk::SmartPointer GetSource() const; //##Documentation //## @brief Get the number of time steps from the TimeGeometry //## As the base data has not a data vector given by itself, the number //## of time steps is defined over the time sliced geometry. In sub classes, //## a better implementation could be over the length of the data vector. unsigned int GetTimeSteps() const { return m_TimeGeometry->CountTimeSteps(); } //##Documentation //## @brief Get the modified time of the last change of the contents //## this data object or its geometry. unsigned long GetMTime() const override; /** * \sa itk::ProcessObject::Graft */ void Graft(const DataObject *) override; protected: BaseData(); BaseData(const BaseData &other); ~BaseData() override; //##Documentation //## \brief Initialize the TimeGeometry for a number of time steps. //## The TimeGeometry is initialized empty and evenly timed. //## In many cases it will be necessary to overwrite this in sub-classes. virtual void InitializeTimeGeometry(unsigned int timeSteps = 1); - /** - * \brief Initialize the TimeGeometry for a number of time steps. - * The TimeGeometry is initialized empty and evenly timed. - * In many cases it will be necessary to overwrite this in sub-classes. - * \deprecatedSince{2013_09} Please use GetUpdatedTimeGeometry instead: For additional information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - DEPRECATED(virtual void InitializeTimeSlicedGeometry(unsigned int timeSteps = 1)) - { - InitializeTimeGeometry(timeSteps); - } - //##Documentation //## @brief reset to non-initialized state, release memory virtual void ClearData(); //##Documentation //## @brief Pure virtual; Must be used in subclasses to get a data object to a //## valid state. Should at least create one empty object and call //## Superclass::InitializeTimeGeometry() to ensure an existing valid geometry virtual void InitializeEmpty() {} void PrintSelf(std::ostream &os, itk::Indent indent) const override; bool m_LastRequestedRegionWasOutsideOfTheBufferedRegion; mutable unsigned int m_SourceOutputIndexDuplicate; bool m_Initialized; private: //##Documentation //## @brief PropertyList, f.e. to hold pic-tags, tracking-data,.. //## PropertyList::Pointer m_PropertyList; TimeGeometry::Pointer m_TimeGeometry; }; } // namespace mitk #endif /* BASEDATA_H_HEADER_INCLUDED_C1EBB6FA */ diff --git a/Modules/Core/include/mitkBaseRenderer.h b/Modules/Core/include/mitkBaseRenderer.h index b4015ffd7a..909de3a27c 100644 --- a/Modules/Core/include/mitkBaseRenderer.h +++ b/Modules/Core/include/mitkBaseRenderer.h @@ -1,532 +1,523 @@ /*============================================================================ 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 BASERENDERER_H_HEADER_INCLUDED_C1CCA0F4 #define BASERENDERER_H_HEADER_INCLUDED_C1CCA0F4 #include "mitkCameraRotationController.h" #include "mitkDataStorage.h" #include "mitkPlaneGeometry.h" #include "mitkPlaneGeometryData.h" #include "mitkSliceNavigationController.h" #include "mitkTimeGeometry.h" #include "mitkBindDispatcherInteractor.h" #include "mitkDispatcher.h" #include #include #include #include -// DEPRECATED -#include - namespace mitk { class NavigationController; class SliceNavigationController; class CameraRotationController; class CameraController; class DataStorage; class Mapper; class BaseLocalStorageHandler; class KeyEvent; //##Documentation //## @brief Organizes the rendering process //## //## Organizes the rendering process. A Renderer contains a reference to a //## DataStorage and asks the mappers of the data objects to render //## the data into the renderwindow it is associated to. //## //## \#Render() checks if rendering is currently allowed by calling //## RenderWindow::PrepareRendering(). Initialization of a rendering context //## can also be performed in this method. //## //## The actual rendering code has been moved to \#Repaint() //## Both \#Repaint() and \#Update() are declared protected now. //## //## Note: Separation of the Repaint and Update processes (rendering vs //## creating a vtk prop tree) still needs to be worked on. The whole //## rendering process also should be reworked to use VTK based classes for //## both 2D and 3D rendering. //## @ingroup Renderer class MITKCORE_EXPORT BaseRenderer : public itk::Object { public: typedef std::map BaseRendererMapType; static BaseRendererMapType baseRendererMap; static BaseRenderer *GetInstance(vtkRenderWindow *renWin); static void AddInstance(vtkRenderWindow *renWin, BaseRenderer *baseRenderer); static void RemoveInstance(vtkRenderWindow *renWin); static BaseRenderer *GetByName(const std::string &name); static vtkRenderWindow *GetRenderWindowByName(const std::string &name); #pragma GCC visibility push(default) itkEventMacro(RendererResetEvent, itk::AnyEvent); #pragma GCC visibility pop /** Standard class typedefs. */ mitkClassMacroItkParent(BaseRenderer, itk::Object); BaseRenderer(const char *name = nullptr, vtkRenderWindow *renWin = nullptr); //##Documentation //## @brief MapperSlotId defines which kind of mapper (e.g. 2D or 3D) should be used. typedef int MapperSlotId; enum StandardMapperSlot { Standard2D = 1, Standard3D = 2 }; //##Documentation //## @brief Possible view directions for render windows. enum class ViewDirection { AXIAL = 0, SAGITTAL, CORONAL, THREE_D }; virtual void SetDataStorage(DataStorage *storage); ///< set the datastorage that will be used for rendering //##Documentation //## return the DataStorage that is used for rendering virtual DataStorage::Pointer GetDataStorage() const { return m_DataStorage.GetPointer(); } //##Documentation //## @brief Access the RenderWindow into which this renderer renders. vtkRenderWindow *GetRenderWindow() const { return m_RenderWindow; } vtkRenderer *GetVtkRenderer() const { return m_VtkRenderer; } //##Documentation //## @brief Returns the Dispatcher which handles Events for this BaseRenderer Dispatcher::Pointer GetDispatcher() const; //##Documentation //## @brief Default mapper id to use. static const MapperSlotId defaultMapper; //##Documentation //## @brief Do the rendering and flush the result. virtual void Paint(); //##Documentation //## @brief Initialize the RenderWindow. Should only be called from RenderWindow. virtual void Initialize(); //##Documentation //## @brief Called to inform the renderer that the RenderWindow has been resized. virtual void Resize(int w, int h); //##Documentation //## @brief Initialize the renderer with a RenderWindow (@a renderwindow). virtual void InitRenderer(vtkRenderWindow *renderwindow); //##Documentation //## @brief Set the initial size. Called by RenderWindow after it has become //## visible for the first time. virtual void InitSize(int w, int h); //##Documentation //## @brief Draws a point on the widget. //## Should be used during conferences to show the position of the remote mouse virtual void DrawOverlayMouse(Point2D &p2d); //##Documentation //## @brief Set/Get the WorldGeometry (m_WorldGeometry) for 3D and 2D rendering, that describing the //## (maximal) area to be rendered. //## //## Depending of the type of the passed BaseGeometry more or less information can be extracted: //## \li if it is a PlaneGeometry (which is a sub-class of BaseGeometry), m_CurrentWorldPlaneGeometry is //## also set to point to it. m_WorldTimeGeometry is set to nullptr. //## \li if it is a TimeGeometry, m_WorldTimeGeometry is also set to point to it. //## If m_WorldTimeGeometry contains instances of SlicedGeometry3D, m_CurrentWorldPlaneGeometry is set to //## one of geometries stored in the SlicedGeometry3D according to the value of m_Slice; otherwise //## a PlaneGeometry describing the top of the bounding-box of the BaseGeometry is set as the //## m_CurrentWorldPlaneGeometry. //## \li otherwise a PlaneGeometry describing the top of the bounding-box of the BaseGeometry //## is set as the m_CurrentWorldPlaneGeometry. m_WorldTimeGeometry is set to nullptr. //## @todo add calculation of PlaneGeometry describing the top of the bounding-box of the BaseGeometry //## when the passed BaseGeometry is not sliced. //## \sa m_WorldGeometry //## \sa m_WorldTimeGeometry //## \sa m_CurrentWorldPlaneGeometry virtual void SetWorldGeometry3D(const BaseGeometry *geometry); virtual void SetWorldTimeGeometry(const mitk::TimeGeometry *geometry); - /** - * \deprecatedSince{2013_09} Please use TimeGeometry instead of TimeSlicedGeometry. For more information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - DEPRECATED(void SetWorldGeometry3D(TimeSlicedGeometry *geometry)); - itkGetConstObjectMacro(WorldTimeGeometry, TimeGeometry); //##Documentation //## @brief Get the current 3D-worldgeometry (m_CurrentWorldGeometry) used for 3D-rendering itkGetConstObjectMacro(CurrentWorldGeometry, BaseGeometry); //##Documentation //## @brief Get the current 2D-worldgeometry (m_CurrentWorldPlaneGeometry) used for 2D-rendering itkGetConstObjectMacro(CurrentWorldPlaneGeometry, PlaneGeometry) /** * \deprecatedSince{2014_10} Please use GetCurrentWorldPlaneGeometry */ DEPRECATED(const PlaneGeometry *GetCurrentWorldGeometry2D()) { return GetCurrentWorldPlaneGeometry(); }; //##Documentation //## Calculates the bounds of the DataStorage (if it contains any valid data), //## creates a geometry from these bounds and sets it as world geometry of the renderer. //## //## Call this method to re-initialize the renderer to the current DataStorage //## (e.g. after loading an additional dataset), to ensure that the view is //## aligned correctly. //## \warning This is not implemented yet. virtual bool SetWorldGeometryToDataStorageBounds() { return false; } //##Documentation //## @brief Set/Get m_Slice which defines together with m_TimeStep the 2D geometry //## stored in m_WorldTimeGeometry used as m_CurrentWorldPlaneGeometry //## //## \sa m_Slice virtual void SetSlice(unsigned int slice); itkGetConstMacro(Slice, unsigned int); //##Documentation //## @brief Set/Get m_TimeStep which defines together with m_Slice the 2D geometry //## stored in m_WorldTimeGeometry used as m_CurrentWorldPlaneGeometry //## //## \sa m_TimeStep virtual void SetTimeStep(unsigned int timeStep); itkGetConstMacro(TimeStep, unsigned int); //##Documentation //## @brief Get the time-step of a BaseData object which //## exists at the time of the currently displayed content //## //## Returns -1 or mitk::BaseData::m_TimeSteps if there //## is no data at the current time. //## \sa GetTimeStep, m_TimeStep TimeStepType GetTimeStep(const BaseData *data) const; //##Documentation //## @brief Get the time in ms of the currently displayed content //## //## \sa GetTimeStep, m_TimeStep ScalarType GetTime() const; //##Documentation //## @brief SetWorldGeometry is called according to the geometrySliceEvent, //## which is supposed to be a SliceNavigationController::GeometrySendEvent virtual void SetGeometry(const itk::EventObject &geometrySliceEvent); //##Documentation //## @brief UpdateWorldGeometry is called to re-read the 2D geometry from the //## slice navigation controller virtual void UpdateGeometry(const itk::EventObject &geometrySliceEvent); //##Documentation //## @brief SetSlice is called according to the geometrySliceEvent, //## which is supposed to be a SliceNavigationController::GeometrySliceEvent virtual void SetGeometrySlice(const itk::EventObject &geometrySliceEvent); //##Documentation //## @brief SetTimeStep is called according to the geometrySliceEvent, //## which is supposed to be a SliceNavigationController::GeometryTimeEvent virtual void SetGeometryTime(const itk::EventObject &geometryTimeEvent); //##Documentation //## @brief Get a DataNode pointing to a data object containing the current 2D-worldgeometry // m_CurrentWorldPlaneGeometry (for 2D rendering) itkGetObjectMacro(CurrentWorldPlaneGeometryNode, DataNode) /** * \deprecatedSince{2014_10} Please use GetCurrentWorldPlaneGeometryNode */ DEPRECATED(DataNode *GetCurrentWorldGeometry2DNode()) { return GetCurrentWorldPlaneGeometryNode(); }; //##Documentation //## @brief Sets timestamp of CurrentWorldPlaneGeometry and forces so reslicing in that renderwindow void SendUpdateSlice(); //##Documentation //## @brief Get timestamp of last call of SetCurrentWorldPlaneGeometry unsigned long GetCurrentWorldPlaneGeometryUpdateTime() { return m_CurrentWorldPlaneGeometryUpdateTime; } /** * \deprecatedSince{2014_10} Please use GetCurrentWorldPlaneGeometryUpdateTime */ DEPRECATED(unsigned long GetCurrentWorldGeometry2DUpdateTime()) { return GetCurrentWorldPlaneGeometryUpdateTime(); }; //##Documentation //## @brief Get timestamp of last change of current TimeStep unsigned long GetTimeStepUpdateTime() { return m_TimeStepUpdateTime; } //##Documentation //## @brief Perform a picking: find the x,y,z world coordinate of a //## display x,y coordinate. //## @warning Has to be overwritten in subclasses for the 3D-case. //## //## Implemented here only for 2D-rendering virtual void PickWorldPoint(const Point2D &diplayPosition, Point3D &worldPosition) const = 0; /** \brief Determines the object (mitk::DataNode) closest to the current * position by means of picking * * \warning Implementation currently empty for 2D rendering; intended to be * implemented for 3D renderers */ virtual DataNode *PickObject(const Point2D & /*displayPosition*/, Point3D & /*worldPosition*/) const { return nullptr; } //##Documentation //## @brief Get the MapperSlotId to use. itkGetMacro(MapperID, MapperSlotId); itkGetConstMacro(MapperID, MapperSlotId); //##Documentation //## @brief Set the MapperSlotId to use. virtual void SetMapperID(MapperSlotId id); virtual int *GetSize() const; virtual int *GetViewportSize() const; void SetSliceNavigationController(SliceNavigationController *SlicenavigationController); itkGetObjectMacro(CameraController, CameraController); itkGetObjectMacro(SliceNavigationController, SliceNavigationController); itkGetObjectMacro(CameraRotationController, CameraRotationController); itkGetMacro(EmptyWorldGeometry, bool); //##Documentation //## @brief Tells if the displayed region is shifted and rescaled if the render window is resized. itkGetMacro(KeepDisplayedRegion, bool) //##Documentation //## @brief Tells if the displayed region should be shifted and rescaled if the render window is resized. itkSetMacro(KeepDisplayedRegion, bool); //##Documentation //## @brief get the name of the Renderer //## @note const char *GetName() const { return m_Name.c_str(); } //##Documentation //## @brief get the x_size of the RendererWindow //## @note int GetSizeX() const { return GetSize()[0]; } //##Documentation //## @brief get the y_size of the RendererWindow //## @note int GetSizeY() const { return GetSize()[1]; } const double *GetBounds() const; void RequestUpdate(); void ForceImmediateUpdate(); /** Returns number of mappers which are visible and have level-of-detail * rendering enabled */ unsigned int GetNumberOfVisibleLODEnabledMappers() const; //##Documentation //## @brief This method converts a display point to the 3D world index //## using the geometry of the renderWindow. void DisplayToWorld(const Point2D &displayPoint, Point3D &worldIndex) const; //##Documentation //## @brief This method converts a display point to the 2D world index, mapped onto the display plane //## using the geometry of the renderWindow. void DisplayToPlane(const Point2D &displayPoint, Point2D &planePointInMM) const; //##Documentation //## @brief This method converts a 3D world index to the display point //## using the geometry of the renderWindow. void WorldToDisplay(const Point3D &worldIndex, Point2D &displayPoint) const; //##Documentation //## @brief This method converts a 3D world index to the point on the viewport //## using the geometry of the renderWindow. void WorldToView(const Point3D &worldIndex, Point2D &viewPoint) const; //##Documentation //## @brief This method converts a 2D plane coordinate to the display point //## using the geometry of the renderWindow. void PlaneToDisplay(const Point2D &planePointInMM, Point2D &displayPoint) const; //##Documentation //## @brief This method converts a 2D plane coordinate to the point on the viewport //## using the geometry of the renderWindow. void PlaneToView(const Point2D &planePointInMM, Point2D &viewPoint) const; double GetScaleFactorMMPerDisplayUnit() const; Point2D GetDisplaySizeInMM() const; Point2D GetViewportSizeInMM() const; Point2D GetOriginInMM() const; itkGetConstMacro(ConstrainZoomingAndPanning, bool) virtual void SetConstrainZoomingAndPanning(bool constrain); /** * \brief Provides (1) world coordinates for a given mouse position and (2) * translates mousePosition to Display coordinates * \deprecated Map2DRendererPositionTo3DWorldPosition is deprecated. Please use DisplayToWorld instead. */ DEPRECATED(virtual Point3D Map2DRendererPositionTo3DWorldPosition(const Point2D &mousePosition) const); protected: ~BaseRenderer() override; //##Documentation //## @brief Call update of all mappers. To be implemented in subclasses. virtual void Update() = 0; vtkRenderWindow *m_RenderWindow; vtkRenderer *m_VtkRenderer; //##Documentation //## @brief MapperSlotId to use. Defines which kind of mapper (e.g., 2D or 3D) shoud be used. MapperSlotId m_MapperID; //##Documentation //## @brief The DataStorage that is used for rendering. DataStorage::Pointer m_DataStorage; //##Documentation //## @brief Timestamp of last call of Update(). unsigned long m_LastUpdateTime; //##Documentation //## @brief CameraController for 3D rendering //## @note preliminary. itk::SmartPointer m_CameraController; SliceNavigationController::Pointer m_SliceNavigationController; CameraRotationController::Pointer m_CameraRotationController; //##Documentation //## @brief Sets m_CurrentWorldPlaneGeometry virtual void SetCurrentWorldPlaneGeometry(const PlaneGeometry *geometry2d); /** * \deprecatedSince{2014_10} Please use SetCurrentWorldPlaneGeometry */ DEPRECATED(void SetCurrentWorldGeometry2D(PlaneGeometry *geometry2d)) { SetCurrentWorldPlaneGeometry(geometry2d); }; //##Documentation //## @brief Sets m_CurrentWorldGeometry virtual void SetCurrentWorldGeometry(const BaseGeometry *geometry); private: //##Documentation //## m_WorldTimeGeometry is set by SetWorldGeometry if the passed BaseGeometry is a //## TimeGeometry (or a sub-class of it). If it contains instances of SlicedGeometry3D, //## m_Slice and m_TimeStep (set via SetSlice and SetTimeStep, respectively) define //## which 2D geometry stored in m_WorldTimeGeometry (if available) //## is used as m_CurrentWorldPlaneGeometry. //## \sa m_CurrentWorldPlaneGeometry TimeGeometry::ConstPointer m_WorldTimeGeometry; //##Documentation //## Pointer to the current 3D-worldgeometry. BaseGeometry::ConstPointer m_CurrentWorldGeometry; //##Documentation //## Pointer to the current 2D-worldgeometry. The 2D-worldgeometry //## describes the maximal area (2D manifold) to be rendered in case we //## are doing 2D-rendering. //## It is const, since we are not allowed to change it (it may be taken //## directly from the geometry of an image-slice and thus it would be //## very strange when suddenly the image-slice changes its geometry). PlaneGeometry::Pointer m_CurrentWorldPlaneGeometry; //##Documentation //## Defines together with m_Slice which 2D geometry stored in m_WorldTimeGeometry //## is used as m_CurrentWorldPlaneGeometry: m_WorldTimeGeometry->GetPlaneGeometry(m_Slice, m_TimeStep). //## \sa m_WorldTimeGeometry unsigned int m_Slice; //##Documentation //## Defines together with m_TimeStep which 2D geometry stored in m_WorldTimeGeometry //## is used as m_CurrentWorldPlaneGeometry: m_WorldTimeGeometry->GetPlaneGeometry(m_Slice, m_TimeStep). //## \sa m_WorldTimeGeometry unsigned int m_TimeStep; //##Documentation //## @brief timestamp of last call of SetWorldGeometry itk::TimeStamp m_CurrentWorldPlaneGeometryUpdateTime; //##Documentation //## @brief timestamp of last change of the current time step itk::TimeStamp m_TimeStepUpdateTime; //##Documentation //## @brief Helper class which establishes connection between Interactors and Dispatcher via a common DataStorage. BindDispatcherInteractor *m_BindDispatcherInteractor; //##Documentation //## @brief Tells if the displayed region should be shifted or rescaled if the render window is resized. bool m_KeepDisplayedRegion; protected: void PrintSelf(std::ostream &os, itk::Indent indent) const override; //##Documentation //## Data object containing the m_CurrentWorldPlaneGeometry defined above. PlaneGeometryData::Pointer m_CurrentWorldPlaneGeometryData; //##Documentation //## DataNode objects containing the m_CurrentWorldPlaneGeometryData defined above. DataNode::Pointer m_CurrentWorldPlaneGeometryNode; //##Documentation //## @brief test only unsigned long m_CurrentWorldPlaneGeometryTransformTime; std::string m_Name; double m_Bounds[6]; bool m_EmptyWorldGeometry; typedef std::set LODEnabledMappersType; /** Number of mappers which are visible and have level-of-detail * rendering enabled */ unsigned int m_NumberOfVisibleLODEnabledMappers; // Local Storage Handling for mappers protected: std::list m_RegisteredLocalStorageHandlers; bool m_ConstrainZoomingAndPanning; public: void RemoveAllLocalStorages(); void RegisterLocalStorageHandler(mitk::BaseLocalStorageHandler *lsh); void UnregisterLocalStorageHandler(mitk::BaseLocalStorageHandler *lsh); }; } // namespace mitk #endif /* BASERENDERER_H_HEADER_INCLUDED_C1CCA0F4 */ diff --git a/Modules/Core/include/mitkImage.h b/Modules/Core/include/mitkImage.h index fe8e646ebc..359b536d87 100644 --- a/Modules/Core/include/mitkImage.h +++ b/Modules/Core/include/mitkImage.h @@ -1,799 +1,644 @@ /*============================================================================ 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 MITKIMAGE_H_HEADER_INCLUDED_C1C2FCD2 #define MITKIMAGE_H_HEADER_INCLUDED_C1C2FCD2 #include "mitkBaseData.h" #include "mitkImageAccessorBase.h" #include "mitkImageDataItem.h" #include "mitkImageDescriptor.h" #include "mitkImageVtkAccessor.h" #include "mitkLevelWindow.h" #include "mitkPlaneGeometry.h" #include "mitkSlicedData.h" #include #include -// DEPRECATED -#include - #ifndef __itkHistogram_h #include #endif class vtkImageData; namespace itk { template class MutexLockHolder; } namespace mitk { class SubImageSelector; class ImageTimeSelector; class ImageStatisticsHolder; /** * @brief Image class for storing images * * Can be asked for header information, the data vector, * the mitkIpPicDescriptor struct or vtkImageData objects. If not the complete * data is required, the appropriate SubImageSelector class should be used * for access. * Image organizes sets of slices (s x 2D), volumes (t x 3D) and channels (n * x ND). Channels are for different kind of data, e.g., morphology in * channel 0, velocities in channel 1. All channels must have the same Geometry! In * particular, the dimensions of all channels are the same, only the pixel-type * may differ between channels. * * For importing ITK images use of mitk::ITKImageImport is recommended, see * \ref Adaptor. * * For ITK v3.8 and older: Converting coordinates from the ITK physical * coordinate system (which does not support rotated images) to the MITK world * coordinate system should be performed via the BaseGeometry of the Image, see * BaseGeometry::WorldToItkPhysicalPoint. * * For more information, see \ref MitkImagePage . * @ingroup Data */ class MITKCORE_EXPORT Image : public SlicedData { friend class SubImageSelector; friend class ImageAccessorBase; friend class ImageVtkAccessor; friend class ImageVtkReadAccessor; friend class ImageVtkWriteAccessor; friend class ImageReadAccessor; friend class ImageWriteAccessor; public: mitkClassMacro(Image, SlicedData); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** Smart Pointer type to a ImageDataItem. */ typedef itk::SmartPointer ImageDataItemPointer; typedef itk::Statistics::Histogram HistogramType; typedef mitk::ImageStatisticsHolder *StatisticsHolderPointer; /** This enum is evaluated when setting new data to an image. */ enum ImportMemoryManagementType { CopyMemory, /**< Data to be set is copied and assigned to a new memory block. Data memory block will be freed on deletion of mitk::Image. */ ManageMemory, /**< Data to be set will be referenced, and Data memory block will be freed on deletion of mitk::Image. */ ReferenceMemory, /**< Data to be set will be referenced, but Data memory block will not be freed on deletion of mitk::Image. */ DontManageMemory = ReferenceMemory }; /** * @brief Vector container of SmartPointers to ImageDataItems; * Class is only for internal usage to allow convenient access to all slices over iterators; * See documentation of ImageDataItem for details. */ typedef std::vector ImageDataItemPointerArray; public: /** * @brief Returns the PixelType of channel @a n. */ const mitk::PixelType GetPixelType(int n = 0) const; /** * @brief Get dimension of the image */ unsigned int GetDimension() const; /** * @brief Get the size of dimension @a i (e.g., i=0 results in the number of pixels in x-direction). * * @sa GetDimensions() */ unsigned int GetDimension(int i) const; - /** @brief Get the data vector of the complete image, i.e., of all channels linked together. - - If you only want to access a slice, volume at a specific time or single channel - use one of the SubImageSelector classes. - \deprecatedSince{2012_09} Please use image accessors instead: See Doxygen/Related-Pages/Concepts/Image. This method - can be replaced by ImageWriteAccessor::GetData() or ImageReadAccessor::GetData() */ - DEPRECATED(virtual void *GetData()); - public: - /** @brief Get the pixel value at one specific index position. - - The pixel type is always being converted to double. - \deprecatedSince{2012_09} Please use image accessors instead: See Doxygen/Related-Pages/Concepts/Image. This method - can be replaced by a method from ImagePixelWriteAccessor or ImagePixelReadAccessor */ - DEPRECATED(double GetPixelValueByIndex(const itk::Index<3> &position, - unsigned int timestep = 0, - unsigned int component = 0)); - - /** @brief Get the pixel value at one specific world position. - - The pixel type is always being converted to double. - Please consider using image accessors instead: See Doxygen/Related-Pages/Concepts/Image. This method - can be replaced by a templated method from ImagePixelWriteAccessor or ImagePixelReadAccessor */ - double GetPixelValueByWorldCoordinate(const mitk::Point3D &position, - unsigned int timestep = 0, - unsigned int component = 0); - /** * @brief Get a volume at a specific time @a t of channel @a n as a vtkImageData. */ virtual vtkImageData *GetVtkImageData(int t = 0, int n = 0); virtual const vtkImageData *GetVtkImageData(int t = 0, int n = 0) const; /** * @brief Check whether slice @a s at time @a t in channel @a n is set */ bool IsSliceSet(int s = 0, int t = 0, int n = 0) const override; /** * @brief Check whether volume at time @a t in channel @a n is set */ bool IsVolumeSet(int t = 0, int n = 0) const override; /** * @brief Check whether the channel @a n is set */ bool IsChannelSet(int n = 0) const override; /** * @brief Set @a data as slice @a s at time @a t in channel @a n. It is in * the responsibility of the caller to ensure that the data vector @a data * is really a slice (at least is not smaller than a slice), since there is * no chance to check this. * * The data is copied to an array managed by the image. If the image shall * reference the data, use SetImportSlice with ImportMemoryManagementType * set to ReferenceMemory. For importing ITK images use of mitk:: * ITKImageImport is recommended. * @sa SetPicSlice, SetImportSlice, SetImportVolume */ virtual bool SetSlice(const void *data, int s = 0, int t = 0, int n = 0); /** * @brief Set @a data as volume at time @a t in channel @a n. It is in * the responsibility of the caller to ensure that the data vector @a data * is really a volume (at least is not smaller than a volume), since there is * no chance to check this. * * The data is copied to an array managed by the image. If the image shall * reference the data, use SetImportVolume with ImportMemoryManagementType * set to ReferenceMemory. For importing ITK images use of mitk:: * ITKImageImport is recommended. * @sa SetPicVolume, SetImportVolume */ virtual bool SetVolume(const void *data, int t = 0, int n = 0); /** * @brief Set @a data in channel @a n. It is in * the responsibility of the caller to ensure that the data vector @a data * is really a channel (at least is not smaller than a channel), since there is * no chance to check this. * * The data is copied to an array managed by the image. If the image shall * reference the data, use SetImportChannel with ImportMemoryManagementType * set to ReferenceMemory. For importing ITK images use of mitk:: * ITKImageImport is recommended. * @sa SetPicChannel, SetImportChannel */ virtual bool SetChannel(const void *data, int n = 0); /** * @brief Set @a data as slice @a s at time @a t in channel @a n. It is in * the responsibility of the caller to ensure that the data vector @a data * is really a slice (at least is not smaller than a slice), since there is * no chance to check this. * * The data is managed according to the parameter \a importMemoryManagement. * @sa SetPicSlice */ virtual bool SetImportSlice( void *data, int s = 0, int t = 0, int n = 0, ImportMemoryManagementType importMemoryManagement = CopyMemory); /** * @brief Set @a data as volume at time @a t in channel @a n. It is in * the responsibility of the caller to ensure that the data vector @a data * is really a volume (at least is not smaller than a volume), since there is * no chance to check this. * * The data is managed according to the parameter \a importMemoryManagement. * @sa SetPicVolume */ virtual bool SetImportVolume(void *data, int t = 0, int n = 0, ImportMemoryManagementType importMemoryManagement = CopyMemory); virtual bool SetImportVolume(const void *const_data, int t = 0, int n = 0); /** * @brief Set @a data in channel @a n. It is in * the responsibility of the caller to ensure that the data vector @a data * is really a channel (at least is not smaller than a channel), since there is * no chance to check this. * * The data is managed according to the parameter \a importMemoryManagement. * @sa SetPicChannel */ virtual bool SetImportChannel(void *data, int n = 0, ImportMemoryManagementType importMemoryManagement = CopyMemory); /** * initialize new (or re-initialize) image information * @warning Initialize() by pic assumes a plane, evenly spaced geometry starting at (0,0,0). */ virtual void Initialize(const mitk::PixelType &type, unsigned int dimension, const unsigned int *dimensions, unsigned int channels = 1); /** * initialize new (or re-initialize) image information by a BaseGeometry * * \param type * \param geometry * \param channels * @param tDim defines the number of time steps for which the Image should be initialized */ virtual void Initialize(const mitk::PixelType &type, const mitk::BaseGeometry &geometry, unsigned int channels = 1, int tDim = 1); - /** - * initialize new (or re-initialize) image information by a TimeGeometry - * - * \deprecatedSince{2013_09} Please use TimeGeometry instead of TimeSlicedGeometry. For more information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - DEPRECATED(virtual void Initialize(const mitk::PixelType & /*type*/, - const mitk::TimeSlicedGeometry * /*geometry*/, - unsigned int /*channels = 1*/, - int /*tDim=1*/)) - { - } - /** * \brief Initialize new (or re-initialize) image information by a TimeGeometry * * \param type * \param geometry * \param channels * \param tDim override time dimension if the value is bigger than 0 (Default -1) */ virtual void Initialize(const mitk::PixelType &type, const mitk::TimeGeometry &geometry, unsigned int channels = 1, int tDim = -1); /** * initialize new (or re-initialize) image information by a PlaneGeometry and number of slices * * Initializes the bounding box according to the width/height of the * PlaneGeometry and @a sDim via SlicedGeometry3D::InitializeEvenlySpaced. * The spacing is calculated from the PlaneGeometry. * \sa SlicedGeometry3D::InitializeEvenlySpaced - * \deprecatedSince{2016_11} Use a left-handed or right-handed PlaneGeometry to define the - * direction of the image stack instead of the flipped parameter */ - DEPRECATED(virtual void Initialize(const mitk::PixelType &type, - int sDim, - const mitk::PlaneGeometry &geometry2d, - bool flipped, - unsigned int channels = 1, - int tDim = 1)); - virtual void Initialize(const mitk::PixelType &type, int sDim, const mitk::PlaneGeometry &geometry2d, unsigned int channels = 1, int tDim = 1); /** * initialize new (or re-initialize) image information by another * mitk-image. * Only the header is used, not the data vector! */ virtual void Initialize(const mitk::Image *image); virtual void Initialize(const mitk::ImageDescriptor::Pointer inDesc); /** * initialize new (or re-initialize) image information by @a vtkimagedata, * a vtk-image. * Only the header is used, not the data vector! Use * SetVolume(vtkimage->GetScalarPointer()) to set the data vector. * * @param vtkimagedata * @param channels * @param tDim override time dimension in @a vtkimagedata (if >0 and <) * @param sDim override z-space dimension in @a vtkimagedata (if >0 and <) * @param pDim override y-space dimension in @a vtkimagedata (if >0 and <) */ virtual void Initialize(vtkImageData *vtkimagedata, int channels = 1, int tDim = -1, int sDim = -1, int pDim = -1); /** * initialize new (or re-initialize) image information by @a itkimage, * a templated itk-image. * Only the header is used, not the data vector! Use * SetVolume(itkimage->GetBufferPointer()) to set the data vector. * * @param itkimage * @param channels * @param tDim override time dimension in @a itkimage (if >0 and <) * @param sDim override z-space dimension in @a itkimage (if >0 and <) */ template void InitializeByItk(const itkImageType *itkimage, int channels = 1, int tDim = -1, int sDim = -1) { if (itkimage == nullptr) return; MITK_DEBUG << "Initializing MITK image from ITK image."; // build array with dimensions in each direction with at least 4 entries m_Dimension = itkimage->GetImageDimension(); unsigned int i, *tmpDimensions = new unsigned int[m_Dimension > 4 ? m_Dimension : 4]; for (i = 0; i < m_Dimension; ++i) tmpDimensions[i] = itkimage->GetLargestPossibleRegion().GetSize().GetSize()[i]; if (m_Dimension < 4) { unsigned int *p; for (i = 0, p = tmpDimensions + m_Dimension; i < 4 - m_Dimension; ++i, ++p) *p = 1; } // overwrite number of slices if sDim is set if ((m_Dimension > 2) && (sDim >= 0)) tmpDimensions[2] = sDim; // overwrite number of time points if tDim is set if ((m_Dimension > 3) && (tDim >= 0)) tmpDimensions[3] = tDim; // rough initialization of Image // mitk::PixelType importType = ImportItkPixelType( itkimage::PixelType ); Initialize( MakePixelType(itkimage->GetNumberOfComponentsPerPixel()), m_Dimension, tmpDimensions, channels); const typename itkImageType::SpacingType &itkspacing = itkimage->GetSpacing(); MITK_DEBUG << "ITK spacing " << itkspacing; // access spacing of itk::Image Vector3D spacing; FillVector3D(spacing, itkspacing[0], 1.0, 1.0); if (m_Dimension >= 2) spacing[1] = itkspacing[1]; if (m_Dimension >= 3) spacing[2] = itkspacing[2]; // access origin of itk::Image Point3D origin; const typename itkImageType::PointType &itkorigin = itkimage->GetOrigin(); MITK_DEBUG << "ITK origin " << itkorigin; FillVector3D(origin, itkorigin[0], 0.0, 0.0); if (m_Dimension >= 2) origin[1] = itkorigin[1]; if (m_Dimension >= 3) origin[2] = itkorigin[2]; // access direction of itk::Imagm_PixelType = new mitk::PixelType(type);e and include spacing const typename itkImageType::DirectionType &itkdirection = itkimage->GetDirection(); MITK_DEBUG << "ITK direction " << itkdirection; mitk::Matrix3D matrix; matrix.SetIdentity(); unsigned int j, itkDimMax3 = (m_Dimension >= 3 ? 3 : m_Dimension); // check if spacing has no zero entry and itkdirection has no zero columns bool itkdirectionOk = true; mitk::ScalarType columnSum; for (j = 0; j < itkDimMax3; ++j) { columnSum = 0.0; for (i = 0; i < itkDimMax3; ++i) { columnSum += fabs(itkdirection[i][j]); } if (columnSum < mitk::eps) { itkdirectionOk = false; } if ((spacing[j] < -mitk::eps) // (normally sized) negative value && (j == 2) && (m_Dimensions[2] == 1)) { // Negative spacings can occur when reading single DICOM slices with ITK via GDCMIO // In these cases spacing is not determind by ITK correctly (because it distinguishes correctly // between slice thickness and inter slice distance -- slice distance is meaningless for // single slices). // I experienced that ITK produced something meaningful nonetheless because is is // evaluating the tag "(0018,0088) Spacing between slices" as a fallback. This tag is not // reliable (http://www.itk.org/pipermail/insight-users/2005-September/014711.html) // but gives at least a hint. // In real world cases I experienced that this tag contained the correct inter slice distance // with a negative sign, so we just invert such negative spacings. MITK_WARN << "Illegal value of itk::Image::GetSpacing()[" << j << "]=" << spacing[j] << ". Using inverted value " << -spacing[j]; spacing[j] = -spacing[j]; } else if (spacing[j] < mitk::eps) // value near zero { MITK_ERROR << "Illegal value of itk::Image::GetSpacing()[" << j << "]=" << spacing[j] << ". Using 1.0 instead."; spacing[j] = 1.0; } } if (itkdirectionOk == false) { MITK_ERROR << "Illegal matrix returned by itk::Image::GetDirection():" << itkdirection << " Using identity instead."; for (i = 0; i < itkDimMax3; ++i) for (j = 0; j < itkDimMax3; ++j) if (i == j) matrix[i][j] = spacing[j]; else matrix[i][j] = 0.0; } else { for (i = 0; i < itkDimMax3; ++i) for (j = 0; j < itkDimMax3; ++j) matrix[i][j] = itkdirection[i][j] * spacing[j]; } // re-initialize PlaneGeometry with origin and direction PlaneGeometry *planeGeometry = static_cast(GetSlicedGeometry(0)->GetPlaneGeometry(0)); planeGeometry->SetOrigin(origin); planeGeometry->GetIndexToWorldTransform()->SetMatrix(matrix); // re-initialize SlicedGeometry3D SlicedGeometry3D *slicedGeometry = GetSlicedGeometry(0); slicedGeometry->InitializeEvenlySpaced(planeGeometry, m_Dimensions[2]); slicedGeometry->SetSpacing(spacing); // re-initialize TimeGeometry ProportionalTimeGeometry::Pointer timeGeometry = ProportionalTimeGeometry::New(); timeGeometry->Initialize(slicedGeometry, m_Dimensions[3]); SetTimeGeometry(timeGeometry); // clean-up delete[] tmpDimensions; this->Initialize(); } /** * @brief Check whether slice @a s at time @a t in channel @a n is valid, i.e., * is (or can be) inside of the image */ virtual bool IsValidSlice(int s = 0, int t = 0, int n = 0) const; /** * @brief Check whether volume at time @a t in channel @a n is valid, i.e., * is (or can be) inside of the image */ virtual bool IsValidVolume(int t = 0, int n = 0) const; /** * @brief Check whether the channel @a n is valid, i.e., * is (or can be) inside of the image */ virtual bool IsValidChannel(int n = 0) const; /** * @brief Returns true if an image is rotated, i.e. its geometry's * transformation matrix has nonzero elements besides the diagonal. * Non-diagonal elements are checked if larger then 1/1000 of the matrix' trace. */ bool IsRotated() const; /** * @brief Get the sizes of all dimensions as an integer-array. * * @sa GetDimension(int i); */ unsigned int *GetDimensions() const; ImageDescriptor::Pointer GetImageDescriptor() const { return m_ImageDescriptor; } ChannelDescriptor GetChannelDescriptor(int id = 0) const { return m_ImageDescriptor->GetChannelDescriptor(id); } /** \brief Sets a geometry to an image. */ void SetGeometry(BaseGeometry *aGeometry3D) override; /** * @warning for internal use only */ virtual ImageDataItemPointer GetSliceData(int s = 0, int t = 0, int n = 0, void *data = nullptr, ImportMemoryManagementType importMemoryManagement = CopyMemory) const; /** * @warning for internal use only */ virtual ImageDataItemPointer GetVolumeData(int t = 0, int n = 0, void *data = nullptr, ImportMemoryManagementType importMemoryManagement = CopyMemory) const; /** * @warning for internal use only */ virtual ImageDataItemPointer GetChannelData(int n = 0, void *data = nullptr, ImportMemoryManagementType importMemoryManagement = CopyMemory) const; - /** - \brief (DEPRECATED) Get the minimum for scalar images - */ - DEPRECATED(ScalarType GetScalarValueMin(int t = 0) const); - - /** - \brief (DEPRECATED) Get the maximum for scalar images - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(ScalarType GetScalarValueMax(int t = 0) const); - - /** - \brief (DEPRECATED) Get the second smallest value for scalar images - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(ScalarType GetScalarValue2ndMin(int t = 0) const); - - /** - \brief (DEPRECATED) Get the smallest value for scalar images, but do not recompute it first - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(ScalarType GetScalarValueMinNoRecompute(unsigned int t = 0) const); - - /** - \brief (DEPRECATED) Get the second smallest value for scalar images, but do not recompute it first - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(ScalarType GetScalarValue2ndMinNoRecompute(unsigned int t = 0) const); - - /** - \brief (DEPRECATED) Get the second largest value for scalar images - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(ScalarType GetScalarValue2ndMax(int t = 0) const); - - /** - \brief (DEPRECATED) Get the largest value for scalar images, but do not recompute it first - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(ScalarType GetScalarValueMaxNoRecompute(unsigned int t = 0) const); - - /** - \brief (DEPRECATED) Get the second largest value for scalar images, but do not recompute it first - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(ScalarType GetScalarValue2ndMaxNoRecompute(unsigned int t = 0) const); - - /** - \brief (DEPRECATED) Get the count of voxels with the smallest scalar value in the dataset - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(ScalarType GetCountOfMinValuedVoxels(int t = 0) const); - - /** - \brief (DEPRECATED) Get the count of voxels with the largest scalar value in the dataset - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(ScalarType GetCountOfMaxValuedVoxels(int t = 0) const); - - /** - \brief (DEPRECATED) Get the count of voxels with the largest scalar value in the dataset - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(unsigned int GetCountOfMaxValuedVoxelsNoRecompute(unsigned int t = 0) const); - - /** - \brief (DEPRECATED) Get the count of voxels with the smallest scalar value in the dataset - - \warning This method is deprecated and will not be available in the future. Use the \a GetStatistics instead - */ - DEPRECATED(unsigned int GetCountOfMinValuedVoxelsNoRecompute(unsigned int t = 0) const); - /** \brief Returns a pointer to the ImageStatisticsHolder object that holds all statistics information for the image. All Get-methods for statistics properties formerly accessible directly from an Image object are now moved to the new \a ImageStatisticsHolder object. */ StatisticsHolderPointer GetStatistics() const { return m_ImageStatistics; } + protected: mitkCloneMacro(Self); typedef itk::MutexLockHolder MutexHolder; int GetSliceIndex(int s = 0, int t = 0, int n = 0) const; int GetVolumeIndex(int t = 0, int n = 0) const; void ComputeOffsetTable(); virtual bool IsValidTimeStep(int t) const; void Expand(unsigned int timeSteps) override; virtual ImageDataItemPointer AllocateSliceData( int s = 0, int t = 0, int n = 0, void *data = nullptr, ImportMemoryManagementType importMemoryManagement = CopyMemory) const; virtual ImageDataItemPointer AllocateVolumeData( int t = 0, int n = 0, void *data = nullptr, ImportMemoryManagementType importMemoryManagement = CopyMemory) const; virtual ImageDataItemPointer AllocateChannelData( int n = 0, void *data = nullptr, ImportMemoryManagementType importMemoryManagement = CopyMemory) const; Image(); Image(const Image &other); ~Image() override; void Clear() override; /** @warning Has to be called by every Initialize method! */ void Initialize() override; void PrintSelf(std::ostream &os, itk::Indent indent) const override; mutable ImageDataItemPointerArray m_Channels; mutable ImageDataItemPointerArray m_Volumes; mutable ImageDataItemPointerArray m_Slices; mutable itk::SimpleFastMutexLock m_ImageDataArraysLock; unsigned int m_Dimension; unsigned int *m_Dimensions; ImageDescriptor::Pointer m_ImageDescriptor; size_t *m_OffsetTable; ImageDataItemPointer m_CompleteData; // Image statistics Holder replaces the former implementation directly inside this class friend class ImageStatisticsHolder; StatisticsHolderPointer m_ImageStatistics; private: ImageDataItemPointer GetSliceData_unlocked( int s, int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const; ImageDataItemPointer GetVolumeData_unlocked(int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const; ImageDataItemPointer GetChannelData_unlocked(int n, void *data, ImportMemoryManagementType importMemoryManagement) const; ImageDataItemPointer AllocateSliceData_unlocked( int s, int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const; ImageDataItemPointer AllocateVolumeData_unlocked(int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const; ImageDataItemPointer AllocateChannelData_unlocked(int n, void *data, ImportMemoryManagementType importMemoryManagement) const; bool IsSliceSet_unlocked(int s, int t, int n) const; bool IsVolumeSet_unlocked(int t, int n) const; bool IsChannelSet_unlocked(int n) const; /** Stores all existing ImageReadAccessors */ mutable std::vector m_Readers; /** Stores all existing ImageWriteAccessors */ mutable std::vector m_Writers; /** Stores all existing ImageVtkAccessors */ mutable std::vector m_VtkReaders; /** A mutex, which needs to be locked to manage m_Readers and m_Writers */ itk::SimpleFastMutexLock m_ReadWriteLock; /** A mutex, which needs to be locked to manage m_VtkReaders */ itk::SimpleFastMutexLock m_VtkReadersLock; }; - /** - * @brief Equal A function comparing two images for beeing equal in meta- and imagedata - * @warning This method is deprecated and will not be available in the future. Use the \a bool mitk::Equal(const - * mitk::Image& i1, const mitk::Image& i2) instead. - * - * @ingroup MITKTestingAPI - * - * Following aspects are tested for equality: - * - dimension of the images - * - size of the images - * - pixel type - * - pixel values : pixel values are expected to be identical at each position ( for other options see - * mitk::CompareImageFilter ) - * - * @param rightHandSide An image to be compared - * @param leftHandSide An image to be compared - * @param eps Tolarence for comparison. You can use mitk::eps in most cases. - * @param verbose Flag indicating if the user wants detailed console output or not. - * @return true, if all subsequent comparisons are true, false otherwise - */ - DEPRECATED(MITKCORE_EXPORT bool Equal( - const mitk::Image *leftHandSide, const mitk::Image *rightHandSide, ScalarType eps, bool verbose)); - /** * @brief Equal A function comparing two images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - dimension of the images * - size of the images * - pixel type * - pixel values : pixel values are expected to be identical at each position ( for other options see * mitk::CompareImageFilter ) * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKCORE_EXPORT bool Equal(const mitk::Image &leftHandSide, const mitk::Image &rightHandSide, ScalarType eps, bool verbose); } // namespace mitk #endif /* MITKIMAGE_H_HEADER_INCLUDED_C1C2FCD2 */ diff --git a/Modules/Core/include/mitkImageDataItem.h b/Modules/Core/include/mitkImageDataItem.h index f221d344a8..946879ff55 100644 --- a/Modules/Core/include/mitkImageDataItem.h +++ b/Modules/Core/include/mitkImageDataItem.h @@ -1,163 +1,166 @@ /*============================================================================ 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 IMAGEDATAITEM_H #define IMAGEDATAITEM_H #include "mitkCommon.h" #include //#include //#include "mitkPixelType.h" #include "mitkImageDescriptor.h" //#include "mitkImageVtkAccessor.h" class vtkImageData; namespace mitk { class PixelType; class ImageVtkReadAccessor; class ImageVtkWriteAccessor; class Image; //##Documentation //## @brief Internal class for managing references on sub-images //## //## ImageDataItem is a container for image data which is used internal in //## mitk::Image to handle the communication between the different data types for images //## used in MITK (ipPicDescriptor, mitk::Image, vtkImageData). Common for these image data //## types is the actual image data, but they differ in representation of pixel type etc. //## The class is also used to convert ipPic images to vtkImageData. //## //## The class is mainly used to extract sub-images inside of mitk::Image, like single slices etc. //## It should not be used outside of this. //## //## @param manageMemory Determines if image data is removed while destruction of ImageDataItem or not. //## @ingroup Data class MITKCORE_EXPORT ImageDataItem : public itk::LightObject { friend class ImageAccessorBase; friend class ImageWriteAccessor; friend class ImageReadAccessor; template friend class ImagePixelAccessor; friend class Image; // template // friend class ImageToItk; public: typedef itk::SmartPointer ImagePointer; typedef itk::SmartPointer ImageConstPointer; mitkClassMacroItkParent(ImageDataItem, itk::LightObject); itkCloneMacro(ImageDataItem); itk::LightObject::Pointer InternalClone() const override; ImageDataItem(const ImageDataItem &aParent, const mitk::ImageDescriptor::Pointer desc, int timestep, unsigned int dimension, void *data = nullptr, bool manageMemory = false, size_t offset = 0); ~ImageDataItem() override; ImageDataItem(const mitk::ImageDescriptor::Pointer desc, int timestep, void *data, bool manageMemory); ImageDataItem(const mitk::PixelType &type, int timestep, unsigned int dimension, unsigned int *dimensions, void *data, bool manageMemory); ImageDataItem(const ImageDataItem &other); - /** - \deprecatedSince{2012_09} Please use image accessors instead: See Doxygen/Related-Pages/Concepts/Image. This method - can be replaced by ImageWriteAccessor::GetData() or ImageReadAccessor::GetData() */ - DEPRECATED(void *GetData() const) { return m_Data; } bool IsComplete() const { return m_IsComplete; } void SetComplete(bool complete) { m_IsComplete = complete; } int GetOffset() const { return m_Offset; } PixelType GetPixelType() const { return *m_PixelType; } void SetTimestep(int t) { m_Timestep = t; } void SetManageMemory(bool b) { m_ManageMemory = b; } int GetDimension() const { return m_Dimension; } int GetDimension(int i) const { int returnValue = 0; // return the true size if dimension available if (i < (int)m_Dimension) returnValue = m_Dimensions[i]; return returnValue; } ImageDataItem::ConstPointer GetParent() const { return m_Parent; } /** * @brief GetVtkImageAccessor Returns a vtkImageDataItem, if none is present, a new one is constructed by the * ConstructVtkImageData method. * Due to historical development of MITK and VTK, the vtkImage origin is explicitly set * to * (0, 0, 0) for 3D images. * See bug 5050 for detailed information. * @return Pointer of type ImageVtkReadAccessor */ ImageVtkReadAccessor *GetVtkImageAccessor(ImageConstPointer) const; ImageVtkWriteAccessor *GetVtkImageAccessor(ImagePointer); // Returns if image data should be deleted on destruction of ImageDataItem. bool GetManageMemory() const { return m_ManageMemory; } virtual void ConstructVtkImageData(ImageConstPointer) const; size_t GetSize() const { return m_Size; } virtual void Modified() const; protected: + + /**Helper function to allow friend classes to access m_Data without changing their code. + * Moved to protected visibility because only friends are allowed to access m_Data directly. + * Other classes should used ImageWriteAccessor::GetData() or ImageReadAccessor::GetData() + * to get access.*/ + void* GetData() const { return m_Data; } + unsigned char *m_Data; PixelType *m_PixelType; bool m_ManageMemory; mutable vtkImageData *m_VtkImageData; mutable ImageVtkReadAccessor *m_VtkImageReadAccessor; ImageVtkWriteAccessor *m_VtkImageWriteAccessor; int m_Offset; bool m_IsComplete; size_t m_Size; private: void ComputeItemSize(const unsigned int *dimensions, unsigned int dimension); ImageDataItem::ConstPointer m_Parent; unsigned int m_Dimension; unsigned int m_Dimensions[MAX_IMAGE_DIMENSIONS]; int m_Timestep; }; } // namespace mitk #endif /* IMAGEDATAITEM_H */ diff --git a/Modules/Core/include/mitkLevelWindowManager.h b/Modules/Core/include/mitkLevelWindowManager.h index b639cbc2b8..aedd51c4c7 100644 --- a/Modules/Core/include/mitkLevelWindowManager.h +++ b/Modules/Core/include/mitkLevelWindowManager.h @@ -1,215 +1,205 @@ /*============================================================================ 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 MITKLEVELWINDOWMANAGER_H #define MITKLEVELWINDOWMANAGER_H // mitk core #include "mitkBaseProperty.h" #include "mitkDataStorage.h" #include "mitkLevelWindowProperty.h" // c++ #include #include namespace mitk { /** @brief Provides access to the LevelWindowProperty object and LevelWindow of the "current" image. - provides a LevelWindowProperty for purposes like GUI editors - this property comes from one of two possible sources - either something (e.g. the application) sets the property because of some user selection - OR the "Auto top-most" logic is used to search a DataStorage for the image with the highest "layer" property value Changes on Level/Window can be set with SetLevelWindow() and will affect either the topmost layer image, - if isAutoTopMost() returns true, or an image which is set by SetLevelWindowProperty(LevelWindowProperty::Pointer + if IsAutoTopMost() returns true, or an image which is set by SetLevelWindowProperty(LevelWindowProperty::Pointer levelWindowProperty). + Additionally the changes on Level/Window will affect one or multiple selected images, if IsSelectedImages() returns true. + Only one of the two different modes can be enabled at the same time. Changes to Level/Window, when another image gets active or by SetLevelWindow(const LevelWindow& levelWindow), will be sent to all listeners by Modified(). - DataStorageChanged() listens to the DataStorage for new or removed images. Depending on how m_AutoTopMost is set, + DataStorageChanged() listens to the DataStorage for new or removed images. Depending on the currently enabled mode, the new image becomes active or not. If an image is removed from the DataStorage and m_AutoTopMost is false, there is a check to proof, if the active image is still available. If not, then m_AutoTopMost becomes true. Note that this class is not thread safe at the moment! */ class MITKCORE_EXPORT LevelWindowManager : public itk::Object { public: mitkClassMacroItkParent(LevelWindowManager, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - void SetDataStorage(DataStorage *ds); + void SetDataStorage(DataStorage* dataStorage); DataStorage *GetDataStorage(); /** * @brief (Re-)Initialize the LevelWindowManager by setting the topmost image. * Use the removedNode parameter if a node was removed. * * @param autoTopMost Set the topmost layer image to be affected by changes, if true. * @param removedNode A node was removed from the data storage if != nullptr. */ void SetAutoTopMostImage(bool autoTopMost, const DataNode *removedNode = nullptr); /** * @brief (Re-)Initialize the LevelWindowManager by setting the selected images. * Use the removedNode parameter if a node was removed. * * @param selectedImagesMode Set the selected images to be affected by changes, if true. * @param removedNode A node was removed from the data storage if != nullptr. */ void SetSelectedImages(bool selectedImagesMode, const DataNode *removedNode = nullptr); void RecalculateLevelWindowForSelectedComponent(const itk::EventObject&); /** * @brief Update the level window. * This function is called if a property of a data node is changed. * Relevant properties are defined in the protected 'ObserverToPropertyValueMap'-members. */ void Update(const itk::EventObject&); /** * @brief Update the level window. * This function is only called if the 'selected' property of a data node is changed. * This is done in order to avoid finding the correct image each time a node is selected but * the 'm_SelectedImages' bool value is set to false (as the normal 'Update'-function would do). * Changes of the 'selected' property happen quite a lot so this should not slow down the application. */ void UpdateSelected(const itk::EventObject&); /** * @brief Set a specific LevelWindowProperty; all changes will affect the image belonging to this property. * @throw mitk::Exception Throw an exception if the there is no image in the data storage which belongs to this * property. */ void SetLevelWindowProperty(LevelWindowProperty::Pointer levelWindowProperty); /** * @brief Set new Level/Window values and inform all listeners about changes. */ void SetLevelWindow(const LevelWindow &levelWindow); /** * @brief Return the current LevelWindowProperty object from the image that is affected by changes. * * @return The current LevelWindowProperty */ - LevelWindowProperty::Pointer GetLevelWindowProperty(); + LevelWindowProperty::Pointer GetLevelWindowProperty() const; /** * @brief Return Level/Window values for the current image * * @return The LevelWindow value for the current image. */ - const LevelWindow &GetLevelWindow(); + const LevelWindow &GetLevelWindow() const; /** - * @brief Return true, if the changes on slider or line-edits will affect the topmost layer image. + * @brief Return true, if level window changes will affect the topmost layer image. * * @return Return the member value that denotes the auto-topmost mode. */ - bool IsAutoTopMost(); + bool IsAutoTopMost() const; /** - * @brief Return true, if changes on slider or line-edits will affect the currently selected images. + * @brief Return true, if level window changes will affect the currently selected images. * * @return Return the member value that denotes the selected-images mode. */ - bool IsSelectedImages(); - /** @brief This method is called when a node is added to the data storage. - * A listener on the data storage is used to call this method automatically after a node was added. - * @throw mitk::Exception Throws an exception if something is wrong, e.g. if the number of observers differs from - * the number of nodes. + bool IsSelectedImages() const; + /** + * @brief This method is called when a node is added to the data storage. + * A listener on the data storage is used to call this method automatically after a node was added. + * @throw mitk::Exception Throws an exception if something is wrong, e.g. if the number of observers differs from + * the number of nodes. */ - void DataStorageAddedNode(const DataNode *n = nullptr); - /** @brief This method is called when a node is removed to the data storage. - * A listener on the data storage is used to call this method automatically directly before a node will be - * removed. - * @throw mitk::Exception Throws an exception if something is wrong, e.g. if the number of observers differs from - * the number of nodes. + void DataStorageAddedNode(const DataNode *dataNode = nullptr); + /** + * @brief This method is called when a node is removed from the data storage. + * A listener on the data storage is used to call this method automatically before a node will be removed. + * @throw mitk::Exception Throws an exception if something is wrong, e.g. if the number of observers differs from + * the number of nodes. */ - void DataStorageRemovedNode(const DataNode *removedNode = nullptr); + void DataStorageRemovedNode(const DataNode *dataNode = nullptr); /** * @brief Change notifications from mitkLevelWindowProperty. */ - void OnPropertyModified(const itk::EventObject &e); + void OnPropertyModified(const itk::EventObject&); /** * @brief Return the currently active image. * * @return The member variable holding the currently active image. */ - Image *GetCurrentImage(); + Image *GetCurrentImage() const; /** - * @return Returns the current number of observers which are registered in this object. - * @throw mitk::Exception Throws an exception if the number of observers differs from - * the number of relevant objects - * which means that something is wrong. + * @brief Return the number of observers for data node's "visible" property. + * This basically returns the number of relevant nodes to observe. * + * @return The current number of observers which are registered in this object. */ - int GetNumberOfObservers(); - + int GetNumberOfObservers() const; /** - * @brief Returns all nodes in the DataStorage that have the following properties: + * @brief Return all nodes in the data storage that have the following properties: * - "binary" == false * - "levelwindow" * - DataType == Image / DiffusionImage / TensorImage / OdfImage / ShImage + * + @ return The filtered list of relevant nodes in the data storage */ - DataStorage::SetOfObjects::ConstPointer GetRelevantNodes(); + DataStorage::SetOfObjects::ConstPointer GetRelevantNodes() const; - protected: + private: LevelWindowManager(); ~LevelWindowManager() override; DataStorage::Pointer m_DataStorage; - /// Pointer to the LevelWindowProperty of the current image. LevelWindowProperty::Pointer m_LevelWindowProperty; typedef std::pair PropDataPair; typedef std::map ObserverToPropertyValueMap; - /// Map to hold observer IDs to every "visible" property of DataNode's BaseProperty. + ObserverToPropertyValueMap m_ObserverToVisibleProperty; - /// Map to hold observer IDs to every "layer" property of DataNode's BaseProperty. ObserverToPropertyValueMap m_ObserverToLayerProperty; - /// Map to hold observer IDs to every "Image Rendering.Mode" property of DataNode's BaseProperty. ObserverToPropertyValueMap m_ObserverToRenderingModeProperty; - /// Map to hold observer IDs to every "Image.Displayed Component" property of DataNode's BaseProperty. ObserverToPropertyValueMap m_ObserverToDisplayedComponentProperty; - /// Map to hold observer IDs to every "imageForLevelWindow" property of DataNode's BaseProperty. ObserverToPropertyValueMap m_ObserverToLevelWindowImageProperty; - /// Map to hold observer IDs to every "selected" property of DataNode's BaseProperty. ObserverToPropertyValueMap m_ObserverToSelectedProperty; - /// Updates the internal observer list. - /// Ignores nodes which are marked to be deleted in the variable m_NodeMarkedToDelete. void UpdateObservers(); - /// Internal help method to clear both lists/maps. - void ClearPropObserverLists(); - /// Internal help method to create both lists/maps. - void CreatePropObserverLists(); + void ClearPropertyObserverMaps(); + void CreatePropertyObserverMaps(); - bool IgnoreNode(const DataNode* dataNode); + bool HasLevelWindowRenderingMode(DataNode *dataNode) const; - /// This variable holds a data node which will be deleted from the datastorage immediately - /// Nullptr, if there is no data node to be deleted. + // This variable holds a data node which will be deleted from the datastorage immediately. const DataNode *m_NodeMarkedToDelete; bool m_AutoTopMost; bool m_SelectedImagesMode; - unsigned long m_ObserverTag; - bool m_IsObserverTagSet; unsigned long m_PropertyModifiedTag; Image *m_CurrentImage; - std::vector m_RelevantDataNodes; + std::vector m_DataNodesForLevelWindow; bool m_IsPropertyModifiedTagSet; bool m_LevelWindowMutex; }; } #endif // MITKLEVELWINDOWMANAGER_H diff --git a/Modules/Core/include/mitkPointSetVtkMapper2D.h b/Modules/Core/include/mitkPointSetVtkMapper2D.h index 08a0524cc5..f8520be771 100644 --- a/Modules/Core/include/mitkPointSetVtkMapper2D.h +++ b/Modules/Core/include/mitkPointSetVtkMapper2D.h @@ -1,238 +1,238 @@ /*============================================================================ 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 mitkPointSetVtkMapper2D_h #define mitkPointSetVtkMapper2D_h #include "mitkBaseRenderer.h" #include "mitkLocalStorageHandler.h" #include "mitkVtkMapper.h" #include #include // VTK #include class vtkActor; class vtkPropAssembly; class vtkPolyData; class vtkPolyDataMapper; class vtkGlyphSource2D; class vtkGlyph3D; class vtkFloatArray; class vtkCellArray; namespace mitk { class PointSet; /** * @brief Vtk-based 2D mapper for PointSet * * Due to the need of different colors for selected * and unselected points and the facts, that we also have a contour and * labels for the points, the vtk structure is build up the following way: * * We have three PolyData, one selected, and one unselected and one * for a contour between the points. Each one is connected to an own * PolyDataMapper and an Actor. The different color for the unselected and * selected state and for the contour is read from properties. * * This mapper has several additional functionalities, such as rendering * a contour between points, calculating and displaying distances or angles * between points. * * @section mitkPointSetVtkMapper2D_point_rep Point Representation * * The points are displayed as small glyphs of configurable shape * (see property "PointSet.2D.shape"). The size of these glyphs * is given in world units. That means, the size or shape of those * glyphs is independent of the BaseGeometry object that you assign * to the PointSet. As for all other objects, _positions_ of points * will be transformed into the world via the Geometry's index-to-world * transform. * * Then the three Actors are combined inside a vtkPropAssembly and this * object is returned in GetProp() and so hooked up into the rendering * pipeline. * * @section mitkPointSetVtkMapper2D_propertires Applicable Properties * * Properties that can be set for point sets and influence the PointSetVTKMapper2D are: * * - \b "line width": (IntProperty 2) // line width of the line from one point to another * - \b "point line width": (IntProperty 1) // line width of the cross marking a point * - \b "point 2D size": (FloatProperty 6) // size of the glyph marking a point (diameter, in world * units!) * - \b "show contour": (BoolProperty false) // enable contour rendering between points (lines) * - \b "close contour": (BoolProperty false) // if enabled, the open strip is closed (first point * connected with last point) * - \b "show points": (BoolProperty true) // show or hide points * - \b "show distances": (BoolProperty false) // show or hide distance measure * - \b "distance decimal digits": (IntProperty 2) // set the number of decimal digits to be shown when * rendering the distance information * - \b "show angles": (BoolProperty false) // show or hide angle measurement * - \b "show distant lines": (BoolProperty false) // show the line between to points from a distant view * (equals "always on top" option) * - \b "layer": (IntProperty 1) // default is drawing pointset above images (they have a * default layer of 0) * - \b "PointSet.2D.shape" (EnumerationProperty Cross) // provides different shapes marking a point * 0 = "None", 1 = "Vertex", 2 = "Dash", 3 = "Cross", 4 = "ThickCross", 5 = "Triangle", 6 = "Square", 7 = * "Circle", * 8 = "Diamond", 9 = "Arrow", 10 = "ThickArrow", 11 = "HookedArrow", 12 = "Cross" * - \b "PointSet.2D.fill shape": (BoolProperty false) // fill or do not fill the glyph shape * - \b "Pointset.2D.distance to plane": (FloatProperty 4.0) //In the 2D render window, points are rendered which lie * within a certain distance * to the current plane. They are projected on the current * plane and scaled according to their distance. * Point markers appear smaller as the plane moves away * from * their true location. * The distance threshold can be adjusted by this float * property, which ables the user to delineate the points * that lie exactly on the plane. (+/- rounding error) * * Other Properties used here but not defined in this class: * * - \b "selectedcolor": (ColorProperty (1.0f, 0.0f, 0.0f)) // default color of the selected pointset e.g. the * current * point is red * - \b "contourcolor" : (ColorProperty (1.0f, 0.0f, 0.0f)) // default color for the contour is red * - \b "color": (ColorProperty (1.0f, 1.0f, 0.0f)) // default color of the (unselected) pointset is yellow * - \b "opacity": (FloatProperty 1.0) // opacity of point set, contours * - \b "label": (StringProperty nullptr) // a label can be defined for each point, which is rendered in proximity * to * the point * * @ingroup Mapper */ class MITKCORE_EXPORT PointSetVtkMapper2D : public VtkMapper { public: mitkClassMacro(PointSetVtkMapper2D, VtkMapper); itkFactorylessNewMacro(Self); itkCloneMacro(Self); virtual const mitk::PointSet *GetInput() const; /** \brief returns the a prop assembly */ vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; /** \brief set the default properties for this mapper */ static void SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer = nullptr, bool overwrite = false); /** \brief Internal class holding the mapper, actor, etc. for each of the 3 2D render windows */ class LocalStorage : public mitk::Mapper::BaseLocalStorage { public: /* constructor */ LocalStorage(); /* destructor */ ~LocalStorage() override; // points vtkSmartPointer m_UnselectedPoints; vtkSmartPointer m_SelectedPoints; vtkSmartPointer m_ContourPoints; // scales vtkSmartPointer m_UnselectedScales; vtkSmartPointer m_SelectedScales; // distances vtkSmartPointer m_DistancesBetweenPoints; // lines vtkSmartPointer m_ContourLines; // glyph source (provides different shapes for the points) vtkSmartPointer m_UnselectedGlyphSource2D; vtkSmartPointer m_SelectedGlyphSource2D; // glyph vtkSmartPointer m_UnselectedGlyph3D; vtkSmartPointer m_SelectedGlyph3D; // polydata vtkSmartPointer m_VtkUnselectedPointListPolyData; vtkSmartPointer m_VtkSelectedPointListPolyData; vtkSmartPointer m_VtkContourPolyData; // actor vtkSmartPointer m_UnselectedActor; vtkSmartPointer m_SelectedActor; vtkSmartPointer m_ContourActor; vtkSmartPointer m_VtkTextActor; std::vector> m_VtkTextLabelActors; std::vector> m_VtkTextDistanceActors; std::vector> m_VtkTextAngleActors; // mappers vtkSmartPointer m_VtkUnselectedPolyDataMapper; vtkSmartPointer m_VtkSelectedPolyDataMapper; vtkSmartPointer m_VtkContourPolyDataMapper; // propassembly vtkSmartPointer m_PropAssembly; }; /** \brief The LocalStorageHandler holds all (three) LocalStorages for the three 2D render windows. */ mitk::LocalStorageHandler m_LSH; protected: /* constructor */ PointSetVtkMapper2D(); /* destructor */ ~PointSetVtkMapper2D() override; /* \brief Applies the color and opacity properties and calls CreateVTKRenderObjects */ void GenerateDataForRenderer(mitk::BaseRenderer *renderer) override; /* \brief Called in mitk::Mapper::Update - * If TimeSlicedGeometry or time step is not valid of point set: reset mapper so that nothing is + * If TimeGeometry or time step is not valid of point set: reset mapper so that nothing is * displayed e.g. toggle visiblity of the propassembly */ void ResetMapper(BaseRenderer *renderer) override; /* \brief Fills the vtk objects, thus it is only called when the point set has been changed. * This function iterates over the input point set and determines the glyphs which lie in a specific * range around the current slice. Those glyphs are rendered using a specific shape defined in vtk glyph source * to mark each point. The shape can be changed in MITK using the property "PointSet.2D.shape". * * There were issues when rendering vtk glyphs in the 2D-render windows. By default, the glyphs are * rendered within the x-y plane in each 2D-render window, so you would only see them from the * side in the saggital and coronal 2D-render window. The solution to this is to rotate the glyphs in order * to be ortogonal to the current view vector. To achieve this, the rotation (vtktransform) of the current * PlaneGeometry is applied to the orienation of the glyphs. */ virtual void CreateVTKRenderObjects(mitk::BaseRenderer *renderer); // member variables holding the current value of the properties used in this mapper bool m_ShowContour; // "show contour" property bool m_CloseContour; // "close contour" property bool m_ShowPoints; // "show points" property bool m_ShowDistances; // "show distances" property int m_DistancesDecimalDigits; // "distance decimal digits" property bool m_ShowAngles; // "show angles" property bool m_ShowDistantLines; // "show distant lines" property int m_LineWidth; // "line width" property int m_PointLineWidth; // "point line width" property float m_Point2DSize; // "point 2D size" property int m_IDShapeProperty; // ID for mitkPointSetShape Enumeration Property "Pointset.2D.shape" bool m_FillShape; // "Pointset.2D.fill shape" property float m_DistanceToPlane; // "Pointset.2D.distance to plane" property }; } // namespace mitk #endif /* mitkPointSetVtkMapper2D_h */ diff --git a/Modules/Core/include/mitkSurfaceVtkMapper2D.h b/Modules/Core/include/mitkSurfaceVtkMapper2D.h index 1a0e357f42..9e52cbc226 100644 --- a/Modules/Core/include/mitkSurfaceVtkMapper2D.h +++ b/Modules/Core/include/mitkSurfaceVtkMapper2D.h @@ -1,215 +1,215 @@ /*============================================================================ 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 mitkSurfaceVtkMapper2D_h #define mitkSurfaceVtkMapper2D_h #include "mitkBaseRenderer.h" #include "mitkLocalStorageHandler.h" #include "mitkVtkMapper.h" #include // VTK #include class vtkAssembly; class vtkCutter; class vtkPlane; class vtkLookupTable; class vtkGlyph3D; class vtkArrowSource; class vtkReverseSense; namespace mitk { class Surface; /** * @brief Vtk-based mapper for cutting 2D slices out of Surfaces. * * The mapper uses a vtkCutter filter to cut out slices (contours) of the 3D * volume and render these slices as vtkPolyData. The data is transformed * according to its geometry before cutting, to support the geometry concept * of MITK. * * Properties: * \b Surface.2D.Line Width: Thickness of the rendered lines in 2D. * \b Surface.2D.Normals.Draw Normals: enables drawing of normals as 3D arrows * in the 2D render window. The normals are created with a vtkGlyph3D from * the vtkPolyData. * \b Surface.2D.Normals.Draw Inverse Normals: same as normals, but in the * other direction. The inverse normals are computed with a vtkReverseSense * filter. * \b Surface.2D.Normals.(Inverse) Normals Color: Color of the (inverse) normals. * \b Surface.2D.Normals.(Inverse) Normals Scale Factor: Regulates the size of the normals. * * @ingroup Mapper */ class MITKCORE_EXPORT SurfaceVtkMapper2D : public VtkMapper { public: mitkClassMacro(SurfaceVtkMapper2D, VtkMapper); itkFactorylessNewMacro(Self); itkCloneMacro(Self); virtual const mitk::Surface *GetInput() const; /** \brief returns the prop assembly */ vtkProp *GetVtkProp(mitk::BaseRenderer *renderer) override; /** \brief set the default properties for this mapper */ static void SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer = nullptr, bool overwrite = false); /** \brief Internal class holding the mapper, actor, etc. for each of the 3 2D render windows */ class LocalStorage : public mitk::Mapper::BaseLocalStorage { public: /** \brief Timestamp of last update of stored data. */ itk::TimeStamp m_LastUpdateTime; /** * @brief m_PropAssembly Contains all vtkProps for the final rendering. * * Consists of 3 actors: * The surface cut (the slice from the 3D surface). * The normals and the inverse normals. */ vtkSmartPointer m_PropAssembly; /** * @brief m_Actor actor for the surface cut. */ vtkSmartPointer m_Actor; /** * @brief m_NormalActor actor for the normals. */ vtkSmartPointer m_NormalActor; /** * @brief m_InverseNormalActor actor for the inverse normals. */ vtkSmartPointer m_InverseNormalActor; /** * @brief m_Mapper VTK mapper for all types of 2D polydata e.g. werewolves. */ vtkSmartPointer m_Mapper; /** * @brief m_Cutter Filter to cut out the 2D slice. */ vtkSmartPointer m_Cutter; /** * @brief m_CuttingPlane The plane where to cut off the 2D slice. */ vtkSmartPointer m_CuttingPlane; /** * @brief m_NormalMapper Mapper for the normals. */ vtkSmartPointer m_NormalMapper; /** * @brief m_InverseNormalMapper Mapper for the inverse normals. */ vtkSmartPointer m_InverseNormalMapper; /** * @brief m_NormalGlyph Glyph for creating normals. */ vtkSmartPointer m_NormalGlyph; /** * @brief m_InverseNormalGlyph Glyph for creating inverse normals. */ vtkSmartPointer m_InverseNormalGlyph; /** * @brief m_ArrowSource Arrow representation of the normals. */ vtkSmartPointer m_ArrowSource; /** * @brief m_ReverseSense Filter to invert the normals. */ vtkSmartPointer m_ReverseSense; /** \brief Default constructor of the local storage. */ LocalStorage(); /** \brief Default deconstructor of the local storage. */ ~LocalStorage() override; }; /** \brief The LocalStorageHandler holds all (three) LocalStorages for the three 2D render windows. */ mitk::LocalStorageHandler m_LSH; /** * @brief UpdateVtkTransform Overwrite the method of the base class. * * The base class transforms the actor according to the respective * geometry which is correct for most cases. This mapper, however, * uses a vtkCutter to cut out a contour. To cut out the correct * contour, the data has to be transformed beforehand. Else the * current plane geometry will point the cutter to en empty location * (if the surface does have a geometry, which is a rather rare case). */ void UpdateVtkTransform(mitk::BaseRenderer * /*renderer*/) override {} protected: /** * @brief SurfaceVtkMapper2D default constructor. */ SurfaceVtkMapper2D(); /** * @brief ~SurfaceVtkMapper2D default destructor. */ ~SurfaceVtkMapper2D() override; /** * @brief GenerateDataForRenderer produces all the data. * @param renderer The respective renderer of the mitkRenderWindow. */ void GenerateDataForRenderer(mitk::BaseRenderer *renderer) override; /** * @brief ResetMapper Called in mitk::Mapper::Update to hide objects. - * If TimeSlicedGeometry or time step is not valid, reset the mapper. + * If TimeGeometry or time step is not valid, reset the mapper. * so that nothing is displayed e.g. toggle visiblity of the propassembly. * * @param renderer The respective renderer of the mitkRenderWindow. */ void ResetMapper(BaseRenderer *renderer) override; /** * @brief Updates legacy properties to current behavior/interpretation. * @param properties The property list which should be adapted to new behaviour. * * Whenever a mapper decides to change its property types or its * interpretation of certain values, it should add something to this * method and call it before methods like ApplyProperties(); * * This is particularly helpful when dealing with data from * archive/scene files that were created before changes. */ virtual void FixupLegacyProperties(PropertyList *properties); /** * @brief ApplyAllProperties Pass all the properties to VTK. * @param renderer The respective renderer of the mitkRenderWindow. */ void ApplyAllProperties(BaseRenderer *renderer); /** * @brief Update Check if data should be generated. * @param renderer The respective renderer of the mitkRenderWindow. */ void Update(BaseRenderer *renderer) override; }; } // namespace mitk #endif /* mitkSurfaceVtkMapper2D_h */ diff --git a/Modules/Core/include/mitkTimeSlicedGeometry.h b/Modules/Core/include/mitkTimeSlicedGeometry.h deleted file mode 100644 index 6d350b4689..0000000000 --- a/Modules/Core/include/mitkTimeSlicedGeometry.h +++ /dev/null @@ -1,30 +0,0 @@ -/*============================================================================ - -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 mitkTimeSlicedGeometry_h -#define mitkTimeSlicedGeometry_h - -#include - -namespace mitk -{ - /** - * \deprecatedSince{2013_09} Please use TimeGeometry instead. For more information see - * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 - */ - class TimeSlicedGeometry - { - DEPRECATED(TimeSlicedGeometry()); - }; -} - -#endif diff --git a/Modules/Core/src/DataManagement/mitkImage.cpp b/Modules/Core/src/DataManagement/mitkImage.cpp index 4ed855b751..251e1ca36f 100644 --- a/Modules/Core/src/DataManagement/mitkImage.cpp +++ b/Modules/Core/src/DataManagement/mitkImage.cpp @@ -1,1547 +1,1369 @@ /*============================================================================ 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. ============================================================================*/ // MITK #include "mitkImage.h" #include "mitkCompareImageDataFilter.h" #include "mitkImageStatisticsHolder.h" #include "mitkImageVtkReadAccessor.h" #include "mitkImageVtkWriteAccessor.h" #include "mitkPixelTypeMultiplex.h" #include // VTK #include // ITK #include // Other #include #define FILL_C_ARRAY(_arr, _size, _value) \ for (unsigned int i = 0u; i < _size; i++) \ \ { \ _arr[i] = _value; \ } mitk::Image::Image() : m_Dimension(0), m_Dimensions(nullptr), m_ImageDescriptor(nullptr), m_OffsetTable(nullptr), m_CompleteData(nullptr), m_ImageStatistics(nullptr) { m_Dimensions = new unsigned int[MAX_IMAGE_DIMENSIONS]; FILL_C_ARRAY(m_Dimensions, MAX_IMAGE_DIMENSIONS, 0u); m_Initialized = false; } mitk::Image::Image(const Image &other) : SlicedData(other), m_Dimension(0), m_Dimensions(nullptr), m_ImageDescriptor(nullptr), m_OffsetTable(nullptr), m_CompleteData(nullptr), m_ImageStatistics(nullptr) { m_Dimensions = new unsigned int[MAX_IMAGE_DIMENSIONS]; FILL_C_ARRAY(m_Dimensions, MAX_IMAGE_DIMENSIONS, 0u); this->Initialize(other.GetPixelType(), other.GetDimension(), other.GetDimensions()); // Since the above called "Initialize" method doesn't take the geometry into account we need to set it // here manually TimeGeometry::Pointer cloned = other.GetTimeGeometry()->Clone(); this->SetTimeGeometry(cloned.GetPointer()); if (this->GetDimension() > 3) { const unsigned int time_steps = this->GetDimension(3); for (unsigned int i = 0u; i < time_steps; ++i) { ImageDataItemPointer volume = other.GetVolumeData(i); this->SetVolume(volume->GetData(), i); } } else { ImageDataItemPointer volume = other.GetVolumeData(0); this->SetVolume(volume->GetData(), 0); } } mitk::Image::~Image() { this->Clear(); m_ReferenceCount = 3; m_ReferenceCount = 0; delete[] m_OffsetTable; delete m_ImageStatistics; } const mitk::PixelType mitk::Image::GetPixelType(int n) const { return this->m_ImageDescriptor->GetChannelTypeById(n); } unsigned int mitk::Image::GetDimension() const { return m_Dimension; } unsigned int mitk::Image::GetDimension(int i) const { if ((i >= 0) && (i < (int)m_Dimension)) return m_Dimensions[i]; return 1; } -void *mitk::Image::GetData() -{ - if (m_Initialized == false) - { - if (GetSource().IsNull()) - return nullptr; - if (GetSource()->Updating() == false) - GetSource()->UpdateOutputInformation(); - } - m_CompleteData = GetChannelData(); - - // update channel's data - // if data was not available at creation point, the m_Data of channel descriptor is nullptr - // if data present, it won't be overwritten - m_ImageDescriptor->GetChannelDescriptor(0).SetData(m_CompleteData->GetData()); - - return m_CompleteData->GetData(); -} - template void AccessPixel(const mitk::PixelType ptype, void *data, const unsigned int offset, double &value) { value = 0.0; if (data == nullptr) return; if (ptype.GetBpe() != 24) { value = (double)(((T *)data)[offset]); } else { const unsigned int rgboffset = offset; double returnvalue = (((T *)data)[rgboffset]); returnvalue += (((T *)data)[rgboffset + 1]); returnvalue += (((T *)data)[rgboffset + 2]); value = returnvalue; } } -double mitk::Image::GetPixelValueByIndex(const itk::Index<3> &position, unsigned int timestep, unsigned int component) -{ - double value = 0; - if (this->GetTimeSteps() < timestep) - { - timestep = this->GetTimeSteps(); - } - - value = 0.0; - - const unsigned int *imageDims = this->m_ImageDescriptor->GetDimensions(); - const mitk::PixelType ptype = this->m_ImageDescriptor->GetChannelTypeById(0); - - // Comparison ?>=0 not needed since all position[i] and timestep are unsigned int - // (position[0]>=0 && position[1] >=0 && position[2]>=0 && timestep>=0) - // bug-11978 : we still need to catch index with negative values - if (position[0] < 0 || position[1] < 0 || position[2] < 0) - { - MITK_WARN << "Given position (" << position << ") is out of image range, returning 0."; - } - // check if the given position is inside the index range of the image, the 3rd dimension needs to be compared only if - // the dimension is not 0 - else if ((unsigned int)position[0] >= imageDims[0] || (unsigned int)position[1] >= imageDims[1] || - (imageDims[2] && (unsigned int)position[2] >= imageDims[2])) - { - MITK_WARN << "Given position (" << position << ") is out of image range, returning 0."; - } - else - { - const unsigned int offset = component + - ptype.GetNumberOfComponents() * (position[0] + position[1] * imageDims[0] + - position[2] * imageDims[0] * imageDims[1] + - timestep * imageDims[0] * imageDims[1] * imageDims[2]); - - mitkPixelTypeMultiplex3(AccessPixel, ptype, this->GetData(), offset, value); - } - - return value; -} - -double mitk::Image::GetPixelValueByWorldCoordinate(const mitk::Point3D &position, - unsigned int timestep, - unsigned int component) -{ - double value = 0.0; - if (this->GetTimeSteps() < timestep) - { - timestep = this->GetTimeSteps(); - } - - itk::Index<3> itkIndex; - this->GetGeometry()->WorldToIndex(position, itkIndex); - - value = this->GetPixelValueByIndex(itkIndex, timestep, component); - - return value; -} - vtkImageData *mitk::Image::GetVtkImageData(int t, int n) { if (m_Initialized == false) { if (GetSource().IsNull()) return nullptr; if (GetSource()->Updating() == false) GetSource()->UpdateOutputInformation(); } ImageDataItemPointer volume = GetVolumeData(t, n); return volume.GetPointer() == nullptr ? nullptr : volume->GetVtkImageAccessor(this)->GetVtkImageData(); } const vtkImageData *mitk::Image::GetVtkImageData(int t, int n) const { if (m_Initialized == false) { if (GetSource().IsNull()) return nullptr; if (GetSource()->Updating() == false) GetSource()->UpdateOutputInformation(); } ImageDataItemPointer volume = GetVolumeData(t, n); return volume.GetPointer() == nullptr ? nullptr : volume->GetVtkImageAccessor(this)->GetVtkImageData(); } mitk::Image::ImageDataItemPointer mitk::Image::GetSliceData( int s, int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const { MutexHolder lock(m_ImageDataArraysLock); return GetSliceData_unlocked(s, t, n, data, importMemoryManagement); } mitk::Image::ImageDataItemPointer mitk::Image::GetSliceData_unlocked( int s, int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const { if (IsValidSlice(s, t, n) == false) return nullptr; const size_t ptypeSize = this->m_ImageDescriptor->GetChannelTypeById(n).GetSize(); // slice directly available? int pos = GetSliceIndex(s, t, n); if (m_Slices[pos].GetPointer() != nullptr) { return m_Slices[pos]; } // is slice available as part of a volume that is available? ImageDataItemPointer sl, ch, vol; vol = m_Volumes[GetVolumeIndex(t, n)]; if ((vol.GetPointer() != nullptr) && (vol->IsComplete())) { sl = new ImageDataItem(*vol, m_ImageDescriptor, t, 2, data, importMemoryManagement == ManageMemory, ((size_t)s) * m_OffsetTable[2] * (ptypeSize)); sl->SetComplete(true); return m_Slices[pos] = sl; } // is slice available as part of a channel that is available? ch = m_Channels[n]; if ((ch.GetPointer() != nullptr) && (ch->IsComplete())) { sl = new ImageDataItem(*ch, m_ImageDescriptor, t, 2, data, importMemoryManagement == ManageMemory, (((size_t)s) * m_OffsetTable[2] + ((size_t)t) * m_OffsetTable[3]) * (ptypeSize)); sl->SetComplete(true); return m_Slices[pos] = sl; } // slice is unavailable. Can we calculate it? if ((GetSource().IsNotNull()) && (GetSource()->Updating() == false)) { // ... wir mussen rechnen!!! .... m_RequestedRegion.SetIndex(0, 0); m_RequestedRegion.SetIndex(1, 0); m_RequestedRegion.SetIndex(2, s); m_RequestedRegion.SetIndex(3, t); m_RequestedRegion.SetIndex(4, n); m_RequestedRegion.SetSize(0, m_Dimensions[0]); m_RequestedRegion.SetSize(1, m_Dimensions[1]); m_RequestedRegion.SetSize(2, 1); m_RequestedRegion.SetSize(3, 1); m_RequestedRegion.SetSize(4, 1); m_RequestedRegionInitialized = true; GetSource()->Update(); if (IsSliceSet_unlocked(s, t, n)) // yes: now we can call ourselves without the risk of a endless loop (see "if" above) return GetSliceData_unlocked(s, t, n, data, importMemoryManagement); else return nullptr; } else { ImageDataItemPointer item = AllocateSliceData_unlocked(s, t, n, data, importMemoryManagement); item->SetComplete(true); return item; } } mitk::Image::ImageDataItemPointer mitk::Image::GetVolumeData(int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const { MutexHolder lock(m_ImageDataArraysLock); return GetVolumeData_unlocked(t, n, data, importMemoryManagement); } mitk::Image::ImageDataItemPointer mitk::Image::GetVolumeData_unlocked( int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const { if (IsValidVolume(t, n) == false) return nullptr; ImageDataItemPointer ch, vol; // volume directly available? int pos = GetVolumeIndex(t, n); vol = m_Volumes[pos]; if ((vol.GetPointer() != nullptr) && (vol->IsComplete())) return vol; const size_t ptypeSize = this->m_ImageDescriptor->GetChannelTypeById(n).GetSize(); // is volume available as part of a channel that is available? ch = m_Channels[n]; if ((ch.GetPointer() != nullptr) && (ch->IsComplete())) { vol = new ImageDataItem(*ch, m_ImageDescriptor, t, 3, data, importMemoryManagement == ManageMemory, (((size_t)t) * m_OffsetTable[3]) * (ptypeSize)); vol->SetComplete(true); return m_Volumes[pos] = vol; } // let's see if all slices of the volume are set, so that we can (could) combine them to a volume bool complete = true; unsigned int s; for (s = 0; s < m_Dimensions[2]; ++s) { if (m_Slices[GetSliceIndex(s, t, n)].GetPointer() == nullptr) { complete = false; break; } } if (complete) { // if there is only single slice we do not need to combine anything if (m_Dimensions[2] <= 1) { ImageDataItemPointer sl; sl = GetSliceData_unlocked(0, t, n, data, importMemoryManagement); vol = new ImageDataItem(*sl, m_ImageDescriptor, t, 3, data, importMemoryManagement == ManageMemory); vol->SetComplete(true); } else { mitk::PixelType chPixelType = this->m_ImageDescriptor->GetChannelTypeById(n); vol = m_Volumes[pos]; // ok, let's combine the slices! if (vol.GetPointer() == nullptr) { vol = new ImageDataItem(chPixelType, t, 3, m_Dimensions, nullptr, true); } vol->SetComplete(true); size_t size = m_OffsetTable[2] * (ptypeSize); for (s = 0; s < m_Dimensions[2]; ++s) { int posSl; ImageDataItemPointer sl; posSl = GetSliceIndex(s, t, n); sl = m_Slices[posSl]; if (sl->GetParent() != vol) { // copy data of slices in volume size_t offset = ((size_t)s) * size; std::memcpy(static_cast(vol->GetData()) + offset, sl->GetData(), size); // FIXME mitkIpPicDescriptor * pic = sl->GetPicDescriptor(); // replace old slice with reference to volume sl = new ImageDataItem( *vol, m_ImageDescriptor, t, 2, data, importMemoryManagement == ManageMemory, ((size_t)s) * size); sl->SetComplete(true); // mitkIpFuncCopyTags(sl->GetPicDescriptor(), pic); m_Slices[posSl] = sl; } } // if(vol->GetPicDescriptor()->info->tags_head==nullptr) // mitkIpFuncCopyTags(vol->GetPicDescriptor(), m_Slices[GetSliceIndex(0,t,n)]->GetPicDescriptor()); } return m_Volumes[pos] = vol; } // volume is unavailable. Can we calculate it? if ((GetSource().IsNotNull()) && (GetSource()->Updating() == false)) { // ... wir muessen rechnen!!! .... m_RequestedRegion.SetIndex(0, 0); m_RequestedRegion.SetIndex(1, 0); m_RequestedRegion.SetIndex(2, 0); m_RequestedRegion.SetIndex(3, t); m_RequestedRegion.SetIndex(4, n); m_RequestedRegion.SetSize(0, m_Dimensions[0]); m_RequestedRegion.SetSize(1, m_Dimensions[1]); m_RequestedRegion.SetSize(2, m_Dimensions[2]); m_RequestedRegion.SetSize(3, 1); m_RequestedRegion.SetSize(4, 1); m_RequestedRegionInitialized = true; GetSource()->Update(); if (IsVolumeSet_unlocked(t, n)) // yes: now we can call ourselves without the risk of a endless loop (see "if" above) return GetVolumeData_unlocked(t, n, data, importMemoryManagement); else return nullptr; } else { ImageDataItemPointer item = AllocateVolumeData_unlocked(t, n, data, importMemoryManagement); item->SetComplete(true); return item; } } mitk::Image::ImageDataItemPointer mitk::Image::GetChannelData(int n, void *data, ImportMemoryManagementType importMemoryManagement) const { MutexHolder lock(m_ImageDataArraysLock); return GetChannelData_unlocked(n, data, importMemoryManagement); } mitk::Image::ImageDataItemPointer mitk::Image::GetChannelData_unlocked( int n, void *data, ImportMemoryManagementType importMemoryManagement) const { if (IsValidChannel(n) == false) return nullptr; ImageDataItemPointer ch, vol; ch = m_Channels[n]; if ((ch.GetPointer() != nullptr) && (ch->IsComplete())) return ch; // let's see if all volumes are set, so that we can (could) combine them to a channel if (IsChannelSet_unlocked(n)) { // if there is only one time frame we do not need to combine anything if (m_Dimensions[3] <= 1) { vol = GetVolumeData_unlocked(0, n, data, importMemoryManagement); ch = new ImageDataItem(*vol, m_ImageDescriptor, 0, m_ImageDescriptor->GetNumberOfDimensions(), data, importMemoryManagement == ManageMemory); ch->SetComplete(true); } else { const size_t ptypeSize = this->m_ImageDescriptor->GetChannelTypeById(n).GetSize(); ch = m_Channels[n]; // ok, let's combine the volumes! if (ch.GetPointer() == nullptr) ch = new ImageDataItem(this->m_ImageDescriptor, -1, nullptr, true); ch->SetComplete(true); size_t size = m_OffsetTable[m_Dimension - 1] * (ptypeSize); unsigned int t; auto slicesIt = m_Slices.begin() + n * m_Dimensions[2] * m_Dimensions[3]; for (t = 0; t < m_Dimensions[3]; ++t) { int posVol; ImageDataItemPointer vol; posVol = GetVolumeIndex(t, n); vol = GetVolumeData_unlocked(t, n, data, importMemoryManagement); if (vol->GetParent() != ch) { // copy data of volume in channel size_t offset = ((size_t)t) * m_OffsetTable[3] * (ptypeSize); std::memcpy(static_cast(ch->GetData()) + offset, vol->GetData(), size); // REVEIW FIX mitkIpPicDescriptor * pic = vol->GetPicDescriptor(); // replace old volume with reference to channel vol = new ImageDataItem(*ch, m_ImageDescriptor, t, 3, data, importMemoryManagement == ManageMemory, offset); vol->SetComplete(true); // mitkIpFuncCopyTags(vol->GetPicDescriptor(), pic); m_Volumes[posVol] = vol; // get rid of slices - they may point to old volume ImageDataItemPointer dnull = nullptr; for (unsigned int i = 0; i < m_Dimensions[2]; ++i, ++slicesIt) { assert(slicesIt != m_Slices.end()); *slicesIt = dnull; } } } // REVIEW FIX // if(ch->GetPicDescriptor()->info->tags_head==nullptr) // mitkIpFuncCopyTags(ch->GetPicDescriptor(), m_Volumes[GetVolumeIndex(0,n)]->GetPicDescriptor()); } return m_Channels[n] = ch; } // channel is unavailable. Can we calculate it? if ((GetSource().IsNotNull()) && (GetSource()->Updating() == false)) { // ... wir muessen rechnen!!! .... m_RequestedRegion.SetIndex(0, 0); m_RequestedRegion.SetIndex(1, 0); m_RequestedRegion.SetIndex(2, 0); m_RequestedRegion.SetIndex(3, 0); m_RequestedRegion.SetIndex(4, n); m_RequestedRegion.SetSize(0, m_Dimensions[0]); m_RequestedRegion.SetSize(1, m_Dimensions[1]); m_RequestedRegion.SetSize(2, m_Dimensions[2]); m_RequestedRegion.SetSize(3, m_Dimensions[3]); m_RequestedRegion.SetSize(4, 1); m_RequestedRegionInitialized = true; GetSource()->Update(); // did it work? if (IsChannelSet_unlocked(n)) // yes: now we can call ourselves without the risk of a endless loop (see "if" above) return GetChannelData_unlocked(n, data, importMemoryManagement); else return nullptr; } else { ImageDataItemPointer item = AllocateChannelData_unlocked(n, data, importMemoryManagement); item->SetComplete(true); return item; } } bool mitk::Image::IsSliceSet(int s, int t, int n) const { MutexHolder lock(m_ImageDataArraysLock); return IsSliceSet_unlocked(s, t, n); } bool mitk::Image::IsSliceSet_unlocked(int s, int t, int n) const { if (IsValidSlice(s, t, n) == false) return false; if (m_Slices[GetSliceIndex(s, t, n)].GetPointer() != nullptr) { return true; } ImageDataItemPointer ch, vol; vol = m_Volumes[GetVolumeIndex(t, n)]; if ((vol.GetPointer() != nullptr) && (vol->IsComplete())) { return true; } ch = m_Channels[n]; if ((ch.GetPointer() != nullptr) && (ch->IsComplete())) { return true; } return false; } bool mitk::Image::IsVolumeSet(int t, int n) const { MutexHolder lock(m_ImageDataArraysLock); return IsVolumeSet_unlocked(t, n); } bool mitk::Image::IsVolumeSet_unlocked(int t, int n) const { if (IsValidVolume(t, n) == false) return false; ImageDataItemPointer ch, vol; // volume directly available? vol = m_Volumes[GetVolumeIndex(t, n)]; if ((vol.GetPointer() != nullptr) && (vol->IsComplete())) return true; // is volume available as part of a channel that is available? ch = m_Channels[n]; if ((ch.GetPointer() != nullptr) && (ch->IsComplete())) return true; // let's see if all slices of the volume are set, so that we can (could) combine them to a volume unsigned int s; for (s = 0; s < m_Dimensions[2]; ++s) { if (m_Slices[GetSliceIndex(s, t, n)].GetPointer() == nullptr) { return false; } } return true; } bool mitk::Image::IsChannelSet(int n) const { MutexHolder lock(m_ImageDataArraysLock); return IsChannelSet_unlocked(n); } bool mitk::Image::IsChannelSet_unlocked(int n) const { if (IsValidChannel(n) == false) return false; ImageDataItemPointer ch, vol; ch = m_Channels[n]; if ((ch.GetPointer() != nullptr) && (ch->IsComplete())) return true; // let's see if all volumes are set, so that we can (could) combine them to a channel unsigned int t; for (t = 0; t < m_Dimensions[3]; ++t) { if (IsVolumeSet_unlocked(t, n) == false) { return false; } } return true; } bool mitk::Image::SetSlice(const void *data, int s, int t, int n) { // const_cast is no risk for ImportMemoryManagementType == CopyMemory return SetImportSlice(const_cast(data), s, t, n, CopyMemory); } bool mitk::Image::SetVolume(const void *data, int t, int n) { // const_cast is no risk for ImportMemoryManagementType == CopyMemory return SetImportVolume(const_cast(data), t, n, CopyMemory); } bool mitk::Image::SetChannel(const void *data, int n) { // const_cast is no risk for ImportMemoryManagementType == CopyMemory return SetImportChannel(const_cast(data), n, CopyMemory); } bool mitk::Image::SetImportSlice(void *data, int s, int t, int n, ImportMemoryManagementType importMemoryManagement) { if (IsValidSlice(s, t, n) == false) return false; ImageDataItemPointer sl; const size_t ptypeSize = this->m_ImageDescriptor->GetChannelTypeById(n).GetSize(); if (IsSliceSet(s, t, n)) { sl = GetSliceData(s, t, n, data, importMemoryManagement); if (sl->GetManageMemory() == false) { sl = AllocateSliceData(s, t, n, data, importMemoryManagement); if (sl.GetPointer() == nullptr) return false; } if (sl->GetData() != data) std::memcpy(sl->GetData(), data, m_OffsetTable[2] * (ptypeSize)); sl->Modified(); // we have changed the data: call Modified()! Modified(); } else { sl = AllocateSliceData(s, t, n, data, importMemoryManagement); if (sl.GetPointer() == nullptr) return false; if (sl->GetData() != data) std::memcpy(sl->GetData(), data, m_OffsetTable[2] * (ptypeSize)); // we just added a missing slice, which is not regarded as modification. // Therefore, we do not call Modified()! } return true; } bool mitk::Image::SetImportVolume(void *data, int t, int n, ImportMemoryManagementType importMemoryManagement) { if (IsValidVolume(t, n) == false) return false; const size_t ptypeSize = this->m_ImageDescriptor->GetChannelTypeById(n).GetSize(); ImageDataItemPointer vol; if (IsVolumeSet(t, n)) { vol = GetVolumeData(t, n, data, importMemoryManagement); if (vol->GetManageMemory() == false) { vol = AllocateVolumeData(t, n, data, importMemoryManagement); if (vol.GetPointer() == nullptr) return false; } if (vol->GetData() != data) std::memcpy(vol->GetData(), data, m_OffsetTable[3] * (ptypeSize)); vol->Modified(); vol->SetComplete(true); // we have changed the data: call Modified()! Modified(); } else { vol = AllocateVolumeData(t, n, data, importMemoryManagement); if (vol.GetPointer() == nullptr) return false; if (vol->GetData() != data) { std::memcpy(vol->GetData(), data, m_OffsetTable[3] * (ptypeSize)); } vol->SetComplete(true); this->m_ImageDescriptor->GetChannelDescriptor(n).SetData(vol->GetData()); // we just added a missing Volume, which is not regarded as modification. // Therefore, we do not call Modified()! } return true; } bool mitk::Image::SetImportVolume(const void *const_data, int t, int n) { return this->SetImportVolume(const_cast(const_data), t, n, CopyMemory); } bool mitk::Image::SetImportChannel(void *data, int n, ImportMemoryManagementType importMemoryManagement) { if (IsValidChannel(n) == false) return false; // channel descriptor const size_t ptypeSize = this->m_ImageDescriptor->GetChannelTypeById(n).GetSize(); ImageDataItemPointer ch; if (IsChannelSet(n)) { ch = GetChannelData(n, data, importMemoryManagement); if (ch->GetManageMemory() == false) { ch = AllocateChannelData(n, data, importMemoryManagement); if (ch.GetPointer() == nullptr) return false; } if (ch->GetData() != data) std::memcpy(ch->GetData(), data, m_OffsetTable[4] * (ptypeSize)); ch->Modified(); ch->SetComplete(true); // we have changed the data: call Modified()! Modified(); } else { ch = AllocateChannelData(n, data, importMemoryManagement); if (ch.GetPointer() == nullptr) return false; if (ch->GetData() != data) std::memcpy(ch->GetData(), data, m_OffsetTable[4] * (ptypeSize)); ch->SetComplete(true); this->m_ImageDescriptor->GetChannelDescriptor(n).SetData(ch->GetData()); // we just added a missing Channel, which is not regarded as modification. // Therefore, we do not call Modified()! } return true; } void mitk::Image::Initialize() { ImageDataItemPointerArray::iterator it, end; for (it = m_Slices.begin(), end = m_Slices.end(); it != end; ++it) { (*it) = nullptr; } for (it = m_Volumes.begin(), end = m_Volumes.end(); it != end; ++it) { (*it) = nullptr; } for (it = m_Channels.begin(), end = m_Channels.end(); it != end; ++it) { (*it) = nullptr; } m_CompleteData = nullptr; if (m_ImageStatistics == nullptr) { m_ImageStatistics = new mitk::ImageStatisticsHolder(this); } SetRequestedRegionToLargestPossibleRegion(); } void mitk::Image::Initialize(const mitk::ImageDescriptor::Pointer inDesc) { // store the descriptor this->m_ImageDescriptor = inDesc; // initialize image this->Initialize( inDesc->GetChannelDescriptor(0).GetPixelType(), inDesc->GetNumberOfDimensions(), inDesc->GetDimensions(), 1); } void mitk::Image::Initialize(const mitk::PixelType &type, unsigned int dimension, const unsigned int *dimensions, unsigned int channels) { Clear(); m_Dimension = dimension; if (!dimensions) itkExceptionMacro(<< "invalid zero dimension image"); unsigned int i; for (i = 0; i < dimension; ++i) { if (dimensions[i] < 1) itkExceptionMacro(<< "invalid dimension[" << i << "]: " << dimensions[i]); } // create new array since the old was deleted m_Dimensions = new unsigned int[MAX_IMAGE_DIMENSIONS]; // initialize the first four dimensions to 1, the remaining 4 to 0 FILL_C_ARRAY(m_Dimensions, 4, 1u); FILL_C_ARRAY((m_Dimensions + 4), 4, 0u); // copy in the passed dimension information std::memcpy(m_Dimensions, dimensions, sizeof(unsigned int) * m_Dimension); this->m_ImageDescriptor = mitk::ImageDescriptor::New(); this->m_ImageDescriptor->Initialize(this->m_Dimensions, this->m_Dimension); for (i = 0; i < 4; ++i) { m_LargestPossibleRegion.SetIndex(i, 0); m_LargestPossibleRegion.SetSize(i, m_Dimensions[i]); } m_LargestPossibleRegion.SetIndex(i, 0); m_LargestPossibleRegion.SetSize(i, channels); if (m_LargestPossibleRegion.GetNumberOfPixels() == 0) { delete[] m_Dimensions; m_Dimensions = nullptr; return; } for (unsigned int i = 0u; i < channels; i++) { this->m_ImageDescriptor->AddNewChannel(type); } PlaneGeometry::Pointer planegeometry = PlaneGeometry::New(); planegeometry->InitializeStandardPlane(m_Dimensions[0], m_Dimensions[1]); SlicedGeometry3D::Pointer slicedGeometry = SlicedGeometry3D::New(); slicedGeometry->InitializeEvenlySpaced(planegeometry, m_Dimensions[2]); ProportionalTimeGeometry::Pointer timeGeometry = ProportionalTimeGeometry::New(); timeGeometry->Initialize(slicedGeometry, m_Dimensions[3]); for (TimeStepType step = 0; step < timeGeometry->CountTimeSteps(); ++step) { timeGeometry->GetGeometryForTimeStep(step)->ImageGeometryOn(); } SetTimeGeometry(timeGeometry); ImageDataItemPointer dnull = nullptr; m_Channels.assign(GetNumberOfChannels(), dnull); m_Volumes.assign(GetNumberOfChannels() * m_Dimensions[3], dnull); m_Slices.assign(GetNumberOfChannels() * m_Dimensions[3] * m_Dimensions[2], dnull); ComputeOffsetTable(); Initialize(); m_Initialized = true; } void mitk::Image::Initialize(const mitk::PixelType &type, const mitk::BaseGeometry &geometry, unsigned int channels, int tDim) { mitk::ProportionalTimeGeometry::Pointer timeGeometry = ProportionalTimeGeometry::New(); timeGeometry->Initialize(geometry.Clone(), tDim); this->Initialize(type, *timeGeometry, channels, tDim); } void mitk::Image::Initialize(const mitk::PixelType &type, const mitk::TimeGeometry &geometry, unsigned int channels, int tDim) { unsigned int dimensions[5]; dimensions[0] = (unsigned int)(geometry.GetGeometryForTimeStep(0)->GetExtent(0) + 0.5); dimensions[1] = (unsigned int)(geometry.GetGeometryForTimeStep(0)->GetExtent(1) + 0.5); dimensions[2] = (unsigned int)(geometry.GetGeometryForTimeStep(0)->GetExtent(2) + 0.5); dimensions[3] = (tDim > 0) ? tDim : geometry.CountTimeSteps(); dimensions[4] = 0; unsigned int dimension = 2; if (dimensions[2] > 1) dimension = 3; if (dimensions[3] > 1) dimension = 4; Initialize(type, dimension, dimensions, channels); if (geometry.CountTimeSteps() > 1) { TimeGeometry::Pointer cloned = geometry.Clone(); SetTimeGeometry(cloned.GetPointer()); // make sure the image geometry flag is properly set for all time steps for (TimeStepType step = 0; step < cloned->CountTimeSteps(); ++step) { if (!cloned->GetGeometryCloneForTimeStep(step)->GetImageGeometry()) { MITK_WARN("Image.3DnT.Initialize") << " Attempt to initialize an image with a non-image geometry. " "Re-interpretting the initialization geometry for timestep " << step << " as image geometry, the original geometry remains unchanged."; cloned->GetGeometryForTimeStep(step)->ImageGeometryOn(); } } } else { // make sure the image geometry coming from outside has proper value of the image geometry flag BaseGeometry::Pointer cloned = geometry.GetGeometryCloneForTimeStep(0)->Clone(); if (!cloned->GetImageGeometry()) { MITK_WARN("Image.Initialize") << " Attempt to initialize an image with a non-image geometry. Re-interpretting " "the initialization geometry as image geometry, the original geometry remains " "unchanged."; cloned->ImageGeometryOn(); } Superclass::SetGeometry(cloned); } - /* //Old //TODO_GOETZ Really necessary? - mitk::BoundingBox::BoundsArrayType bounds = geometry.GetBoundingBoxInWorld()->GetBounds(); - if( (bounds[0] != 0.0) || (bounds[2] != 0.0) || (bounds[4] != 0.0) ) - { - SlicedGeometry3D* slicedGeometry = GetSlicedGeometry(0); - - mitk::Point3D origin; origin.Fill(0.0); - slicedGeometry->IndexToWorld(origin, origin); - - bounds[1]-=bounds[0]; bounds[3]-=bounds[2]; bounds[5]-=bounds[4]; - bounds[0] = 0.0; bounds[2] = 0.0; bounds[4] = 0.0; - this->m_ImageDescriptor->Initialize( this->m_Dimensions, this->m_Dimension ); - slicedGeometry->SetBounds(bounds); - slicedGeometry->GetIndexToWorldTransform()->SetOffset(origin.GetVnlVector().data_block()); - - ProportionalTimeGeometry::Pointer timeGeometry = ProportionalTimeGeometry::New(); - timeGeometry->Initialize(slicedGeometry, m_Dimensions[3]); - SetTimeGeometry(timeGeometry); - }*/ -} - -void mitk::Image::Initialize(const mitk::PixelType &, - int, - const mitk::PlaneGeometry &, - bool, - unsigned int, - int) -{ - mitkThrow() << "Use this method without the flipped parameter (direction is specified by the handedness of the PlaneGeometry instead)."; } void mitk::Image::Initialize(const mitk::PixelType &type, int sDim, const mitk::PlaneGeometry &geometry2d, unsigned int channels, int tDim) { SlicedGeometry3D::Pointer slicedGeometry = SlicedGeometry3D::New(); slicedGeometry->InitializeEvenlySpaced(geometry2d.Clone(), sDim); Initialize(type, *slicedGeometry, channels, tDim); } void mitk::Image::Initialize(const mitk::Image *image) { Initialize(image->GetPixelType(), *image->GetTimeGeometry()); } void mitk::Image::Initialize(vtkImageData *vtkimagedata, int channels, int tDim, int sDim, int pDim) { if (vtkimagedata == nullptr) return; m_Dimension = vtkimagedata->GetDataDimension(); unsigned int i, *tmpDimensions = new unsigned int[m_Dimension > 4 ? m_Dimension : 4]; for (i = 0; i < m_Dimension; ++i) tmpDimensions[i] = vtkimagedata->GetDimensions()[i]; if (m_Dimension < 4) { unsigned int *p; for (i = 0, p = tmpDimensions + m_Dimension; i < 4 - m_Dimension; ++i, ++p) *p = 1; } if (pDim >= 0) { tmpDimensions[1] = pDim; if (m_Dimension < 2) m_Dimension = 2; } if (sDim >= 0) { tmpDimensions[2] = sDim; if (m_Dimension < 3) m_Dimension = 3; } if (tDim >= 0) { tmpDimensions[3] = tDim; if (m_Dimension < 4) m_Dimension = 4; } mitk::PixelType pixelType(MakePixelType(vtkimagedata)); Initialize(pixelType, m_Dimension, tmpDimensions, channels); const double *spacinglist = vtkimagedata->GetSpacing(); Vector3D spacing; FillVector3D(spacing, spacinglist[0], 1.0, 1.0); if (m_Dimension >= 2) spacing[1] = spacinglist[1]; if (m_Dimension >= 3) spacing[2] = spacinglist[2]; // access origin of vtkImage Point3D origin; double vtkorigin[3]; vtkimagedata->GetOrigin(vtkorigin); FillVector3D(origin, vtkorigin[0], 0.0, 0.0); if (m_Dimension >= 2) origin[1] = vtkorigin[1]; if (m_Dimension >= 3) origin[2] = vtkorigin[2]; SlicedGeometry3D *slicedGeometry = GetSlicedGeometry(0); // re-initialize PlaneGeometry with origin and direction auto *planeGeometry = static_cast(slicedGeometry->GetPlaneGeometry(0)); planeGeometry->SetOrigin(origin); // re-initialize SlicedGeometry3D slicedGeometry->SetOrigin(origin); slicedGeometry->SetSpacing(spacing); ProportionalTimeGeometry::Pointer timeGeometry = ProportionalTimeGeometry::New(); timeGeometry->Initialize(slicedGeometry, m_Dimensions[3]); SetTimeGeometry(timeGeometry); delete[] tmpDimensions; } bool mitk::Image::IsValidSlice(int s, int t, int n) const { if (m_Initialized) return ((s >= 0) && (s < (int)m_Dimensions[2]) && (t >= 0) && (t < (int)m_Dimensions[3]) && (n >= 0) && (n < (int)GetNumberOfChannels())); else return false; } bool mitk::Image::IsValidVolume(int t, int n) const { if (m_Initialized) return IsValidSlice(0, t, n); else return false; } bool mitk::Image::IsValidChannel(int n) const { if (m_Initialized) return IsValidSlice(0, 0, n); else return false; } void mitk::Image::ComputeOffsetTable() { if (m_OffsetTable != nullptr) delete[] m_OffsetTable; m_OffsetTable = new size_t[m_Dimension > 4 ? m_Dimension + 1 : 4 + 1]; unsigned int i; size_t num = 1; m_OffsetTable[0] = 1; for (i = 0; i < m_Dimension; ++i) { num *= m_Dimensions[i]; m_OffsetTable[i + 1] = num; } for (; i < 4; ++i) m_OffsetTable[i + 1] = num; } bool mitk::Image::IsValidTimeStep(int t) const { return ((m_Dimension >= 4 && t <= (int)m_Dimensions[3] && t > 0) || (t == 0)); } void mitk::Image::Expand(unsigned int timeSteps) { if (timeSteps < 1) itkExceptionMacro(<< "Invalid timestep in Image!"); Superclass::Expand(timeSteps); } int mitk::Image::GetSliceIndex(int s, int t, int n) const { if (IsValidSlice(s, t, n) == false) return false; return ((size_t)s) + ((size_t)t) * m_Dimensions[2] + ((size_t)n) * m_Dimensions[3] * m_Dimensions[2]; //?? } int mitk::Image::GetVolumeIndex(int t, int n) const { if (IsValidVolume(t, n) == false) return false; return ((size_t)t) + ((size_t)n) * m_Dimensions[3]; //?? } mitk::Image::ImageDataItemPointer mitk::Image::AllocateSliceData( int s, int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const { MutexHolder lock(m_ImageDataArraysLock); return AllocateSliceData_unlocked(s, t, n, data, importMemoryManagement); } mitk::Image::ImageDataItemPointer mitk::Image::AllocateSliceData_unlocked( int s, int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const { int pos; pos = GetSliceIndex(s, t, n); const size_t ptypeSize = this->m_ImageDescriptor->GetChannelTypeById(n).GetSize(); // is slice available as part of a volume that is available? ImageDataItemPointer sl, ch, vol; vol = m_Volumes[GetVolumeIndex(t, n)]; if (vol.GetPointer() != nullptr) { sl = new ImageDataItem(*vol, m_ImageDescriptor, t, 2, data, importMemoryManagement == ManageMemory, ((size_t)s) * m_OffsetTable[2] * (ptypeSize)); sl->SetComplete(true); return m_Slices[pos] = sl; } // is slice available as part of a channel that is available? ch = m_Channels[n]; if (ch.GetPointer() != nullptr) { sl = new ImageDataItem(*ch, m_ImageDescriptor, t, 2, data, importMemoryManagement == ManageMemory, (((size_t)s) * m_OffsetTable[2] + ((size_t)t) * m_OffsetTable[3]) * (ptypeSize)); sl->SetComplete(true); return m_Slices[pos] = sl; } // allocate new volume (instead of a single slice to keep data together!) m_Volumes[GetVolumeIndex(t, n)] = vol = AllocateVolumeData_unlocked(t, n, nullptr, importMemoryManagement); sl = new ImageDataItem(*vol, m_ImageDescriptor, t, 2, data, importMemoryManagement == ManageMemory, ((size_t)s) * m_OffsetTable[2] * (ptypeSize)); sl->SetComplete(true); return m_Slices[pos] = sl; ////ALTERNATIVE: //// allocate new slice // sl=new ImageDataItem(*m_PixelType, 2, m_Dimensions); // m_Slices[pos]=sl; // return vol; } mitk::Image::ImageDataItemPointer mitk::Image::AllocateVolumeData( int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const { MutexHolder lock(m_ImageDataArraysLock); return AllocateVolumeData_unlocked(t, n, data, importMemoryManagement); } mitk::Image::ImageDataItemPointer mitk::Image::AllocateVolumeData_unlocked( int t, int n, void *data, ImportMemoryManagementType importMemoryManagement) const { int pos; pos = GetVolumeIndex(t, n); const size_t ptypeSize = this->m_ImageDescriptor->GetChannelTypeById(n).GetSize(); // is volume available as part of a channel that is available? ImageDataItemPointer ch, vol; ch = m_Channels[n]; if (ch.GetPointer() != nullptr) { vol = new ImageDataItem(*ch, m_ImageDescriptor, t, 3, data, importMemoryManagement == ManageMemory, (((size_t)t) * m_OffsetTable[3]) * (ptypeSize)); return m_Volumes[pos] = vol; } mitk::PixelType chPixelType = this->m_ImageDescriptor->GetChannelTypeById(n); // allocate new volume if (importMemoryManagement == CopyMemory) { vol = new ImageDataItem(chPixelType, t, 3, m_Dimensions, nullptr, true); if (data != nullptr) std::memcpy(vol->GetData(), data, m_OffsetTable[3] * (ptypeSize)); } else { vol = new ImageDataItem(chPixelType, t, 3, m_Dimensions, data, importMemoryManagement == ManageMemory); } m_Volumes[pos] = vol; return vol; } mitk::Image::ImageDataItemPointer mitk::Image::AllocateChannelData( int n, void *data, ImportMemoryManagementType importMemoryManagement) const { MutexHolder lock(m_ImageDataArraysLock); return AllocateChannelData_unlocked(n, data, importMemoryManagement); } mitk::Image::ImageDataItemPointer mitk::Image::AllocateChannelData_unlocked( int n, void *data, ImportMemoryManagementType importMemoryManagement) const { ImageDataItemPointer ch; // allocate new channel if (importMemoryManagement == CopyMemory) { const size_t ptypeSize = this->m_ImageDescriptor->GetChannelTypeById(n).GetSize(); ch = new ImageDataItem(this->m_ImageDescriptor, -1, nullptr, true); if (data != nullptr) std::memcpy(ch->GetData(), data, m_OffsetTable[4] * (ptypeSize)); } else { ch = new ImageDataItem(this->m_ImageDescriptor, -1, data, importMemoryManagement == ManageMemory); } m_Channels[n] = ch; return ch; } unsigned int *mitk::Image::GetDimensions() const { return m_Dimensions; } void mitk::Image::Clear() { Superclass::Clear(); delete[] m_Dimensions; m_Dimensions = nullptr; } void mitk::Image::SetGeometry(BaseGeometry *aGeometry3D) { // Please be aware of the 0.5 offset/pixel-center issue! See Geometry documentation for further information if (aGeometry3D->GetImageGeometry() == false) { MITK_INFO << "WARNING: Applied a non-image geometry onto an image. Please be SURE that this geometry is " "pixel-center-based! If it is not, you need to call " "Geometry3D->ChangeImageGeometryConsideringOriginOffset(true) before calling image->setGeometry(..)\n"; } Superclass::SetGeometry(aGeometry3D); for (TimeStepType step = 0; step < GetTimeGeometry()->CountTimeSteps(); ++step) GetTimeGeometry()->GetGeometryForTimeStep(step)->ImageGeometryOn(); } void mitk::Image::PrintSelf(std::ostream &os, itk::Indent indent) const { if (m_Initialized) { unsigned char i; os << indent << " Dimension: " << m_Dimension << std::endl; os << indent << " Dimensions: "; for (i = 0; i < m_Dimension; ++i) os << GetDimension(i) << " "; os << std::endl; for (unsigned int ch = 0; ch < this->m_ImageDescriptor->GetNumberOfChannels(); ch++) { mitk::PixelType chPixelType = this->m_ImageDescriptor->GetChannelTypeById(ch); os << indent << " Channel: " << this->m_ImageDescriptor->GetChannelName(ch) << std::endl; os << indent << " PixelType: " << chPixelType.GetPixelTypeAsString() << std::endl; os << indent << " BytesPerElement: " << chPixelType.GetSize() << std::endl; os << indent << " ComponentType: " << chPixelType.GetComponentTypeAsString() << std::endl; os << indent << " NumberOfComponents: " << chPixelType.GetNumberOfComponents() << std::endl; os << indent << " BitsPerComponent: " << chPixelType.GetBitsPerComponent() << std::endl; } } else { os << indent << " Image not initialized: m_Initialized: false" << std::endl; } Superclass::PrintSelf(os, indent); } bool mitk::Image::IsRotated() const { const mitk::BaseGeometry *geo = this->GetGeometry(); bool ret = false; if (geo) { const vnl_matrix_fixed &mx = geo->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix(); mitk::ScalarType ref = 0; for (short k = 0; k < 3; ++k) ref += mx[k][k]; ref /= 1000; // Arbitrary value; if a non-diagonal (nd) element is bigger then this, matrix is considered nd. for (short i = 0; i < 3; ++i) { for (short j = 0; j < 3; ++j) { if (i != j) { if (std::abs(mx[i][j]) > ref) // matrix is nd ret = true; } } } } return ret; } -mitk::ScalarType mitk::Image::GetScalarValueMin(int t) const -{ - return m_ImageStatistics->GetScalarValueMin(t); -} - -//## \brief Get the maximum for scalar images -mitk::ScalarType mitk::Image::GetScalarValueMax(int t) const -{ - return m_ImageStatistics->GetScalarValueMax(t); -} - -//## \brief Get the second smallest value for scalar images -mitk::ScalarType mitk::Image::GetScalarValue2ndMin(int t) const -{ - return m_ImageStatistics->GetScalarValue2ndMin(t); -} - -mitk::ScalarType mitk::Image::GetScalarValueMinNoRecompute(unsigned int t) const -{ - return m_ImageStatistics->GetScalarValueMinNoRecompute(t); -} - -mitk::ScalarType mitk::Image::GetScalarValue2ndMinNoRecompute(unsigned int t) const -{ - return m_ImageStatistics->GetScalarValue2ndMinNoRecompute(t); -} - -mitk::ScalarType mitk::Image::GetScalarValue2ndMax(int t) const -{ - return m_ImageStatistics->GetScalarValue2ndMax(t); -} - -mitk::ScalarType mitk::Image::GetScalarValueMaxNoRecompute(unsigned int t) const -{ - return m_ImageStatistics->GetScalarValueMaxNoRecompute(t); -} - -mitk::ScalarType mitk::Image::GetScalarValue2ndMaxNoRecompute(unsigned int t) const -{ - return m_ImageStatistics->GetScalarValue2ndMaxNoRecompute(t); -} - -mitk::ScalarType mitk::Image::GetCountOfMinValuedVoxels(int t) const -{ - return m_ImageStatistics->GetCountOfMinValuedVoxels(t); -} - -mitk::ScalarType mitk::Image::GetCountOfMaxValuedVoxels(int t) const -{ - return m_ImageStatistics->GetCountOfMaxValuedVoxels(t); -} - -unsigned int mitk::Image::GetCountOfMaxValuedVoxelsNoRecompute(unsigned int t) const -{ - return m_ImageStatistics->GetCountOfMaxValuedVoxelsNoRecompute(t); -} - -unsigned int mitk::Image::GetCountOfMinValuedVoxelsNoRecompute(unsigned int t) const -{ - return m_ImageStatistics->GetCountOfMinValuedVoxelsNoRecompute(t); -} - -bool mitk::Equal(const mitk::Image *leftHandSide, const mitk::Image *rightHandSide, ScalarType eps, bool verbose) -{ - if ((leftHandSide == nullptr) || (rightHandSide == nullptr)) - { - MITK_ERROR << "mitk::Equal(const mitk::Image* leftHandSide, const mitk::Image* rightHandSide, ScalarType eps, bool " - "verbose) does not work with nullptr pointer input."; - return false; - } - return mitk::Equal(*leftHandSide, *rightHandSide, eps, verbose); -} bool mitk::Equal(const mitk::Image &leftHandSide, const mitk::Image &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; // Dimensionality if (rightHandSide.GetDimension() != leftHandSide.GetDimension()) { if (verbose) { MITK_INFO << "[( Image )] Dimensionality differs."; MITK_INFO << "leftHandSide is " << leftHandSide.GetDimension() << "rightHandSide is " << rightHandSide.GetDimension(); } returnValue = false; } // Pair-wise dimension (size) comparison unsigned int minDimensionality = std::min(rightHandSide.GetDimension(), leftHandSide.GetDimension()); for (unsigned int i = 0; i < minDimensionality; ++i) { if (rightHandSide.GetDimension(i) != leftHandSide.GetDimension(i)) { returnValue = false; if (verbose) { MITK_INFO << "[( Image )] dimension differs."; MITK_INFO << "leftHandSide->GetDimension(" << i << ") is " << leftHandSide.GetDimension(i) << "rightHandSide->GetDimension(" << i << ") is " << rightHandSide.GetDimension(i); } } } // Pixeltype mitk::PixelType pixelTypeRightHandSide = rightHandSide.GetPixelType(); mitk::PixelType pixelTypeLeftHandSide = leftHandSide.GetPixelType(); if (!(pixelTypeRightHandSide == pixelTypeLeftHandSide)) { if (verbose) { MITK_INFO << "[( Image )] PixelType differs."; MITK_INFO << "leftHandSide is " << pixelTypeLeftHandSide.GetTypeAsString() << "rightHandSide is " << pixelTypeRightHandSide.GetTypeAsString(); } returnValue = false; } // Geometries if (!mitk::Equal(*leftHandSide.GetGeometry(), *rightHandSide.GetGeometry(), eps, verbose)) { if (verbose) { MITK_INFO << "[( Image )] Geometries differ."; } returnValue = false; } // Pixel values - default mode [ 0 threshold in difference ] // compare only if all previous checks were successfull, otherwise the ITK filter will throw an exception if (returnValue) { mitk::CompareImageDataFilter::Pointer compareFilter = mitk::CompareImageDataFilter::New(); compareFilter->SetInput(0, &rightHandSide); compareFilter->SetInput(1, &leftHandSide); compareFilter->SetTolerance(eps); compareFilter->Update(); if ((!compareFilter->GetResult())) { returnValue = false; if (verbose) { MITK_INFO << "[(Image)] Pixel values differ: "; compareFilter->GetCompareResults().PrintSelf(); } } } return returnValue; } diff --git a/Modules/Core/src/DataManagement/mitkLevelWindowManager.cpp b/Modules/Core/src/DataManagement/mitkLevelWindowManager.cpp index c0b04c6570..d67daa36e3 100644 --- a/Modules/Core/src/DataManagement/mitkLevelWindowManager.cpp +++ b/Modules/Core/src/DataManagement/mitkLevelWindowManager.cpp @@ -1,737 +1,723 @@ /*============================================================================ 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 "mitkLevelWindowManager.h" -#include "mitkDataStorage.h" + #include "mitkImage.h" #include "mitkMessage.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateBase.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateProperty.h" #include "mitkProperties.h" #include "mitkRenderingModeProperty.h" #include mitk::LevelWindowManager::LevelWindowManager() : m_DataStorage(nullptr) , m_LevelWindowProperty(nullptr) , m_AutoTopMost(true) , m_SelectedImagesMode(false) - , m_IsObserverTagSet(false) , m_CurrentImage(nullptr) , m_IsPropertyModifiedTagSet(false) , m_LevelWindowMutex(false) { } mitk::LevelWindowManager::~LevelWindowManager() { if (m_DataStorage.IsNotNull()) { m_DataStorage->AddNodeEvent.RemoveListener( MessageDelegate1(this, &LevelWindowManager::DataStorageAddedNode)); m_DataStorage->RemoveNodeEvent.RemoveListener( MessageDelegate1(this, &LevelWindowManager::DataStorageRemovedNode)); m_DataStorage = nullptr; } if (m_IsPropertyModifiedTagSet && m_LevelWindowProperty.IsNotNull()) { m_LevelWindowProperty->RemoveObserver(m_PropertyModifiedTag); m_IsPropertyModifiedTagSet = false; } - // clear both observer maps - this->ClearPropObserverLists(); + this->ClearPropertyObserverMaps(); } -void mitk::LevelWindowManager::SetDataStorage(DataStorage *ds) +void mitk::LevelWindowManager::SetDataStorage(DataStorage *dataStorage) { - if (ds == nullptr) + if (nullptr == dataStorage) return; - /* remove listeners of old DataStorage */ + // remove listeners of old DataStorage if (m_DataStorage.IsNotNull()) { m_DataStorage->AddNodeEvent.RemoveListener( MessageDelegate1(this, &LevelWindowManager::DataStorageAddedNode)); m_DataStorage->RemoveNodeEvent.RemoveListener( MessageDelegate1(this, &LevelWindowManager::DataStorageRemovedNode)); } - /* register listener for new DataStorage */ - m_DataStorage = ds; // register + // register listener for new DataStorage + m_DataStorage = dataStorage; m_DataStorage->AddNodeEvent.AddListener( MessageDelegate1(this, &LevelWindowManager::DataStorageAddedNode)); m_DataStorage->RemoveNodeEvent.AddListener( MessageDelegate1(this, &LevelWindowManager::DataStorageRemovedNode)); - this->DataStorageAddedNode(); // update us with new DataStorage + this->DataStorageAddedNode(); } mitk::DataStorage *mitk::LevelWindowManager::GetDataStorage() { return m_DataStorage.GetPointer(); } void mitk::LevelWindowManager::SetAutoTopMostImage(bool autoTopMost, const DataNode *removedNode/* = nullptr*/) { m_AutoTopMost = autoTopMost; if (false == m_AutoTopMost) { return; } // deactivate other mode m_SelectedImagesMode = false; if (m_IsPropertyModifiedTagSet && m_LevelWindowProperty.IsNotNull()) { m_LevelWindowProperty->RemoveObserver(m_PropertyModifiedTag); m_IsPropertyModifiedTagSet = false; } // find topmost image in the data storage if (m_DataStorage.IsNull()) { mitkThrow() << "DataStorage not set"; } DataNode::Pointer topLevelNode; int maxVisibleLayer = itk::NumericTraits::min(); m_LevelWindowProperty = nullptr; m_CurrentImage = nullptr; DataStorage::SetOfObjects::ConstPointer all = this->GetRelevantNodes(); for (DataStorage::SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { DataNode::Pointer node = it->Value(); if (node.IsNull() || node == removedNode) { continue; } + // reset the "imageForLevelWindow" of each node m_LevelWindowMutex = true; node->SetBoolProperty("imageForLevelWindow", false); m_LevelWindowMutex = false; if (false == node->IsVisible(nullptr)) { continue; } - int layer = -1; - node->GetIntProperty("layer", layer); - if (layer < maxVisibleLayer) + bool validRenderingMode = HasLevelWindowRenderingMode(node); + if (false == validRenderingMode) { continue; } - bool ignore = this->IgnoreNode(node); - if (ignore) + int layer = -1; + node->GetIntProperty("layer", layer); + if (layer <= maxVisibleLayer) { continue; } m_LevelWindowProperty = dynamic_cast(node->GetProperty("levelwindow")); topLevelNode = node; maxVisibleLayer = layer; } // this will set the "imageForLevelWindow" property and the 'm_CurrentImage' and call 'Modified()' this->SetLevelWindowProperty(m_LevelWindowProperty); if (m_LevelWindowProperty.IsNull()) { this->Modified(); } } void mitk::LevelWindowManager::SetSelectedImages(bool selectedImagesMode, const DataNode *removedNode/* = nullptr*/) { m_SelectedImagesMode = selectedImagesMode; if (false == m_SelectedImagesMode) { return; } // deactivate other mode m_AutoTopMost = false; if (m_IsPropertyModifiedTagSet && m_LevelWindowProperty.IsNotNull()) { m_LevelWindowProperty->RemoveObserver(m_PropertyModifiedTag); m_IsPropertyModifiedTagSet = false; } // find selected images in the data storage if (m_DataStorage.IsNull()) { mitkThrow() << "DataStorage not set"; } - DataNode::Pointer lastSelectedNode; m_LevelWindowProperty = nullptr; m_CurrentImage = nullptr; DataStorage::SetOfObjects::ConstPointer all = this->GetRelevantNodes(); for (DataStorage::SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { DataNode::Pointer node = it->Value(); if (node.IsNull() || node == removedNode) { continue; } + // reset the "imageForLevelWindow" of each node m_LevelWindowMutex = true; node->SetBoolProperty("imageForLevelWindow", false); m_LevelWindowMutex = false; if (false == node->IsSelected()) { continue; } - bool ignore = this->IgnoreNode(node); - if (ignore) + bool validRenderingMode = HasLevelWindowRenderingMode(node); + if (false == validRenderingMode) { continue; } m_LevelWindowProperty = dynamic_cast(node->GetProperty("levelwindow")); - m_RelevantDataNodes.push_back(node); - lastSelectedNode = node; + m_DataNodesForLevelWindow.push_back(node); // nodes are used inside "SetLevelWindow" if the level window is changed } // this will set the "imageForLevelWindow" property and the 'm_CurrentImage' and call 'Modified()' this->SetLevelWindowProperty(m_LevelWindowProperty); if (m_LevelWindowProperty.IsNull()) { this->Modified(); } } void mitk::LevelWindowManager::RecalculateLevelWindowForSelectedComponent(const itk::EventObject &event) { DataStorage::SetOfObjects::ConstPointer all = this->GetRelevantNodes(); for (DataStorage::SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { DataNode::Pointer node = it->Value(); if (node.IsNull()) continue; bool isSelected = false; node->GetBoolProperty("selected", isSelected); if (isSelected) { LevelWindow selectedLevelWindow; node->GetLevelWindow(selectedLevelWindow); // node is an image node because of predicates auto *image = dynamic_cast(node->GetData()); int displayedComponent = 0; if (image && (node->GetIntProperty("Image.Displayed Component", displayedComponent))) { // we found a selected image with a displayed component // let's recalculate the levelwindow for this. selectedLevelWindow.SetAuto(image, true, true, static_cast(displayedComponent)); node->SetLevelWindow(selectedLevelWindow); } } LevelWindow levelWindow; node->GetLevelWindow(levelWindow); } this->Update(event); } void mitk::LevelWindowManager::Update(const itk::EventObject &) { if (m_LevelWindowMutex) // no mutex, should still help { return; } - m_RelevantDataNodes.clear(); + m_DataNodesForLevelWindow.clear(); if (m_AutoTopMost) { this->SetAutoTopMostImage(true); return; } if (m_SelectedImagesMode) { this->SetSelectedImages(true); return; } int maxVisibleLayer = itk::NumericTraits::min(); DataNode::Pointer topLevelNode = nullptr; std::vector nodesForLevelWindow; DataStorage::SetOfObjects::ConstPointer all = this->GetRelevantNodes(); for (DataStorage::SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { DataNode::Pointer node = it->Value(); - if (node.IsNull()) { continue; } - if (node->IsVisible(nullptr) == false) + if (false == node->IsVisible(nullptr)) + { + continue; + } + + bool validRenderingMode = HasLevelWindowRenderingMode(node); + if (false == validRenderingMode) { continue; } bool prop = false; node->GetBoolProperty("imageForLevelWindow", prop); if (prop) { nodesForLevelWindow.push_back(node); continue; } int layer = -1; node->GetIntProperty("layer", layer); - if (layer > maxVisibleLayer) + if (layer <= maxVisibleLayer) { - // top level node is backup node, if no node with - // "imageForLevelWindow" property with value "true" is found - topLevelNode = node; - maxVisibleLayer = layer; + continue; } + + // top level node is backup node, if no node with + // "imageForLevelWindow" property with value "true" is found + topLevelNode = node; + maxVisibleLayer = layer; } int nodesForLevelWindowSize = nodesForLevelWindow.size(); if (nodesForLevelWindowSize > 2) { MITK_ERROR << "Error: not more than two visible nodes are expected to have the imageForLevelWindow property set at " "any point."; } if (nodesForLevelWindowSize > 0) { // 1 or 2 nodes for level window found for (const auto& node : nodesForLevelWindow) { LevelWindowProperty::Pointer newProp = dynamic_cast(node->GetProperty("levelwindow")); if (newProp != m_LevelWindowProperty) { this->SetLevelWindowProperty(newProp); return; } } } else if (topLevelNode) { // no nodes for level window found LevelWindowProperty::Pointer lvlProp = dynamic_cast(topLevelNode->GetProperty("levelwindow")); this->SetLevelWindowProperty(lvlProp); } else { // no nodes for level window found and no visible top level node found this->Modified(); } } void mitk::LevelWindowManager::UpdateSelected(const itk::EventObject &) { if (m_LevelWindowMutex) // no mutex, should still help { return; } - m_RelevantDataNodes.clear(); + m_DataNodesForLevelWindow.clear(); if (m_SelectedImagesMode) { this->SetSelectedImages(true); } } void mitk::LevelWindowManager::SetLevelWindowProperty(LevelWindowProperty::Pointer levelWindowProperty) { if (levelWindowProperty.IsNull()) { return; } // find data node that belongs to the property DataStorage::SetOfObjects::ConstPointer all = m_DataStorage->GetAll(); - mitk::DataNode::Pointer propNode = nullptr; + DataNode::Pointer propNode = nullptr; for (DataStorage::SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { DataNode::Pointer node = it.Value(); LevelWindowProperty::Pointer property = dynamic_cast(node->GetProperty("levelwindow")); if (property == levelWindowProperty) { propNode = node; } else { m_LevelWindowMutex = true; node->SetBoolProperty("imageForLevelWindow", false); m_LevelWindowMutex = false; } } if (propNode.IsNull()) { mitkThrow() << "No Image in the data storage that belongs to level-window property " << m_LevelWindowProperty; } if (m_IsPropertyModifiedTagSet) // remove listener for old property { m_LevelWindowProperty->RemoveObserver(m_PropertyModifiedTag); m_IsPropertyModifiedTagSet = false; } m_LevelWindowProperty = levelWindowProperty; - itk::ReceptorMemberCommand::Pointer command = - itk::ReceptorMemberCommand::New(); // register listener for new property + auto command = itk::ReceptorMemberCommand::New(); // register listener for new property command->SetCallbackFunction(this, &LevelWindowManager::OnPropertyModified); m_PropertyModifiedTag = m_LevelWindowProperty->AddObserver(itk::ModifiedEvent(), command); m_IsPropertyModifiedTagSet = true; m_CurrentImage = dynamic_cast(propNode->GetData()); m_LevelWindowMutex = true; propNode->SetBoolProperty("imageForLevelWindow", true); m_LevelWindowMutex = false; this->Modified(); } void mitk::LevelWindowManager::SetLevelWindow(const LevelWindow &levelWindow) { - if (m_LevelWindowProperty.IsNotNull()) + if (m_LevelWindowProperty.IsNull()) { - m_LevelWindowProperty->SetLevelWindow(levelWindow); - for (const auto &dataNode : m_RelevantDataNodes) - { - auto levelWindowProperty = dynamic_cast(dataNode->GetProperty("levelwindow")); - if (nullptr == levelWindowProperty) - { - continue; - } + return; + } - levelWindowProperty->SetLevelWindow(levelWindow); + m_LevelWindowProperty->SetLevelWindow(levelWindow); + for (const auto &dataNode : m_DataNodesForLevelWindow) + { + auto levelWindowProperty = dynamic_cast(dataNode->GetProperty("levelwindow")); + if (nullptr == levelWindowProperty) + { + continue; } + + levelWindowProperty->SetLevelWindow(levelWindow); } + this->Modified(); } -mitk::LevelWindowProperty::Pointer mitk::LevelWindowManager::GetLevelWindowProperty() +mitk::LevelWindowProperty::Pointer mitk::LevelWindowManager::GetLevelWindowProperty() const { return m_LevelWindowProperty; } -const mitk::LevelWindow &mitk::LevelWindowManager::GetLevelWindow() +const mitk::LevelWindow &mitk::LevelWindowManager::GetLevelWindow() const { if (m_LevelWindowProperty.IsNotNull()) { return m_LevelWindowProperty->GetLevelWindow(); } else { mitkThrow() << "No LevelWindow available!"; } } -bool mitk::LevelWindowManager::IsAutoTopMost() +bool mitk::LevelWindowManager::IsAutoTopMost() const { return m_AutoTopMost; } -bool mitk::LevelWindowManager::IsSelectedImages() +bool mitk::LevelWindowManager::IsSelectedImages() const { return m_SelectedImagesMode; } void mitk::LevelWindowManager::DataStorageAddedNode(const DataNode *) { // update observers with new data storage this->UpdateObservers(); - // Initialize LevelWindowsManager to new image + // initialize LevelWindowManager to new image this->SetAutoTopMostImage(true); // check if everything is still ok if ((m_ObserverToVisibleProperty.size() != m_ObserverToLayerProperty.size()) || (m_ObserverToLayerProperty.size() != this->GetRelevantNodes()->size())) { mitkThrow() << "Wrong number of observers in Level Window Manager!"; } } void mitk::LevelWindowManager::DataStorageRemovedNode(const DataNode *removedNode) { // First: check if deleted node is part of relevant nodes. // If not, abort method because there is no need change anything. bool removedNodeIsRelevant = false; DataStorage::SetOfObjects::ConstPointer relevantNodes = GetRelevantNodes(); for (DataStorage::SetOfObjects::ConstIterator it = relevantNodes->Begin(); it != relevantNodes->End(); ++it) { if (it->Value() == removedNode) { removedNodeIsRelevant = true; } } if (false == removedNodeIsRelevant) { return; } // remember node which will be removed m_NodeMarkedToDelete = removedNode; // update observers this->UpdateObservers(); // search image that belongs to the property if (m_LevelWindowProperty.IsNull()) { this->SetAutoTopMostImage(true, removedNode); } else { - NodePredicateProperty::Pointer property = NodePredicateProperty::New("levelwindow", m_LevelWindowProperty); + auto property = NodePredicateProperty::New("levelwindow", m_LevelWindowProperty); DataNode *n = m_DataStorage->GetNode(property); if (n == nullptr || m_AutoTopMost) // if node was deleted, change our behavior to AutoTopMost, if AutoTopMost is // true change level window to topmost node { this->SetAutoTopMostImage(true, removedNode); } } // reset variable m_NodeMarkedToDelete = nullptr; // check if everything is still ok if ((m_ObserverToVisibleProperty.size() != m_ObserverToLayerProperty.size()) || (m_ObserverToLayerProperty.size() != (relevantNodes->size() - 1))) { mitkThrow() << "Wrong number of observers in Level Window Manager!"; } } void mitk::LevelWindowManager::OnPropertyModified(const itk::EventObject &) { this->Modified(); } -mitk::Image *mitk::LevelWindowManager::GetCurrentImage() +mitk::Image *mitk::LevelWindowManager::GetCurrentImage() const { return m_CurrentImage; } -int mitk::LevelWindowManager::GetNumberOfObservers() +int mitk::LevelWindowManager::GetNumberOfObservers() const { return m_ObserverToVisibleProperty.size(); } -mitk::DataStorage::SetOfObjects::ConstPointer mitk::LevelWindowManager::GetRelevantNodes() +mitk::DataStorage::SetOfObjects::ConstPointer mitk::LevelWindowManager::GetRelevantNodes() const { if (m_DataStorage.IsNull()) { return DataStorage::SetOfObjects::ConstPointer(DataStorage::SetOfObjects::New()); } - NodePredicateProperty::Pointer notBinary = NodePredicateProperty::New("binary", BoolProperty::New(false)); - NodePredicateProperty::Pointer hasLevelWindow = NodePredicateProperty::New("levelwindow", nullptr); + auto notBinary = NodePredicateProperty::New("binary", BoolProperty::New(false)); + auto hasLevelWindow = NodePredicateProperty::New("levelwindow", nullptr); - NodePredicateDataType::Pointer isImage = NodePredicateDataType::New("Image"); - NodePredicateDataType::Pointer isDImage = NodePredicateDataType::New("DiffusionImage"); - NodePredicateDataType::Pointer isTImage = NodePredicateDataType::New("TensorImage"); - NodePredicateDataType::Pointer isOdfImage = NodePredicateDataType::New("OdfImage"); - NodePredicateDataType::Pointer isShImage = NodePredicateDataType::New("ShImage"); - NodePredicateOr::Pointer predicateTypes = NodePredicateOr::New(); + auto isImage = NodePredicateDataType::New("Image"); + auto isDImage = NodePredicateDataType::New("DiffusionImage"); + auto isTImage = NodePredicateDataType::New("TensorImage"); + auto isOdfImage = NodePredicateDataType::New("OdfImage"); + auto isShImage = NodePredicateDataType::New("ShImage"); + auto predicateTypes = NodePredicateOr::New(); predicateTypes->AddPredicate(isImage); predicateTypes->AddPredicate(isDImage); predicateTypes->AddPredicate(isTImage); predicateTypes->AddPredicate(isOdfImage); predicateTypes->AddPredicate(isShImage); NodePredicateAnd::Pointer predicate = NodePredicateAnd::New(); predicate->AddPredicate(notBinary); predicate->AddPredicate(hasLevelWindow); predicate->AddPredicate(predicateTypes); DataStorage::SetOfObjects::ConstPointer relevantNodes = m_DataStorage->GetSubset(predicate); return relevantNodes; } void mitk::LevelWindowManager::UpdateObservers() { - this->ClearPropObserverLists(); // remove old observers - this->CreatePropObserverLists(); // create new observer lists + this->ClearPropertyObserverMaps(); + this->CreatePropertyObserverMaps(); } -void mitk::LevelWindowManager::ClearPropObserverLists() +void mitk::LevelWindowManager::ClearPropertyObserverMaps() { for (auto iter = m_ObserverToVisibleProperty.begin(); iter != m_ObserverToVisibleProperty.end(); ++iter) { (*iter).second->RemoveObserver((*iter).first.first); (*iter).second = nullptr; } m_ObserverToVisibleProperty.clear(); for (auto iter = m_ObserverToLayerProperty.begin(); iter != m_ObserverToLayerProperty.end(); ++iter) { (*iter).second->RemoveObserver((*iter).first.first); (*iter).second = nullptr; } m_ObserverToLayerProperty.clear(); for (auto iter = m_ObserverToRenderingModeProperty.begin(); iter != m_ObserverToRenderingModeProperty.end(); ++iter) { (*iter).second->RemoveObserver((*iter).first.first); (*iter).second = nullptr; } m_ObserverToRenderingModeProperty.clear(); for (auto iter = m_ObserverToDisplayedComponentProperty.begin(); iter != m_ObserverToDisplayedComponentProperty.end(); ++iter) { (*iter).second->RemoveObserver((*iter).first.first); (*iter).second = nullptr; } m_ObserverToDisplayedComponentProperty.clear(); for (auto iter = m_ObserverToLevelWindowImageProperty.begin(); iter != m_ObserverToLevelWindowImageProperty.end(); ++iter) { (*iter).second->RemoveObserver((*iter).first.first); (*iter).second = nullptr; } m_ObserverToLevelWindowImageProperty.clear(); for (auto iter = m_ObserverToSelectedProperty.begin(); iter != m_ObserverToSelectedProperty.end(); ++iter) { (*iter).second->RemoveObserver((*iter).first.first); (*iter).second = nullptr; } m_ObserverToSelectedProperty.clear(); } -void mitk::LevelWindowManager::CreatePropObserverLists() +void mitk::LevelWindowManager::CreatePropertyObserverMaps() { - if (m_DataStorage.IsNull()) // check if data storage is set + if (m_DataStorage.IsNull()) { mitkThrow() << "DataStorage not set"; } - /* add observers for all relevant nodes */ + // add observers for all relevant nodes DataStorage::SetOfObjects::ConstPointer all = this->GetRelevantNodes(); for (DataStorage::SetOfObjects::ConstIterator it = all->Begin(); it != all->End(); ++it) { - if ((it->Value().IsNull()) || (it->Value() == m_NodeMarkedToDelete)) + DataNode::Pointer node = it->Value(); + if (node.IsNull() || node == m_NodeMarkedToDelete) { continue; } - /* register listener for changes in visible property */ - itk::ReceptorMemberCommand::Pointer command = - itk::ReceptorMemberCommand::New(); + auto command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &LevelWindowManager::Update); - unsigned long visIdx = it->Value()->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); - m_ObserverToVisibleProperty[PropDataPair(visIdx, it->Value())] = it->Value()->GetProperty("visible"); + unsigned long visIdx = node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); + m_ObserverToVisibleProperty[PropDataPair(visIdx, node)] = node->GetProperty("visible"); - /* register listener for changes in layer property */ - itk::ReceptorMemberCommand::Pointer command2 = - itk::ReceptorMemberCommand::New(); + auto command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction(this, &LevelWindowManager::Update); - unsigned long layerIdx = it->Value()->GetProperty("layer")->AddObserver(itk::ModifiedEvent(), command2); - m_ObserverToLayerProperty[PropDataPair(layerIdx, it->Value())] = it->Value()->GetProperty("layer"); + unsigned long layerIdx = node->GetProperty("layer")->AddObserver(itk::ModifiedEvent(), command2); + m_ObserverToLayerProperty[PropDataPair(layerIdx, node)] = node->GetProperty("layer"); - /* register listener for changes in layer property */ - itk::ReceptorMemberCommand::Pointer command3 = - itk::ReceptorMemberCommand::New(); + auto command3 = itk::ReceptorMemberCommand::New(); command3->SetCallbackFunction(this, &LevelWindowManager::Update); - BaseProperty::Pointer imageRenderingMode = it->Value()->GetProperty("Image Rendering.Mode"); + BaseProperty::Pointer imageRenderingMode = node->GetProperty("Image Rendering.Mode"); if (imageRenderingMode.IsNotNull()) { unsigned long rendIdx = imageRenderingMode->AddObserver(itk::ModifiedEvent(), command3); - m_ObserverToRenderingModeProperty[PropDataPair(rendIdx, it->Value())] = imageRenderingMode.GetPointer(); + m_ObserverToRenderingModeProperty[PropDataPair(rendIdx, node)] = imageRenderingMode.GetPointer(); } - itk::ReceptorMemberCommand::Pointer command4 = - itk::ReceptorMemberCommand::New(); + auto command4 = itk::ReceptorMemberCommand::New(); command4->SetCallbackFunction(this, &LevelWindowManager::RecalculateLevelWindowForSelectedComponent); - BaseProperty::Pointer displayedImageComponent = it->Value()->GetProperty("Image.Displayed Component"); + BaseProperty::Pointer displayedImageComponent = node->GetProperty("Image.Displayed Component"); if (displayedImageComponent.IsNotNull()) { unsigned long dispIdx = displayedImageComponent->AddObserver(itk::ModifiedEvent(), command4); - m_ObserverToDisplayedComponentProperty[PropDataPair(dispIdx, it->Value())] = displayedImageComponent.GetPointer(); + m_ObserverToDisplayedComponentProperty[PropDataPair(dispIdx, node)] = displayedImageComponent.GetPointer(); } - itk::ReceptorMemberCommand::Pointer command5 = - itk::ReceptorMemberCommand::New(); + auto command5 = itk::ReceptorMemberCommand::New(); command5->SetCallbackFunction(this, &LevelWindowManager::Update); - BaseProperty::Pointer imgForLvlWin = it->Value()->GetProperty("imageForLevelWindow"); + BaseProperty::Pointer imgForLvlWin = node->GetProperty("imageForLevelWindow"); if (imgForLvlWin.IsNull()) { - it->Value()->SetBoolProperty("imageForLevelWindow", false); - imgForLvlWin = it->Value()->GetProperty("imageForLevelWindow"); + node->SetBoolProperty("imageForLevelWindow", false); + imgForLvlWin = node->GetProperty("imageForLevelWindow"); } unsigned long lvlWinIdx = imgForLvlWin->AddObserver(itk::ModifiedEvent(), command5); - m_ObserverToLevelWindowImageProperty[PropDataPair(lvlWinIdx, it->Value())] = it->Value()->GetProperty("imageForLevelWindow"); + m_ObserverToLevelWindowImageProperty[PropDataPair(lvlWinIdx, node)] = node->GetProperty("imageForLevelWindow"); - itk::ReceptorMemberCommand::Pointer command6 = - itk::ReceptorMemberCommand::New(); + auto command6 = itk::ReceptorMemberCommand::New(); command6->SetCallbackFunction(this, &LevelWindowManager::UpdateSelected); - BaseProperty::Pointer selectedDataNode = it->Value()->GetProperty("selected"); + BaseProperty::Pointer selectedDataNode = node->GetProperty("selected"); if (selectedDataNode.IsNull()) { - it->Value()->SetBoolProperty("selected", false); - selectedDataNode = it->Value()->GetProperty("selected"); + node->SetBoolProperty("selected", false); + selectedDataNode = node->GetProperty("selected"); } unsigned long selectedIdx = selectedDataNode->AddObserver(itk::ModifiedEvent(), command5); - m_ObserverToSelectedProperty[PropDataPair(selectedIdx, it->Value())] = it->Value()->GetProperty("selected"); + m_ObserverToSelectedProperty[PropDataPair(selectedIdx, node)] = node->GetProperty("selected"); } } -bool mitk::LevelWindowManager::IgnoreNode(const DataNode* dataNode) +bool mitk::LevelWindowManager::HasLevelWindowRenderingMode(DataNode *dataNode) const { - LevelWindowProperty::Pointer levelWindowProperty = - dynamic_cast(dataNode->GetProperty("levelwindow")); - if (levelWindowProperty.IsNull()) - { - return true; - } - - int nonLvlWinMode1 = RenderingModeProperty::LOOKUPTABLE_COLOR; - int nonLvlWinMode2 = RenderingModeProperty::COLORTRANSFERFUNCTION_COLOR; - RenderingModeProperty::Pointer mode = dynamic_cast(dataNode->GetProperty("Image Rendering.Mode")); if (mode.IsNotNull()) { int currMode = mode->GetRenderingMode(); - if (currMode == nonLvlWinMode1 || currMode == nonLvlWinMode2) + if (currMode == RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR || + currMode == RenderingModeProperty::COLORTRANSFERFUNCTION_LEVELWINDOW_COLOR) { return true; } } - else - { - return true; - } return false; } diff --git a/Modules/Core/test/CMakeLists.txt b/Modules/Core/test/CMakeLists.txt index dc8d4ee9b4..7839cb73bd 100644 --- a/Modules/Core/test/CMakeLists.txt +++ b/Modules/Core/test/CMakeLists.txt @@ -1,181 +1,177 @@ # The core tests need relaxed compiler flags... # TODO fix core tests to compile without these additional no-error flags if(MSVC_VERSION) # disable deprecated warnings (they would lead to errors) mitkFunctionCheckCAndCXXCompilerFlags("/wd4996" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) else() mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=deprecated" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) mitkFunctionCheckCAndCXXCompilerFlags("-Wno-error=deprecated-declarations" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) endif() MITK_CREATE_MODULE_TESTS() if(TARGET ${TESTDRIVER}) mitk_use_modules(TARGET ${TESTDRIVER} PACKAGES ITK|IONRRD VTK|TestingRendering tinyxml2) mitkAddCustomModuleTest(mitkVolumeCalculatorTest_Png2D-bw mitkVolumeCalculatorTest ${MITK_DATA_DIR}/Png2D-bw.png ${MITK_DATA_DIR}/Pic2DplusT.nrrd ) mitkAddCustomModuleTest(mitkEventConfigTest_CreateObjectInDifferentWays mitkEventConfigTest ${MITK_SOURCE_DIR}/Modules/Core/test/resource/Interactions/StatemachineConfigTest.xml ) mitkAddCustomModuleTest(mitkDataStorageTest_US4DCyl mitkDataStorageTest ${MITK_DATA_DIR}/US4DCyl.nrrd ) mitkAddCustomModuleTest(mitkPointSetReaderTest mitkPointSetReaderTest ${MITK_DATA_DIR}/PointSetReaderTestData.mps ) mitkAddCustomModuleTest(mitkImageTest_4DImageData mitkImageTest ${MITK_DATA_DIR}/US4DCyl.nrrd ) mitkAddCustomModuleTest(mitkImageTest_2D+tImageData mitkImageTest ${MITK_DATA_DIR}/Pic2DplusT.nrrd ) mitkAddCustomModuleTest(mitkImageTest_3DImageData mitkImageTest ${MITK_DATA_DIR}/Pic3D.nrrd ) mitkAddCustomModuleTest(mitkImageEqualTest mitkImageEqualTest) mitkAddCustomModuleTest(mitkImageTest_brainImage mitkImageTest ${MITK_DATA_DIR}/brain.mhd ) - mitkAddCustomModuleTest(mitkLevelWindowManagerTest mitkLevelWindowManagerTest - ${MITK_DATA_DIR}/Pic3D.nrrd - ) - mitkAddCustomModuleTest(mitkMultiComponentImageDataComparisonFilterTest mitkMultiComponentImageDataComparisonFilterTest ${MITK_DATA_DIR}/NrrdWritingTestImage.jpg ) mitkAddCustomModuleTest(mitkImageToItkTest mitkImageToItkTest ${MITK_DATA_DIR}/Pic3D.nrrd ) mitkAddCustomModuleTest(mitkImageSliceSelectorTest mitkImageSliceSelectorTest ${MITK_DATA_DIR}/Pic2DplusT.nrrd ) mitkAddCustomModuleTest(mitkRotatedSlice4DTest mitkRotatedSlice4DTest ${MITK_DATA_DIR}/UltrasoundImages/4D_TEE_Data_MV.dcm ) mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2D_rgbaImage640x480 mitkImageVtkMapper2DTest ${MITK_DATA_DIR}/RenderingTestData/rgbaImage.png #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/rgbaImage640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2D_pic3d640x480 mitkImageVtkMapper2DTest #test for standard Pic3D axial slice ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3d640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2D_pic3dColorBlue640x480 mitkImageVtkMapper2DColorTest #test for color property (=blue) Pic3D sagittal slice ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dColorBlue640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2D_pic3dLevelWindow640x480 mitkImageVtkMapper2DLevelWindowTest #test for levelwindow property (=blood) #Pic3D sagittal slice ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dLevelWindowBlood640x480REF.png #corresponding reference #screenshot ) mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2D_pic3dSwivel640x480 mitkImageVtkMapper2DSwivelTest #test for a randomly chosen Pic3D swivelled slice ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dSwivel640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleRenderingTest(mitkPointSetVtkMapper2D_openMeAlone640x480 mitkPointSetVtkMapper2DTest ${MITK_DATA_DIR}/RenderingTestData/openMeAlone.mps #input point set to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/openMeAlone640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleRenderingTest(mitkPointSetVtkMapper2D_Pic3DPointSetForPic3D640x480 mitkPointSetVtkMapper2DImageTest ${MITK_DATA_DIR}/Pic3D.nrrd ${MITK_DATA_DIR}/RenderingTestData/PointSetForPic3D.mps #input point set and image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/Pic3DPointSetForPic3D640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleRenderingTest(mitkPointSetVtkMapper2D_openMeAloneGlyphType640x480 mitkPointSetVtkMapper2DGlyphTypeTest ${MITK_DATA_DIR}/RenderingTestData/openMeAlone.mps #input point set to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/openMeAloneGlyphType640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleRenderingTest(mitkPointSetVtkMapper2D_openMeAloneTransformed640x480 mitkPointSetVtkMapper2DTransformedPointsTest ${MITK_DATA_DIR}/RenderingTestData/openMeAlone.mps #input point set to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/openMeAloneTransformedPoints640x480REF.png #corresponding reference screenshot ) # Currently not working on windows because of a rendering timing issue # see bug 18083 for details if(NOT WIN32) mitkAddCustomModuleRenderingTest(mitkSurfaceDepthSortingTransparency_StanfordBunnySTL640x480 mitkSurfaceDepthSortingTest ${MITK_DATA_DIR}/RenderingTestData/Stanford_bunny.stl -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/Stanford_bunnySTLDepthSorting640x480REF.png) endif() # BUG 18695 - tests deactivated, because win 32 bit continuous renders images slightly different. TODO! #Test reslice interpolation #note: nearest mode is already tested by swivel test #mitkAddCustomModuleRenderingTest(ResliceInterpolationIsLinear mitkImageVtkMapper2DResliceInterpolationPropertyTest # 1 #linear # ${MITK_DATA_DIR}/Pic3D.nrrd # -V # ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dRefLinear.png #corresponding reference screenshot LINEAR #) #mitkAddCustomModuleRenderingTest(ResliceInterpolationIsCubic mitkImageVtkMapper2DResliceInterpolationPropertyTest # 3 #cubic # ${MITK_DATA_DIR}/Pic3D.nrrd # -V # ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/pic3dRefCubic.png #corresponding reference screenshot CUBIC #) #End test reslice interpolation # Testing of the rendering of binary images #mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2D_binaryTestImage640x480 mitkImageVtkMapper2DTest #test for standard Pic3D axial slice # ${MITK_DATA_DIR}/RenderingTestData/binaryImage.nrrd #input image to load in data storage # -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/binaryImage640x480REF.png #corresponding reference screenshot #) #mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2D_binaryTestImageWithRef640x480 mitkImageVtkMapper2DTest #test for standard Pic3D axial slice # ${MITK_DATA_DIR}/Pic3D.nrrd ${MITK_DATA_DIR}/RenderingTestData/binaryImage.nrrd #input image to load in data storage # -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/binaryImageWithRef640x480REF.png #corresponding reference screenshot #) # End of binary image tests mitkAddCustomModuleRenderingTest(mitkSurfaceVtkMapper3DTest_TextureProperty mitkSurfaceVtkMapper3DTest ${MITK_DATA_DIR}/RenderingTestData/earth.jpg -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/texturedSphere640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2DTransferFunctionTest_Png2D-bw mitkImageVtkMapper2DTransferFunctionTest ${MITK_DATA_DIR}/Png2D-bw.png -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/Png2D-bw-TransferFunctionRGBImage640x480REF.png #corresponding reference screenshot ) mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2DOpacityTransferFunctionTest_Png2D-bw mitkImageVtkMapper2DOpacityTransferFunctionTest ${MITK_DATA_DIR}/Png2D-bw.png -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/Png2D-bw-OpacityTransferFunctionRGBImage640x480REF.png #corresponding reference screenshot ) ############################## DISABLED TESTS mitkAddCustomModuleRenderingTest(mitkImageVtkMapper2DLookupTableTest_Png2D-bw mitkImageVtkMapper2DLookupTableTest ${MITK_DATA_DIR}/Png2D-bw.png -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/Png2D-bw-LookupTableRGBImage640x480REF.png #corresponding reference screenshot ) #mitkAddCustomModuleRenderingTest(mitkImageTest_color2DImage mitkImageTest # ${MITK_DATA_DIR}/NrrdWritingTestImage.jpg #) #mitkAddCustomModuleRenderingTest(mitkNodeDependentPointSetInteractorTest mitkNodeDependentPointSetInteractorTest # ${MITK_DATA_DIR}/Pic3D.pic.gz ${MITK_DATA_DIR}/BallBinary30x30x30.pic.gz #) mitkAddCustomModuleRenderingTest(mitkPlaneGeometryDataMapper2DTest mitkPlaneGeometryDataMapper2DTest ${MITK_DATA_DIR}/Pic3D.nrrd #input image to load in data storage -V ${MITK_DATA_DIR}/RenderingTestData/ReferenceScreenshots/PlaneGeometryMapper640x480REF.png #corresponding reference screenshot ) endif() # TARGET ${TESTDRIVER} diff --git a/Modules/Core/test/files.cmake b/Modules/Core/test/files.cmake index 201b7c43ef..9aa105bf67 100644 --- a/Modules/Core/test/files.cmake +++ b/Modules/Core/test/files.cmake @@ -1,198 +1,198 @@ # tests with no extra command line parameter set(MODULE_TESTS # IMPORTANT: If you plan to deactivate / comment out a test please write a bug number to the commented out line of code. # # Example: #mitkMyTest #this test is commented out because of bug 12345 # # It is important that the bug is open and that the test will be activated again before the bug is closed. This assures that # no test is forgotten after it was commented out. If there is no bug for your current problem, please add a new one and # mark it as critical. ################## DISABLED TESTS ################################################# #mitkAbstractTransformGeometryTest.cpp #seems as tested class mitkExternAbstractTransformGeometry doesnt exist any more #mitkStateMachineContainerTest.cpp #rewrite test, indirect since no longer exported Bug 14529 #mitkRegistrationBaseTest.cpp #tested class mitkRegistrationBase doesn't exist any more #mitkSegmentationInterpolationTest.cpp #file doesn't exist! #mitkPipelineSmartPointerCorrectnessTest.cpp #file doesn't exist! #mitkITKThreadingTest.cpp #test outdated because itk::Semaphore was removed from ITK #mitkAbstractTransformPlaneGeometryTest.cpp #mitkVtkAbstractTransformPlaneGeometry doesn't exist any more #mitkTestUtilSharedLibrary.cpp #Linker problem with this test... #mitkTextOverlay2DSymbolsRenderingTest.cpp #Implementation of the tested feature is not finished yet. Ask Christoph or see bug 15104 for details. ################# RUNNING TESTS ################################################### mitkAccessByItkTest.cpp mitkCoreObjectFactoryTest.cpp mitkDataNodeTest.cpp mitkMaterialTest.cpp mitkActionTest.cpp mitkDispatcherTest.cpp mitkEnumerationPropertyTest.cpp mitkFileReaderRegistryTest.cpp #mitkFileWriterRegistryTest.cpp mitkFloatToStringTest.cpp mitkGenericPropertyTest.cpp mitkGeometry3DTest.cpp mitkGeometry3DEqualTest.cpp mitkGeometryDataIOTest.cpp mitkGeometryDataToSurfaceFilterTest.cpp mitkImageCastTest.cpp mitkImageDataItemTest.cpp mitkImageGeneratorTest.cpp mitkIOUtilTest.cpp mitkBaseDataTest.cpp mitkImportItkImageTest.cpp mitkGrabItkImageMemoryTest.cpp mitkInstantiateAccessFunctionTest.cpp mitkLevelWindowTest.cpp mitkMessageTest.cpp mitkPixelTypeTest.cpp mitkPlaneGeometryTest.cpp mitkPointSetTest.cpp mitkPointSetEqualTest.cpp mitkPointSetFileIOTest.cpp mitkPointSetOnEmptyTest.cpp mitkPointSetLocaleTest.cpp mitkPointSetWriterTest.cpp mitkPointSetPointOperationsTest.cpp mitkProgressBarTest.cpp mitkPropertyTest.cpp mitkPropertyListTest.cpp mitkPropertyPersistenceTest.cpp mitkPropertyPersistenceInfoTest.cpp mitkPropertyRelationRuleBaseTest.cpp mitkPropertyRelationsTest.cpp mitkSlicedGeometry3DTest.cpp mitkSliceNavigationControllerTest.cpp mitkSurfaceTest.cpp mitkSurfaceEqualTest.cpp mitkSurfaceToSurfaceFilterTest.cpp mitkTimeGeometryTest.cpp mitkProportionalTimeGeometryTest.cpp mitkUndoControllerTest.cpp mitkVtkWidgetRenderingTest.cpp mitkVerboseLimitedLinearUndoTest.cpp mitkWeakPointerTest.cpp mitkTransferFunctionTest.cpp mitkStepperTest.cpp mitkRenderingManagerTest.cpp mitkCompositePixelValueToStringTest.cpp vtkMitkThickSlicesFilterTest.cpp mitkNodePredicateSourceTest.cpp mitkNodePredicateDataPropertyTest.cpp mitkNodePredicateFunctionTest.cpp mitkVectorTest.cpp mitkClippedSurfaceBoundsCalculatorTest.cpp mitkExceptionTest.cpp mitkExtractSliceFilterTest.cpp mitkLogTest.cpp mitkImageDimensionConverterTest.cpp mitkLoggingAdapterTest.cpp mitkUIDGeneratorTest.cpp mitkPlanePositionManagerTest.cpp mitkAffineTransformBaseTest.cpp mitkPropertyAliasesTest.cpp mitkPropertyDescriptionsTest.cpp mitkPropertyExtensionsTest.cpp mitkPropertyFiltersTest.cpp mitkPropertyKeyPathTest.cpp mitkTinyXMLTest.cpp mitkRawImageFileReaderTest.cpp mitkInteractionEventTest.cpp mitkLookupTableTest.cpp mitkSTLFileReaderTest.cpp mitkPointTypeConversionTest.cpp mitkVectorTypeConversionTest.cpp mitkMatrixTypeConversionTest.cpp mitkArrayTypeConversionTest.cpp mitkSurfaceToImageFilterTest.cpp mitkBaseGeometryTest.cpp mitkImageToSurfaceFilterTest.cpp mitkEqualTest.cpp mitkLineTest.cpp mitkArbitraryTimeGeometryTest.cpp mitkItkImageIOTest.cpp - mitkLevelWindowManagerCppUnitTest.cpp + mitkLevelWindowManagerTest.cpp mitkVectorPropertyTest.cpp mitkTemporoSpatialStringPropertyTest.cpp mitkPropertyNameHelperTest.cpp mitkNodePredicateGeometryTest.cpp mitkNodePredicateSubGeometryTest.cpp mitkPreferenceListReaderOptionsFunctorTest.cpp mitkGenericIDRelationRuleTest.cpp mitkSourceImageRelationRuleTest.cpp mitkTemporalJoinImagesFilterTest.cpp ) set(MODULE_RENDERING_TESTS mitkPointSetDataInteractorTest.cpp mitkSurfaceVtkMapper2DTest.cpp mitkSurfaceVtkMapper2D3DTest.cpp ) # test with image filename as an extra command line parameter set(MODULE_IMAGE_TESTS mitkImageTimeSelectorTest.cpp #only runs on images mitkImageAccessorTest.cpp #only runs on images ) set(MODULE_SURFACE_TESTS mitkSurfaceVtkWriterTest.cpp #only runs on surfaces ) # list of images for which the tests are run set(MODULE_TESTIMAGE US4DCyl.nrrd Pic3D.nrrd Pic2DplusT.nrrd BallBinary30x30x30.nrrd Png2D-bw.png ) set(MODULE_TESTSURFACE binary.stl ball.stl ) set(MODULE_CUSTOM_TESTS mitkDataStorageTest.cpp mitkDataNodeTest.cpp mitkEventConfigTest.cpp mitkPointSetLocaleTest.cpp mitkImageTest.cpp mitkImageVtkMapper2DTest.cpp mitkImageVtkMapper2DLevelWindowTest.cpp mitkImageVtkMapper2DOpacityTest.cpp mitkImageVtkMapper2DResliceInterpolationPropertyTest.cpp mitkImageVtkMapper2DColorTest.cpp mitkImageVtkMapper2DSwivelTest.cpp mitkImageVtkMapper2DTransferFunctionTest.cpp mitkImageVtkMapper2DOpacityTransferFunctionTest.cpp mitkImageVtkMapper2DLookupTableTest.cpp mitkSurfaceVtkMapper3DTest.cpp mitkVolumeCalculatorTest.cpp mitkLevelWindowManagerTest.cpp mitkPointSetVtkMapper2DTest.cpp mitkPointSetVtkMapper2DImageTest.cpp mitkPointSetVtkMapper2DGlyphTypeTest.cpp mitkPointSetVtkMapper2DTransformedPointsTest.cpp mitkVTKRenderWindowSizeTest.cpp mitkMultiComponentImageDataComparisonFilterTest.cpp mitkImageToItkTest.cpp mitkImageSliceSelectorTest.cpp mitkPointSetReaderTest.cpp mitkImageEqualTest.cpp mitkRotatedSlice4DTest.cpp mitkPlaneGeometryDataMapper2DTest.cpp ) # Currently not working on windows because of a rendering timing issue # see bug 18083 for details if(NOT WIN32) set(MODULE_CUSTOM_TESTS ${MODULE_CUSTOM_TESTS} mitkSurfaceDepthSortingTest.cpp) endif() set(RESOURCE_FILES Interactions/AddAndRemovePoints.xml Interactions/globalConfig.xml Interactions/StatemachineTest.xml Interactions/StatemachineConfigTest.xml ) diff --git a/Modules/Core/test/mitkLevelWindowManagerCppUnitTest.cpp b/Modules/Core/test/mitkLevelWindowManagerCppUnitTest.cpp deleted file mode 100644 index ece270c293..0000000000 --- a/Modules/Core/test/mitkLevelWindowManagerCppUnitTest.cpp +++ /dev/null @@ -1,150 +0,0 @@ -/*============================================================================ - -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 "mitkTestingMacros.h" -#include - -#include "mitkLevelWindowManager.h" -#include "mitkStandaloneDataStorage.h" -#include -#include -#include -#include -#include - -class mitkLevelWindowManagerCppUnitTestSuite : public mitk::TestFixture -{ - CPPUNIT_TEST_SUITE(mitkLevelWindowManagerCppUnitTestSuite); - MITK_TEST(TestMultiComponentRescaling); - CPPUNIT_TEST_SUITE_END(); - -private: - mitk::Image::Pointer m_mitkMultiComponentImage; - mitk::Image::Pointer m_mitkImageComponent1; - mitk::Image::Pointer m_mitkImageComponent2; - -public: - void setUp() override - { - typedef itk::Image ImageType; - typedef itk::ImageRegionIterator ImageIteratorType; - typedef itk::ImageDuplicator DuplicatorType; - typedef itk::ComposeImageFilter CompositeFilterType; - - // generate two images with one component - ImageType::Pointer imageComponent1 = itk::Image::New(); - ImageType::IndexType start; - start.Fill(0); - ImageType::SizeType size; - size.Fill(5); - ImageType::RegionType region; - region.SetSize(size); - region.SetIndex(start); - imageComponent1->SetRegions(region); - imageComponent1->Allocate(); - - DuplicatorType::Pointer duplicator = DuplicatorType::New(); - duplicator->SetInputImage(imageComponent1); - duplicator->Update(); - ImageType::Pointer imageComponent2 = duplicator->GetOutput(); - - // give them differing data - ImageIteratorType iterator1(imageComponent1, imageComponent1->GetLargestPossibleRegion()); - iterator1.GoToBegin(); - int i = 0; - while (!iterator1.IsAtEnd()) - { - iterator1.Set((double)i); - ++iterator1; - ++i; - } - - ImageIteratorType iterator2(imageComponent2, imageComponent2->GetLargestPossibleRegion()); - iterator2.GoToBegin(); - i = 2000; - while (!iterator2.IsAtEnd()) - { - iterator2.Set((double)i); - ++iterator2; - ++i; - } - - // copy into single VectorImage - CompositeFilterType::Pointer compositeFilter = CompositeFilterType::New(); - compositeFilter->SetInput(0, imageComponent1); - compositeFilter->SetInput(1, imageComponent2); - compositeFilter->Update(); - itk::VectorImage::Pointer multiComponentImage = compositeFilter->GetOutput(); - - // cast images to mitk - mitk::CastToMitkImage(multiComponentImage, m_mitkMultiComponentImage); - mitk::CastToMitkImage(imageComponent1, m_mitkImageComponent1); - mitk::CastToMitkImage(imageComponent2, m_mitkImageComponent2); - } - - static mitk::LevelWindow getLevelWindowForImage(mitk::Image *image, unsigned component) - { - mitk::LevelWindowManager::Pointer manager; - manager = mitk::LevelWindowManager::New(); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); - - try - { - manager->SetDataStorage(ds); - } - catch (std::exception &e) - { - MITK_ERROR << "Exception: " << e.what(); - } - - mitk::DataNode::Pointer node = mitk::DataNode::New(); - node->SetData(image); - ds->Add(node); - - node->SetBoolProperty("selected", true); - node->SetIntProperty("Image.Displayed Component", component); - - mitk::LevelWindow levelWindow; - node->GetLevelWindow(levelWindow); - return levelWindow; // node is an image node because of predicates - } - - void TestMultiComponentRescaling() - { - // compute level windows for the three images - mitk::LevelWindow imageComponent1LevelWindow = getLevelWindowForImage(m_mitkImageComponent1, 0); - mitk::LevelWindow imageComponent2LevelWindow = getLevelWindowForImage(m_mitkImageComponent2, 0); - // calculate level window for second component in multi-component image - mitk::LevelWindow multiComponentImageLevelWindow = getLevelWindowForImage(m_mitkMultiComponentImage, 1); - - // compare level window boundaries. the multicomponent image level window - // should match the second image, since the second component was selected - CPPUNIT_ASSERT_EQUAL_MESSAGE("default lower bounds equal", - imageComponent2LevelWindow.GetDefaultLowerBound(), - multiComponentImageLevelWindow.GetDefaultLowerBound()); - - CPPUNIT_ASSERT_EQUAL_MESSAGE("default upper bounds equal", - imageComponent2LevelWindow.GetDefaultUpperBound(), - multiComponentImageLevelWindow.GetDefaultUpperBound()); - - CPPUNIT_ASSERT_EQUAL_MESSAGE( - "range equal", imageComponent2LevelWindow.GetRange(), multiComponentImageLevelWindow.GetRange()); - - CPPUNIT_ASSERT(imageComponent1LevelWindow.GetDefaultLowerBound() != - multiComponentImageLevelWindow.GetDefaultLowerBound()); - - CPPUNIT_ASSERT(imageComponent1LevelWindow.GetDefaultUpperBound() != - multiComponentImageLevelWindow.GetDefaultUpperBound()); - } -}; - -MITK_TEST_SUITE_REGISTRATION(mitkLevelWindowManagerCppUnit) diff --git a/Modules/Core/test/mitkLevelWindowManagerTest.cpp b/Modules/Core/test/mitkLevelWindowManagerTest.cpp index 1d7de8c01e..43cddaee66 100644 --- a/Modules/Core/test/mitkLevelWindowManagerTest.cpp +++ b/Modules/Core/test/mitkLevelWindowManagerTest.cpp @@ -1,621 +1,439 @@ /*============================================================================ 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 "itkMersenneTwisterRandomVariateGenerator.h" -#include "mitkLevelWindowManager.h" -#include "mitkRenderingModeProperty.h" -#include "mitkStandaloneDataStorage.h" +#include +#include + +#include +#include +#include +#include + #include #include #include #include -#include -#include #include -#include -class mitkLevelWindowManagerTestClass +class mitkLevelWindowManagerTestSuite : public mitk::TestFixture { -public: - static void TestInstantiation() - { - mitk::LevelWindowManager::Pointer manager; - manager = mitk::LevelWindowManager::New(); - MITK_TEST_CONDITION_REQUIRED(manager.IsNotNull(), "Testing mitk::LevelWindowManager::New()"); - } + CPPUNIT_TEST_SUITE(mitkLevelWindowManagerTestSuite); + MITK_TEST(TestModes); + MITK_TEST(TestSetLevelWindowProperty); + MITK_TEST(TestVisibilityPropertyChanged); + MITK_TEST(TestLayerPropertyChanged); + MITK_TEST(TestImageRenderingModePropertyChanged); + MITK_TEST(TestImageForLevelWindowPropertyChanged); + MITK_TEST(TestSelectedPropertyChanged); + MITK_TEST(TestRemoveDataNodes); + MITK_TEST(TestCombinedPropertiesChanged); - static void TestSetGetDataStorage() - { - mitk::LevelWindowManager::Pointer manager; - manager = mitk::LevelWindowManager::New(); - MITK_TEST_OUTPUT(<< "Creating DataStorage: "); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); + CPPUNIT_TEST_SUITE_END(); - bool success = true; - try - { - manager->SetDataStorage(ds); - } - catch (std::exception &e) - { - success = false; - MITK_ERROR << "Exception: " << e.what(); - } - MITK_TEST_CONDITION_REQUIRED(success, "Testing mitk::LevelWindowManager SetDataStorage() "); - MITK_TEST_CONDITION_REQUIRED(ds == manager->GetDataStorage(), "Testing mitk::LevelWindowManager GetDataStorage "); - } +private: - static void TestMethodsWithInvalidParameters() - { - mitk::LevelWindowManager::Pointer manager; - manager = mitk::LevelWindowManager::New(); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); - manager->SetDataStorage(ds); - - bool success = false; - mitk::LevelWindowProperty::Pointer levelWindowProperty = mitk::LevelWindowProperty::New(); - try - { - manager->SetLevelWindowProperty(levelWindowProperty); - } - catch (const mitk::Exception &) - { - success = true; - } - MITK_TEST_CONDITION(success, "Testing mitk::LevelWindowManager SetLevelWindowProperty with invalid parameter"); - } + mitk::LevelWindowManager::Pointer m_LevelWindowManager; + mitk::StandaloneDataStorage::Pointer m_DataManager; - static void TestOtherMethods() - { - mitk::LevelWindowManager::Pointer manager; - manager = mitk::LevelWindowManager::New(); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); - manager->SetDataStorage(ds); - - MITK_TEST_CONDITION(manager->IsAutoTopMost(), "Testing mitk::LevelWindowManager isAutoTopMost"); - - // It is not clear what the following code is supposed to test. The expression in - // the catch(...) block does have no effect, so success is always true. - // Related bugs are 13894 and 13889 - /* - bool success = true; - try + std::string m_ImagePath1; + std::string m_ImagePath2; + std::string m_ImagePath3; + + mitk::DataNode::Pointer m_DataNode1; + mitk::DataNode::Pointer m_DataNode2; + mitk::DataNode::Pointer m_DataNode3; + + mitk::Image::Pointer m_mitkMultiComponentImage; + mitk::Image::Pointer m_mitkImageComponent1; + mitk::Image::Pointer m_mitkImageComponent2; + + bool AssertImageForLevelWindowProperty(bool assert1, bool assert2, bool assert3) { - mitk::LevelWindow levelWindow = manager->GetLevelWindow(); - manager->SetLevelWindow(levelWindow); + bool imageForLevelWindowProperty1, imageForLevelWindowProperty2, imageForLevelWindowProperty3; + + m_DataNode1->GetBoolProperty("imageForLevelWindow", imageForLevelWindowProperty1); + m_DataNode2->GetBoolProperty("imageForLevelWindow", imageForLevelWindowProperty2); + m_DataNode3->GetBoolProperty("imageForLevelWindow", imageForLevelWindowProperty3); + + return (assert1 == imageForLevelWindowProperty1) && + (assert2 == imageForLevelWindowProperty2) && + (assert3 == imageForLevelWindowProperty3); } - catch (...) + + bool AssertLevelWindowProperty(bool assert1, bool assert2, bool assert3) { - success == false; + auto levelWindowProperty1 = + dynamic_cast(m_DataNode1->GetProperty("levelwindow")); + auto levelWindowProperty2 = + dynamic_cast(m_DataNode2->GetProperty("levelwindow")); + auto levelWindowProperty3 = + dynamic_cast(m_DataNode3->GetProperty("levelwindow")); + + // Check if the active level window property of the manager is equal to any of the level window properties of the nodes + auto managerLevelWindowProperty = m_LevelWindowManager->GetLevelWindowProperty(); + + return (assert1 == (managerLevelWindowProperty == levelWindowProperty1)) && + (assert2 == (managerLevelWindowProperty == levelWindowProperty2)) && + (assert3 == (managerLevelWindowProperty == levelWindowProperty3)); } - MITK_TEST_CONDITION(success,"Testing mitk::LevelWindowManager GetLevelWindow() and SetLevelWindow()"); - */ - manager->SetAutoTopMostImage(true); - MITK_TEST_CONDITION(manager->IsAutoTopMost(), "Testing mitk::LevelWindowManager isAutoTopMost()"); - } +public: - static void TestRemoveObserver(std::string testImageFile) + void setUp() override { - mitk::LevelWindowManager::Pointer manager; - manager = mitk::LevelWindowManager::New(); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); - manager->SetDataStorage(ds); - - // add multiple objects to the data storage => multiple observers should be created - mitk::Image::Pointer image1 = mitk::IOUtil::Load(testImageFile); - mitk::DataNode::Pointer node1 = mitk::DataNode::New(); - node1->SetData(image1); - mitk::Image::Pointer image2 = mitk::IOUtil::Load(testImageFile); - mitk::DataNode::Pointer node2 = mitk::DataNode::New(); - node2->SetData(image2); - ds->Add(node1); - ds->Add(node2); - - MITK_TEST_CONDITION_REQUIRED(manager->GetRelevantNodes()->size() == 2, "Test if nodes have been added"); - MITK_TEST_CONDITION_REQUIRED( - static_cast(manager->GetRelevantNodes()->size()) == manager->GetNumberOfObservers(), - "Test if number of nodes is similar to number of observers"); - - mitk::Image::Pointer image3 = mitk::IOUtil::Load(testImageFile); - mitk::DataNode::Pointer node3 = mitk::DataNode::New(); - node3->SetData(image3); - ds->Add(node3); - MITK_TEST_CONDITION_REQUIRED(manager->GetRelevantNodes()->size() == 3, "Test if another node have been added"); - MITK_TEST_CONDITION_REQUIRED( - static_cast(manager->GetRelevantNodes()->size()) == manager->GetNumberOfObservers(), - "Test if number of nodes is similar to number of observers"); - - ds->Remove(node1); - MITK_TEST_CONDITION_REQUIRED(manager->GetRelevantNodes()->size() == 2, "Deleted node 1 (test GetRelevantNodes())"); - MITK_TEST_CONDITION_REQUIRED(manager->GetNumberOfObservers() == 2, "Deleted node 1 (test GetNumberOfObservers())"); - - ds->Remove(node2); - MITK_TEST_CONDITION_REQUIRED(manager->GetRelevantNodes()->size() == 1, "Deleted node 2 (test GetRelevantNodes())"); - MITK_TEST_CONDITION_REQUIRED(manager->GetNumberOfObservers() == 1, "Deleted node 2 (test GetNumberOfObservers())"); - - ds->Remove(node3); - MITK_TEST_CONDITION_REQUIRED(manager->GetRelevantNodes()->size() == 0, "Deleted node 3 (test GetRelevantNodes())"); - MITK_TEST_CONDITION_REQUIRED(manager->GetNumberOfObservers() == 0, "Deleted node 3 (test GetNumberOfObservers())"); + m_LevelWindowManager = mitk::LevelWindowManager::New(); + m_DataManager = mitk::StandaloneDataStorage::New(); + + CPPUNIT_ASSERT_NO_THROW_MESSAGE("DataStorage could not be set for the new level window manager", m_LevelWindowManager->SetDataStorage(m_DataManager)); + CPPUNIT_ASSERT_MESSAGE("DataStorage could not be retrieved from the new level window manager", m_DataManager == m_LevelWindowManager->GetDataStorage()); + + m_ImagePath1 = GetTestDataFilePath("Pic3D.nrrd"); + m_ImagePath2 = GetTestDataFilePath("UltrasoundImages/4D_TEE_Data_MV.dcm"); + m_ImagePath3 = GetTestDataFilePath("RenderingTestData/defaultWatermark.png"); + + // add multiple objects to the data storage => property observers will be created + m_DataNode1 = mitk::IOUtil::Load(m_ImagePath1, *m_DataManager)->GetElement(0); + m_DataNode2 = mitk::IOUtil::Load(m_ImagePath2, *m_DataManager)->GetElement(0); + CPPUNIT_ASSERT_MESSAGE("Not two relevant nodes found in the data storage", + m_LevelWindowManager->GetRelevantNodes()->size() == 2); + CPPUNIT_ASSERT_MESSAGE("Not two observers created for the relevant nodes", + m_LevelWindowManager->GetNumberOfObservers() == 2); + + m_DataNode1->SetIntProperty("layer", 1); + m_DataNode2->SetIntProperty("layer", 2); + + bool isImageForLevelWindow1, isImageForLevelWindow2; + m_DataNode1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); + m_DataNode2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); + CPPUNIT_ASSERT_MESSAGE("Initial \"imageForLevelWindow\" property not exclusively set for node 2", + !isImageForLevelWindow1 && isImageForLevelWindow2); + + m_DataNode3 = mitk::IOUtil::Load(m_ImagePath3, *m_DataManager)->GetElement(0); + CPPUNIT_ASSERT_MESSAGE("Not three relevant nodes found in the data storage", + m_LevelWindowManager->GetRelevantNodes()->size() == 3); + CPPUNIT_ASSERT_MESSAGE("Not three observers created for the relevant nodes", + m_LevelWindowManager->GetNumberOfObservers() == 3); + + m_DataNode3->SetIntProperty("layer", 3); + AssertImageForLevelWindowProperty(false, false, true); } - static bool VerifyRenderingModes() + void tearDown() override {} + + void TestModes() { - bool ok = (mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR == 1) && - (mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_LEVELWINDOW_COLOR == 2) && - (mitk::RenderingModeProperty::LOOKUPTABLE_COLOR == 3) && - (mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_COLOR == 4); + CPPUNIT_ASSERT_MESSAGE("AutoTopMost mode is not enabled per default", m_LevelWindowManager->IsAutoTopMost()); + CPPUNIT_ASSERT_MESSAGE("SelectedImagesMode mode is not disabled per default", !m_LevelWindowManager->IsSelectedImages()); - return ok; - } + m_LevelWindowManager->SetSelectedImages(true); + CPPUNIT_ASSERT_MESSAGE("AutoTopMost mode was not disabled on mode switch", !m_LevelWindowManager->IsAutoTopMost()); + CPPUNIT_ASSERT_MESSAGE("SelectedImagesMode mode was not enabled on mode switch", m_LevelWindowManager->IsSelectedImages()); - static void TestLevelWindowSliderVisibility(std::string testImageFile) - { - bool renderingModesValid = mitkLevelWindowManagerTestClass::VerifyRenderingModes(); - if (!renderingModesValid) - { - MITK_ERROR << "Exception: Image Rendering.Mode property value types inconsistent."; - } + m_LevelWindowManager->SetSelectedImages(false); + CPPUNIT_ASSERT_MESSAGE("AutoTopMost mode was not disabled on mode switch", !m_LevelWindowManager->IsAutoTopMost()); + CPPUNIT_ASSERT_MESSAGE("SelectedImagesMode mode was not disabled on mode switch", !m_LevelWindowManager->IsSelectedImages()); - mitk::LevelWindowManager::Pointer manager; - manager = mitk::LevelWindowManager::New(); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); - manager->SetDataStorage(ds); - - // add multiple objects to the data storage => multiple observers should be created - mitk::Image::Pointer image1 = mitk::IOUtil::Load(testImageFile); - mitk::DataNode::Pointer node1 = mitk::DataNode::New(); - node1->SetData(image1); - ds->Add(node1); - - mitk::DataNode::Pointer node2 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - mitk::DataNode::Pointer node3 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - std::vector nodeVec; - // nodeVec.resize( 3 ); - nodeVec.push_back(node1); - nodeVec.push_back(node2); - nodeVec.push_back(node3); - - typedef itk::Statistics::MersenneTwisterRandomVariateGenerator RandomGeneratorType; - RandomGeneratorType::Pointer rnd = RandomGeneratorType::New(); - rnd->Initialize(); - - for (unsigned int i = 0; i < 8; ++i) - { - unsigned int parity = i; - - for (unsigned int img = 0; img < 3; ++img) - { - if (parity & 1) - { - int mode = rnd->GetIntegerVariate() % 3; - nodeVec[img]->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(mode)); - } - else - { - int mode = rnd->GetIntegerVariate() % 2; - nodeVec[img]->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New(3 + mode)); - } - parity >>= 1; - } - - MITK_TEST_CONDITION( - renderingModesValid && ((!manager->GetLevelWindowProperty() && !i) || (manager->GetLevelWindowProperty() && i)), - "Testing level window property member according to rendering mode"); - } + m_LevelWindowManager->SetSelectedImages(true); // to enable "SelectedImagesMode" + m_LevelWindowManager->SetAutoTopMostImage(true); + CPPUNIT_ASSERT_MESSAGE("AutoTopMost mode was not enabled on mode switch", m_LevelWindowManager->IsAutoTopMost()); + CPPUNIT_ASSERT_MESSAGE("SelectedImagesMode mode was not disabled on mode switch", !m_LevelWindowManager->IsSelectedImages()); } - static void TestSetLevelWindowProperty(std::string testImageFile) + void TestSetLevelWindowProperty() { - mitk::LevelWindowManager::Pointer manager = mitk::LevelWindowManager::New(); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); - manager->SetDataStorage(ds); - - // add multiple objects to the data storage => multiple observers should be created - mitk::DataNode::Pointer node3 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - mitk::DataNode::Pointer node2 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - mitk::DataNode::Pointer node1 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - - node3->SetIntProperty("layer", 1); - node2->SetIntProperty("layer", 2); - node1->SetIntProperty("layer", 3); - - manager->SetAutoTopMostImage(true); - - bool isImageForLevelWindow1, isImageForLevelWindow2, isImageForLevelWindow3; - node1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); - node2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); - node3->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow3); - - MITK_TEST_CONDITION(isImageForLevelWindow1 && !isImageForLevelWindow2 && !isImageForLevelWindow3, - "Testing exclusive imageForLevelWindow property for node 1."); - - manager->SetAutoTopMostImage(false); - - mitk::LevelWindowProperty::Pointer prop = - dynamic_cast(node2->GetProperty("levelwindow")); - manager->SetLevelWindowProperty(prop); - node1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); - node2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); - node3->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow3); - - MITK_TEST_CONDITION(!isImageForLevelWindow1 && isImageForLevelWindow2 && !isImageForLevelWindow3, - "Testing exclusive imageForLevelWindow property for node 2."); - - prop = dynamic_cast(node3->GetProperty("levelwindow")); - manager->SetLevelWindowProperty(prop); - node1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); - node2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); - node3->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow3); - - MITK_TEST_CONDITION(!isImageForLevelWindow1 && !isImageForLevelWindow2 && isImageForLevelWindow3, - "Testing exclusive imageForLevelWindow property for node 3."); - - prop = dynamic_cast(node1->GetProperty("levelwindow")); - manager->SetLevelWindowProperty(prop); - node1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); - node2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); - node3->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow3); - - MITK_TEST_CONDITION(isImageForLevelWindow1 && !isImageForLevelWindow2 && !isImageForLevelWindow3, - "Testing exclusive imageForLevelWindow property for node 3."); + m_LevelWindowManager->SetAutoTopMostImage(false); + + // Setting the level window property of the manager + // will make the corresponding node be the "imageForLevelWindow" node. + auto levelWindowProperty = dynamic_cast(m_DataNode1->GetProperty("levelwindow")); + m_LevelWindowManager->SetLevelWindowProperty(levelWindowProperty); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + + levelWindowProperty = dynamic_cast(m_DataNode2->GetProperty("levelwindow")); + m_LevelWindowManager->SetLevelWindowProperty(levelWindowProperty); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); + + levelWindowProperty = dynamic_cast(m_DataNode3->GetProperty("levelwindow")); + m_LevelWindowManager->SetLevelWindowProperty(levelWindowProperty); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); + + levelWindowProperty = mitk::LevelWindowProperty::New(); + //CPPUNIT_ASSERT_THROW_MESSAGE("Expected exception for an invalid level window property was not thrown", + //m_LevelWindowManager->SetLevelWindowProperty(levelWindowProperty), mitk::Exception); } - static void TestImageForLevelWindowOnVisibilityChange(std::string testImageFile) + void TestVisibilityPropertyChanged() { - mitk::LevelWindowManager::Pointer manager = mitk::LevelWindowManager::New(); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); - manager->SetDataStorage(ds); - - // add multiple objects to the data storage => multiple observers should be created - mitk::DataNode::Pointer node3 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - mitk::DataNode::Pointer node2 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - mitk::DataNode::Pointer node1 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - - node3->SetIntProperty("layer", 1); - node2->SetIntProperty("layer", 2); - node1->SetIntProperty("layer", 3); + // Hiding a node will make the "next" node be the "imageForLevelWindow" node, if + // the hidden node was the "imageForLevelWindow" node before. "Next" is dependent on the node layer. + m_DataNode3->SetVisibility(false); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); + + m_DataNode2->SetVisibility(false); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + + m_DataNode1->SetVisibility(false); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("LevelWindowProperty is not null", !m_LevelWindowManager->GetLevelWindowProperty()); + + m_DataNode3->SetVisibility(true); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); + } - manager->SetAutoTopMostImage(false); + void TestLayerPropertyChanged() + { + m_DataNode3->SetIntProperty("layer", itk::NumericTraits::min()); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); + + m_DataNode2->SetIntProperty("layer", itk::NumericTraits::min()); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + + m_DataNode1->SetIntProperty("layer", itk::NumericTraits::min()); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("LevelWindowProperty is not null", !m_LevelWindowManager->GetLevelWindowProperty()); + + m_DataNode3->SetIntProperty("layer", 1); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); + } - bool isImageForLevelWindow1, isImageForLevelWindow2, isImageForLevelWindow3; - node1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); - node2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); - node3->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow3); + void TestImageRenderingModePropertyChanged() + { + // checking the default rendering mode + auto renderingMode = dynamic_cast(m_DataNode3->GetProperty("Image Rendering.Mode")); + CPPUNIT_ASSERT_MESSAGE("Initial \"Image Rendering.Mode\" property not set to \"RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR\"", + mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR == renderingMode->GetRenderingMode()); + + // Changing the "Image Rendering.Mode" of a node to either "LOOKUPTABLE_COLOR" or + // "COLORTRANSFERFUNCTION_COLOR" will ignore this node for the level window + m_DataNode3->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New( + mitk::RenderingModeProperty::LOOKUPTABLE_COLOR)); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); + + m_DataNode2->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New( + mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_COLOR)); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + + m_DataNode1->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New( + mitk::RenderingModeProperty::LOOKUPTABLE_COLOR)); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("LevelWindowProperty is not null", !m_LevelWindowManager->GetLevelWindowProperty()); + + m_DataNode3->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New( + mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_LEVELWINDOW_COLOR)); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); + } - MITK_TEST_CONDITION(isImageForLevelWindow1 && !isImageForLevelWindow2 && !isImageForLevelWindow3, - "Testing initial imageForLevelWindow setting."); + void TestImageForLevelWindowPropertyChanged() + { + m_LevelWindowManager->SetAutoTopMostImage(false); - node1->SetVisibility(false); - node1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); - node2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); - node3->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow3); + m_DataNode1->SetBoolProperty("imageForLevelWindow", true); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); - MITK_TEST_CONDITION(!isImageForLevelWindow1 && isImageForLevelWindow2 && !isImageForLevelWindow3, - "Testing exclusive imageForLevelWindow property for node 2."); + m_DataNode2->SetBoolProperty("imageForLevelWindow", true); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); - node2->SetVisibility(false); - node1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); - node2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); - node3->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow3); + m_DataNode3->SetBoolProperty("imageForLevelWindow", true); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); - MITK_TEST_CONDITION(!isImageForLevelWindow1 && !isImageForLevelWindow2 && isImageForLevelWindow3, - "Testing exclusive imageForLevelWindow property for node 3."); + // The top level node will alsways be used as a fall back node + // if no specific mode is selected. + m_DataNode3->SetBoolProperty("imageForLevelWindow", false); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); + } - node3->SetVisibility(false); - node1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); - node2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); - node3->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow3); + void TestSelectedPropertyChanged() + { + // Selecting a node will make this node be the "imageForLevelWindow" node, if + // the "SelectedImagesMode" is enabled (disabled per default). + m_DataNode1->SetSelected(true); // selection mode not enabled - node3 stays the "imageForLevelWindow" node + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); + + // This will immediately check for the selected node (node 1). + m_LevelWindowManager->SetSelectedImages(true); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + + // Second node will be selected (node 1 and node 2 selected). + // It is not specified which data node will be used for the "imageForLevelWindow" / "levelwindow" property, + // since the data storage access to the nodes is non-deterministic. + // At least check for any valid level window property. + m_DataNode2->SetSelected(true); + CPPUNIT_ASSERT_MESSAGE("LevelWindowProperty is null", m_LevelWindowManager->GetLevelWindowProperty()); + + // Third node will be selected (node 1, node 2 and node 3 selected) + // It is not specified which data node will be used for the "imageForLevelWindow" / "levelwindow" property, + // since the data storage access to the nodes is non-deterministic. + // At least check for any valid level window property. + m_DataNode3->SetSelected(true); + auto usedLevelWindowProperty = m_LevelWindowManager->GetLevelWindowProperty(); + CPPUNIT_ASSERT_MESSAGE("LevelWindowProperty is null", usedLevelWindowProperty); + + // All three nodes are selected: Check if only one node has the "imageForLevelWindow" property set and this node's + // "levelwindow" property is used by the level window manager. + auto levelWindowProperty1 = dynamic_cast(m_DataNode1->GetProperty("imageForLevelWindow")); + if (usedLevelWindowProperty == levelWindowProperty1) + { + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + } - MITK_TEST_CONDITION(!isImageForLevelWindow1 && !isImageForLevelWindow2 && isImageForLevelWindow3, - "Testing exclusive imageForLevelWindow property for node 3."); + auto levelWindowProperty2 = dynamic_cast(m_DataNode2->GetProperty("imageForLevelWindow")); + if (usedLevelWindowProperty == levelWindowProperty2) + { + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); + } - node1->SetVisibility(true); - node1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); - node2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); - node3->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow3); + auto levelWindowProperty3 = dynamic_cast(m_DataNode3->GetProperty("imageForLevelWindow")); + if (usedLevelWindowProperty == levelWindowProperty3) + { + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); + } - MITK_TEST_CONDITION(isImageForLevelWindow1 && !isImageForLevelWindow2 && !isImageForLevelWindow3, - "Testing exclusive imageForLevelWindow property for node 3."); + m_DataNode1->SetSelected(false); + m_DataNode2->SetSelected(false); + m_DataNode3->SetSelected(false); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("LevelWindowProperty is not null", !m_LevelWindowManager->GetLevelWindowProperty()); } - static void TestImageForLevelWindowOnRandomPropertyChange(std::string testImageFile) + void TestCombinedPropertiesChanged() { - typedef std::vector BoolVecType; - typedef itk::Statistics::MersenneTwisterRandomVariateGenerator RandomGeneratorType; - - // initialize the data storage - mitk::LevelWindowManager::Pointer manager = mitk::LevelWindowManager::New(); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); - manager->SetDataStorage(ds); - - mitk::DataNode::Pointer node3 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - mitk::DataNode::Pointer node2 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - mitk::DataNode::Pointer node1 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - - node3->SetIntProperty("layer", 1); - node2->SetIntProperty("layer", 2); - node1->SetIntProperty("layer", 3); - - // node visibilities - std::vector nodesVisible; - nodesVisible.resize(3); - std::fill(nodesVisible.begin(), nodesVisible.end(), true); - - // which node has the level window - std::vector nodesForLevelWindow; - nodesForLevelWindow.resize(3); - std::fill(nodesForLevelWindow.begin(), nodesForLevelWindow.end(), false); - - // the nodes themselves - std::vector nodes; - nodes.push_back(node1); - nodes.push_back(node2); - nodes.push_back(node3); - - // status quo - manager->SetAutoTopMostImage(false); - - bool lvlWin1, lvlWin2, lvlWin3; - node1->GetBoolProperty("imageForLevelWindow", lvlWin1); - node2->GetBoolProperty("imageForLevelWindow", lvlWin2); - node3->GetBoolProperty("imageForLevelWindow", lvlWin3); - - MITK_TEST_CONDITION(lvlWin1 && !lvlWin2 && !lvlWin3, "Testing initial imageForLevelWindow setting."); - - nodesForLevelWindow[0] = lvlWin1; - nodesForLevelWindow[1] = lvlWin2; - nodesForLevelWindow[2] = lvlWin3; - - // prepare randomized visibility changes - RandomGeneratorType::Pointer ranGen = RandomGeneratorType::New(); - ranGen->Initialize(); - - int ranCount = 100; - int validCount = 0; - int invalidCount = 0; - int mustHaveLvlWindow = 4; - for (int run = 0; run < ranCount; ++run) - { - // toggle node visibility - int ran = ranGen->GetIntegerVariate(2); - nodes[ran]->SetBoolProperty("imageForLevelWindow", !nodesForLevelWindow[ran]); - - // one node must have the level window - std::vector::const_iterator found = std::find(nodesForLevelWindow.begin(), nodesForLevelWindow.end(), true); - if (found == nodesForLevelWindow.end()) - { - break; - } - - // all invisible? - found = std::find(nodesVisible.begin(), nodesVisible.end(), true); - - if (!nodesForLevelWindow[ran]) - { - mustHaveLvlWindow = pow(2, 2 - ran); - } - else - { - mustHaveLvlWindow = 4; - } - - // get the current status - node1->GetBoolProperty("imageForLevelWindow", lvlWin1); - node2->GetBoolProperty("imageForLevelWindow", lvlWin2); - node3->GetBoolProperty("imageForLevelWindow", lvlWin3); - nodesForLevelWindow[0] = lvlWin1; - nodesForLevelWindow[1] = lvlWin2; - nodesForLevelWindow[2] = lvlWin3; - - int hasLevelWindow = 0; - for (int i = 0; i < 3; ++i) - { - if (nodesForLevelWindow[i]) - { - hasLevelWindow += pow(2, 2 - i); - } - } - - validCount += hasLevelWindow == mustHaveLvlWindow ? 1 : 0; - - // test sensitivity - int falseran = 0; - while (falseran == 0) - { - falseran = ranGen->GetIntegerVariate(7); - } - BoolVecType falseNodes; - falseNodes.push_back((falseran & 1) == 1 ? !lvlWin1 : lvlWin1); - falseran >>= 1; - falseNodes.push_back((falseran & 1) == 1 ? !lvlWin2 : lvlWin2); - falseran >>= 1; - falseNodes.push_back((falseran & 1) == 1 ? !lvlWin3 : lvlWin3); - int falseLevelWindow = 0; - for (int i = 0; i < 3; ++i) - { - if (falseNodes[i]) - { - falseLevelWindow += pow(2, 2 - i); - } - } - - invalidCount += falseLevelWindow == mustHaveLvlWindow ? 0 : 1; - - // in case of errors proceed anyway - mustHaveLvlWindow = hasLevelWindow; - } + m_LevelWindowManager->SetSelectedImages(true); + m_DataNode1->SetSelected(true); // selection mode enabled - node1 becomes the "imageForLevelWindow" node + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + + m_DataNode2->SetIntProperty("layer", itk::NumericTraits::max()); // selection mode enabled - node1 stays the "imageForLevelWindow" node + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + + m_DataNode1->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New( + mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_COLOR)); // selection mode enabled - but node1 is ignored - no active level window property + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, false)); + + m_DataNode3->SetBoolProperty("imageForLevelWindow", true); // selection mode enabled - no valid node selected, no active level window property + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, false)); + + m_LevelWindowManager->SetSelectedImages(false); // no mode enabled - however, level window is not modified / updated + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, false)); + + m_LevelWindowManager->Update(itk::ModifiedEvent()); // no mode enabled - level window is modified / updated with topmost visible node2 as fallback + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); + + m_DataNode3->SetBoolProperty("imageForLevelWindow", true); // no mode enabled - use node3 with "imageForLevelWindow" property set + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); + + m_DataNode3->SetVisibility(false); // node3 with "imageForLevelWindow" property not visible - use topmost visible node2 as fallback + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); + + m_DataNode2->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New( + mitk::RenderingModeProperty::LOOKUPTABLE_COLOR)); // fallback node2 is ignored - but "imageForLevelWindow" and "levelWindow" stay "true" for node2 + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); + + m_LevelWindowManager->SetAutoTopMostImage(true); // no visible node exists that is not ignored + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, false)); + + auto levelWindowProperty = dynamic_cast(m_DataNode3->GetProperty("levelwindow")); + m_LevelWindowManager->SetLevelWindowProperty(levelWindowProperty); // explicitely set the level window to node3 + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); + + m_DataNode1->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New( + mitk::RenderingModeProperty::COLORTRANSFERFUNCTION_LEVELWINDOW_COLOR)); // auto topmost mode enabled - node1 is the only visible non-ignored node + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + + m_DataNode2->SetProperty("Image Rendering.Mode", mitk::RenderingModeProperty::New( + mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR)); // auto topmost mode enabled - node2 is topmost visible node + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, true, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, true, false)); + + m_DataNode2->SetIntProperty("layer", itk::NumericTraits::min()); // auto topmost mode enabled - node1 is topmost visible node + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(true, false, false)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(true, false, false)); + + m_DataNode3->SetVisibility(true); // auto topmost mode enabled - node3 is topmost visible node + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", AssertImageForLevelWindowProperty(false, false, true)); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", AssertLevelWindowProperty(false, false, true)); - MITK_TEST_CONDITION(validCount == ranCount, "Testing proper node for level window property."); - MITK_TEST_CONDITION(invalidCount == ranCount, "Sensitivity test."); } - static void TestImageForLevelWindowOnRandomVisibilityChange(std::string testImageFile) + void TestRemoveDataNodes() { - typedef std::vector BoolVecType; - typedef itk::Statistics::MersenneTwisterRandomVariateGenerator RandomGeneratorType; - - // initialize the data storage - mitk::LevelWindowManager::Pointer manager = mitk::LevelWindowManager::New(); - mitk::StandaloneDataStorage::Pointer ds = mitk::StandaloneDataStorage::New(); - manager->SetDataStorage(ds); - - mitk::DataNode::Pointer node3 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - mitk::DataNode::Pointer node2 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - mitk::DataNode::Pointer node1 = mitk::IOUtil::Load(testImageFile, *ds)->GetElement(0); - - node3->SetIntProperty("layer", 1); - node2->SetIntProperty("layer", 2); - node1->SetIntProperty("layer", 3); - - // node visibilities - std::vector nodesVisible; - nodesVisible.resize(3); - std::fill(nodesVisible.begin(), nodesVisible.end(), true); - - // which node has the level window - std::vector nodesForLevelWindow; - nodesForLevelWindow.resize(3); - std::fill(nodesForLevelWindow.begin(), nodesForLevelWindow.end(), false); - - // the nodes themselves - std::vector nodes; - nodes.push_back(node1); - nodes.push_back(node2); - nodes.push_back(node3); - - // status quo - manager->SetAutoTopMostImage(false); - - bool lvlWin1, lvlWin2, lvlWin3; - node1->GetBoolProperty("imageForLevelWindow", lvlWin1); - node2->GetBoolProperty("imageForLevelWindow", lvlWin2); - node3->GetBoolProperty("imageForLevelWindow", lvlWin3); - - MITK_TEST_CONDITION(lvlWin1 && !lvlWin2 && !lvlWin3, "Testing initial imageForLevelWindow setting."); - - nodesForLevelWindow[0] = lvlWin1; - nodesForLevelWindow[1] = lvlWin2; - nodesForLevelWindow[2] = lvlWin3; - - // prepare randomized visibility changes - RandomGeneratorType::Pointer ranGen = RandomGeneratorType::New(); - ranGen->Initialize(); - - int ranCount = 100; - int validCount = 0; - int invalidCount = 0; - int mustHaveLvlWindow = 4; - for (int run = 0; run < ranCount; ++run) - { - // toggle node visibility - int ran = ranGen->GetIntegerVariate(2); - nodesVisible[ran] = !nodesVisible[ran]; - nodes[ran]->SetVisibility(nodesVisible[ran]); - - // one node must have the level window - std::vector::const_iterator found = std::find(nodesForLevelWindow.begin(), nodesForLevelWindow.end(), true); - if (found == nodesForLevelWindow.end()) - { - break; - } - int ind = found - nodesForLevelWindow.begin(); - - // all invisible? - found = std::find(nodesVisible.begin(), nodesVisible.end(), true); - bool allInvisible = (found == nodesVisible.end()); - - // which node shall get the level window now - if (!allInvisible && !nodesVisible[ind]) - { - int count = 0; - for (std::vector::const_iterator it = nodesVisible.begin(); it != nodesVisible.end(); ++it, ++count) - { - if (*it) - { - mustHaveLvlWindow = pow(2, 2 - count); - break; - } - } - } - - // get the current status - node1->GetBoolProperty("imageForLevelWindow", lvlWin1); - node2->GetBoolProperty("imageForLevelWindow", lvlWin2); - node3->GetBoolProperty("imageForLevelWindow", lvlWin3); - nodesForLevelWindow[0] = lvlWin1; - nodesForLevelWindow[1] = lvlWin2; - nodesForLevelWindow[2] = lvlWin3; - - int hasLevelWindow = 0; - for (int i = 0; i < 3; ++i) - { - if (nodesForLevelWindow[i]) - { - hasLevelWindow += pow(2, 2 - i); - } - } - - validCount += hasLevelWindow == mustHaveLvlWindow ? 1 : 0; - - // test sensitivity - int falseran = 0; - while (falseran == 0) - { - falseran = ranGen->GetIntegerVariate(7); - } - BoolVecType falseNodes; - falseNodes.push_back((falseran & 1) == 1 ? !lvlWin1 : lvlWin1); - falseran >>= 1; - falseNodes.push_back((falseran & 1) == 1 ? !lvlWin2 : lvlWin2); - falseran >>= 1; - falseNodes.push_back((falseran & 1) == 1 ? !lvlWin3 : lvlWin3); - int falseLevelWindow = 0; - for (int i = 0; i < 3; ++i) - { - if (falseNodes[i]) - { - falseLevelWindow += pow(2, 2 - i); - } - } - - invalidCount += falseLevelWindow == mustHaveLvlWindow ? 0 : 1; - - // in case of errors proceed anyway - mustHaveLvlWindow = hasLevelWindow; - } - - MITK_TEST_CONDITION(validCount == ranCount, "Testing proper node for level window property."); - MITK_TEST_CONDITION(invalidCount == ranCount, "Sensitivity test."); + m_DataManager->Remove(m_DataNode3); + CPPUNIT_ASSERT_MESSAGE("Node not correctly removed", m_LevelWindowManager->GetRelevantNodes()->size() == 2); + CPPUNIT_ASSERT_MESSAGE("Observer not correctly removed", m_LevelWindowManager->GetNumberOfObservers() == 2); + + bool isImageForLevelWindow1, isImageForLevelWindow2; + m_DataNode1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); + m_DataNode2->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow2); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", !isImageForLevelWindow1 && isImageForLevelWindow2); + + auto levelWindowProperty1 = dynamic_cast(m_DataNode1->GetProperty("levelwindow")); + auto levelWindowProperty2 = dynamic_cast(m_DataNode2->GetProperty("levelwindow")); + auto managerLevelWindowProperty = m_LevelWindowManager->GetLevelWindowProperty(); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", + (false == (managerLevelWindowProperty == levelWindowProperty1)) && + (true == (managerLevelWindowProperty == levelWindowProperty2))); + + m_DataManager->Remove(m_DataNode2); + CPPUNIT_ASSERT_MESSAGE("Node not correctly removed", m_LevelWindowManager->GetRelevantNodes()->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Observer not correctly removed", m_LevelWindowManager->GetNumberOfObservers() == 1); + + m_DataNode1->GetBoolProperty("imageForLevelWindow", isImageForLevelWindow1); + CPPUNIT_ASSERT_MESSAGE("\"imageForLevelWindow\" property not correctly set", isImageForLevelWindow1); + + levelWindowProperty1 = dynamic_cast(m_DataNode1->GetProperty("levelwindow")); + managerLevelWindowProperty = m_LevelWindowManager->GetLevelWindowProperty(); + CPPUNIT_ASSERT_MESSAGE("\"levelwindow\" property not correctly set", true == (managerLevelWindowProperty == levelWindowProperty1)); + + m_DataManager->Remove(m_DataNode1); + CPPUNIT_ASSERT_MESSAGE("Node not correctly removed", m_LevelWindowManager->GetRelevantNodes()->size() == 0); + CPPUNIT_ASSERT_MESSAGE("Observer not correctly removed", m_LevelWindowManager->GetNumberOfObservers() == 0); + + CPPUNIT_ASSERT_MESSAGE("LevelWindowProperty is not null", !m_LevelWindowManager->GetLevelWindowProperty()); } }; -int mitkLevelWindowManagerTest(int argc, char *args[]) -{ - MITK_TEST_BEGIN("mitkLevelWindowManager"); - - MITK_TEST_CONDITION_REQUIRED(argc >= 2, "Testing if test file is given."); - std::string testImage = args[1]; - - mitkLevelWindowManagerTestClass::TestInstantiation(); - mitkLevelWindowManagerTestClass::TestSetGetDataStorage(); - mitkLevelWindowManagerTestClass::TestMethodsWithInvalidParameters(); - mitkLevelWindowManagerTestClass::TestOtherMethods(); - mitkLevelWindowManagerTestClass::TestRemoveObserver(testImage); - mitkLevelWindowManagerTestClass::TestLevelWindowSliderVisibility(testImage); - mitkLevelWindowManagerTestClass::TestSetLevelWindowProperty(testImage); - mitkLevelWindowManagerTestClass::TestImageForLevelWindowOnVisibilityChange(testImage); - mitkLevelWindowManagerTestClass::TestImageForLevelWindowOnRandomVisibilityChange(testImage); - mitkLevelWindowManagerTestClass::TestImageForLevelWindowOnRandomPropertyChange(testImage); - - MITK_TEST_END(); -} +MITK_TEST_SUITE_REGISTRATION(mitkLevelWindowManager) diff --git a/Modules/CppMicroServices/core/test/CMakeLists.txt b/Modules/CppMicroServices/core/test/CMakeLists.txt index 6a763a0fa7..f0c606b453 100644 --- a/Modules/CppMicroServices/core/test/CMakeLists.txt +++ b/Modules/CppMicroServices/core/test/CMakeLists.txt @@ -1,98 +1,100 @@ #----------------------------------------------------------------------------- # Configure files, include dirs, etc. #----------------------------------------------------------------------------- configure_file("${CMAKE_CURRENT_SOURCE_DIR}/usTestingConfig.h.in" "${PROJECT_BINARY_DIR}/include/usTestingConfig.h") include_directories(${CMAKE_CURRENT_SOURCE_DIR}) #----------------------------------------------------------------------------- # Create test modules #----------------------------------------------------------------------------- include(usFunctionCreateTestModule) set(_us_test_module_libs "" CACHE INTERNAL "" FORCE) add_subdirectory(modules) #----------------------------------------------------------------------------- # Add unit tests #----------------------------------------------------------------------------- set(_tests usAnyTest usLDAPFilterTest usLogTest usModuleHooksTest usModuleManifestTest usModuleTest usModuleResourceTest usServiceFactoryTest usServiceHooksTest usServiceRegistryPerformanceTest usServiceRegistryTest usServiceTemplateTest usServiceTrackerTest usStaticModuleResourceTest usStaticModuleTest ) if(US_BUILD_SHARED_LIBS) list(APPEND _tests usServiceListenerTest usSharedLibraryTest ) if(US_ENABLE_AUTOLOADING_SUPPORT) list(APPEND _tests usModuleAutoLoadTest) endif() endif() +list(TRANSFORM _tests APPEND ".cpp" OUTPUT_VARIABLE _test_files) + set(_additional_srcs usTestDriverActivator.cpp usTestManager.cpp usTestUtilModuleListener.cpp ) set(_test_driver us${PROJECT_NAME}TestDriver) set(_test_sourcelist_extra_args ) -create_test_sourcelist(_srcs ${_test_driver}.cpp ${_tests} ${_test_sourcelist_extra_args}) +create_test_sourcelist(_srcs ${_test_driver}.cpp ${_test_files} ${_test_sourcelist_extra_args}) # Generate a custom "module init" file for the test driver executable usFunctionGenerateModuleInit(_srcs) usFunctionGetResourceSource(TARGET ${_test_driver} OUT _srcs) add_executable(${_test_driver} ${_srcs} ${_additional_srcs}) set_property(TARGET ${_test_driver} APPEND PROPERTY COMPILE_DEFINITIONS US_MODULE_NAME=main) set_property(TARGET ${_test_driver} PROPERTY US_MODULE_NAME main) set_property(TARGET ${_test_driver} PROPERTY FOLDER "${MITK_ROOT_FOLDER}/CppMicroServices/Tests") if(NOT US_BUILD_SHARED_LIBS) set_property(TARGET ${_test_driver} APPEND PROPERTY COMPILE_DEFINITIONS US_STATIC_MODULE) target_link_libraries(${_test_driver} ${_us_test_module_libs}) endif() target_link_libraries(${_test_driver} ${Core_TARGET}) if(UNIX AND NOT APPLE) target_link_libraries(${_test_driver} rt) endif() # Add resources usFunctionEmbedResources(TARGET ${_test_driver} FILES usTestResource.txt manifest.json ZIP_ARCHIVES ${Core_TARGET} ${_us_test_module_libs}) # Register tests foreach(_test ${_tests}) add_test(NAME ${_test} COMMAND ${_test_driver} ${_test}) endforeach() #----------------------------------------------------------------------------- # Add dependencies for shared libraries #----------------------------------------------------------------------------- if(US_BUILD_SHARED_LIBS) foreach(_test_module ${_us_test_module_libs}) add_dependencies(${_test_driver} ${_test_module}) endforeach() endif() diff --git a/Modules/Multilabel/mitkLabelSetIOHelper.cpp b/Modules/Multilabel/mitkLabelSetIOHelper.cpp index 70d5e47426..af54833042 100644 --- a/Modules/Multilabel/mitkLabelSetIOHelper.cpp +++ b/Modules/Multilabel/mitkLabelSetIOHelper.cpp @@ -1,271 +1,275 @@ /*============================================================================ 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 "mitkLabelSetIOHelper.h" #include "mitkLabelSetImage.h" #include #include namespace { std::string EnsureExtension(const std::string& filename) { const std::string extension = ".lsetp"; if (filename.size() < extension.size() || std::string::npos == filename.find(extension, filename.size() - extension.size())) return filename + extension; return filename; } } bool mitk::LabelSetIOHelper::SaveLabelSetImagePreset(const std::string &presetFilename, mitk::LabelSetImage::Pointer &inputImage) { const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; xmlDocument.InsertEndChild(xmlDocument.NewDeclaration()); auto *rootElement = xmlDocument.NewElement("LabelSetImagePreset"); rootElement->SetAttribute("layers", inputImage->GetNumberOfLayers()); xmlDocument.InsertEndChild(rootElement); for (unsigned int layerIndex = 0; layerIndex < inputImage->GetNumberOfLayers(); layerIndex++) { auto *layerElement = xmlDocument.NewElement("Layer"); layerElement->SetAttribute("index", layerIndex); layerElement->SetAttribute("labels", inputImage->GetNumberOfLabels(layerIndex)); rootElement->InsertEndChild(layerElement); for (unsigned int labelIndex = 0; labelIndex < inputImage->GetNumberOfLabels(layerIndex); labelIndex++) layerElement->InsertEndChild(LabelSetIOHelper::GetLabelAsXMLElement(xmlDocument, inputImage->GetLabel(labelIndex, layerIndex))); } return tinyxml2::XML_SUCCESS == xmlDocument.SaveFile(filename.c_str()); } void mitk::LabelSetIOHelper::LoadLabelSetImagePreset(const std::string &presetFilename, mitk::LabelSetImage::Pointer &inputImage) { if (inputImage.IsNull()) return; const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; if(tinyxml2::XML_SUCCESS != xmlDocument.LoadFile(filename.c_str())) return; auto *rootElement = xmlDocument.FirstChildElement("LabelSetImagePreset"); if (nullptr == rootElement) { MITK_WARN << "Not a valid LabelSet preset"; return; } auto activeLayerBackup = inputImage->GetActiveLayer(); int numberOfLayers = 0; rootElement->QueryIntAttribute("layers", &numberOfLayers); auto* layerElement = rootElement->FirstChildElement("Layer"); if (nullptr == layerElement) { MITK_WARN << "LabelSet preset does not contain any layers"; return; } for (int layerIndex = 0; layerIndex < numberOfLayers; layerIndex++) { int numberOfLabels = 0; layerElement->QueryIntAttribute("labels", &numberOfLabels); if (nullptr == inputImage->GetLabelSet(layerIndex)) { inputImage->AddLayer(); } else { inputImage->SetActiveLayer(layerIndex); } auto *labelElement = layerElement->FirstChildElement("Label"); if (nullptr == labelElement) continue; for (int labelIndex = 0; labelIndex < numberOfLabels; labelIndex++) { auto label = mitk::LabelSetIOHelper::LoadLabelFromXMLDocument(labelElement); const auto labelValue = label->GetValue(); if (0 != labelValue) { auto* labelSet = inputImage->GetLabelSet(layerIndex); auto* alreadyExistingLabel = labelSet->GetLabel(labelValue); if (nullptr != alreadyExistingLabel) { // Override existing label with label from preset alreadyExistingLabel->ConcatenatePropertyList(label); labelSet->UpdateLookupTable(labelValue); } else { labelSet->AddLabel(label); } } labelElement = labelElement->NextSiblingElement("Label"); if (nullptr == labelElement) continue; } layerElement = layerElement->NextSiblingElement("Layer"); if (nullptr == layerElement) continue; } inputImage->SetActiveLayer(activeLayerBackup); } tinyxml2::XMLElement *mitk::LabelSetIOHelper::GetLabelAsXMLElement(tinyxml2::XMLDocument &doc, Label *label) { auto *labelElem = doc.NewElement("Label"); - // add XML contents - const PropertyList::PropertyMap *propmap = label->GetMap(); - for (auto iter = propmap->begin(); iter != propmap->end(); ++iter) + if (nullptr != label) { - std::string key = iter->first; - const BaseProperty *property = iter->second; - auto *element = PropertyToXMLElement(doc, key, property); - if (element) - labelElem->InsertEndChild(element); + // add XML contents + const PropertyList::PropertyMap* propmap = label->GetMap(); + for (auto iter = propmap->begin(); iter != propmap->end(); ++iter) + { + std::string key = iter->first; + const BaseProperty* property = iter->second; + auto* element = PropertyToXMLElement(doc, key, property); + if (element) + labelElem->InsertEndChild(element); + } } + return labelElem; } mitk::Label::Pointer mitk::LabelSetIOHelper::LoadLabelFromXMLDocument(const tinyxml2::XMLElement *labelElem) { // reread auto *propElem = labelElem->FirstChildElement("property"); std::string name; mitk::BaseProperty::Pointer prop; mitk::Label::Pointer label = mitk::Label::New(); while (propElem) { LabelSetIOHelper::PropertyFromXMLElement(name, prop, propElem); label->SetProperty(name, prop); propElem = propElem->NextSiblingElement("property"); } return label.GetPointer(); } tinyxml2::XMLElement *mitk::LabelSetIOHelper::PropertyToXMLElement(tinyxml2::XMLDocument &doc, const std::string &key, const BaseProperty *property) { auto *keyelement = doc.NewElement("property"); keyelement->SetAttribute("key", key.c_str()); keyelement->SetAttribute("type", property->GetNameOfClass()); // construct name of serializer class std::string serializername(property->GetNameOfClass()); serializername += "Serializer"; std::list allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << property->GetNameOfClass() << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple serializers found for " << property->GetNameOfClass() << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { serializer->SetProperty(property); try { auto *valueelement = serializer->Serialize(doc); if (valueelement) keyelement->InsertEndChild(valueelement); } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } break; } } return keyelement; } bool mitk::LabelSetIOHelper::PropertyFromXMLElement(std::string &key, mitk::BaseProperty::Pointer &prop, const tinyxml2::XMLElement *elem) { const char* typeC = elem->Attribute("type"); std::string type = nullptr != typeC ? typeC : ""; const char* keyC = elem->Attribute("key"); key = nullptr != keyC ? keyC : ""; // construct name of serializer class std::string serializername(type); serializername += "Serializer"; std::list allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << type << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple deserializers found for " << type << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { try { prop = serializer->Deserialize(elem->FirstChildElement()); } catch (std::exception &e) { MITK_ERROR << "Deserializer " << serializer->GetNameOfClass() << " failed: " << e.what(); return false; } break; } } if (prop.IsNull()) return false; return true; } diff --git a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp index 56069a2be9..beb6d792ce 100644 --- a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp +++ b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp @@ -1,650 +1,649 @@ /*============================================================================ 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 "mitkLabelSetImageVtkMapper2D.h" // MITK #include #include #include #include #include #include #include #include #include #include #include -#include #include #include // MITK Rendering #include "vtkMitkLevelWindowFilter.h" #include "vtkMitkThickSlicesFilter.h" #include "vtkNeverTranslucentTexture.h" // VTK #include #include #include #include #include #include #include #include #include #include #include #include //#include // ITK #include #include mitk::LabelSetImageVtkMapper2D::LabelSetImageVtkMapper2D() { } mitk::LabelSetImageVtkMapper2D::~LabelSetImageVtkMapper2D() { } vtkProp *mitk::LabelSetImageVtkMapper2D::GetVtkProp(mitk::BaseRenderer *renderer) { // return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actors; } mitk::LabelSetImageVtkMapper2D::LocalStorage *mitk::LabelSetImageVtkMapper2D::GetLocalStorage( mitk::BaseRenderer *renderer) { return m_LSH.GetLocalStorage(renderer); } void mitk::LabelSetImageVtkMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::DataNode *node = this->GetDataNode(); auto *image = dynamic_cast(node->GetData()); assert(image && image->IsInitialized()); // check if there is a valid worldGeometry const PlaneGeometry *worldGeometry = renderer->GetCurrentWorldPlaneGeometry(); if ((worldGeometry == nullptr) || (!worldGeometry->IsValid()) || (!worldGeometry->HasReferenceGeometry())) return; image->Update(); int numberOfLayers = image->GetNumberOfLayers(); int activeLayer = image->GetActiveLayer(); float opacity = 1.0f; node->GetOpacity(opacity, renderer, "opacity"); if (numberOfLayers != localStorage->m_NumberOfLayers) { localStorage->m_NumberOfLayers = numberOfLayers; localStorage->m_ReslicedImageVector.clear(); localStorage->m_ReslicerVector.clear(); localStorage->m_LayerTextureVector.clear(); localStorage->m_LevelWindowFilterVector.clear(); localStorage->m_LayerMapperVector.clear(); localStorage->m_LayerActorVector.clear(); localStorage->m_Actors = vtkSmartPointer::New(); for (int lidx = 0; lidx < numberOfLayers; ++lidx) { localStorage->m_ReslicedImageVector.push_back(vtkSmartPointer::New()); localStorage->m_ReslicerVector.push_back(mitk::ExtractSliceFilter::New()); localStorage->m_LayerTextureVector.push_back(vtkSmartPointer::New()); localStorage->m_LevelWindowFilterVector.push_back(vtkSmartPointer::New()); localStorage->m_LayerMapperVector.push_back(vtkSmartPointer::New()); localStorage->m_LayerActorVector.push_back(vtkSmartPointer::New()); // do not repeat the texture (the image) localStorage->m_LayerTextureVector[lidx]->RepeatOff(); // set corresponding mappers for the actors localStorage->m_LayerActorVector[lidx]->SetMapper(localStorage->m_LayerMapperVector[lidx]); localStorage->m_Actors->AddPart(localStorage->m_LayerActorVector[lidx]); } localStorage->m_Actors->AddPart(localStorage->m_OutlineShadowActor); localStorage->m_Actors->AddPart(localStorage->m_OutlineActor); } // early out if there is no intersection of the current rendering geometry // and the geometry of the image that is to be rendered. if (!RenderingGeometryIntersectsImage(worldGeometry, image->GetSlicedGeometry())) { // set image to nullptr, to clear the texture in 3D, because // the latest image is used there if the plane is out of the geometry // see bug-13275 for (int lidx = 0; lidx < numberOfLayers; ++lidx) { localStorage->m_ReslicedImageVector[lidx] = nullptr; localStorage->m_LayerMapperVector[lidx]->SetInputData(localStorage->m_EmptyPolyData); localStorage->m_OutlineActor->SetVisibility(false); localStorage->m_OutlineShadowActor->SetVisibility(false); } return; } for (int lidx = 0; lidx < numberOfLayers; ++lidx) { mitk::Image *layerImage = nullptr; // set main input for ExtractSliceFilter if (lidx == activeLayer) layerImage = image; else layerImage = image->GetLayerImage(lidx); localStorage->m_ReslicerVector[lidx]->SetInput(layerImage); localStorage->m_ReslicerVector[lidx]->SetWorldGeometry(worldGeometry); localStorage->m_ReslicerVector[lidx]->SetTimeStep(this->GetTimestep()); // set the transformation of the image to adapt reslice axis localStorage->m_ReslicerVector[lidx]->SetResliceTransformByGeometry( layerImage->GetTimeGeometry()->GetGeometryForTimeStep(this->GetTimestep())); // is the geometry of the slice based on the image image or the worldgeometry? bool inPlaneResampleExtentByGeometry = false; node->GetBoolProperty("in plane resample extent by geometry", inPlaneResampleExtentByGeometry, renderer); localStorage->m_ReslicerVector[lidx]->SetInPlaneResampleExtentByGeometry(inPlaneResampleExtentByGeometry); localStorage->m_ReslicerVector[lidx]->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); localStorage->m_ReslicerVector[lidx]->SetVtkOutputRequest(true); // this is needed when thick mode was enabled before. These variables have to be reset to default values localStorage->m_ReslicerVector[lidx]->SetOutputDimensionality(2); localStorage->m_ReslicerVector[lidx]->SetOutputSpacingZDirection(1.0); localStorage->m_ReslicerVector[lidx]->SetOutputExtentZDirection(0, 0); // Bounds information for reslicing (only required if reference geometry is present) // this used for generating a vtkPLaneSource with the right size double sliceBounds[6]; sliceBounds[0] = 0.0; sliceBounds[1] = 0.0; sliceBounds[2] = 0.0; sliceBounds[3] = 0.0; sliceBounds[4] = 0.0; sliceBounds[5] = 0.0; localStorage->m_ReslicerVector[lidx]->GetClippedPlaneBounds(sliceBounds); // setup the textured plane this->GeneratePlane(renderer, sliceBounds); // get the spacing of the slice localStorage->m_mmPerPixel = localStorage->m_ReslicerVector[lidx]->GetOutputSpacing(); localStorage->m_ReslicerVector[lidx]->Modified(); // start the pipeline with updating the largest possible, needed if the geometry of the image has changed localStorage->m_ReslicerVector[lidx]->UpdateLargestPossibleRegion(); localStorage->m_ReslicedImageVector[lidx] = localStorage->m_ReslicerVector[lidx]->GetVtkOutput(); const auto *planeGeometry = dynamic_cast(worldGeometry); double textureClippingBounds[6]; for (auto &textureClippingBound : textureClippingBounds) { textureClippingBound = 0.0; } // Calculate the actual bounds of the transformed plane clipped by the // dataset bounding box; this is required for drawing the texture at the // correct position during 3D mapping. mitk::PlaneClipping::CalculateClippedPlaneBounds(layerImage->GetGeometry(), planeGeometry, textureClippingBounds); textureClippingBounds[0] = static_cast(textureClippingBounds[0] / localStorage->m_mmPerPixel[0] + 0.5); textureClippingBounds[1] = static_cast(textureClippingBounds[1] / localStorage->m_mmPerPixel[0] + 0.5); textureClippingBounds[2] = static_cast(textureClippingBounds[2] / localStorage->m_mmPerPixel[1] + 0.5); textureClippingBounds[3] = static_cast(textureClippingBounds[3] / localStorage->m_mmPerPixel[1] + 0.5); // clipping bounds for cutting the imageLayer localStorage->m_LevelWindowFilterVector[lidx]->SetClippingBounds(textureClippingBounds); localStorage->m_LevelWindowFilterVector[lidx]->SetLookupTable( image->GetLabelSet(lidx)->GetLookupTable()->GetVtkLookupTable()); // do not use a VTK lookup table (we do that ourselves in m_LevelWindowFilter) localStorage->m_LayerTextureVector[lidx]->SetColorModeToDirectScalars(); // connect the imageLayer with the levelwindow filter localStorage->m_LevelWindowFilterVector[lidx]->SetInputData(localStorage->m_ReslicedImageVector[lidx]); // connect the texture with the output of the levelwindow filter // check for texture interpolation property bool textureInterpolation = false; node->GetBoolProperty("texture interpolation", textureInterpolation, renderer); // set the interpolation modus according to the property localStorage->m_LayerTextureVector[lidx]->SetInterpolate(textureInterpolation); localStorage->m_LayerTextureVector[lidx]->SetInputConnection( localStorage->m_LevelWindowFilterVector[lidx]->GetOutputPort()); this->TransformActor(renderer); // set the plane as input for the mapper localStorage->m_LayerMapperVector[lidx]->SetInputConnection(localStorage->m_Plane->GetOutputPort()); // set the texture for the actor localStorage->m_LayerActorVector[lidx]->SetTexture(localStorage->m_LayerTextureVector[lidx]); localStorage->m_LayerActorVector[lidx]->GetProperty()->SetOpacity(opacity); } mitk::Label* activeLabel = image->GetActiveLabel(activeLayer); if (nullptr != activeLabel) { bool contourActive = false; node->GetBoolProperty("labelset.contour.active", contourActive, renderer); if (contourActive && activeLabel->GetVisible()) //contour rendering { //generate contours/outlines localStorage->m_OutlinePolyData = this->CreateOutlinePolyData(renderer, localStorage->m_ReslicedImageVector[activeLayer], activeLabel->GetValue()); localStorage->m_OutlineActor->SetVisibility(true); localStorage->m_OutlineShadowActor->SetVisibility(true); const mitk::Color& color = activeLabel->GetColor(); localStorage->m_OutlineActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); localStorage->m_OutlineShadowActor->GetProperty()->SetColor(0, 0, 0); float contourWidth(2.0); node->GetFloatProperty("labelset.contour.width", contourWidth, renderer); localStorage->m_OutlineActor->GetProperty()->SetLineWidth(contourWidth); localStorage->m_OutlineShadowActor->GetProperty()->SetLineWidth(contourWidth * 1.5); localStorage->m_OutlineActor->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineShadowActor->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineMapper->SetInputData(localStorage->m_OutlinePolyData); return; } } localStorage->m_OutlineActor->SetVisibility(false); localStorage->m_OutlineShadowActor->SetVisibility(false); } bool mitk::LabelSetImageVtkMapper2D::RenderingGeometryIntersectsImage(const PlaneGeometry *renderingGeometry, SlicedGeometry3D *imageGeometry) { // if either one of the two geometries is nullptr we return true // for safety reasons if (renderingGeometry == nullptr || imageGeometry == nullptr) return true; // get the distance for the first cornerpoint ScalarType initialDistance = renderingGeometry->SignedDistance(imageGeometry->GetCornerPoint(0)); for (int i = 1; i < 8; i++) { mitk::Point3D cornerPoint = imageGeometry->GetCornerPoint(i); // get the distance to the other cornerpoints ScalarType distance = renderingGeometry->SignedDistance(cornerPoint); // if it has not the same signing as the distance of the first point if (initialDistance * distance < 0) { // we have an intersection and return true return true; } } // all distances have the same sign, no intersection and we return false return false; } vtkSmartPointer mitk::LabelSetImageVtkMapper2D::CreateOutlinePolyData(mitk::BaseRenderer *renderer, vtkImageData *image, int pixelValue) { LocalStorage *localStorage = this->GetLocalStorage(renderer); // get the min and max index values of each direction int *extent = image->GetExtent(); int xMin = extent[0]; int xMax = extent[1]; int yMin = extent[2]; int yMax = extent[3]; int *dims = image->GetDimensions(); // dimensions of the image int line = dims[0]; // how many pixels per line? int x = xMin; // pixel index x int y = yMin; // pixel index y // get the depth for each contour float depth = this->CalculateLayerDepth(renderer); vtkSmartPointer points = vtkSmartPointer::New(); // the points to draw vtkSmartPointer lines = vtkSmartPointer::New(); // the lines to connect the points // We take the pointer to the first pixel of the image auto *currentPixel = static_cast(image->GetScalarPointer()); while (y <= yMax) { // if the current pixel value is set to something if ((currentPixel) && (*currentPixel == pixelValue)) { // check in which direction a line is necessary // a line is added if the neighbor of the current pixel has the value 0 // and if the pixel is located at the edge of the image // if vvvvv not the first line vvvvv if (y > yMin && *(currentPixel - line) != pixelValue) { // x direction - bottom edge of the pixel // add the 2 points vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); // add the line between both points lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the last line vvvvv if (y < yMax && *(currentPixel + line) != pixelValue) { // x direction - top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the first pixel vvvvv if ((x > xMin || y > yMin) && *(currentPixel - 1) != pixelValue) { // y direction - left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the last pixel vvvvv if ((y < yMax || (x < xMax)) && *(currentPixel + 1) != pixelValue) { // y direction - right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } /* now consider pixels at the edge of the image */ // if vvvvv left edge of image vvvvv if (x == xMin) { // draw left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv right edge of image vvvvv if (x == xMax) { // draw right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv bottom edge of image vvvvv if (y == yMin) { // draw bottom edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv top edge of image vvvvv if (y == yMax) { // draw top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } } // end if currentpixel is set x++; if (x > xMax) { // reached end of line x = xMin; y++; } // Increase the pointer-position to the next pixel. // This is safe, as the while-loop and the x-reset logic above makes // sure we do not exceed the bounds of the image currentPixel++; } // end of while // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); return polyData; } void mitk::LabelSetImageVtkMapper2D::ApplyColor(mitk::BaseRenderer *renderer, const mitk::Color &color) { LocalStorage *localStorage = this->GetLocalStorage(renderer); localStorage->m_OutlineActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); localStorage->m_OutlineShadowActor->GetProperty()->SetColor(0, 0, 0); } void mitk::LabelSetImageVtkMapper2D::ApplyOpacity(mitk::BaseRenderer *renderer, int layer) { LocalStorage *localStorage = this->GetLocalStorage(renderer); float opacity = 1.0f; this->GetDataNode()->GetOpacity(opacity, renderer, "opacity"); localStorage->m_LayerActorVector[layer]->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineActor->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineShadowActor->GetProperty()->SetOpacity(opacity); } void mitk::LabelSetImageVtkMapper2D::ApplyLookuptable(mitk::BaseRenderer *renderer, int layer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); auto *input = dynamic_cast(this->GetDataNode()->GetData()); localStorage->m_LevelWindowFilterVector[layer]->SetLookupTable( input->GetLabelSet(layer)->GetLookupTable()->GetVtkLookupTable()); } void mitk::LabelSetImageVtkMapper2D::Update(mitk::BaseRenderer *renderer) { bool visible = true; const DataNode *node = this->GetDataNode(); node->GetVisibility(visible, renderer, "visible"); if (!visible) return; auto *image = dynamic_cast(node->GetData()); if (image == nullptr || image->IsInitialized() == false) return; // Calculate time step of the image data for the specified renderer (integer value) this->CalculateTimeStep(renderer); // Check if time step is valid const TimeGeometry *dataTimeGeometry = image->GetTimeGeometry(); if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || (!dataTimeGeometry->IsValidTimeStep(this->GetTimestep()))) { return; } image->UpdateOutputInformation(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // check if something important has changed and we need to re-render if ((localStorage->m_LastDataUpdateTime < image->GetMTime()) || (localStorage->m_LastDataUpdateTime < image->GetPipelineMTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime())) { this->GenerateDataForRenderer(renderer); localStorage->m_LastDataUpdateTime.Modified(); } else if ((localStorage->m_LastPropertyUpdateTime < node->GetPropertyList()->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList(renderer)->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < image->GetPropertyList()->GetMTime())) { this->GenerateDataForRenderer(renderer); localStorage->m_LastPropertyUpdateTime.Modified(); } } // set the two points defining the textured plane according to the dimension and spacing void mitk::LabelSetImageVtkMapper2D::GeneratePlane(mitk::BaseRenderer *renderer, double planeBounds[6]) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); float depth = this->CalculateLayerDepth(renderer); // Set the origin to (xMin; yMin; depth) of the plane. This is necessary for obtaining the correct // plane size in crosshair rotation and swivel mode. localStorage->m_Plane->SetOrigin(planeBounds[0], planeBounds[2], depth); // These two points define the axes of the plane in combination with the origin. // Point 1 is the x-axis and point 2 the y-axis. // Each plane is transformed according to the view (axial, coronal and saggital) afterwards. localStorage->m_Plane->SetPoint1(planeBounds[1], planeBounds[2], depth); // P1: (xMax, yMin, depth) localStorage->m_Plane->SetPoint2(planeBounds[0], planeBounds[3], depth); // P2: (xMin, yMax, depth) } float mitk::LabelSetImageVtkMapper2D::CalculateLayerDepth(mitk::BaseRenderer *renderer) { // get the clipping range to check how deep into z direction we can render images double maxRange = renderer->GetVtkRenderer()->GetActiveCamera()->GetClippingRange()[1]; // Due to a VTK bug, we cannot use the whole clipping range. /100 is empirically determined float depth = -maxRange * 0.01; // divide by 100 int layer = 0; GetDataNode()->GetIntProperty("layer", layer, renderer); // add the layer property for each image to render images with a higher layer on top of the others depth += layer * 10; //*10: keep some room for each image (e.g. for ODFs in between) if (depth > 0.0f) { depth = 0.0f; MITK_WARN << "Layer value exceeds clipping range. Set to minimum instead."; } return depth; } void mitk::LabelSetImageVtkMapper2D::TransformActor(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // get the transformation matrix of the reslicer in order to render the slice as axial, coronal or saggital vtkSmartPointer trans = vtkSmartPointer::New(); vtkSmartPointer matrix = localStorage->m_ReslicerVector[0]->GetResliceAxes(); // same for all layers trans->SetMatrix(matrix); for (int lidx = 0; lidx < localStorage->m_NumberOfLayers; ++lidx) { // transform the plane/contour (the actual actor) to the corresponding view (axial, coronal or saggital) localStorage->m_LayerActorVector[lidx]->SetUserTransform(trans); // transform the origin to center based coordinates, because MITK is center based. localStorage->m_LayerActorVector[lidx]->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); } // same for outline actor localStorage->m_OutlineActor->SetUserTransform(trans); localStorage->m_OutlineActor->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); // same for outline shadow actor localStorage->m_OutlineShadowActor->SetUserTransform(trans); localStorage->m_OutlineShadowActor->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); } void mitk::LabelSetImageVtkMapper2D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { // add/replace the following properties node->SetProperty("opacity", FloatProperty::New(1.0f), renderer); node->SetProperty("binary", BoolProperty::New(false), renderer); mitk::RenderingModeProperty::Pointer renderingModeProperty = mitk::RenderingModeProperty::New(RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); node->SetProperty("Image Rendering.Mode", renderingModeProperty, renderer); mitk::LevelWindow levelwindow(32767.5, 65535); mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(levelwindow); levWinProp->SetLevelWindow(levelwindow); node->SetProperty("levelwindow", levWinProp, renderer); node->SetProperty("labelset.contour.active", BoolProperty::New(true), renderer); node->SetProperty("labelset.contour.width", FloatProperty::New(2.0), renderer); Superclass::SetDefaultProperties(node, renderer, overwrite); } mitk::LabelSetImageVtkMapper2D::LocalStorage::~LocalStorage() { } mitk::LabelSetImageVtkMapper2D::LocalStorage::LocalStorage() { // Do as much actions as possible in here to avoid double executions. m_Plane = vtkSmartPointer::New(); m_Actors = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_EmptyPolyData = vtkSmartPointer::New(); m_OutlineActor = vtkSmartPointer::New(); m_OutlineMapper = vtkSmartPointer::New(); m_OutlineShadowActor = vtkSmartPointer::New(); m_NumberOfLayers = 0; m_mmPerPixel = nullptr; m_OutlineActor->SetMapper(m_OutlineMapper); m_OutlineShadowActor->SetMapper(m_OutlineMapper); m_OutlineActor->SetVisibility(false); m_OutlineShadowActor->SetVisibility(false); } diff --git a/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp b/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp index c9057a5bc8..66828da564 100644 --- a/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp +++ b/Modules/Pharmacokinetics/cmdapps/MRSignal2ConcentrationMiniApp.cpp @@ -1,287 +1,305 @@ /*============================================================================ 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. ============================================================================*/ // std includes #include // itk includes #include "itksys/SystemTools.hxx" // CTK includes #include "mitkCommandLineParser.h" // MITK includes #include #include #include #include #include std::string inFilename; std::string outFileName; mitk::Image::Pointer image; bool verbose(false); bool t1_absolute(false); bool t1_relative(false); bool t1_flash(false); bool t2(false); float k(1.0); float te(0); float rec_time(0); float relaxivity(0); float rel_time(0); void setupParser(mitkCommandLineParser& parser) { // set general information about your MiniApp parser.setCategory("Dynamic Data Analysis Tools"); parser.setTitle("MR Signal to Concentration Converter"); parser.setDescription("MiniApp that allows to convert a T1 or T2 signal image into a concentration image for perfusion analysis."); parser.setContributor("DKFZ MIC"); //! [create parser] //! [add arguments] // how should arguments be prefixed parser.setArgumentPrefix("--", "-"); // add each argument, unless specified otherwise each argument is optional // see mitkCommandLineParser::addArgument for more information parser.beginGroup("Required I/O parameters"); parser.addArgument( "input", "i", mitkCommandLineParser::File, "Input file", "input 3D+t image file", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::File, "Output file", "where to save the output concentration image.", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.endGroup(); parser.beginGroup("Conversion parameters"); parser.addArgument( "t1-absolute", "", mitkCommandLineParser::Bool, "T1 absolute signal enhancement", "Activate conversion for T1 absolute signal enhancement."); parser.addArgument( "t1-relative", "", mitkCommandLineParser::Bool, "T1 relative signal enhancement", "Activate conversion for T1 relative signal enhancement."); parser.addArgument( "t1-flash", "", mitkCommandLineParser::Bool, "T1 turbo flash", "Activate specific conversion for T1 turbo flash sequences."); parser.addArgument( "t2", "", mitkCommandLineParser::Bool, "T2 signal conversion", "Activate conversion for T2 signal enhancement to concentration."); parser.addArgument( "k", "k", mitkCommandLineParser::Float, "Conversion factor k", "Needed for the following conversion modes: T1-absolute, T1-relative, T2. Default value is 1.", us::Any(1)); parser.addArgument( "recovery-time", "", mitkCommandLineParser::Float, "Recovery time", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "relaxivity", "", mitkCommandLineParser::Float, "Relaxivity", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "relaxation-time", "", mitkCommandLineParser::Float, "Relaxation time", "Needed for the following conversion modes: T1-flash."); parser.addArgument( "te", "", mitkCommandLineParser::Float, "Echo time TE", "Needed for the following conversion modes: T2.", us::Any(1)); parser.beginGroup("Optional parameters"); parser.addArgument( "verbose", "v", mitkCommandLineParser::Bool, "Verbose Output", "Whether to produce verbose output"); parser.addArgument("help", "h", mitkCommandLineParser::Bool, "Help:", "Show this help text"); parser.endGroup(); //! [add arguments] } bool configureApplicationSettings(std::map parsedArgs) { if (parsedArgs.size() == 0) return false; inFilename = us::any_cast(parsedArgs["input"]); outFileName = us::any_cast(parsedArgs["output"]); verbose = false; if (parsedArgs.count("verbose")) { verbose = us::any_cast(parsedArgs["verbose"]); } t1_absolute = false; if (parsedArgs.count("t1-absolute")) { t1_absolute = us::any_cast(parsedArgs["t1-absolute"]); } t1_relative = false; if (parsedArgs.count("t1-relative")) { t1_relative = us::any_cast(parsedArgs["t1-relative"]); } t1_flash = false; if (parsedArgs.count("t1-flash")) { t1_flash = us::any_cast(parsedArgs["t1-flash"]); } t2 = false; if (parsedArgs.count("t2")) { t2 = us::any_cast(parsedArgs["t2"]); } k = 0.0; if (parsedArgs.count("k")) { k = us::any_cast(parsedArgs["k"]); } relaxivity = 0.0; if (parsedArgs.count("relaxivity")) { relaxivity = us::any_cast(parsedArgs["relaxivity"]); } rec_time = 0.0; if (parsedArgs.count("recovery-time")) { rec_time = us::any_cast(parsedArgs["recovery-time"]); } rel_time = 0.0; if (parsedArgs.count("relaxation-time")) { rel_time = us::any_cast(parsedArgs["relaxation-time"]); } te = 0.0; if (parsedArgs.count("te")) { te = us::any_cast(parsedArgs["te"]); } //consistency checks int modeCount = 0; if (t1_absolute) ++modeCount; if (t1_flash) ++modeCount; if (t1_relative) ++modeCount; if (t2) ++modeCount; if (modeCount==0) { mitkThrow() << "Invalid program call. Please select the type of conversion."; } if (modeCount >1) { mitkThrow() << "Invalid program call. Please select only ONE type of conversion."; } if (!k && (t2 || t1_absolute || t1_relative)) { mitkThrow() << "Invalid program call. Please set 'k', if you use t1-absolute, t1-relative or t2."; } if (!te && t2) { mitkThrow() << "Invalid program call. Please set 'te', if you use t2 mode."; } if ((!rec_time||!rel_time||!relaxivity) && t1_flash) { mitkThrow() << "Invalid program call. Please set 'recovery-time', 'relaxation-time' and 'relaxivity', if you use t1-flash mode."; } return true; } void doConversion() { mitk::ConcentrationCurveGenerator::Pointer concentrationGen = mitk::ConcentrationCurveGenerator::New(); concentrationGen->SetDynamicImage(image); concentrationGen->SetisTurboFlashSequence(t1_flash); concentrationGen->SetAbsoluteSignalEnhancement(t1_absolute); concentrationGen->SetRelativeSignalEnhancement(t1_relative); concentrationGen->SetisT2weightedImage(t2); if (t1_flash) { concentrationGen->SetRecoveryTime(rec_time); concentrationGen->SetRelaxationTime(rel_time); concentrationGen->SetRelaxivity(relaxivity); } else if (t2) { concentrationGen->SetT2Factor(k); concentrationGen->SetT2EchoTime(te); } else { concentrationGen->SetFactor(k); } mitk::Image::Pointer concentrationImage = concentrationGen->GetConvertedImage(); mitk::IOUtil::Save(concentrationImage, outFileName); std::cout << "Store result: " << outFileName << std::endl; } int main(int argc, char* argv[]) { mitkCommandLineParser parser; setupParser(parser); const std::map& parsedArgs = parser.parseArguments(argc, argv); - if (!configureApplicationSettings(parsedArgs)) + try { + if (!configureApplicationSettings(parsedArgs)) + { return EXIT_FAILURE; - }; + } + } + catch (const itk::ExceptionObject& e) + { + MITK_ERROR << e.what(); + return EXIT_FAILURE; + } + catch (const std::exception& e) + { + MITK_ERROR << e.what(); + return EXIT_FAILURE; + } + catch (...) + { + MITK_ERROR << "Unexpected error encountered when parsing the CLI arguments."; + return EXIT_FAILURE; + } mitk::PreferenceListReaderOptionsFunctor readerFilterFunctor = mitk::PreferenceListReaderOptionsFunctor({ "MITK DICOM Reader v2 (autoselect)" }, { "" }); // Show a help message if (parsedArgs.count("help") || parsedArgs.count("h")) { std::cout << parser.helpText(); return EXIT_SUCCESS; } //! [do processing] try { image = mitk::IOUtil::Load(inFilename, &readerFilterFunctor); std::cout << "Input: " << inFilename << std::endl; doConversion(); std::cout << "Processing finished." << std::endl; return EXIT_SUCCESS; } catch (const itk::ExceptionObject& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { MITK_ERROR << "Unexpected error encountered."; return EXIT_FAILURE; } } diff --git a/Modules/QtWidgets/src/QmitkLevelWindowWidgetContextMenu.cpp b/Modules/QtWidgets/src/QmitkLevelWindowWidgetContextMenu.cpp index dfd7b828f6..dd290731b4 100644 --- a/Modules/QtWidgets/src/QmitkLevelWindowWidgetContextMenu.cpp +++ b/Modules/QtWidgets/src/QmitkLevelWindowWidgetContextMenu.cpp @@ -1,301 +1,304 @@ /*============================================================================ 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 core #include // mitk qt widgets #include "QmitkLevelWindowPresetDefinitionDialog.h" #include "QmitkLevelWindowRangeChangeDialog.h" // qt #include QmitkLevelWindowWidgetContextMenu::QmitkLevelWindowWidgetContextMenu(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f) { m_LevelWindowPreset = mitk::LevelWindowPreset::New(); m_LevelWindowPreset->LoadPreset(); } QmitkLevelWindowWidgetContextMenu::~QmitkLevelWindowWidgetContextMenu() { m_LevelWindowPreset->Delete(); } void QmitkLevelWindowWidgetContextMenu::OnSetPreset(const QAction *presetAction) { QString item = presetAction->text(); if (!(presetAction == m_PresetAction)) { double dlevel = m_LevelWindowPreset->getLevel(item.toStdString()); double dwindow = m_LevelWindowPreset->getWindow(item.toStdString()); if ((dlevel + dwindow / 2) > m_LevelWindow.GetRangeMax()) { double lowerBound = (dlevel - dwindow / 2); if (!(lowerBound > m_LevelWindow.GetRangeMax())) { dwindow = m_LevelWindow.GetRangeMax() - lowerBound; dlevel = lowerBound + dwindow / 2; } else { dlevel = m_LevelWindow.GetRangeMax() - 1; dwindow = 2; } } else if ((dlevel - dwindow / 2) < m_LevelWindow.GetRangeMin()) { double upperBound = (dlevel + dwindow / 2); if (!(upperBound < m_LevelWindow.GetRangeMin())) { dwindow = m_LevelWindow.GetRangeMin() + upperBound; dlevel = upperBound - dwindow / 2; } else { dlevel = m_LevelWindow.GetRangeMin() + 1; dwindow = 2; } } m_LevelWindow.SetLevelWindow(dlevel, dwindow); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkLevelWindowWidgetContextMenu::SetLevelWindowManager(mitk::LevelWindowManager *levelWindowManager) { m_Manager = levelWindowManager; } void QmitkLevelWindowWidgetContextMenu::OnAddPreset() { QmitkLevelWindowPresetDefinitionDialog addPreset(this); addPreset.setPresets(m_LevelWindowPreset->getLevelPresets(), m_LevelWindowPreset->getWindowPresets(), QString::number((int)m_LevelWindow.GetLevel()), QString::number((int)m_LevelWindow.GetWindow())); if (addPreset.exec()) { m_LevelWindowPreset->newPresets(addPreset.getLevelPresets(), addPreset.getWindowPresets()); } } void QmitkLevelWindowWidgetContextMenu::OnSetFixed() { m_LevelWindow.SetFixed(!m_LevelWindow.GetFixed()); m_Manager->SetLevelWindow(m_LevelWindow); } void QmitkLevelWindowWidgetContextMenu::OnUseAllGreyvaluesFromImage() { m_LevelWindow.SetToImageRange(m_Manager->GetCurrentImage()); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnUseOptimizedLevelWindow() { m_LevelWindow.SetAuto(m_Manager->GetCurrentImage(), false, false); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnSetDefaultLevelWindow() { m_LevelWindow.ResetDefaultLevelWindow(); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnSetMaximumWindow() { m_LevelWindow.SetToMaxWindowSize(); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnSetDefaultScaleRange() { m_LevelWindow.ResetDefaultRangeMinMax(); m_LevelWindow.SetLevelWindow(m_LevelWindow.GetLevel(), m_LevelWindow.GetWindow()); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkLevelWindowWidgetContextMenu::OnChangeScaleRange() { QmitkLevelWindowRangeChangeDialog changeRange(this); changeRange.setLowerLimit((mitk::ScalarType)m_LevelWindow.GetRangeMin()); changeRange.setUpperLimit((mitk::ScalarType)m_LevelWindow.GetRangeMax()); if (changeRange.exec()) { m_LevelWindow.SetRangeMinMax(changeRange.getLowerLimit(), changeRange.getUpperLimit()); m_LevelWindow.SetLevelWindow(m_LevelWindow.GetLevel(), m_LevelWindow.GetWindow()); m_Manager->SetLevelWindow(m_LevelWindow); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkLevelWindowWidgetContextMenu::OnSetImage(QAction *imageAction) { if (imageAction == m_AutoTopmostAction) { if (m_Manager->IsAutoTopMost() == false) { m_Manager->SetAutoTopMostImage(true); + m_SelectedImagesAction->setChecked(false); } else { m_Manager->SetAutoTopMostImage(false); } } else if(imageAction == m_SelectedImagesAction) { if (m_Manager->IsSelectedImages() == false) { m_Manager->SetSelectedImages(true); + m_AutoTopmostAction->setChecked(false); } else { m_Manager->SetSelectedImages(false); } } else { + m_AutoTopmostAction->setChecked(false); + m_SelectedImagesAction->setChecked(false); m_Manager->SetLevelWindowProperty(m_Images.at(imageAction)); } } void QmitkLevelWindowWidgetContextMenu::GetContextMenu(QMenu *contextMenu) { if (nullptr == contextMenu) { return; } try { m_LevelWindow = m_Manager->GetLevelWindow(); QAction *sliderFixed = contextMenu->addAction(tr("Set slider fixed"), this, &QmitkLevelWindowWidgetContextMenu::OnSetFixed); sliderFixed->setCheckable(true); sliderFixed->setChecked(m_LevelWindow.IsFixed()); contextMenu->addSeparator(); contextMenu->addAction(tr("Use whole image grey values"), this, &QmitkLevelWindowWidgetContextMenu::OnUseAllGreyvaluesFromImage); contextMenu->addAction(tr("Use optimized level-window"), this, &QmitkLevelWindowWidgetContextMenu::OnUseOptimizedLevelWindow); contextMenu->addSeparator(); contextMenu->addAction(tr("Set maximum window"), this, &QmitkLevelWindowWidgetContextMenu::OnSetMaximumWindow); contextMenu->addAction(tr("Default level-window"), this, &QmitkLevelWindowWidgetContextMenu::OnSetDefaultLevelWindow); contextMenu->addSeparator(); contextMenu->addAction(tr("Change scale range"), this, &QmitkLevelWindowWidgetContextMenu::OnChangeScaleRange); contextMenu->addAction(tr("Default scale range"), this, &QmitkLevelWindowWidgetContextMenu::OnSetDefaultScaleRange); contextMenu->addSeparator(); m_PresetSubmenu = new QMenu(this); m_PresetSubmenu->setTitle("Presets"); m_PresetAction = m_PresetSubmenu->addAction(tr("Preset definition"), this, &QmitkLevelWindowWidgetContextMenu::OnAddPreset); m_PresetSubmenu->addSeparator(); std::map preset = m_LevelWindowPreset->getLevelPresets(); for (auto iter = preset.begin(); iter != preset.end(); iter++) { QString item = ((*iter).first.c_str()); m_PresetSubmenu->addAction(item); } connect(m_PresetSubmenu, &QMenu::triggered, this, &QmitkLevelWindowWidgetContextMenu::OnSetPreset); contextMenu->addMenu(m_PresetSubmenu); contextMenu->addSeparator(); m_ImageSubmenu = new QMenu(this); m_ImageSubmenu->setTitle("Images"); // add action for "auto topmost image" action m_AutoTopmostAction = m_ImageSubmenu->addAction(tr("Set topmost image")); m_AutoTopmostAction->setCheckable(true); if (m_Manager->IsAutoTopMost()) { m_AutoTopmostAction->setChecked(true); } // add action for "selected images" action - m_ImageSubmenu->addSeparator(); m_SelectedImagesAction = m_ImageSubmenu->addAction(tr("Use selected images")); m_SelectedImagesAction->setCheckable(true); if (m_Manager->IsSelectedImages()) { m_SelectedImagesAction->setChecked(true); } // add action for individual images m_ImageSubmenu->addSeparator(); mitk::DataStorage::SetOfObjects::ConstPointer allObjects = m_Manager->GetRelevantNodes(); for (mitk::DataStorage::SetOfObjects::ConstIterator objectIter = allObjects->Begin(); objectIter != allObjects->End(); ++objectIter) { mitk::DataNode *node = objectIter->Value(); if (nullptr == node) { continue; } bool isHelperObject = false; node->GetBoolProperty("helper object", isHelperObject); if (isHelperObject) { continue; } if (!node->IsVisible(nullptr)) { continue; } mitk::LevelWindowProperty::Pointer levelWindowProperty = dynamic_cast(node->GetProperty("levelwindow")); if (levelWindowProperty.IsNotNull()) { std::string name; node->GetName(name); QString item = name.c_str(); QAction *id = m_ImageSubmenu->addAction(item); id->setCheckable(true); m_Images[id] = levelWindowProperty; if (levelWindowProperty == m_Manager->GetLevelWindowProperty()) { id->setChecked(true); } } } connect(m_ImageSubmenu, &QMenu::triggered, this, &QmitkLevelWindowWidgetContextMenu::OnSetImage); contextMenu->addMenu(m_ImageSubmenu); contextMenu->exec(QCursor::pos()); } catch (...) { } } void QmitkLevelWindowWidgetContextMenu::GetContextMenu() { auto contextMenu = new QMenu(this); GetContextMenu(contextMenu); delete contextMenu; } diff --git a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp index 3bc01722ad..ea074a7f31 100644 --- a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp +++ b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp @@ -1,135 +1,157 @@ /*============================================================================ 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 "mitkShapeBasedInterpolationAlgorithm.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include #include #include #include #include +#include + mitk::Image::Pointer mitk::ShapeBasedInterpolationAlgorithm::Interpolate( Image::ConstPointer lowerSlice, unsigned int lowerSliceIndex, Image::ConstPointer upperSlice, unsigned int upperSliceIndex, unsigned int requestedIndex, unsigned int /*sliceDimension*/, // commented variables are not used Image::Pointer resultImage, unsigned int /*timeStep*/, Image::ConstPointer /*referenceImage*/) { - mitk::Image::Pointer lowerDistanceImage = mitk::Image::New(); - AccessFixedDimensionByItk_1(lowerSlice, ComputeDistanceMap, 2, lowerDistanceImage); - - mitk::Image::Pointer upperDistanceImage = mitk::Image::New(); - AccessFixedDimensionByItk_1(upperSlice, ComputeDistanceMap, 2, upperDistanceImage); + auto lowerDistanceImage = this->ComputeDistanceMap(lowerSliceIndex, lowerSlice); + auto upperDistanceImage = this->ComputeDistanceMap(upperSliceIndex, upperSlice); // calculate where the current slice is in comparison to the lower and upper neighboring slices float ratio = (float)(requestedIndex - lowerSliceIndex) / (float)(upperSliceIndex - lowerSliceIndex); - AccessFixedDimensionByItk_3( - resultImage, InterpolateIntermediateSlice, 2, upperDistanceImage, lowerDistanceImage, ratio); + AccessFixedDimensionByItk_3(resultImage, InterpolateIntermediateSlice, 2, upperDistanceImage, lowerDistanceImage, ratio); return resultImage; } +mitk::Image::Pointer mitk::ShapeBasedInterpolationAlgorithm::ComputeDistanceMap(unsigned int sliceIndex, Image::ConstPointer slice) +{ + static const auto MAX_CACHE_SIZE = 2 * std::thread::hardware_concurrency(); + + { + std::lock_guard lock(m_DistanceImageCacheMutex); + + if (0 != m_DistanceImageCache.count(sliceIndex)) + return m_DistanceImageCache[sliceIndex]; + + if (MAX_CACHE_SIZE < m_DistanceImageCache.size()) + m_DistanceImageCache.clear(); + } + + mitk::Image::Pointer distanceImage; + AccessFixedDimensionByItk_1(slice, ComputeDistanceMap, 2, distanceImage); + + std::lock_guard lock(m_DistanceImageCacheMutex); + + m_DistanceImageCache[sliceIndex] = distanceImage; + + return distanceImage; +} + template void mitk::ShapeBasedInterpolationAlgorithm::ComputeDistanceMap(const itk::Image *binaryImage, mitk::Image::Pointer &result) { typedef itk::Image DistanceFilterInputImageType; typedef itk::FastChamferDistanceImageFilter DistanceFilterType; typedef itk::IsoContourDistanceImageFilter IsoContourType; typedef itk::InvertIntensityImageFilter InvertIntensityImageFilterType; typedef itk::SubtractImageFilter SubtractImageFilterType; typename DistanceFilterType::Pointer distanceFilter = DistanceFilterType::New(); typename DistanceFilterType::Pointer distanceFilterInverted = DistanceFilterType::New(); typename IsoContourType::Pointer isoContourFilter = IsoContourType::New(); typename IsoContourType::Pointer isoContourFilterInverted = IsoContourType::New(); typename InvertIntensityImageFilterType::Pointer invertFilter = InvertIntensityImageFilterType::New(); typename SubtractImageFilterType::Pointer subtractImageFilter = SubtractImageFilterType::New(); // arbitrary maximum distance int maximumDistance = 100; // this assumes the image contains only 1 and 0 invertFilter->SetInput(binaryImage); invertFilter->SetMaximum(1); // do the processing on the image and the inverted image to get inside and outside distance isoContourFilter->SetInput(binaryImage); isoContourFilter->SetFarValue(maximumDistance + 1); isoContourFilter->SetLevelSetValue(0); isoContourFilterInverted->SetInput(invertFilter->GetOutput()); isoContourFilterInverted->SetFarValue(maximumDistance + 1); isoContourFilterInverted->SetLevelSetValue(0); distanceFilter->SetInput(isoContourFilter->GetOutput()); distanceFilter->SetMaximumDistance(maximumDistance); distanceFilterInverted->SetInput(isoContourFilterInverted->GetOutput()); distanceFilterInverted->SetMaximumDistance(maximumDistance); // inside distance should be negative, outside distance positive subtractImageFilter->SetInput2(distanceFilter->GetOutput()); subtractImageFilter->SetInput1(distanceFilterInverted->GetOutput()); subtractImageFilter->Update(); result = mitk::GrabItkImageMemory(subtractImageFilter->GetOutput()); } template void mitk::ShapeBasedInterpolationAlgorithm::InterpolateIntermediateSlice(itk::Image *result, const mitk::Image::Pointer &lower, const mitk::Image::Pointer &upper, float ratio) { typename DistanceFilterImageType::Pointer lowerITK = DistanceFilterImageType::New(); typename DistanceFilterImageType::Pointer upperITK = DistanceFilterImageType::New(); CastToItkImage(lower, lowerITK); CastToItkImage(upper, upperITK); itk::ImageRegionConstIteratorWithIndex lowerIter(lowerITK, lowerITK->GetLargestPossibleRegion()); lowerIter.GoToBegin(); if (!lowerITK->GetLargestPossibleRegion().IsInside(upperITK->GetLargestPossibleRegion()) || !lowerITK->GetLargestPossibleRegion().IsInside(result->GetLargestPossibleRegion())) { // TODO Exception etc. MITK_ERROR << "The regions of the slices for the 2D interpolation are not equally sized!"; return; } float weight[2] = {1.0f - ratio, ratio}; while (!lowerIter.IsAtEnd()) { typename DistanceFilterImageType::PixelType lowerPixelVal = lowerIter.Get(); typename DistanceFilterImageType::PixelType upperPixelVal = upperITK->GetPixel(lowerIter.GetIndex()); typename DistanceFilterImageType::PixelType intermediatePixelVal = (weight[0] * upperPixelVal + weight[1] * lowerPixelVal > 0 ? 0 : 1); result->SetPixel(lowerIter.GetIndex(), static_cast(intermediatePixelVal)); ++lowerIter; } } diff --git a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.h b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.h index 934271b89b..1ba28195f6 100644 --- a/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.h +++ b/Modules/Segmentation/Algorithms/mitkShapeBasedInterpolationAlgorithm.h @@ -1,65 +1,73 @@ /*============================================================================ 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 mitkShapeBasedInterpolationAlgorithm_h_Included #define mitkShapeBasedInterpolationAlgorithm_h_Included #include "mitkSegmentationInterpolationAlgorithm.h" #include +#include +#include + namespace mitk { /** * \brief Shape-based binary image interpolation. * * This class uses legacy code from ipSegmentation to implement * the shape-based interpolation algorithm described in * * G.T. Herman, J. Zheng, C.A. Bucholtz: "Shape-based interpolation" * IEEE Computer Graphics & Applications, pp. 69-79,May 1992 * * Last contributor: * $Author:$ */ class MITKSEGMENTATION_EXPORT ShapeBasedInterpolationAlgorithm : public SegmentationInterpolationAlgorithm { public: mitkClassMacro(ShapeBasedInterpolationAlgorithm, SegmentationInterpolationAlgorithm); itkFactorylessNewMacro(Self); itkCloneMacro(Self); Image::Pointer Interpolate(Image::ConstPointer lowerSlice, unsigned int lowerSliceIndex, Image::ConstPointer upperSlice, unsigned int upperSliceIndex, unsigned int requestedIndex, unsigned int sliceDimension, Image::Pointer resultImage, unsigned int timeStep, Image::ConstPointer referenceImage) override; private: typedef itk::Image DistanceFilterImageType; template void ComputeDistanceMap(const itk::Image *, mitk::Image::Pointer &result); + Image::Pointer ComputeDistanceMap(unsigned int sliceIndex, Image::ConstPointer slice); + template void InterpolateIntermediateSlice(itk::Image *result, const mitk::Image::Pointer &lowerDistanceImage, const mitk::Image::Pointer &upperDistanceImage, float ratio); + + std::map m_DistanceImageCache; + std::mutex m_DistanceImageCacheMutex; }; } // namespace #endif diff --git a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp index 7306f74e74..c016754e06 100644 --- a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp +++ b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp @@ -1,586 +1,602 @@ /*============================================================================ 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 "mitkSegmentationInterpolationController.h" #include "mitkImageCast.h" #include "mitkImageReadAccessor.h" #include "mitkImageTimeSelector.h" #include #include //#include -#include "mitkShapeBasedInterpolationAlgorithm.h" - #include #include #include +#include + mitk::SegmentationInterpolationController::InterpolatorMapType mitk::SegmentationInterpolationController::s_InterpolatorForImage; // static member initialization mitk::SegmentationInterpolationController *mitk::SegmentationInterpolationController::InterpolatorForImage( const Image *image) { auto iter = s_InterpolatorForImage.find(image); if (iter != s_InterpolatorForImage.end()) { return iter->second; } else { return nullptr; } } mitk::SegmentationInterpolationController::SegmentationInterpolationController() - : m_BlockModified(false), m_2DInterpolationActivated(false) + : m_BlockModified(false), m_2DInterpolationActivated(false), m_EnableSliceImageCache(false) { } void mitk::SegmentationInterpolationController::Activate2DInterpolation(bool status) { m_2DInterpolationActivated = status; } mitk::SegmentationInterpolationController *mitk::SegmentationInterpolationController::GetInstance() { static mitk::SegmentationInterpolationController::Pointer m_Instance; if (m_Instance.IsNull()) { m_Instance = SegmentationInterpolationController::New(); } return m_Instance; } mitk::SegmentationInterpolationController::~SegmentationInterpolationController() { // remove this from the list of interpolators for (auto iter = s_InterpolatorForImage.begin(); iter != s_InterpolatorForImage.end(); ++iter) { if (iter->second == this) { s_InterpolatorForImage.erase(iter); break; } } } void mitk::SegmentationInterpolationController::OnImageModified(const itk::EventObject &) { if (!m_BlockModified && m_Segmentation.IsNotNull() && m_2DInterpolationActivated) { SetSegmentationVolume(m_Segmentation); } } void mitk::SegmentationInterpolationController::BlockModified(bool block) { m_BlockModified = block; } void mitk::SegmentationInterpolationController::SetSegmentationVolume(const Image *segmentation) { // clear old information (remove all time steps m_SegmentationCountInSlice.clear(); // delete this from the list of interpolators auto iter = s_InterpolatorForImage.find(segmentation); if (iter != s_InterpolatorForImage.end()) { s_InterpolatorForImage.erase(iter); } if (!segmentation) return; if (segmentation->GetDimension() > 4 || segmentation->GetDimension() < 3) { itkExceptionMacro("SegmentationInterpolationController needs a 3D-segmentation or 3D+t, not 2D."); } if (m_Segmentation != segmentation) { // observe Modified() event of image itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &SegmentationInterpolationController::OnImageModified); segmentation->AddObserver(itk::ModifiedEvent(), command); } m_Segmentation = segmentation; m_SegmentationCountInSlice.resize(m_Segmentation->GetTimeSteps()); for (unsigned int timeStep = 0; timeStep < m_Segmentation->GetTimeSteps(); ++timeStep) { m_SegmentationCountInSlice[timeStep].resize(3); for (unsigned int dim = 0; dim < 3; ++dim) { m_SegmentationCountInSlice[timeStep][dim].clear(); m_SegmentationCountInSlice[timeStep][dim].resize(m_Segmentation->GetDimension(dim)); m_SegmentationCountInSlice[timeStep][dim].assign(m_Segmentation->GetDimension(dim), 0); } } s_InterpolatorForImage.insert(std::make_pair(m_Segmentation, this)); // for all timesteps // scan whole image for (unsigned int timeStep = 0; timeStep < m_Segmentation->GetTimeSteps(); ++timeStep) { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->UpdateLargestPossibleRegion(); Image::Pointer segmentation3D = timeSelector->GetOutput(); AccessFixedDimensionByItk_2(segmentation3D, ScanWholeVolume, 3, m_Segmentation, timeStep); } // PrintStatus(); SetReferenceVolume(m_ReferenceImage); Modified(); } void mitk::SegmentationInterpolationController::SetReferenceVolume(const Image *referenceImage) { m_ReferenceImage = referenceImage; if (m_ReferenceImage.IsNull()) return; // no image set - ignore it then assert(m_Segmentation.IsNotNull()); // should never happen // ensure the reference image has the same dimensionality and extents as the segmentation image if (m_ReferenceImage.IsNull() || m_Segmentation.IsNull() || m_ReferenceImage->GetDimension() != m_Segmentation->GetDimension() || m_ReferenceImage->GetPixelType().GetNumberOfComponents() != 1 || m_Segmentation->GetPixelType().GetNumberOfComponents() != 1) { MITK_WARN << "Segmentation image has different image characteristics than reference image." << std::endl; m_ReferenceImage = nullptr; return; } for (unsigned int dim = 0; dim < m_Segmentation->GetDimension(); ++dim) if (m_ReferenceImage->GetDimension(dim) != m_Segmentation->GetDimension(dim)) { MITK_WARN << "original patient image does not match segmentation (different extent in dimension " << dim << "), ignoring patient image" << std::endl; m_ReferenceImage = nullptr; return; } } void mitk::SegmentationInterpolationController::SetChangedVolume(const Image *sliceDiff, unsigned int timeStep) { if (!sliceDiff) return; if (sliceDiff->GetDimension() != 3) return; AccessFixedDimensionByItk_1(sliceDiff, ScanChangedVolume, 3, timeStep); // PrintStatus(); Modified(); } void mitk::SegmentationInterpolationController::SetChangedSlice(const Image *sliceDiff, unsigned int sliceDimension, unsigned int sliceIndex, unsigned int timeStep) { if (!sliceDiff) return; if (sliceDimension > 2) return; if (timeStep >= m_SegmentationCountInSlice.size()) return; if (sliceIndex >= m_SegmentationCountInSlice[timeStep][sliceDimension].size()) return; unsigned int dim0(0); unsigned int dim1(1); // determine the other two dimensions switch (sliceDimension) { default: case 2: dim0 = 0; dim1 = 1; break; case 1: dim0 = 0; dim1 = 2; break; case 0: dim0 = 1; dim1 = 2; break; } mitk::ImageReadAccessor readAccess(sliceDiff); auto *rawSlice = (unsigned char *)readAccess.GetData(); if (!rawSlice) return; AccessFixedDimensionByItk_1( sliceDiff, ScanChangedSlice, 2, SetChangedSliceOptions(sliceDimension, sliceIndex, dim0, dim1, timeStep, rawSlice)); Modified(); } template void mitk::SegmentationInterpolationController::ScanChangedSlice(const itk::Image *, const SetChangedSliceOptions &options) { auto *pixelData((DATATYPE *)options.pixelData); unsigned int timeStep(options.timeStep); unsigned int sliceDimension(options.sliceDimension); unsigned int sliceIndex(options.sliceIndex); if (sliceDimension > 2) return; if (sliceIndex >= m_SegmentationCountInSlice[timeStep][sliceDimension].size()) return; unsigned int dim0(options.dim0); unsigned int dim1(options.dim1); int numberOfPixels(0); // number of pixels in this slice that are not 0 unsigned int dim0max = m_SegmentationCountInSlice[timeStep][dim0].size(); unsigned int dim1max = m_SegmentationCountInSlice[timeStep][dim1].size(); // scan the slice from two directions // and set the flags for the two dimensions of the slice for (unsigned int v = 0; v < dim1max; ++v) { for (unsigned int u = 0; u < dim0max; ++u) { DATATYPE value = *(pixelData + u + v * dim0max); assert((signed)m_SegmentationCountInSlice[timeStep][dim0][u] + (signed)value >= 0); // just for debugging. This must always be true, otherwise some counting is going wrong assert((signed)m_SegmentationCountInSlice[timeStep][dim1][v] + (signed)value >= 0); m_SegmentationCountInSlice[timeStep][dim0][u] = static_cast(m_SegmentationCountInSlice[timeStep][dim0][u] + value); m_SegmentationCountInSlice[timeStep][dim1][v] = static_cast(m_SegmentationCountInSlice[timeStep][dim1][v] + value); numberOfPixels += static_cast(value); } } // flag for the dimension of the slice itself assert((signed)m_SegmentationCountInSlice[timeStep][sliceDimension][sliceIndex] + numberOfPixels >= 0); m_SegmentationCountInSlice[timeStep][sliceDimension][sliceIndex] += numberOfPixels; // MITK_INFO << "scan t=" << timeStep << " from (0,0) to (" << dim0max << "," << dim1max << ") (" << pixelData << "-" // << pixelData+dim0max*dim1max-1 << ") in slice " << sliceIndex << " found " << numberOfPixels << " pixels" << // std::endl; } template void mitk::SegmentationInterpolationController::ScanChangedVolume(const itk::Image *diffImage, unsigned int timeStep) { typedef itk::ImageSliceConstIteratorWithIndex> IteratorType; IteratorType iter(diffImage, diffImage->GetLargestPossibleRegion()); iter.SetFirstDirection(0); iter.SetSecondDirection(1); int numberOfPixels(0); // number of pixels in this slice that are not 0 typename IteratorType::IndexType index; unsigned int x = 0; unsigned int y = 0; unsigned int z = 0; iter.GoToBegin(); while (!iter.IsAtEnd()) { while (!iter.IsAtEndOfSlice()) { while (!iter.IsAtEndOfLine()) { index = iter.GetIndex(); x = index[0]; y = index[1]; z = index[2]; TPixel value = iter.Get(); assert((signed)m_SegmentationCountInSlice[timeStep][0][x] + (signed)value >= 0); // just for debugging. This must always be true, otherwise some counting is going wrong assert((signed)m_SegmentationCountInSlice[timeStep][1][y] + (signed)value >= 0); m_SegmentationCountInSlice[timeStep][0][x] = static_cast(m_SegmentationCountInSlice[timeStep][0][x] + value); m_SegmentationCountInSlice[timeStep][1][y] = static_cast(m_SegmentationCountInSlice[timeStep][1][y] + value); numberOfPixels += static_cast(value); ++iter; } iter.NextLine(); } assert((signed)m_SegmentationCountInSlice[timeStep][2][z] + numberOfPixels >= 0); m_SegmentationCountInSlice[timeStep][2][z] += numberOfPixels; numberOfPixels = 0; iter.NextSlice(); } } template void mitk::SegmentationInterpolationController::ScanWholeVolume(const itk::Image *, const Image *volume, unsigned int timeStep) { if (!volume) return; if (timeStep >= m_SegmentationCountInSlice.size()) return; ImageReadAccessor readAccess(volume, volume->GetVolumeData(timeStep)); for (unsigned int slice = 0; slice < volume->GetDimension(2); ++slice) { const auto *rawVolume = static_cast(readAccess.GetData()); // we again promise not to change anything, we'll just count const DATATYPE *rawSlice = rawVolume + (volume->GetDimension(0) * volume->GetDimension(1) * slice); ScanChangedSlice(nullptr, SetChangedSliceOptions(2, slice, 0, 1, timeStep, rawSlice)); } } void mitk::SegmentationInterpolationController::PrintStatus() { unsigned int timeStep(0); // if needed, put a loop over time steps around everyting, but beware, output will be long MITK_INFO << "Interpolator status (timestep 0): dimensions " << m_SegmentationCountInSlice[timeStep][0].size() << " " << m_SegmentationCountInSlice[timeStep][1].size() << " " << m_SegmentationCountInSlice[timeStep][2].size() << std::endl; MITK_INFO << "Slice 0: " << m_SegmentationCountInSlice[timeStep][2][0] << std::endl; // row "x" for (unsigned int index = 0; index < m_SegmentationCountInSlice[timeStep][0].size(); ++index) { if (m_SegmentationCountInSlice[timeStep][0][index] > 0) MITK_INFO << "O"; else MITK_INFO << "."; } MITK_INFO << std::endl; // rows "y" and "z" (diagonal) for (unsigned int index = 1; index < m_SegmentationCountInSlice[timeStep][1].size(); ++index) { if (m_SegmentationCountInSlice[timeStep][1][index] > 0) MITK_INFO << "O"; else MITK_INFO << "."; if (m_SegmentationCountInSlice[timeStep][2].size() > index) // if we also have a z value here, then print it, too { for (unsigned int indent = 1; indent < index; ++indent) MITK_INFO << " "; if (m_SegmentationCountInSlice[timeStep][2][index] > 0) MITK_INFO << m_SegmentationCountInSlice[timeStep][2][index]; //"O"; else MITK_INFO << "."; } MITK_INFO << std::endl; } // z indices that are larger than the biggest y index for (unsigned int index = m_SegmentationCountInSlice[timeStep][1].size(); index < m_SegmentationCountInSlice[timeStep][2].size(); ++index) { for (unsigned int indent = 0; indent < index; ++indent) MITK_INFO << " "; if (m_SegmentationCountInSlice[timeStep][2][index] > 0) MITK_INFO << m_SegmentationCountInSlice[timeStep][2][index]; //"O"; else MITK_INFO << "."; MITK_INFO << std::endl; } } mitk::Image::Pointer mitk::SegmentationInterpolationController::Interpolate(unsigned int sliceDimension, unsigned int sliceIndex, const mitk::PlaneGeometry *currentPlane, - unsigned int timeStep) + unsigned int timeStep, + ShapeBasedInterpolationAlgorithm::Pointer algorithm) { - if (m_Segmentation.IsNull()) + if (m_Segmentation.IsNull() || nullptr == currentPlane) return nullptr; - if (!currentPlane) - { - return nullptr; - } - if (timeStep >= m_SegmentationCountInSlice.size()) return nullptr; + if (sliceDimension > 2) return nullptr; - unsigned int upperLimit = m_SegmentationCountInSlice[timeStep][sliceDimension].size(); - if (sliceIndex >= upperLimit - 1) - return nullptr; // can't interpolate first and last slice - if (sliceIndex < 1) - return nullptr; + + if (0 == sliceIndex) + return nullptr; // First slice, nothing to interpolate + + const unsigned int lastSliceIndex = m_SegmentationCountInSlice[timeStep][sliceDimension].size() - 1; + + if (lastSliceIndex <= sliceIndex) + return nullptr; // Last slice, nothing to interpolate if (m_SegmentationCountInSlice[timeStep][sliceDimension][sliceIndex] > 0) - return nullptr; // slice contains a segmentation, won't interpolate anything then + return nullptr; // Slice contains segmentation, nothing to interopolate - unsigned int lowerBound(0); - unsigned int upperBound(0); - bool bounds(false); + unsigned int lowerBound = 0; + unsigned int upperBound = 0; + bool bounds = false; - for (lowerBound = sliceIndex - 1; /*lowerBound >= 0*/; --lowerBound) + for (lowerBound = sliceIndex - 1; ; --lowerBound) { if (m_SegmentationCountInSlice[timeStep][sliceDimension][lowerBound] > 0) { bounds = true; break; } - if (lowerBound == 0) - break; // otherwise overflow and start at something like 4294967295 + if (0 == lowerBound) + break; } if (!bounds) return nullptr; bounds = false; - for (upperBound = sliceIndex + 1; upperBound < upperLimit; ++upperBound) + + for (upperBound = sliceIndex + 1; upperBound <= lastSliceIndex; ++upperBound) { if (m_SegmentationCountInSlice[timeStep][sliceDimension][upperBound] > 0) { bounds = true; break; } } if (!bounds) return nullptr; - // ok, we have found two neighboring slices with segmentations (and we made sure that the current slice does NOT - // contain anything - // MITK_INFO << "Interpolate in timestep " << timeStep << ", dimension " << sliceDimension << ": estimate slice " << - // sliceIndex << " from slices " << lowerBound << " and " << upperBound << std::endl; + // We have found two neighboring slices with segmentations and made sure that the current slice does not contain anything - mitk::Image::Pointer lowerMITKSlice; - mitk::Image::Pointer upperMITKSlice; + mitk::Image::Pointer lowerSlice; + mitk::Image::Pointer upperSlice; mitk::Image::Pointer resultImage; try { - // Setting up the ExtractSliceFilter - mitk::ExtractSliceFilter::Pointer extractor = ExtractSliceFilter::New(); - extractor->SetInput(m_Segmentation); - extractor->SetTimeStep(timeStep); - extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); - extractor->SetVtkOutputRequest(false); - - // Reslicing the current plane - extractor->SetWorldGeometry(currentPlane); - extractor->Modified(); - extractor->Update(); - resultImage = extractor->GetOutput(); - resultImage->DisconnectPipeline(); + // Extract current slice + resultImage = this->ExtractSlice(currentPlane, sliceIndex, timeStep); // Creating PlaneGeometry for lower slice - mitk::PlaneGeometry::Pointer reslicePlane = currentPlane->Clone(); + auto reslicePlane = currentPlane->Clone(); // Transforming the current origin so that it matches the lower slice - mitk::Point3D origin = currentPlane->GetOrigin(); + auto origin = currentPlane->GetOrigin(); m_Segmentation->GetSlicedGeometry(timeStep)->WorldToIndex(origin, origin); origin[sliceDimension] = lowerBound; m_Segmentation->GetSlicedGeometry(timeStep)->IndexToWorld(origin, origin); reslicePlane->SetOrigin(origin); - // Extract the lower slice - extractor = ExtractSliceFilter::New(); - extractor->SetInput(m_Segmentation); - extractor->SetTimeStep(timeStep); - extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); - extractor->SetVtkOutputRequest(false); + // Extract lower slice + lowerSlice = this->ExtractSlice(reslicePlane, lowerBound, timeStep, true); - extractor->SetWorldGeometry(reslicePlane); - extractor->Modified(); - extractor->Update(); - lowerMITKSlice = extractor->GetOutput(); - lowerMITKSlice->DisconnectPipeline(); + if (lowerSlice.IsNull()) + return nullptr; // Transforming the current origin so that it matches the upper slice m_Segmentation->GetSlicedGeometry(timeStep)->WorldToIndex(origin, origin); origin[sliceDimension] = upperBound; m_Segmentation->GetSlicedGeometry(timeStep)->IndexToWorld(origin, origin); reslicePlane->SetOrigin(origin); // Extract the upper slice - extractor = ExtractSliceFilter::New(); - extractor->SetInput(m_Segmentation); - extractor->SetTimeStep(timeStep); - extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); - extractor->SetVtkOutputRequest(false); - - extractor->SetWorldGeometry(reslicePlane); - extractor->Modified(); - extractor->Update(); - upperMITKSlice = extractor->GetOutput(); - upperMITKSlice->DisconnectPipeline(); - - if (lowerMITKSlice.IsNull() || upperMITKSlice.IsNull()) + upperSlice = this->ExtractSlice(reslicePlane, upperBound, timeStep, true); + + if (upperSlice.IsNull()) return nullptr; } catch (const std::exception &e) { MITK_ERROR << "Error in 2D interpolation: " << e.what(); return nullptr; } - // interpolation algorithm gets some inputs - // two segmentations (guaranteed to be of the same data type, but no special data type guaranteed) - // orientation (sliceDimension) of the segmentations - // position of the two slices (sliceIndices) - // one volume image (original patient image) + // Interpolation algorithm inputs: + // - Two segmentations (guaranteed to be of the same data type) + // - Orientation of the segmentations (sliceDimension) + // - Position of the two slices (sliceIndices) + // - Reference image // - // interpolation algorithm can use e.g. itk::ImageSliceConstIteratorWithIndex to - // inspect the original patient image at appropriate positions - - mitk::SegmentationInterpolationAlgorithm::Pointer algorithm = - mitk::ShapeBasedInterpolationAlgorithm::New().GetPointer(); - return algorithm->Interpolate(lowerMITKSlice.GetPointer(), - lowerBound, - upperMITKSlice.GetPointer(), - upperBound, - sliceIndex, - sliceDimension, - resultImage, - timeStep, - m_ReferenceImage); + // The interpolation algorithm can use e.g. itk::ImageSliceConstIteratorWithIndex to + // inspect the reference image at appropriate positions. + + if (algorithm.IsNull()) + algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); + + return algorithm->Interpolate( + lowerSlice.GetPointer(), + lowerBound, + upperSlice.GetPointer(), + upperBound, + sliceIndex, + sliceDimension, + resultImage, + timeStep, + m_ReferenceImage); +} + +mitk::Image::Pointer mitk::SegmentationInterpolationController::ExtractSlice(const PlaneGeometry* planeGeometry, unsigned int sliceIndex, unsigned int timeStep, bool cache) +{ + static const auto MAX_CACHE_SIZE = 2 * std::thread::hardware_concurrency(); + const auto key = std::make_pair(sliceIndex, timeStep); + + if (cache && m_EnableSliceImageCache) + { + std::lock_guard lock(m_SliceImageCacheMutex); + + if (0 != m_SliceImageCache.count(key)) + return m_SliceImageCache[key]; + + if (MAX_CACHE_SIZE < m_SliceImageCache.size()) + m_SliceImageCache.clear(); + } + + auto extractor = ExtractSliceFilter::New(); + extractor->SetInput(m_Segmentation); + extractor->SetTimeStep(timeStep); + extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); + extractor->SetVtkOutputRequest(false); + extractor->SetWorldGeometry(planeGeometry); + extractor->Update(); + + if (cache && m_EnableSliceImageCache) + { + std::lock_guard lock(m_SliceImageCacheMutex); + m_SliceImageCache[key] = extractor->GetOutput(); + } + + return extractor->GetOutput(); +} + +void mitk::SegmentationInterpolationController::EnableSliceImageCache() +{ + m_EnableSliceImageCache = true; +} + +void mitk::SegmentationInterpolationController::DisableSliceImageCache() +{ + m_EnableSliceImageCache = false; + m_SliceImageCache.clear(); } diff --git a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h index 30a9f8489c..5ac3839fa2 100644 --- a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h +++ b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h @@ -1,217 +1,242 @@ /*============================================================================ 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 mitkSegmentationInterpolationController_h_Included #define mitkSegmentationInterpolationController_h_Included #include "mitkCommon.h" #include "mitkImage.h" #include +#include #include #include #include +#include +#include #include namespace mitk { class Image; /** \brief Generates interpolations of 2D slices. \sa QmitkSlicesInterpolator \sa QmitkInteractiveSegmentation \ingroup ToolManagerEtAl This class keeps track of the contents of a 3D segmentation image. \attention mitk::SegmentationInterpolationController assumes that the image contains pixel values of 0 and 1. After you set the segmentation image using SetSegmentationVolume(), the whole image is scanned for pixels other than 0. SegmentationInterpolationController registers as an observer to the segmentation image, and repeats the scan whenvever the image is modified. You can prevent this (time consuming) scan if you do the changes slice-wise and send difference images to SegmentationInterpolationController. For this purpose SetChangedSlice() should be used. mitk::OverwriteImageFilter already does this every time it changes a slice of an image. There is a static method InterpolatorForImage(), which can be used to find out if there already is an interpolator instance for a specified image. OverwriteImageFilter uses this to get to know its interpolator. SegmentationInterpolationController needs to maintain some information about the image slices (in every dimension). This information is stored internally in m_SegmentationCountInSlice, which is basically three std::vectors (one for each dimension). Each item describes one image dimension, each vector item holds the count of pixels in "its" slice. $Author$ */ class MITKSEGMENTATION_EXPORT SegmentationInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SegmentationInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Find interpolator for a given image. \return nullptr if there is no interpolator yet. This method is useful if several "clients" modify the same image and want to access the interpolations. Then they can share the same object. */ static SegmentationInterpolationController *InterpolatorForImage(const Image *); /** \brief Block reaction to an images Modified() events. Blocking the scan of the whole image is especially useful when you are about to change a single slice of the image. Then you would send a difference image of this single slice to SegmentationInterpolationController but call image->Modified() anyway. Before calling image->Modified() you should block SegmentationInterpolationController's reactions to this modified by using this method. */ void BlockModified(bool); /** \brief Initialize with a whole volume. Will scan the volume for segmentation pixels (values other than 0) and fill some internal data structures. You don't have to call this method every time something changes, but only when several slices at once change. When you change a single slice, call SetChangedSlice() instead. */ void SetSegmentationVolume(const Image *segmentation); /** \brief Set a reference image (original patient image) - optional. If this volume is set (must exactly match the dimensions of the segmentation), the interpolation algorithm may consider image content to improve the interpolated (estimated) segmentation. */ void SetReferenceVolume(const Image *segmentation); /** \brief Update after changing a single slice. \param sliceDiff is a 2D image with the difference image of the slice determined by sliceDimension and sliceIndex. The difference is (pixel value in the new slice minus pixel value in the old slice). \param sliceDimension Number of the dimension which is constant for all pixels of the meant slice. \param sliceIndex Which slice to take, in the direction specified by sliceDimension. Count starts from 0. \param timeStep Which time step is changed */ void SetChangedSlice(const Image *sliceDiff, unsigned int sliceDimension, unsigned int sliceIndex, unsigned int timeStep); void SetChangedVolume(const Image *sliceDiff, unsigned int timeStep); /** \brief Generates an interpolated image for the given slice. \param sliceDimension Number of the dimension which is constant for all pixels of the meant slice. \param sliceIndex Which slice to take, in the direction specified by sliceDimension. Count starts from 0. \param currentPlane \param timeStep Which time step to use + + \param algorithm Optional algorithm instance to potentially benefit from caching for repeated interpolation */ Image::Pointer Interpolate(unsigned int sliceDimension, unsigned int sliceIndex, const mitk::PlaneGeometry *currentPlane, - unsigned int timeStep); + unsigned int timeStep, + mitk::ShapeBasedInterpolationAlgorithm::Pointer algorithm = nullptr); void OnImageModified(const itk::EventObject &); /** * Activate/Deactivate the 2D interpolation. */ void Activate2DInterpolation(bool); + /** + * Enable slice extraction cache for upper and lower slices. + */ + void EnableSliceImageCache(); + + /** + * Disable slice extraction cache for upper and lower slices. + */ + void DisableSliceImageCache(); + /** \brief Get existing instance or create a new one */ static SegmentationInterpolationController *GetInstance(); protected: /** \brief Protected class of mitk::SegmentationInterpolationController. Don't use (you shouldn't be able to do so)! */ class MITKSEGMENTATION_EXPORT SetChangedSliceOptions { public: SetChangedSliceOptions( unsigned int sd, unsigned int si, unsigned int d0, unsigned int d1, unsigned int t, const void *pixels) : sliceDimension(sd), sliceIndex(si), dim0(d0), dim1(d1), timeStep(t), pixelData(pixels) { } unsigned int sliceDimension; unsigned int sliceIndex; unsigned int dim0; unsigned int dim1; unsigned int timeStep; const void *pixelData; }; typedef std::vector DirtyVectorType; // typedef std::vector< DirtyVectorType[3] > TimeResolvedDirtyVectorType; // cannot work with C++, so next line is // used for implementation typedef std::vector> TimeResolvedDirtyVectorType; typedef std::map InterpolatorMapType; SegmentationInterpolationController(); // purposely hidden ~SegmentationInterpolationController() override; /// internal scan of a single slice template void ScanChangedSlice(const itk::Image *, const SetChangedSliceOptions &options); template void ScanChangedVolume(const itk::Image *, unsigned int timeStep); template void ScanWholeVolume(const itk::Image *, const Image *volume, unsigned int timeStep); void PrintStatus(); + /** + * Extract a slice and optionally use a caching mechanism if enabled. + */ + mitk::Image::Pointer ExtractSlice(const PlaneGeometry* planeGeometry, unsigned int sliceIndex, unsigned int timeStep, bool cache = false); + /** An array of flags. One for each dimension of the image. A flag is set, when a slice in a certain dimension has at least one pixel that is not 0 (which would mean that it has to be considered by the interpolation algorithm). E.g. flags for axial slices are stored in m_SegmentationCountInSlice[0][index]. Enhanced with time steps it is now m_SegmentationCountInSlice[timeStep][0][index] */ TimeResolvedDirtyVectorType m_SegmentationCountInSlice; static InterpolatorMapType s_InterpolatorForImage; Image::ConstPointer m_Segmentation; Image::ConstPointer m_ReferenceImage; bool m_BlockModified; bool m_2DInterpolationActivated; + + bool m_EnableSliceImageCache; + std::map, Image::Pointer> m_SliceImageCache; + std::mutex m_SliceImageCacheMutex; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp index d4a109ccfe..579561cfd0 100644 --- a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp @@ -1,120 +1,120 @@ /*============================================================================ 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 "mitkAdaptiveRegionGrowingTool.h" #include "mitkImage.h" #include "mitkProperties.h" #include "mitkToolManager.h" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, AdaptiveRegionGrowingTool, "AdaptiveRegionGrowingTool"); } mitk::AdaptiveRegionGrowingTool::AdaptiveRegionGrowingTool() { m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->GetPropertyList()->SetProperty("name", mitk::StringProperty::New("3D_Regiongrowing_Seedpoint")); m_PointSetNode->GetPropertyList()->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PointSet = mitk::PointSet::New(); m_PointSetNode->SetData(m_PointSet); } mitk::AdaptiveRegionGrowingTool::~AdaptiveRegionGrowingTool() { } bool mitk::AdaptiveRegionGrowingTool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { if (referenceData == nullptr) return false; auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; if (image->GetTimeSteps() > 1) //release quickfix for T28275 return false; return true; } const char **mitk::AdaptiveRegionGrowingTool::GetXPM() const { return nullptr; } const char *mitk::AdaptiveRegionGrowingTool::GetName() const { return "Region Growing 3D"; } us::ModuleResource mitk::AdaptiveRegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); return resource; } void mitk::AdaptiveRegionGrowingTool::Activated() { Superclass::Activated(); if (!GetDataStorage()->Exists(m_PointSetNode)) GetDataStorage()->Add(m_PointSetNode, GetWorkingData()); m_SeedPointInteractor = mitk::SinglePointDataInteractor::New(); m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); m_SeedPointInteractor->SetDataNode(m_PointSetNode); } void mitk::AdaptiveRegionGrowingTool::Deactivated() { m_PointSet->Clear(); GetDataStorage()->Remove(m_PointSetNode); Superclass::Deactivated(); } void mitk::AdaptiveRegionGrowingTool::ConfirmSegmentation() { - m_ToolManager->ActivateTool(-1); + this->GetToolManager()->ActivateTool(-1); } mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetReferenceData() { - return this->m_ToolManager->GetReferenceData(0); + return this->GetToolManager()->GetReferenceData(0); } mitk::DataStorage *mitk::AdaptiveRegionGrowingTool::GetDataStorage() { - return this->m_ToolManager->GetDataStorage(); + return this->GetToolManager()->GetDataStorage(); } mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetWorkingData() { - return this->m_ToolManager->GetWorkingData(0); + return this->GetToolManager()->GetWorkingData(0); } mitk::DataNode::Pointer mitk::AdaptiveRegionGrowingTool::GetPointSetNode() { return m_PointSetNode; } diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp index 5451a67f34..63c827db0c 100644 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp @@ -1,204 +1,205 @@ /*============================================================================ 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. ============================================================================*/ // MITK #include "mitkAutoMLSegmentationWithPreviewTool.h" #include "mitkImageAccessByItk.h" #include "mitkToolManager.h" #include #include #include #include #include #include +#include // ITK #include #include mitk::AutoMLSegmentationWithPreviewTool::AutoMLSegmentationWithPreviewTool() : AutoSegmentationWithPreviewTool(true) { } void mitk::AutoMLSegmentationWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& regions) { if (m_SelectedLabels != regions) { m_SelectedLabels = regions; //Note: we do not call this->Modified() on puprose. Reason: changing the //selected regions should not force to run otsu filter in DoUpdatePreview due to changed MTime. } } const mitk::LabelSetImage* mitk::AutoMLSegmentationWithPreviewTool::GetMLPreview() const { if (m_MLPreviewNode.IsNotNull()) { const auto mlPreviewImage = dynamic_cast(this->m_MLPreviewNode->GetData()); return mlPreviewImage; } return nullptr; } mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType mitk::AutoMLSegmentationWithPreviewTool::GetSelectedLabels() const { return this->m_SelectedLabels; } void mitk::AutoMLSegmentationWithPreviewTool::Activated() { Superclass::Activated(); m_SelectedLabels = {}; m_MLPreviewNode = mitk::DataNode::New(); m_MLPreviewNode->SetProperty("name", StringProperty::New(std::string(this->GetName()) + "ML preview")); m_MLPreviewNode->SetProperty("helper object", BoolProperty::New(true)); m_MLPreviewNode->SetVisibility(true); m_MLPreviewNode->SetOpacity(1.0); - m_ToolManager->GetDataStorage()->Add(m_MLPreviewNode); + this->GetToolManager()->GetDataStorage()->Add(m_MLPreviewNode); } void mitk::AutoMLSegmentationWithPreviewTool::Deactivated() { - m_ToolManager->GetDataStorage()->Remove(m_MLPreviewNode); + this->GetToolManager()->GetDataStorage()->Remove(m_MLPreviewNode); m_MLPreviewNode = nullptr; Superclass::Deactivated(); } void mitk::AutoMLSegmentationWithPreviewTool::UpdateCleanUp() { if (m_MLPreviewNode.IsNotNull()) m_MLPreviewNode->SetVisibility(m_SelectedLabels.empty()); if (nullptr != this->GetPreviewSegmentationNode()) this->GetPreviewSegmentationNode()->SetVisibility(!m_SelectedLabels.empty()); if (m_SelectedLabels.empty()) { this->ResetPreviewNode(); } } -void mitk::AutoMLSegmentationWithPreviewTool::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) +void mitk::AutoMLSegmentationWithPreviewTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (nullptr == m_MLPreviewNode->GetData() || this->GetMTime() > m_MLPreviewNode->GetData()->GetMTime() || this->m_LastMLTimeStep != timeStep //this covers the case where dynamic //segmentations have to compute a preview //for all time steps on confirmation || this->GetLastTimePointOfUpdate() != timePoint //this ensures that static seg //previews work with dynamic images //with avoiding unnecessary other computations ) { if (nullptr == inputAtTimeStep) { MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; return; } this->m_LastMLTimeStep = timeStep; auto newMLPreview = ComputeMLPreview(inputAtTimeStep, timeStep); if (newMLPreview.IsNotNull()) { this->m_MLPreviewNode->SetData(newMLPreview); this->m_MLPreviewNode->SetProperty("binary", mitk::BoolProperty::New(false)); mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); this->m_MLPreviewNode->SetProperty("Image Rendering.Mode", renderingMode); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); vtkSmartPointer lookupTable = vtkSmartPointer::New(); lookupTable->SetHueRange(1.0, 0.0); lookupTable->SetSaturationRange(1.0, 1.0); lookupTable->SetValueRange(1.0, 1.0); lookupTable->SetTableRange(-1.0, 1.0); lookupTable->Build(); lut->SetVtkLookupTable(lookupTable); prop->SetLookupTable(lut); this->m_MLPreviewNode->SetProperty("LookupTable", prop); mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); mitk::LevelWindow levelwindow; - levelwindow.SetRangeMinMax(0, newMLPreview->GetScalarValueMax()); + levelwindow.SetRangeMinMax(0, newMLPreview->GetStatistics()->GetScalarValueMax()); levWinProp->SetLevelWindow(levelwindow); this->m_MLPreviewNode->SetProperty("levelwindow", levWinProp); } } if (!m_SelectedLabels.empty()) { const auto mlPreviewImage = this->GetMLPreview(); if (nullptr != mlPreviewImage) { AccessByItk_n(mlPreviewImage, CalculateMergedSimplePreview, (previewImage, timeStep)); } } } template void mitk::AutoMLSegmentationWithPreviewTool::CalculateMergedSimplePreview(const itk::Image* itkImage, mitk::Image* segmentation, unsigned int timeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::BinaryThresholdImageFilter FilterType; typename FilterType::Pointer filter = FilterType::New(); // InputImageType::Pointer itkImage; typename OutputImageType::Pointer itkBinaryResultImage; filter->SetInput(itkImage); filter->SetLowerThreshold(m_SelectedLabels[0]); filter->SetUpperThreshold(m_SelectedLabels[0]); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); filter->Update(); itkBinaryResultImage = filter->GetOutput(); itkBinaryResultImage->DisconnectPipeline(); // if more than one region id is used compute the union of all given binary regions for (const auto labelID : m_SelectedLabels) { if (labelID != m_SelectedLabels[0]) { filter->SetLowerThreshold(labelID); filter->SetUpperThreshold(labelID); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); typename OutputImageType::Pointer tempImage = filter->GetOutput(); typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(tempImage); orFilter->SetInput2(itkBinaryResultImage); orFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); orFilter->UpdateLargestPossibleRegion(); itkBinaryResultImage = orFilter->GetOutput(); } } //---------------------------------------------------------------------------------------------------- segmentation->SetVolume((void*)(itkBinaryResultImage->GetPixelContainer()->GetBufferPointer()), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h index 94163df1ad..878fd5592c 100644 --- a/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoMLSegmentationWithPreviewTool.h @@ -1,82 +1,82 @@ /*============================================================================ 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_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H #define MITK_AUTO_ML_SEGMENTATION_WITH_PREVIEW_TOOL_H #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkLabelSetImage.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation and generates segmentations with multiple labels. This tool class implements the basic logic to handle previews of multi label segmentations and to allow to pick arbitrary labels as selected and merge them to a single segmentation to store this segmentation as confirmed segmentation. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AutoMLSegmentationWithPreviewTool : public AutoSegmentationWithPreviewTool { public: mitkClassMacro(AutoMLSegmentationWithPreviewTool, AutoSegmentationWithPreviewTool); void Activated() override; void Deactivated() override; using SelectedLabelVectorType = std::vector; void SetSelectedLabels(const SelectedLabelVectorType& regions); SelectedLabelVectorType GetSelectedLabels() const; const LabelSetImage* GetMLPreview() const; protected: AutoMLSegmentationWithPreviewTool(); ~AutoMLSegmentationWithPreviewTool() = default; void UpdateCleanUp() override; - void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; /** Function to generate the new multi lable preview for a given time step input image. * The function must be implemented by derived tools. * This function is called by DoUpdatePreview if needed. * Reasons are: * - ML preview does not exist * - Modify time of tools is newer then of ML preview * - ML preview was not generated for the current selected timestep of input image or for the current selected timepoint.*/ virtual LabelSetImage::Pointer ComputeMLPreview(const Image* inputAtTimeStep, TimeStepType timeStep) = 0; private: /** Function to generate a simple (single lable) preview by merging all labels of the ML preview that are selected and * copies that single label preview to the passed previewImage. * This function is called by DoUpdatePreview if needed. * @param mlPreviewImage Multi label preview that is the source. * @param previewImage Pointer to the single label preview image that should receive the merged selected labels. * @timeStep Time step of the previewImage that should be filled.*/ template void CalculateMergedSimplePreview(const itk::Image* mlImage, mitk::Image* segmentation, unsigned int timeStep); SelectedLabelVectorType m_SelectedLabels = {}; // holds the multilabel result as a preview image mitk::DataNode::Pointer m_MLPreviewNode; TimeStepType m_LastMLTimeStep = 0; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp index 647fa8b80d..a206b7c65e 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp @@ -1,130 +1,130 @@ /*============================================================================ 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 "mitkAutoSegmentationTool.h" #include "mitkImage.h" #include "mitkToolManager.h" #include mitk::AutoSegmentationTool::AutoSegmentationTool() : Tool("dummy"), m_OverwriteExistingSegmentation(false) { } mitk::AutoSegmentationTool::AutoSegmentationTool(const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_OverwriteExistingSegmentation(false) { } mitk::AutoSegmentationTool::~AutoSegmentationTool() { } void mitk::AutoSegmentationTool::Activated() { Superclass::Activated(); m_NoneOverwriteTargetSegmentationNode = nullptr; } void mitk::AutoSegmentationTool::Deactivated() { m_NoneOverwriteTargetSegmentationNode = nullptr; Superclass::Deactivated(); } const char *mitk::AutoSegmentationTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimeStep(const mitk::Image* image, unsigned int timestep) { if (nullptr == image) return image; if (image->GetDimension() != 4) return image; mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(timestep)); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; return AutoSegmentationTool::GetImageByTimeStep(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); } void mitk::AutoSegmentationTool::SetOverwriteExistingSegmentation(bool overwrite) { if (m_OverwriteExistingSegmentation != overwrite) { m_OverwriteExistingSegmentation = overwrite; m_NoneOverwriteTargetSegmentationNode = nullptr; } } std::string mitk::AutoSegmentationTool::GetCurrentSegmentationName() { - if (m_ToolManager->GetWorkingData(0)) - return m_ToolManager->GetWorkingData(0)->GetName(); + if (this->GetToolManager()->GetWorkingData(0)) + return this->GetToolManager()->GetWorkingData(0)->GetName(); else return ""; } mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() const { - mitk::DataNode::Pointer segmentationNode = m_ToolManager->GetWorkingData(0); + mitk::DataNode::Pointer segmentationNode = this->GetToolManager()->GetWorkingData(0); if (!m_OverwriteExistingSegmentation) { if (m_NoneOverwriteTargetSegmentationNode.IsNull()) { - mitk::DataNode::Pointer refNode = m_ToolManager->GetReferenceData(0); + mitk::DataNode::Pointer refNode = this->GetToolManager()->GetReferenceData(0); if (refNode.IsNull()) { // TODO create and use segmentation exceptions instead!! MITK_ERROR << "No valid reference data!"; return nullptr; } std::string nodename = refNode->GetName() + "_" + this->GetName(); mitk::Color color; color.SetRed(1); color.SetBlue(0); color.SetGreen(0); //create a new segmentation node based on the current segmentation as template m_NoneOverwriteTargetSegmentationNode = CreateEmptySegmentationNode(dynamic_cast(segmentationNode->GetData()), nodename, color); } segmentationNode = m_NoneOverwriteTargetSegmentationNode; } return segmentationNode; } void mitk::AutoSegmentationTool::EnsureTargetSegmentationNodeInDataStorage() const { auto targetNode = this->GetTargetSegmentationNode(); - if (!m_ToolManager->GetDataStorage()->Exists(targetNode)) + if (!this->GetToolManager()->GetDataStorage()->Exists(targetNode)) { - m_ToolManager->GetDataStorage()->Add(targetNode, m_ToolManager->GetReferenceData(0)); + this->GetToolManager()->GetDataStorage()->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); } } diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp index adfa517175..e19770ed6a 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp @@ -1,523 +1,549 @@ /*============================================================================ 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 "mitkAutoSegmentationWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkLevelWindowProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkImageTimeSelector.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews): m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = mitk::ToolCommand::New(); } mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : AutoSegmentationTool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = mitk::ToolCommand::New(); } mitk::AutoSegmentationWithPreviewTool::~AutoSegmentationWithPreviewTool() { } bool mitk::AutoSegmentationWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return true; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* image = dynamic_cast(workingData); if (image == nullptr) return false; //if it is a normal image and not a label set image is used as working data //it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == image->GetPixelType(); } void mitk::AutoSegmentationWithPreviewTool::Activated() { Superclass::Activated(); - m_ToolManager->RoiDataChanged += + this->GetToolManager()->RoiDataChanged += mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); - m_ToolManager->SelectedTimePointChanged += + this->GetToolManager()->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); - m_ReferenceDataNode = m_ToolManager->GetReferenceData(0); + m_ReferenceDataNode = this->GetToolManager()->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { - m_ToolManager->ActivateTool(-1); + this->GetToolManager()->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::Deactivated() { - m_ToolManager->RoiDataChanged -= + this->GetToolManager()->RoiDataChanged -= mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); - m_ToolManager->SelectedTimePointChanged -= + this->GetToolManager()->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { - if (DataStorage *storage = m_ToolManager->GetDataStorage()) + if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::AutoSegmentationWithPreviewTool::ConfirmSegmentation() { if (m_LazyDynamicPreviews && m_CreateAllTimeSteps) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { - m_ToolManager->ActivateTool(-1); + this->GetToolManager()->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } void mitk::AutoSegmentationWithPreviewTool::ResetPreviewNode() { itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { mitk::LabelSetImage::ConstPointer workingImage = - dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); + if (this->GetResetsToEmptyPreview()) + { + newPreviewImage->ClearBuffer(); + } + if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); // Let's paint the feedback node green... newPreviewImage->GetActiveLabel()->SetColor(previewColor); newPreviewImage->GetActiveLabelSet()->UpdateLookupTable(newPreviewImage->GetActiveLabel()->GetValue()); } else { - mitk::Image::ConstPointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + mitk::Image::ConstPointer workingImageBin = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { - auto newPreviewImage = workingImageBin->Clone(); + mitk::Image::Pointer newPreviewImage; + if (this->GetResetsToEmptyPreview()) + { + newPreviewImage = mitk::Image::New(); + newPreviewImage->Initialize(workingImageBin); + } + else + { + auto newPreviewImage = workingImageBin->Clone(); + } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } - - m_PreviewSegmentationNode->SetData(newPreviewImage->Clone()); + m_PreviewSegmentationNode->SetData(newPreviewImage); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); - if (DataStorage *ds = m_ToolManager->GetDataStorage()) + if (DataStorage *ds = this->GetToolManager()->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } template static void ITKSetVolume(const itk::Image *originalImage, mitk::Image *segmentation, unsigned int timeStep) { auto constPixelContainer = originalImage->GetPixelContainer(); //have to make a const cast because itk::PixelContainer does not provide a const correct access :( auto pixelContainer = const_cast::PixelContainer*>(constPixelContainer); segmentation->SetVolume((void *)pixelContainer->GetBufferPointer(), timeStep); } void mitk::AutoSegmentationWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) { try { Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } if (nullptr != this->GetWorkingPlaneGeometry()) { auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, sourceSlice, timeStep); } else { //take care of the full segmentation volume if (sourceImageAtTimeStep->GetDimension() == 2) { AccessFixedDimensionByItk_2( sourceImageAtTimeStep, ITKSetVolume, 2, destinationImage, timeStep); } else { AccessFixedDimensionByItk_2( sourceImageAtTimeStep, ITKSetVolume, 3, destinationImage, timeStep); } } } catch (...) { Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); throw; } } void mitk::AutoSegmentationWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working image (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot perform threshold. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } // since we are maybe working on a smaller image, pad it to the size of the original image if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } - - m_ToolManager->SetWorkingData(resultSegmentationNode); - m_ToolManager->GetWorkingData(0)->Modified(); + if (m_OverwriteExistingSegmentation) + { //if we overwrite the segmentation (and not just store it as a new result + //in the data storage) we update also the tool manager state. + this->GetToolManager()->SetWorkingData(resultSegmentationNode); + this->GetToolManager()->GetWorkingData(0)->Modified(); + } this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged() { - mitk::DataNode::ConstPointer node = m_ToolManager->GetRoiData(0); + mitk::DataNode::ConstPointer node = this->GetToolManager()->GetRoiData(0); if (node.IsNotNull()) { mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); mitk::Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic image this->UpdatePreview(); } } } void mitk::AutoSegmentationWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; + const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); + this->CurrentlyBusy.Send(true); m_IsUpdating = true; this->UpdatePrepare(); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; + Image::ConstPointer currentSegImage; + auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback image. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); + currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback image feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); + currentSegImage = this->GetImageByTimePoint(workingImage, previewTimePoint); } - this->DoUpdatePreview(feedBackImage, previewImage, timeStep); + this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; + Image::ConstPointer currentSegImage; + if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); + currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); + currentSegImage = this->GetImageByTimePoint(workingImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); - this->DoUpdatePreview(feedBackImage, previewImage, timeStep); + this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } bool mitk::AutoSegmentationWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::AutoSegmentationWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::AutoSegmentationWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } mitk::TimePointType mitk::AutoSegmentationWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h index b3cad84572..aabe1c032f 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h @@ -1,169 +1,186 @@ /*============================================================================ 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 mitkAutoSegmentationWithPreviewTool_h_Included #define mitkAutoSegmentationWithPreviewTool_h_Included #include "mitkAutoSegmentationTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkToolCommand.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation. This tool class implements a lot basic logic to handle auto segmentation tools with preview, Time point and ROI support. Derived classes will ask to update the segmentation preview if needed (e.g. because the ROI or the current time point has changed) or because derived tools indicated the need to update themselves. This class also takes care to properly transfer a confirmed preview into the segementation result. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AutoSegmentationWithPreviewTool : public AutoSegmentationTool { public: mitkClassMacro(AutoSegmentationWithPreviewTool, AutoSegmentationTool); void Activated() override; void Deactivated() override; void ConfirmSegmentation(); itkSetMacro(CreateAllTimeSteps, bool); itkGetMacro(CreateAllTimeSteps, bool); itkBooleanMacro(CreateAllTimeSteps); itkSetMacro(KeepActiveAfterAccept, bool); itkGetMacro(KeepActiveAfterAccept, bool); itkBooleanMacro(KeepActiveAfterAccept); itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); + itkSetMacro(ResetsToEmptyPreview, bool); + itkGetMacro(ResetsToEmptyPreview, bool); + itkBooleanMacro(ResetsToEmptyPreview); + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /** Triggers the actualization of the preview * @param ignoreLazyPreviewSetting If set true UpdatePreview will always * generate the preview for all time steps. If set to false, UpdatePreview * will regard the setting specified by the constructor. * To define the update generation for time steps implement DoUpdatePreview. * To alter what should be done directly before or after the update of the preview, * reimplement UpdatePrepare() or UpdateCleanUp().*/ void UpdatePreview(bool ignoreLazyPreviewSetting = false); /** Indicate if currently UpdatePreview is triggered (true) or not (false).*/ bool IsUpdating() const; protected: mitk::ToolCommand::Pointer m_ProgressCommand; /** Member is always called if GetSegmentationInput() has changed * (e.g. because a new ROI was defined, or on activation) to give derived * classes the posibility to initiate their state accordingly. * Reimplement this function to implement special behavior. */ virtual void InitiateToolByInput(); + /** This member function offers derived classes the possibility to alter what should + happen directly before the update of the preview is performed. It is called by + UpdatePreview. Default implementation does nothing.*/ virtual void UpdatePrepare(); + + /** This member function offers derived classes the possibility to alter what should + happen directly after the update of the preview is performed. It is called by + UpdatePreview. Default implementation does nothing.*/ virtual void UpdateCleanUp(); /** This function does the real work. Here the preview for a given * input image should be computed and stored in the also passed * preview image at the passed time step. + * It also provides the current/old segmentation at the time point, + * which can be used, if the preview depends on the the segmenation so far. */ - virtual void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) = 0; + virtual void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) = 0; AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews = false); // purposely hidden AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden ~AutoSegmentationWithPreviewTool() override; /** Returns the image that contains the preview of the current segmentation. * Returns null if the node is not set or does not contain an image.*/ Image* GetPreviewSegmentation(); DataNode* GetPreviewSegmentationNode(); /** Returns the input that should be used for any segmentation/preview or tool update. * It is either the data of ReferenceDataNode itself or a part of it defined by a ROI mask * provided by the tool manager. Derived classes should regard this as the relevant * input data for any processing. * Returns null if the node is not set or does not contain an image.*/ const Image* GetSegmentationInput() const; /** Returns the image that is provided by the ReferenceDataNode. * Returns null if the node is not set or does not contain an image.*/ const Image* GetReferenceData() const; /** Resets the preview node so it is empty and ready to be filled by the tool*/ void ResetPreviewNode(); TimePointType GetLastTimePointOfUpdate() const; itkSetObjectMacro(WorkingPlaneGeometry, PlaneGeometry); itkGetConstObjectMacro(WorkingPlaneGeometry, PlaneGeometry); private: void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); void CreateResultSegmentationFromPreview(); void OnRoiDataChanged(); void OnTimePointChanged(); /** Node that containes the preview data generated and managed by this class or derived ones.*/ DataNode::Pointer m_PreviewSegmentationNode; /** The reference data recieved from ToolManager::GetReferenceData when tool was activated.*/ DataNode::Pointer m_ReferenceDataNode; /** Node that containes the data that should be used as input for any auto segmentation. It might * be the same like m_ReferenceDataNode (if no ROI is set) or a sub region (if ROI is set).*/ DataNode::Pointer m_SegmentationInputNode; /** Indicates if Accepting the threshold should transfer/create the segmentations of all time steps (true) or only of the currently selected timepoint (false).*/ bool m_CreateAllTimeSteps = false; /** Indicates if the tool should kept active after accepting the segmentation or not.*/ bool m_KeepActiveAfterAccept = false; /** Relevant if the working data / preview image has multiple time steps (dynamic segmentations). * This flag has to be set by derived classes accordingly to there way to generate dynamic previews. * If LazyDynamicPreview is true, the tool generates only the preview for the current time step. * Therefore it always has to update the preview if current time point has changed and it has to (re)compute * all timeframes if ConfirmSegmentation() is called.*/ bool m_LazyDynamicPreviews = false; bool m_IsTimePointChangeAware = true; + /** Controls if ResetPreviewNode generates an empty content (true) or clones the current + segmentation (false).*/ + bool m_ResetsToEmptyPreview = false; + TimePointType m_LastTimePointOfUpdate = 0.; bool m_IsUpdating = false; /** This variable indicates if for the tool a working plane geometry is defined. * If a working plane is defined the tool will only work an the slice of the input * and the segmentation. Thus only the relevant input slice will be passed to * DoUpdatePreview(...) and only the relevant slice of the preview will be transfered when * ConfirmSegmentation() is called.*/ PlaneGeometry::Pointer m_WorkingPlaneGeometry; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp index a8ee655db0..40c2a088b6 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp @@ -1,126 +1,126 @@ /*============================================================================ 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 "mitkBinaryThresholdBaseTool.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkLabelSetImage.h" #include #include mitk::BinaryThresholdBaseTool::BinaryThresholdBaseTool() : m_SensibleMinimumThresholdValue(-100), m_SensibleMaximumThresholdValue(+100), m_CurrentLowerThresholdValue(1), m_CurrentUpperThresholdValue(1) { } mitk::BinaryThresholdBaseTool::~BinaryThresholdBaseTool() { } void mitk::BinaryThresholdBaseTool::SetThresholdValues(double lower, double upper) { /* If value is not in the min/max range, do nothing. In that case, this method will be called again with a proper value right after. The only known case where this happens is with an [0.0, 1.0[ image, where value could be an epsilon greater than the max. */ if (lower < m_SensibleMinimumThresholdValue || lower > m_SensibleMaximumThresholdValue || upper < m_SensibleMinimumThresholdValue || upper > m_SensibleMaximumThresholdValue) { return; } m_CurrentLowerThresholdValue = lower; m_CurrentUpperThresholdValue = upper; if (nullptr != this->GetPreviewSegmentation()) { UpdatePreview(); } } void mitk::BinaryThresholdBaseTool::InitiateToolByInput() { const auto referenceImage = this->GetReferenceData(); if (nullptr != referenceImage) { m_SensibleMinimumThresholdValue = std::numeric_limits::max(); m_SensibleMaximumThresholdValue = std::numeric_limits::lowest(); Image::StatisticsHolderPointer statistics = referenceImage->GetStatistics(); for (unsigned int ts = 0; ts < referenceImage->GetTimeSteps(); ++ts) { m_SensibleMinimumThresholdValue = std::min(m_SensibleMinimumThresholdValue, static_cast(statistics->GetScalarValueMin())); m_SensibleMaximumThresholdValue = std::max(m_SensibleMaximumThresholdValue, static_cast(statistics->GetScalarValueMax())); } if (m_LockedUpperThreshold) { m_CurrentLowerThresholdValue = (m_SensibleMaximumThresholdValue + m_SensibleMinimumThresholdValue) / 2.0; m_CurrentUpperThresholdValue = m_SensibleMaximumThresholdValue; } else { double range = m_SensibleMaximumThresholdValue - m_SensibleMinimumThresholdValue; m_CurrentLowerThresholdValue = m_SensibleMinimumThresholdValue + range / 3.0; m_CurrentUpperThresholdValue = m_SensibleMinimumThresholdValue + 2 * range / 3.0; } bool isFloatImage = false; if ((referenceImage->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) && (referenceImage->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT || referenceImage->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE)) { isFloatImage = true; } IntervalBordersChanged.Send(m_SensibleMinimumThresholdValue, m_SensibleMaximumThresholdValue, isFloatImage); ThresholdingValuesChanged.Send(m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue); } } template static void ITKThresholding(const itk::Image *originalImage, mitk::Image *segmentation, double lower, double upper, unsigned int timeStep) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(originalImage); filter->SetLowerThreshold(lower); filter->SetUpperThreshold(upper); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } -void mitk::BinaryThresholdBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) +void mitk::BinaryThresholdBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) { if (nullptr != inputAtTimeStep && nullptr != previewImage) { AccessByItk_n(inputAtTimeStep, ITKThresholding, (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); } } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h index cdc15f18be..7eb15979c4 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h @@ -1,70 +1,70 @@ /*============================================================================ 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 mitkBinaryThresholdBaseTool_h_Included #define mitkBinaryThresholdBaseTool_h_Included #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include #include #include namespace mitk { /** \brief Base class for binary threshold tools. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public AutoSegmentationWithPreviewTool { public: Message3 IntervalBordersChanged; Message2 ThresholdingValuesChanged; mitkClassMacro(BinaryThresholdBaseTool, AutoSegmentationWithPreviewTool); virtual void SetThresholdValues(double lower, double upper); protected: BinaryThresholdBaseTool(); // purposely hidden ~BinaryThresholdBaseTool() override; itkSetMacro(LockedUpperThreshold, bool); itkGetMacro(LockedUpperThreshold, bool); itkBooleanMacro(LockedUpperThreshold); itkGetMacro(SensibleMinimumThresholdValue, ScalarType); itkGetMacro(SensibleMaximumThresholdValue, ScalarType); void InitiateToolByInput() override; - void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; private: ScalarType m_SensibleMinimumThresholdValue; ScalarType m_SensibleMaximumThresholdValue; ScalarType m_CurrentLowerThresholdValue; ScalarType m_CurrentUpperThresholdValue; /** Indicates if the tool should behave like a single threshold tool (true) or like a upper/lower threshold tool (false)*/ bool m_LockedUpperThreshold = false; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkCalculateGrayValueStatisticsTool.cpp b/Modules/Segmentation/Interactions/mitkCalculateGrayValueStatisticsTool.cpp index 18db43a60a..77eae790c0 100644 --- a/Modules/Segmentation/Interactions/mitkCalculateGrayValueStatisticsTool.cpp +++ b/Modules/Segmentation/Interactions/mitkCalculateGrayValueStatisticsTool.cpp @@ -1,332 +1,332 @@ /*============================================================================ 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 "mitkCalculateGrayValueStatisticsTool.h" #include "mitkCalculateGrayValueStatisticsTool.xpm" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include "mitkProgressBar.h" #include "mitkStatusBar.h" #include "mitkToolManager.h" #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, CalculateGrayValueStatisticsTool, "Statistics tool"); } mitk::CalculateGrayValueStatisticsTool::CalculateGrayValueStatisticsTool() { } mitk::CalculateGrayValueStatisticsTool::~CalculateGrayValueStatisticsTool() { } const char **mitk::CalculateGrayValueStatisticsTool::GetXPM() const { return mitkCalculateGrayValueStatisticsTool_xpm; } const char *mitk::CalculateGrayValueStatisticsTool::GetName() const { return "Statistics"; } std::string mitk::CalculateGrayValueStatisticsTool::GetErrorMessage() { return "No statistics generated for these segmentations:"; } void mitk::CalculateGrayValueStatisticsTool::StartProcessingAllData() { // clear/prepare report m_CompleteReport.str(""); } bool mitk::CalculateGrayValueStatisticsTool::ProcessOneWorkingData(DataNode *node) { if (node) { Image::Pointer image = dynamic_cast(node->GetData()); if (image.IsNull()) return false; - DataNode *referencenode = m_ToolManager->GetReferenceData(0); + DataNode *referencenode = this->GetToolManager()->GetReferenceData(0); if (!referencenode) return false; try { ProgressBar::GetInstance()->AddStepsToDo(1); // add to report std::string nodename("structure"); node->GetName(nodename); std::string message = "Calculating statistics for "; message += nodename; StatusBar::GetInstance()->DisplayText(message.c_str()); Image::Pointer refImage = dynamic_cast(referencenode->GetData()); Image::Pointer image = dynamic_cast(node->GetData()); m_CompleteReport << "======== Gray value analysis of " << nodename << " ========\n"; if (image.IsNotNull() && refImage.IsNotNull()) { for (unsigned int timeStep = 0; timeStep < image->GetTimeSteps(); ++timeStep) { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(refImage); timeSelector->SetTimeNr(timeStep); timeSelector->UpdateLargestPossibleRegion(); Image::Pointer refImage3D = timeSelector->GetOutput(); ImageTimeSelector::Pointer timeSelector2 = ImageTimeSelector::New(); timeSelector2->SetInput(image); timeSelector2->SetTimeNr(timeStep); timeSelector2->UpdateLargestPossibleRegion(); Image::Pointer image3D = timeSelector2->GetOutput(); if (image3D.IsNotNull() && refImage3D.IsNotNull()) { m_CompleteReport << "=== " << nodename << ", time step " << timeStep << " ===\n"; AccessFixedDimensionByItk_2(refImage3D, ITKHistogramming, 3, image3D, m_CompleteReport); } } } m_CompleteReport << "======== End of analysis for " << nodename << " ===========\n\n\n"; ProgressBar::GetInstance()->Progress(); } catch (...) { return false; } } return true; } void mitk::CalculateGrayValueStatisticsTool::FinishProcessingAllData() { SegmentationsProcessingTool::FinishProcessingAllData(); // show/send results StatisticsCompleted.Send(); // MITK_INFO << m_CompleteReport.str() << std::endl; } #define ROUND_P(x) ((int)((x) + 0.5)) template void mitk::CalculateGrayValueStatisticsTool::CalculateMinMax(itk::Image *referenceImage, Image *segmentation, TPixel &minimum, TPixel &maximum) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typename SegmentationType::Pointer segmentationItk; CastToItkImage(segmentation, segmentationItk); typename SegmentationType::RegionType segmentationRegion = segmentationItk->GetLargestPossibleRegion(); segmentationRegion.Crop(referenceImage->GetLargestPossibleRegion()); itk::ImageRegionConstIteratorWithIndex segmentationIterator(segmentationItk, segmentationRegion); itk::ImageRegionConstIteratorWithIndex referenceIterator(referenceImage, segmentationRegion); segmentationIterator.GoToBegin(); referenceIterator.GoToBegin(); minimum = std::numeric_limits::max(); maximum = std::numeric_limits::min(); while (!segmentationIterator.IsAtEnd()) { itk::Point pt; segmentationItk->TransformIndexToPhysicalPoint(segmentationIterator.GetIndex(), pt); typename ImageType::IndexType ind; itk::ContinuousIndex contInd; if (referenceImage->TransformPhysicalPointToContinuousIndex(pt, contInd)) { for (unsigned int i = 0; i < 3; ++i) ind[i] = ROUND_P(contInd[i]); referenceIterator.SetIndex(ind); if (segmentationIterator.Get() > 0) { if (referenceIterator.Get() < minimum) minimum = referenceIterator.Get(); if (referenceIterator.Get() > maximum) maximum = referenceIterator.Get(); } } ++segmentationIterator; } } template void mitk::CalculateGrayValueStatisticsTool::ITKHistogramming(itk::Image *referenceImage, Image *segmentation, std::stringstream &report) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typename SegmentationType::Pointer segmentationItk; CastToItkImage(segmentation, segmentationItk); // generate histogram typename SegmentationType::RegionType segmentationRegion = segmentationItk->GetLargestPossibleRegion(); segmentationRegion.Crop(referenceImage->GetLargestPossibleRegion()); itk::ImageRegionConstIteratorWithIndex segmentationIterator(segmentationItk, segmentationRegion); itk::ImageRegionConstIteratorWithIndex referenceIterator(referenceImage, segmentationRegion); segmentationIterator.GoToBegin(); referenceIterator.GoToBegin(); m_ITKHistogram = HistogramType::New(); TPixel minimum = std::numeric_limits::max(); TPixel maximum = std::numeric_limits::min(); CalculateMinMax(referenceImage, segmentation, minimum, maximum); // initialize the histogram to the range of the cropped region HistogramType::SizeType size; #if defined(ITK_USE_REVIEW_STATISTICS) typedef typename HistogramType::SizeType::ValueType HSizeValueType; #else typedef typename HistogramType::SizeType::SizeValueType HSizeValueType; #endif size.Fill(static_cast(maximum - minimum + 1)); HistogramType::MeasurementVectorType lowerBound; HistogramType::MeasurementVectorType upperBound; lowerBound[0] = minimum; upperBound[0] = maximum; m_ITKHistogram->Initialize(size, lowerBound, upperBound); double mean(0.0); double sd(0.0); double voxelCount(0.0); // iterate through the cropped region add the values to the histogram while (!segmentationIterator.IsAtEnd()) { itk::Point pt; segmentationItk->TransformIndexToPhysicalPoint(segmentationIterator.GetIndex(), pt); typename ImageType::IndexType ind; itk::ContinuousIndex contInd; if (referenceImage->TransformPhysicalPointToContinuousIndex(pt, contInd)) { for (unsigned int i = 0; i < 3; ++i) ind[i] = ROUND_P(contInd[i]); referenceIterator.SetIndex(ind); if (segmentationIterator.Get() > 0) { HistogramType::MeasurementVectorType currentMeasurementVector; currentMeasurementVector[0] = static_cast(referenceIterator.Get()); m_ITKHistogram->IncreaseFrequencyOfMeasurement(currentMeasurementVector, 1); mean = (mean * (static_cast(voxelCount) / static_cast(voxelCount + 1))) // 3 points: old center * 2/3 + currentPoint * 1/3; + static_cast(referenceIterator.Get()) / static_cast(voxelCount + 1); voxelCount += 1.0; } } ++segmentationIterator; } // second pass for SD segmentationIterator.GoToBegin(); referenceIterator.GoToBegin(); while (!segmentationIterator.IsAtEnd()) { itk::Point pt; segmentationItk->TransformIndexToPhysicalPoint(segmentationIterator.GetIndex(), pt); typename ImageType::IndexType ind; itk::ContinuousIndex contInd; if (referenceImage->TransformPhysicalPointToContinuousIndex(pt, contInd)) { for (unsigned int i = 0; i < 3; ++i) ind[i] = ROUND_P(contInd[i]); referenceIterator.SetIndex(ind); if (segmentationIterator.Get() > 0) { sd += ((static_cast(referenceIterator.Get()) - mean) * (static_cast(referenceIterator.Get()) - mean)); } } ++segmentationIterator; } sd /= static_cast(voxelCount - 1); sd = sqrt(sd); // generate quantiles TPixel histogramQuantileValues[5]; histogramQuantileValues[0] = m_ITKHistogram->Quantile(0, 0.05); histogramQuantileValues[1] = m_ITKHistogram->Quantile(0, 0.25); histogramQuantileValues[2] = m_ITKHistogram->Quantile(0, 0.50); histogramQuantileValues[3] = m_ITKHistogram->Quantile(0, 0.75); histogramQuantileValues[4] = m_ITKHistogram->Quantile(0, 0.95); // report histogram values std::locale C("C"); std::locale originalLocale = report.getloc(); report.imbue(C); report << " Minimum:" << minimum << "\n 5% quantile: " << histogramQuantileValues[0] << "\n 25% quantile: " << histogramQuantileValues[1] << "\n 50% quantile: " << histogramQuantileValues[2] << "\n 75% quantile: " << histogramQuantileValues[3] << "\n 95% quantile: " << histogramQuantileValues[4] << "\n Maximum: " << maximum << "\n Mean: " << mean << "\n SD: " << sd << "\n"; report.imbue(originalLocale); } std::string mitk::CalculateGrayValueStatisticsTool::GetReport() const { return m_CompleteReport.str(); } mitk::CalculateGrayValueStatisticsTool::HistogramType::ConstPointer mitk::CalculateGrayValueStatisticsTool::GetHistogram() { return m_ITKHistogram.GetPointer(); } diff --git a/Modules/Segmentation/Interactions/mitkCalculateVolumetryTool.cpp b/Modules/Segmentation/Interactions/mitkCalculateVolumetryTool.cpp index 57e1e69a67..f5b53f6c3a 100644 --- a/Modules/Segmentation/Interactions/mitkCalculateVolumetryTool.cpp +++ b/Modules/Segmentation/Interactions/mitkCalculateVolumetryTool.cpp @@ -1,86 +1,86 @@ /*============================================================================ 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 "mitkCalculateVolumetryTool.h" #include "mitkCalculateVolumetryTool.xpm" #include "mitkProperties.h" #include "mitkToolManager.h" #include "mitkVolumeCalculator.h" namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, CalculateVolumetryTool, "Volumetry tool"); } mitk::CalculateVolumetryTool::CalculateVolumetryTool() { } mitk::CalculateVolumetryTool::~CalculateVolumetryTool() { } const char **mitk::CalculateVolumetryTool::GetXPM() const { return mitkCalculateVolumetryTool_xpm; } const char *mitk::CalculateVolumetryTool::GetName() const { return "Volumetry"; } std::string mitk::CalculateVolumetryTool::GetErrorMessage() { return "Volume could not be calculated for these nodes:"; } bool mitk::CalculateVolumetryTool::ProcessOneWorkingData(DataNode *node) { if (node) { Image::Pointer image = dynamic_cast(node->GetData()); if (image.IsNull()) return false; if (image->GetDimension() == 4) { Tool::ErrorMessage("Volumetry only valid for timestep 0! Bug #1280"); } VolumeCalculator::Pointer volumetryFilter = VolumeCalculator::New(); volumetryFilter->SetImage(image); volumetryFilter->SetThreshold(1); // comparison is >= try { volumetryFilter->ComputeVolume(); float volumeInTimeStep0 = volumetryFilter->GetVolume(); node->SetProperty("volume", FloatProperty::New(volumeInTimeStep0)); } catch (...) { return false; } } return true; } void mitk::CalculateVolumetryTool::FinishProcessingAllData() { Superclass::FinishProcessingAllData(); - m_ToolManager->NodePropertiesChanged(); + this->GetToolManager()->NodePropertiesChanged(); } diff --git a/Modules/Segmentation/Interactions/mitkCreateSurfaceTool.cpp b/Modules/Segmentation/Interactions/mitkCreateSurfaceTool.cpp index 4fa399745d..0e5bcfbe90 100644 --- a/Modules/Segmentation/Interactions/mitkCreateSurfaceTool.cpp +++ b/Modules/Segmentation/Interactions/mitkCreateSurfaceTool.cpp @@ -1,98 +1,98 @@ /*============================================================================ 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 "mitkCreateSurfaceTool.h" #include "mitkCreateSurfaceTool.xpm" #include "mitkProgressBar.h" #include "mitkShowSegmentationAsSurface.h" #include "mitkStatusBar.h" #include "mitkToolManager.h" #include "itkCommand.h" namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, CreateSurfaceTool, "Surface creation tool"); } mitk::CreateSurfaceTool::CreateSurfaceTool() { } mitk::CreateSurfaceTool::~CreateSurfaceTool() { } const char **mitk::CreateSurfaceTool::GetXPM() const { return mitkCreateSurfaceTool_xpm; } const char *mitk::CreateSurfaceTool::GetName() const { return "Surface"; } std::string mitk::CreateSurfaceTool::GetErrorMessage() { return "No surfaces created for these segmentations:"; } bool mitk::CreateSurfaceTool::ProcessOneWorkingData(DataNode *node) { if (node) { Image::Pointer image = dynamic_cast(node->GetData()); if (image.IsNull()) return false; try { mitk::ShowSegmentationAsSurface::Pointer surfaceFilter = mitk::ShowSegmentationAsSurface::New(); // attach observer to get notified about result itk::SimpleMemberCommand::Pointer goodCommand = itk::SimpleMemberCommand::New(); goodCommand->SetCallbackFunction(this, &CreateSurfaceTool::OnSurfaceCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), goodCommand); itk::SimpleMemberCommand::Pointer badCommand = itk::SimpleMemberCommand::New(); badCommand->SetCallbackFunction(this, &CreateSurfaceTool::OnSurfaceCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), badCommand); DataNode::Pointer nodepointer = node; surfaceFilter->SetPointerParameter("Input", image); surfaceFilter->SetPointerParameter("Group node", nodepointer); surfaceFilter->SetParameter("Show result", true); surfaceFilter->SetParameter("Sync visibility", false); - surfaceFilter->SetDataStorage(*m_ToolManager->GetDataStorage()); + surfaceFilter->SetDataStorage(*this->GetToolManager()->GetDataStorage()); ProgressBar::GetInstance()->AddStepsToDo(1); StatusBar::GetInstance()->DisplayText("Surface creation started in background..."); surfaceFilter->StartAlgorithm(); } catch (...) { return false; } } return true; } void mitk::CreateSurfaceTool::OnSurfaceCalculationDone() { ProgressBar::GetInstance()->Progress(); - m_ToolManager->NewNodesGenerated(); + this->GetToolManager()->NewNodesGenerated(); } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp index 1ded558ddc..e74291e8a9 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp @@ -1,335 +1,330 @@ /*============================================================================ 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 "mitkFastMarchingBaseTool.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkInteractionConst.h" #include "mitkRenderingManager.h" #include "mitkInteractionPositionEvent.h" #include "mitkImageAccessByItk.h" #include "mitkSegTool2D.h" #include // itk filter #include "itkBinaryThresholdImageFilter.h" #include "itkCurvatureAnisotropicDiffusionImageFilter.h" #include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" #include "itkSigmoidImageFilter.h" +#include "itkFastMarchingImageFilter.h" // us #include #include #include #include mitk::FastMarchingBaseTool::FastMarchingBaseTool(unsigned int toolDim) : AutoSegmentationWithPreviewTool(false, "FastMarchingTool"), m_LowerThreshold(0), m_UpperThreshold(200), m_StoppingValue(100), m_Sigma(1.0), m_Alpha(-0.5), m_Beta(3.0), m_ToolDimension(toolDim) { } mitk::FastMarchingBaseTool::~FastMarchingBaseTool() { } bool mitk::FastMarchingBaseTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if(!Superclass::CanHandle(referenceData, workingData)) return false; if (referenceData == nullptr) return false; auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; return true; } const char **mitk::FastMarchingBaseTool::GetXPM() const { return nullptr; // mitkFastMarchingBaseTool_xpm; } us::ModuleResource mitk::FastMarchingBaseTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); return resource; } us::ModuleResource mitk::FastMarchingBaseTool::GetCursorIconResource() const { us::Module* module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("FastMarching_Cursor_32x32.png"); return resource; } void mitk::FastMarchingBaseTool::SetUpperThreshold(double value) { m_UpperThreshold = value / 10.0; } void mitk::FastMarchingBaseTool::SetLowerThreshold(double value) { m_LowerThreshold = value / 10.0; } void mitk::FastMarchingBaseTool::SetBeta(double value) { if (m_Beta != value) { m_Beta = value; } } void mitk::FastMarchingBaseTool::SetSigma(double value) { if (m_Sigma != value) { if (value > 0.0) { m_Sigma = value; } } } void mitk::FastMarchingBaseTool::SetAlpha(double value) { if (m_Alpha != value) { m_Alpha = value; } } void mitk::FastMarchingBaseTool::SetStoppingValue(double value) { if (m_StoppingValue != value) { m_StoppingValue = value; } } void mitk::FastMarchingBaseTool::Activated() { Superclass::Activated(); m_SeedsAsPointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_SeedsAsPointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); m_SeedsAsPointSetNode = mitk::DataNode::New(); m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); m_SeedsAsPointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); m_SeedsAsPointSetNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); + this->GetDataStorage()->Add(m_SeedsAsPointSetNode, this->GetToolManager()->GetWorkingData(0)); } void mitk::FastMarchingBaseTool::Deactivated() { this->ClearSeeds(); - m_ToolManager->GetDataStorage()->Remove(m_SeedsAsPointSetNode); + this->GetDataStorage()->Remove(m_SeedsAsPointSetNode); m_SeedsAsPointSetNode = nullptr; m_SeedsAsPointSet = nullptr; Superclass::Deactivated(); } void mitk::FastMarchingBaseTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::FastMarchingBaseTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_SeedsAsPointSet.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { auto workingPlaneGeometry = positionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); // if click was on another plane and we are in 2D mode we should reset the seeds if (m_ToolDimension == 2 && ( nullptr == this->GetWorkingPlaneGeometry() || !this->GetWorkingPlaneGeometry()->IsOnPlane(workingPlaneGeometry))) { this->ClearSeeds(); this->SetWorkingPlaneGeometry(workingPlaneGeometry->Clone()); } m_SeedsAsPointSet->InsertPoint(m_SeedsAsPointSet->GetSize(), positionEvent->GetPositionInWorld()); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - this->UpdatePreview(); } } } void mitk::FastMarchingBaseTool::OnDelete(StateMachineAction*, InteractionEvent* /*interactionEvent*/) { if (!this->IsUpdating() && m_SeedsAsPointSet.IsNotNull()) { // delete last seed point if (this->m_SeedsAsPointSet->GetSize() > 0) { m_SeedsAsPointSet->RemovePointAtEnd(0); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - this->UpdatePreview(); } } } void mitk::FastMarchingBaseTool::ClearSeeds() { if (this->m_SeedsAsPointSet.IsNotNull()) { // renew pointset this->m_SeedsAsPointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_SeedsAsPointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); } } template void mitk::FastMarchingBaseTool::DoITKFastMarching(const itk::Image* inputImage, Image* previewImage, unsigned int timeStep, const BaseGeometry* inputGeometry) { // typedefs for itk pipeline typedef itk::Image InputImageType; typedef float InternalPixelType; typedef itk::Image InternalImageType; typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; typedef itk::Image OutputImageType; typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; typedef itk::SigmoidImageFilter SigmoidFilterType; typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; typedef itk::FastMarchingImageFilter FastMarchingFilterType; typedef typename FastMarchingFilterType::NodeContainer NodeContainer; typedef typename FastMarchingFilterType::NodeType NodeType; //convert point set seed into trialpoint typename NodeContainer::Pointer trialPoints = NodeContainer::New(); trialPoints->Initialize(); for (auto pos = m_SeedsAsPointSet->Begin(); pos != m_SeedsAsPointSet->End(); ++pos) { mitk::Point3D clickInIndex; inputGeometry->WorldToIndex(pos->Value(), clickInIndex); itk::Index seedPosition; for (unsigned int dim = 0; dim < VImageDimension; ++dim) { seedPosition[dim] = clickInIndex[dim]; } NodeType node; const double seedValue = 0.0; node.SetValue(seedValue); node.SetIndex(seedPosition); trialPoints->InsertElement(trialPoints->Size(), node); } // assemble pipeline auto smoothFilter = SmoothingFilterType::New(); smoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); smoothFilter->SetTimeStep(0.05); smoothFilter->SetNumberOfIterations(2); smoothFilter->SetConductanceParameter(9.0); auto gradientMagnitudeFilter = GradientFilterType::New(); gradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); gradientMagnitudeFilter->SetSigma(m_Sigma); auto sigmoidFilter = SigmoidFilterType::New(); sigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); sigmoidFilter->SetAlpha(m_Alpha); sigmoidFilter->SetBeta(m_Beta); sigmoidFilter->SetOutputMinimum(0.0); sigmoidFilter->SetOutputMaximum(1.0); auto fastMarchingFilter = FastMarchingFilterType::New(); fastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); fastMarchingFilter->SetStoppingValue(m_StoppingValue); fastMarchingFilter->SetTrialPoints(trialPoints); auto thresholdFilter = ThresholdingFilterType::New(); thresholdFilter->SetLowerThreshold(m_LowerThreshold); thresholdFilter->SetUpperThreshold(m_UpperThreshold); thresholdFilter->SetOutsideValue(0); thresholdFilter->SetInsideValue(1.0); // set up pipeline smoothFilter->SetInput(inputImage); gradientMagnitudeFilter->SetInput(smoothFilter->GetOutput()); sigmoidFilter->SetInput(gradientMagnitudeFilter->GetOutput()); fastMarchingFilter->SetInput(sigmoidFilter->GetOutput()); thresholdFilter->SetInput(fastMarchingFilter->GetOutput()); thresholdFilter->Update(); if (nullptr == this->GetWorkingPlaneGeometry()) { previewImage->SetVolume((void*)(thresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } else { mitk::Image::Pointer sliceImage = mitk::Image::New(); mitk::CastToMitkImage(thresholdFilter->GetOutput(), sliceImage); SegTool2D::WriteSliceToVolume(previewImage, this->GetWorkingPlaneGeometry(), sliceImage, timeStep, false); } } -void mitk::FastMarchingBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) +void mitk::FastMarchingBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, Image* previewImage, TimeStepType timeStep) { if (nullptr != inputAtTimeStep && nullptr != previewImage && m_SeedsAsPointSet.IsNotNull() && m_SeedsAsPointSet->GetSize()>0) { if (nullptr == this->GetWorkingPlaneGeometry()) { AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 3, (previewImage, timeStep, inputAtTimeStep->GetGeometry())); } else { AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 2, (previewImage, timeStep, inputAtTimeStep->GetGeometry())); } } } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h index ebec9fd0b2..68a4c6ffcb 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h @@ -1,118 +1,117 @@ /*============================================================================ 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 mitkFastMarchingBaseTool_h_Included #define mitkFastMarchingBaseTool_h_Included #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkPointSet.h" #include "mitkPointSetDataInteractor.h" #include "mitkToolCommand.h" #include "itkImage.h" -#include "itkFastMarchingImageFilter.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief FastMarching semgentation tool base class. The segmentation is done by setting one or more seed points on the image and adapting the time range and threshold. The pipeline is: Smoothing->GradientMagnitude->SigmoidFunction->FastMarching->Threshold The resulting binary image is seen as a segmentation of an object. For detailed documentation see ITK Software Guide section 9.3.1 Fast Marching Segmentation. */ class MITKSEGMENTATION_EXPORT FastMarchingBaseTool : public AutoSegmentationWithPreviewTool { public: mitkClassMacro(FastMarchingBaseTool, AutoSegmentationWithPreviewTool); bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /* icon stuff */ const char **GetXPM() const override; us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; void Activated() override; void Deactivated() override; /// \brief Set parameter used in Threshold filter. void SetUpperThreshold(double); /// \brief Set parameter used in Threshold filter. void SetLowerThreshold(double); /// \brief Set parameter used in Fast Marching filter. void SetStoppingValue(double); /// \brief Set parameter used in Gradient Magnitude filter. void SetSigma(double); /// \brief Set parameter used in Fast Marching filter. void SetAlpha(double); /// \brief Set parameter used in Fast Marching filter. void SetBeta(double); /// \brief Clear all seed points. void ClearSeeds(); protected: FastMarchingBaseTool(unsigned int toolDim); ~FastMarchingBaseTool() override; void ConnectActionsAndFunctions() override; /// \brief Add point action of StateMachine pattern virtual void OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent); /// \brief Delete action of StateMachine pattern virtual void OnDelete(StateMachineAction*, InteractionEvent* interactionEvent); - void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; template void DoITKFastMarching(const itk::Image* inputImage, Image* segmentation, unsigned int timeStep, const BaseGeometry* inputGeometry); float m_LowerThreshold; // used in Threshold filter float m_UpperThreshold; // used in Threshold filter float m_StoppingValue; // used in Fast Marching filter float m_Sigma; // used in GradientMagnitude filter float m_Alpha; // used in Sigmoid filter float m_Beta; // used in Sigmoid filter DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points PointSet::Pointer m_SeedsAsPointSet; private: /** Indicating if the tool is used in 2D mode (just segment the current slice) * or 3D mode (segment the whole current volume),*/ unsigned int m_ToolDimension; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp index c6eb82b51a..e37f014527 100644 --- a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp @@ -1,278 +1,278 @@ /*============================================================================ 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 "mitkFeedbackContourTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkStringProperty.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include "mitkAbstractTransformGeometry.h" mitk::FeedbackContourTool::FeedbackContourTool(const char *type) : SegTool2D(type), m_FeedbackContourVisible(false) { m_FeedbackContourNode = DataNode::New(); m_FeedbackContourNode->SetProperty("name", StringProperty::New("One of FeedbackContourTool's feedback nodes")); m_FeedbackContourNode->SetProperty("visible", BoolProperty::New(true)); m_FeedbackContourNode->SetProperty("helper object", BoolProperty::New(true)); m_FeedbackContourNode->SetProperty("layer", IntProperty::New(1000)); m_FeedbackContourNode->SetProperty("contour.project-onto-plane", BoolProperty::New(false)); m_FeedbackContourNode->SetProperty("contour.width", FloatProperty::New(1.0)); // set to max short, max int doesn't work, max value somewhere hardcoded? m_FeedbackContourNode->SetProperty("layer", IntProperty::New(std::numeric_limits::max())); m_FeedbackContourNode->SetProperty("fixedLayer", BoolProperty::New(true)); this->InitializeFeedbackContour(true); SetFeedbackContourColorDefault(); } mitk::FeedbackContourTool::~FeedbackContourTool() { } void mitk::FeedbackContourTool::SetFeedbackContourColor(float r, float g, float b) { m_FeedbackContourNode->SetProperty("contour.color", ColorProperty::New(r, g, b)); } void mitk::FeedbackContourTool::SetFeedbackContourColorDefault() { m_FeedbackContourNode->SetProperty("contour.color", ColorProperty::New(0.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0)); } void mitk::FeedbackContourTool::Deactivated() { Superclass::Deactivated(); - DataStorage *storage = m_ToolManager->GetDataStorage(); + DataStorage *storage = this->GetToolManager()->GetDataStorage(); if (storage && m_FeedbackContourNode.IsNotNull()) { storage->Remove(m_FeedbackContourNode); m_FeedbackContour->Clear(); SetFeedbackContourVisible(false); } } void mitk::FeedbackContourTool::Activated() { Superclass::Activated(); this->InitializeFeedbackContour(true); this->SetFeedbackContourVisible(true); } const mitk::ContourModel *mitk::FeedbackContourTool::GetFeedbackContour() const { return m_FeedbackContour; } void mitk::FeedbackContourTool::InitializeFeedbackContour(bool isClosed) { m_FeedbackContour = ContourModel::New(); m_FeedbackContour->SetClosed(isClosed); auto workingImage = this->GetWorkingData(); if (nullptr != workingImage) { m_FeedbackContour->Expand(workingImage->GetTimeSteps()); auto contourTimeGeometry = workingImage->GetTimeGeometry()->Clone(); contourTimeGeometry->ReplaceTimeStepGeometries(m_FeedbackContour->GetGeometry()); m_FeedbackContour->SetTimeGeometry(contourTimeGeometry); for (unsigned int t = 0; t < m_FeedbackContour->GetTimeSteps(); ++t) { m_FeedbackContour->SetClosed(isClosed, t); } } m_FeedbackContourNode->SetData(m_FeedbackContour); } void mitk::FeedbackContourTool::ClearsCurrentFeedbackContour(bool isClosed) { if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) { MITK_WARN << "Cannot clear feedback contour at current time step. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: " << this->GetLastTimePointTriggered(); return; } auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); m_FeedbackContour->Clear(feedbackTimeStep); m_FeedbackContour->SetClosed(isClosed, feedbackTimeStep); } void mitk::FeedbackContourTool::UpdateCurrentFeedbackContour(const ContourModel* sourceModel, TimeStepType sourceTimeStep) { if (nullptr == sourceModel) return; if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) { MITK_WARN << "Cannot update feedback contour. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: "<GetLastTimePointTriggered(); return; } auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); this->UpdateFeedbackContour(sourceModel, feedbackTimeStep, sourceTimeStep); } void mitk::FeedbackContourTool::UpdateFeedbackContour(const ContourModel* sourceModel, TimeStepType feedbackTimeStep, TimeStepType sourceTimeStep) { if (nullptr == sourceModel) return; if (!sourceModel->GetTimeGeometry()->IsValidTimeStep(sourceTimeStep)) { MITK_WARN << "Cannot update feedback contour. Source contour time geometry does not support passed time step. Invalid time step: " << sourceTimeStep; return; } if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimeStep(feedbackTimeStep)) { MITK_WARN << "Cannot update feedback contour. Feedback contour time geometry does not support passed time step. Invalid time step: "<UpdateContour(sourceModel, feedbackTimeStep, sourceTimeStep); } void mitk::FeedbackContourTool::AddVertexToCurrentFeedbackContour(const Point3D& point) { if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) { MITK_WARN << "Cannot add vertex to feedback contour. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: " << this->GetLastTimePointTriggered(); return; } auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); this->AddVertexToFeedbackContour(point, feedbackTimeStep); }; /** Adds a vertex to the feedback contour for the passed time step. If time step is invalid, nothing will be added.*/ void mitk::FeedbackContourTool::AddVertexToFeedbackContour(const Point3D& point, TimeStepType feedbackTimeStep) { if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimeStep(feedbackTimeStep)) { MITK_WARN << "Cannot add vertex to feedback contour. Feedback contour time geometry does not support passed time step. Invalid time step: " << feedbackTimeStep; return; } m_FeedbackContour->AddVertex(point, feedbackTimeStep); } void mitk::FeedbackContourTool::SetFeedbackContourVisible(bool visible) { if (m_FeedbackContourVisible == visible) return; // nothing changed - if (DataStorage *storage = m_ToolManager->GetDataStorage()) + if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { if (visible) { // Add the feedback contour node as a derived node of the first working data. // If there is no working data, the node is added at the top level. storage->Add(m_FeedbackContourNode, this->GetWorkingDataNode()); } else { storage->Remove(m_FeedbackContourNode); } } m_FeedbackContourVisible = visible; } mitk::ContourModel::Pointer mitk::FeedbackContourTool::ProjectContourTo2DSlice(const Image *slice, const ContourModel *contourIn3D, bool correctionForIpSegmentation, bool constrainToInside) { return mitk::ContourModelUtils::ProjectContourTo2DSlice( slice, contourIn3D, correctionForIpSegmentation, constrainToInside); } mitk::ContourModel::Pointer mitk::FeedbackContourTool::BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, const ContourModel *contourIn2D, bool correctionForIpSegmentation) { return mitk::ContourModelUtils::BackProjectContourFrom2DSlice( sliceGeometry, contourIn2D, correctionForIpSegmentation); } void mitk::FeedbackContourTool::WriteBackFeedbackContourAsSegmentationResult(const InteractionPositionEvent* positionEvent, int paintingPixelValue, bool setInvisibleAfterSuccess) { if (!positionEvent) return; auto workingImage = this->GetWorkingData(); const auto planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (!workingImage || !planeGeometry) return; const auto* abstractTransformGeometry( dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (nullptr != abstractTransformGeometry) return; Image::Pointer slice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); if (slice.IsNull()) { MITK_ERROR << "Unable to extract slice." << std::endl; return; } const auto feedbackContour = FeedbackContourTool::GetFeedbackContour(); auto contourTimeStep = positionEvent->GetSender()->GetTimeStep(feedbackContour); ContourModel::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( slice, feedbackContour, false, false); // false: don't add 0.5 (done by FillContourInSlice) // false: don't constrain the contour to the image's inside if (projectedContour.IsNull()) return; auto activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); mitk::ContourModelUtils::FillContourInSlice( projectedContour, contourTimeStep, slice, workingImage, paintingPixelValue * activePixelValue); this->WriteBackSegmentationResult(positionEvent, slice); if (setInvisibleAfterSuccess) { this->SetFeedbackContourVisible(false); } } void mitk::FeedbackContourTool::FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, int paintingPixelValue) { this->FillContourInSlice(projectedContour, 0, sliceImage, paintingPixelValue); } void mitk::FeedbackContourTool::FillContourInSlice(ContourModel *projectedContour, unsigned int timeStep, Image *sliceImage, int paintingPixelValue) { mitk::ContourModelUtils::FillContourInSlice(projectedContour, timeStep, sliceImage, sliceImage, paintingPixelValue); } diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp index 5870887320..9ba41907cd 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -1,620 +1,620 @@ /*============================================================================ 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 namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, LiveWireTool2D, "LiveWire tool"); } mitk::LiveWireTool2D::LiveWireTool2D() : SegTool2D("LiveWireTool"), m_CreateAndUseDynamicCosts(false) { } mitk::LiveWireTool2D::~LiveWireTool2D() { this->ClearSegmentation(); } void mitk::LiveWireTool2D::RemoveHelperObjects() { - auto dataStorage = m_ToolManager->GetDataStorage(); + auto dataStorage = this->GetToolManager()->GetDataStorage(); if (nullptr == dataStorage) return; for (const auto &editingContour : m_EditingContours) dataStorage->Remove(editingContour.first); for (const auto &workingContour : m_WorkingContours) dataStorage->Remove(workingContour.first); if (m_EditingContourNode.IsNotNull()) dataStorage->Remove(m_EditingContourNode); if (m_LiveWireContourNode.IsNotNull()) dataStorage->Remove(m_LiveWireContourNode); if (m_ContourNode.IsNotNull()) dataStorage->Remove(m_ContourNode); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::LiveWireTool2D::ReleaseHelperObjects() { this->RemoveHelperObjects(); m_EditingContours.clear(); m_WorkingContours.clear(); m_EditingContourNode = nullptr; m_EditingContour = nullptr; m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; m_ContourNode = nullptr; m_Contour = nullptr; } void mitk::LiveWireTool2D::ReleaseInteractors() { this->EnableContourLiveWireInteraction(false); m_LiveWireInteractors.clear(); } void mitk::LiveWireTool2D::ConnectActionsAndFunctions() { CONNECT_CONDITION("CheckContourClosed", OnCheckPoint); CONNECT_FUNCTION("InitObject", OnInitLiveWire); CONNECT_FUNCTION("AddPoint", OnAddPoint); CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); CONNECT_FUNCTION("MovePoint", OnMouseMoveNoDynamicCosts); CONNECT_FUNCTION("FinishContour", OnFinish); CONNECT_FUNCTION("DeletePoint", OnLastSegmentDelete); CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); } const char **mitk::LiveWireTool2D::GetXPM() const { return mitkLiveWireTool2D_xpm; } us::ModuleResource mitk::LiveWireTool2D::GetIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_48x48.png"); } us::ModuleResource mitk::LiveWireTool2D::GetCursorIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_Cursor_32x32.png"); } const char *mitk::LiveWireTool2D::GetName() const { return "Live Wire"; } void mitk::LiveWireTool2D::Activated() { Superclass::Activated(); this->ResetToStartState(); this->EnableContourLiveWireInteraction(true); } void mitk::LiveWireTool2D::Deactivated() { this->ConfirmSegmentation(); Superclass::Deactivated(); } void mitk::LiveWireTool2D::UpdateLiveWireContour() { if (m_Contour.IsNotNull()) { auto timeGeometry = m_Contour->GetTimeGeometry()->Clone(); m_LiveWireContour = this->m_LiveWireFilter->GetOutput(); m_LiveWireContour->SetTimeGeometry(timeGeometry); //needed because the results of the filter are always from 0 ms to 1 ms and the filter also resets its outputs. m_LiveWireContourNode->SetData(this->m_LiveWireContour); } } void mitk::LiveWireTool2D::OnTimePointChanged() { auto reference = this->GetReferenceData(); if (nullptr == reference || m_PlaneGeometry.IsNull() || m_LiveWireFilter.IsNull() || m_LiveWireContourNode.IsNull()) return; auto timeStep = reference->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); m_ReferenceDataSlice = GetAffectedImageSliceAs2DImageByTimePoint(m_PlaneGeometry, reference, timeStep); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); m_LiveWireFilter->Update(); this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdateAll(); }; void mitk::LiveWireTool2D::EnableContourLiveWireInteraction(bool on) { for (const auto &interactor : m_LiveWireInteractors) interactor->EnableInteraction(on); } void mitk::LiveWireTool2D::ConfirmSegmentation() { auto referenceImage = this->GetReferenceData(); auto workingImage = this->GetWorkingData(); if (nullptr != referenceImage && nullptr != workingImage) { std::vector sliceInfos; sliceInfos.reserve(m_WorkingContours.size()); const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); for (const auto &workingContour : m_WorkingContours) { auto contour = dynamic_cast(workingContour.first->GetData()); if (nullptr == contour || contour->IsEmpty()) continue; auto sameSlicePredicate = [&workingContour, workingImageTimeStep](const SliceInformation& si) { return workingContour.second->IsOnPlane(si.plane) && workingImageTimeStep == si.timestep; }; auto finding = std::find_if(sliceInfos.begin(), sliceInfos.end(), sameSlicePredicate); if (finding == sliceInfos.end()) { auto workingSlice = this->GetAffectedImageSliceAs2DImage(workingContour.second, workingImage, workingImageTimeStep)->Clone(); sliceInfos.emplace_back(workingSlice, workingContour.second, workingImageTimeStep); finding = std::prev(sliceInfos.end()); } //cast const away is OK in this case, because these are all slices created and manipulated //localy in this function call. And we want to keep the high constness of SliceInformation for //public interfaces. auto workingSlice = const_cast(finding->slice.GetPointer()); auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour, true, false); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); ContourModelUtils::FillContourInSlice( projectedContour, workingSlice, workingImage, activePixelValue); } this->WriteBackSegmentationResults(sliceInfos); } this->ClearSegmentation(); } void mitk::LiveWireTool2D::ClearSegmentation() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); this->ResetToStartState(); } bool mitk::LiveWireTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent *positionEvent, mitk::BaseData *data) { bool isPositionEventInsideImageRegion = nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); if (!isPositionEventInsideImageRegion) MITK_WARN("LiveWireTool2D") << "PositionEvent is outside ImageRegion!"; return isPositionEventInsideImageRegion; } mitk::ContourModel::Pointer mitk::LiveWireTool2D::CreateNewContour() const { auto workingData = this->GetWorkingData(); if (nullptr == workingData) { this->InteractiveSegmentationBugMessage("Cannot create new contour. No valid working data is set. Application is in invalid state."); mitkThrow() << "Cannot create new contour. No valid working data is set. Application is in invalid state."; } auto contour = ContourModel::New(); //generate a time geometry that is always visible as the working contour should always be. auto contourTimeGeometry = ProportionalTimeGeometry::New(); contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); contour->SetTimeGeometry(contourTimeGeometry); return contour; } void mitk::LiveWireTool2D::OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; auto workingDataNode = this->GetWorkingDataNode(); if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) { this->ResetToStartState(); return; } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_Contour = this->CreateNewContour(); m_ContourNode = mitk::DataNode::New(); m_ContourNode->SetData(m_Contour); m_ContourNode->SetName("working contour node"); m_ContourNode->SetProperty("layer", IntProperty::New(100)); m_ContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_ContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_ContourNode->AddProperty("contour.color", ColorProperty::New(1.0f, 1.0f, 0.0f), nullptr, true); m_ContourNode->AddProperty("contour.points.color", ColorProperty::New(1.0f, 0.0f, 0.1f), nullptr, true); m_ContourNode->AddProperty("contour.controlpoints.show", BoolProperty::New(true), nullptr, true); m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode = mitk::DataNode::New(); m_LiveWireContourNode->SetData(m_LiveWireContour); m_LiveWireContourNode->SetName("active livewire node"); m_LiveWireContourNode->SetProperty("layer", IntProperty::New(101)); m_LiveWireContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_LiveWireContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_LiveWireContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_LiveWireContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); m_EditingContour = this->CreateNewContour(); m_EditingContourNode = mitk::DataNode::New(); m_EditingContourNode->SetData(m_EditingContour); m_EditingContourNode->SetName("editing node"); m_EditingContourNode->SetProperty("layer", IntProperty::New(102)); m_EditingContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_EditingContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_EditingContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_EditingContourNode->AddProperty("contour.points.color", ColorProperty::New(0.0f, 0.0f, 1.0f), nullptr, true); m_EditingContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); - auto dataStorage = m_ToolManager->GetDataStorage(); + auto dataStorage = this->GetToolManager()->GetDataStorage(); dataStorage->Add(m_ContourNode, workingDataNode); dataStorage->Add(m_LiveWireContourNode, workingDataNode); dataStorage->Add(m_EditingContourNode, workingDataNode); // Set current slice as input for ImageToLiveWireContourFilter m_ReferenceDataSlice = this->GetAffectedReferenceSlice(positionEvent); auto origin = m_ReferenceDataSlice->GetSlicedGeometry()->GetOrigin(); m_ReferenceDataSlice->GetSlicedGeometry()->WorldToIndex(origin, origin); m_ReferenceDataSlice->GetSlicedGeometry()->IndexToWorld(origin, origin); m_ReferenceDataSlice->GetSlicedGeometry()->SetOrigin(origin); m_LiveWireFilter = ImageLiveWireContourModelFilter::New(); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); // Map click to pixel coordinates auto click = positionEvent->GetPositionInWorld(); itk::Index<3> idx; m_ReferenceDataSlice->GetGeometry()->WorldToIndex(click, idx); // Get the pixel with the highest gradient in a 7x7 region itk::Index<3> indexWithHighestGradient; AccessFixedDimensionByItk_2(m_ReferenceDataSlice, FindHighestGradientMagnitudeByITK, 2, idx, indexWithHighestGradient); click[0] = indexWithHighestGradient[0]; click[1] = indexWithHighestGradient[1]; click[2] = indexWithHighestGradient[2]; m_ReferenceDataSlice->GetGeometry()->IndexToWorld(click, click); // Set initial start point m_Contour->AddVertex(click, true); m_LiveWireFilter->SetStartPoint(click); // Remember PlaneGeometry to determine if events were triggered in the same plane m_PlaneGeometry = interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); m_CreateAndUseDynamicCosts = true; mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { // Complete LiveWire interaction for the last segment. Add current LiveWire contour to // the finished contour and reset to start a new segment and computation. auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } // Add repulsive points to avoid getting the same path again std::for_each(m_LiveWireContour->IteratorBegin(), m_LiveWireContour->IteratorEnd(), [this](ContourElement::VertexType *vertex) { ImageLiveWireContourModelFilter::InternalImageType::IndexType idx; this->m_ReferenceDataSlice->GetGeometry()->WorldToIndex(vertex->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); }); // Remove duplicate first vertex, it's already contained in m_Contour m_LiveWireContour->RemoveVertexAt(0); // Set last point as control point m_LiveWireContour->SetControlVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1); // Merge contours m_Contour->Concatenate(m_LiveWireContour); // Clear the LiveWire contour and reset the corresponding DataNode m_LiveWireContour->Clear(); // Set new start point m_LiveWireFilter->SetStartPoint(positionEvent->GetPositionInWorld()); if (m_CreateAndUseDynamicCosts) { // Use dynamic cost map for next update m_LiveWireFilter->CreateDynamicCostMap(m_Contour); m_LiveWireFilter->SetUseDynamicCostMap(true); } mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { // Compute LiveWire segment from last control point to current mouse position auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; m_LiveWireFilter->SetEndPoint(positionEvent->GetPositionInWorld()); m_LiveWireFilter->Update(); this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) { m_LiveWireFilter->SetUseDynamicCostMap(false); this->OnMouseMoved(nullptr, interactionEvent); m_LiveWireFilter->SetUseDynamicCostMap(true); } bool mitk::LiveWireTool2D::OnCheckPoint(const InteractionEvent *interactionEvent) { // Check double click on first control point to finish the LiveWire tool auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return false; mitk::Point3D click = positionEvent->GetPositionInWorld(); mitk::Point3D first = this->m_Contour->GetVertexAt(0)->Coordinates; return first.EuclideanDistanceTo(click) < 4.5; } void mitk::LiveWireTool2D::OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) { // Finish LiveWire tool interaction auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; // Remove last control point added by double click m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices() - 1); // Save contour and corresponding plane geometry to list this->m_WorkingContours.emplace_back(std::make_pair(m_ContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); this->m_EditingContours.emplace_back(std::make_pair(m_EditingContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); m_LiveWireFilter->SetUseDynamicCostMap(false); this->FinishTool(); } void mitk::LiveWireTool2D::FinishTool() { auto numberOfTimesteps = static_cast(m_Contour->GetTimeSteps()); for (int i = 0; i <= numberOfTimesteps; ++i) m_Contour->Close(i); - m_ToolManager->GetDataStorage()->Remove(m_LiveWireContourNode); + this->GetToolManager()->GetDataStorage()->Remove(m_LiveWireContourNode); m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; m_ContourInteractor = mitk::ContourModelLiveWireInteractor::New(); m_ContourInteractor->SetDataNode(m_ContourNode); m_ContourInteractor->LoadStateMachine("ContourModelModificationInteractor.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetEventConfig("ContourModelModificationConfig.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetWorkingImage(this->m_ReferenceDataSlice); m_ContourInteractor->SetEditingContourModelNode(this->m_EditingContourNode); m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); this->m_LiveWireInteractors.push_back(m_ContourInteractor); } void mitk::LiveWireTool2D::OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) { // If last point of current contour will be removed go to start state and remove nodes if (m_Contour->GetNumberOfVertices() <= 1) { - auto dataStorage = m_ToolManager->GetDataStorage(); + auto dataStorage = this->GetToolManager()->GetDataStorage(); dataStorage->Remove(m_LiveWireContourNode); dataStorage->Remove(m_ContourNode); dataStorage->Remove(m_EditingContourNode); m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); m_Contour = this->CreateNewContour(); m_ContourNode->SetData(m_Contour); this->ResetToStartState(); } else // Remove last segment from contour and reset LiveWire contour { m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); auto newContour = this->CreateNewContour(); auto begin = m_Contour->IteratorBegin(); // Iterate from last point to next active point auto newLast = m_Contour->IteratorBegin() + (m_Contour->GetNumberOfVertices() - 1); // Go at least one down if (newLast != begin) --newLast; // Search next active control point while (newLast != begin && !((*newLast)->IsControlPoint)) --newLast; // Set position of start point for LiveWire filter to coordinates of the new last point m_LiveWireFilter->SetStartPoint((*newLast)->Coordinates); auto it = m_Contour->IteratorBegin(); // Fll new Contour while (it <= newLast) { newContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint); ++it; } newContour->SetClosed(m_Contour->IsClosed()); m_ContourNode->SetData(newContour); m_Contour = newContour; mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } template void mitk::LiveWireTool2D::FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; const auto MAX_X = inputImage->GetLargestPossibleRegion().GetSize()[0]; const auto MAX_Y = inputImage->GetLargestPossibleRegion().GetSize()[1]; returnIndex[0] = index[0]; returnIndex[1] = index[1]; returnIndex[2] = 0.0; double gradientMagnitude = 0.0; double maxGradientMagnitude = 0.0; // The size and thus the region of 7x7 is only used to calculate the gradient magnitude in that region, // not for searching the maximum value. // Maximum value in each direction for size typename InputImageType::SizeType size; size[0] = 7; size[1] = 7; // Minimum value in each direction for startRegion IndexType startRegion; startRegion[0] = index[0] - 3; startRegion[1] = index[1] - 3; if (startRegion[0] < 0) startRegion[0] = 0; if (startRegion[1] < 0) startRegion[1] = 0; if (MAX_X - index[0] < 7) startRegion[0] = MAX_X - 7; if (MAX_Y - index[1] < 7) startRegion[1] = MAX_Y - 7; index[0] = startRegion[0] + 3; index[1] = startRegion[1] + 3; typename InputImageType::RegionType region; region.SetSize(size); region.SetIndex(startRegion); typedef typename itk::GradientMagnitudeImageFilter GradientMagnitudeFilterType; typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); gradientFilter->SetInput(inputImage); gradientFilter->GetOutput()->SetRequestedRegion(region); gradientFilter->Update(); typename InputImageType::Pointer gradientMagnitudeImage; gradientMagnitudeImage = gradientFilter->GetOutput(); IndexType currentIndex; currentIndex[0] = 0; currentIndex[1] = 0; // Search max (approximate) gradient magnitude for (int x = -1; x <= 1; ++x) { currentIndex[0] = index[0] + x; for (int y = -1; y <= 1; ++y) { currentIndex[1] = index[1] + y; gradientMagnitude = gradientMagnitudeImage->GetPixel(currentIndex); // Check for new max if (maxGradientMagnitude < gradientMagnitude) { maxGradientMagnitude = gradientMagnitude; returnIndex[0] = currentIndex[0]; returnIndex[1] = currentIndex[1]; returnIndex[2] = 0.0; } } currentIndex[1] = index[1]; } } diff --git a/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp b/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp index 54231db23f..080fb76ab2 100644 --- a/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp @@ -1,566 +1,566 @@ /*============================================================================ 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 "mitkPaintbrushTool.h" #include "mitkAbstractTransformGeometry.h" #include "mitkBaseRenderer.h" #include "mitkToolManager.h" #include "mitkContourModelUtils.h" #include "mitkLevelWindowProperty.h" int mitk::PaintbrushTool::m_Size = 1; mitk::PaintbrushTool::PaintbrushTool(int paintingPixelValue) : FeedbackContourTool("PressMoveReleaseWithCTRLInversionAllMouseMoves"), m_PaintingPixelValue(paintingPixelValue), m_LastContourSize(0) // other than initial mitk::PaintbrushTool::m_Size (around l. 28) { m_MasterContour = ContourModel::New(); m_MasterContour->Initialize(); m_CurrentPlane = nullptr; m_WorkingNode = DataNode::New(); m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true)); } mitk::PaintbrushTool::~PaintbrushTool() { } void mitk::PaintbrushTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnPrimaryButtonPressedMoved); CONNECT_FUNCTION("MouseMove", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); CONNECT_FUNCTION("InvertLogic", OnInvertLogic); } void mitk::PaintbrushTool::Activated() { Superclass::Activated(); FeedbackContourTool::SetFeedbackContourVisible(true); SizeChanged.Send(m_Size); - m_ToolManager->WorkingDataChanged += + this->GetToolManager()->WorkingDataChanged += mitk::MessageDelegate(this, &mitk::PaintbrushTool::OnToolManagerWorkingDataModified); } void mitk::PaintbrushTool::Deactivated() { FeedbackContourTool::SetFeedbackContourVisible(false); - if (m_ToolManager->GetDataStorage()->Exists(m_WorkingNode)) - m_ToolManager->GetDataStorage()->Remove(m_WorkingNode); + if (this->GetToolManager()->GetDataStorage()->Exists(m_WorkingNode)) + this->GetToolManager()->GetDataStorage()->Remove(m_WorkingNode); m_WorkingSlice = nullptr; m_CurrentPlane = nullptr; - m_ToolManager->WorkingDataChanged -= + this->GetToolManager()->WorkingDataChanged -= mitk::MessageDelegate(this, &mitk::PaintbrushTool::OnToolManagerWorkingDataModified); Superclass::Deactivated(); } void mitk::PaintbrushTool::SetSize(int value) { m_Size = value; } mitk::Point2D mitk::PaintbrushTool::upperLeft(mitk::Point2D p) { p[0] -= 0.5; p[1] += 0.5; return p; } void mitk::PaintbrushTool::UpdateContour(const InteractionPositionEvent *positionEvent) { // MITK_INFO<<"Update..."; // examine stateEvent and create a contour that matches the pixel mask that we are going to draw // mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); // const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return; // Get Spacing of current Slice // mitk::Vector3D vSpacing = m_WorkingSlice->GetSlicedGeometry()->GetPlaneGeometry(0)->GetSpacing(); // // Draw a contour in Square according to selected brush size // int radius = (m_Size) / 2; float fradius = static_cast(m_Size) / 2.0f; ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New(); // estimate center point of the brush ( relative to the pixel the mouse points on ) // -- left upper corner for even sizes, // -- midpoint for uneven sizes mitk::Point2D centerCorrection; centerCorrection.Fill(0); // even --> correction of [+0.5, +0.5] bool evenSize = ((m_Size % 2) == 0); if (evenSize) { centerCorrection[0] += 0.5; centerCorrection[1] += 0.5; } // we will compute the control points for the upper left quarter part of a circle contour std::vector quarterCycleUpperRight; std::vector quarterCycleLowerRight; std::vector quarterCycleLowerLeft; std::vector quarterCycleUpperLeft; mitk::Point2D curPoint; bool curPointIsInside = true; curPoint[0] = 0; curPoint[1] = radius; quarterCycleUpperRight.push_back(upperLeft(curPoint)); // to estimate if a pixel is inside the circle, we need to compare against the 'outer radius' // i.e. the distance from the midpoint [0,0] to the border of the pixel [0,radius] // const float outer_radius = static_cast(radius) + 0.5; while (curPoint[1] > 0) { // Move right until pixel is outside circle float curPointX_squared = 0.0f; float curPointY_squared = (curPoint[1] - centerCorrection[1]) * (curPoint[1] - centerCorrection[1]); while (curPointIsInside) { // increment posX and chec curPoint[0]++; curPointX_squared = (curPoint[0] - centerCorrection[0]) * (curPoint[0] - centerCorrection[0]); const float len = sqrt(curPointX_squared + curPointY_squared); if (len > fradius) { // found first Pixel in this horizontal line, that is outside the circle curPointIsInside = false; } } quarterCycleUpperRight.push_back(upperLeft(curPoint)); // Move down until pixel is inside circle while (!curPointIsInside) { // increment posX and chec curPoint[1]--; curPointY_squared = (curPoint[1] - centerCorrection[1]) * (curPoint[1] - centerCorrection[1]); const float len = sqrt(curPointX_squared + curPointY_squared); if (len <= fradius) { // found first Pixel in this horizontal line, that is outside the circle curPointIsInside = true; quarterCycleUpperRight.push_back(upperLeft(curPoint)); } // Quarter cycle is full, when curPoint y position is 0 if (curPoint[1] <= 0) break; } } // QuarterCycle is full! Now copy quarter cycle to other quarters. if (!evenSize) { std::vector::const_iterator it = quarterCycleUpperRight.begin(); while (it != quarterCycleUpperRight.end()) { mitk::Point2D p; p = *it; // the contour points in the lower right corner have same position but with negative y values p[1] *= -1; quarterCycleLowerRight.push_back(p); // the contour points in the lower left corner have same position // but with both x,y negative p[0] *= -1; quarterCycleLowerLeft.push_back(p); // the contour points in the upper left corner have same position // but with x negative p[1] *= -1; quarterCycleUpperLeft.push_back(p); it++; } } else { std::vector::const_iterator it = quarterCycleUpperRight.begin(); while (it != quarterCycleUpperRight.end()) { mitk::Point2D p, q; p = *it; q = p; // the contour points in the lower right corner have same position but with negative y values q[1] *= -1; // correct for moved offset if size even = the midpoint is not the midpoint of the current pixel // but its upper rigt corner q[1] += 1; quarterCycleLowerRight.push_back(q); q = p; // the contour points in the lower left corner have same position // but with both x,y negative q[1] = -1.0f * q[1] + 1; q[0] = -1.0f * q[0] + 1; quarterCycleLowerLeft.push_back(q); // the contour points in the upper left corner have same position // but with x negative q = p; q[0] *= -1; q[0] += 1; quarterCycleUpperLeft.push_back(q); it++; } } // fill contour with poins in right ordering, starting with the upperRight block mitk::Point3D tempPoint; for (unsigned int i = 0; i < quarterCycleUpperRight.size(); i++) { tempPoint[0] = quarterCycleUpperRight[i][0]; tempPoint[1] = quarterCycleUpperRight[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } // the lower right has to be parsed in reverse order for (int i = quarterCycleLowerRight.size() - 1; i >= 0; i--) { tempPoint[0] = quarterCycleLowerRight[i][0]; tempPoint[1] = quarterCycleLowerRight[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } for (unsigned int i = 0; i < quarterCycleLowerLeft.size(); i++) { tempPoint[0] = quarterCycleLowerLeft[i][0]; tempPoint[1] = quarterCycleLowerLeft[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } // the upper left also has to be parsed in reverse order for (int i = quarterCycleUpperLeft.size() - 1; i >= 0; i--) { tempPoint[0] = quarterCycleUpperLeft[i][0]; tempPoint[1] = quarterCycleUpperLeft[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } m_MasterContour = contourInImageIndexCoordinates; } /** Just show the contour, get one point as the central point and add surrounding points to the contour. */ void mitk::PaintbrushTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { if (m_WorkingSlice.IsNull()) return; auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_WorkingSlice->GetGeometry()->WorldToIndex(positionEvent->GetPositionInWorld(), m_LastPosition); // create new working node // a fresh node is needed to only display the actual drawing process for // the undo function - if (m_ToolManager->GetDataStorage()->Exists(m_WorkingNode)) - m_ToolManager->GetDataStorage()->Remove(m_WorkingNode); + if (this->GetToolManager()->GetDataStorage()->Exists(m_WorkingNode)) + this->GetToolManager()->GetDataStorage()->Remove(m_WorkingNode); m_WorkingSlice = nullptr; m_CurrentPlane = nullptr; m_WorkingNode = DataNode::New(); m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true)); this->m_WorkingNode->SetVisibility(true); m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_MasterContour->SetClosed(true); this->MouseMoved(interactionEvent, true); } void mitk::PaintbrushTool::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { MouseMoved(interactionEvent, false); } void mitk::PaintbrushTool::OnPrimaryButtonPressedMoved(StateMachineAction *, InteractionEvent *interactionEvent) { MouseMoved(interactionEvent, true); } /** Insert the point to the feedback contour,finish to build the contour and at the same time the painting function */ void mitk::PaintbrushTool::MouseMoved(mitk::InteractionEvent *interactionEvent, bool leftMouseButtonPressed) { auto *positionEvent = dynamic_cast(interactionEvent); CheckIfCurrentSliceHasChanged(positionEvent); if (m_LastContourSize != m_Size) { UpdateContour(positionEvent); m_LastContourSize = m_Size; } Point3D worldCoordinates = positionEvent->GetPositionInWorld(); Point3D indexCoordinates; m_WorkingSlice->GetGeometry()->WorldToIndex(worldCoordinates, indexCoordinates); // round to nearest voxel center (abort if this hasn't changed) if (m_Size % 2 == 0) // even { indexCoordinates[0] = std::round(indexCoordinates[0]); indexCoordinates[1] = std::round(indexCoordinates[1]); } else // odd { indexCoordinates[0] = std::round(indexCoordinates[0]); indexCoordinates[1] = std::round(indexCoordinates[1]); } static Point3D lastPos; // uninitialized: if somebody finds out how this can be initialized in a one-liner, tell me if (fabs(indexCoordinates[0] - lastPos[0]) > mitk::eps || fabs(indexCoordinates[1] - lastPos[1]) > mitk::eps || fabs(indexCoordinates[2] - lastPos[2]) > mitk::eps || leftMouseButtonPressed) { lastPos = indexCoordinates; } else { return; } auto contour = ContourModel::New(); contour->SetClosed(true); auto it = m_MasterContour->Begin(); auto end = m_MasterContour->End(); while (it != end) { auto point = (*it)->Coordinates; point[0] += indexCoordinates[0]; point[1] += indexCoordinates[1]; contour->AddVertex(point); ++it; } if (leftMouseButtonPressed) { const double dist = indexCoordinates.EuclideanDistanceTo(m_LastPosition); const double radius = static_cast(m_Size) / 2.0; - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); + DataNode *workingNode(this->GetToolManager()->GetWorkingData(0)); auto workingImage = dynamic_cast(workingNode->GetData()); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); // m_PaintingPixelValue only decides whether to paint or erase mitk::ContourModelUtils::FillContourInSlice( contour, m_WorkingSlice, workingImage, m_PaintingPixelValue * activePixelValue); m_WorkingNode->SetData(m_WorkingSlice); m_WorkingNode->Modified(); // if points are >= radius away draw rectangle to fill empty holes // in between the 2 points if (dist > radius) { const mitk::Point3D ¤tPos = indexCoordinates; mitk::Point3D direction; mitk::Point3D vertex; mitk::Point3D normal; direction[0] = indexCoordinates[0] - m_LastPosition[0]; direction[1] = indexCoordinates[1] - m_LastPosition[1]; direction[2] = indexCoordinates[2] - m_LastPosition[2]; direction[0] = direction.GetVnlVector().normalize()[0]; direction[1] = direction.GetVnlVector().normalize()[1]; direction[2] = direction.GetVnlVector().normalize()[2]; // 90 degrees rotation of direction normal[0] = -1.0 * direction[1]; normal[1] = direction[0]; contour->Clear(); // upper left corner vertex[0] = m_LastPosition[0] + (normal[0] * radius); vertex[1] = m_LastPosition[1] + (normal[1] * radius); contour->AddVertex(vertex); // upper right corner vertex[0] = currentPos[0] + (normal[0] * radius); vertex[1] = currentPos[1] + (normal[1] * radius); contour->AddVertex(vertex); // lower right corner vertex[0] = currentPos[0] - (normal[0] * radius); vertex[1] = currentPos[1] - (normal[1] * radius); contour->AddVertex(vertex); // lower left corner vertex[0] = m_LastPosition[0] - (normal[0] * radius); vertex[1] = m_LastPosition[1] - (normal[1] * radius); contour->AddVertex(vertex); mitk::ContourModelUtils::FillContourInSlice(contour, m_WorkingSlice, workingImage, m_PaintingPixelValue * activePixelValue); m_WorkingNode->SetData(m_WorkingSlice); m_WorkingNode->Modified(); } } else { // switched from different renderwindow // no activate hover highlighting. Otherwise undo / redo wont work this->m_WorkingNode->SetVisibility(false); } m_LastPosition = indexCoordinates; // visualize contour ContourModel::Pointer tmp = FeedbackContourTool::BackProjectContourFrom2DSlice(m_WorkingSlice->GetGeometry(), contour); this->UpdateCurrentFeedbackContour(tmp); assert(positionEvent->GetSender()->GetRenderWindow()); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::PaintbrushTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { // When mouse is released write segmentationresult back into image auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice->Clone()); // deactivate visibility of helper node m_WorkingNode->SetVisibility(false); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } /** Called when the CTRL key is pressed. Will change the painting pixel value from 0 to 1 or from 1 to 0. */ void mitk::PaintbrushTool::OnInvertLogic(StateMachineAction *, InteractionEvent *) { // Inversion only for 0 and 1 as painting values if (m_PaintingPixelValue == 1) { m_PaintingPixelValue = 0; FeedbackContourTool::SetFeedbackContourColor(1.0, 0.0, 0.0); } else if (m_PaintingPixelValue == 0) { m_PaintingPixelValue = 1; FeedbackContourTool::SetFeedbackContourColorDefault(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PaintbrushTool::CheckIfCurrentSliceHasChanged(const InteractionPositionEvent *event) { const PlaneGeometry *planeGeometry((event->GetSender()->GetCurrentWorldPlaneGeometry())); const auto *abstractTransformGeometry( dynamic_cast(event->GetSender()->GetCurrentWorldPlaneGeometry())); - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); + DataNode *workingNode(this->GetToolManager()->GetWorkingData(0)); if (!workingNode) return; Image::Pointer image = dynamic_cast(workingNode->GetData()); if (!image || !planeGeometry || abstractTransformGeometry) return; if (m_CurrentPlane.IsNull() || m_WorkingSlice.IsNull()) { m_CurrentPlane = planeGeometry; m_WorkingSlice = SegTool2D::GetAffectedImageSliceAs2DImage(event, image)->Clone(); m_WorkingNode->ReplaceProperty("color", workingNode->GetProperty("color")); m_WorkingNode->SetData(m_WorkingSlice); } else { bool isSameSlice(false); isSameSlice = mitk::MatrixEqualElementWise(planeGeometry->GetIndexToWorldTransform()->GetMatrix(), m_CurrentPlane->GetIndexToWorldTransform()->GetMatrix()); isSameSlice = mitk::Equal(planeGeometry->GetIndexToWorldTransform()->GetOffset(), m_CurrentPlane->GetIndexToWorldTransform()->GetOffset()); if (!isSameSlice) { - m_ToolManager->GetDataStorage()->Remove(m_WorkingNode); + this->GetToolManager()->GetDataStorage()->Remove(m_WorkingNode); m_CurrentPlane = nullptr; m_WorkingSlice = nullptr; m_WorkingNode = nullptr; m_CurrentPlane = planeGeometry; m_WorkingSlice = SegTool2D::GetAffectedImageSliceAs2DImage(event, image)->Clone(); m_WorkingNode = mitk::DataNode::New(); m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_WorkingNode->SetData(m_WorkingSlice); // So that the paintbrush contour vanished in the previous render window RenderingManager::GetInstance()->RequestUpdateAll(); } } - if (!m_ToolManager->GetDataStorage()->Exists(m_WorkingNode)) + if (!this->GetToolManager()->GetDataStorage()->Exists(m_WorkingNode)) { m_WorkingNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_WorkingNode->SetProperty("color", workingNode->GetProperty("color")); m_WorkingNode->SetProperty("name", mitk::StringProperty::New("Paintbrush_Node")); m_WorkingNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_WorkingNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_WorkingNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_WorkingNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); - m_ToolManager->GetDataStorage()->Add(m_WorkingNode); + this->GetToolManager()->GetDataStorage()->Add(m_WorkingNode); } } void mitk::PaintbrushTool::OnToolManagerWorkingDataModified() { // Here we simply set the current working slice to null. The next time the mouse is moved // within a renderwindow a new slice will be extracted from the new working data m_WorkingSlice = nullptr; } diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.cpp b/Modules/Segmentation/Interactions/mitkPickingTool.cpp index e2bfed1ca1..52ab30dfad 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPickingTool.cpp @@ -1,269 +1,215 @@ /*============================================================================ 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 "mitkPickingTool.h" #include "mitkProperties.h" #include "mitkToolManager.h" +#include "mitkInteractionPositionEvent.h" // us #include #include #include #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include "mitkImageTimeSelector.h" +#include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, PickingTool, "PickingTool"); } -mitk::PickingTool::PickingTool() : m_WorkingData(nullptr) +mitk::PickingTool::PickingTool() : AutoSegmentationWithPreviewTool(false, "PressMoveReleaseAndPointSetting") { - m_PointSetNode = mitk::DataNode::New(); - m_PointSetNode->GetPropertyList()->SetProperty("name", mitk::StringProperty::New("Picking_Seedpoint")); - m_PointSetNode->GetPropertyList()->SetProperty("helper object", mitk::BoolProperty::New(true)); - m_PointSet = mitk::PointSet::New(); - m_PointSetNode->SetData(m_PointSet); - - // Watch for point added or modified - itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); - pointAddedCommand->SetCallbackFunction(this, &mitk::PickingTool::OnPointAdded); - m_PointSetAddObserverTag = m_PointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); - - // create new node for picked region - m_ResultNode = mitk::DataNode::New(); - // set some properties - m_ResultNode->SetProperty("name", mitk::StringProperty::New("result")); - m_ResultNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - m_ResultNode->SetProperty("color", mitk::ColorProperty::New(0, 1, 0)); - m_ResultNode->SetProperty("layer", mitk::IntProperty::New(1)); - m_ResultNode->SetProperty("opacity", mitk::FloatProperty::New(0.33f)); + this->ResetsToEmptyPreviewOn(); } mitk::PickingTool::~PickingTool() { - m_PointSet->RemoveObserver(m_PointSetAddObserverTag); } bool mitk::PickingTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData,workingData)) return false; auto* image = dynamic_cast(referenceData); if (image == nullptr) return false; - if (image->GetTimeSteps() > 1) //release quickfix for T28248 - return false; - return true; } const char **mitk::PickingTool::GetXPM() const { return nullptr; } const char *mitk::PickingTool::GetName() const { return "Picking"; } us::ModuleResource mitk::PickingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Pick_48x48.png"); return resource; } void mitk::PickingTool::Activated() { Superclass::Activated(); - DataStorage *dataStorage = this->GetDataStorage(); - m_WorkingData = this->GetWorkingData(); - - // add to datastorage and enable interaction - if (!dataStorage->Exists(m_PointSetNode)) - dataStorage->Add(m_PointSetNode, m_WorkingData); + m_PointSet = mitk::PointSet::New(); + //ensure that the seed points are visible for all timepoints. + dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); - m_SeedPointInteractor = mitk::SinglePointDataInteractor::New(); - m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); - m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); - m_SeedPointInteractor->SetDataNode(m_PointSetNode); + m_PointSetNode = mitk::DataNode::New(); + m_PointSetNode->SetData(m_PointSet); + m_PointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); + m_PointSetNode->SetBoolProperty("helper object", true); + m_PointSetNode->SetColor(0.0, 1.0, 0.0); + m_PointSetNode->SetVisibility(true); - // now add result to data tree - dataStorage->Add(m_ResultNode, m_WorkingData); + this->GetDataStorage()->Add(m_PointSetNode, this->GetToolManager()->GetWorkingData(0)); } void mitk::PickingTool::Deactivated() { - m_PointSet->Clear(); + this->ClearSeeds(); + // remove from data storage and disable interaction GetDataStorage()->Remove(m_PointSetNode); - GetDataStorage()->Remove(m_ResultNode); + m_PointSetNode = nullptr; + m_PointSet = nullptr; Superclass::Deactivated(); } -mitk::DataNode *mitk::PickingTool::GetReferenceData() -{ - return this->m_ToolManager->GetReferenceData(0); -} - -mitk::DataStorage *mitk::PickingTool::GetDataStorage() +void mitk::PickingTool::ConnectActionsAndFunctions() { - return this->m_ToolManager->GetDataStorage(); + CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); + CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); + CONNECT_FUNCTION("DeletePoint", OnDelete); } -mitk::DataNode *mitk::PickingTool::GetWorkingData() +void mitk::PickingTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { - return this->m_ToolManager->GetWorkingData(0); -} + if (!this->IsUpdating() && m_PointSet.IsNotNull()) + { + const auto positionEvent = dynamic_cast(interactionEvent); -mitk::DataNode::Pointer mitk::PickingTool::GetPointSetNode() -{ - return m_PointSetNode; + if (positionEvent != nullptr) + { + m_PointSet->InsertPoint(m_PointSet->GetSize(), positionEvent->GetPositionInWorld()); + this->UpdatePreview(); + } + } } -void mitk::PickingTool::OnPointAdded() +void mitk::PickingTool::OnDelete(StateMachineAction*, InteractionEvent* /*interactionEvent*/) { - if (m_WorkingData != this->GetWorkingData()) + if (!this->IsUpdating() && m_PointSet.IsNotNull()) { - DataStorage *dataStorage = this->GetDataStorage(); - - if (dataStorage->Exists(m_PointSetNode)) + // delete last seed point + if (this->m_PointSet->GetSize() > 0) { - dataStorage->Remove(m_PointSetNode); - dataStorage->Add(m_PointSetNode, this->GetWorkingData()); - } + m_PointSet->RemovePointAtEnd(0); - if (dataStorage->Exists(m_ResultNode)) - { - dataStorage->Remove(m_ResultNode); - dataStorage->Add(m_ResultNode, this->GetWorkingData()); + this->UpdatePreview(); } - - m_WorkingData = this->GetWorkingData(); } +} - // Perform region growing/picking - - int timeStep = - mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))->GetTimeStep(); - - mitk::PointSet::PointType seedPoint = m_PointSet->GetPointSet(timeStep)->GetPoints()->Begin().Value(); +void mitk::PickingTool::ClearPicks() +{ + this->ClearSeeds(); + this->UpdatePreview(); +} - // as we want to pick a region from our segmentation image use the working data from ToolManager - mitk::Image::Pointer orgImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); +bool mitk::PickingTool::HasPicks() const +{ + return this->m_PointSet.IsNotNull() && this->m_PointSet->GetSize()>0; +} - if (orgImage.IsNotNull()) +void mitk::PickingTool::ClearSeeds() +{ + if (this->m_PointSet.IsNotNull()) { - if (orgImage->GetDimension() == 4) - { // there may be 4D segmentation data even though we currently don't support that - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(orgImage); - timeSelector->SetTimeNr(timeStep); - timeSelector->UpdateLargestPossibleRegion(); - mitk::Image *timedImage = timeSelector->GetOutput(); - - AccessByItk_2(timedImage, StartRegionGrowing, timedImage->GetGeometry(), seedPoint); - } - else if (orgImage->GetDimension() == 3) - { - AccessByItk_2(orgImage, StartRegionGrowing, orgImage->GetGeometry(), seedPoint); - } - this->m_PointSet->Clear(); + // renew pointset + this->m_PointSet = mitk::PointSet::New(); + //ensure that the seed points are visible for all timepoints. + dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); + this->m_PointSetNode->SetData(this->m_PointSet); } } template -void mitk::PickingTool::StartRegionGrowing(itk::Image *itkImage, - mitk::BaseGeometry *imageGeometry, - mitk::PointSet::PointType seedPoint) +void DoITKRegionGrowing(const itk::Image* oldSegImage, + mitk::Image* segmentation, + const mitk::PointSet* seedPoints, + unsigned int timeStep, const mitk::BaseGeometry* inputGeometry) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); // convert world coordinates to image indices - IndexType seedIndex; - imageGeometry->WorldToIndex(seedPoint, seedIndex); + for (auto pos = seedPoints->Begin(); pos != seedPoints->End(); ++pos) + { + IndexType seedIndex; + inputGeometry->WorldToIndex(pos->Value(), seedIndex); + regionGrower->AddSeed(seedIndex); + } // perform region growing in desired segmented region - regionGrower->SetInput(itkImage); - regionGrower->AddSeed(seedIndex); + regionGrower->SetInput(oldSegImage); - // TODO: conversion added to silence warning and - // maintain existing behaviour, should be fixed - // since it's not correct e.g. for signed char regionGrower->SetLower(static_cast(1)); - regionGrower->SetUpper(static_cast(255)); + regionGrower->SetUpper(std::numeric_limits::max()); try { regionGrower->Update(); } catch (const itk::ExceptionObject &) { return; // can't work } catch (...) { return; } - // Store result and preview - mitk::Image::Pointer resultImage = mitk::ImportItkImage(regionGrower->GetOutput(), imageGeometry)->Clone(); - mitk::LabelSetImage::Pointer resultLabelSetImage = mitk::LabelSetImage::New(); - resultLabelSetImage->InitializeByLabeledImage(resultImage); - - m_ResultNode->SetData(resultLabelSetImage); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + segmentation->SetVolume((void*)(regionGrower->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } -void mitk::PickingTool::ConfirmSegmentation() +void mitk::PickingTool::DoUpdatePreview(const Image* /*inputAtTimeStep*/, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) { - mitk::DataNode::Pointer newNode = mitk::DataNode::New(); - newNode->SetProperty("name", mitk::StringProperty::New(m_WorkingData->GetName() + "_picked")); - - float rgb[3] = {1.0f, 0.0f, 0.0f}; - m_WorkingData->GetColor(rgb); - newNode->SetProperty("color", mitk::ColorProperty::New(rgb)); - - float opacity = 1.0f; - m_WorkingData->GetOpacity(opacity, nullptr); - newNode->SetProperty("opacity", mitk::FloatProperty::New(opacity)); - - newNode->SetData(m_ResultNode->GetData()); - - GetDataStorage()->Add(newNode, this->GetReferenceData()); - m_WorkingData->SetVisibility(false); - - m_ResultNode->SetData(nullptr); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + if (nullptr != oldSegAtTimeStep && nullptr != previewImage && m_PointSet.IsNotNull()) + { + AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry())); + } } diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.h b/Modules/Segmentation/Interactions/mitkPickingTool.h index a619ecbf3d..dd092dcf28 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.h +++ b/Modules/Segmentation/Interactions/mitkPickingTool.h @@ -1,95 +1,85 @@ /*============================================================================ 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 mitkPickingTool_h_Included #define mitkPickingTool_h_Included -#include "itkImage.h" -#include "mitkAutoSegmentationTool.h" -#include "mitkCommon.h" -#include "mitkDataStorage.h" +#include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkPointSet.h" -#include "mitkSinglePointDataInteractor.h" + #include namespace us { class ModuleResource; } namespace mitk { /** \brief Extracts a single region from a segmentation image and creates a new image with same geometry of the input image. The region is extracted in 3D space. This is done by performing region growing within the desired region. Use shift click to add the seed point. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ - class MITKSEGMENTATION_EXPORT PickingTool : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT PickingTool : public AutoSegmentationWithPreviewTool { public: - mitkClassMacro(PickingTool, AutoSegmentationTool); + mitkClassMacro(PickingTool, AutoSegmentationWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - const char **GetXPM() const override; + const char **GetXPM() const override; const char *GetName() const override; us::ModuleResource GetIconResource() const override; - void Activated() override; - void Deactivated() override; - bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; - virtual DataNode::Pointer GetPointSetNode(); + void Activated() override; + void Deactivated() override; - mitk::DataNode *GetReferenceData(); - mitk::DataNode *GetWorkingData(); - mitk::DataStorage *GetDataStorage(); + /**Clears all picks and updates the preview.*/ + void ClearPicks(); - void ConfirmSegmentation(); + bool HasPicks() const; protected: PickingTool(); // purposely hidden ~PickingTool() override; - // Callback for point add event of PointSet - void OnPointAdded(); + void ConnectActionsAndFunctions() override; - // Observer id - long m_PointSetAddObserverTag; + /// \brief Add point action of StateMachine pattern + virtual void OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent); - mitk::DataNode::Pointer m_ResultNode; + /// \brief Delete action of StateMachine pattern + virtual void OnDelete(StateMachineAction*, InteractionEvent* interactionEvent); - // itk regrowing - template - void StartRegionGrowing(itk::Image *itkImage, - mitk::BaseGeometry *imageGeometry, - mitk::PointSet::PointType seedPoint); + /// \brief Clear all seed points. + void ClearSeeds(); + + void DoUpdatePreview(const Image* inputAtTimeStep, const Image* oldSegAtTimeStep, Image* previewImage, TimeStepType timeStep) override; // seed point PointSet::Pointer m_PointSet; - SinglePointDataInteractor::Pointer m_SeedPointInteractor; DataNode::Pointer m_PointSetNode; - - DataNode *m_WorkingData; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkPixelManipulationTool.cpp b/Modules/Segmentation/Interactions/mitkPixelManipulationTool.cpp index 5a080fe056..1507bbcc3a 100644 --- a/Modules/Segmentation/Interactions/mitkPixelManipulationTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPixelManipulationTool.cpp @@ -1,191 +1,191 @@ /*============================================================================ 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 "mitkPixelManipulationTool.h" #include "mitkBoundingObjectToSegmentationFilter.h" #include "mitkImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkProperties.h" #include "mitkToolManager.h" #include #include "mitkPixelManipulationTool.xpm" namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, PixelManipulationTool, "Pixel manipulation tool"); } mitk::PixelManipulationTool::PixelManipulationTool() : Tool("dummy"), m_Value(0), m_FixedValue(false) { } mitk::PixelManipulationTool::~PixelManipulationTool() { } void mitk::PixelManipulationTool::Activated() { Superclass::Activated(); - m_ToolManager->RoiDataChanged += + this->GetToolManager()->RoiDataChanged += mitk::MessageDelegate(this, &mitk::PixelManipulationTool::OnRoiDataChanged); - m_OriginalImageNode = m_ToolManager->GetReferenceData(0); + m_OriginalImageNode = this->GetToolManager()->GetReferenceData(0); if (m_OriginalImageNode.IsNotNull()) { mitk::Image::Pointer image = dynamic_cast(m_OriginalImageNode->GetData()); if (image.IsNotNull()) { // mitk::ScalarType scalar = image->GetScalarValueMax(); } } else - m_ToolManager->ActivateTool(-1); + this->GetToolManager()->ActivateTool(-1); } void mitk::PixelManipulationTool::Deactivated() { - m_ToolManager->RoiDataChanged -= + this->GetToolManager()->RoiDataChanged -= mitk::MessageDelegate(this, &mitk::PixelManipulationTool::OnRoiDataChanged); Superclass::Deactivated(); } const char *mitk::PixelManipulationTool::GetName() const { return "pixelmanipulation"; } const char **mitk::PixelManipulationTool::GetXPM() const { return mitkPixelManipulationTool_xpm; } void mitk::PixelManipulationTool::OnRoiDataChanged() { } void mitk::PixelManipulationTool::CalculateImage() { if (m_OriginalImageNode.IsNotNull()) { mitk::Image::Pointer image = dynamic_cast(m_OriginalImageNode->GetData()); - mitk::DataNode *maskNode = m_ToolManager->GetRoiData(0); + mitk::DataNode *maskNode = this->GetToolManager()->GetRoiData(0); mitk::Image::Pointer roi = mitk::Image::New(); if (maskNode) { auto *boundingObject = dynamic_cast(maskNode->GetData()); if (boundingObject) { mitk::BoundingObjectToSegmentationFilter::Pointer filter = mitk::BoundingObjectToSegmentationFilter::New(); filter->SetBoundingObject(boundingObject); filter->SetInput(image); filter->Update(); roi = filter->GetOutput(); } else roi = dynamic_cast(maskNode->GetData()); mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(image); if (image) { AccessByItk_3(image, ITKPixelManipulation, roi, newImage, m_Value); this->AddImageToDataStorage(newImage); } } } } template void mitk::PixelManipulationTool::ITKPixelManipulation(itk::Image *originalImage, Image *maskImage, Image *newImage, int newValue) { typedef itk::Image itkImageType; typedef itk::Image itkMaskType; typename itkImageType::Pointer itkImage; typename itkMaskType::Pointer itkMask; CastToItkImage(newImage, itkImage); CastToItkImage(maskImage, itkMask); typedef itk::ImageRegionConstIterator InputIteratorType; typedef itk::ImageRegionIterator OutputIteratorType; typedef itk::ImageRegionConstIterator MaskIteratorType; MaskIteratorType maskIterator(itkMask, itkMask->GetLargestPossibleRegion()); InputIteratorType inputIterator(originalImage, originalImage->GetLargestPossibleRegion()); OutputIteratorType outputIterator(itkImage, itkImage->GetLargestPossibleRegion()); inputIterator.GoToBegin(); outputIterator.GoToBegin(); maskIterator.GoToBegin(); while (!outputIterator.IsAtEnd()) { if (maskIterator.Get()) { if (m_FixedValue) outputIterator.Set(newValue); else outputIterator.Set(inputIterator.Get() + newValue); } else outputIterator.Set(inputIterator.Get()); ++inputIterator; ++outputIterator; ++maskIterator; } } void mitk::PixelManipulationTool::AddImageToDataStorage(mitk::Image::Pointer image) { if (image.IsNotNull()) { mitk::DataNode::Pointer node = mitk::DataNode::New(); std::string name = m_OriginalImageNode->GetName(); name.append("_modified"); node->SetName(name); node->SetProperty("binary", mitk::BoolProperty::New(false)); node->SetData(image); - if (m_ToolManager) - m_ToolManager->GetDataStorage()->Add(node, m_OriginalImageNode); + if (nullptr != this->GetToolManager()) + this->GetToolManager()->GetDataStorage()->Add(node, m_OriginalImageNode); } } void mitk::PixelManipulationTool::SetValue(int value) { m_Value = value; } int mitk::PixelManipulationTool::GetValue() { return m_Value; } void mitk::PixelManipulationTool::SetFixedValue(int value) { m_FixedValue = value; } int mitk::PixelManipulationTool::GetFixedValue() { return m_FixedValue; } diff --git a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp index 3e2617f963..0fa978346e 100644 --- a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp @@ -1,573 +1,573 @@ /*============================================================================ 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 "mitkRegionGrowingTool.h" #include "mitkBaseRenderer.h" #include "mitkImageToContourModelFilter.h" #include "mitkRegionGrowingTool.xpm" #include "mitkRenderingManager.h" #include "mitkToolManager.h" // us #include #include #include #include // ITK #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, RegionGrowingTool, "Region growing tool"); } #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) mitk::RegionGrowingTool::RegionGrowingTool() : FeedbackContourTool("PressMoveRelease"), m_SeedValue(0), m_ScreenYDifference(0), m_ScreenXDifference(0), m_MouseDistanceScaleFactor(0.5), m_PaintingPixelValue(0), m_FillFeedbackContour(true), m_ConnectedComponentValue(1) { } mitk::RegionGrowingTool::~RegionGrowingTool() { } void mitk::RegionGrowingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); } const char **mitk::RegionGrowingTool::GetXPM() const { return mitkRegionGrowingTool_xpm; } us::ModuleResource mitk::RegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); return resource; } us::ModuleResource mitk::RegionGrowingTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_Cursor_32x32.png"); return resource; } const char *mitk::RegionGrowingTool::GetName() const { return "Region Growing"; } void mitk::RegionGrowingTool::Activated() { Superclass::Activated(); } void mitk::RegionGrowingTool::Deactivated() { Superclass::Deactivated(); } // Get the average pixel value of square/cube with radius=neighborhood around index template void mitk::RegionGrowingTool::GetNeighborhoodAverage(const itk::Image *itkImage, const itk::Index& index, ScalarType *result, unsigned int neighborhood) { // maybe assert that image dimension is only 2 or 3? auto neighborhoodInt = (int)neighborhood; TPixel averageValue(0); unsigned int numberOfPixels = (2 * neighborhood + 1) * (2 * neighborhood + 1); if (imageDimension == 3) { numberOfPixels *= (2 * neighborhood + 1); } MITK_DEBUG << "Getting neighborhood of " << numberOfPixels << " pixels around " << index; itk::Index currentIndex; for (int i = (0 - neighborhoodInt); i <= neighborhoodInt; ++i) { currentIndex[0] = index[0] + i; for (int j = (0 - neighborhoodInt); j <= neighborhoodInt; ++j) { currentIndex[1] = index[1] + j; if (imageDimension == 3) { for (int k = (0 - neighborhoodInt); k <= neighborhoodInt; ++k) { currentIndex[2] = index[2] + k; if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } else { if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } } *result = (ScalarType)averageValue; *result /= numberOfPixels; } // Check whether index lies inside a segmentation template void mitk::RegionGrowingTool::IsInsideSegmentation(const itk::Image *itkImage, const itk::Index& index, bool *result) { if (itkImage->GetPixel(index) > 0) { *result = true; } else { *result = false; } } // Do the region growing (i.e. call an ITK filter that does it) template void mitk::RegionGrowingTool::StartRegionGrowing(const itk::Image *inputImage, const itk::Index& seedIndex, const std::array& thresholds, mitk::Image::Pointer &outputImage) { MITK_DEBUG << "Starting region growing at index " << seedIndex << " with lower threshold " << thresholds[0] << " and upper threshold " << thresholds[1]; typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); // perform region growing in desired segmented region regionGrower->SetInput(inputImage); regionGrower->SetSeed(seedIndex); regionGrower->SetLower(thresholds[0]); regionGrower->SetUpper(thresholds[1]); try { regionGrower->Update(); } catch (...) { return; // Should we do something? } typename OutputImageType::Pointer resultImage = regionGrower->GetOutput(); // Smooth result: Every pixel is replaced by the majority of the neighborhood typedef itk::NeighborhoodIterator NeighborhoodIteratorType; typedef itk::ImageRegionIterator ImageIteratorType; typename NeighborhoodIteratorType::RadiusType radius; radius.Fill(2); // for now, maybe make this something the user can adjust in the preferences? typedef itk::ImageDuplicator< OutputImageType > DuplicatorType; typename DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage(resultImage); duplicator->Update(); typename OutputImageType::Pointer resultDup = duplicator->GetOutput(); NeighborhoodIteratorType neighborhoodIterator(radius, resultDup, resultDup->GetRequestedRegion()); ImageIteratorType imageIterator(resultImage, resultImage->GetRequestedRegion()); for (neighborhoodIterator.GoToBegin(), imageIterator.GoToBegin(); !neighborhoodIterator.IsAtEnd(); ++neighborhoodIterator, ++imageIterator) { DefaultSegmentationDataType voteYes(0); DefaultSegmentationDataType voteNo(0); for (unsigned int i = 0; i < neighborhoodIterator.Size(); ++i) { if (neighborhoodIterator.GetPixel(i) > 0) { voteYes += 1; } else { voteNo += 1; } } if (voteYes > voteNo) { imageIterator.Set(1); } else { imageIterator.Set(0); } } if (resultImage.IsNull()) { MITK_DEBUG << "Region growing result is empty."; } // Can potentially have multiple regions, use connected component image filter to label disjunct regions typedef itk::ConnectedComponentImageFilter ConnectedComponentImageFilterType; typename ConnectedComponentImageFilterType::Pointer connectedComponentFilter = ConnectedComponentImageFilterType::New(); connectedComponentFilter->SetInput(resultImage); connectedComponentFilter->Update(); typename OutputImageType::Pointer resultImageCC = connectedComponentFilter->GetOutput(); m_ConnectedComponentValue = resultImageCC->GetPixel(seedIndex); outputImage = mitk::GrabItkImageMemory(resultImageCC); } template void mitk::RegionGrowingTool::CalculateInitialThresholds(const itk::Image*) { LevelWindow levelWindow; - m_ToolManager->GetReferenceData(0)->GetLevelWindow(levelWindow); + this->GetToolManager()->GetReferenceData(0)->GetLevelWindow(levelWindow); m_ThresholdExtrema[0] = static_cast(std::numeric_limits::lowest()); m_ThresholdExtrema[1] = static_cast(std::numeric_limits::max()); const ScalarType lowerWindowBound = std::max(m_ThresholdExtrema[0], levelWindow.GetLowerWindowBound()); const ScalarType upperWindowBound = std::min(m_ThresholdExtrema[1], levelWindow.GetUpperWindowBound()); if (m_SeedValue < lowerWindowBound) { m_InitialThresholds = { m_ThresholdExtrema[0], lowerWindowBound }; } else if (m_SeedValue > upperWindowBound) { m_InitialThresholds = { upperWindowBound, m_ThresholdExtrema[1] }; } else { const ScalarType range = 0.1 * (upperWindowBound - lowerWindowBound); // 10% of the visible window m_InitialThresholds[0] = std::min(std::max(lowerWindowBound, m_SeedValue - 0.5 * range), upperWindowBound - range); m_InitialThresholds[1] = m_InitialThresholds[0] + range; } } void mitk::RegionGrowingTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_LastScreenPosition = Point2I(positionEvent->GetPointerPositionOnScreen()); // ReferenceSlice is from the underlying image, WorkingSlice from the active segmentation (can be empty) m_ReferenceSlice = FeedbackContourTool::GetAffectedReferenceSlice(positionEvent); m_WorkingSlice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); if (m_WorkingSlice.IsNotNull()) // can't do anything without a working slice (i.e. a possibly empty segmentation) { // 2. Determine if the user clicked inside or outside of the segmentation/working slice (i.e. the whole volume) mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); workingSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), m_SeedPoint); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; if (workingSliceGeometry->IsIndexInside(m_SeedPoint)) { MITK_DEBUG << "OnMousePressed: point " << positionEvent->GetPositionInWorld() << " (index coordinates " << m_SeedPoint << ") is inside working slice"; // 3. determine the pixel value under the last click to determine what to do bool inside(true); AccessFixedDimensionByItk_2(m_WorkingSlice, IsInsideSegmentation, 2, indexInWorkingSlice2D, &inside); m_PaintingPixelValue = inside ? 0 : 1; if (inside) { MITK_DEBUG << "Clicked inside segmentation"; // For now, we're doing nothing when the user clicks inside the segmentation. Behaviour can be implemented via // OnMousePressedInside() // When you do, be sure to remove the m_PaintingPixelValue check in OnMouseMoved() and OnMouseReleased() return; } else { MITK_DEBUG << "Clicked outside of segmentation"; OnMousePressedOutside(nullptr, interactionEvent); } } } } // Use this to implement a behaviour for when the user clicks inside a segmentation (for example remove something) // Old IpPic code is kept as comment for reference void mitk::RegionGrowingTool::OnMousePressedInside() { // mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent // ); // //const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); // checked in // OnMousePressed // // 3.1.1. Create a skeletonization of the segmentation and try to find a nice cut // // apply the skeletonization-and-cut algorithm // // generate contour to remove // // set m_ReferenceSlice = nullptr so nothing will happen during mouse move // // remember to fill the contour with 0 in mouserelease // mitkIpPicDescriptor* segmentationHistory = ipMITKSegmentationCreateGrowerHistory( workingPicSlice, // m_LastWorkingSeed, nullptr ); // free again // if (segmentationHistory) // { // tCutResult cutContour = ipMITKSegmentationGetCutPoints( workingPicSlice, segmentationHistory, // initialWorkingOffset ); // tCutResult is a ipSegmentation type // mitkIpPicFree( segmentationHistory ); // if (cutContour.cutIt) // { // int timestep = positionEvent->GetSender()->GetTimeStep(); // // 3.1.2 copy point from float* to mitk::Contour // ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New(); // contourInImageIndexCoordinates->Expand(timestep + 1); // contourInImageIndexCoordinates->SetClosed(true, timestep); // Point3D newPoint; // for (int index = 0; index < cutContour.deleteSize; ++index) // { // newPoint[0] = cutContour.deleteCurve[ 2 * index + 0 ] - 0.5;//correction is needed because the // output of the algorithm is center based // newPoint[1] = cutContour.deleteCurve[ 2 * index + 1 ] - 0.5;//and we want our contour displayed // corner based. // newPoint[2] = 0.0; // contourInImageIndexCoordinates->AddVertex( newPoint, timestep ); // } // free(cutContour.traceline); // free(cutContour.deleteCurve); // perhaps visualize this for fun? // free(cutContour.onGradient); // ContourModel::Pointer contourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( // m_WorkingSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true: sub 0.5 for // ipSegmentation correction // FeedbackContourTool::SetFeedbackContour( contourInWorldCoordinates ); // FeedbackContourTool::SetFeedbackContourVisible(true); // mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); // m_FillFeedbackContour = true; // } // else // { // m_FillFeedbackContour = false; // } // } // else // { // m_FillFeedbackContour = false; // } // m_ReferenceSlice = nullptr; // return true; } void mitk::RegionGrowingTool::OnMousePressedOutside(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent) { // Get geometry and indices mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; mitk::BaseGeometry::Pointer referenceSliceGeometry; referenceSliceGeometry = m_ReferenceSlice->GetGeometry(); itk::Index<3> indexInReferenceSlice; itk::Index<2> indexInReferenceSlice2D; referenceSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), indexInReferenceSlice); indexInReferenceSlice2D[0] = indexInReferenceSlice[0]; indexInReferenceSlice2D[1] = indexInReferenceSlice[1]; // Get seed neighborhood ScalarType averageValue(0); AccessFixedDimensionByItk_3(m_ReferenceSlice, GetNeighborhoodAverage, 2, indexInReferenceSlice2D, &averageValue, 1); m_SeedValue = averageValue; MITK_DEBUG << "Seed value is " << m_SeedValue; // Calculate initial thresholds AccessFixedDimensionByItk(m_ReferenceSlice, CalculateInitialThresholds, 2); m_Thresholds[0] = m_InitialThresholds[0]; m_Thresholds[1] = m_InitialThresholds[1]; // Perform region growing mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Extract contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(m_LastEventSender->GetRenderWindow()); } } } } void mitk::RegionGrowingTool::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, // i.e. when the user clicked inside the segmentation if (m_PaintingPixelValue == 0) { return; } auto *positionEvent = dynamic_cast(interactionEvent); if (m_ReferenceSlice.IsNotNull() && positionEvent) { // Get geometry and indices mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; m_ScreenYDifference += positionEvent->GetPointerPositionOnScreen()[1] - m_LastScreenPosition[1]; m_ScreenXDifference += positionEvent->GetPointerPositionOnScreen()[0] - m_LastScreenPosition[0]; m_LastScreenPosition = Point2I(positionEvent->GetPointerPositionOnScreen()); // Moving the mouse up and down adjusts the width of the threshold window, // moving it left and right shifts the threshold window m_Thresholds[0] = std::min(m_SeedValue, m_InitialThresholds[0] - (m_ScreenYDifference - m_ScreenXDifference) * m_MouseDistanceScaleFactor); m_Thresholds[1] = std::max(m_SeedValue, m_InitialThresholds[1] + (m_ScreenYDifference + m_ScreenXDifference) * m_MouseDistanceScaleFactor); // Do not exceed the pixel type extrema of the reference slice, though m_Thresholds[0] = std::max(m_ThresholdExtrema[0], m_Thresholds[0]); m_Thresholds[1] = std::min(m_ThresholdExtrema[1], m_Thresholds[1]); // Perform region growing again and show the result mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Update the contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(positionEvent->GetSender()->GetRenderWindow()); } } } } void mitk::RegionGrowingTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, // i.e. when the user clicked inside the segmentation if (m_PaintingPixelValue == 0) { return; } auto *positionEvent = dynamic_cast(interactionEvent); if (m_WorkingSlice.IsNotNull() && m_FillFeedbackContour && positionEvent) { this->WriteBackFeedbackContourAsSegmentationResult(positionEvent, m_PaintingPixelValue); m_ScreenYDifference = 0; m_ScreenXDifference = 0; } } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index 67a2b2e8d3..ab3ce6c2a5 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,757 +1,757 @@ /*============================================================================ 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 "mitkSegTool2D.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkPlaneGeometry.h" // Include of the new ImageExtractor #include "mitkMorphologicalOperations.h" #include "mitkPlanarCircle.h" #include "usGetModuleContext.h" // Includes for 3DSurfaceInterpolation #include "mitkImageTimeSelector.h" #include "mitkImageToContourFilter.h" #include "mitkSurfaceInterpolationController.h" // includes for resling and overwriting #include #include #include #include #include "mitkOperationEvent.h" #include "mitkUndoController.h" #include #include "mitkAbstractTransformGeometry.h" #include "mitkLabelSetImage.h" #include "mitkContourModelUtils.h" #include "itkImageRegionIterator.h" #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) bool mitk::SegTool2D::m_SurfaceInterpolationEnabled = true; mitk::SegTool2D::SliceInformation::SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep) : slice(aSlice), plane(aPlane), timestep(aTimestep) { } mitk::SegTool2D::SegTool2D(const char *type, const us::Module *interactorModule) : Tool(type, interactorModule), m_Contourmarkername("Position") { Tool::m_EventConfig = "DisplayConfigMITKNoCrosshair.xml"; } mitk::SegTool2D::~SegTool2D() { } bool mitk::SegTool2D::FilterEvents(InteractionEvent *interactionEvent, DataNode *) { const auto *positionEvent = dynamic_cast(interactionEvent); bool isValidEvent = (positionEvent && // Only events of type mitk::InteractionPositionEvent interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard2D // Only events from the 2D renderwindows ); return isValidEvent; } bool mitk::SegTool2D::DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice) { assert(image); assert(plane); // compare normal of plane to the three axis vectors of the image Vector3D normal = plane->GetNormal(); Vector3D imageNormal0 = image->GetSlicedGeometry()->GetAxisVector(0); Vector3D imageNormal1 = image->GetSlicedGeometry()->GetAxisVector(1); Vector3D imageNormal2 = image->GetSlicedGeometry()->GetAxisVector(2); normal.Normalize(); imageNormal0.Normalize(); imageNormal1.Normalize(); imageNormal2.Normalize(); imageNormal0.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal0.GetVnlVector())); imageNormal1.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal1.GetVnlVector())); imageNormal2.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal2.GetVnlVector())); double eps(0.00001); // axial if (imageNormal2.GetNorm() <= eps) { affectedDimension = 2; } // sagittal else if (imageNormal1.GetNorm() <= eps) { affectedDimension = 1; } // frontal else if (imageNormal0.GetNorm() <= eps) { affectedDimension = 0; } else { affectedDimension = -1; // no idea return false; } // determine slice number in image BaseGeometry *imageGeometry = image->GetGeometry(0); Point3D testPoint = imageGeometry->GetCenter(); Point3D projectedPoint; plane->Project(testPoint, projectedPoint); Point3D indexPoint; imageGeometry->WorldToIndex(projectedPoint, indexPoint); affectedSlice = ROUND(indexPoint[affectedDimension]); MITK_DEBUG << "indexPoint " << indexPoint << " affectedDimension " << affectedDimension << " affectedSlice " << affectedSlice; // check if this index is still within the image if (affectedSlice < 0 || affectedSlice >= static_cast(image->GetDimension(affectedDimension))) return false; return true; } void mitk::SegTool2D::UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, const PlaneGeometry *plane, bool detectIntersection) { std::vector slices = { SliceInformation(slice, plane, 0)}; Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection); } void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; contourInfo.contourNormal = sliceInfo.plane->GetNormal(); contourInfo.contourPoint = sliceInfo.plane->GetOrigin(); mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); } void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection) { if (!m_SurfaceInterpolationEnabled) return; //Remark: the ImageTimeSelector is just needed to extract a timestep/channel of //the image in order to get the image dimension (time dimension and channel dimension //stripped away). Therfore it is OK to always use time step 0 and channel 0 mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(workingImage); timeSelector->SetTimeNr(0); timeSelector->SetChannelNr(0); timeSelector->Update(); const auto dimRefImg = timeSelector->GetOutput()->GetDimension(); if (dimRefImg != 3) return; std::vector contourList; contourList.reserve(sliceInfos.size()); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); std::vector relevantSlices = sliceInfos; if (detectIntersection) { relevantSlices.clear(); for (const auto& sliceInfo : sliceInfos) { // Test whether there is something to extract or whether the slice just contains intersections of others mitk::Image::Pointer slice2 = sliceInfo.slice->Clone(); mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); contourExtractor->SetInput(slice2); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { Self::RemoveContourFromInterpolator(sliceInfo); } else { relevantSlices.push_back(sliceInfo); } } } if (relevantSlices.empty()) return; for (const auto& sliceInfo : relevantSlices) { contourExtractor->SetInput(sliceInfo.slice); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { Self::RemoveContourFromInterpolator(sliceInfo); } else { contour->DisconnectPipeline(); contourList.push_back(contour); } } mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const InteractionPositionEvent *positionEvent, const Image *image, unsigned int component /*= 0*/) { if (!positionEvent) { return nullptr; } assert(positionEvent->GetSender()); // sure, right? const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); // get the timestep of the visible part (time-wise) of the image return GetAffectedImageSliceAs2DImage(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry(), image, timeStep, component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; return SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint), component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const PlaneGeometry *planeGeometry, const Image *image, TimeStepType timeStep, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); // use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); // additionally extract the given component // default is 0; the extractor checks for multi-component images extractor->SetComponent(component); extractor->Modified(); extractor->Update(); Image::Pointer slice = extractor->GetOutput(); return slice; } mitk::Image::Pointer mitk::SegTool2D::GetAffectedWorkingSlice(const InteractionPositionEvent *positionEvent) const { const auto workingNode = this->GetWorkingDataNode(); if (!workingNode) { return nullptr; } const auto *workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { return nullptr; } return GetAffectedImageSliceAs2DImage(positionEvent, workingImage); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const InteractionPositionEvent *positionEvent) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto *referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage); } } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto* referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep); } } void mitk::SegTool2D::Activated() { Superclass::Activated(); - m_ToolManager->SelectedTimePointChanged += + this->GetToolManager()->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); m_LastTimePointTriggered = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); } void mitk::SegTool2D::Deactivated() { - m_ToolManager->SelectedTimePointChanged -= + this->GetToolManager()->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); Superclass::Deactivated(); } void mitk::SegTool2D::OnTimePointChangedInternal() { if (m_IsTimePointChangeAware && nullptr != this->GetWorkingDataNode()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (timePoint != m_LastTimePointTriggered) { m_LastTimePointTriggered = timePoint; this->OnTimePointChanged(); } } } void mitk::SegTool2D::OnTimePointChanged() { //default implementation does nothing } mitk::DataNode* mitk::SegTool2D::GetWorkingDataNode() const { - if (nullptr != m_ToolManager) + if (nullptr != this->GetToolManager()) { - return m_ToolManager->GetWorkingData(0); + return this->GetToolManager()->GetWorkingData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetWorkingData() const { auto node = this->GetWorkingDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } mitk::DataNode* mitk::SegTool2D::GetReferenceDataNode() const { - if (nullptr != m_ToolManager) + if (nullptr != this->GetToolManager()) { - return m_ToolManager->GetReferenceData(0); + return this->GetToolManager()->GetReferenceData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetReferenceData() const { auto node = this->GetReferenceDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } void mitk::SegTool2D::WriteBackSegmentationResult(const InteractionPositionEvent *positionEvent, const Image * segmentationResult) { if (!positionEvent) return; const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); const auto *abstractTransformGeometry( dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (planeGeometry && segmentationResult && !abstractTransformGeometry) { const auto workingNode = this->GetWorkingDataNode(); auto *image = dynamic_cast(workingNode->GetData()); const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); this->WriteBackSegmentationResult(planeGeometry, segmentationResult, timeStep); } } void mitk::SegTool2D::WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); Self::WriteBackSegmentationResults(workingNode, { sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image * segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); WriteBackSegmentationResults({ sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResults(const std::vector &sliceList, bool writeSliceToVolume) { if (sliceList.empty()) { return; } if (nullptr == m_LastEventSender) { MITK_WARN << "Cannot write tool results. Tool seems to be in an invalid state, as no interaction event was recieved but is expected."; return; } const auto workingNode = this->GetWorkingDataNode(); mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); // the first geometry is needed otherwise restoring the position is not working const auto* plane3 = dynamic_cast(dynamic_cast( m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) ->GetPlaneGeometry(0)); unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetSlice()->GetPos(); /* A cleaner solution would be to add a contour marker for each slice info. It currently does not work as the contour markers expect that the plane is always the plane of slice 0. Had not the time to do it properly no. Should be solved by T28146*/ this->AddContourmarker(plane3, slicePosition); } void mitk::SegTool2D::WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume) { if (sliceList.empty()) { return; } if (nullptr == workingNode) { mitkThrow() << "Cannot write slice to working node. Working node is invalid."; } auto* image = dynamic_cast(workingNode->GetData()); if (nullptr == image) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } for (const auto& sliceInfo : sliceList) { if (writeSliceToVolume && nullptr != sliceInfo.plane && sliceInfo.slice.IsNotNull()) { mitk::SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } mitk::SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false); // also mark its node as modified (T27308). Can be removed if T27307 // is properly solved if (workingNode != nullptr) workingNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo) { SliceInformation sliceInfo(slice, planeGeometry, timeStep); WriteSliceToVolume(workingImage, sliceInfo , allowUndo); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo) { if (nullptr == workingImage) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } DiffSliceOperation* undoOperation = nullptr; if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // Create undo operation by caching the not yet modified slices mitk::Image::Pointer originalSlice = GetAffectedImageSliceAs2DImage(sliceInfo.plane, workingImage, sliceInfo.timestep); undoOperation = new DiffSliceOperation(workingImage, originalSlice, dynamic_cast(originalSlice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); /*============= END undo/redo feature block ========================*/ } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set the slice as 'input' // casting const away is needed and OK as long the OverwriteMode of // mitkVTKImageOverwrite is true. // Reason: because then the input slice is not touched but // used to overwrite the input of the ExtractSliceFilter. auto noneConstSlice = const_cast(sliceInfo.slice.GetPointer()); reslice->SetInputSlice(noneConstSlice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(workingImage); extractor->SetTimeStep(sliceInfo.timestep); extractor->SetWorldGeometry(sliceInfo.plane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(workingImage->GetGeometry(sliceInfo.timestep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so workingImage->Modified(); workingImage->GetVtkImageData()->Modified(); if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // specify the redo operation with the edited slice auto* doOperation = new DiffSliceOperation(workingImage, extractor->GetOutput(), dynamic_cast(sliceInfo.slice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); // create an operation event for the undo stack OperationEvent* undoStackItem = new OperationEvent(DiffSliceOperationApplier::GetInstance(), doOperation, undoOperation, "Segmentation"); // add it to the undo controller UndoStackItem::IncCurrObjectEventId(); UndoStackItem::IncCurrGroupEventId(); UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); /*============= END undo/redo feature block ========================*/ } } void mitk::SegTool2D::SetShowMarkerNodes(bool status) { m_ShowMarkerNodes = status; } void mitk::SegTool2D::SetEnable3DInterpolation(bool enabled) { m_SurfaceInterpolationEnabled = enabled; } int mitk::SegTool2D::AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex) { if (planeGeometry == nullptr) return -1; us::ServiceReference serviceRef = us::GetModuleContext()->GetServiceReference(); PlanePositionManagerService *service = us::GetModuleContext()->GetService(serviceRef); unsigned int size = service->GetNumberOfPlanePositions(); unsigned int id = service->AddNewPlanePosition(planeGeometry, sliceIndex); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); mitk::Point2D p2 = p1; p2[0] -= planeGeometry->GetSpacing()[0]; p2[1] -= planeGeometry->GetSpacing()[1]; contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); contourMarker->SetPlaneGeometry(planeGeometry->Clone()); std::stringstream markerStream; auto workingNode = this->GetWorkingDataNode(); markerStream << m_Contourmarkername; markerStream << " "; markerStream << id + 1; DataNode::Pointer rotatedContourNode = DataNode::New(); rotatedContourNode->SetData(contourMarker); rotatedContourNode->SetProperty("name", StringProperty::New(markerStream.str())); rotatedContourNode->SetProperty("isContourMarker", BoolProperty::New(true)); rotatedContourNode->SetBoolProperty("PlanarFigureInitializedWindow", true, m_LastEventSender); rotatedContourNode->SetProperty("includeInBoundingBox", BoolProperty::New(false)); rotatedContourNode->SetProperty("helper object", mitk::BoolProperty::New(!m_ShowMarkerNodes)); rotatedContourNode->SetProperty("planarfigure.drawcontrolpoints", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawname", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawoutline", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawshadow", BoolProperty::New(false)); if (planeGeometry) { if (id == size) { - m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); + this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } else { mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer markers = - m_ToolManager->GetDataStorage()->GetDerivations(workingNode, isMarker); + this->GetToolManager()->GetDataStorage()->GetDerivations(workingNode, isMarker); for (auto iter = markers->begin(); iter != markers->end(); ++iter) { std::string nodeName = (*iter)->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int markerId = atof(nodeName.substr(t + 1).c_str()) - 1; if (id == markerId) { return id; } } - m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); + this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } } return id; } void mitk::SegTool2D::InteractiveSegmentationBugMessage(const std::string &message) const { MITK_ERROR << "********************************************************************************" << std::endl << " " << message << std::endl << "********************************************************************************" << std::endl << " " << std::endl << " If your image is rotated or the 2D views don't really contain the patient image, try to press the " "button next to the image selection. " << std::endl << " " << std::endl << " Please file a BUG REPORT: " << std::endl << " https://phabricator.mitk.org/" << std::endl << " Contain the following information:" << std::endl << " - What image were you working on?" << std::endl << " - Which region of the image?" << std::endl << " - Which tool did you use?" << std::endl << " - What did you do?" << std::endl << " - What happened (not)? What did you expect?" << std::endl; } void mitk::SegTool2D::WritePreviewOnWorkingImage( Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue) { if (nullptr == targetSlice) { mitkThrow() << "Cannot write preview on working image. Target slice does not point to a valid instance."; } if (nullptr == sourceSlice) { mitkThrow() << "Cannot write preview on working image. Source slice does not point to a valid instance."; } if (nullptr == workingImage) { mitkThrow() << "Cannot write preview on working image. Working image does not point to a valid instance."; } auto constVtkSource = sourceSlice->GetVtkImageData(); /*Need to const cast because Vtk interface does not support const correctly. (or I am not experienced enough to use it correctly)*/ auto nonConstVtkSource = const_cast(constVtkSource); ContourModelUtils::FillSliceInSlice(nonConstVtkSource, targetSlice->GetVtkImageData(), workingImage, paintingPixelValue, 1.0); } diff --git a/Modules/Segmentation/Interactions/mitkSegmentationInteractor.cpp b/Modules/Segmentation/Interactions/mitkSegmentationInteractor.cpp index 1b67d32f1f..f14f92c7a4 100644 --- a/Modules/Segmentation/Interactions/mitkSegmentationInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkSegmentationInteractor.cpp @@ -1,63 +1,65 @@ /*============================================================================ 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 "mitkSegmentationInteractor.h" #include "mitkInteractionPositionEvent.h" #include "mitkLabelSetImage.h" #include "mitkToolManager.h" #include "mitkToolManagerProvider.h" #include #include void mitk::SegmentationInteractor::ConnectActionsAndFunctions() { Superclass::ConnectActionsAndFunctions(); // CONNECT_FUNCTION("change_active_label", ChangeActiveLabel); } bool mitk::SegmentationInteractor::ChangeActiveLabel(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer::Pointer sender = interactionEvent->GetSender(); - auto *positionEvent = static_cast(interactionEvent); + auto positionEvent = static_cast(interactionEvent); - // MLI TODO - // m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; - // m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); - - auto *toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager( + auto toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager( mitk::ToolManagerProvider::MULTILABEL_SEGMENTATION); assert(toolManager); DataNode *workingNode(toolManager->GetWorkingData(0)); - if (workingNode) + if (workingNode && positionEvent) { - auto *workingImage = dynamic_cast(workingNode->GetData()); - assert(workingImage); + //TODO T28561 + //Code uses a deprecated method. deactivated whole code until the refactorization is done. + throw "TODO T28561. Was forgot to refactor in context of T28524. The new MultiLabelSegmentation class will have a dedicated function for querying the label of world position."; + + //auto *workingImage = dynamic_cast(workingNode->GetData()); + //assert(workingImage); + + //const auto timestep = positionEvent->GetSender()->GetTimeStep(workingImage); - const auto timestep = positionEvent->GetSender()->GetTimeStep(workingImage); - int pixelValue = static_cast(workingImage->GetPixelValueByWorldCoordinate(positionEvent->GetPositionInWorld(), timestep)); + //int pixelValue = static_cast(workingImage->GetPixelValueByWorldCoordinate(positionEvent->GetPositionInWorld(), timestep)); + //workingImage->GetActiveLabelSet()->SetActiveLabel(pixelValue); // can be the background - workingImage->GetActiveLabelSet()->SetActiveLabel(pixelValue); // can be the background + // // Call Events + // // workingImage->ActiveLabelEvent.Send(pixelValue); - // Call Events - // workingImage->ActiveLabelEvent.Send(pixelValue); + // // MLI TODO + // // toolManager->WorkingDataModified.Send(); - // MLI TODO - // toolManager->WorkingDataModified.Send(); + //TODO END Refactor with T28524 } RenderingManager::GetInstance()->RequestUpdateAll(); return true; } diff --git a/Modules/Segmentation/Interactions/mitkSegmentationsProcessingTool.cpp b/Modules/Segmentation/Interactions/mitkSegmentationsProcessingTool.cpp index 4e6848a45c..96d82261d6 100644 --- a/Modules/Segmentation/Interactions/mitkSegmentationsProcessingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegmentationsProcessingTool.cpp @@ -1,103 +1,103 @@ /*============================================================================ 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 "mitkSegmentationsProcessingTool.h" #include "mitkProgressBar.h" #include "mitkToolManager.h" mitk::SegmentationsProcessingTool::SegmentationsProcessingTool() : Tool("dummy") { } mitk::SegmentationsProcessingTool::~SegmentationsProcessingTool() { } const char *mitk::SegmentationsProcessingTool::GetGroup() const { return "segmentationProcessing"; } void mitk::SegmentationsProcessingTool::Activated() { Superclass::Activated(); ProcessAllObjects(); - m_ToolManager->ActivateTool(-1); + this->GetToolManager()->ActivateTool(-1); } void mitk::SegmentationsProcessingTool::Deactivated() { Superclass::Deactivated(); } void mitk::SegmentationsProcessingTool::ProcessAllObjects() { m_FailedNodes.clear(); StartProcessingAllData(); - ToolManager::DataVectorType nodes = m_ToolManager->GetWorkingData(); + ToolManager::DataVectorType nodes = this->GetToolManager()->GetWorkingData(); ProgressBar::GetInstance()->AddStepsToDo(nodes.size() + 2); // for all selected nodes for (auto nodeiter = nodes.begin(); nodeiter != nodes.end(); ++nodeiter) { DataNode::Pointer node = *nodeiter; if (!ProcessOneWorkingData(node)) { std::string nodeName; m_FailedNodes += " '"; if (node->GetName(nodeName)) { m_FailedNodes += nodeName.c_str(); } else { m_FailedNodes += "(no name)"; } m_FailedNodes += "'"; } ProgressBar::GetInstance()->Progress(); } FinishProcessingAllData(); ProgressBar::GetInstance()->Progress(2); } void mitk::SegmentationsProcessingTool::StartProcessingAllData() { } bool mitk::SegmentationsProcessingTool::ProcessOneWorkingData(DataNode *) { return true; } void mitk::SegmentationsProcessingTool::FinishProcessingAllData() { SendErrorMessageIfAny(); } void mitk::SegmentationsProcessingTool::SendErrorMessageIfAny() { if (!m_FailedNodes.empty()) { Tool::ErrorMessage(GetErrorMessage() + m_FailedNodes); } } std::string mitk::SegmentationsProcessingTool::GetErrorMessage() { return "Processing of these nodes failed:"; } diff --git a/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp b/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp index da8a94ed20..486679ddaf 100644 --- a/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp @@ -1,138 +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 "mitkSetRegionTool.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include #include #include #include mitk::SetRegionTool::SetRegionTool(int paintingPixelValue) : FeedbackContourTool("PressMoveRelease"), m_PaintingPixelValue(paintingPixelValue) { } mitk::SetRegionTool::~SetRegionTool() { } void mitk::SetRegionTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Release", OnMouseReleased); CONNECT_FUNCTION("Move", OnMouseMoved); } void mitk::SetRegionTool::Activated() { Superclass::Activated(); } void mitk::SetRegionTool::Deactivated() { Superclass::Deactivated(); } void mitk::SetRegionTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); // 1. Get the working image Image::Pointer workingSlice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); if (workingSlice.IsNull()) return; // can't do anything without the segmentation // if click was outside the image, don't continue const BaseGeometry *sliceGeometry = workingSlice->GetGeometry(); itk::Index<3> projectedPointIn2D; sliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), projectedPointIn2D); if (!sliceGeometry->IsIndexInside(projectedPointIn2D)) { MITK_WARN << "Point outside of segmentation slice." << std::endl; return; // can't use that as a seed point } typedef itk::Image InputImageType; typedef InputImageType::IndexType IndexType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); // convert world coordinates to image indices IndexType seedIndex; sliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), seedIndex); // perform region growing in desired segmented region InputImageType::Pointer itkImage = InputImageType::New(); CastToItkImage(workingSlice, itkImage); regionGrower->SetInput(itkImage); regionGrower->AddSeed(seedIndex); InputImageType::PixelType bound = itkImage->GetPixel(seedIndex); regionGrower->SetLower(bound); regionGrower->SetUpper(bound); regionGrower->SetReplaceValue(1); itk::BinaryFillholeImageFilter::Pointer fillHolesFilter = itk::BinaryFillholeImageFilter::New(); fillHolesFilter->SetInput(regionGrower->GetOutput()); fillHolesFilter->SetForegroundValue(1); // Store result and preview mitk::Image::Pointer resultImage = mitk::GrabItkImageMemory(fillHolesFilter->GetOutput()); resultImage->SetGeometry(workingSlice->GetGeometry()); // Get the current working color - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); + DataNode *workingNode(this->GetToolManager()->GetWorkingData(0)); if (!workingNode) return; mitk::ImageToContourModelFilter::Pointer contourextractor = mitk::ImageToContourModelFilter::New(); contourextractor->SetInput(resultImage); contourextractor->Update(); mitk::ContourModel::Pointer awesomeContour = contourextractor->GetOutput(); this->UpdateCurrentFeedbackContour(awesomeContour); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::SetRegionTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; assert(positionEvent->GetSender()->GetRenderWindow()); // 1. Hide the feedback contour, find out which slice the user clicked, find out which slice of the toolmanager's // working image corresponds to that mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); this->WriteBackFeedbackContourAsSegmentationResult(positionEvent, m_PaintingPixelValue); } void mitk::SetRegionTool::OnMouseMoved(mitk::StateMachineAction *, mitk::InteractionEvent *) { } diff --git a/Modules/Segmentation/Interactions/mitkTool.cpp b/Modules/Segmentation/Interactions/mitkTool.cpp index 5f15bc5dbf..e17746d67a 100644 --- a/Modules/Segmentation/Interactions/mitkTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTool.cpp @@ -1,333 +1,348 @@ /*============================================================================ 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 "mitkTool.h" #include #include "mitkDisplayInteractor.h" #include "mitkDisplayActionEventBroadcast.h" #include "mitkImageReadAccessor.h" #include "mitkImageWriteAccessor.h" #include "mitkLevelWindowProperty.h" #include "mitkLookupTableProperty.h" #include "mitkProperties.h" #include "mitkVtkResliceInterpolationProperty.h" #include +#include // us #include #include // itk #include mitk::Tool::Tool(const char *type, const us::Module *interactorModule) : m_EventConfig("DisplayConfigMITK.xml"), m_ToolManager(nullptr), m_PredicateImages(NodePredicateDataType::New("Image")), // for reference images m_PredicateDim3(NodePredicateDimension::New(3, 1)), m_PredicateDim4(NodePredicateDimension::New(4, 1)), m_PredicateDimension(mitk::NodePredicateOr::New(m_PredicateDim3, m_PredicateDim4)), m_PredicateImage3D(NodePredicateAnd::New(m_PredicateImages, m_PredicateDimension)), m_PredicateBinary(NodePredicateProperty::New("binary", BoolProperty::New(true))), m_PredicateNotBinary(NodePredicateNot::New(m_PredicateBinary)), m_PredicateSegmentation(NodePredicateProperty::New("segmentation", BoolProperty::New(true))), m_PredicateNotSegmentation(NodePredicateNot::New(m_PredicateSegmentation)), m_PredicateHelper(NodePredicateProperty::New("helper object", BoolProperty::New(true))), m_PredicateNotHelper(NodePredicateNot::New(m_PredicateHelper)), m_PredicateImageColorful(NodePredicateAnd::New(m_PredicateNotBinary, m_PredicateNotSegmentation)), m_PredicateImageColorfulNotHelper(NodePredicateAnd::New(m_PredicateImageColorful, m_PredicateNotHelper)), m_PredicateReference(NodePredicateAnd::New(m_PredicateImage3D, m_PredicateImageColorfulNotHelper)), m_IsSegmentationPredicate( NodePredicateAnd::New(NodePredicateOr::New(m_PredicateBinary, m_PredicateSegmentation), m_PredicateNotHelper)), m_InteractorType(type), m_DisplayInteractorConfigs(), m_InteractorModule(interactorModule) { } mitk::Tool::~Tool() { } bool mitk::Tool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { if (referenceData == nullptr) return false; return true; } void mitk::Tool::InitializeStateMachine() { if (m_InteractorType.empty()) return; try { auto isThisModule = nullptr == m_InteractorModule; auto module = isThisModule ? us::GetModuleContext()->GetModule() : m_InteractorModule; LoadStateMachine(m_InteractorType + ".xml", module); SetEventConfig(isThisModule ? "SegmentationToolsConfig.xml" : m_InteractorType + "Config.xml", module); } catch (const std::exception &e) { MITK_ERROR << "Could not load statemachine pattern " << m_InteractorType << ".xml with exception: " << e.what(); } } void mitk::Tool::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled) { this->HandleEvent(interactionEvent, nullptr); } } void mitk::Tool::ConnectActionsAndFunctions() { } bool mitk::Tool::FilterEvents(InteractionEvent *, DataNode *) { return true; } const char *mitk::Tool::GetGroup() const { return "default"; } void mitk::Tool::SetToolManager(ToolManager *manager) { m_ToolManager = manager; } +mitk::ToolManager* mitk::Tool::GetToolManager() const +{ + return m_ToolManager; +} + +mitk::DataStorage* mitk::Tool::GetDataStorage() const +{ + if (nullptr != m_ToolManager) + { + return m_ToolManager->GetDataStorage(); + } + return nullptr; +} + void mitk::Tool::Activated() { // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts // with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction // will still be enabled m_DisplayInteractorConfigs.clear(); std::vector> listEventObserver = us::GetModuleContext()->GetServiceReferences(); for (auto it = listEventObserver.begin(); it != listEventObserver.end(); ++it) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig(m_EventConfig.c_str()); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayActionEventBroadcast != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayActionEventBroadcast->GetEventConfig())); // here the alternative configuration is loaded displayActionEventBroadcast->SetEventConfig(m_EventConfig.c_str()); } } } void mitk::Tool::Deactivated() { // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (auto it = m_DisplayInteractorConfigs.begin(); it != m_DisplayInteractorConfigs.end(); ++it) { if (it->first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(it->first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(it->second); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(it->first)); if (displayActionEventBroadcast != nullptr) { // here the regular configuration is loaded again displayActionEventBroadcast->SetEventConfig(it->second); } } } m_DisplayInteractorConfigs.clear(); } itk::Object::Pointer mitk::Tool::GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix) { itk::Object::Pointer object; std::string classname = this->GetNameOfClass(); std::string guiClassname = toolkitPrefix + classname + toolkitPostfix; std::list allGUIs = itk::ObjectFactoryBase::CreateAllInstance(guiClassname.c_str()); for (auto iter = allGUIs.begin(); iter != allGUIs.end(); ++iter) { if (object.IsNull()) { object = dynamic_cast(iter->GetPointer()); } else { MITK_ERROR << "There is more than one GUI for " << classname << " (several factories claim ability to produce a " << guiClassname << " ) " << std::endl; return nullptr; // people should see and fix this error } } return object; } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetReferenceDataPreference() const { return m_PredicateReference.GetPointer(); } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetWorkingDataPreference() const { return m_IsSegmentationPredicate.GetPointer(); } mitk::DataNode::Pointer mitk::Tool::CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) const { // we NEED a reference image for size etc. if (!original) return nullptr; // actually create a new empty segmentation PixelType pixelType(mitk::MakeScalarPixelType()); LabelSetImage::Pointer segmentation = LabelSetImage::New(); if (original->GetDimension() == 2) { const unsigned int dimensions[] = {original->GetDimension(0), original->GetDimension(1), 1}; segmentation->Initialize(pixelType, 3, dimensions); segmentation->AddLayer(); } else { segmentation->Initialize(original); } mitk::Label::Pointer label = mitk::Label::New(); label->SetName(organName); label->SetColor(color); label->SetValue(1); segmentation->GetActiveLabelSet()->AddLabel(label); segmentation->GetActiveLabelSet()->SetActiveLabel(1); unsigned int byteSize = sizeof(mitk::Label::PixelType); if (segmentation->GetDimension() < 4) { for (unsigned int dim = 0; dim < segmentation->GetDimension(); ++dim) { byteSize *= segmentation->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); } else { // if we have a time-resolved image we need to set memory to 0 for each time step for (unsigned int dim = 0; dim < 3; ++dim) { byteSize *= segmentation->GetDimension(dim); } for (unsigned int volumeNumber = 0; volumeNumber < segmentation->GetDimension(3); volumeNumber++) { mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(volumeNumber)); memset(writeAccess.GetData(), 0, byteSize); } } if (original->GetTimeGeometry()) { TimeGeometry::Pointer originalGeometry = original->GetTimeGeometry()->Clone(); segmentation->SetTimeGeometry(originalGeometry); } else { Tool::ErrorMessage("Original image does not have a 'Time sliced geometry'! Cannot create a segmentation."); return nullptr; } return CreateSegmentationNode(segmentation, organName, color); } mitk::DataNode::Pointer mitk::Tool::CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) const { if (!image) return nullptr; // decorate the datatreenode with some properties DataNode::Pointer segmentationNode = DataNode::New(); segmentationNode->SetData(image); // name segmentationNode->SetProperty("name", StringProperty::New(organName)); // visualization properties segmentationNode->SetProperty("binary", BoolProperty::New(true)); segmentationNode->SetProperty("color", ColorProperty::New(color)); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::MULTILABEL); mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(); lutProp->SetLookupTable(lut); segmentationNode->SetProperty("LookupTable", lutProp); segmentationNode->SetProperty("texture interpolation", BoolProperty::New(false)); segmentationNode->SetProperty("layer", IntProperty::New(10)); segmentationNode->SetProperty("levelwindow", LevelWindowProperty::New(LevelWindow(0.5, 1))); segmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); segmentationNode->SetProperty("segmentation", BoolProperty::New(true)); segmentationNode->SetProperty("reslice interpolation", VtkResliceInterpolationProperty::New()); // otherwise -> segmentation appears in 2 // slices sometimes (only visual effect, not // different data) // For MITK-3M3 release, the volume of all segmentations should be shown segmentationNode->SetProperty("showVolume", BoolProperty::New(true)); return segmentationNode; } us::ModuleResource mitk::Tool::GetIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } us::ModuleResource mitk::Tool::GetCursorIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } diff --git a/Modules/Segmentation/Interactions/mitkTool.h b/Modules/Segmentation/Interactions/mitkTool.h index ddc998b8d0..c2f6af5a78 100644 --- a/Modules/Segmentation/Interactions/mitkTool.h +++ b/Modules/Segmentation/Interactions/mitkTool.h @@ -1,269 +1,275 @@ /*============================================================================ 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 mitkTool_h_Included #define mitkTool_h_Included #include "itkObjectFactoryBase.h" #include "itkVersion.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkEventStateMachine.h" #include "mitkInteractionEventObserver.h" #include "mitkLabelSetImage.h" #include "mitkMessage.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateDimension.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateProperty.h" #include "mitkToolEvents.h" #include "mitkToolFactoryMacro.h" #include #include #include #include #include #include #include "usServiceRegistration.h" namespace us { class ModuleResource; } namespace mitk { class ToolManager; /** \brief Base class of all tools used by mitk::ToolManager. \sa ToolManager \sa SegTool2D \ingroup Interaction \ingroup ToolManagerEtAl Every tool is a mitk::StateMachine, which can follow any transition pattern that it likes. One important thing to know is, that every derived tool should always call SuperClass::Deactivated() at the end of its own implementation of Deactivated, because mitk::Tool resets the StateMachine in this method. Only if you are very sure that you covered all possible things that might happen to your own tool, you should consider not to reset the StateMachine from time to time. To learn about the MITK implementation of state machines in general, have a look at \ref InteractionPage. To derive a non-abstract tool, you inherit from mitk::Tool (or some other base class further down the inheritance tree), and in your own parameterless constructor (that is called from the itkFactorylessNewMacro that you use) you pass a StateMachine pattern name to the superclass. Names for valid patterns can be found in StateMachine.xml (which might be enhanced by you). You have to implement at least GetXPM() and GetName() to provide some identification. Each Tool knows its ToolManager, which can provide the data that the tool should work on. \warning Only to be instantiated by mitk::ToolManager (because SetToolManager has to be called). All other uses are unsupported. $Author$ */ class MITKSEGMENTATION_EXPORT Tool : public EventStateMachine, public InteractionEventObserver { public: typedef mitk::Label::PixelType DefaultSegmentationDataType; /** * \brief To let GUI process new events (e.g. qApp->processEvents() ) */ Message<> GUIProcessEventsMessage; /** * \brief To send error messages (to be shown by some GUI) */ Message1 ErrorMessage; /** * \brief To send whether the tool is busy (to be shown by some GUI) */ Message1 CurrentlyBusy; /** * \brief To send general messages (to be shown by some GUI) */ Message1 GeneralMessage; mitkClassMacro(Tool, EventStateMachine); // no New(), there should only be subclasses /** \brief Returns an icon in the XPM format. This icon has to fit into some kind of button in most applications, so make it smaller than 25x25 pixels. XPM is e.g. supported by The Gimp. But if you open any XPM file in your text editor, you will see that you could also "draw" it with an editor. */ virtual const char **GetXPM() const = 0; /** * \brief Returns the path of an icon. * * This icon is preferred to the XPM icon. */ virtual std::string GetIconPath() const { return ""; } /** * \brief Returns the path of a cursor icon. * */ virtual us::ModuleResource GetCursorIconResource() const; /** * @brief Returns the tool button icon of the tool wrapped by a usModuleResource * @return a valid ModuleResource or an invalid if this function * is not reimplemented */ virtual us::ModuleResource GetIconResource() const; /** \brief Returns the name of this tool. Make it short! This name has to fit into some kind of button in most applications, so take some time to think of a good name! */ virtual const char *GetName() const = 0; /** \brief Name of a group. You can group several tools by assigning a group name. Graphical tool selectors might use this information to group tools. (What other reason could there be?) */ virtual const char *GetGroup() const; virtual void InitializeStateMachine(); /** * \brief Interface for GUI creation. * * This is the basic interface for creation of a GUI object belonging to one tool. * * Tools that support a GUI (e.g. for display/editing of parameters) should follow some rules: * * - A Tool and its GUI are two separate classes * - There may be several instances of a GUI at the same time. * - mitk::Tool is toolkit (Qt, wxWidgets, etc.) independent, the GUI part is of course dependent * - The GUI part inherits both from itk::Object and some GUI toolkit class * - The GUI class name HAS to be constructed like "toolkitPrefix" tool->GetClassName() + "toolkitPostfix", e.g. * MyTool -> wxMyToolGUI * - For each supported toolkit there is a base class for tool GUIs, which contains some convenience methods * - Tools notify the GUI about changes using ITK events. The GUI must observe interesting events. * - The GUI base class may convert all ITK events to the GUI toolkit's favoured messaging system (Qt -> signals) * - Calling methods of a tool by its GUI is done directly. * In some cases GUIs don't want to be notified by the tool when they cause a change in a tool. * There is a macro CALL_WITHOUT_NOTICE(method()), which will temporarily disable all notifications during a * method call. */ virtual itk::Object::Pointer GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix); virtual NodePredicateBase::ConstPointer GetReferenceDataPreference() const; virtual NodePredicateBase::ConstPointer GetWorkingDataPreference() const; DataNode::Pointer CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) const; DataNode::Pointer CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) const; /** Function used to check if a tool can handle the referenceData and (if specified) the working data. @pre referenceData must be a valid pointer @param referenceData Pointer to the data that should be checked as valid reference for the tool. @param workingData Pointer to the data that should be checked as valid working data for this tool. This parameter can be null if no working data is specified so far.*/ virtual bool CanHandle(const BaseData *referenceData, const BaseData *workingData) const; protected: friend class ToolManager; virtual void SetToolManager(ToolManager *); + /** Returns the pointer to the tool manager of the tool. May be null.*/ + ToolManager* GetToolManager() const; + + /** Returns the data storage provided by the toolmanager. May be null (e.g. if + ToolManager is not set).*/ + mitk::DataStorage* GetDataStorage() const; void ConnectActionsAndFunctions() override; /** \brief Called when the tool gets activated. Derived tools should call their parents implementation at the beginning of the overriding function. */ virtual void Activated(); /** \brief Called when the tool gets deactivated. Derived tools should call their parents implementation at the end of the overriding function. */ virtual void Deactivated(); /** \brief Let subclasses change their event configuration. */ std::string m_EventConfig; Tool(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~Tool() override; void Notify(InteractionEvent *interactionEvent, bool isHandled) override; bool FilterEvents(InteractionEvent *, DataNode *) override; - ToolManager *m_ToolManager; - private: + ToolManager* m_ToolManager; + // for reference data NodePredicateDataType::Pointer m_PredicateImages; NodePredicateDimension::Pointer m_PredicateDim3; NodePredicateDimension::Pointer m_PredicateDim4; NodePredicateOr::Pointer m_PredicateDimension; NodePredicateAnd::Pointer m_PredicateImage3D; NodePredicateProperty::Pointer m_PredicateBinary; NodePredicateNot::Pointer m_PredicateNotBinary; NodePredicateProperty::Pointer m_PredicateSegmentation; NodePredicateNot::Pointer m_PredicateNotSegmentation; NodePredicateProperty::Pointer m_PredicateHelper; NodePredicateNot::Pointer m_PredicateNotHelper; NodePredicateAnd::Pointer m_PredicateImageColorful; NodePredicateAnd::Pointer m_PredicateImageColorfulNotHelper; NodePredicateAnd::Pointer m_PredicateReference; // for working data NodePredicateAnd::Pointer m_IsSegmentationPredicate; std::string m_InteractorType; std::map m_DisplayInteractorConfigs; const us::Module *m_InteractorModule; }; } // namespace #endif diff --git a/Modules/Segmentation/Resources/Interactions/PickingTool.xml b/Modules/Segmentation/Resources/Interactions/PickingTool.xml new file mode 100644 index 0000000000..1baf80a04b --- /dev/null +++ b/Modules/Segmentation/Resources/Interactions/PickingTool.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 68d22a6ccc..cb3c19d915 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,116 +1,117 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkOverwriteDirectedPlaneImageFilter.cpp Algorithms/mitkOverwriteSliceImageFilter.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAdaptiveRegionGrowingTool.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkAutoSegmentationTool.cpp Interactions/mitkAutoSegmentationWithPreviewTool.cpp Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCalculateGrayValueStatisticsTool.cpp Interactions/mitkCalculateVolumetryTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkContourTool.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFastMarchingBaseTool.cpp Interactions/mitkFastMarchingTool.cpp Interactions/mitkFastMarchingTool3D.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkPixelManipulationTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkWatershedTool.cpp Interactions/mitkPickingTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add_48x48.png Add_Cursor_32x32.png Erase_48x48.png Erase_Cursor_32x32.png FastMarching_48x48.png FastMarching_Cursor_32x32.png Fill_48x48.png Fill_Cursor_32x32.png LiveWire_48x48.png LiveWire_Cursor_32x32.png Otsu_48x48.png Paint_48x48.png Paint_Cursor_32x32.png Pick_48x48.png RegionGrowing_48x48.png RegionGrowing_Cursor_32x32.png Subtract_48x48.png Subtract_Cursor_32x32.png Threshold_48x48.png TwoThresholds_48x48.png Watershed_48x48.png Watershed_Cursor_32x32.png Wipe_48x48.png Wipe_Cursor_32x32.png Interactions/dummy.xml Interactions/LiveWireTool.xml Interactions/FastMarchingTool.xml + Interactions/PickingTool.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp index b332866f6f..863e753c08 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp @@ -1,95 +1,89 @@ /*============================================================================ 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 "QmitkAutoMLSegmentationToolGUIBase.h" #include "mitkAutoMLSegmentationWithPreviewTool.h" #include QmitkAutoMLSegmentationToolGUIBase::QmitkAutoMLSegmentationToolGUIBase() : QmitkAutoSegmentationToolGUIBase(false) { - auto enableOtsuDelegate = [this](bool enabled) + auto enableMLSelectedDelegate = [this](bool enabled) { bool result = enabled; auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { result = !tool->GetSelectedLabels().empty() && enabled; } else { result = false; } return result; }; - m_EnableConfirmSegBtnFnc = enableOtsuDelegate; -} - -void QmitkAutoMLSegmentationToolGUIBase::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) -{ - Superclass::ConnectNewTool(newTool); - this->EnableWidgets(true); + m_EnableConfirmSegBtnFnc = enableMLSelectedDelegate; } void QmitkAutoMLSegmentationToolGUIBase::InitializeUI(QBoxLayout* mainLayout) { m_LabelSelectionList = new QmitkSimpleLabelSetListWidget(this); m_LabelSelectionList->setObjectName(QString::fromUtf8("m_LabelSelectionList")); QSizePolicy sizePolicy2(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); sizePolicy2.setHorizontalStretch(0); sizePolicy2.setVerticalStretch(0); sizePolicy2.setHeightForWidth(m_LabelSelectionList->sizePolicy().hasHeightForWidth()); m_LabelSelectionList->setSizePolicy(sizePolicy2); m_LabelSelectionList->setMaximumSize(QSize(10000000, 10000000)); mainLayout->addWidget(m_LabelSelectionList); connect(m_LabelSelectionList, &QmitkSimpleLabelSetListWidget::SelectedLabelsChanged, this, &QmitkAutoMLSegmentationToolGUIBase::OnLabelSelectionChanged); Superclass::InitializeUI(mainLayout); } void QmitkAutoMLSegmentationToolGUIBase::OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels) { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType labelIDs; for (const auto& label : selectedLabels) { labelIDs.push_back(label->GetValue()); } tool->SetSelectedLabels(labelIDs); tool->UpdatePreview(); this->EnableWidgets(true); //used to actualize the ConfirmSeg btn via the delegate; } } void QmitkAutoMLSegmentationToolGUIBase::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); if (nullptr != m_LabelSelectionList) { m_LabelSelectionList->setEnabled(enabled); } } void QmitkAutoMLSegmentationToolGUIBase::SetLabelSetPreview(const mitk::LabelSetImage* preview) { if (nullptr != m_LabelSelectionList) { m_LabelSelectionList->SetLabelSetImage(preview); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h index 76bf69cea8..e4e48dc867 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h @@ -1,53 +1,52 @@ /*============================================================================ 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 QmitkAutoMLSegmentationToolGUIBase_h_Included #define QmitkAutoMLSegmentationToolGUIBase_h_Included #include "QmitkAutoSegmentationToolGUIBase.h" #include "QmitkSimpleLabelSetListWidget.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for tools based on mitk::AutoMLSegmentationWithPreviewTool. This GUI offers an additional list to select the label that should be confirmed. */ class MITKSEGMENTATIONUI_EXPORT QmitkAutoMLSegmentationToolGUIBase : public QmitkAutoSegmentationToolGUIBase { Q_OBJECT public: mitkClassMacro(QmitkAutoMLSegmentationToolGUIBase, QmitkAutoSegmentationToolGUIBase); protected slots : void OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels); protected: QmitkAutoMLSegmentationToolGUIBase(); ~QmitkAutoMLSegmentationToolGUIBase() = default; void InitializeUI(QBoxLayout* mainLayout) override; - void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; void EnableWidgets(bool enabled) override; void SetLabelSetPreview(const mitk::LabelSetImage* preview); private: QmitkSimpleLabelSetListWidget* m_LabelSelectionList = nullptr; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp index 19e7da115e..5ca33cd4e2 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp @@ -1,155 +1,157 @@ /*============================================================================ 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 "QmitkAutoSegmentationToolGUIBase.h" #include #include #include #include #include bool DefaultEnableConfirmSegBtnFunction(bool enabled) { return enabled; } QmitkAutoSegmentationToolGUIBase::QmitkAutoSegmentationToolGUIBase(bool mode2D) : QmitkToolGUI(), m_EnableConfirmSegBtnFnc(DefaultEnableConfirmSegBtnFunction), m_Mode2D(mode2D) { connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkAutoSegmentationToolGUIBase::~QmitkAutoSegmentationToolGUIBase() { if (m_Tool.IsNotNull()) { m_Tool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkAutoSegmentationToolGUIBase::BusyStateChanged); } } void QmitkAutoSegmentationToolGUIBase::OnNewToolAssociated(mitk::Tool *tool) { if (m_Tool.IsNotNull()) { this->DisconnectOldTool(m_Tool); } m_Tool = dynamic_cast(tool); if (nullptr == m_MainLayout) { // create the visible widgets m_MainLayout = new QVBoxLayout(this); m_ConfirmSegBtn = new QPushButton("Confirm Segmentation", this); connect(m_ConfirmSegBtn, SIGNAL(clicked()), this, SLOT(OnAcceptPreview())); m_CheckProcessAll = new QCheckBox("Process all time steps", this); m_CheckProcessAll->setChecked(false); m_CheckProcessAll->setToolTip("Process all time steps of the dynamic segmentation and not just the currently visible time step."); m_CheckProcessAll->setVisible(!m_Mode2D); //remark: keept m_CheckProcessAll deactivated in 2D because in this refactoring //it should be kept to the status quo and it was not clear how interpolation //would behave. As soon as it is sorted out we can remove that "feature switch" //or the comment. m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); m_CheckCreateNew->setChecked(false); m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); m_CheckCreateNew->setVisible(!m_Mode2D); //remark: keept m_CheckCreateNew deactivated in 2D because in this refactoring //it should be kept to the status quo and it was not clear how interpolation //would behave. As soon as it is sorted out we can remove that "feature switch" //or the comment. this->InitializeUI(m_MainLayout); m_MainLayout->addWidget(m_ConfirmSegBtn); m_MainLayout->addWidget(m_CheckProcessAll); m_MainLayout->addWidget(m_CheckCreateNew); } if (m_Tool.IsNotNull()) { this->ConnectNewTool(m_Tool); } } void QmitkAutoSegmentationToolGUIBase::OnAcceptPreview() { if (m_Tool.IsNotNull()) { if (m_CheckCreateNew->isChecked()) { m_Tool->SetOverwriteExistingSegmentation(false); } else { m_Tool->SetOverwriteExistingSegmentation(true); } m_Tool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); m_ConfirmSegBtn->setEnabled(false); m_Tool->ConfirmSegmentation(); } } void QmitkAutoSegmentationToolGUIBase::DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool) { oldTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkAutoSegmentationToolGUIBase::BusyStateChanged); } void QmitkAutoSegmentationToolGUIBase::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) { newTool->CurrentlyBusy += mitk::MessageDelegate1(this, &QmitkAutoSegmentationToolGUIBase::BusyStateChanged); newTool->SetOverwriteExistingSegmentation(true); m_CheckProcessAll->setVisible(newTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); + + this->EnableWidgets(true); } void QmitkAutoSegmentationToolGUIBase::InitializeUI(QBoxLayout* /*mainLayout*/) { //default implementation does nothing } void QmitkAutoSegmentationToolGUIBase::BusyStateChanged(bool isBusy) { if (isBusy) { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); } else { QApplication::restoreOverrideCursor(); } this->EnableWidgets(!isBusy); } void QmitkAutoSegmentationToolGUIBase::EnableWidgets(bool enabled) { if (nullptr != m_MainLayout) { if (nullptr != m_ConfirmSegBtn) { m_ConfirmSegBtn->setEnabled(m_EnableConfirmSegBtnFnc(enabled)); } if (nullptr != m_CheckProcessAll) { m_CheckProcessAll->setEnabled(enabled); } if (nullptr != m_CheckCreateNew) { m_CheckCreateNew->setEnabled(enabled); } } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.h index 6501a2e1b5..2c6eddf881 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.h +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.h @@ -1,81 +1,93 @@ /*============================================================================ 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 QmitkAutoSegmentationToolGUIBase_h_Included #define QmitkAutoSegmentationToolGUIBase_h_Included #include "QmitkToolGUI.h" #include "mitkAutoSegmentationWithPreviewTool.h" #include class QCheckBox; class QPushButton; class QBoxLayout; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI base clase for tools derived from mitk::AutoSegmentationTool. */ class MITKSEGMENTATIONUI_EXPORT QmitkAutoSegmentationToolGUIBase : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkAutoSegmentationToolGUIBase, QmitkToolGUI); itkCloneMacro(Self); itkGetConstMacro(Mode2D, bool); protected slots: void OnNewToolAssociated(mitk::Tool *); void OnAcceptPreview(); protected: QmitkAutoSegmentationToolGUIBase(bool mode2D); ~QmitkAutoSegmentationToolGUIBase() override; virtual void DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool); virtual void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool); + + /**This method is called by OnNewToolAssociated if the UI is initialized the + first time to allow derived classes to introduce own UI code. Overwrite to change. + The implementation should ensure that alle widgets needed for the tool UI are + properly allocated. If one needs to eecute time (e.g. to connect events between the tool + and the UI) each time the tool changes, override the functions ConnectNewTool() and + DisconnectOldTool().*/ virtual void InitializeUI(QBoxLayout* mainLayout); void BusyStateChanged(bool isBusy) override; using EnableConfirmSegBtnFunctionType = std::function; EnableConfirmSegBtnFunctionType m_EnableConfirmSegBtnFnc; + /**This method is used to control/set the enabled state of the tool UI + widgets. It is e.g. used if the busy state is changed (see BusyStateChanged). + Override the default implmentation, e.g. if a tool adds his own UI elements + (normally by overriding InitializeUI()) and wants to control how the widgets + are enabled/disabled.*/ virtual void EnableWidgets(bool enabled); template TTool* GetConnectedToolAs() { return dynamic_cast(m_Tool.GetPointer()); }; private: QCheckBox* m_CheckProcessAll = nullptr; QCheckBox* m_CheckCreateNew = nullptr; QPushButton* m_ConfirmSegBtn = nullptr; QBoxLayout* m_MainLayout = nullptr; /**Indicates if the tool is in 2D or 3D mode.*/ bool m_Mode2D; mitk::AutoSegmentationWithPreviewTool::Pointer m_Tool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h index f9d87fd615..55b7030ea5 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h @@ -1,64 +1,64 @@ /*============================================================================ 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 QmitkOtsuTool3DGUI_h_Included #define QmitkOtsuTool3DGUI_h_Included #include "QmitkAutoMLSegmentationToolGUIBase.h" #include "ui_QmitkOtsuToolWidgetControls.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal - \brief GUI for mitk::. + \brief GUI for mitk::OtsuTool3D. \sa mitk:: This GUI shows ... Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkOtsuTool3DGUI : public QmitkAutoMLSegmentationToolGUIBase { Q_OBJECT public: mitkClassMacro(QmitkOtsuTool3DGUI, QmitkAutoMLSegmentationToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots : void OnPreviewBtnClicked(); void OnRegionSpinboxChanged(int); private slots: void OnAdvancedSettingsButtonToggled(bool toggled); protected: QmitkOtsuTool3DGUI(); ~QmitkOtsuTool3DGUI() = default; void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; void InitializeUI(QBoxLayout* mainLayout) override; void EnableWidgets(bool enabled) override; Ui_QmitkOtsuToolWidgetControls m_Controls; bool m_FirstPreviewComputation = true; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.cpp index be695b9ae5..a54812842d 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.cpp @@ -1,51 +1,74 @@ /*============================================================================ 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 "QmitkPickingToolGUI.h" -#include "QmitkNewSegmentationDialog.h" - -#include "mitkBaseRenderer.h" -#include "mitkStepper.h" +#include #include -#include -#include #include -#include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkPickingToolGUI, "") -QmitkPickingToolGUI::QmitkPickingToolGUI() : QmitkToolGUI() +QmitkPickingToolGUI::QmitkPickingToolGUI() : QmitkAutoSegmentationToolGUIBase(false) { - m_Controls.setupUi(this); + auto enablePickingDelegate = [this](bool enabled) + { + bool result = false; + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + result = enabled && tool->HasPicks(); + } - connect(m_Controls.m_ConfirmButton, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); + return result; + }; - connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); + m_EnableConfirmSegBtnFnc = enablePickingDelegate; } QmitkPickingToolGUI::~QmitkPickingToolGUI() { } -void QmitkPickingToolGUI::OnNewToolAssociated(mitk::Tool *tool) +void QmitkPickingToolGUI::OnResetPicksClicked() { - m_PickingTool = dynamic_cast(tool); + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->ClearPicks(); + } } -void QmitkPickingToolGUI::OnConfirmSegmentation() +void QmitkPickingToolGUI::InitializeUI(QBoxLayout* mainLayout) { - if (m_PickingTool.IsNotNull()) + QLabel* label = new QLabel("Press SHIFT and click to pick region(s).\nPress DEL to remove last pick.", this); + mainLayout->addWidget(label); + + auto clearButton = new QPushButton("Reset picks",this); + connect(clearButton, &QPushButton::clicked, this, &QmitkPickingToolGUI::OnResetPicksClicked); + mainLayout->addWidget(clearButton); + m_ClearPicksBtn = clearButton; + + Superclass::InitializeUI(mainLayout); +} + +void QmitkPickingToolGUI::EnableWidgets(bool enabled) +{ + Superclass::EnableWidgets(enabled); + + if (nullptr != m_ClearPicksBtn) { - m_PickingTool->ConfirmSegmentation(); + m_ClearPicksBtn->setEnabled(enabled); } } + diff --git a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.h index bf8a3b78b5..0a1c65aecb 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUI.h @@ -1,60 +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 QmitkPickingToolGUI_h_Included #define QmitkPickingToolGUI_h_Included -#include "QmitkToolGUI.h" -#include "mitkPickingTool.h" -#include "ui_QmitkPickingToolGUIControls.h" +#include "QmitkAutoSegmentationToolGUIBase.h" #include -class QSlider; -class QLabel; -class QFrame; -class QPushButton; -#include - -#include "QmitkStepperAdapter.h" - -class QmitkPickingToolGUIControls; - /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal -\brief GUI for mitk::LiveWireTool. +\brief GUI for mitk::PickingTool. \sa mitk::PickingTool */ -class MITKSEGMENTATIONUI_EXPORT QmitkPickingToolGUI : public QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkPickingToolGUI : public QmitkAutoSegmentationToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkPickingToolGUI, QmitkToolGUI); + mitkClassMacro(QmitkPickingToolGUI, QmitkAutoSegmentationToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - protected slots : - - void OnNewToolAssociated(mitk::Tool *); - - void OnConfirmSegmentation(); +protected slots : + void OnResetPicksClicked(); protected: QmitkPickingToolGUI(); ~QmitkPickingToolGUI() override; - Ui::QmitkPickingToolGUIControls m_Controls; + void InitializeUI(QBoxLayout* mainLayout) override; + void EnableWidgets(bool enabled) override; - mitk::PickingTool::Pointer m_PickingTool; +private: + QWidget* m_ClearPicksBtn = nullptr; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUIControls.ui deleted file mode 100644 index 02979d04cf..0000000000 --- a/Modules/SegmentationUI/Qmitk/QmitkPickingToolGUIControls.ui +++ /dev/null @@ -1,93 +0,0 @@ - - - QmitkPickingToolGUIControls - - - - 0 - 0 - 421 - 263 - - - - - 0 - 0 - - - - - 0 - 0 - - - - QmitkPickingToolGUIControls - - - Confirm all previous contour. - - - - 0 - - - - - - - - 16777215 - 20 - - - - Use shift click to pick a single - - - - - - - - 16777215 - 20 - - - - region from the segmentation image. - - - - - - - Qt::Vertical - - - QSizePolicy::Fixed - - - - 20 - 15 - - - - - - - - Confirm picked region - - - - - - - - - - - diff --git a/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp index 81080382e7..d731966d1f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSliceBasedInterpolatorWidget.cpp @@ -1,710 +1,710 @@ /*============================================================================ 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 "QmitkSliceBasedInterpolatorWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "QmitkStdMultiWidget.h" #include #include #include #include #include QmitkSliceBasedInterpolatorWidget::QmitkSliceBasedInterpolatorWidget(QWidget *parent, const char * /*name*/) : QWidget(parent), m_SliceInterpolatorController(mitk::SliceBasedInterpolationController::New()), m_ToolManager(nullptr), m_Activated(false), m_DataStorage(nullptr), m_LastSNC(nullptr), m_LastSliceIndex(0) { m_Controls.setupUi(this); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(mitk::ToolManagerProvider::MULTILABEL_SEGMENTATION); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate( this, &QmitkSliceBasedInterpolatorWidget::OnToolManagerWorkingDataModified); connect(m_Controls.m_btStart, SIGNAL(toggled(bool)), this, SLOT(OnToggleWidgetActivation(bool))); connect(m_Controls.m_btApplyForCurrentSlice, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_Controls.m_btApplyForAllSlices, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnSliceInterpolationInfoChanged); m_InterpolationInfoChangedObserverTag = m_SliceInterpolatorController->AddObserver(itk::ModifiedEvent(), command); // feedback node and its visualization properties m_PreviewNode = mitk::DataNode::New(); m_PreviewNode->SetName("3D tool preview"); m_PreviewNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_PreviewNode->SetProperty("layer", mitk::IntProperty::New(100)); m_PreviewNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_PreviewNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_PreviewNode->SetProperty("outline binary shadow", mitk::BoolProperty::New(true)); m_PreviewNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PreviewNode->SetOpacity(1.0); m_PreviewNode->SetColor(0.0, 1.0, 0.0); m_Controls.m_btApplyForCurrentSlice->setEnabled(false); m_Controls.m_btApplyForAllSlices->setEnabled(false); this->setEnabled(false); } QmitkSliceBasedInterpolatorWidget::~QmitkSliceBasedInterpolatorWidget() { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate( this, &QmitkSliceBasedInterpolatorWidget::OnToolManagerWorkingDataModified); foreach (mitk::SliceNavigationController *slicer, m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToTimeObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } m_ActionToSliceDimensionMap.clear(); // remove observer m_SliceInterpolatorController->RemoveObserver(m_InterpolationInfoChangedObserverTag); } const QmitkSliceBasedInterpolatorWidget::ActionToSliceDimensionMapType QmitkSliceBasedInterpolatorWidget::CreateActionToSliceDimension() { ActionToSliceDimensionMapType actionToSliceDimension; foreach (mitk::SliceNavigationController *slicer, m_ControllerToDeleteObserverTag.keys()) { std::string name = slicer->GetRenderer()->GetName(); if (name == "stdmulti.widget0") name = "Axial (red window)"; else if (name == "stdmulti.widget1") name = "Sagittal (green window)"; else if (name == "stdmulti.widget2") name = "Coronal (blue window)"; actionToSliceDimension[new QAction(QString::fromStdString(name), nullptr)] = slicer; } return actionToSliceDimension; } void QmitkSliceBasedInterpolatorWidget::SetDataStorage(mitk::DataStorage &storage) { m_DataStorage = &storage; } void QmitkSliceBasedInterpolatorWidget::SetSliceNavigationControllers( const QList &controllers) { Q_ASSERT(!controllers.empty()); // connect to the slice navigation controller. after each change, call the interpolator foreach (mitk::SliceNavigationController *slicer, controllers) { // Has to be initialized m_LastSNC = slicer; m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag.insert(slicer, slicer->AddObserver(itk::DeleteEvent(), deleteCommand)); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnTimeChanged); m_ControllerToTimeObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand)); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSliceBasedInterpolatorWidget::OnSliceChanged); m_ControllerToSliceObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand)); } m_ActionToSliceDimensionMap = this->CreateActionToSliceDimension(); } void QmitkSliceBasedInterpolatorWidget::OnToolManagerWorkingDataModified() { mitk::DataNode *workingNode = this->m_ToolManager->GetWorkingData(0); if (!workingNode) { this->setEnabled(false); return; } mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); // TODO adapt tool manager so that this check is done there, e.g. convenience function // Q_ASSERT(workingImage); if (!workingImage) { this->setEnabled(false); return; } if (workingImage->GetDimension() > 4 || workingImage->GetDimension() < 3) { this->setEnabled(false); return; } m_WorkingImage = workingImage; this->setEnabled(true); } void QmitkSliceBasedInterpolatorWidget::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { // Check if we really have a GeometryTimeEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); Q_ASSERT(slicer); m_TimePoints[slicer] = slicer->GetSelectedTimePoint(); // TODO Macht das hier wirklich Sinn???? if (m_LastSNC == slicer) { slicer->SendSlice(); // will trigger a new interpolation } } void QmitkSliceBasedInterpolatorWidget::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { if (m_Activated && m_WorkingImage.IsNotNull()) { // Check whether we really have a GeometrySliceEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); if (slicer) { this->TranslateAndInterpolateChangedSlice(e, slicer); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); // slicer->GetRenderer()->RequestUpdate(); } } } void QmitkSliceBasedInterpolatorWidget::TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer) { if (m_Activated && m_WorkingImage.IsNotNull()) { const mitk::SliceNavigationController::GeometrySliceEvent &geometrySliceEvent = dynamic_cast(e); mitk::TimeGeometry *timeGeometry = geometrySliceEvent.GetTimeGeometry(); if (timeGeometry && m_TimePoints.contains(slicer) && timeGeometry->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = dynamic_cast(timeGeometry->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); if (slicedGeometry) { mitk::PlaneGeometry *plane = slicedGeometry->GetPlaneGeometry(geometrySliceEvent.GetPos()); if (plane) { m_LastSNC = slicer; this->Interpolate(plane, m_TimePoints[slicer], slicer); } } } } } void QmitkSliceBasedInterpolatorWidget::Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer) { int clickedSliceDimension(-1); int clickedSliceIndex(-1); if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot interpolate WorkingImage. Passed time point is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); - // calculate real slice position, i.e. slice of the image and not slice of the TimeSlicedGeometry + // calculate real slice position, i.e. slice of the image // see if timestep is needed here mitk::SegTool2D::DetermineAffectedImageSlice(m_WorkingImage, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_SliceInterpolatorController->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_PreviewNode->SetData(interpolation); const mitk::Color &color = m_WorkingImage->GetActiveLabel()->GetColor(); m_PreviewNode->SetColor(color); m_LastSNC = slicer; m_LastSliceIndex = clickedSliceIndex; } mitk::Image::Pointer QmitkSliceBasedInterpolatorWidget::GetWorkingSlice(const mitk::PlaneGeometry *planeGeometry) { const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot get slice of WorkingImage. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return nullptr; } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); // use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(m_WorkingImage); const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); try { extractor->Update(); } catch (itk::ExceptionObject &excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); return nullptr; } mitk::Image::Pointer slice = extractor->GetOutput(); // specify the undo operation with the non edited slice // MLI TODO added code starts here mitk::SlicedGeometry3D *sliceGeometry = dynamic_cast(slice->GetGeometry()); // m_undoOperation = new mitk::DiffSliceOperation(m_WorkingImage, extractor->GetVtkOutput(), slice->GetGeometry(), // timeStep, const_cast(planeGeometry)); // added code ends here m_undoOperation = new mitk::DiffSliceOperation( m_WorkingImage, extractor->GetOutput(), sliceGeometry, timeStep, const_cast(planeGeometry)); slice->DisconnectPipeline(); return slice; } void QmitkSliceBasedInterpolatorWidget::OnToggleWidgetActivation(bool enabled) { Q_ASSERT(m_ToolManager); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (!workingNode) return; m_Controls.m_btApplyForCurrentSlice->setEnabled(enabled); m_Controls.m_btApplyForAllSlices->setEnabled(enabled); if (enabled) m_Controls.m_btStart->setText("Stop"); else m_Controls.m_btStart->setText("Start"); unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { // mitk::SegTool2D* tool = dynamic_cast(m_ToolManager->GetToolById(i)); // MLI TODO // if (tool) tool->SetEnable2DInterpolation( enabled ); } if (enabled) { if (!m_DataStorage->Exists(m_PreviewNode)) { m_DataStorage->Add(m_PreviewNode); } m_SliceInterpolatorController->SetWorkingImage(m_WorkingImage); this->UpdateVisibleSuggestion(); } else { if (m_DataStorage->Exists(m_PreviewNode)) { m_DataStorage->Remove(m_PreviewNode); } mitk::UndoController::GetCurrentUndoModel()->Clear(); } m_Activated = enabled; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } template void QmitkSliceBasedInterpolatorWidget::WritePreviewOnWorkingImage(itk::Image *targetSlice, const mitk::Image *sourceSlice, int overwritevalue) { typedef itk::Image ImageType; typename ImageType::Pointer sourceSliceITK; mitk::CastToItkImage(sourceSlice, sourceSliceITK); // now the original slice and the ipSegmentation-painted slice are in the same format, and we can just copy all pixels // that are non-zero typedef itk::ImageRegionIterator OutputIteratorType; typedef itk::ImageRegionConstIterator InputIteratorType; InputIteratorType inputIterator(sourceSliceITK, sourceSliceITK->GetLargestPossibleRegion()); OutputIteratorType outputIterator(targetSlice, targetSlice->GetLargestPossibleRegion()); outputIterator.GoToBegin(); inputIterator.GoToBegin(); int activePixelValue = m_WorkingImage->GetActiveLabel()->GetValue(); if (activePixelValue == 0) // if exterior is the active label { while (!outputIterator.IsAtEnd()) { if (inputIterator.Get() != 0) { outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } else if (overwritevalue != 0) // if we are not erasing { while (!outputIterator.IsAtEnd()) { int targetValue = static_cast(outputIterator.Get()); if (inputIterator.Get() != 0) { if (!m_WorkingImage->GetLabel(targetValue)->GetLocked()) outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } else // if we are erasing { while (!outputIterator.IsAtEnd()) { const int targetValue = outputIterator.Get(); if (inputIterator.Get() != 0) { if (targetValue == activePixelValue) outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } } void QmitkSliceBasedInterpolatorWidget::OnAcceptInterpolationClicked() { if (m_WorkingImage.IsNotNull() && m_PreviewNode->GetData()) { const mitk::PlaneGeometry *planeGeometry = m_LastSNC->GetCurrentPlaneGeometry(); if (!planeGeometry) return; mitk::Image::Pointer sliceImage = this->GetWorkingSlice(planeGeometry); if (sliceImage.IsNull()) return; mitk::Image::Pointer previewSlice = dynamic_cast(m_PreviewNode->GetData()); AccessFixedDimensionByItk_2( sliceImage, WritePreviewOnWorkingImage, 2, previewSlice, m_WorkingImage->GetActiveLabel()->GetValue()); // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer overwrite = vtkSmartPointer::New(); overwrite->SetInputSlice(sliceImage->GetVtkImageData()); // set overwrite mode to true to write back to the image volume overwrite->SetOverwriteMode(true); overwrite->Modified(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(overwrite); extractor->SetInput(m_WorkingImage); const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); try { extractor->Update(); } catch (itk::ExceptionObject &excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); return; } // the image was modified within the pipeline, but not marked so m_WorkingImage->Modified(); int clickedSliceDimension(-1); int clickedSliceIndex(-1); mitk::SegTool2D::DetermineAffectedImageSlice( m_WorkingImage, planeGeometry, clickedSliceDimension, clickedSliceIndex); m_SliceInterpolatorController->SetChangedSlice(sliceImage, clickedSliceDimension, clickedSliceIndex, timeStep); // specify the undo operation with the edited slice // MLI TODO added code starts here mitk::SlicedGeometry3D *sliceGeometry = dynamic_cast(sliceImage->GetGeometry()); // m_undoOperation = new mitk::DiffSliceOperation(m_WorkingImage, extractor->GetVtkOutput(), slice->GetGeometry(), // timeStep, const_cast(planeGeometry)); // added code ends here m_doOperation = new mitk::DiffSliceOperation(m_WorkingImage, extractor->GetOutput(), sliceGeometry, timeStep, const_cast(planeGeometry)); // create an operation event for the undo stack mitk::OperationEvent *undoStackItem = new mitk::OperationEvent( mitk::DiffSliceOperationApplier::GetInstance(), m_doOperation, m_undoOperation, "Slice Interpolation"); // add it to the undo controller mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); // clear the pointers as the operation are stored in the undo controller and also deleted from there m_undoOperation = nullptr; m_doOperation = nullptr; m_PreviewNode->SetData(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSliceBasedInterpolatorWidget::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { // Since we need to shift the plane it must be clone so that the original plane isn't altered mitk::PlaneGeometry::Pointer reslicePlane = slicer->GetCurrentPlaneGeometry()->Clone(); const auto timePoint = slicer->GetSelectedTimePoint(); if (!m_WorkingImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } const auto timeStep = m_WorkingImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); int sliceDimension(-1); int sliceIndex(-1); mitk::SegTool2D::DetermineAffectedImageSlice(m_WorkingImage, reslicePlane, sliceDimension, sliceIndex); unsigned int zslices = m_WorkingImage->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->Reset(); mitk::ProgressBar::GetInstance()->AddStepsToDo(zslices); mitk::Point3D origin = reslicePlane->GetOrigin(); for (unsigned int idx = 0; idx < zslices; ++idx) { // Transforming the current origin of the reslice plane // so that it matches the one of the next slice m_WorkingImage->GetSlicedGeometry()->WorldToIndex(origin, origin); origin[sliceDimension] = idx; m_WorkingImage->GetSlicedGeometry()->IndexToWorld(origin, origin); reslicePlane->SetOrigin(origin); mitk::Image::Pointer interpolation = m_SliceInterpolatorController->Interpolate(sliceDimension, idx, reslicePlane, timeStep); if (interpolation.IsNotNull()) { m_PreviewNode->SetData(interpolation); mitk::Image::Pointer sliceImage = this->GetWorkingSlice(reslicePlane); if (sliceImage.IsNull()) return; AccessFixedDimensionByItk_2( sliceImage, WritePreviewOnWorkingImage, 2, interpolation, m_WorkingImage->GetActiveLabel()->GetValue()); // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer overwrite = vtkSmartPointer::New(); overwrite->SetInputSlice(sliceImage->GetVtkImageData()); // set overwrite mode to true to write back to the image volume overwrite->SetOverwriteMode(true); overwrite->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(overwrite); extractor->SetInput(m_WorkingImage); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(reslicePlane); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); try { extractor->Update(); } catch (itk::ExceptionObject &excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); return; } m_WorkingImage->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_2DWINDOWS); } mitk::ProgressBar::GetInstance()->Progress(); } m_SliceInterpolatorController->SetWorkingImage(m_WorkingImage); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSliceBasedInterpolatorWidget::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); std::map::const_iterator it; for (it = m_ActionToSliceDimensionMap.begin(); it != m_ActionToSliceDimensionMap.end(); it++) orientationPopup.addAction(it->first); connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSliceBasedInterpolatorWidget::OnAcceptAllPopupActivated(QAction *action) { ActionToSliceDimensionMapType::const_iterator iter = m_ActionToSliceDimensionMap.find(action); if (iter != m_ActionToSliceDimensionMap.end()) { mitk::SliceNavigationController *slicer = iter->second; this->AcceptAllInterpolations(slicer); } } void QmitkSliceBasedInterpolatorWidget::UpdateVisibleSuggestion() { if (m_Activated && m_LastSNC) { // determine which one is the current view, try to do an initial interpolation mitk::BaseRenderer *renderer = m_LastSNC->GetRenderer(); if (renderer && renderer->GetMapperID() == mitk::BaseRenderer::Standard2D) { const mitk::TimeGeometry *timeGeometry = dynamic_cast(renderer->GetWorldTimeGeometry()); if (timeGeometry) { mitk::SliceNavigationController::GeometrySliceEvent event(const_cast(timeGeometry), renderer->GetSlice()); this->TranslateAndInterpolateChangedSlice(event, m_LastSNC); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } } } void QmitkSliceBasedInterpolatorWidget::OnSliceInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display this->UpdateVisibleSuggestion(); } void QmitkSliceBasedInterpolatorWidget::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToTimeObserverTag.remove(slicer); m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSliceBasedInterpolatorWidget::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkSliceBasedInterpolatorWidget::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkSliceBasedInterpolatorWidget::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index 6820efbc3b..55cb106313 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1405 +1,1432 @@ /*============================================================================ 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 "QmitkSlicesInterpolator.h" #include "QmitkSelectableGLWidget.h" #include "QmitkStdMultiWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include "mitkToolManager.h" #include "mitkUndoController.h" #include #include #include #include #include #include #include +#include #include #include #include #include #include #include #include #include #include #include +#include +#include +#include namespace { template itk::SmartPointer GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const std::map QmitkSlicesInterpolator::createActionToSliceDimension() { std::map actionToSliceDimension; foreach (mitk::SliceNavigationController *slicer, m_ControllerToDeleteObserverTag.keys()) { actionToSliceDimension[new QAction(QString::fromStdString(slicer->GetViewDirectionAsString()), nullptr)] = slicer; } return actionToSliceDimension; } QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), // ACTION_TO_SLICEDIMENSION( createActionToSliceDimension() ), m_Interpolator(mitk::SegmentationInterpolationController::New()), m_SurfaceInterpolator(mitk::SurfaceInterpolationController::GetInstance()), m_ToolManager(nullptr), m_Initialized(false), m_LastSNC(nullptr), m_LastSliceIndex(0), m_2DInterpolationEnabled(false), m_3DInterpolationEnabled(false), m_FirstRun(true) { m_GroupBoxEnableExclusiveInterpolationMode = new QGroupBox("Interpolation", this); QVBoxLayout *vboxLayout = new QVBoxLayout(m_GroupBoxEnableExclusiveInterpolationMode); m_EdgeDetector = mitk::FeatureBasedEdgeDetectionFilter::New(); m_PointScorer = mitk::PointCloudScoringFilter::New(); m_CmbInterpolation = new QComboBox(m_GroupBoxEnableExclusiveInterpolationMode); m_CmbInterpolation->addItem("Disabled"); m_CmbInterpolation->addItem("2-Dimensional"); m_CmbInterpolation->addItem("3-Dimensional"); vboxLayout->addWidget(m_CmbInterpolation); m_BtnApply2D = new QPushButton("Confirm for single slice", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply2D); m_BtnApplyForAllSlices2D = new QPushButton("Confirm for all slices", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApplyForAllSlices2D); m_BtnApply3D = new QPushButton("Confirm", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply3D); // T28261 // m_BtnSuggestPlane = new QPushButton("Suggest a plane", m_GroupBoxEnableExclusiveInterpolationMode); // vboxLayout->addWidget(m_BtnSuggestPlane); m_BtnReinit3DInterpolation = new QPushButton("Reinit Interpolation", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnReinit3DInterpolation); m_ChkShowPositionNodes = new QCheckBox("Show Position Nodes", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_ChkShowPositionNodes); this->HideAllInterpolationControls(); connect(m_CmbInterpolation, SIGNAL(currentIndexChanged(int)), this, SLOT(OnInterpolationMethodChanged(int))); connect(m_BtnApply2D, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_BtnApplyForAllSlices2D, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); connect(m_BtnApply3D, SIGNAL(clicked()), this, SLOT(OnAccept3DInterpolationClicked())); // T28261 // connect(m_BtnSuggestPlane, SIGNAL(clicked()), this, SLOT(OnSuggestPlaneClicked())); connect(m_BtnReinit3DInterpolation, SIGNAL(clicked()), this, SLOT(OnReinit3DInterpolation())); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SIGNAL(SignalShowMarkerNodes(bool))); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_GroupBoxEnableExclusiveInterpolationMode); this->setLayout(layout); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); // feedback node and its visualization properties m_FeedbackNode = mitk::DataNode::New(); mitk::CoreObjectFactory::GetInstance()->SetDefaultProperties(m_FeedbackNode); m_FeedbackNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_FeedbackNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_FeedbackNode->SetProperty("layer", mitk::IntProperty::New(20)); m_FeedbackNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_FeedbackNode->SetProperty("name", mitk::StringProperty::New("Interpolation feedback")); m_FeedbackNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_FeedbackNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->SetProperty("name", mitk::StringProperty::New("Surface Interpolation feedback")); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("line width", mitk::FloatProperty::New(4.0f)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); m_3DContourNode = mitk::DataNode::New(); m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(0.0, 0.0, 0.0)); m_3DContourNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("name", mitk::StringProperty::New("Drawn Contours")); m_3DContourNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); m_3DContourNode->SetProperty("material.wireframeLineWidth", mitk::FloatProperty::New(2.0f)); m_3DContourNode->SetProperty("3DContourContainer", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))); m_3DContourNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList &controllers) { Q_ASSERT(!controllers.empty()); if (m_Initialized) { // remove old observers Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); // connect to the slice navigation controller. after each change, call the interpolator foreach (mitk::SliceNavigationController *slicer, controllers) { // Has to be initialized m_LastSNC = slicer; m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag.insert(slicer, slicer->AddObserver(itk::DeleteEvent(), deleteCommand)); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand)); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag.insert( slicer, slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand)); } ACTION_TO_SLICEDIMENSION = createActionToSliceDimension(); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } foreach (mitk::SliceNavigationController *slicer, m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToTimeObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } ACTION_TO_SLICEDIMENSION.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); if (m_DataStorage->Exists(m_3DContourNode)) m_DataStorage->Remove(m_3DContourNode); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) m_DataStorage->Remove(m_InterpolatedSurfaceNode); } // remove observer m_Interpolator->RemoveObserver(InterpolationInfoChangedObserverTag); m_SurfaceInterpolator->RemoveObserver(SurfaceInterpolationInfoChangedObserverTag); delete m_Timer; } /** External enableization... */ void QmitkSlicesInterpolator::setEnabled(bool enable) { QWidget::setEnabled(enable); // Set the gui elements of the different interpolation modi enabled if (enable) { if (m_2DInterpolationEnabled) { this->Show2DInterpolationControls(true); m_Interpolator->Activate2DInterpolation(true); } else if (m_3DInterpolationEnabled) { this->Show3DInterpolationControls(true); this->Show3DInterpolationResult(true); } } // Set all gui elements of the interpolation disabled else { this->HideAllInterpolationControls(); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::On2DInterpolationEnabled(bool status) { OnInterpolationActivated(status); m_Interpolator->Activate2DInterpolation(status); } void QmitkSlicesInterpolator::On3DInterpolationEnabled(bool status) { On3DInterpolationActivated(status); } void QmitkSlicesInterpolator::OnInterpolationDisabled(bool status) { if (status) { OnInterpolationActivated(!status); On3DInterpolationActivated(!status); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::HideAllInterpolationControls() { this->Show2DInterpolationControls(false); this->Show3DInterpolationControls(false); } void QmitkSlicesInterpolator::Show2DInterpolationControls(bool show) { m_BtnApply2D->setVisible(show); m_BtnApplyForAllSlices2D->setVisible(show); } void QmitkSlicesInterpolator::Show3DInterpolationControls(bool show) { m_BtnApply3D->setVisible(show); // T28261 // m_BtnSuggestPlane->setVisible(show); m_ChkShowPositionNodes->setVisible(show); m_BtnReinit3DInterpolation->setVisible(show); } void QmitkSlicesInterpolator::OnInterpolationMethodChanged(int index) { switch (index) { case 0: // Disabled m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation"); this->HideAllInterpolationControls(); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(false); break; case 1: // 2D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show2DInterpolationControls(true); this->OnInterpolationActivated(true); this->On3DInterpolationActivated(false); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { if (m_ToolManager->GetWorkingData(0) != nullptr) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_3DContourNode); m_3DContourNode->SetData(nullptr); this->GetDataStorage()->Remove(m_InterpolatedSurfaceNode); m_InterpolatedSurfaceNode->SetData(nullptr); m_BtnReinit3DInterpolation->setEnabled(false); return; } // Updating the current selected segmentation for the 3D interpolation SetCurrentContourListID(); if (m_2DInterpolationEnabled) { OnInterpolationActivated(true); // re-initialize if needed } this->CheckSupportedImageDimension(); } void QmitkSlicesInterpolator::OnToolManagerReferenceDataModified() { } void QmitkSlicesInterpolator::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { // Check if we really have a GeometryTimeEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); Q_ASSERT(slicer); const auto timePoint = slicer->GetSelectedTimePoint(); m_TimePoints[slicer] = timePoint; m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); if (m_LastSNC == slicer) { slicer->SendSlice(); // will trigger a new interpolation } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { // Check whether we really have a GeometrySliceEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); if (TranslateAndInterpolateChangedSlice(e, slicer)) { slicer->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer) { if (!m_2DInterpolationEnabled) return false; try { const mitk::SliceNavigationController::GeometrySliceEvent &event = dynamic_cast(e); mitk::TimeGeometry *tsg = event.GetTimeGeometry(); if (tsg && m_TimePoints.contains(slicer) && tsg->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = dynamic_cast(tsg->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); if (slicedGeometry) { m_LastSNC = slicer; mitk::PlaneGeometry *plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(event.GetPos())); if (plane) Interpolate(plane, m_TimePoints[slicer], slicer); return true; } } } catch (const std::bad_cast &) { return false; // so what } return false; } void QmitkSlicesInterpolator::Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer) { if (m_ToolManager) { mitk::DataNode *node = m_ToolManager->GetWorkingData(0); if (node) { m_Segmentation = dynamic_cast(node->GetData()); if (m_Segmentation) { if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot interpolate segmentation. Passed time point is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); int clickedSliceDimension(-1); int clickedSliceIndex(-1); - // calculate real slice position, i.e. slice of the image and not slice of the TimeSlicedGeometry + // calculate real slice position, i.e. slice of the image mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_FeedbackNode->SetData(interpolation); m_LastSNC = slicer; m_LastSliceIndex = clickedSliceIndex; } } } } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (interpolatedSurface.IsNotNull() && workingNode && workingNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2")))) { m_BtnApply3D->setEnabled(true); // T28261 // m_BtnSuggestPlane->setEnabled(true); m_InterpolatedSurfaceNode->SetData(interpolatedSurface); m_3DContourNode->SetData(m_SurfaceInterpolator->GetContoursAsSurface()); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } if (!m_DataStorage->Exists(m_3DContourNode)) { m_DataStorage->Add(m_3DContourNode, workingNode); } } else if (interpolatedSurface.IsNull()) { m_BtnApply3D->setEnabled(false); // T28261 // m_BtnSuggestPlane->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } m_BtnReinit3DInterpolation->setEnabled(true); foreach (mitk::SliceNavigationController *slicer, m_ControllerToTimeObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { if (m_Segmentation && m_FeedbackNode->GetData()) { // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set slice as input mitk::Image::Pointer slice = dynamic_cast(m_FeedbackNode->GetData()); reslice->SetInputSlice(slice->GetSliceData()->GetVtkImageAccessor(slice)->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(m_Segmentation); const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(m_LastSNC->GetCurrentPlaneGeometry()); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so m_Segmentation->Modified(); m_Segmentation->GetVtkImageData()->Modified(); m_FeedbackNode->SetData(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSlicesInterpolator::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { /* * What exactly is done here: * 1. We create an empty diff image for the current segmentation * 2. All interpolated slices are written into the diff image * 3. Then the diffimage is applied to the original segmentation */ if (m_Segmentation) { - mitk::Image::Pointer image3D = m_Segmentation; - unsigned int timeStep(0); + mitk::Image::Pointer segmentation3D = m_Segmentation; + unsigned int timeStep = 0; const auto timePoint = slicer->GetSelectedTimePoint(); - if (m_Segmentation->GetDimension() == 4) + + if (4 == m_Segmentation->GetDimension()) { - if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) + const auto* geometry = m_Segmentation->GetTimeGeometry(); + + if (!geometry->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } - timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); + timeStep = geometry->TimePointToTimeStep(timePoint); + + auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); - image3D = timeSelector->GetOutput(); + + segmentation3D = timeSelector->GetOutput(); } - // create a empty diff image for the undo operation - mitk::Image::Pointer diffImage = mitk::Image::New(); - diffImage->Initialize(image3D); - // Create scope for ImageWriteAccessor so that the accessor is destroyed - // after the image is initialized. Otherwise later image access will lead to an error + // Create an empty diff image for the undo operation + auto diffImage = mitk::Image::New(); + diffImage->Initialize(segmentation3D); + + // Create scope for ImageWriteAccessor so that the accessor is destroyed right after use { - mitk::ImageWriteAccessor imAccess(diffImage); + mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero - mitk::PixelType pixelType(mitk::MakeScalarPixelType()); + auto pixelType = mitk::MakeScalarPixelType(); // For legacy purpose support former pixel type of segmentations (before multilabel) - if (m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType() == - itk::ImageIOBase::UCHAR) - { + if (itk::ImageIOBase::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType(); - } - memset(imAccess.GetData(), - 0, - (pixelType.GetBpe() >> 3) * diffImage->GetDimension(0) * diffImage->GetDimension(1) * - diffImage->GetDimension(2)); + memset(accessor.GetData(), 0, pixelType.GetSize() * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered - mitk::PlaneGeometry::Pointer reslicePlane = slicer->GetCurrentPlaneGeometry()->Clone(); + auto slicedGeometry = m_Segmentation->GetSlicedGeometry(); + auto planeGeometry = slicer->GetCurrentPlaneGeometry()->Clone(); + int sliceDimension = -1; + int sliceIndex = -1; + + mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, planeGeometry, sliceDimension, sliceIndex); + + const auto numSlices = m_Segmentation->GetDimension(sliceDimension); + mitk::ProgressBar::GetInstance()->AddStepsToDo(numSlices); + + std::atomic_uint totalChangedSlices; - int sliceDimension(-1); - int sliceIndex(-1); - mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, reslicePlane, sliceDimension, sliceIndex); + // Reuse interpolation algorithm instance for each slice to cache boundary calculations + auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); - unsigned int zslices = m_Segmentation->GetDimension(sliceDimension); - mitk::ProgressBar::GetInstance()->AddStepsToDo(zslices); + // Distribute slice interpolations to multiple threads + const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); + std::vector> sliceIndices(numThreads); - mitk::Point3D origin = reslicePlane->GetOrigin(); - unsigned int totalChangedSlices(0); + for (std::remove_const_t sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) + sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); - for (unsigned int sliceIndex = 0; sliceIndex < zslices; ++sliceIndex) + std::vector threads; + threads.reserve(numThreads); + + // This lambda will be executed by the threads + auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { - // Transforming the current origin of the reslice plane - // so that it matches the one of the next slice - m_Segmentation->GetSlicedGeometry()->WorldToIndex(origin, origin); - origin[sliceDimension] = sliceIndex; - m_Segmentation->GetSlicedGeometry()->IndexToWorld(origin, origin); - reslicePlane->SetOrigin(origin); - // Set the slice as 'input' - mitk::Image::Pointer interpolation = - m_Interpolator->Interpolate(sliceDimension, sliceIndex, reslicePlane, timeStep); - - if (interpolation.IsNotNull()) // we don't check if interpolation is necessary/sensible - but m_Interpolator does + auto clonedPlaneGeometry = planeGeometry->Clone(); + auto origin = clonedPlaneGeometry->GetOrigin(); + + for (auto sliceIndex : sliceIndices[threadIndex]) { - // Setting up the reslicing pipeline which allows us to write the interpolation results back into - // the image volume - vtkSmartPointer reslice = vtkSmartPointer::New(); - - // set overwrite mode to true to write back to the image volume - reslice->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); - reslice->SetOverwriteMode(true); - reslice->Modified(); - - mitk::ExtractSliceFilter::Pointer diffslicewriter = mitk::ExtractSliceFilter::New(reslice); - diffslicewriter->SetInput(diffImage); - diffslicewriter->SetTimeStep(0); - diffslicewriter->SetWorldGeometry(reslicePlane); - diffslicewriter->SetVtkOutputRequest(true); - diffslicewriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); - - diffslicewriter->Modified(); - diffslicewriter->Update(); - ++totalChangedSlices; + slicedGeometry->WorldToIndex(origin, origin); + origin[sliceDimension] = sliceIndex; + slicedGeometry->IndexToWorld(origin, origin); + clonedPlaneGeometry->SetOrigin(origin); + + auto interpolation = interpolator->Interpolate(sliceDimension, sliceIndex, clonedPlaneGeometry, timeStep, algorithm); + + if (interpolation.IsNotNull()) + { + // Setting up the reslicing pipeline which allows us to write the interpolation results back into the image volume + auto reslicer = vtkSmartPointer::New(); + + // Set overwrite mode to true to write back to the image volume + reslicer->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); + reslicer->SetOverwriteMode(true); + reslicer->Modified(); + + auto diffSliceWriter = mitk::ExtractSliceFilter::New(reslicer); + + diffSliceWriter->SetInput(diffImage); + diffSliceWriter->SetTimeStep(0); + diffSliceWriter->SetWorldGeometry(clonedPlaneGeometry); + diffSliceWriter->SetVtkOutputRequest(true); + diffSliceWriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); + diffSliceWriter->Modified(); + diffSliceWriter->Update(); + + ++totalChangedSlices; + } + + mitk::ProgressBar::GetInstance()->Progress(); } - mitk::ProgressBar::GetInstance()->Progress(); - } - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + }; + + m_Interpolator->EnableSliceImageCache(); + + for (std::remove_const_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) + threads.emplace_back(interpolate, threadIndex); // Run the interpolation + + for (auto& thread : threads) + thread.join(); + + m_Interpolator->DisableSliceImageCache(); if (totalChangedSlices > 0) { - // store undo stack items - if (true) - { - // create do/undo operations - mitk::ApplyDiffImageOperation *doOp = - new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); - mitk::ApplyDiffImageOperation *undoOp = - new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); - undoOp->SetFactor(-1.0); - std::stringstream comment; - comment << "Confirm all interpolations (" << totalChangedSlices << ")"; - mitk::OperationEvent *undoStackItem = - new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment.str()); - mitk::OperationEvent::IncCurrGroupEventId(); - mitk::OperationEvent::IncCurrObjectEventId(); - mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); - - // acutally apply the changes here to the original image - mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); - } + // Create do/undo operations + auto* doOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); + + auto* undoOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); + undoOp->SetFactor(-1.0); + + auto comment = "Confirm all interpolations (" + std::to_string(totalChangedSlices) + ")"; + + auto* undoStackItem = new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment); + + mitk::OperationEvent::IncCurrGroupEventId(); + mitk::OperationEvent::IncCurrObjectEventId(); + mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); + + // Apply the changes to the original image + mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } m_FeedbackNode->SetData(nullptr); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } + + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::FinishInterpolation(mitk::SliceNavigationController *slicer) { // this redirect is for calling from outside if (slicer == nullptr) OnAcceptAllInterpolationsClicked(); else AcceptAllInterpolations(slicer); } void QmitkSlicesInterpolator::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); std::map::const_iterator it; for (it = ACTION_TO_SLICEDIMENSION.begin(); it != ACTION_TO_SLICEDIMENSION.end(); it++) orientationPopup.addAction(it->first); connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { auto referenceImage = GetData(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); auto segmentation = GetData(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(timePoint) || !segmentationGeometry->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Current time point is not within the time bounds of the patient image and segmentation."; return; } auto interpolatedSurface = GetData(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::ImageIOBase::USHORT == segmentation->GetPixelType().GetComponentType()); surfaceToImageFilter->SetInput(interpolatedSurface); surfaceToImageFilter->Update(); mitk::Image::Pointer interpolatedSegmentation = surfaceToImageFilter->GetOutput(); auto timeStep = interpolatedSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::ImageReadAccessor readAccessor(interpolatedSegmentation, interpolatedSegmentation->GetVolumeData(timeStep)); const auto* dataPointer = readAccessor.GetData(); if (nullptr == dataPointer) return; timeStep = segmentationGeometry->TimePointToTimeStep(timePoint); segmentation->SetVolume(dataPointer, timeStep, 0); m_CmbInterpolation->setCurrentIndex(0); this->Show3DInterpolationResult(false); std::string name = segmentationDataNode->GetName() + "_3D-interpolation"; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); std::array rgb; segmentationDataNode->GetColor(rgb.data()); interpolatedSurfaceDataNode->SetColor(rgb.data()); m_DataStorage->Add(interpolatedSurfaceDataNode, segmentationDataNode); } void ::QmitkSlicesInterpolator::OnSuggestPlaneClicked() { if (m_PlaneWatcher.isRunning()) m_PlaneWatcher.waitForFinished(); m_PlaneFuture = QtConcurrent::run(this, &QmitkSlicesInterpolator::RunPlaneSuggestion); m_PlaneWatcher.setFuture(m_PlaneFuture); } void ::QmitkSlicesInterpolator::RunPlaneSuggestion() { if (m_FirstRun) mitk::ProgressBar::GetInstance()->AddStepsToDo(7); else mitk::ProgressBar::GetInstance()->AddStepsToDo(3); m_EdgeDetector->SetSegmentationMask(m_Segmentation); m_EdgeDetector->SetInput(dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData())); m_EdgeDetector->Update(); mitk::UnstructuredGrid::Pointer uGrid = mitk::UnstructuredGrid::New(); uGrid->SetVtkUnstructuredGrid(m_EdgeDetector->GetOutput()->GetVtkUnstructuredGrid()); mitk::ProgressBar::GetInstance()->Progress(); mitk::Surface::Pointer surface = dynamic_cast(m_InterpolatedSurfaceNode->GetData()); vtkSmartPointer vtkpoly = surface->GetVtkPolyData(); vtkSmartPointer vtkpoints = vtkpoly->GetPoints(); vtkSmartPointer vGrid = vtkSmartPointer::New(); vtkSmartPointer verts = vtkSmartPointer::New(); verts->GetPointIds()->SetNumberOfIds(vtkpoints->GetNumberOfPoints()); for (int i = 0; i < vtkpoints->GetNumberOfPoints(); i++) { verts->GetPointIds()->SetId(i, i); } vGrid->Allocate(1); vGrid->InsertNextCell(verts->GetCellType(), verts->GetPointIds()); vGrid->SetPoints(vtkpoints); mitk::UnstructuredGrid::Pointer interpolationGrid = mitk::UnstructuredGrid::New(); interpolationGrid->SetVtkUnstructuredGrid(vGrid); m_PointScorer->SetInput(0, uGrid); m_PointScorer->SetInput(1, interpolationGrid); m_PointScorer->Update(); mitk::UnstructuredGrid::Pointer scoredGrid = mitk::UnstructuredGrid::New(); scoredGrid = m_PointScorer->GetOutput(); mitk::ProgressBar::GetInstance()->Progress(); double spacing = mitk::SurfaceInterpolationController::GetInstance()->GetDistanceImageSpacing(); mitk::UnstructuredGridClusteringFilter::Pointer clusterFilter = mitk::UnstructuredGridClusteringFilter::New(); clusterFilter->SetInput(scoredGrid); clusterFilter->SetMeshing(false); clusterFilter->SetMinPts(4); clusterFilter->Seteps(spacing); clusterFilter->Update(); mitk::ProgressBar::GetInstance()->Progress(); // Create plane suggestion mitk::BaseRenderer::Pointer br = mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); mitk::PlaneProposer planeProposer; std::vector grids = clusterFilter->GetAllClusters(); planeProposer.SetUnstructuredGrids(grids); mitk::SliceNavigationController::Pointer snc = br->GetSliceNavigationController(); planeProposer.SetSliceNavigationController(snc); planeProposer.SetUseDistances(true); try { planeProposer.CreatePlaneInfo(); } catch (const mitk::Exception &e) { MITK_ERROR << e.what(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); m_FirstRun = false; } void QmitkSlicesInterpolator::OnReinit3DInterpolation() { mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("3DContourContainer", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(m_ToolManager->GetWorkingData(0), pred); if (contourNodes->Size() != 0) { m_BtnApply3D->setEnabled(true); m_3DContourNode = contourNodes->at(0); mitk::Surface::Pointer contours = dynamic_cast(m_3DContourNode->GetData()); if (contours) mitk::SurfaceInterpolationController::GetInstance()->ReinitializeInterpolation(contours); m_BtnReinit3DInterpolation->setEnabled(false); } else { m_BtnApply3D->setEnabled(false); QMessageBox errorInfo; errorInfo.setWindowTitle("Reinitialize surface interpolation"); errorInfo.setIcon(QMessageBox::Information); errorInfo.setText("No contours available for the selected segmentation!"); errorInfo.exec(); } } void QmitkSlicesInterpolator::OnAcceptAllPopupActivated(QAction *action) { try { std::map::const_iterator iter = ACTION_TO_SLICEDIMENSION.find(action); if (iter != ACTION_TO_SLICEDIMENSION.end()) { mitk::SliceNavigationController *slicer = iter->second; AcceptAllInterpolations(slicer); } } catch (...) { /* Showing message box with possible memory error */ QMessageBox errorInfo; errorInfo.setWindowTitle("Interpolation Process"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during interpolation. Possible cause: Not enough memory!"); errorInfo.exec(); // additional error message on std::cerr std::cerr << "Ill construction in " __FILE__ " l. " << __LINE__ << std::endl; } } void QmitkSlicesInterpolator::OnInterpolationActivated(bool on) { m_2DInterpolationEnabled = on; try { if (m_DataStorage.IsNotNull()) { if (on && !m_DataStorage->Exists(m_FeedbackNode)) { m_DataStorage->Add(m_FeedbackNode); } } } catch (...) { // don't care (double add/remove) } if (m_ToolManager) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); QWidget::setEnabled(workingNode != nullptr); m_BtnApply2D->setEnabled(on); m_FeedbackNode->SetVisibility(on); if (!on) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } if (workingNode) { mitk::Image *segmentation = dynamic_cast(workingNode->GetData()); if (segmentation) { m_Interpolator->SetSegmentationVolume(segmentation); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { m_SurfaceInterpolator->Interpolate(); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { m_Timer->stop(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); mitk::RenderingManager::GetInstance()->RequestUpdate( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); if (currentColor[2] == SURFACE_COLOR_RGB[2]) { m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(1.0f, 1.0f, 1.0f)); } else { m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); } m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdate( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } void QmitkSlicesInterpolator::On3DInterpolationActivated(bool on) { m_3DInterpolationEnabled = on; this->CheckSupportedImageDimension(); try { if (m_DataStorage.IsNotNull() && m_ToolManager && m_3DInterpolationEnabled) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { if ((workingNode->IsVisible(mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))))) { int ret = QMessageBox::Yes; if (m_SurfaceInterpolator->EstimatePortionOfNeededMemory() > 0.5) { QMessageBox msgBox; msgBox.setText("Due to short handed system memory the 3D interpolation may be very slow!"); msgBox.setInformativeText("Are you sure you want to activate the 3D interpolation?"); msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); ret = msgBox.exec(); } if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (ret == QMessageBox::Yes) { m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } else { m_CmbInterpolation->setCurrentIndex(0); } } } else { QWidget::setEnabled(false); m_ChkShowPositionNodes->setEnabled(m_3DInterpolationEnabled); } } if (!m_3DInterpolationEnabled) { this->Show3DInterpolationResult(false); m_BtnApply3D->setEnabled(m_3DInterpolationEnabled); // T28261 // m_BtnSuggestPlane->setEnabled(m_3DInterpolationEnabled); } } catch (...) { MITK_ERROR << "Error with 3D surface interpolation!"; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::EnableInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated OnInterpolationActivated(on); } void QmitkSlicesInterpolator::Enable3DInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated On3DInterpolationActivated(on); } void QmitkSlicesInterpolator::UpdateVisibleSuggestion() { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { if (m_3DInterpolationEnabled) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { QWidget::setEnabled(true); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); // In case the time is not valid use 0 to access the time geometry of the working node unsigned int time_position = 0; if (!workingNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } time_position = workingNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::Vector3D spacing = workingNode->GetData()->GetGeometry(time_position)->GetSpacing(); double minSpacing(100); double maxSpacing(0); for (int i = 0; i < 3; i++) { if (spacing[i] < minSpacing) { minSpacing = spacing[i]; } if (spacing[i] > maxSpacing) { maxSpacing = spacing[i]; } } m_SurfaceInterpolator->SetMaxSpacing(maxSpacing); m_SurfaceInterpolator->SetMinSpacing(minSpacing); m_SurfaceInterpolator->SetDistanceImageVolume(50000); mitk::Image *segmentationImage = dynamic_cast(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); if (m_3DInterpolationEnabled) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); m_Watcher.setFuture(m_Future); } } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); if (m_3DContourNode.IsNotNull()) m_3DContourNode->SetVisibility( status, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); /*if (m_3DInterpolationEnabled && m_Segmentation && m_Segmentation->GetDimension() != 3) { QMessageBox info; info.setWindowTitle("3D Interpolation Process"); info.setIcon(QMessageBox::Information); info.setText("3D Interpolation is only supported for 3D images at the moment!"); info.exec(); m_CmbInterpolation->setCurrentIndex(0); }*/ } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToTimeObserverTag.remove(slicer); m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSlicesInterpolator::WaitForFutures() { if (m_Watcher.isRunning()) { m_Watcher.waitForFinished(); } if (m_PlaneWatcher.isRunning()) { m_PlaneWatcher.waitForFinished(); } } void QmitkSlicesInterpolator::NodeRemoved(const mitk::DataNode* node) { if ((m_ToolManager && m_ToolManager->GetWorkingData(0) == node) || node == m_3DContourNode || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } } diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index fb35d7707a..e71eb77792 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,91 +1,90 @@ set( CPP_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp Qmitk/QmitkAutoSegmentationToolGUIBase.cpp Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp Qmitk/QmitkFastMarchingToolGUIBase.cpp Qmitk/QmitkFastMarchingTool3DGUI.cpp Qmitk/QmitkFastMarchingToolGUI.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkNewSegmentationDialog.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkPixelManipulationToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitkWatershedToolGUI.cpp #Added from ML Qmitk/QmitkLabelSetWidget.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkSliceBasedInterpolatorWidget.cpp Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp Qmitk/QmitkSearchLabelDialog.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp ) set(MOC_H_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h Qmitk/QmitkAutoSegmentationToolGUIBase.h Qmitk/QmitkAutoMLSegmentationToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h Qmitk/QmitkFastMarchingToolGUIBase.h Qmitk/QmitkFastMarchingTool3DGUI.h Qmitk/QmitkFastMarchingToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkNewSegmentationDialog.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkPixelManipulationToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitkWatershedToolGUI.h #Added from ML Qmitk/QmitkLabelSetWidget.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkSliceBasedInterpolatorWidget.h Qmitk/QmitkSurfaceBasedInterpolatorWidget.h Qmitk/QmitkSearchLabelDialog.h Qmitk/QmitkSimpleLabelSetListWidget.h ) set(UI_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUIControls.ui Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkOtsuToolWidgetControls.ui -Qmitk/QmitkPickingToolGUIControls.ui Qmitk/QmitkLiveWireTool2DGUIControls.ui Qmitk/QmitkWatershedToolGUIControls.ui #Added from ML Qmitk/QmitkLabelSetWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitkSliceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSurfaceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSearchLabelDialogGUI.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Modules/SurfaceInterpolation/Testing/files.cmake b/Modules/SurfaceInterpolation/Testing/files.cmake index be4eb82fbf..f126978731 100644 --- a/Modules/SurfaceInterpolation/Testing/files.cmake +++ b/Modules/SurfaceInterpolation/Testing/files.cmake @@ -1,8 +1,8 @@ set(MODULE_TESTS mitkComputeContourSetNormalsFilterTest.cpp mitkCreateDistanceImageFromSurfaceFilterTest.cpp mitkImageToPointCloudFilterTest.cpp - mitkPointCloudScoringFilterTest + mitkPointCloudScoringFilterTest.cpp mitkReduceContourSetFilterTest.cpp mitkSurfaceInterpolationControllerTest.cpp ) diff --git a/Plugins/org.mitk.gui.qt.dicombrowser/src/internal/DicomEventHandler.cpp b/Plugins/org.mitk.gui.qt.dicombrowser/src/internal/DicomEventHandler.cpp index ac03ea8e63..a33ad356bd 100644 --- a/Plugins/org.mitk.gui.qt.dicombrowser/src/internal/DicomEventHandler.cpp +++ b/Plugins/org.mitk.gui.qt.dicombrowser/src/internal/DicomEventHandler.cpp @@ -1,266 +1,258 @@ /*============================================================================ 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 "mitkPluginActivator.h" #include "DicomEventHandler.h" #include #include #include #include #include #include #include #include #include #include "mitkImage.h" #include #include #include #include #include #include #include #include #include #include "mitkBaseDICOMReaderService.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { mitk::IFileReader* GetReader(mitk::FileReaderRegistry& readerRegistry, const mitk::CustomMimeType& mimeType) { try { return readerRegistry.GetReaders(mitk::MimeType(mimeType, -1, -1)).at(0); } catch (const std::out_of_range&) { mitkThrow() << "Cannot find " << mimeType.GetCategory() << " " << mimeType.GetComment() << " file reader."; } } } DicomEventHandler::DicomEventHandler() { } DicomEventHandler::~DicomEventHandler() { } void DicomEventHandler::OnSignalAddSeriesToDataManager(const ctkEvent& ctkEvent) { QStringList listOfFilesForSeries; listOfFilesForSeries = ctkEvent.getProperty("FilesForSeries").toStringList(); if (!listOfFilesForSeries.isEmpty()) { //for rt data, if the modality tag isn't defined or is "CT" the image is handled like before if(ctkEvent.containsProperty("Modality") && (ctkEvent.getProperty("Modality").toString().compare("RTDOSE",Qt::CaseInsensitive) == 0 || ctkEvent.getProperty("Modality").toString().compare("RTSTRUCT",Qt::CaseInsensitive) == 0 || ctkEvent.getProperty("Modality").toString().compare("RTPLAN", Qt::CaseInsensitive) == 0)) { QString modality = ctkEvent.getProperty("Modality").toString(); mitk::FileReaderRegistry readerRegistry; if(modality.compare("RTDOSE",Qt::CaseInsensitive) == 0) { auto doseReader = GetReader(readerRegistry, mitk::DICOMRTMimeTypes::DICOMRT_DOSE_MIMETYPE()); doseReader->SetInput(ImporterUtil::getUTF8String(listOfFilesForSeries.front())); std::vector > readerOutput = doseReader->Read(); if (!readerOutput.empty()){ mitk::Image::Pointer doseImage = dynamic_cast(readerOutput.at(0).GetPointer()); mitk::DataNode::Pointer doseImageNode = mitk::DataNode::New(); doseImageNode->SetData(doseImage); doseImageNode->SetName("RTDose"); if (doseImage != nullptr) { std::string sopUID; if (mitk::GetBackwardsCompatibleDICOMProperty(0x0008, 0x0016, "dicomseriesreader.SOPClassUID", doseImage->GetPropertyList(), sopUID)) { doseImageNode->SetName(sopUID); }; berry::IPreferencesService* prefService = berry::Platform::GetPreferencesService(); berry::IPreferences::Pointer prefNode = prefService->GetSystemPreferences()->Node(mitk::RTUIConstants::ROOT_DOSE_VIS_PREFERENCE_NODE_ID.c_str()); if (prefNode.IsNull()) { mitkThrow() << "Error in preference interface. Cannot find preset node under given name. Name: " << prefNode->ToString().toStdString(); } //set some specific colorwash and isoline properties bool showColorWashGlobal = prefNode->GetBool(mitk::RTUIConstants::GLOBAL_VISIBILITY_COLORWASH_ID.c_str(), true); //Set reference dose property double referenceDose = prefNode->GetDouble(mitk::RTUIConstants::REFERENCE_DOSE_ID.c_str(), mitk::RTUIConstants::DEFAULT_REFERENCE_DOSE_VALUE); mitk::ConfigureNodeAsDoseNode(doseImageNode, mitk::GenerateIsoLevels_Virtuos(), referenceDose, showColorWashGlobal); ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); dataStorage->Add(doseImageNode); mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(dataStorage); } }//END DOSE } else if(modality.compare("RTSTRUCT",Qt::CaseInsensitive) == 0) { auto structReader = GetReader(readerRegistry, mitk::DICOMRTMimeTypes::DICOMRT_STRUCT_MIMETYPE()); structReader->SetInput(ImporterUtil::getUTF8String(listOfFilesForSeries.front())); std::vector > readerOutput = structReader->Read(); if (readerOutput.empty()){ MITK_ERROR << "No structure sets were created" << endl; } else { std::vector modelVector; ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); for (const auto& aStruct : readerOutput){ mitk::ContourModelSet::Pointer countourModelSet = dynamic_cast(aStruct.GetPointer()); mitk::DataNode::Pointer structNode = mitk::DataNode::New(); structNode->SetData(countourModelSet); structNode->SetProperty("name", aStruct->GetProperty("name")); structNode->SetProperty("color", aStruct->GetProperty("contour.color")); structNode->SetProperty("contour.color", aStruct->GetProperty("contour.color")); structNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); - structNode->SetVisibility(true, mitk::BaseRenderer::GetInstance( - mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))); - structNode->SetVisibility(false, mitk::BaseRenderer::GetInstance( - mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget1"))); - structNode->SetVisibility(false, mitk::BaseRenderer::GetInstance( - mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))); - structNode->SetVisibility(true, mitk::BaseRenderer::GetInstance( - mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); dataStorage->Add(structNode); } mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(dataStorage); } } else if (modality.compare("RTPLAN", Qt::CaseInsensitive) == 0) { auto planReader = GetReader(readerRegistry, mitk::DICOMRTMimeTypes::DICOMRT_PLAN_MIMETYPE()); planReader->SetInput(ImporterUtil::getUTF8String(listOfFilesForSeries.front())); std::vector > readerOutput = planReader->Read(); if (!readerOutput.empty()){ //there is no image, only the properties are interesting mitk::Image::Pointer planDummyImage = dynamic_cast(readerOutput.at(0).GetPointer()); mitk::DataNode::Pointer planImageNode = mitk::DataNode::New(); planImageNode->SetData(planDummyImage); planImageNode->SetName("RTPlan"); ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); dataStorage->Add(planImageNode); } } } else { mitk::StringList seriesToLoad; QStringListIterator it(listOfFilesForSeries); while (it.hasNext()) { seriesToLoad.push_back(ImporterUtil::getUTF8String(it.next())); } //Get Reference for default data storage. ctkServiceReference serviceReference = mitk::PluginActivator::getContext()->getServiceReference(); mitk::IDataStorageService* storageService = mitk::PluginActivator::getContext()->getService(serviceReference); mitk::DataStorage* dataStorage = storageService->GetDefaultDataStorage().GetPointer()->GetDataStorage(); std::vector baseDatas = mitk::IOUtil::Load(seriesToLoad.front()); for (const auto &data : baseDatas) { mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(data); std::string nodeName = mitk::DataNode::NO_NAME_VALUE(); auto nameDataProp = data->GetProperty("name"); if (nameDataProp.IsNotNull()) { //if data has a name property set by reader, use this name nodeName = nameDataProp->GetValueAsString(); } else { //reader didn't specify a name, generate one. nodeName = mitk::GenerateNameFromDICOMProperties(node); } node->SetName(nodeName); dataStorage->Add(node); } } } else { MITK_INFO << "There are no files for the current series"; } } void DicomEventHandler::OnSignalRemoveSeriesFromStorage(const ctkEvent& /*ctkEvent*/) { } void DicomEventHandler::SubscribeSlots() { ctkServiceReference ref = mitk::PluginActivator::getContext()->getServiceReference(); if (ref) { ctkEventAdmin* eventAdmin = mitk::PluginActivator::getContext()->getService(ref); ctkDictionary properties; properties[ctkEventConstants::EVENT_TOPIC] = "org/mitk/gui/qt/dicom/ADD"; eventAdmin->subscribeSlot(this, SLOT(OnSignalAddSeriesToDataManager(ctkEvent)), properties); properties[ctkEventConstants::EVENT_TOPIC] = "org/mitk/gui/qt/dicom/DELETED"; eventAdmin->subscribeSlot(this, SLOT(OnSignalRemoveSeriesFromStorage(ctkEvent)), properties); } } diff --git a/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp b/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp index 5b601b2f03..9004dac7f8 100644 --- a/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp +++ b/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp @@ -1,377 +1,378 @@ /*============================================================================ 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. ============================================================================*/ // Blueberry #include #include #include // mitk #include #include #include // Qt #include #include #include #include #include "QmitkDicomInspectorView.h" const std::string QmitkDicomInspectorView::VIEW_ID = "org.mitk.views.dicominspector"; QmitkDicomInspectorView::ObserverInfo::ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, const std::string& renderWindowName, mitk::IRenderWindowPart* part) : controller(controller), observerTag(observerTag), renderWindowName(renderWindowName), renderWindowPart(part) { } QmitkDicomInspectorView::QmitkDicomInspectorView() : m_RenderWindowPart(nullptr) , m_PendingSliceChangedEvent(false) , m_SelectedNode(nullptr) , m_SelectedTimePoint(0.) , m_CurrentSelectedZSlice(0) { m_SelectedPosition.Fill(0.0); } QmitkDicomInspectorView::~QmitkDicomInspectorView() { this->RemoveAllObservers(); } void QmitkDicomInspectorView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; if (!InitObservers()) { QMessageBox::information(nullptr, "Error", "Unable to set up the event observers. The " \ "plot will not be triggered on changing the crosshair, " \ "position or time step."); } } } void QmitkDicomInspectorView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) { m_RenderWindowPart = nullptr; this->RemoveAllObservers(renderWindowPart); } void QmitkDicomInspectorView::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.singleSlot->SetDataStorage(GetDataStorage()); m_Controls.singleSlot->SetSelectionIsOptional(true); + m_Controls.singleSlot->SetAutoSelectNewNodes(true); m_Controls.singleSlot->SetEmptyInfo(QString("Please select a data node")); m_Controls.singleSlot->SetPopUpTitel(QString("Select data node")); m_SelectionServiceConnector = std::make_unique(); SetAsSelectionListener(true); m_Controls.timePointValueLabel->setText(QString("")); m_Controls.sliceNumberValueLabel->setText(QString("")); connect(m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkDicomInspectorView::OnCurrentSelectionChanged); mitk::IRenderWindowPart* renderWindowPart = GetRenderWindowPart(); RenderWindowPartActivated(renderWindowPart); } bool QmitkDicomInspectorView::InitObservers() { bool result = true; typedef QHash WindowMapType; WindowMapType windowMap = m_RenderWindowPart->GetQmitkRenderWindows(); auto i = windowMap.begin(); while (i != windowMap.end()) { mitk::SliceNavigationController* sliceNavController = i.value()->GetSliceNavigationController(); if (sliceNavController) { auto cmdSliceEvent = itk::SimpleMemberCommand::New(); cmdSliceEvent->SetCallbackFunction(this, &QmitkDicomInspectorView::OnSliceChanged); int tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), cmdSliceEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_RenderWindowPart))); auto cmdTimeEvent = itk::SimpleMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &QmitkDicomInspectorView::OnSliceChanged); tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometryTimeEvent(nullptr, 0), cmdTimeEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_RenderWindowPart))); auto cmdDelEvent = itk::MemberCommand::New(); cmdDelEvent->SetCallbackFunction(this, &QmitkDicomInspectorView::OnSliceNavigationControllerDeleted); tag = sliceNavController->AddObserver(itk::DeleteEvent(), cmdDelEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_RenderWindowPart))); } ++i; result = result && sliceNavController; } return result; } void QmitkDicomInspectorView::RemoveObservers(const mitk::SliceNavigationController* deletedSlicer) { std::pair obsRange = m_ObserverMap.equal_range(deletedSlicer); for (ObserverMapType::const_iterator pos = obsRange.first; pos != obsRange.second; ++pos) { pos->second.controller->RemoveObserver(pos->second.observerTag); } m_ObserverMap.erase(deletedSlicer); } void QmitkDicomInspectorView::RemoveAllObservers(mitk::IRenderWindowPart* deletedPart) { for (ObserverMapType::const_iterator pos = m_ObserverMap.begin(); pos != m_ObserverMap.end();) { ObserverMapType::const_iterator delPos = pos++; if (nullptr == deletedPart || deletedPart == delPos->second.renderWindowPart) { delPos->second.controller->RemoveObserver(delPos->second.observerTag); m_ObserverMap.erase(delPos); } } } void QmitkDicomInspectorView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { m_SelectedNode = nullptr; m_SelectedData = nullptr; UpdateData(); return; } if (nodes.front() != this->m_SelectedNode) { // node is selected, create DICOM tag table m_SelectedNode = nodes.front(); m_SelectedData = this->m_SelectedNode->GetData(); m_SelectedNodeTime.Modified(); UpdateData(); OnSliceChangedDelayed(); } } void QmitkDicomInspectorView::OnSliceChanged() { // Taken from QmitkStdMultiWidget::HandleCrosshairPositionEvent(). // Since there are always 3 events arriving (one for each render window) every time the slice // or time changes, the slot OnSliceChangedDelayed is triggered - and only if it hasn't been // triggered yet - so it is only executed once for every slice/time change. if (!m_PendingSliceChangedEvent) { m_PendingSliceChangedEvent = true; QTimer::singleShot(0, this, SLOT(OnSliceChangedDelayed())); } } void QmitkDicomInspectorView::OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/) { auto sendingSlicer = dynamic_cast(sender); this->RemoveObservers(sendingSlicer); } void QmitkDicomInspectorView::ValidateAndSetCurrentPosition() { auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto currentSelectedPosition = renderWindowPart->GetSelectedPosition(nullptr); const auto currentSelectedTimePoint = renderWindowPart->GetSelectedTimePoint(); if (m_SelectedPosition != currentSelectedPosition || m_SelectedTimePoint != currentSelectedTimePoint || m_SelectedNodeTime > m_CurrentPositionTime) { // the current position has been changed, the selected node has been changed since // the last position validation or the current time position has been changed -> check position m_SelectedPosition = currentSelectedPosition; m_SelectedTimePoint = currentSelectedTimePoint; m_CurrentPositionTime.Modified(); m_ValidSelectedPosition = false; if (m_SelectedData.IsNull()) { return; } mitk::BaseGeometry::Pointer geometry = m_SelectedData->GetTimeGeometry()->GetGeometryForTimePoint(m_SelectedTimePoint); // check for invalid time step if (geometry.IsNull()) { geometry = m_SelectedData->GetTimeGeometry()->GetGeometryForTimeStep(0); } if (geometry.IsNull()) { return; } m_ValidSelectedPosition = geometry->IsInside(m_SelectedPosition); itk::Index<3> index; geometry->WorldToIndex(m_SelectedPosition, index); m_CurrentSelectedZSlice = index[2]; } } void QmitkDicomInspectorView::OnSliceChangedDelayed() { m_PendingSliceChangedEvent = false; ValidateAndSetCurrentPosition(); m_Controls.tableTags->setEnabled(m_ValidSelectedPosition); if (m_SelectedNode.IsNotNull()) { RenderTable(); } } void QmitkDicomInspectorView::RenderTable() { assert(nullptr != m_RenderWindowPart); const auto timeStep = (m_SelectedData.IsNull()) ? 0 : m_SelectedData->GetTimeGeometry()->TimePointToTimeStep(m_SelectedTimePoint); unsigned int rowIndex = 0; for (const auto& element : m_Tags) { QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString( element.second.prop->GetValue(timeStep, m_CurrentSelectedZSlice, true, true))); m_Controls.tableTags->setItem(rowIndex, 3, newItem); ++rowIndex; } UpdateLabels(); } void QmitkDicomInspectorView::UpdateData() { QStringList headers; m_Controls.tableTags->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_Tags.clear(); if (m_SelectedData.IsNotNull()) { for (const auto& element : *(m_SelectedData->GetPropertyList()->GetMap())) { if (element.first.find("DICOM") == 0) { std::istringstream stream(element.first); std::string token; std::getline(stream, token, '.'); //drop the DICOM suffix std::getline(stream, token, '.'); //group id unsigned long dcmgroup = std::stoul(token, nullptr, 16); std::getline(stream, token, '.'); //element id unsigned long dcmelement = std::stoul(token, nullptr, 16); TagInfo info(mitk::DICOMTag(dcmgroup, dcmelement), dynamic_cast(element.second.GetPointer())); m_Tags.insert(std::make_pair(element.first, info)); } } } m_Controls.tableTags->setRowCount(m_Tags.size()); unsigned int rowIndex = 0; for (const auto& element : m_Tags) { QTableWidgetItem* newItem = new QTableWidgetItem(QString::number(element.second.tag.GetGroup(), 16)); m_Controls.tableTags->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(QString::number(element.second.tag.GetElement(), 16)); m_Controls.tableTags->setItem(rowIndex, 1, newItem); newItem = new QTableWidgetItem(QString::fromStdString(element.second.tag.GetName())); m_Controls.tableTags->setItem(rowIndex, 2, newItem); newItem = new QTableWidgetItem(QString::fromStdString(element.second.prop->GetValue())); m_Controls.tableTags->setItem(rowIndex, 3, newItem); ++rowIndex; } UpdateLabels(); } void QmitkDicomInspectorView::UpdateLabels() { if (m_SelectedData.IsNull()) { m_Controls.timePointValueLabel->setText(QString("")); m_Controls.sliceNumberValueLabel->setText(QString("")); } else { const auto timeStep = m_SelectedData->GetTimeGeometry()->TimePointToTimeStep(m_SelectedTimePoint); if (m_ValidSelectedPosition) { m_Controls.timePointValueLabel->setText(QString::number(timeStep) + QStringLiteral("(")+ QString::number(m_SelectedTimePoint/1000.) + QStringLiteral(" [s])")); m_Controls.sliceNumberValueLabel->setText(QString::number(m_CurrentSelectedZSlice)); } else { m_Controls.timePointValueLabel->setText(QString("outside data geometry")); m_Controls.sliceNumberValueLabel->setText(QString("outside data geometry")); } } } void QmitkDicomInspectorView::SetAsSelectionListener(bool checked) { if (checked) { m_SelectionServiceConnector->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp index 1007251dc0..98a6bb780f 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp @@ -1,874 +1,877 @@ /*============================================================================ 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 "QmitkMeasurementView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ctkDoubleSpinBox.h" #include "mitkPluginActivator.h" #include "usModuleRegistry.h" #include "mitkInteractionEventObserver.h" #include "mitkDisplayInteractor.h" #include "usGetModuleContext.h" #include "usModuleContext.h" #include US_INITIALIZE_MODULE struct QmitkPlanarFigureData { QmitkPlanarFigureData() : m_EndPlacementObserverTag(0), m_SelectObserverTag(0), m_StartInteractionObserverTag(0), m_EndInteractionObserverTag(0) { } mitk::PlanarFigure::Pointer m_Figure; unsigned int m_EndPlacementObserverTag; unsigned int m_SelectObserverTag; unsigned int m_StartInteractionObserverTag; unsigned int m_EndInteractionObserverTag; }; struct QmitkMeasurementViewData { QmitkMeasurementViewData() : m_LineCounter(0), m_PathCounter(0), m_AngleCounter(0), m_FourPointAngleCounter(0), m_CircleCounter(0), m_EllipseCounter(0), m_DoubleEllipseCounter(0), m_RectangleCounter(0), m_PolygonCounter(0), m_BezierCurveCounter(0), m_SubdivisionPolygonCounter(0), m_UnintializedPlanarFigure(false), m_ScrollEnabled(true), m_Parent(nullptr), m_SingleNodeSelectionWidget(nullptr), m_DrawLine(nullptr), m_DrawPath(nullptr), m_DrawAngle(nullptr), m_DrawFourPointAngle(nullptr), m_DrawRectangle(nullptr), m_DrawPolygon(nullptr), m_DrawCircle(nullptr), m_DrawEllipse(nullptr), m_DrawDoubleEllipse(nullptr), m_DrawBezierCurve(nullptr), m_DrawSubdivisionPolygon(nullptr), m_DrawActionsToolBar(nullptr), m_DrawActionsGroup(nullptr), m_SelectedPlanarFiguresText(nullptr), m_CopyToClipboard(nullptr), m_Layout(nullptr), m_Radius(nullptr), m_Thickness(nullptr), m_FixedParameterBox(nullptr) { } unsigned int m_LineCounter; unsigned int m_PathCounter; unsigned int m_AngleCounter; unsigned int m_FourPointAngleCounter; unsigned int m_CircleCounter; unsigned int m_EllipseCounter; unsigned int m_DoubleEllipseCounter; unsigned int m_RectangleCounter; unsigned int m_PolygonCounter; unsigned int m_BezierCurveCounter; unsigned int m_SubdivisionPolygonCounter; QList m_CurrentSelection; std::map m_DataNodeToPlanarFigureData; mitk::DataNode::Pointer m_SelectedImageNode; bool m_UnintializedPlanarFigure; bool m_ScrollEnabled; QWidget* m_Parent; + QLabel* m_SelectedImageLabel; QmitkSingleNodeSelectionWidget* m_SingleNodeSelectionWidget; QAction* m_DrawLine; QAction* m_DrawPath; QAction* m_DrawAngle; QAction* m_DrawFourPointAngle; QAction* m_DrawRectangle; QAction* m_DrawPolygon; QAction* m_DrawCircle; QAction* m_DrawEllipse; QAction* m_DrawDoubleEllipse; QAction* m_DrawBezierCurve; QAction* m_DrawSubdivisionPolygon; QToolBar* m_DrawActionsToolBar; QActionGroup* m_DrawActionsGroup; QTextBrowser* m_SelectedPlanarFiguresText; QPushButton* m_CopyToClipboard; QGridLayout* m_Layout; ctkDoubleSpinBox* m_Radius; ctkDoubleSpinBox* m_Thickness; QGroupBox* m_FixedParameterBox; }; const std::string QmitkMeasurementView::VIEW_ID = "org.mitk.views.measurement"; QmitkMeasurementView::QmitkMeasurementView() : d(new QmitkMeasurementViewData) { } QmitkMeasurementView::~QmitkMeasurementView() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeRemoved(it.Value()); delete d; } void QmitkMeasurementView::CreateQtPartControl(QWidget* parent) { d->m_Parent = parent; d->m_SingleNodeSelectionWidget = new QmitkSingleNodeSelectionWidget(); d->m_SingleNodeSelectionWidget->SetDataStorage(GetDataStorage()); d->m_SingleNodeSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); d->m_SingleNodeSelectionWidget->SetSelectionIsOptional(true); d->m_SingleNodeSelectionWidget->SetEmptyInfo(QStringLiteral("Please select a reference image")); d->m_SingleNodeSelectionWidget->SetPopUpTitel(QStringLiteral("Select a reference image")); d->m_DrawActionsToolBar = new QToolBar; d->m_DrawActionsGroup = new QActionGroup(this); d->m_DrawActionsGroup->setExclusive(true); auto* currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/line.png"), tr("Draw Line")); currentAction->setCheckable(true); d->m_DrawLine = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/path.png"), tr("Draw Path")); currentAction->setCheckable(true); d->m_DrawPath = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/angle.png"), tr("Draw Angle")); currentAction->setCheckable(true); d->m_DrawAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/four-point-angle.png"), tr("Draw Four Point Angle")); currentAction->setCheckable(true); d->m_DrawFourPointAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/circle.png"), tr("Draw Circle")); currentAction->setCheckable(true); d->m_DrawCircle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/ellipse.png"), tr("Draw Ellipse")); currentAction->setCheckable(true); d->m_DrawEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/doubleellipse.png"), tr("Draw Double Ellipse")); currentAction->setCheckable(true); d->m_DrawDoubleEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/rectangle.png"), tr("Draw Rectangle")); currentAction->setCheckable(true); d->m_DrawRectangle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/polygon.png"), tr("Draw Polygon")); currentAction->setCheckable(true); d->m_DrawPolygon = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/beziercurve.png"), tr("Draw Bezier Curve")); currentAction->setCheckable(true); d->m_DrawBezierCurve = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/subdivisionpolygon.png"), tr("Draw Subdivision Polygon")); currentAction->setCheckable(true); d->m_DrawSubdivisionPolygon = currentAction; d->m_DrawActionsToolBar->setEnabled(false); // fixed parameter section auto fixedLayout = new QGridLayout(); d->m_FixedParameterBox = new QGroupBox(); d->m_FixedParameterBox->setCheckable(true); d->m_FixedParameterBox->setChecked(false); d->m_FixedParameterBox->setTitle("Fixed sized circle/double ellipse"); d->m_FixedParameterBox->setToolTip("If activated, circles and double ellipses (as rings) figures will always be created with the set parameters as fixed size."); d->m_FixedParameterBox->setAlignment(Qt::AlignLeft); auto labelRadius1 = new QLabel(QString("Radius")); d->m_Radius = new ctkDoubleSpinBox(); d->m_Radius->setMinimum(0); d->m_Radius->setValue(10); d->m_Radius->setSuffix(" mm"); d->m_Radius->setAlignment(Qt::AlignLeft); d->m_Radius->setToolTip("Sets the radius for following planar figures: circle, double ellipse (as ring)."); auto labelThickness = new QLabel(QString("Thickness")); d->m_Thickness = new ctkDoubleSpinBox(); d->m_Thickness->setMinimum(0); d->m_Thickness->setMaximum(10); d->m_Thickness->setValue(5); d->m_Thickness->setSuffix(" mm"); d->m_Thickness->setAlignment(Qt::AlignLeft); d->m_Thickness->setToolTip("Sets the thickness for following planar figures: double ellipse (as ring)."); fixedLayout->addWidget(labelRadius1,0,0); fixedLayout->addWidget(d->m_Radius,0,1); fixedLayout->addWidget(labelThickness,1,0); fixedLayout->addWidget(d->m_Thickness,1,1); d->m_FixedParameterBox->setLayout(fixedLayout); // planar figure details text d->m_SelectedPlanarFiguresText = new QTextBrowser; // copy to clipboard button d->m_CopyToClipboard = new QPushButton(tr("Copy to Clipboard")); + d->m_SelectedImageLabel = new QLabel("Selected Image"); d->m_Layout = new QGridLayout; - d->m_Layout->addWidget(d->m_SingleNodeSelectionWidget, 0, 0, 1, 2); + d->m_Layout->addWidget(d->m_SelectedImageLabel, 0, 0); + d->m_Layout->addWidget(d->m_SingleNodeSelectionWidget, 0, 1, 1, 1); d->m_Layout->addWidget(d->m_DrawActionsToolBar, 1, 0, 1, 2); d->m_Layout->addWidget(d->m_FixedParameterBox, 2, 0, 1, 2); d->m_Layout->addWidget(d->m_SelectedPlanarFiguresText, 3, 0, 1, 2); d->m_Layout->addWidget(d->m_CopyToClipboard, 4, 0, 1, 2); d->m_Parent->setLayout(d->m_Layout); this->CreateConnections(); this->AddAllInteractors(); // placed after CreateConnections to trigger update of the current selection d->m_SingleNodeSelectionWidget->SetAutoSelectNewNodes(true); } void QmitkMeasurementView::CreateConnections() { connect(d->m_SingleNodeSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMeasurementView::OnCurrentSelectionChanged); connect(d->m_DrawLine, SIGNAL(triggered(bool)), this, SLOT(OnDrawLineTriggered(bool))); connect(d->m_DrawPath, SIGNAL(triggered(bool)), this, SLOT(OnDrawPathTriggered(bool))); connect(d->m_DrawAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawAngleTriggered(bool))); connect(d->m_DrawFourPointAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawFourPointAngleTriggered(bool))); connect(d->m_DrawCircle, SIGNAL(triggered(bool)), this, SLOT(OnDrawCircleTriggered(bool))); connect(d->m_DrawEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawEllipseTriggered(bool))); connect(d->m_DrawDoubleEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawDoubleEllipseTriggered(bool))); connect(d->m_DrawRectangle, SIGNAL(triggered(bool)), this, SLOT(OnDrawRectangleTriggered(bool))); connect(d->m_DrawPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawPolygonTriggered(bool))); connect(d->m_DrawBezierCurve, SIGNAL(triggered(bool)), this, SLOT(OnDrawBezierCurveTriggered(bool))); connect(d->m_DrawSubdivisionPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawSubdivisionPolygonTriggered(bool))); connect(d->m_CopyToClipboard, SIGNAL(clicked(bool)), this, SLOT(OnCopyToClipboard(bool))); connect(d->m_Radius, QOverload::of(&ctkDoubleSpinBox::valueChanged), d->m_Thickness, &ctkDoubleSpinBox::setMaximum); } void QmitkMeasurementView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { d->m_SelectedImageNode = nullptr; d->m_DrawActionsToolBar->setEnabled(false); } else { d->m_SelectedImageNode = nodes.front(); d->m_DrawActionsToolBar->setEnabled(true); } } void QmitkMeasurementView::NodeAdded(const mitk::DataNode* node) { // add observer for selection in renderwindow mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); auto isPositionMarker = false; node->GetBoolProperty("isContourMarker", isPositionMarker); if (planarFigure.IsNotNull() && !isPositionMarker) { auto nonConstNode = const_cast(node); mitk::PlanarFigureInteractor::Pointer interactor = dynamic_cast(node->GetDataInteractor().GetPointer()); if (interactor.IsNull()) { interactor = mitk::PlanarFigureInteractor::New(); auto planarFigureModule = us::ModuleRegistry::GetModule("MitkPlanarFigure"); interactor->LoadStateMachine("PlanarFigureInteraction.xml", planarFigureModule); interactor->SetEventConfig("PlanarFigureConfig.xml", planarFigureModule); } interactor->SetDataNode(nonConstNode); QmitkPlanarFigureData data; data.m_Figure = planarFigure; typedef itk::SimpleMemberCommand SimpleCommandType; typedef itk::MemberCommand MemberCommandType; // add observer for event when figure has been placed auto initializationCommand = SimpleCommandType::New(); initializationCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureInitialized); data.m_EndPlacementObserverTag = planarFigure->AddObserver(mitk::EndPlacementPlanarFigureEvent(), initializationCommand); // add observer for event when figure is picked (selected) auto selectCommand = MemberCommandType::New(); selectCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureSelected); data.m_SelectObserverTag = planarFigure->AddObserver(mitk::SelectPlanarFigureEvent(), selectCommand); // add observer for event when interaction with figure starts auto startInteractionCommand = SimpleCommandType::New(); startInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::DisableCrosshairNavigation); data.m_StartInteractionObserverTag = planarFigure->AddObserver(mitk::StartInteractionPlanarFigureEvent(), startInteractionCommand); // add observer for event when interaction with figure starts auto endInteractionCommand = SimpleCommandType::New(); endInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::EnableCrosshairNavigation); data.m_EndInteractionObserverTag = planarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), endInteractionCommand); // adding to the map of tracked planarfigures d->m_DataNodeToPlanarFigureData[nonConstNode] = data; } } void QmitkMeasurementView::NodeChanged(const mitk::DataNode* node) { auto it = std::find(d->m_CurrentSelection.begin(), d->m_CurrentSelection.end(), node); if (it != d->m_CurrentSelection.end()) { this->UpdateMeasurementText(); } } void QmitkMeasurementView::NodeRemoved(const mitk::DataNode* node) { auto nonConstNode = const_cast(node); auto it = d->m_DataNodeToPlanarFigureData.find(nonConstNode); auto isFigureFinished = false; auto isPlaced = false; if (it != d->m_DataNodeToPlanarFigureData.end()) { QmitkPlanarFigureData& data = it->second; data.m_Figure->RemoveObserver(data.m_EndPlacementObserverTag); data.m_Figure->RemoveObserver(data.m_SelectObserverTag); data.m_Figure->RemoveObserver(data.m_StartInteractionObserverTag); data.m_Figure->RemoveObserver(data.m_EndInteractionObserverTag); isFigureFinished = data.m_Figure->GetPropertyList()->GetBoolProperty("initiallyplaced", isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons d->m_DataNodeToPlanarFigureData.erase( it ); } if (nonConstNode != nullptr) nonConstNode->SetDataInteractor(nullptr); auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto nodes = this->GetDataStorage()->GetDerivations(node, isPlanarFigure); for (unsigned int x = 0; x < nodes->size(); ++x) { mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(nodes->at(x)->GetData()); if (planarFigure.IsNotNull()) { isFigureFinished = planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced",isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode { this->GetDataStorage()->Remove(nodes->at(x)); if (!d->m_DataNodeToPlanarFigureData.empty()) { it = d->m_DataNodeToPlanarFigureData.find(nodes->at(x)); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_DataNodeToPlanarFigureData.erase(it); this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons this->EnableCrosshairNavigation(); } } } } } } void QmitkMeasurementView::PlanarFigureSelected(itk::Object* object, const itk::EventObject&) { d->m_CurrentSelection.clear(); auto lambda = [&object](const std::pair& element) { return element.second.m_Figure == object; }; auto it = std::find_if(d->m_DataNodeToPlanarFigureData.begin(), d->m_DataNodeToPlanarFigureData.end(), lambda); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_CurrentSelection.push_back(it->first); } this->UpdateMeasurementText(); this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::PlanarFigureInitialized() { d->m_UnintializedPlanarFigure = false; d->m_DrawActionsToolBar->setEnabled(true); d->m_DrawLine->setChecked(false); d->m_DrawPath->setChecked(false); d->m_DrawAngle->setChecked(false); d->m_DrawFourPointAngle->setChecked(false); d->m_DrawCircle->setChecked(false); d->m_DrawEllipse->setChecked(false); d->m_DrawDoubleEllipse->setChecked(false); d->m_DrawRectangle->setChecked(false); d->m_DrawPolygon->setChecked(false); d->m_DrawBezierCurve->setChecked(false); d->m_DrawSubdivisionPolygon->setChecked(false); } void QmitkMeasurementView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList& nodes) { d->m_CurrentSelection = nodes; this->UpdateMeasurementText(); // bug 16600: deselecting all planarfigures by clicking on datamanager when no node is selected if (d->m_CurrentSelection.size() == 0) { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto planarFigures = this->GetDataStorage()->GetSubset(isPlanarFigure); // setting all planar figures which are not helper objects not selected for (mitk::DataStorage::SetOfObjects::ConstIterator it = planarFigures->Begin(); it != planarFigures->End(); ++it) { auto node = it.Value(); auto isHelperObject = false; node->GetBoolProperty("helper object", isHelperObject); if (!isHelperObject) node->SetSelected(false); } } for (int i = d->m_CurrentSelection.size() - 1; i >= 0; --i) { auto node = d->m_CurrentSelection[i]; mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); // the last selected planar figure if (planarFigure.IsNotNull() && planarFigure->GetPlaneGeometry()) { auto planarFigureInitializedWindow = false; auto linkedRenderWindow = dynamic_cast(this->GetRenderWindowPart()); QmitkRenderWindow* selectedRenderWindow; if (!linkedRenderWindow) return; auto axialRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("axial"); auto sagittalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("sagittal"); auto coronalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("coronal"); auto threeDimRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("3d"); if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, threeDimRenderWindow->GetRenderer())) { selectedRenderWindow = threeDimRenderWindow; } else { selectedRenderWindow = nullptr; } auto planeGeometry = dynamic_cast(planarFigure->GetPlaneGeometry()); auto normal = planeGeometry->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer axialPlane = axialRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto axialNormal = axialPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer sagittalPlane = sagittalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto sagittalNormal = sagittalPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer coronalPlane = coronalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto coronalNormal = coronalPlane->GetNormalVnl(); normal[0] = fabs(normal[0]); normal[1] = fabs(normal[1]); normal[2] = fabs(normal[2]); axialNormal[0] = fabs(axialNormal[0]); axialNormal[1] = fabs(axialNormal[1]); axialNormal[2] = fabs(axialNormal[2]); sagittalNormal[0] = fabs(sagittalNormal[0]); sagittalNormal[1] = fabs(sagittalNormal[1]); sagittalNormal[2] = fabs(sagittalNormal[2]); coronalNormal[0] = fabs(coronalNormal[0]); coronalNormal[1] = fabs(coronalNormal[1]); coronalNormal[2] = fabs(coronalNormal[2]); auto ang1 = angle(normal, axialNormal); auto ang2 = angle(normal, sagittalNormal); auto ang3 = angle(normal, coronalNormal); if (ang1 < ang2 && ang1 < ang3) { selectedRenderWindow = axialRenderWindow; } else { if (ang2 < ang3) { selectedRenderWindow = sagittalRenderWindow; } else { selectedRenderWindow = coronalRenderWindow; } } // re-orient view if (selectedRenderWindow) selectedRenderWindow->GetSliceNavigationController()->ReorientSlices(planeGeometry->GetOrigin(), planeGeometry->GetNormal()); } break; } this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::OnDrawLineTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarLine::New(), QString("Line%1").arg(++d->m_LineCounter)); } void QmitkMeasurementView::OnDrawPathTriggered(bool) { mitk::CoreServicePointer propertyFilters(mitk::CoreServices::GetPropertyFilters()); mitk::PropertyFilter filter; filter.AddEntry("ClosedPlanarPolygon", mitk::PropertyFilter::Blacklist); propertyFilters->AddFilter(filter, "PlanarPolygon"); mitk::PlanarPolygon::Pointer planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOff(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Path%1").arg(++d->m_PathCounter)); node->SetProperty("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnDrawAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarAngle::New(), QString("Angle%1").arg(++d->m_AngleCounter)); } void QmitkMeasurementView::OnDrawFourPointAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarFourPointAngle::New(), QString("Four Point Angle%1").arg(++d->m_FourPointAngleCounter)); } void QmitkMeasurementView::OnDrawCircleTriggered(bool) { auto circle = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarCircle::New(d->m_Radius->value()) : mitk::PlanarCircle::New(); this->AddFigureToDataStorage(circle, QString("Circle%1").arg(++d->m_CircleCounter)); } void QmitkMeasurementView::OnDrawEllipseTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarEllipse::New(), QString("Ellipse%1").arg(++d->m_EllipseCounter)); } void QmitkMeasurementView::OnDrawDoubleEllipseTriggered(bool) { auto ellipse = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarDoubleEllipse::New(d->m_Radius->value(),d->m_Thickness->value()) : mitk::PlanarDoubleEllipse::New(); this->AddFigureToDataStorage(ellipse, QString("DoubleEllipse%1").arg(++d->m_DoubleEllipseCounter)); } void QmitkMeasurementView::OnDrawBezierCurveTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarBezierCurve::New(), QString("BezierCurve%1").arg(++d->m_BezierCurveCounter)); } void QmitkMeasurementView::OnDrawSubdivisionPolygonTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarSubdivisionPolygon::New(), QString("SubdivisionPolygon%1").arg(++d->m_SubdivisionPolygonCounter)); } void QmitkMeasurementView::OnDrawRectangleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarRectangle::New(), QString("Rectangle%1").arg(++d->m_RectangleCounter)); } void QmitkMeasurementView::OnDrawPolygonTriggered(bool) { auto planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOn(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Polygon%1").arg(++d->m_PolygonCounter)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnCopyToClipboard(bool) { QApplication::clipboard()->setText(d->m_SelectedPlanarFiguresText->toPlainText(), QClipboard::Clipboard); } mitk::DataNode::Pointer QmitkMeasurementView::AddFigureToDataStorage(mitk::PlanarFigure* figure, const QString& name) { auto newNode = mitk::DataNode::New(); newNode->SetName(name.toStdString()); newNode->SetData(figure); newNode->SetSelected(true); if (d->m_SelectedImageNode.IsNotNull()) { this->GetDataStorage()->Add(newNode, d->m_SelectedImageNode); } else { this->GetDataStorage()->Add(newNode); } for (auto &node : d->m_CurrentSelection) node->SetSelected(false); d->m_CurrentSelection.clear(); d->m_CurrentSelection.push_back(newNode); this->UpdateMeasurementText(); this->DisableCrosshairNavigation(); d->m_DrawActionsToolBar->setEnabled(false); d->m_UnintializedPlanarFigure = true; return newNode; } void QmitkMeasurementView::UpdateMeasurementText() { d->m_SelectedPlanarFiguresText->clear(); QString infoText; QString plainInfoText; int j = 1; mitk::PlanarFigure::Pointer planarFigure; mitk::PlanarAngle::Pointer planarAngle; mitk::PlanarFourPointAngle::Pointer planarFourPointAngle; mitk::DataNode::Pointer node; for (int i = 0; i < d->m_CurrentSelection.size(); ++i, ++j) { plainInfoText.clear(); node = d->m_CurrentSelection[i]; planarFigure = dynamic_cast(node->GetData()); if (planarFigure.IsNull()) continue; if (j > 1) infoText.append("
"); infoText.append(QString("%1
").arg(QString::fromStdString(node->GetName()))); plainInfoText.append(QString("%1").arg(QString::fromStdString(node->GetName()))); planarAngle = dynamic_cast (planarFigure.GetPointer()); if (planarAngle.IsNull()) planarFourPointAngle = dynamic_cast (planarFigure.GetPointer()); double featureQuantity = 0.0; for (unsigned int k = 0; k < planarFigure->GetNumberOfFeatures(); ++k) { if (!planarFigure->IsFeatureActive(k)) continue; featureQuantity = planarFigure->GetQuantity(k); if ((planarAngle.IsNotNull() && k == planarAngle->FEATURE_ID_ANGLE) || (planarFourPointAngle.IsNotNull() && k == planarFourPointAngle->FEATURE_ID_ANGLE)) featureQuantity = featureQuantity * 180 / vnl_math::pi; infoText.append(QString("%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); plainInfoText.append(QString("\n%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); if (k + 1 != planarFigure->GetNumberOfFeatures()) infoText.append("
"); } if (j != d->m_CurrentSelection.size()) infoText.append("
"); } d->m_SelectedPlanarFiguresText->setHtml(infoText); } void QmitkMeasurementView::AddAllInteractors() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeAdded(it.Value()); } void QmitkMeasurementView::EnableCrosshairNavigation() { // enable the crosshair navigation // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractorConfig : m_DisplayInteractorConfigs) { if (displayInteractorConfig.first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(displayInteractorConfig.first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(displayInteractorConfig.second); } } } m_DisplayInteractorConfigs.clear(); d->m_ScrollEnabled = true; } void QmitkMeasurementView::DisableCrosshairNavigation() { // dont deactivate twice, else we will clutter the config list ... if (d->m_ScrollEnabled == false) return; // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction will still be enabled m_DisplayInteractorConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(eventObserver)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(eventObserver, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig("DisplayConfigMITKLimited.xml"); } } d->m_ScrollEnabled = false; } mitk::DataStorage::SetOfObjects::ConstPointer QmitkMeasurementView::GetAllPlanarFigures() const { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto isNotHelperObject = mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(false)); auto isNotHelperButPlanarFigure = mitk::NodePredicateAnd::New( isPlanarFigure, isNotHelperObject ); return this->GetDataStorage()->GetSubset(isPlanarFigure); } diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationControls.ui b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationControls.ui index d055c5a2d9..7bffe9ff5f 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationControls.ui +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationControls.ui @@ -1,806 +1,827 @@ QmitkMultiLabelSegmentationControls 0 0 459 844 0 0 0 0 MS Shell Dlg 2 8 50 false false false false QmitkSegmentation 0 0 Data Selection - Patient Image + Selected Image 0 40 Create a new segmentation session ... :/multilabelsegmentation/NewSegmentationSession_48x48.png:/multilabelsegmentation/NewSegmentationSession_48x48.png 28 28 N true Segmentation 0 40 Layers true false Add a layer to the current segmentation session ... - + :/Qmitk/AddLayer_48x48.png:/Qmitk/AddLayer_48x48.png 28 28 true Delete the active layer ... - + :/Qmitk/DeleteLayer_48x48.png:/Qmitk/DeleteLayer_48x48.png 28 28 true Qt::Horizontal 0 20 Change to the previous available layer ... - + :/Qmitk/PreviousLayer_48x48.png:/Qmitk/PreviousLayer_48x48.png 28 28 true Change to the next available layer ... - + :/Qmitk/NextLayer_48x48.png:/Qmitk/NextLayer_48x48.png 28 28 true 0 0 50 30 40 30 + MS Shell Dlg 2 12 + 50 + false + false + false + false Switch to a layer 0 Labels true Add a new label to the current segmentation session ... :/multilabelsegmentation/NewLabel_48x48.png:/multilabelsegmentation/NewLabel_48x48.png 28 28 N true Lock/Unlock exterior ... - + :/Qmitk/UnlockExterior_48x48.png :/Qmitk/LockExterior_48x48.png:/Qmitk/UnlockExterior_48x48.png 28 28 true true Save LabelSet Preset ... - + :/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg:/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg 28 28 true Load LabelSet Preset ... - + :/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg:/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg 28 28 true Qt::Horizontal 0 20 0 34 Show a table with all labels in the current segmentation session >> 28 28 true false Qt::NoArrow 0 0 0 20 0 0 QTabWidget::tab-bar { alignment: middle; } 0 true false 2D Tools 0 0 50 false 0 0 50 false Qt::Vertical 20 40 3D Tools 0 0 50 false 0 0 50 false Qt::Vertical 20 40 0 0 Interpolation 2 QLayout::SetMinimumSize 2 2 2 2 0 0 Disabled 2D Interpolation 3D Interpolation 0 0 1 0 0 QLayout::SetMinimumSize 0 0 0 0 0 0 0 0 + MS Shell Dlg 2 + 8 50 + false false + false + false 0 0 QLayout::SetMinimumSize 0 0 0 0 0 0 0 50 + MS Shell Dlg 2 + 8 50 + false false + false + false 0 0 Qt::Vertical 20 40 m_LabelSetWidget groupBox_DataSelection m_tw2DTools m_gbInterpolation groupBox_Layer groupBox_Labels QmitkSingleNodeSelectionWidget QWidget
QmitkSingleNodeSelectionWidget.h
1
QmitkToolSelectionBox QWidget
QmitkToolSelectionBox.h
QmitkToolGUIArea QWidget
QmitkToolGUIArea.h
QmitkLabelSetWidget QWidget
Qmitk/QmitkLabelSetWidget.h
1
QmitkSliceBasedInterpolatorWidget QWidget
QmitkSliceBasedInterpolatorWidget.h
1
QmitkSurfaceBasedInterpolatorWidget QWidget
QmitkSurfaceBasedInterpolatorWidget.h
1
QmitkToolGUIArea.h QmitkToolSelectionBox.h + + + + +
diff --git a/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp b/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp index a95ddd70ce..70bc475007 100644 --- a/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp +++ b/Plugins/org.mitk.gui.qt.properties/src/internal/QmitkPropertyTreeView.cpp @@ -1,370 +1,371 @@ /*============================================================================ 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 "QmitkAddNewPropertyDialog.h" #include "QmitkPropertyItemDelegate.h" #include "QmitkPropertyItemModel.h" #include "QmitkPropertyItemSortFilterProxyModel.h" #include "QmitkPropertyTreeView.h" #include #include #include #include #include #include #include #include const std::string QmitkPropertyTreeView::VIEW_ID = "org.mitk.views.properties"; QmitkPropertyTreeView::QmitkPropertyTreeView() : m_PropertyAliases(mitk::CoreServices::GetPropertyAliases(nullptr), nullptr), m_PropertyDescriptions(mitk::CoreServices::GetPropertyDescriptions(nullptr), nullptr), m_PropertyPersistence(mitk::CoreServices::GetPropertyPersistence(nullptr), nullptr), m_ProxyModel(nullptr), m_Model(nullptr), m_Delegate(nullptr), m_Renderer(nullptr) { } QmitkPropertyTreeView::~QmitkPropertyTreeView() { } void QmitkPropertyTreeView::SetFocus() { m_Controls.filterLineEdit->setFocus(); } void QmitkPropertyTreeView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_Controls.propertyListComboBox->count() == 2) { QHash renderWindows = renderWindowPart->GetQmitkRenderWindows(); Q_FOREACH(QString renderWindow, renderWindows.keys()) { m_Controls.propertyListComboBox->insertItem(m_Controls.propertyListComboBox->count() - 1, QString("Data node: ") + renderWindow); } } } void QmitkPropertyTreeView::RenderWindowPartDeactivated(mitk::IRenderWindowPart*) { if (m_Controls.propertyListComboBox->count() > 2) { m_Controls.propertyListComboBox->clear(); m_Controls.propertyListComboBox->addItem("Data node: common"); m_Controls.propertyListComboBox->addItem("Base data"); } } void QmitkPropertyTreeView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.propertyListComboBox->addItem("Data node: common"); mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); if (renderWindowPart != nullptr) { QHash renderWindows = renderWindowPart->GetQmitkRenderWindows(); for(const auto& renderWindow : renderWindows.keys()) { m_Controls.propertyListComboBox->addItem(QString("Data node: ") + renderWindow); } } m_Controls.propertyListComboBox->addItem("Base data"); m_Controls.newButton->setEnabled(false); this->HideAllIcons(); m_ProxyModel = new QmitkPropertyItemSortFilterProxyModel(m_Controls.treeView); m_Model = new QmitkPropertyItemModel(m_ProxyModel); m_ProxyModel->setSourceModel(m_Model); m_ProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_ProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); m_ProxyModel->setDynamicSortFilter(true); m_Delegate = new QmitkPropertyItemDelegate(m_Controls.treeView); m_Controls.singleSlot->SetDataStorage(GetDataStorage()); m_Controls.singleSlot->SetSelectionIsOptional(true); + m_Controls.singleSlot->SetAutoSelectNewNodes(true); m_Controls.singleSlot->SetEmptyInfo(QString("Please select a data node")); m_Controls.singleSlot->SetPopUpTitel(QString("Select data node")); m_SelectionServiceConnector = std::make_unique(); SetAsSelectionListener(true); m_Controls.filterLineEdit->setClearButtonEnabled(true); m_Controls.treeView->setItemDelegateForColumn(1, m_Delegate); m_Controls.treeView->setModel(m_ProxyModel); m_Controls.treeView->setColumnWidth(0, 160); m_Controls.treeView->sortByColumn(0, Qt::AscendingOrder); m_Controls.treeView->setSelectionBehavior(QAbstractItemView::SelectRows); m_Controls.treeView->setSelectionMode(QAbstractItemView::SingleSelection); m_Controls.treeView->setEditTriggers(QAbstractItemView::SelectedClicked | QAbstractItemView::DoubleClicked); const int ICON_SIZE = 32; auto icon = berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/tags.svg")); m_Controls.tagsLabel->setPixmap(icon.pixmap(ICON_SIZE)); icon = berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/tag.svg")); m_Controls.tagLabel->setPixmap(icon.pixmap(ICON_SIZE)); icon = berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg")); m_Controls.saveLabel->setPixmap(icon.pixmap(ICON_SIZE)); connect(m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkPropertyTreeView::OnCurrentSelectionChanged); connect(m_Controls.filterLineEdit, &QLineEdit::textChanged, this, &QmitkPropertyTreeView::OnFilterTextChanged); connect(m_Controls.propertyListComboBox, static_cast(&QComboBox::currentIndexChanged), this, &QmitkPropertyTreeView::OnPropertyListChanged); connect(m_Controls.newButton, &QPushButton::clicked, this, &QmitkPropertyTreeView::OnAddNewProperty); connect(m_Controls.treeView->selectionModel(), &QItemSelectionModel::currentRowChanged, this, &QmitkPropertyTreeView::OnCurrentRowChanged); connect(m_Model, &QmitkPropertyItemModel::modelReset, this, &QmitkPropertyTreeView::OnModelReset); } void QmitkPropertyTreeView::SetAsSelectionListener(bool checked) { if (checked) { m_SelectionServiceConnector->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } } QString QmitkPropertyTreeView::GetPropertyNameOrAlias(const QModelIndex& index) { QString propertyName; if (index.isValid()) { QModelIndex current = index; while (current.isValid()) { QString name = m_ProxyModel->data(m_ProxyModel->index(current.row(), 0, current.parent())).toString(); propertyName.prepend(propertyName.isEmpty() ? name : name.append('.')); current = current.parent(); } } return propertyName; } void QmitkPropertyTreeView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { m_SelectedNode = nullptr; this->SetPartName("Properties"); m_Model->SetPropertyList(nullptr); m_Delegate->SetPropertyList(nullptr); m_Controls.newButton->setEnabled(false); return; } // node is selected, create tree with node properties m_SelectedNode = nodes.front(); mitk::PropertyList* propertyList = m_Model->GetPropertyList(); if (m_Renderer == nullptr && m_Controls.propertyListComboBox->currentText() == "Base data") { propertyList = m_SelectedNode->GetData() != nullptr ? m_SelectedNode->GetData()->GetPropertyList() : nullptr; } else { propertyList = m_SelectedNode->GetPropertyList(m_Renderer); } QString selectionClassName = m_SelectedNode->GetData() != nullptr ? m_SelectedNode->GetData()->GetNameOfClass() : ""; m_SelectionClassName = selectionClassName.toStdString(); m_Model->SetPropertyList(propertyList, selectionClassName); m_Delegate->SetPropertyList(propertyList); m_Controls.newButton->setEnabled(true); if (!m_ProxyModel->filterRegExp().isEmpty()) { m_Controls.treeView->expandAll(); } } void QmitkPropertyTreeView::HideAllIcons() { m_Controls.tagLabel->hide(); m_Controls.tagsLabel->hide(); m_Controls.saveLabel->hide(); } void QmitkPropertyTreeView::OnCurrentRowChanged(const QModelIndex& current, const QModelIndex&) { if (current.isValid()) { QString name = this->GetPropertyNameOrAlias(current); if (!name.isEmpty()) { QString alias; bool isTrueName = true; std::string trueName = m_PropertyAliases->GetPropertyName(name.toStdString()); if (trueName.empty() && !m_SelectionClassName.empty()) trueName = m_PropertyAliases->GetPropertyName(name.toStdString(), m_SelectionClassName); if (!trueName.empty()) { alias = name; name = QString::fromStdString(trueName); isTrueName = false; } QString description = QString::fromStdString(m_PropertyDescriptions->GetDescription(name.toStdString())); std::vector aliases; if (!isTrueName) { aliases = m_PropertyAliases->GetAliases(name.toStdString(), m_SelectionClassName); if (aliases.empty() && !m_SelectionClassName.empty()) aliases = m_PropertyAliases->GetAliases(name.toStdString()); } bool isPersistent = m_PropertyPersistence->HasInfo(name.toStdString()); if (!description.isEmpty() || !aliases.empty() || isPersistent) { QString customizedDescription; if (!aliases.empty()) { customizedDescription = "

" + name + "

"; std::size_t numAliases = aliases.size(); std::size_t lastAlias = numAliases - 1; for (std::size_t i = 0; i < numAliases; ++i) { customizedDescription += i != lastAlias ? "
" : "
"; customizedDescription += QString::fromStdString(aliases[i]) + "
"; } } else { customizedDescription = "

" + name + "

"; } if (!description.isEmpty()) customizedDescription += "

" + description + "

"; m_Controls.tagsLabel->setVisible(!aliases.empty() && aliases.size() > 1); m_Controls.tagLabel->setVisible(!aliases.empty() && aliases.size() == 1); m_Controls.saveLabel->setVisible(isPersistent); m_Controls.descriptionLabel->setText(customizedDescription); m_Controls.descriptionLabel->show(); return; } } } m_Controls.descriptionLabel->hide(); this->HideAllIcons(); } void QmitkPropertyTreeView::OnPropertyListChanged(int index) { if (index == -1) return; QString renderer = m_Controls.propertyListComboBox->itemText(index); if (renderer.startsWith("Data node: ")) renderer = QString::fromStdString(renderer.toStdString().substr(11)); m_Renderer = nullptr; if (renderer != "common" && renderer != "Base data") { auto* renderWindowPart = this->GetRenderWindowPart(); if (nullptr != renderWindowPart) m_Renderer = renderWindowPart->GetQmitkRenderWindow(renderer)->GetRenderer(); } QList nodes; if (m_SelectedNode.IsNotNull()) nodes << m_SelectedNode; this->OnCurrentSelectionChanged(nodes); } void QmitkPropertyTreeView::OnAddNewProperty() { std::unique_ptr dialog(m_Controls.propertyListComboBox->currentText() != "Base data" ? new QmitkAddNewPropertyDialog(m_SelectedNode, m_Renderer) : new QmitkAddNewPropertyDialog(m_SelectedNode->GetData())); if (dialog->exec() == QDialog::Accepted) this->m_Model->Update(); } void QmitkPropertyTreeView::OnFilterTextChanged(const QString& filter) { m_ProxyModel->setFilterWildcard(filter); if (filter.isEmpty()) m_Controls.treeView->collapseAll(); else m_Controls.treeView->expandAll(); } void QmitkPropertyTreeView::OnModelReset() { m_Controls.descriptionLabel->hide(); this->HideAllIcons(); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_PickingTool.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_PickingTool.png index 78ccccd56f..0246c8ecd0 100644 Binary files a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_PickingTool.png and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_PickingTool.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationControls.ui index 1b84357ca9..09ab641aa5 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationControls.ui +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationControls.ui @@ -1,496 +1,529 @@ QmitkSegmentationControls 0 0 237 591 0 0 0 0 MS Shell Dlg 2 8 50 false false false false QmitkSegmentation QLayout::SetMinimumSize 6 6 6 6 0 0 Data Selection 6 6 6 6 QLayout::SetMinimumSize 4 0 0 - Patient Image + Selected Image 0 0 Segmentation 0 0 Create a new segmentation &New... :/segmentation/btnNew.png:/segmentation/btnNew.png Qt::ToolButtonTextOnly - - - - 0 - 40 - - - + + + + 0 + 40 + + + - - - - 0 - 40 - - - + + + + 0 + 40 + + + 0 0 200 0 0 200 0 0 200 0 0 + + + + 200 + 0 + 0 + + + 200 0 0 200 0 0 200 0 0 + + + + 200 + 0 + 0 + + + 84 82 78 84 82 78 84 82 78 + + + + 200 + 0 + 0 + + + + MS Shell Dlg 2 + 8 50 + false false + false + false Please load an image! true 0 0 Qt::LeftToRight QTabWidget::tab-bar { alignment: middle; } QTabWidget::North QTabWidget::Triangular 0 0 0 Qt::LeftToRight false 2D Tools 6 6 6 6 0 0 50 false 0 0 50 false 0 0 50 false Qt::Vertical 20 40 0 0 3D Tools 6 6 6 6 0 0 50 false 0 0 50 false Qt::Vertical 20 40 - - QmitkSingleNodeSelectionWidget - QWidget -
QmitkSingleNodeSelectionWidget.h
- 1 -
- + + QmitkSingleNodeSelectionWidget + QWidget +
QmitkSingleNodeSelectionWidget.h
+ 1 +
+ QmitkToolSelectionBox QWidget
QmitkToolSelectionBox.h
QmitkSlicesInterpolator QWidget
QmitkSlicesInterpolator.h
QmitkToolGUIArea QWidget
QmitkToolGUIArea.h
QmitkToolGUIArea.h QmitkToolSelectionBox.h QmitkSlicesInterpolator.h +
diff --git a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp index 7ab3ab1e54..b9b80e8e0b 100755 --- a/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp +++ b/Plugins/org.mitk.gui.qt.volumevisualization/src/internal/QmitkVolumeVisualizationView.cpp @@ -1,292 +1,293 @@ /*============================================================================ 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 "QmitkVolumeVisualizationView.h" #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkVolumeVisualizationView::VIEW_ID = "org.mitk.views.volumevisualization"; enum { DEFAULT_RENDERMODE = 0, RAYCAST_RENDERMODE = 1, GPU_RENDERMODE = 2 }; QmitkVolumeVisualizationView::QmitkVolumeVisualizationView() : QmitkAbstractView() , m_Controls(nullptr) { } void QmitkVolumeVisualizationView::SetFocus() { } void QmitkVolumeVisualizationView::CreateQtPartControl(QWidget* parent) { m_Controls = new Ui::QmitkVolumeVisualizationViewControls; m_Controls->setupUi(parent); m_Controls->volumeSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls->volumeSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateOr::New(mitk::NodePredicateDimension::New(3), mitk::NodePredicateDimension::New(4)), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls->volumeSelectionWidget->SetSelectionIsOptional(true); + m_Controls->volumeSelectionWidget->SetAutoSelectNewNodes(true); m_Controls->volumeSelectionWidget->SetEmptyInfo(QString("Please select a 3D / 4D image volume")); m_Controls->volumeSelectionWidget->SetPopUpTitel(QString("Select image volume")); // Fill the transfer function presets in the generator widget std::vector names; mitk::TransferFunctionInitializer::GetPresetNames(names); for (const auto& name : names) { m_Controls->transferFunctionGeneratorWidget->AddPreset(QString::fromStdString(name)); } // see enum in vtkSmartVolumeMapper m_Controls->renderMode->addItem("Default"); m_Controls->renderMode->addItem("RayCast"); m_Controls->renderMode->addItem("GPU"); // see vtkVolumeMapper::BlendModes m_Controls->blendMode->addItem("Comp"); m_Controls->blendMode->addItem("Max"); m_Controls->blendMode->addItem("Min"); m_Controls->blendMode->addItem("Avg"); m_Controls->blendMode->addItem("Add"); connect(m_Controls->volumeSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkVolumeVisualizationView::OnCurrentSelectionChanged); connect(m_Controls->enableRenderingCB, SIGNAL(toggled(bool)), this, SLOT(OnEnableRendering(bool))); connect(m_Controls->renderMode, SIGNAL(activated(int)), this, SLOT(OnRenderMode(int))); connect(m_Controls->blendMode, SIGNAL(activated(int)), this, SLOT(OnBlendMode(int))); connect(m_Controls->transferFunctionGeneratorWidget, SIGNAL(SignalUpdateCanvas()), m_Controls->transferFunctionWidget, SLOT(OnUpdateCanvas())); connect(m_Controls->transferFunctionGeneratorWidget, SIGNAL(SignalTransferFunctionModeChanged(int)), SLOT(OnMitkInternalPreset(int))); m_Controls->enableRenderingCB->setEnabled(false); m_Controls->blendMode->setEnabled(false); m_Controls->renderMode->setEnabled(false); m_Controls->transferFunctionWidget->setEnabled(false); m_Controls->transferFunctionGeneratorWidget->setEnabled(false); m_Controls->volumeSelectionWidget->SetAutoSelectNewNodes(true); } void QmitkVolumeVisualizationView::OnMitkInternalPreset(int mode) { if (m_SelectedNode.IsExpired()) { return; } auto node = m_SelectedNode.Lock(); mitk::TransferFunctionProperty::Pointer transferFuncProp; if (node->GetProperty(transferFuncProp, "TransferFunction")) { // first item is only information if (--mode == -1) return; // -- Creat new TransferFunction mitk::TransferFunctionInitializer::Pointer tfInit = mitk::TransferFunctionInitializer::New(transferFuncProp->GetValue()); tfInit->SetTransferFunctionMode(mode); RequestRenderWindowUpdate(); m_Controls->transferFunctionWidget->OnUpdateCanvas(); } } void QmitkVolumeVisualizationView::OnCurrentSelectionChanged(QList nodes) { m_SelectedNode = nullptr; if (nodes.empty() || nodes.front().IsNull()) { UpdateInterface(); return; } auto selectedNode = nodes.front(); auto image = dynamic_cast(selectedNode->GetData()); if (nullptr != image) { m_SelectedNode = selectedNode; } UpdateInterface(); } void QmitkVolumeVisualizationView::OnEnableRendering(bool state) { if (m_SelectedNode.IsExpired()) { return; } m_SelectedNode.Lock()->SetProperty("volumerendering", mitk::BoolProperty::New(state)); UpdateInterface(); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::OnRenderMode(int mode) { if (m_SelectedNode.IsExpired()) { return; } auto selectedNode = m_SelectedNode.Lock(); bool usegpu = false; bool useray = false; if (DEFAULT_RENDERMODE == mode) { useray = true; usegpu = true; } else if (GPU_RENDERMODE == mode) { usegpu = true; } else if (RAYCAST_RENDERMODE == mode) { useray = true; } selectedNode->SetProperty("volumerendering.usegpu", mitk::BoolProperty::New(usegpu)); selectedNode->SetProperty("volumerendering.useray", mitk::BoolProperty::New(useray)); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::OnBlendMode(int mode) { if (m_SelectedNode.IsExpired()) { return; } auto selectedNode = m_SelectedNode.Lock(); bool usemip = false; if (vtkVolumeMapper::MAXIMUM_INTENSITY_BLEND == mode) { usemip = true; } selectedNode->SetProperty("volumerendering.usemip", mitk::BoolProperty::New(usemip)); selectedNode->SetProperty("volumerendering.blendmode", mitk::IntProperty::New(mode)); RequestRenderWindowUpdate(); } void QmitkVolumeVisualizationView::UpdateInterface() { if (m_SelectedNode.IsExpired()) { // turnoff all m_Controls->enableRenderingCB->setChecked(false); m_Controls->enableRenderingCB->setEnabled(false); m_Controls->blendMode->setCurrentIndex(0); m_Controls->blendMode->setEnabled(false); m_Controls->renderMode->setCurrentIndex(0); m_Controls->renderMode->setEnabled(false); m_Controls->transferFunctionWidget->SetDataNode(nullptr); m_Controls->transferFunctionWidget->setEnabled(false); m_Controls->transferFunctionGeneratorWidget->SetDataNode(nullptr); m_Controls->transferFunctionGeneratorWidget->setEnabled(false); return; } bool enabled = false; auto selectedNode = m_SelectedNode.Lock(); selectedNode->GetBoolProperty("volumerendering", enabled); m_Controls->enableRenderingCB->setEnabled(true); m_Controls->enableRenderingCB->setChecked(enabled); if (!enabled) { // turnoff all except volumerendering checkbox m_Controls->blendMode->setCurrentIndex(0); m_Controls->blendMode->setEnabled(false); m_Controls->renderMode->setCurrentIndex(0); m_Controls->renderMode->setEnabled(false); m_Controls->transferFunctionWidget->SetDataNode(nullptr); m_Controls->transferFunctionWidget->setEnabled(false); m_Controls->transferFunctionGeneratorWidget->SetDataNode(nullptr); m_Controls->transferFunctionGeneratorWidget->setEnabled(false); return; } // otherwise we can activate em all m_Controls->blendMode->setEnabled(true); m_Controls->renderMode->setEnabled(true); // Determine Combo Box mode { bool usegpu = false; bool useray = false; bool usemip = false; selectedNode->GetBoolProperty("volumerendering.usegpu", usegpu); selectedNode->GetBoolProperty("volumerendering.useray", useray); selectedNode->GetBoolProperty("volumerendering.usemip", usemip); int blendMode; if (selectedNode->GetIntProperty("volumerendering.blendmode", blendMode)) m_Controls->blendMode->setCurrentIndex(blendMode); if (usemip) m_Controls->blendMode->setCurrentIndex(vtkVolumeMapper::MAXIMUM_INTENSITY_BLEND); int mode = DEFAULT_RENDERMODE; if (useray) mode = RAYCAST_RENDERMODE; else if (usegpu) mode = GPU_RENDERMODE; m_Controls->renderMode->setCurrentIndex(mode); } m_Controls->transferFunctionWidget->SetDataNode(selectedNode); m_Controls->transferFunctionWidget->setEnabled(true); m_Controls->transferFunctionGeneratorWidget->SetDataNode(selectedNode); m_Controls->transferFunctionGeneratorWidget->setEnabled(true); }