diff --git a/CMake/Findlz4.cmake b/CMake/Findlz4.cmake new file mode 100644 index 0000000000..e9c68dccc4 --- /dev/null +++ b/CMake/Findlz4.cmake @@ -0,0 +1,35 @@ +find_path(lz4_INCLUDE_DIR lz4.h + PATHS ${MITK_EXTERNAL_PROJECT_PREFIX} + PATH_SUFFIXES include + NO_DEFAULT_PATH +) +mark_as_advanced(lz4_INCLUDE_DIR) + + +find_library(lz4_LIBRARY_RELEASE lz4 + PATHS ${MITK_EXTERNAL_PROJECT_PREFIX} + PATH_SUFFIXES lib lib64 + NO_DEFAULT_PATH) +mark_as_advanced(lz4_LIBRARY_RELEASE) + + +find_library(lz4_LIBRARY_DEBUG lz4d + PATHS ${MITK_EXTERNAL_PROJECT_PREFIX} + PATH_SUFFIXES lib lib64 + NO_DEFAULT_PATH) +mark_as_advanced(lz4_LIBRARY_DEBUG) + +set(lz4_LIBRARIES) + +if(lz4_LIBRARY_RELEASE) + list(APPEND lz4_LIBRARIES optimized ${lz4_LIBRARY_RELEASE}) +endif() + +if(lz4_LIBRARY_DEBUG) + list(APPEND lz4_LIBRARIES debug ${lz4_LIBRARY_DEBUG}) +endif() + +find_package_handle_standard_args(lz4 + FOUND_VAR lz4_FOUND + REQUIRED_VARS lz4_INCLUDE_DIR lz4_LIBRARIES +) diff --git a/CMake/PackageDepends/MITK_lz4_Config.cmake b/CMake/PackageDepends/MITK_lz4_Config.cmake new file mode 100644 index 0000000000..b9b2b8ff86 --- /dev/null +++ b/CMake/PackageDepends/MITK_lz4_Config.cmake @@ -0,0 +1,2 @@ +list(APPEND ALL_INCLUDE_DIRECTORIES ${lz4_INCLUDE_DIR}) +list(APPEND ALL_LIBRARIES ${lz4_LIBRARIES}) diff --git a/CMakeExternals/ExternalProjectList.cmake b/CMakeExternals/ExternalProjectList.cmake index 67c827c45b..e275f8b0f4 100644 --- a/CMakeExternals/ExternalProjectList.cmake +++ b/CMakeExternals/ExternalProjectList.cmake @@ -1,31 +1,32 @@ mitkFunctionAddExternalProject(NAME Poco ON COMPONENTS Foundation Net Util XML Zip) mitkFunctionAddExternalProject(NAME DCMTK ON DOC "EXPERIMENTAL, superbuild only: Use DCMTK in MITK") mitkFunctionAddExternalProject(NAME OpenIGTLink OFF) mitkFunctionAddExternalProject(NAME tinyxml2 ON ADVANCED) mitkFunctionAddExternalProject(NAME GDCM ON ADVANCED) mitkFunctionAddExternalProject(NAME Eigen ON ADVANCED DOC "Use the Eigen library") mitkFunctionAddExternalProject(NAME ANN ON ADVANCED DOC "Use Approximate Nearest Neighbor Library") mitkFunctionAddExternalProject(NAME CppUnit ON ADVANCED DOC "Use CppUnit for unit tests") mitkFunctionAddExternalProject(NAME HDF5 ON ADVANCED) mitkFunctionAddExternalProject(NAME OpenCV OFF) mitkFunctionAddExternalProject(NAME Vigra OFF DEPENDS HDF5) mitkFunctionAddExternalProject(NAME ITK ON NO_CACHE DEPENDS HDF5) mitkFunctionAddExternalProject(NAME VTK ON NO_CACHE) mitkFunctionAddExternalProject(NAME Boost ON NO_CACHE) mitkFunctionAddExternalProject(NAME ZLIB OFF ADVANCED) +mitkFunctionAddExternalProject(NAME lz4 ON ADVANCED) mitkFunctionAddExternalProject(NAME cpprestsdk OFF DEPENDS Boost ZLIB ADVANCED) mitkFunctionAddExternalProject(NAME OpenMesh OFF) mitkFunctionAddExternalProject(NAME 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 AND NOT APPLE) mitkFunctionAddExternalProject(NAME PCRE OFF ADVANCED NO_PACKAGE) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE DEPENDS PCRE) elseif(WIN32) mitkFunctionAddExternalProject(NAME SWIG OFF ADVANCED NO_PACKAGE) endif() diff --git a/CMakeExternals/lz4.cmake b/CMakeExternals/lz4.cmake new file mode 100644 index 0000000000..5f63e12c42 --- /dev/null +++ b/CMakeExternals/lz4.cmake @@ -0,0 +1,54 @@ +#----------------------------------------------------------------------------- +# lz4 +#----------------------------------------------------------------------------- + +if(MITK_USE_lz4) + # Sanity checks + if(DEFINED lz4_DIR AND NOT EXISTS "${lz4_DIR}") + message(FATAL_ERROR "lz4_DIR variable is defined but corresponds to non-existing directory") + endif() + + set(proj lz4) + set(proj_DEPENDENCIES ) + set(lz4_DEPENDS ${proj}) + + if(NOT DEFINED lz4_DIR) + + set(additional_args ) + + if(NOT CMAKE_DEBUG_POSTFIX) + list(APPEND additional_args "-DCMAKE_DEBUG_POSTFIX:STRING=d") + endif() + + if(CTEST_USE_LAUNCHERS) + list(APPEND additional_args + "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" + ) + endif() + + ExternalProject_Add(${proj} + LIST_SEPARATOR ${sep} + GIT_REPOSITORY https://github.com/lz4/lz4.git + GIT_TAG v1.9.3 + SOURCE_SUBDIR build/cmake + CMAKE_GENERATOR ${gen} + CMAKE_GENERATOR_PLATFORM ${gen_platform} + CMAKE_ARGS + ${ep_common_args} + ${additional_args} + CMAKE_CACHE_ARGS + ${ep_common_cache_args} + -DLZ4_BUILD_CLI:BOOL=OFF + -DLZ4_BUILD_LEGACY_LZ4C:BOOL=OFF + CMAKE_CACHE_DEFAULT_ARGS + ${ep_common_cache_default_args} + DEPENDS ${proj_DEPENDENCIES} + ) + + set(lz4_DIR "${ep_prefix}") + mitkFunctionInstallExternalCMakeProject(${proj}) + + else() + mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") + endif() +endif() diff --git a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox index 2b93804d69..5d760fedaa 100644 --- a/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox +++ b/Documentation/Doxygen/3-DeveloperManual/Starting/SettingUpMITK/ThirdPartyLibs.dox @@ -1,107 +1,111 @@ /** \page thirdpartylibs Third-party libraries The following third-party libraries can be used with MITK by default and can, in part, be automatically downloaded during superbuild. \par ANN https://www.cs.umd.edu/~mount/ANN/ \par Boost https://www.boost.org/ \par C++ REST SDK https://github.com/Microsoft/cpprestsdk/ \par CppUnit https://sourceforge.net/projects/cppunit/ \par CTK https://commontk.org/ \par DCMTK https://dicom.offis.de/dcmtk \par Eigen http://eigen.tuxfamily.org/index.php?title=Main_Page \par GDCM https://gdcm.sourceforge.net/ \par HDF5 https://support.hdfgroup.org/HDF5/ \par ITK https://itk.org/ +\par lz4 + +https://github.com/lz4/lz4 + \par MatchPoint https://www.dkfz.de/en/sidt/projects/MatchPoint/info.html \par OpenCL https://www.khronos.org/opencl/ \par OpenCV https://opencv.org/ \par OpenIGTLink http://openigtlink.org/ \par OpenMesh https://www.openmesh.org/ \par PCRE https://www.pcre.org/ \par POCO https://pocoproject.org/ \par Python https://www.python.org/ \par Qt https://www.qt.io/ \par Qwt http://qwt.sourceforge.net/ \par SWIG http://www.swig.org/ \par TinyXML-2 http://www.grinninglizard.com/tinyxml2/ \par VIGRA https://ukoethe.github.io/vigra/ \par VTK https://vtk.org/ \par zlib https://zlib.net/ For copyright information on any of the above toolkits see the corresponding home page or the corresponding source folder. */ diff --git a/Modules/Annotation/src/mitkColorBarAnnotation.cpp b/Modules/Annotation/src/mitkColorBarAnnotation.cpp index 31ee59f04b..cb3cf0273d 100644 --- a/Modules/Annotation/src/mitkColorBarAnnotation.cpp +++ b/Modules/Annotation/src/mitkColorBarAnnotation.cpp @@ -1,186 +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. ============================================================================*/ #include "mitkColorBarAnnotation.h" #include "mitkLookupTable.h" #include "mitkLookupTableProperty.h" #include mitk::ColorBarAnnotation::ColorBarAnnotation() { SetDrawAnnotations(true); SetDrawTickLabels(true); SetOrientationToVertical(); SetMaxNumberOfColors(100); SetNumberOfLabels(4); SetAnnotationTextScaling(false); SetLookupTable(nullptr); } mitk::ColorBarAnnotation::~ColorBarAnnotation() { for (BaseRenderer *renderer : m_LSH.GetRegisteredBaseRenderer()) { if (renderer) { this->RemoveFromBaseRenderer(renderer); } } } mitk::ColorBarAnnotation::LocalStorage::~LocalStorage() { } mitk::ColorBarAnnotation::LocalStorage::LocalStorage() { m_ScalarBarActor = vtkSmartPointer::New(); } void mitk::ColorBarAnnotation::UpdateVtkAnnotation(mitk::BaseRenderer *renderer) { LocalStorage *ls = this->m_LSH.GetLocalStorage(renderer); if (ls->IsGenerateDataRequired(renderer, this)) { ls->m_ScalarBarActor->SetDrawAnnotations(this->GetDrawAnnotations()); ls->m_ScalarBarActor->SetLookupTable(this->GetLookupTable()); ls->m_ScalarBarActor->SetOrientation(this->GetOrientation()); ls->m_ScalarBarActor->SetDrawTickLabels(this->GetDrawTickLabels()); ls->m_ScalarBarActor->SetMaximumNumberOfColors(this->GetMaxNumberOfColors()); ls->m_ScalarBarActor->SetNumberOfLabels(this->GetNumberOfLabels()); ls->m_ScalarBarActor->SetAnnotationTextScaling(this->GetAnnotationTextScaling()); // manually set position so there is no overlap with mitk logo in 3d renderwindow if (this->GetOrientation() == 1) { - ls->m_ScalarBarActor->SetPosition(0.80, 0.15); + ls->m_ScalarBarActor->SetPosition(0.80, 0.10); ls->m_ScalarBarActor->SetWidth(0.15); ls->m_ScalarBarActor->SetHeight(0.85); } else { ls->m_ScalarBarActor->SetPosition(0.03, 0.03); ls->m_ScalarBarActor->SetWidth(0.8); ls->m_ScalarBarActor->SetHeight(0.15); } } } vtkProp *mitk::ColorBarAnnotation::GetVtkProp(BaseRenderer *renderer) const { LocalStorage *ls = this->m_LSH.GetLocalStorage(renderer); return ls->m_ScalarBarActor; } void mitk::ColorBarAnnotation::SetDrawAnnotations(bool annotations) { SetBoolProperty("ColorBarAnnotation.DrawAnnotations", annotations); } bool mitk::ColorBarAnnotation::GetDrawAnnotations() const { bool annotations; GetPropertyList()->GetBoolProperty("ColorBarAnnotation.DrawAnnotations", annotations); return annotations; } void mitk::ColorBarAnnotation::SetLookupTable(vtkSmartPointer table) { mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); lut->SetVtkLookupTable(table); prop->SetLookupTable(lut); SetProperty("ColorBarAnnotation.LookupTable", prop.GetPointer()); } vtkSmartPointer mitk::ColorBarAnnotation::GetLookupTable() const { mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut = dynamic_cast(GetPropertyList()->GetProperty("ColorBarAnnotation.LookupTable")) ->GetLookupTable(); return lut->GetVtkLookupTable(); } void mitk::ColorBarAnnotation::SetOrientation(int orientation) { SetIntProperty("ColorBarAnnotation.Orientation", orientation); } int mitk::ColorBarAnnotation::GetOrientation() const { int orientation; GetPropertyList()->GetIntProperty("ColorBarAnnotation.Orientation", orientation); return orientation; } void mitk::ColorBarAnnotation::SetDrawTickLabels(bool ticks) { SetBoolProperty("ColorBarAnnotation.DrawTicks", ticks); } bool mitk::ColorBarAnnotation::GetDrawTickLabels() const { bool ticks; GetPropertyList()->GetBoolProperty("ColorBarAnnotation.DrawTicks", ticks); return ticks; } void mitk::ColorBarAnnotation::SetOrientationToHorizontal() { SetOrientation(0); } void mitk::ColorBarAnnotation::SetOrientationToVertical() { SetOrientation(1); } void mitk::ColorBarAnnotation::SetMaxNumberOfColors(int numberOfColors) { SetIntProperty("ColorBarAnnotation.MaximumNumberOfColors", numberOfColors); } int mitk::ColorBarAnnotation::GetMaxNumberOfColors() const { int numberOfColors; GetPropertyList()->GetIntProperty("ColorBarAnnotation.MaximumNumberOfColors", numberOfColors); return numberOfColors; } void mitk::ColorBarAnnotation::SetNumberOfLabels(int numberOfLabels) { SetIntProperty("ColorBarAnnotation.NumberOfLabels", numberOfLabels); } int mitk::ColorBarAnnotation::GetNumberOfLabels() const { int numberOfLabels; GetPropertyList()->GetIntProperty("ColorBarAnnotation.NumberOfLabels", numberOfLabels); return numberOfLabels; } void mitk::ColorBarAnnotation::SetAnnotationTextScaling(bool scale) { SetBoolProperty("ColorBarAnnotation.ScaleAnnotationText", scale); } bool mitk::ColorBarAnnotation::GetAnnotationTextScaling() const { bool scale; GetPropertyList()->GetBoolProperty("ColorBarAnnotation.ScaleAnnotationText", scale); return scale; } diff --git a/Modules/BoundingShape/src/Interactions/mitkBoundingShapeInteractor.cpp b/Modules/BoundingShape/src/Interactions/mitkBoundingShapeInteractor.cpp index 3b7b0340bf..ce24457391 100644 --- a/Modules/BoundingShape/src/Interactions/mitkBoundingShapeInteractor.cpp +++ b/Modules/BoundingShape/src/Interactions/mitkBoundingShapeInteractor.cpp @@ -1,609 +1,626 @@ /*============================================================================ 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 "../DataManagement/mitkBoundingShapeUtil.h" #include #include +#include #include #include #include #include #include #include #include #include #include #include #include #include "usGetModuleContext.h" #include "usModuleRegistry.h" // Properties to allow the user to interact with the base data const char *selectedColorPropertyName = "Bounding Shape.Selected Color"; const char *deselectedColorPropertyName = "Bounding Shape.Deselected Color"; const char *activeHandleIdPropertyName = "Bounding Shape.Active Handle ID"; const char *boundingShapePropertyName = "Bounding Shape"; namespace mitk { class BoundingShapeInteractor::Impl { public: Impl() : ScrollEnabled(false), RotationEnabled(false) { Point3D initialPoint; initialPoint.Fill(0.0); for (int i = 0; i < 6; ++i) Handles.push_back(Handle(initialPoint, i, GetHandleIndices(i))); } ~Impl() {} bool ScrollEnabled; Point3D InitialPickedWorldPoint; Point3D LastPickedWorldPoint; Point2D InitialPickedDisplayPoint; std::vector Handles; Handle ActiveHandle; Geometry3D::Pointer OriginalGeometry; bool RotationEnabled; std::map DisplayInteractorConfigs; }; } mitk::BoundingShapeInteractor::BoundingShapeInteractor() : m_Impl(new Impl) { } mitk::BoundingShapeInteractor::~BoundingShapeInteractor() { this->RestoreNodeProperties(); delete m_Impl; } void mitk::BoundingShapeInteractor::ConnectActionsAndFunctions() { // **Conditions** that can be used in the state machine, to ensure that certain conditions are met, before actually // executing an action CONNECT_CONDITION("isHoveringOverObject", CheckOverObject); CONNECT_CONDITION("isHoveringOverHandles", CheckOverHandles); // **Function** in the statemachine patterns also referred to as **Actions** CONNECT_FUNCTION("selectObject", SelectObject); CONNECT_FUNCTION("deselectObject", DeselectObject); CONNECT_FUNCTION("deselectHandles", DeselectHandles); CONNECT_FUNCTION("initInteraction", InitInteraction); CONNECT_FUNCTION("translateObject", TranslateObject); CONNECT_FUNCTION("selectHandle", SelectHandle); CONNECT_FUNCTION("scaleObject", ScaleObject); // CONNECT_FUNCTION("rotateObject",RotateObject); } // RotateObject(StateMachineAction*, InteractionEvent* interactionEvent) // void mitk::BoundingShapeInteractor::RotateGeometry(mitk::ScalarType angle, int rotationaxis, mitk::BaseGeometry* // geometry) //{ // mitk::Vector3D rotationAxis = geometry->GetAxisVector(rotationaxis); // float pointX = 0.0f; // float pointY = 0.0f; // float pointZ = 0.0f; // mitk::Point3D pointOfRotation; // pointOfRotation.Fill(0.0); // this->GetDataNode()->GetFloatProperty(anchorPointX, pointX); // this->GetDataNode()->GetFloatProperty(anchorPointY, pointY); // this->GetDataNode()->GetFloatProperty(anchorPointZ, pointZ); // pointOfRotation[0] = pointX; // pointOfRotation[1] = pointY; // pointOfRotation[2] = pointZ; // // mitk::RotationOperation* doOp = new mitk::RotationOperation(OpROTATE, pointOfRotation, rotationAxis, angle); // // geometry->ExecuteOperation(doOp); // delete doOp; //} void mitk::BoundingShapeInteractor::SetRotationEnabled(bool rotationEnabled) { m_Impl->RotationEnabled = rotationEnabled; } void mitk::BoundingShapeInteractor::DataNodeChanged() { mitk::DataNode::Pointer newInputNode = this->GetDataNode(); if (newInputNode == nullptr) return; // add color properties mitk::ColorProperty::Pointer selectedColor = dynamic_cast(newInputNode->GetProperty(selectedColorPropertyName)); mitk::ColorProperty::Pointer deselectedColor = dynamic_cast(newInputNode->GetProperty(deselectedColorPropertyName)); if (selectedColor.IsNull()) newInputNode->AddProperty(selectedColorPropertyName, mitk::ColorProperty::New(0.0, 1.0, 0.0)); if (deselectedColor.IsNull()) newInputNode->AddProperty(deselectedColorPropertyName, mitk::ColorProperty::New(1.0, 1.0, 1.0)); newInputNode->SetProperty(boundingShapePropertyName, mitk::BoolProperty::New(true)); newInputNode->AddProperty(activeHandleIdPropertyName, mitk::IntProperty::New(-1)); newInputNode->SetProperty("layer", mitk::IntProperty::New(101)); newInputNode->SetBoolProperty("fixedLayer", mitk::BoolProperty::New(true)); newInputNode->SetBoolProperty("pickable", true); mitk::ColorProperty::Pointer initialColor = dynamic_cast(newInputNode->GetProperty(deselectedColorPropertyName)); if (initialColor.IsNotNull()) { newInputNode->SetColor(initialColor->GetColor()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::BoundingShapeInteractor::HandlePositionChanged(const InteractionEvent *interactionEvent, Point3D ¢er) { GeometryData::Pointer geometryData = dynamic_cast(this->GetDataNode()->GetData()); int timeStep = interactionEvent->GetSender()->GetTimeStep(this->GetDataNode()->GetData()); mitk::BaseGeometry::Pointer geometry = geometryData->GetGeometry(timeStep); std::vector cornerPoints = GetCornerPoints(geometry, true); if (m_Impl->Handles.size() == 6) { // set handle positions Point3D pointLeft = CalcAvgPoint(cornerPoints[5], cornerPoints[6]); Point3D pointRight = CalcAvgPoint(cornerPoints[1], cornerPoints[2]); Point3D pointTop = CalcAvgPoint(cornerPoints[0], cornerPoints[6]); Point3D pointBottom = CalcAvgPoint(cornerPoints[7], cornerPoints[1]); Point3D pointFront = CalcAvgPoint(cornerPoints[2], cornerPoints[7]); Point3D pointBack = CalcAvgPoint(cornerPoints[4], cornerPoints[1]); m_Impl->Handles[0].SetPosition(pointLeft); m_Impl->Handles[1].SetPosition(pointRight); m_Impl->Handles[2].SetPosition(pointTop); m_Impl->Handles[3].SetPosition(pointBottom); m_Impl->Handles[4].SetPosition(pointFront); m_Impl->Handles[5].SetPosition(pointBack); // calculate center based on half way of the distance between two opposing cornerpoints center = CalcAvgPoint(cornerPoints[7], cornerPoints[0]); } } void mitk::BoundingShapeInteractor::SetDataNode(DataNode *node) { this->RestoreNodeProperties(); // if there is another node set, restore it's color if (node == nullptr) return; DataInteractor::SetDataNode(node); // calls DataNodeChanged internally this->DataNodeChanged(); } bool mitk::BoundingShapeInteractor::CheckOverObject(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return false; GeometryData::Pointer geometryData = dynamic_cast(this->GetDataNode()->GetData()); int timeStep = interactionEvent->GetSender()->GetTimeStep(this->GetDataNode()->GetData()); BaseGeometry::Pointer geometry = geometryData->GetGeometry(timeStep); // calculates translation based on offset+extent not on the transformation matrix (because the cube is located in the // center not in the origin) vtkSmartPointer imageTransform = geometry->GetVtkTransform()->GetMatrix(); Point3D center = geometry->GetCenter(); auto translation = vtkSmartPointer::New(); auto transform = vtkSmartPointer::New(); translation->Translate(center[0] - imageTransform->GetElement(0, 3), center[1] - imageTransform->GetElement(1, 3), center[2] - imageTransform->GetElement(2, 3)); transform->SetMatrix(imageTransform); transform->PostMultiply(); transform->Concatenate(translation); transform->Update(); mitk::Vector3D extent; for (unsigned int i = 0; i < 3; ++i) extent[i] = (geometry->GetExtent(i)); Point3D currentWorldPosition; Point2D currentDisplayPosition = positionEvent->GetPointerPositionOnScreen(); interactionEvent->GetSender()->DisplayToWorld(currentDisplayPosition, currentWorldPosition); ScalarType transformedPosition[4]; transformedPosition[0] = currentWorldPosition[0]; transformedPosition[1] = currentWorldPosition[1]; transformedPosition[2] = currentWorldPosition[2]; transformedPosition[3] = 1; // transform point from world to object coordinates transform->GetInverse()->TransformPoint(transformedPosition, transformedPosition); // check if the world point is within bounds bool isInside = (transformedPosition[0] >= (-extent[0] / 2.0)) && (transformedPosition[0] <= (extent[0] / 2.0)) && (transformedPosition[1] >= (-extent[1] / 2.0)) && (transformedPosition[1] <= (extent[1] / 2.0)) && (transformedPosition[2] >= (-extent[2] / 2.0)) && (transformedPosition[2] <= (extent[2] / 2.0)); return isInside; } bool mitk::BoundingShapeInteractor::CheckOverHandles(const InteractionEvent *interactionEvent) { Point3D boundingBoxCenter; HandlePositionChanged(interactionEvent, boundingBoxCenter); const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return false; Point2D displayCenterPoint; // to do: change to actual time step (currently not necessary because geometry remains the same for each timestep int timeStep = 0; GeometryData::Pointer geometryData = dynamic_cast(this->GetDataNode()->GetData()); BaseGeometry::Pointer geometry = geometryData->GetUpdatedTimeGeometry()->GetGeometryForTimeStep(timeStep); std::vector cornerPoints = GetCornerPoints(geometry, true); interactionEvent->GetSender()->WorldToDisplay(boundingBoxCenter, displayCenterPoint); double scale = interactionEvent->GetSender()->GetScaleFactorMMPerDisplayUnit(); // GetDisplaySizeInMM mitk::DoubleProperty::Pointer handleSizeProperty = dynamic_cast(this->GetDataNode()->GetProperty("Bounding Shape.Handle Size Factor")); ScalarType initialHandleSize; if (handleSizeProperty != nullptr) initialHandleSize = handleSizeProperty->GetValue(); else initialHandleSize = 1.0 / 40.0; mitk::Point2D displaysize = interactionEvent->GetSender()->GetDisplaySizeInMM(); ScalarType handlesize = ((displaysize[0] + displaysize[1]) / 2.0) * initialHandleSize; unsigned int handleNum = 0; for (auto &handle : m_Impl->Handles) { Point2D centerpoint; interactionEvent->GetSender()->WorldToDisplay(handle.GetPosition(), centerpoint); Point2D currentDisplayPosition = positionEvent->GetPointerPositionOnScreen(); if ((currentDisplayPosition.EuclideanDistanceTo(centerpoint) < (handlesize / scale)) && (currentDisplayPosition.EuclideanDistanceTo(displayCenterPoint) > (handlesize / scale))) // check if mouse is hovering over center point { handle.SetActive(true); m_Impl->ActiveHandle = handle; this->GetDataNode()->GetPropertyList()->SetProperty(activeHandleIdPropertyName, mitk::IntProperty::New(handleNum++)); this->GetDataNode()->GetData()->Modified(); RenderingManager::GetInstance()->RequestUpdateAll(); return true; } else { handleNum++; handle.SetActive(false); } this->GetDataNode()->GetPropertyList()->SetProperty(activeHandleIdPropertyName, mitk::IntProperty::New(-1)); } return false; } void mitk::BoundingShapeInteractor::SelectHandle(StateMachineAction *, InteractionEvent *) { this->DisableCrosshairNavigation(); DataNode::Pointer node = this->GetDataNode(); if (node.IsNull()) return; mitk::ColorProperty::Pointer selectedColor = dynamic_cast(node->GetProperty(deselectedColorPropertyName)); if (selectedColor.IsNotNull()) { this->GetDataNode()->GetPropertyList()->SetProperty("color", selectedColor); } this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::BoundingShapeInteractor::DeselectHandles(StateMachineAction *, InteractionEvent *) { this->DisableCrosshairNavigation(); DataNode::Pointer node = this->GetDataNode(); if (node.IsNull()) return; this->GetDataNode()->GetPropertyList()->SetProperty(activeHandleIdPropertyName, mitk::IntProperty::New(-1)); this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::BoundingShapeInteractor::SelectObject(StateMachineAction *, InteractionEvent *) { this->DisableCrosshairNavigation(); // disable crosshair interaction and scolling if user is hovering over the object DataNode::Pointer node = this->GetDataNode(); if (node.IsNull()) return; mitk::ColorProperty::Pointer selectedColor = dynamic_cast(node->GetProperty(selectedColorPropertyName)); if (selectedColor.IsNotNull()) { node->GetPropertyList()->SetProperty("color", selectedColor); } this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::BoundingShapeInteractor::DeselectObject(StateMachineAction *, InteractionEvent *) { this->EnableCrosshairNavigation(); // enable crosshair interaction and scolling if user is hovering over the object DataNode::Pointer node = this->GetDataNode(); if (node.IsNull()) return; mitk::ColorProperty::Pointer deselectedColor = dynamic_cast(node->GetProperty(deselectedColorPropertyName)); if (deselectedColor.IsNotNull()) { node->GetPropertyList()->SetProperty("color", deselectedColor); } this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::BoundingShapeInteractor::InitInteraction(StateMachineAction *, InteractionEvent *interactionEvent) { InitMembers(interactionEvent); } bool mitk::BoundingShapeInteractor::InitMembers(InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return false; // get initial position coordinates m_Impl->InitialPickedDisplayPoint = positionEvent->GetPointerPositionOnScreen(); m_Impl->InitialPickedWorldPoint = positionEvent->GetPositionInWorld(); m_Impl->LastPickedWorldPoint = positionEvent->GetPositionInWorld(); return true; } void mitk::BoundingShapeInteractor::TranslateObject(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; int timeStep = interactionEvent->GetSender()->GetTimeStep(this->GetDataNode()->GetData()); mitk::BaseGeometry::Pointer geometry = this->GetDataNode()->GetData()->GetUpdatedTimeGeometry()->GetGeometryForTimeStep(timeStep); Vector3D spacing = geometry->GetSpacing(); Point3D currentPickedPoint; interactionEvent->GetSender()->DisplayToWorld(positionEvent->GetPointerPositionOnScreen(), currentPickedPoint); Vector3D interactionMove; // pixel aligned shifting of the bounding box interactionMove[0] = std::round((currentPickedPoint[0] - m_Impl->LastPickedWorldPoint[0]) / spacing[0]) * spacing[0]; interactionMove[1] = std::round((currentPickedPoint[1] - m_Impl->LastPickedWorldPoint[1]) / spacing[1]) * spacing[1]; interactionMove[2] = std::round((currentPickedPoint[2] - m_Impl->LastPickedWorldPoint[2]) / spacing[2]) * spacing[2]; if ((interactionMove[0] + interactionMove[1] + interactionMove[2]) != 0.0) // only update current position if a movement occured { m_Impl->LastPickedWorldPoint = currentPickedPoint; geometry->SetOrigin(geometry->GetOrigin() + interactionMove); this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } return; } void mitk::BoundingShapeInteractor::ScaleObject(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; GeometryData::Pointer geometryData = dynamic_cast(this->GetDataNode()->GetData()); Point3D handlePickedPoint = m_Impl->ActiveHandle.GetPosition(); Point3D currentPickedPoint; interactionEvent->GetSender()->DisplayToWorld(positionEvent->GetPointerPositionOnScreen(), currentPickedPoint); int timeStep = interactionEvent->GetSender()->GetTimeStep(this->GetDataNode()->GetData()); mitk::BaseGeometry::Pointer geometry = geometryData->GetGeometry(timeStep); Vector3D spacing = geometry->GetSpacing(); // pixel aligned bounding box Vector3D interactionMove; interactionMove[0] = (currentPickedPoint[0] - m_Impl->LastPickedWorldPoint[0]); interactionMove[1] = (currentPickedPoint[1] - m_Impl->LastPickedWorldPoint[1]); interactionMove[2] = (currentPickedPoint[2] - m_Impl->LastPickedWorldPoint[2]); std::vector faces = m_Impl->ActiveHandle.GetFaceIndices(); auto pointscontainer = mitk::BoundingBox::PointsContainer::New(); // calculate cornerpoints from geometry plus visualization offset std::vector cornerPoints = GetCornerPoints(geometry, true); unsigned int num = 0; for (const auto &point : cornerPoints) { pointscontainer->InsertElement(num++, point); } // calculate center based on half way of the distance between two opposing cornerpoints mitk::Point3D center = CalcAvgPoint(cornerPoints[7], cornerPoints[0]); Vector3D faceNormal; faceNormal[0] = handlePickedPoint[0] - center[0]; faceNormal[1] = handlePickedPoint[1] - center[1]; faceNormal[2] = handlePickedPoint[2] - center[2]; Vector3D faceShift = ((faceNormal * interactionMove) / (faceNormal.GetNorm() * faceNormal.GetNorm())) * faceNormal; // calculate cornerpoints from geometry without visualization offset to update actual geometry cornerPoints = GetCornerPoints(geometry, false); num = 0; for (const auto &point : cornerPoints) { pointscontainer->InsertElement(num++, point); } bool positionChangeThreshold = true; for (int numFaces = 0; numFaces < 8; numFaces++) // estimate the corresponding face and shift its assigned points { if ((numFaces != faces[0]) && (numFaces != faces[1]) && (numFaces != faces[2]) && (numFaces != faces[3])) { Point3D point = pointscontainer->GetElement(numFaces); if (m_Impl->RotationEnabled) // apply if geometry is rotated at a pixel aligned shift is not possible { point[0] += faceShift[0]; point[1] += faceShift[1]; point[2] += faceShift[2]; } else // shift pixelwise { point[0] += std::round(faceShift[0] / spacing[0]) * spacing[0]; point[1] += std::round(faceShift[1] / spacing[1]) * spacing[1]; point[2] += std::round(faceShift[2] / spacing[2]) * spacing[2]; } if (point == pointscontainer->GetElement(numFaces)) positionChangeThreshold = false; else m_Impl->LastPickedWorldPoint = point; pointscontainer->InsertElement(numFaces, point); } } if (positionChangeThreshold) // update only if bounding box is shifted at least by one pixel { auto inverse = mitk::AffineTransform3D::New(); geometry->GetIndexToWorldTransform()->GetInverse(inverse); for (unsigned int pointid = 0; pointid < 8; pointid++) { pointscontainer->InsertElement(pointid, inverse->TransformPoint(pointscontainer->GetElement(pointid))); } auto bbox = mitk::BoundingBox::New(); bbox->SetPoints(pointscontainer); bbox->ComputeBoundingBox(); mitk::Point3D BBmin = bbox->GetMinimum(); mitk::Point3D BBmax = bbox->GetMaximum(); if (std::abs(BBmin[0] - BBmax[0]) > 0.01 && std::abs(BBmin[1] - BBmax[1]) > 0.01 && std::abs(BBmin[2] - BBmax[2]) > 0.01) // TODO: check if the extent is greater than zero { geometry->SetBounds(bbox->GetBounds()); geometry->Modified(); this->GetDataNode()->GetData()->UpdateOutputInformation(); // Geometry is up-to-date this->GetDataNode()->GetData()->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } return; } void mitk::BoundingShapeInteractor::RestoreNodeProperties() { mitk::DataNode::Pointer inputNode = this->GetDataNode(); if (inputNode.IsNull()) return; mitk::ColorProperty::Pointer color = (mitk::ColorProperty::New(1.0, 1.0, 1.0)); if (color.IsNotNull()) { inputNode->GetPropertyList()->SetProperty("color", color); } inputNode->SetProperty("layer", mitk::IntProperty::New(99)); inputNode->SetProperty(boundingShapePropertyName, mitk::BoolProperty::New(false)); inputNode->GetPropertyList()->DeleteProperty(activeHandleIdPropertyName); EnableCrosshairNavigation(); // update rendering mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::BoundingShapeInteractor::EnableCrosshairNavigation() { // enable the crosshair navigation // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (auto it = m_Impl->DisplayInteractorConfigs.begin(); it != m_Impl->DisplayInteractorConfigs.end(); ++it) { if (it->first) { mitk::DisplayInteractor *displayInteractor = static_cast( us::GetModuleContext()->GetService(it->first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(it->second); - // MITK_INFO << "restore config"; + } + + mitk::DisplayActionEventBroadcast *displayActionEventBroadcast = static_cast( + us::GetModuleContext()->GetService(it->first)); + if (displayActionEventBroadcast != nullptr) + { + // here the regular configuration is loaded again + displayActionEventBroadcast->SetEventConfig(it->second); } } } m_Impl->DisplayInteractorConfigs.clear(); m_Impl->ScrollEnabled = true; } void mitk::BoundingShapeInteractor::DisableCrosshairNavigation() { // dont deactivate twice, else we will clutter the config list ... if (m_Impl->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_Impl->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_Impl->DisplayInteractorConfigs.insert(std::make_pair(*it, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded - displayInteractor->SetEventConfig("DisplayConfigMITKNoCrosshair.xml"); - // MITK_INFO << "change config"; + displayInteractor->AddEventConfig("DisplayConfigBlockLMB.xml"); + } + + auto *displayActionEventBroadcast = + dynamic_cast(us::GetModuleContext()->GetService(*it)); + if (displayActionEventBroadcast != nullptr) + { + // remember the original configuration + m_Impl->DisplayInteractorConfigs.insert(std::make_pair(*it, displayActionEventBroadcast->GetEventConfig())); + // here the alternative configuration is loaded + displayActionEventBroadcast->AddEventConfig("DisplayConfigBlockLMB.xml"); } } m_Impl->ScrollEnabled = false; } diff --git a/Modules/Core/TestingHelper/include/mitkInteractionTestHelper.h b/Modules/Core/TestingHelper/include/mitkInteractionTestHelper.h index c1ba0efb79..0c44c6c66f 100644 --- a/Modules/Core/TestingHelper/include/mitkInteractionTestHelper.h +++ b/Modules/Core/TestingHelper/include/mitkInteractionTestHelper.h @@ -1,142 +1,146 @@ /*============================================================================ 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 mitkInteractionTestHelper_h #define mitkInteractionTestHelper_h #include -#include +#include #include #include #include class vtkRenderWindow; class vtkRenderer; namespace mitk { /** @brief Creates everything needed to load and playback interaction events. * - * The interaction is loaded from an xml file and the event are created. This file is + * The interaction is loaded from an xml file and the events are created. This file is * usually a recorded user interaction with the GUI. This can be done with InteractionEventRecorder * plugin. Also all necessary objects to handle interaction events are generated. * The user of this class is responsible to add the data object to interact with to the data storage * of InteractionTestHelper. And must also make sure that a proper data interactor is associated with the data - * object. + * object. * * To test a PointSet interaction for instance make sure you have a PointSet node and a PointSetDataInteractor. * Then just add the node to the storage of the your InteractionTestHelper by calling - * InteractionTestHelper::AddNodeToStorage. + * InteractionTestHelper::AddNodeToStorage. * Use InteractionTestHelper::PlaybackInteraction to execute. The result can afterwards be compared to a reference - * object. + * object. * * Make sure to destroy the test helper instance after each test, since all render windows and its renderers have to - * be - * unregistered. + * be unregistered. * * \sa XML2EventParser * \sa EventFactory * \sa EventRecorder */ class MITKTESTINGHELPER_EXPORT InteractionTestHelper { public: /** * @brief InteractionTestHelper set up all neseccary objects by calling Initialize. * @param interactionXmlFilePath path to xml file containing events and configuration information for the render * windows. */ InteractionTestHelper(const std::string &interactionXmlFilePath); // unregisters all render windows and its renderers. virtual ~InteractionTestHelper(); /** @brief Returns the datastorage, in order to modify the data inside a rendering test. **/ - mitk::DataStorage::Pointer GetDataStorage(); + DataStorage::Pointer GetDataStorage(); /** * @brief AddNodeToStorage Add a node to the datastorage and perform a reinit which is necessary for rendering. * @param node The data you want to add. */ - void AddNodeToStorage(mitk::DataNode::Pointer node); + void AddNodeToStorage(DataNode::Pointer node); /** * @brief PlaybackInteraction playback loaded interaction by passing events to the dispatcher. */ void PlaybackInteraction(); /** * @brief SetTimeStep Sets timesteps of all SliceNavigationControllers to given timestep. * @param newTimeStep new timestep * * Does the same as using ImageNavigators Time slider. Use this if your data was modified in a timestep other than * 0. */ void SetTimeStep(int newTimeStep); - typedef std::vector RenderWindowListType; + typedef std::vector RenderWindowListType; const RenderWindowListType &GetRenderWindowList() { return m_RenderWindowList; } /** * @brief GetRenderWindowByName Get renderWindow by the name of its renderer. * @param name The name of the renderer of the desired renderWindow. * @return nullptr if not found. */ RenderWindow *GetRenderWindowByName(const std::string &name); /** * @brief GetRenderWindowByDefaultViewDirection Get a renderWindow by its default viewdirection. * @param viewDirection * @return nullptr if not found. */ - RenderWindow *GetRenderWindowByDefaultViewDirection(mitk::SliceNavigationController::ViewDirection viewDirection); + RenderWindow *GetRenderWindowByDefaultViewDirection(SliceNavigationController::ViewDirection viewDirection); /** * @brief GetRenderWindow Get renderWindow at position 'index'. * @param index Position within the renderWindow list. * @return nullptr if index is out of bounds. */ RenderWindow *GetRenderWindow(unsigned int index); /** * @brief AddDisplayPlaneSubTree * * Creates DisplayPlanes that are shown in a 3D RenderWindow. */ void AddDisplayPlaneSubTree(); void Set3dCameraSettings(); protected: /** - * @brief Initialize Internal method to initialize the renderwindow and set the datastorage. - * @throws mitk::Exception if interaction xml file can not be loaded. - */ + * @brief Initialize Internal method to initialize the renderwindow and set the datastorage. + * @throws mitk::Exception if interaction xml file can not be loaded. + */ void Initialize(const std::string &interactionXmlFilePath); - /** - * @brief LoadInteraction loads events from xml file. - */ + * @brief Initialize the interaction event observer / event state machine and register it as a service. + */ + void InitializeDisplayActionEventHandling(); + /** + * @brief LoadInteraction loads events from xml file. + */ void LoadInteraction(); mitk::XML2EventParser::EventContainerType m_Events; // List with loaded interaction events std::string m_InteractionFilePath; RenderWindowListType m_RenderWindowList; - mitk::DataStorage::Pointer m_DataStorage; - mitk::MouseModeSwitcher::Pointer m_MouseModeSwitcher; + DataStorage::Pointer m_DataStorage; + DisplayActionEventBroadcast::Pointer m_DisplayActionEventBroadcast; + }; -} // namespace mitk -#endif +} + +#endif // namespace mitk diff --git a/Modules/Core/TestingHelper/src/mitkInteractionTestHelper.cpp b/Modules/Core/TestingHelper/src/mitkInteractionTestHelper.cpp index 80f98daa8a..cd3d5ebcce 100644 --- a/Modules/Core/TestingHelper/src/mitkInteractionTestHelper.cpp +++ b/Modules/Core/TestingHelper/src/mitkInteractionTestHelper.cpp @@ -1,429 +1,435 @@ /*============================================================================ 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 #include #include #include #include // VTK #include #include // us #include #include mitk::InteractionTestHelper::InteractionTestHelper(const std::string &interactionXmlFilePath) : m_InteractionFilePath(interactionXmlFilePath) { this->Initialize(interactionXmlFilePath); } void mitk::InteractionTestHelper::Initialize(const std::string &interactionXmlFilePath) { tinyxml2::XMLDocument document; if (tinyxml2::XML_SUCCESS == document.LoadFile(interactionXmlFilePath.c_str())) { // get RenderingManager instance auto rm = mitk::RenderingManager::GetInstance(); // create data storage m_DataStorage = mitk::StandaloneDataStorage::New(); // for each renderer found create a render window and configure for (auto *element = document.FirstChildElement(mitk::InteractionEventConst::xmlTagInteractions().c_str()) ->FirstChildElement(mitk::InteractionEventConst::xmlTagConfigRoot().c_str()) ->FirstChildElement(mitk::InteractionEventConst::xmlTagRenderer().c_str()); element != nullptr; element = element->NextSiblingElement(mitk::InteractionEventConst::xmlTagRenderer().c_str())) { // get name of renderer const char *rendererName = element->Attribute(mitk::InteractionEventConst::xmlEventPropertyRendererName().c_str()); // get view direction mitk::SliceNavigationController::ViewDirection viewDirection = mitk::SliceNavigationController::Axial; if (element->Attribute(mitk::InteractionEventConst::xmlEventPropertyViewDirection().c_str()) != nullptr) { int viewDirectionNum = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyViewDirection().c_str())); viewDirection = static_cast(viewDirectionNum); } // get mapper slot id mitk::BaseRenderer::MapperSlotId mapperID = mitk::BaseRenderer::Standard2D; if (element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID().c_str()) != nullptr) { int mapperIDNum = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID().c_str())); mapperID = static_cast(mapperIDNum); } // Get Size of Render Windows int size[3]; size[0] = size[1] = size[2] = 0; if (element->Attribute(mitk::InteractionEventConst::xmlRenderSizeX().c_str()) != nullptr) { size[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeX().c_str())); } if (element->Attribute(mitk::InteractionEventConst::xmlRenderSizeY().c_str()) != nullptr) { size[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeY().c_str())); } if (element->Attribute(mitk::InteractionEventConst::xmlRenderSizeZ().c_str()) != nullptr) { size[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlRenderSizeZ().c_str())); } // create renderWindow, renderer and dispatcher auto rw = RenderWindow::New(nullptr, rendererName); // VtkRenderWindow is created within constructor if nullptr if (size[0] != 0 && size[1] != 0) { rw->SetSize(size[0], size[1]); rw->GetRenderer()->Resize(size[0], size[1]); } // set storage of renderer rw->GetRenderer()->SetDataStorage(m_DataStorage); // set view direction to axial rw->GetSliceNavigationController()->SetDefaultViewDirection(viewDirection); // set renderer to render 2D rw->GetRenderer()->SetMapperID(mapperID); rw->GetRenderer()->PrepareRender(); // Some more magic for the 3D render window case: // Camera view direction, position and focal point if (mapperID == mitk::BaseRenderer::Standard3D) { if (element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointX().c_str()) != nullptr) { double cameraFocalPoint[3]; cameraFocalPoint[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointX().c_str())); cameraFocalPoint[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointY().c_str())); cameraFocalPoint[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointZ().c_str())); rw->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetFocalPoint(cameraFocalPoint); } if (element->Attribute(mitk::InteractionEventConst::xmlCameraPositionX().c_str()) != nullptr) { double cameraPosition[3]; cameraPosition[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionX().c_str())); cameraPosition[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionY().c_str())); cameraPosition[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionZ().c_str())); rw->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetPosition(cameraPosition); } if (element->Attribute(mitk::InteractionEventConst::xmlViewUpX().c_str()) != nullptr) { double viewUp[3]; viewUp[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpX().c_str())); viewUp[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpY().c_str())); viewUp[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpZ().c_str())); rw->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetViewUp(viewUp); } } rw->GetVtkRenderWindow()->Render(); rw->GetVtkRenderWindow()->WaitForCompletion(); // connect SliceNavigationControllers to timestep changed event of TimeNavigationController rw->GetSliceNavigationController()->ConnectGeometryTimeEvent(rm->GetTimeNavigationController(), false); rm->GetTimeNavigationController()->ConnectGeometryTimeEvent(rw->GetSliceNavigationController(), false); - // add to list of kown render windows + // add to list of known render windows m_RenderWindowList.push_back(rw); } // TODO: check the following lines taken from QmitkStdMultiWidget and adapt them to be executed in our code here. // mitkWidget1->GetSliceNavigationController() // ->ConnectGeometrySendEvent(mitk::BaseRenderer::GetInstance(mitkWidget4->GetRenderWindow())); - //########### register display interactor to handle scroll events ################## - // use MouseModeSwitcher to ensure that the statemachine of DisplayInteractor is loaded correctly - m_MouseModeSwitcher = mitk::MouseModeSwitcher::New(); + // register interaction event obserer to handle scroll events + InitializeDisplayActionEventHandling(); } else { mitkThrow() << "Can not load interaction xml file <" << m_InteractionFilePath << ">"; } // WARNING assumes a 3D window exists !!!! this->AddDisplayPlaneSubTree(); } +void mitk::InteractionTestHelper::InitializeDisplayActionEventHandling() +{ + m_DisplayActionEventBroadcast = mitk::DisplayActionEventBroadcast::New(); + m_DisplayActionEventBroadcast->LoadStateMachine("DisplayInteraction.xml"); + m_DisplayActionEventBroadcast->SetEventConfig("DisplayConfigMITK.xml"); +} + mitk::InteractionTestHelper::~InteractionTestHelper() { mitk::RenderingManager *rm = mitk::RenderingManager::GetInstance(); // unregister renderers auto it = m_RenderWindowList.begin(); auto end = m_RenderWindowList.end(); for (; it != end; ++it) { rm->GetTimeNavigationController()->Disconnect((*it)->GetSliceNavigationController()); (*it)->GetSliceNavigationController()->Disconnect(rm->GetTimeNavigationController()); mitk::BaseRenderer::RemoveInstance((*it)->GetVtkRenderWindow()); } rm->RemoveAllObservers(); } mitk::DataStorage::Pointer mitk::InteractionTestHelper::GetDataStorage() { return m_DataStorage; } void mitk::InteractionTestHelper::AddNodeToStorage(mitk::DataNode::Pointer node) { this->m_DataStorage->Add(node); this->Set3dCameraSettings(); } void mitk::InteractionTestHelper::PlaybackInteraction() { mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); // load events if not loaded yet if (m_Events.empty()) this->LoadInteraction(); auto it = m_RenderWindowList.begin(); auto end = m_RenderWindowList.end(); for (; it != end; ++it) { (*it)->GetRenderer()->PrepareRender(); (*it)->GetVtkRenderWindow()->Render(); (*it)->GetVtkRenderWindow()->WaitForCompletion(); } mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); it = m_RenderWindowList.begin(); for (; it != end; ++it) { (*it)->GetVtkRenderWindow()->Render(); (*it)->GetVtkRenderWindow()->WaitForCompletion(); } // mitk::RenderingManager::GetInstance()->ForceImmediateUpdateAll(); // playback all events in queue for (unsigned long i = 0; i < m_Events.size(); ++i) { // let dispatcher of sending renderer process the event m_Events.at(i)->GetSender()->GetDispatcher()->ProcessEvent(m_Events.at(i)); } if (false) { it--; (*it)->GetVtkRenderWindow()->GetInteractor()->Start(); } } void mitk::InteractionTestHelper::LoadInteraction() { // load interaction pattern from xml file std::ifstream xmlStream(m_InteractionFilePath.c_str()); mitk::XML2EventParser parser(xmlStream); m_Events = parser.GetInteractions(); xmlStream.close(); // Avoid VTK warning: Trying to delete object with non-zero reference count. parser.SetReferenceCount(0); } void mitk::InteractionTestHelper::SetTimeStep(int newTimeStep) { mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); bool timeStepIsvalid = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetCreatedWorldGeometry()->IsValidTimeStep( newTimeStep); if (timeStepIsvalid) { mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime()->SetPos(newTimeStep); } } mitk::RenderWindow *mitk::InteractionTestHelper::GetRenderWindowByName(const std::string &name) { auto it = m_RenderWindowList.begin(); auto end = m_RenderWindowList.end(); for (; it != end; ++it) { if (name.compare((*it)->GetRenderer()->GetName()) == 0) return (*it).GetPointer(); } return nullptr; } mitk::RenderWindow *mitk::InteractionTestHelper::GetRenderWindowByDefaultViewDirection( mitk::SliceNavigationController::ViewDirection viewDirection) { auto it = m_RenderWindowList.begin(); auto end = m_RenderWindowList.end(); for (; it != end; ++it) { if (viewDirection == (*it)->GetSliceNavigationController()->GetDefaultViewDirection()) return (*it).GetPointer(); } return nullptr; } mitk::RenderWindow *mitk::InteractionTestHelper::GetRenderWindow(unsigned int index) { if (index < m_RenderWindowList.size()) { return m_RenderWindowList.at(index).GetPointer(); } else { return nullptr; } } void mitk::InteractionTestHelper::AddDisplayPlaneSubTree() { // add the displayed planes of the multiwidget to a node to which the subtree // @a planesSubTree points ... mitk::PlaneGeometryDataMapper2D::Pointer mapper; mitk::IntProperty::Pointer layer = mitk::IntProperty::New(1000); mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetProperty("name", mitk::StringProperty::New("Widgets")); node->SetProperty("helper object", mitk::BoolProperty::New(true)); m_DataStorage->Add(node); for (auto it : m_RenderWindowList) { if (it->GetRenderer()->GetMapperID() == BaseRenderer::Standard3D) continue; // ... of widget 1 mitk::DataNode::Pointer planeNode1 = (mitk::BaseRenderer::GetInstance(it->GetVtkRenderWindow()))->GetCurrentWorldPlaneGeometryNode(); planeNode1->SetProperty("visible", mitk::BoolProperty::New(true)); planeNode1->SetProperty("name", mitk::StringProperty::New("widget1Plane")); planeNode1->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); planeNode1->SetProperty("helper object", mitk::BoolProperty::New(true)); planeNode1->SetProperty("layer", layer); planeNode1->SetColor(1.0, 0.0, 0.0); mapper = mitk::PlaneGeometryDataMapper2D::New(); planeNode1->SetMapper(mitk::BaseRenderer::Standard2D, mapper); m_DataStorage->Add(planeNode1, node); } } void mitk::InteractionTestHelper::Set3dCameraSettings() { tinyxml2::XMLDocument document; if (tinyxml2::XML_SUCCESS == document.LoadFile(m_InteractionFilePath.c_str())) { // for each renderer found create a render window and configure for (auto *element = document.FirstChildElement(mitk::InteractionEventConst::xmlTagInteractions().c_str()) ->FirstChildElement(mitk::InteractionEventConst::xmlTagConfigRoot().c_str()) ->FirstChildElement(mitk::InteractionEventConst::xmlTagRenderer().c_str()); element != nullptr; element = element->NextSiblingElement(mitk::InteractionEventConst::xmlTagRenderer().c_str())) { // get name of renderer const char *rendererName = element->Attribute(mitk::InteractionEventConst::xmlEventPropertyRendererName().c_str()); // get mapper slot id mitk::BaseRenderer::MapperSlotId mapperID = mitk::BaseRenderer::Standard2D; if (element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID().c_str()) != nullptr) { int mapperIDNum = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlEventPropertyMapperID().c_str())); mapperID = static_cast(mapperIDNum); } if (mapperID == mitk::BaseRenderer::Standard3D) { RenderWindow *namedRenderer = nullptr; for (const auto &it : m_RenderWindowList) { if (strcmp(it->GetRenderer()->GetName(), rendererName) == 0) { namedRenderer = it.GetPointer(); break; } } if (namedRenderer == nullptr) { MITK_ERROR << "No match for render window was found."; return; } namedRenderer->GetRenderer()->PrepareRender(); if (element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointX().c_str()) != nullptr) { double cameraFocalPoint[3]; cameraFocalPoint[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointX().c_str())); cameraFocalPoint[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointY().c_str())); cameraFocalPoint[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraFocalPointZ().c_str())); namedRenderer->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetFocalPoint(cameraFocalPoint); } if (element->Attribute(mitk::InteractionEventConst::xmlCameraPositionX().c_str()) != nullptr) { double cameraPosition[3]; cameraPosition[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionX().c_str())); cameraPosition[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionY().c_str())); cameraPosition[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlCameraPositionZ().c_str())); namedRenderer->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetPosition(cameraPosition); } if (element->Attribute(mitk::InteractionEventConst::xmlViewUpX().c_str()) != nullptr) { double viewUp[3]; viewUp[0] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpX().c_str())); viewUp[1] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpY().c_str())); viewUp[2] = std::atoi(element->Attribute(mitk::InteractionEventConst::xmlViewUpZ().c_str())); namedRenderer->GetRenderer()->GetVtkRenderer()->GetActiveCamera()->SetViewUp(viewUp); } namedRenderer->GetVtkRenderWindow()->Render(); } } } } diff --git a/Modules/Core/files.cmake b/Modules/Core/files.cmake index 3178dddf16..8faabae848 100644 --- a/Modules/Core/files.cmake +++ b/Modules/Core/files.cmake @@ -1,326 +1,327 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkCoreActivator.cpp mitkCoreObjectFactoryBase.cpp mitkCoreObjectFactory.cpp mitkCoreServices.cpp mitkException.cpp Algorithms/mitkBaseDataSource.cpp Algorithms/mitkClippedSurfaceBoundsCalculator.cpp Algorithms/mitkCompareImageDataFilter.cpp Algorithms/mitkCompositePixelValueToString.cpp Algorithms/mitkConvert2Dto3DImageFilter.cpp Algorithms/mitkDataNodeSource.cpp Algorithms/mitkExtractSliceFilter.cpp Algorithms/mitkExtractSliceFilter2.cpp Algorithms/mitkHistogramGenerator.cpp Algorithms/mitkImageChannelSelector.cpp Algorithms/mitkImageSliceSelector.cpp Algorithms/mitkImageSource.cpp Algorithms/mitkImageTimeSelector.cpp Algorithms/mitkImageToImageFilter.cpp Algorithms/mitkImageToSurfaceFilter.cpp Algorithms/mitkMultiComponentImageDataComparisonFilter.cpp Algorithms/mitkPlaneGeometryDataToSurfaceFilter.cpp Algorithms/mitkPointSetSource.cpp Algorithms/mitkPointSetToPointSetFilter.cpp Algorithms/mitkRGBToRGBACastImageFilter.cpp Algorithms/mitkSubImageSelector.cpp Algorithms/mitkSurfaceSource.cpp Algorithms/mitkSurfaceToImageFilter.cpp Algorithms/mitkSurfaceToSurfaceFilter.cpp Algorithms/mitkUIDGenerator.cpp Algorithms/mitkVolumeCalculator.cpp Algorithms/mitkTemporalJoinImagesFilter.cpp Controllers/mitkBaseController.cpp Controllers/mitkCallbackFromGUIThread.cpp Controllers/mitkCameraController.cpp Controllers/mitkCameraRotationController.cpp Controllers/mitkLimitedLinearUndo.cpp Controllers/mitkOperationEvent.cpp Controllers/mitkPlanePositionManager.cpp Controllers/mitkProgressBar.cpp Controllers/mitkRenderingManager.cpp Controllers/mitkSliceNavigationController.cpp Controllers/mitkSlicesCoordinator.cpp Controllers/mitkStatusBar.cpp Controllers/mitkStepper.cpp Controllers/mitkTestManager.cpp Controllers/mitkUndoController.cpp Controllers/mitkVerboseLimitedLinearUndo.cpp Controllers/mitkVtkLayerController.cpp DataManagement/mitkAnatomicalStructureColorPresets.cpp DataManagement/mitkArbitraryTimeGeometry.cpp DataManagement/mitkAbstractTransformGeometry.cpp DataManagement/mitkAnnotationProperty.cpp DataManagement/mitkApplicationCursor.cpp DataManagement/mitkApplyTransformMatrixOperation.cpp DataManagement/mitkBaseData.cpp DataManagement/mitkBaseGeometry.cpp DataManagement/mitkBaseProperty.cpp DataManagement/mitkChannelDescriptor.cpp DataManagement/mitkClippingProperty.cpp DataManagement/mitkColorProperty.cpp DataManagement/mitkDataNode.cpp DataManagement/mitkDataStorage.cpp DataManagement/mitkEnumerationProperty.cpp DataManagement/mitkFloatPropertyExtension.cpp DataManagement/mitkGeometry3D.cpp DataManagement/mitkGeometryData.cpp DataManagement/mitkGeometryTransformHolder.cpp DataManagement/mitkGroupTagProperty.cpp DataManagement/mitkGenericIDRelationRule.cpp DataManagement/mitkIdentifiable.cpp DataManagement/mitkImageAccessorBase.cpp DataManagement/mitkImageCaster.cpp DataManagement/mitkImageCastPart1.cpp DataManagement/mitkImageCastPart2.cpp DataManagement/mitkImageCastPart3.cpp DataManagement/mitkImageCastPart4.cpp DataManagement/mitkImage.cpp DataManagement/mitkImageDataItem.cpp DataManagement/mitkImageDescriptor.cpp DataManagement/mitkImageReadAccessor.cpp DataManagement/mitkImageStatisticsHolder.cpp DataManagement/mitkImageVtkAccessor.cpp DataManagement/mitkImageVtkReadAccessor.cpp DataManagement/mitkImageVtkWriteAccessor.cpp DataManagement/mitkImageWriteAccessor.cpp DataManagement/mitkIntPropertyExtension.cpp DataManagement/mitkIPersistenceService.cpp DataManagement/mitkIPropertyAliases.cpp DataManagement/mitkIPropertyDescriptions.cpp DataManagement/mitkIPropertyExtensions.cpp DataManagement/mitkIPropertyFilters.cpp DataManagement/mitkIPropertyOwner.cpp DataManagement/mitkIPropertyPersistence.cpp DataManagement/mitkIPropertyProvider.cpp DataManagement/mitkLandmarkProjectorBasedCurvedGeometry.cpp DataManagement/mitkLandmarkProjector.cpp DataManagement/mitkLevelWindow.cpp DataManagement/mitkLevelWindowManager.cpp DataManagement/mitkLevelWindowPreset.cpp DataManagement/mitkLevelWindowProperty.cpp DataManagement/mitkLine.cpp DataManagement/mitkLookupTable.cpp DataManagement/mitkLookupTableProperty.cpp DataManagement/mitkLookupTables.cpp # specializations of GenericLookupTable DataManagement/mitkMaterial.cpp DataManagement/mitkMemoryUtilities.cpp DataManagement/mitkModalityProperty.cpp DataManagement/mitkModifiedLock.cpp DataManagement/mitkNodePredicateAnd.cpp DataManagement/mitkNodePredicateBase.cpp DataManagement/mitkNodePredicateCompositeBase.cpp DataManagement/mitkNodePredicateData.cpp DataManagement/mitkNodePredicateDataType.cpp DataManagement/mitkNodePredicateDataUID.cpp DataManagement/mitkNodePredicateDimension.cpp DataManagement/mitkNodePredicateFirstLevel.cpp DataManagement/mitkNodePredicateFunction.cpp DataManagement/mitkNodePredicateGeometry.cpp DataManagement/mitkNodePredicateNot.cpp DataManagement/mitkNodePredicateOr.cpp DataManagement/mitkNodePredicateProperty.cpp DataManagement/mitkNodePredicateDataProperty.cpp DataManagement/mitkNodePredicateSource.cpp DataManagement/mitkNodePredicateSubGeometry.cpp DataManagement/mitkNumericConstants.cpp DataManagement/mitkPlaneGeometry.cpp DataManagement/mitkPlaneGeometryData.cpp DataManagement/mitkPlaneOperation.cpp DataManagement/mitkPlaneOrientationProperty.cpp DataManagement/mitkPointOperation.cpp DataManagement/mitkPointSet.cpp DataManagement/mitkPointSetShapeProperty.cpp DataManagement/mitkProperties.cpp DataManagement/mitkPropertyAliases.cpp DataManagement/mitkPropertyDescriptions.cpp DataManagement/mitkPropertyExtension.cpp DataManagement/mitkPropertyExtensions.cpp DataManagement/mitkPropertyFilter.cpp DataManagement/mitkPropertyFilters.cpp DataManagement/mitkPropertyKeyPath.cpp DataManagement/mitkPropertyList.cpp DataManagement/mitkPropertyListReplacedObserver.cpp DataManagement/mitkPropertyNameHelper.cpp DataManagement/mitkPropertyObserver.cpp DataManagement/mitkPropertyPersistence.cpp DataManagement/mitkPropertyPersistenceInfo.cpp DataManagement/mitkPropertyRelationRuleBase.cpp DataManagement/mitkProportionalTimeGeometry.cpp DataManagement/mitkRenderingModeProperty.cpp DataManagement/mitkResliceMethodProperty.cpp DataManagement/mitkRestorePlanePositionOperation.cpp DataManagement/mitkRotationOperation.cpp DataManagement/mitkScaleOperation.cpp DataManagement/mitkSlicedData.cpp DataManagement/mitkSlicedGeometry3D.cpp DataManagement/mitkSmartPointerProperty.cpp DataManagement/mitkStandaloneDataStorage.cpp DataManagement/mitkStringProperty.cpp DataManagement/mitkSurface.cpp DataManagement/mitkSurfaceOperation.cpp DataManagement/mitkSourceImageRelationRule.cpp DataManagement/mitkThinPlateSplineCurvedGeometry.cpp DataManagement/mitkTimeGeometry.cpp DataManagement/mitkTransferFunction.cpp DataManagement/mitkTransferFunctionInitializer.cpp DataManagement/mitkTransferFunctionProperty.cpp DataManagement/mitkTemporoSpatialStringProperty.cpp DataManagement/mitkUIDManipulator.cpp DataManagement/mitkVector.cpp DataManagement/mitkVectorProperty.cpp DataManagement/mitkVtkInterpolationProperty.cpp DataManagement/mitkVtkRepresentationProperty.cpp DataManagement/mitkVtkResliceInterpolationProperty.cpp DataManagement/mitkVtkScalarModeProperty.cpp DataManagement/mitkVtkVolumeRenderingProperty.cpp DataManagement/mitkWeakPointerProperty.cpp DataManagement/mitkIPropertyRelations.cpp DataManagement/mitkPropertyRelations.cpp Interactions/mitkAction.cpp Interactions/mitkBindDispatcherInteractor.cpp Interactions/mitkCrosshairPositionEvent.cpp Interactions/mitkDataInteractor.cpp Interactions/mitkDispatcher.cpp Interactions/mitkDisplayActionEventBroadcast.cpp Interactions/mitkDisplayActionEventFunctions.cpp Interactions/mitkDisplayActionEventHandler.cpp Interactions/mitkDisplayActionEventHandlerDesynchronized.cpp Interactions/mitkDisplayActionEventHandlerStd.cpp Interactions/mitkDisplayActionEventHandlerSynchronized.cpp Interactions/mitkDisplayCoordinateOperation.cpp Interactions/mitkDisplayInteractor.cpp Interactions/mitkEventConfig.cpp Interactions/mitkEventFactory.cpp Interactions/mitkEventRecorder.cpp Interactions/mitkEventStateMachine.cpp Interactions/mitkInteractionEventConst.cpp Interactions/mitkInteractionEvent.cpp Interactions/mitkInteractionEventHandler.cpp Interactions/mitkInteractionEventObserver.cpp Interactions/mitkInteractionKeyEvent.cpp Interactions/mitkInteractionPositionEvent.cpp Interactions/mitkInteractionSchemeSwitcher.cpp Interactions/mitkInternalEvent.cpp Interactions/mitkMouseDoubleClickEvent.cpp - Interactions/mitkMouseModeSwitcher.cpp Interactions/mitkMouseMoveEvent.cpp Interactions/mitkMousePressEvent.cpp Interactions/mitkMouseReleaseEvent.cpp Interactions/mitkMouseWheelEvent.cpp Interactions/mitkPointSetDataInteractor.cpp Interactions/mitkSinglePointDataInteractor.cpp Interactions/mitkStateMachineAction.cpp Interactions/mitkStateMachineCondition.cpp Interactions/mitkStateMachineContainer.cpp Interactions/mitkStateMachineState.cpp Interactions/mitkStateMachineTransition.cpp Interactions/mitkVtkEventAdapter.cpp Interactions/mitkVtkInteractorStyle.cxx Interactions/mitkXML2EventParser.cpp IO/mitkAbstractFileIO.cpp IO/mitkAbstractFileReader.cpp IO/mitkAbstractFileWriter.cpp IO/mitkCustomMimeType.cpp IO/mitkFileReader.cpp IO/mitkFileReaderRegistry.cpp IO/mitkFileReaderSelector.cpp IO/mitkFileReaderWriterBase.cpp IO/mitkFileWriter.cpp IO/mitkFileWriterRegistry.cpp IO/mitkFileWriterSelector.cpp IO/mitkGeometry3DToXML.cpp IO/mitkIFileIO.cpp IO/mitkIFileReader.cpp IO/mitkIFileWriter.cpp IO/mitkGeometryDataReaderService.cpp IO/mitkGeometryDataWriterService.cpp IO/mitkImageGenerator.cpp IO/mitkImageVtkLegacyIO.cpp IO/mitkImageVtkXmlIO.cpp IO/mitkIMimeTypeProvider.cpp IO/mitkIOConstants.cpp IO/mitkIOMimeTypes.cpp IO/mitkIOUtil.cpp IO/mitkItkImageIO.cpp IO/mitkItkLoggingAdapter.cpp IO/mitkLegacyFileReaderService.cpp IO/mitkLegacyFileWriterService.cpp IO/mitkLocaleSwitch.cpp IO/mitkLog.cpp IO/mitkMimeType.cpp IO/mitkMimeTypeProvider.cpp IO/mitkOperation.cpp IO/mitkPixelType.cpp IO/mitkPointSetReaderService.cpp IO/mitkPointSetWriterService.cpp IO/mitkProportionalTimeGeometryToXML.cpp IO/mitkRawImageFileReader.cpp IO/mitkStandardFileLocations.cpp IO/mitkSurfaceStlIO.cpp IO/mitkSurfaceVtkIO.cpp IO/mitkSurfaceVtkLegacyIO.cpp IO/mitkSurfaceVtkXmlIO.cpp IO/mitkVtkLoggingAdapter.cpp IO/mitkPreferenceListReaderOptionsFunctor.cpp IO/mitkIOMetaInformationPropertyConstants.cpp Rendering/mitkAbstractAnnotationRenderer.cpp Rendering/mitkAnnotationUtils.cpp Rendering/mitkBaseRenderer.cpp #Rendering/mitkGLMapper.cpp Moved to deprecated LegacyGL Module Rendering/mitkGradientBackground.cpp Rendering/mitkImageVtkMapper2D.cpp Rendering/mitkMapper.cpp Rendering/mitkAnnotation.cpp Rendering/mitkPlaneGeometryDataMapper2D.cpp Rendering/mitkPlaneGeometryDataVtkMapper3D.cpp Rendering/mitkPointSetVtkMapper2D.cpp Rendering/mitkPointSetVtkMapper3D.cpp Rendering/mitkRenderWindowBase.cpp Rendering/mitkRenderWindow.cpp Rendering/mitkRenderWindowFrame.cpp #Rendering/mitkSurfaceGLMapper2D.cpp Moved to deprecated LegacyGL Module Rendering/mitkSurfaceVtkMapper2D.cpp Rendering/mitkSurfaceVtkMapper3D.cpp Rendering/mitkVtkEventProvider.cpp Rendering/mitkVtkMapper.cpp Rendering/mitkVtkPropRenderer.cpp Rendering/mitkVtkWidgetRendering.cpp Rendering/vtkMitkLevelWindowFilter.cpp Rendering/vtkMitkRectangleProp.cpp Rendering/vtkMitkRenderProp.cpp Rendering/vtkMitkThickSlicesFilter.cpp Rendering/vtkNeverTranslucentTexture.cpp ) set(RESOURCE_FILES Interactions/globalConfig.xml Interactions/DisplayInteraction.xml Interactions/DisplayConfig.xml Interactions/DisplayConfigPACS.xml +Interactions/DisplayConfigPACSCrosshair.xml Interactions/DisplayConfigPACSPan.xml Interactions/DisplayConfigPACSScroll.xml Interactions/DisplayConfigPACSZoom.xml Interactions/DisplayConfigPACSLevelWindow.xml Interactions/DisplayConfigMITK.xml Interactions/DisplayConfigMITKNoCrosshair.xml Interactions/DisplayConfigMITKRotation.xml Interactions/DisplayConfigMITKRotationUnCoupled.xml Interactions/DisplayConfigMITKSwivel.xml Interactions/DisplayConfigMITKLimited.xml +Interactions/DisplayConfigBlockLMB.xml Interactions/PointSet.xml Interactions/Legacy/StateMachine.xml Interactions/Legacy/DisplayConfigMITKTools.xml Interactions/PointSetConfig.xml mitkLevelWindowPresets.xml mitkAnatomicalStructureColorPresets.xml ) diff --git a/Modules/Core/include/mitkInteractionSchemeSwitcher.h b/Modules/Core/include/mitkInteractionSchemeSwitcher.h index 673472a03d..139d6f0738 100644 --- a/Modules/Core/include/mitkInteractionSchemeSwitcher.h +++ b/Modules/Core/include/mitkInteractionSchemeSwitcher.h @@ -1,122 +1,116 @@ /*============================================================================ 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 MITKINTERACTIONSCHEMESWITCHER_H #define MITKINTERACTIONSCHEMESWITCHER_H #include "MitkCoreExports.h" #include "mitkInteractionEventHandler.h" #include namespace mitk { /*********************************************************************** * * \brief Class that offers a convenient way to switch between different - * interaction schemes and their different modes. + * interaction schemes. * - * This class offers the possibility to switch between the two different + * This class offers the possibility to switch between the different * interaction schemes that are available: * - * - MITK : The original interaction scheme - * - left mouse button : setting the cross position in the MPR view - * - middle mouse button : panning - * - right mouse button : zooming - * - * There are 3 different MITK modes that are available in the MITK scheme. + * - MITKStandard : The original MITK interaction scheme + * - MITKRotationUncoupled : A modified MITK interaction scheme with rotation + * - MITKRotationCoupled : A modified MTIK interaction scheme with coupled rotation + * - MITKSwivel : A modified MITK interaction scheme with plane swiveling * * - PACS : An alternative interaction scheme that behaves more like a * PACS workstation - * - left mouse button : behavior depends on current MouseMode + * - left mouse button : behavior depends on current PACS scheme + * Always enabled: * - middle mouse button : fast scrolling * - right mouse button : level-window * - ctrl + right button : zooming * - shift+ right button : panning * - * There are 5 different PACS modes that are available in the PACS scheme. - * Each mode defines the interaction that is performed on a left + * There are 6 different PACS schemes. + * Each scheme defines the interaction that is performed on a left * mouse button click: - * - Pointer : sets the cross position for the MPR - * - Scroll - * - Level-Window - * - Zoom - * - Pan + * - PACSBase : No interaction on a left mouse button click + - This scheme serves as a base for other PACS schemes and defines the right + and middle mouse button clicks, which are available in every PACS scheme. + * - PACSStandard : Sets the cross position for the MPR + * - PACSLevelWindow : Sets the level window + * - PACSPan : Moves the slice + * - PACSScroll : Scrolls through the slices stepwise + * - PACSZoom : Zooms into / out of the slice * * When the interaction scheme is changed, this class sets the corresponding * interaction .xml-files for a given interaction event handler. * ***********************************************************************/ + class MITKCORE_EXPORT InteractionSchemeSwitcher : public itk::Object { public: #pragma GCC visibility push(default) /** \brief Can be observed by GUI class to update button states when type is changed programmatically. */ itkEventMacro(InteractionSchemeChangedEvent, itk::AnyEvent); #pragma GCC visibility pop mitkClassMacroItkParent(InteractionSchemeSwitcher, itk::Object); itkFactorylessNewMacro(Self); - itkCloneMacro(Self); // enum of the different interaction schemes that are available enum InteractionScheme { MITKStandard = 0, MITKRotationUncoupled, MITKRotationCoupled, MITKSwivel, + PACSBase, PACSStandard, PACSLevelWindow, PACSPan, PACSScroll, PACSZoom }; /** * @brief Set the current interaction scheme of the given interaction event handler * * The interaction event handler is able to accept xml-configuration files that will define the interaction scheme. * Based on the given interaction scheme different configuration files are loaded into the interaction event handler. - * The interaction scheme can be a variant of the MITK-mouse mode or the PACS-mouse mode (see 'enum InteractionScheme'). + * The interaction scheme can be a variant of the MITK-scheme or the PACS-scheme (see 'enum InteractionScheme'). * The default is 'MITKStandard'. * If the interaction scheme has been changed, an 'InteractionSchemeChangedEvent' will be invoked. * * @pre The interaction event handler has to be valid (!nullptr). * @throw mitk::Exception, if the interaction event handler is invalid (==nullptr). * * @param interactionEventHandler The interaction event handler that defines the interaction scheme via configuration files * @param interactionScheme The interaction scheme that should be used for the currently active interaction event handler. */ void SetInteractionScheme(mitk::InteractionEventHandler* interactionEventHandler, InteractionScheme interactionScheme); - /** - * @brief Return the current interaction scheme - * - * @return The currently set InteractionScheme - */ - InteractionScheme GetInteractionScheme() const { return m_InteractionScheme; }; protected: InteractionSchemeSwitcher(); ~InteractionSchemeSwitcher() override; - private: - - InteractionScheme m_InteractionScheme; }; } // namespace mitk #endif // MITKINTERACTIONSCHEMESWITCHER_H diff --git a/Modules/Core/include/mitkMouseModeSwitcher.h b/Modules/Core/include/mitkMouseModeSwitcher.h deleted file mode 100644 index 17b9bd5f54..0000000000 --- a/Modules/Core/include/mitkMouseModeSwitcher.h +++ /dev/null @@ -1,127 +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 MITKMouseModeSwitcher_H_HEADER_INCLUDED_C10DC4EB -#define MITKMouseModeSwitcher_H_HEADER_INCLUDED_C10DC4EB - -#include "MitkCoreExports.h" -#include "mitkDisplayInteractor.h" -#include - -namespace mitk -{ - /*********************************************************************** - * - * \brief Class that offers a convenient way to switch between different - * interaction schemes - * - * This class offers the possibility to switch between the two different - * interaction schemes that are available: - * - MITK : The original interaction scheme - * - left mouse button : setting the cross position in the MPR view - * - middle mouse button : panning - * - right mouse button : zooming - * - * - * - PACS : an alternative interaction scheme that behaves more like a - * PACS workstation - * - left mouse button : behavior depends on current MouseMode - * - middle mouse button : fast scrolling - * - right mouse button : level-window - * - ctrl + right button : zooming - * - shift+ right button : panning - * - * There are 5 different MouseModes that are available in the PACS scheme. - * Each MouseMode defines the interaction that is performed on a left - * mouse button click: - * - Pointer : sets the cross position for the MPR - * - Scroll - * - Level-Window - * - Zoom - * - Pan - * - * When the interaction scheme or the MouseMode is changed, this class - * manages the adding and removing of the relevant listeners offering - * a convenient way to modify the interaction behavior. - * - ***********************************************************************/ - class MITKCORE_EXPORT MouseModeSwitcher : public itk::Object - { - public: -#pragma GCC visibility push(default) - /** - \brief Can be observed by GUI class to update button states when mode is changed programatically. - */ - itkEventMacro(MouseModeChangedEvent, itk::AnyEvent); -#pragma GCC visibility pop - - mitkClassMacroItkParent(MouseModeSwitcher, itk::Object); - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); - - // enum of the different interaction schemes that are available - enum InteractionScheme { - PACS = 0, - MITK = 1, - ROTATION = 2, - ROTATIONLINKED = 3, - SWIVEL = 4 - }; - - // enum of available mouse modes for PACS interaction scheme - enum MouseMode - { - MousePointer = 0, - Scroll, - LevelWindow, - Zoom, - Pan - }; - - /** - * \brief Setter for interaction scheme - */ - void SetInteractionScheme(InteractionScheme); - - /** - * \brief Setter for mouse mode - */ - void SelectMouseMode(MouseMode mode); - - /** - * \brief Returns the current mouse mode - */ - MouseMode GetCurrentMouseMode() const; - - protected: - MouseModeSwitcher(); - ~MouseModeSwitcher() override; - - private: - /** - * \brief Initializes the listener with the MITK default behavior. - */ - void InitializeListeners(); - - InteractionScheme m_ActiveInteractionScheme; - MouseMode m_ActiveMouseMode; - DisplayInteractor::Pointer m_CurrentObserver; - - /** - * Reference to the service registration of the observer, - * it is needed to unregister the observer on unload. - */ - us::ServiceRegistration m_ServiceRegistration; - }; -} // namespace mitk - -#endif /* MITKMouseModeSwitcher_H_HEADER_INCLUDED_C10DC4EB */ diff --git a/Modules/Core/include/mitkPointSetVtkMapper2D.h b/Modules/Core/include/mitkPointSetVtkMapper2D.h index f8520be771..31f3cfdced 100644 --- a/Modules/Core/include/mitkPointSetVtkMapper2D.h +++ b/Modules/Core/include/mitkPointSetVtkMapper2D.h @@ -1,238 +1,239 @@ /*============================================================================ 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 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 + bool m_FixedSizeOnScreen; // "Pointset.2D.fixed size on screen" property }; } // namespace mitk #endif /* mitkPointSetVtkMapper2D_h */ diff --git a/Modules/Core/include/mitkRenderingManager.h b/Modules/Core/include/mitkRenderingManager.h index bc47776433..f0c7279010 100644 --- a/Modules/Core/include/mitkRenderingManager.h +++ b/Modules/Core/include/mitkRenderingManager.h @@ -1,402 +1,405 @@ /*============================================================================ 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 MITKRENDERINGMANAGER_H_HEADER_INCLUDED_C135A197 #define MITKRENDERINGMANAGER_H_HEADER_INCLUDED_C135A197 #include #include #include #include #include #include "mitkProperties.h" #include "mitkPropertyList.h" #include "mitkTimeGeometry.h" #include class vtkRenderWindow; class vtkObject; namespace mitk { class RenderingManager; class RenderingManagerFactory; class BaseGeometry; class SliceNavigationController; class BaseRenderer; class DataStorage; /** * \brief Manager for coordinating the rendering process. * * RenderingManager is a central instance retrieving and executing * RenderWindow update requests. Its main purpose is to coordinate * distributed requests which cannot be aware of each other - lacking the * knowledge of whether they are really necessary or not. For example, two * objects might determine that a specific RenderWindow needs to be updated. * This would result in one unnecessary update, if both executed the update * on their own. * * The RenderingManager addresses this by letting each such object * request an update, and waiting for other objects to possibly * issue the same request. The actual update will then only be executed at a * well-defined point in the main event loop (this may be each time after * event processing is done). * * Convinience methods for updating all RenderWindows which have been * registered with the RenderingManager exist. If theses methods are not * used, it is not required to register (add) RenderWindows prior to using * the RenderingManager. * * The methods #ForceImmediateUpdate() and #ForceImmediateUpdateAll() can * be used to force the RenderWindow update execution without any delay, * bypassing the request functionality. * * The interface of RenderingManager is platform independent. Platform * specific subclasses have to be implemented, though, to supply an * appropriate event issueing for controlling the update execution process. * See method documentation for a description of how this can be done. * * \sa TestingRenderingManager An "empty" RenderingManager implementation which * can be used in tests etc. * */ class MITKCORE_EXPORT RenderingManager : public itk::Object { public: mitkClassMacroItkParent(RenderingManager, itk::Object); typedef std::vector RenderWindowVector; typedef std::vector FloatVector; typedef std::vector BoolVector; typedef itk::SmartPointer DataStoragePointer; enum RequestType { REQUEST_UPDATE_ALL = 0, REQUEST_UPDATE_2DWINDOWS, REQUEST_UPDATE_3DWINDOWS }; static Pointer New(); /** Set the object factory which produces the desired platform specific * RenderingManager singleton instance. */ static void SetFactory(RenderingManagerFactory *factory); /** Get the object factory which produces the platform specific * RenderingManager instances. */ static const RenderingManagerFactory *GetFactory(); /** Returns true if a factory has already been set. */ static bool HasFactory(); /** Get the RenderingManager singleton instance. */ static RenderingManager *GetInstance(); /** Returns true if the singleton instance does already exist. */ static bool IsInstantiated(); /** Adds a RenderWindow. This is required if the methods #RequestUpdateAll * or #ForceImmediateUpdate are to be used. */ void AddRenderWindow(vtkRenderWindow *renderWindow); /** Removes a RenderWindow. */ void RemoveRenderWindow(vtkRenderWindow *renderWindow); /** Get a list of all registered RenderWindows */ const RenderWindowVector &GetAllRegisteredRenderWindows(); /** Requests an update for the specified RenderWindow, to be executed as * soon as the main loop is ready for rendering. */ void RequestUpdate(vtkRenderWindow *renderWindow); /** Immediately executes an update of the specified RenderWindow. */ void ForceImmediateUpdate(vtkRenderWindow *renderWindow); /** Requests all currently registered RenderWindows to be updated. * If only 2D or 3D windows should be updated, this can be specified * via the parameter requestType. */ void RequestUpdateAll(RequestType type = REQUEST_UPDATE_ALL); /** Immediately executes an update of all registered RenderWindows. * If only 2D or 3D windows should be updated, this can be specified * via the parameter requestType. */ void ForceImmediateUpdateAll(RequestType type = REQUEST_UPDATE_ALL); /** Initializes the windows specified by requestType to the geometry of the * given DataStorage. */ // virtual bool InitializeViews( const DataStorage *storage, const DataNode* node = nullptr, // RequestType type = REQUEST_UPDATE_ALL, bool preserveRoughOrientationInWorldSpace = false ); /** Initializes the windows specified by requestType to the given * geometry. PLATFORM SPECIFIC. TODO: HOW IS THIS PLATFORM SPECIFIC? * Throws an exception if bounding box has 0 extent due to exceeding * double precision range. */ virtual bool InitializeViews(const BaseGeometry *geometry, RequestType type = REQUEST_UPDATE_ALL, - bool preserveRoughOrientationInWorldSpace = false); + bool resetCamera = true); virtual bool InitializeViews(const TimeGeometry *geometry, RequestType type = REQUEST_UPDATE_ALL, - bool preserveRoughOrientationInWorldSpace = false); + bool resetCamera = true); /** Initializes the windows to the default viewing direction * (geomtry information is NOT changed). PLATFORM SPECIFIC. */ virtual bool InitializeViews(RequestType type = REQUEST_UPDATE_ALL); /** Initializes the specified window to the geometry of the given * DataNode. Set "initializeGlobalTimeSNC" to true in order to use this * geometry as global TimeGeometry. PLATFORM SPECIFIC. */ // virtual bool InitializeView( vtkRenderWindow *renderWindow, const DataStorage* ds, const DataNode* node = nullptr, // bool initializeGlobalTimeSNC = false ); /** Initializes the specified window to the given geometry. Set * "initializeGlobalTimeSNC" to true in order to use this geometry as * global TimeGeometry. PLATFORM SPECIFIC. */ virtual bool InitializeView(vtkRenderWindow *renderWindow, const BaseGeometry *geometry, - bool initializeGlobalTimeSNC = false); + bool initializeGlobalTimeSNC = false, + bool resetCamera = true); virtual bool InitializeView(vtkRenderWindow *renderWindow, const TimeGeometry *geometry, - bool initializeGlobalTimeSNC = false); + bool initializeGlobalTimeSNC = false, + bool resetCamera = true); /** Initializes the specified window to the default viewing direction * (geomtry information is NOT changed). PLATFORM SPECIFIC. */ virtual bool InitializeView(vtkRenderWindow *renderWindow); /** * @brief Initializes the renderwindows by the aggregated geometry of * all objects that are held in the data storage. * This is basically a global reinit * @param dataStorage The data storage from which the bounding object can be retrieved */ virtual void InitializeViewsByBoundingObjects(const DataStorage *dataStorage); /** Gets the (global) SliceNavigationController responsible for * time-slicing. */ const SliceNavigationController *GetTimeNavigationController() const; /** Gets the (global) SliceNavigationController responsible for * time-slicing. */ SliceNavigationController *GetTimeNavigationController(); ~RenderingManager() override; /** Executes all pending requests. This method has to be called by the * system whenever a RenderingManager induced request event occurs in * the system pipeline (see concrete RenderingManager implementations). */ virtual void ExecutePendingRequests(); bool IsRendering() const; void AbortRendering(); /** En-/Disable LOD increase globally. */ itkSetMacro(LODIncreaseBlocked, bool); /** En-/Disable LOD increase globally. */ itkGetMacro(LODIncreaseBlocked, bool); /** En-/Disable LOD increase globally. */ itkBooleanMacro(LODIncreaseBlocked); /** En-/Disable LOD abort mechanism. */ itkSetMacro(LODAbortMechanismEnabled, bool); /** En-/Disable LOD abort mechanism. */ itkGetMacro(LODAbortMechanismEnabled, bool); /** En-/Disable LOD abort mechanism. */ itkBooleanMacro(LODAbortMechanismEnabled); /** Force a sub-class to start a timer for a pending hires-rendering request */ virtual void StartOrResetTimer(){}; /** To be called by a sub-class from a timer callback */ void ExecutePendingHighResRenderingRequest(); virtual void DoStartRendering(){}; virtual void DoMonitorRendering(){}; virtual void DoFinishAbortRendering(){}; int GetNextLOD(BaseRenderer *renderer); /** Set current LOD (nullptr means all renderers)*/ void SetMaximumLOD(unsigned int max); void SetShading(bool state, unsigned int lod); bool GetShading(unsigned int lod); void SetClippingPlaneStatus(bool status); bool GetClippingPlaneStatus(); void SetShadingValues(float ambient, float diffuse, float specular, float specpower); FloatVector &GetShadingValues(); /** Returns a property list */ PropertyList::Pointer GetPropertyList() const; /** Returns a property from m_PropertyList */ BaseProperty *GetProperty(const char *propertyKey) const; /** Sets or adds (if not present) a property in m_PropertyList */ void SetProperty(const char *propertyKey, BaseProperty *propertyValue); /** * \brief Setter / Getter for internal DataStorage * * Sets / returns the mitk::DataStorage that is used internally. This instance holds all mitk::DataNodes that are * rendered by the registered BaseRenderers. * * If this DataStorage is changed at runtime by calling SetDataStorage(), * all currently registered BaseRenderers are automatically given the correct instance. * When a new BaseRenderer is added, it is automatically initialized with the currently active DataStorage. */ void SetDataStorage(mitk::DataStorage *storage); /** * \brief Setter / Getter for internal DataStorage * * Sets / returns the mitk::DataStorage that is used internally. This instance holds all mitk::DataNodes that are * rendered by the registered BaseRenderers. * * If this DataStorage is changed at runtime by calling SetDataStorage(), * all currently registered BaseRenderers are automatically given the correct instance. * When a new BaseRenderer is added, it is automatically initialized with the currently active DataStorage. */ mitk::DataStorage *GetDataStorage(); /** * @brief Sets a flag to the given renderwindow to indicated that it has the focus e.g. has been clicked recently. * @param focusWindow */ void SetRenderWindowFocus(vtkRenderWindow *focusWindow); itkGetMacro(FocusedRenderWindow, vtkRenderWindow *); itkSetMacro(ConstrainedPanningZooming, bool); itkGetMacro(AntiAliasing, AntiAliasing); void SetAntiAliasing(AntiAliasing antiAliasing); protected: enum { RENDERING_INACTIVE = 0, RENDERING_REQUESTED, RENDERING_INPROGRESS }; RenderingManager(); /** Abstract method for generating a system specific event for rendering * request. This method is called whenever an update is requested */ virtual void GenerateRenderingRequestEvent() = 0; virtual void InitializePropertyList(); bool m_UpdatePending; typedef std::map RendererIntMap; typedef std::map RendererBoolMap; RendererBoolMap m_RenderingAbortedMap; RendererIntMap m_NextLODMap; unsigned int m_MaxLOD; bool m_LODIncreaseBlocked; bool m_LODAbortMechanismEnabled; BoolVector m_ShadingEnabled; bool m_ClippingPlaneEnabled; FloatVector m_ShadingValues; static void RenderingStartCallback(vtkObject *caller, unsigned long eid, void *clientdata, void *calldata); static void RenderingProgressCallback(vtkObject *caller, unsigned long eid, void *clientdata, void *calldata); static void RenderingEndCallback(vtkObject *caller, unsigned long eid, void *clientdata, void *calldata); typedef std::map RenderWindowList; RenderWindowList m_RenderWindowList; RenderWindowVector m_AllRenderWindows; struct RenderWindowCallbacks { vtkCallbackCommand *commands[3u]; }; typedef std::map RenderWindowCallbacksList; RenderWindowCallbacksList m_RenderWindowCallbacksList; itk::SmartPointer m_TimeNavigationController; static RenderingManager::Pointer s_Instance; static RenderingManagerFactory *s_RenderingManagerFactory; PropertyList::Pointer m_PropertyList; DataStoragePointer m_DataStorage; bool m_ConstrainedPanningZooming; private: void InternalViewInitialization(mitk::BaseRenderer *baseRenderer, const mitk::TimeGeometry *geometry, bool boundingBoxInitialized, - int mapperID); + int mapperID, + bool resetCamera); vtkRenderWindow *m_FocusedRenderWindow; AntiAliasing m_AntiAliasing; }; #pragma GCC visibility push(default) itkEventMacro(RenderingManagerEvent, itk::AnyEvent); itkEventMacro(RenderingManagerViewsInitializedEvent, RenderingManagerEvent); #pragma GCC visibility pop itkEventMacroDeclaration(FocusChangedEvent, itk::AnyEvent); /** * Generic RenderingManager implementation for "non-rendering-plattform", * e.g. for tests. Its factory (TestingRenderingManagerFactory) is * automatically on start-up and is used by default if not other * RenderingManagerFactory is instantiated explicitly thereafter. * (see mitkRenderingManager.cpp) */ class MITKCORE_EXPORT TestingRenderingManager : public RenderingManager { public: mitkClassMacro(TestingRenderingManager, RenderingManager); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected: void GenerateRenderingRequestEvent() override {}; }; } // namespace mitk #endif /* MITKRenderingManager_H_HEADER_INCLUDED_C135A197 */ diff --git a/Modules/Core/resource/Interactions/DisplayConfigBlockLMB.xml b/Modules/Core/resource/Interactions/DisplayConfigBlockLMB.xml new file mode 100644 index 0000000000..050093513b --- /dev/null +++ b/Modules/Core/resource/Interactions/DisplayConfigBlockLMB.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/Modules/Core/resource/Interactions/DisplayConfigPACS.xml b/Modules/Core/resource/Interactions/DisplayConfigPACS.xml index 40dee1e9d7..5183391440 100644 --- a/Modules/Core/resource/Interactions/DisplayConfigPACS.xml +++ b/Modules/Core/resource/Interactions/DisplayConfigPACS.xml @@ -1,70 +1,62 @@ - - - - - - - - - - + + diff --git a/Modules/Core/resource/Interactions/DisplayConfigPACSCrosshair.xml b/Modules/Core/resource/Interactions/DisplayConfigPACSCrosshair.xml new file mode 100644 index 0000000000..88430a286d --- /dev/null +++ b/Modules/Core/resource/Interactions/DisplayConfigPACSCrosshair.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Modules/Core/resource/Interactions/DisplayInteraction.xml b/Modules/Core/resource/Interactions/DisplayInteraction.xml index e88d74d8db..acf53f7cd8 100644 --- a/Modules/Core/resource/Interactions/DisplayInteraction.xml +++ b/Modules/Core/resource/Interactions/DisplayInteraction.xml @@ -1,163 +1,171 @@ + + + + + + + + diff --git a/Modules/Core/src/Controllers/mitkRenderingManager.cpp b/Modules/Core/src/Controllers/mitkRenderingManager.cpp index cc36bc0e19..c4b707d00a 100644 --- a/Modules/Core/src/Controllers/mitkRenderingManager.cpp +++ b/Modules/Core/src/Controllers/mitkRenderingManager.cpp @@ -1,767 +1,776 @@ /*============================================================================ 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 "mitkRenderingManager.h" #include "mitkBaseRenderer.h" #include "mitkCameraController.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateProperty.h" #include "mitkProportionalTimeGeometry.h" #include "mitkRenderingManagerFactory.h" +#include #include +#include #include #include "mitkNumericTypes.h" #include #include #include #include #include namespace mitk { itkEventMacroDefinition(FocusChangedEvent, itk::AnyEvent); RenderingManager::Pointer RenderingManager::s_Instance = nullptr; RenderingManagerFactory *RenderingManager::s_RenderingManagerFactory = nullptr; RenderingManager::RenderingManager() : m_UpdatePending(false), m_MaxLOD(1), m_LODIncreaseBlocked(false), m_LODAbortMechanismEnabled(false), m_ClippingPlaneEnabled(false), m_TimeNavigationController(SliceNavigationController::New()), m_DataStorage(nullptr), m_ConstrainedPanningZooming(true), m_FocusedRenderWindow(nullptr), m_AntiAliasing(AntiAliasing::FastApproximate) { m_ShadingEnabled.assign(3, false); m_ShadingValues.assign(4, 0.0); InitializePropertyList(); } RenderingManager::~RenderingManager() { // Decrease reference counts of all registered vtkRenderWindows for // proper destruction RenderWindowVector::iterator it; for (it = m_AllRenderWindows.begin(); it != m_AllRenderWindows.end(); ++it) { (*it)->UnRegister(nullptr); auto callbacks_it = this->m_RenderWindowCallbacksList.find(*it); if (callbacks_it != this->m_RenderWindowCallbacksList.end()) { (*it)->RemoveObserver(callbacks_it->second.commands[0u]); (*it)->RemoveObserver(callbacks_it->second.commands[1u]); (*it)->RemoveObserver(callbacks_it->second.commands[2u]); } } } void RenderingManager::SetFactory(RenderingManagerFactory *factory) { s_RenderingManagerFactory = factory; } const RenderingManagerFactory *RenderingManager::GetFactory() { return s_RenderingManagerFactory; } bool RenderingManager::HasFactory() { if (RenderingManager::s_RenderingManagerFactory) { return true; } else { return false; } } RenderingManager::Pointer RenderingManager::New() { const RenderingManagerFactory *factory = GetFactory(); if (factory == nullptr) return nullptr; return factory->CreateRenderingManager(); } RenderingManager *RenderingManager::GetInstance() { if (!RenderingManager::s_Instance) { if (s_RenderingManagerFactory) { s_Instance = s_RenderingManagerFactory->CreateRenderingManager(); } } return s_Instance; } bool RenderingManager::IsInstantiated() { if (RenderingManager::s_Instance) return true; else return false; } void RenderingManager::AddRenderWindow(vtkRenderWindow *renderWindow) { if (renderWindow && (m_RenderWindowList.find(renderWindow) == m_RenderWindowList.end())) { m_RenderWindowList[renderWindow] = RENDERING_INACTIVE; m_AllRenderWindows.push_back(renderWindow); if (m_DataStorage.IsNotNull()) mitk::BaseRenderer::GetInstance(renderWindow)->SetDataStorage(m_DataStorage.GetPointer()); // Register vtkRenderWindow instance renderWindow->Register(nullptr); // Add callbacks for rendering abort mechanism // BaseRenderer *renderer = BaseRenderer::GetInstance( renderWindow ); vtkCallbackCommand *startCallbackCommand = vtkCallbackCommand::New(); startCallbackCommand->SetCallback(RenderingManager::RenderingStartCallback); renderWindow->AddObserver(vtkCommand::StartEvent, startCallbackCommand); vtkCallbackCommand *progressCallbackCommand = vtkCallbackCommand::New(); progressCallbackCommand->SetCallback(RenderingManager::RenderingProgressCallback); renderWindow->AddObserver(vtkCommand::AbortCheckEvent, progressCallbackCommand); vtkCallbackCommand *endCallbackCommand = vtkCallbackCommand::New(); endCallbackCommand->SetCallback(RenderingManager::RenderingEndCallback); renderWindow->AddObserver(vtkCommand::EndEvent, endCallbackCommand); RenderWindowCallbacks callbacks; callbacks.commands[0u] = startCallbackCommand; callbacks.commands[1u] = progressCallbackCommand; callbacks.commands[2u] = endCallbackCommand; this->m_RenderWindowCallbacksList[renderWindow] = callbacks; // Delete vtk variables correctly startCallbackCommand->Delete(); progressCallbackCommand->Delete(); endCallbackCommand->Delete(); } } void RenderingManager::RemoveRenderWindow(vtkRenderWindow *renderWindow) { if (m_RenderWindowList.erase(renderWindow)) { auto callbacks_it = this->m_RenderWindowCallbacksList.find(renderWindow); if (callbacks_it != this->m_RenderWindowCallbacksList.end()) { renderWindow->RemoveObserver(callbacks_it->second.commands[0u]); renderWindow->RemoveObserver(callbacks_it->second.commands[1u]); renderWindow->RemoveObserver(callbacks_it->second.commands[2u]); this->m_RenderWindowCallbacksList.erase(callbacks_it); } auto rw_it = std::find(m_AllRenderWindows.begin(), m_AllRenderWindows.end(), renderWindow); if (rw_it != m_AllRenderWindows.cend()) { // Decrease reference count for proper destruction (*rw_it)->UnRegister(nullptr); m_AllRenderWindows.erase(rw_it); } } } const RenderingManager::RenderWindowVector &RenderingManager::GetAllRegisteredRenderWindows() { return m_AllRenderWindows; } void RenderingManager::RequestUpdate(vtkRenderWindow *renderWindow) { // If the renderWindow is not valid, we do not want to inadvertantly create // an entry in the m_RenderWindowList map. It is possible if the user is // regularly calling AddRenderer and RemoveRenderer for a rendering update // to come into this method with a renderWindow pointer that is valid in the // sense that the window does exist within the application, but that // renderWindow has been temporarily removed from this RenderingManager for // performance reasons. if (m_RenderWindowList.find(renderWindow) == m_RenderWindowList.cend()) { return; } m_RenderWindowList[renderWindow] = RENDERING_REQUESTED; if (!m_UpdatePending) { m_UpdatePending = true; this->GenerateRenderingRequestEvent(); } } void RenderingManager::ForceImmediateUpdate(vtkRenderWindow *renderWindow) { // If the renderWindow is not valid, we do not want to inadvertantly create // an entry in the m_RenderWindowList map. It is possible if the user is // regularly calling AddRenderer and RemoveRenderer for a rendering update // to come into this method with a renderWindow pointer that is valid in the // sense that the window does exist within the application, but that // renderWindow has been temporarily removed from this RenderingManager for // performance reasons. if (m_RenderWindowList.find(renderWindow) == m_RenderWindowList.cend()) { return; } // Erase potentially pending requests for this window m_RenderWindowList[renderWindow] = RENDERING_INACTIVE; m_UpdatePending = false; // Immediately repaint this window (implementation platform specific) // If the size is 0 it crashes int *size = renderWindow->GetSize(); if (0 != size[0] && 0 != size[1]) { // prepare the camera etc. before rendering // Note: this is a very important step which should be called before the VTK render! // If you modify the camera anywhere else or after the render call, the scene cannot be seen. auto *vPR = dynamic_cast(mitk::BaseRenderer::GetInstance(renderWindow)); if (vPR) vPR->PrepareRender(); // Execute rendering renderWindow->Render(); } } void RenderingManager::RequestUpdateAll(RequestType type) { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { int id = BaseRenderer::GetInstance(it->first)->GetMapperID(); if ((type == REQUEST_UPDATE_ALL) || ((type == REQUEST_UPDATE_2DWINDOWS) && (id == 1)) || ((type == REQUEST_UPDATE_3DWINDOWS) && (id == 2))) { this->RequestUpdate(it->first); } } } void RenderingManager::ForceImmediateUpdateAll(RequestType type) { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { int id = BaseRenderer::GetInstance(it->first)->GetMapperID(); if ((type == REQUEST_UPDATE_ALL) || ((type == REQUEST_UPDATE_2DWINDOWS) && (id == 1)) || ((type == REQUEST_UPDATE_3DWINDOWS) && (id == 2))) { // Immediately repaint this window (implementation platform specific) // If the size is 0, it crashes this->ForceImmediateUpdate(it->first); } } } void RenderingManager::InitializeViewsByBoundingObjects(const DataStorage *ds) { if (!ds) return; // get all nodes that have not set "includeInBoundingBox" to false mitk::NodePredicateNot::Pointer pred = mitk::NodePredicateNot::New( mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false))); mitk::DataStorage::SetOfObjects::ConstPointer rs = ds->GetSubset(pred); // calculate bounding geometry of these nodes auto bounds = ds->ComputeBoundingGeometry3D(rs, "visible"); // initialize the views to the bounding geometry this->InitializeViews(bounds); } // TODO_GOETZ // Remove old function, so only this one is working. bool RenderingManager::InitializeViews(const BaseGeometry *dataGeometry, RequestType type, - bool preserveRoughOrientationInWorldSpace) + bool resetCamera) { ProportionalTimeGeometry::Pointer propTimeGeometry = ProportionalTimeGeometry::New(); propTimeGeometry->Initialize(dynamic_cast(dataGeometry->Clone().GetPointer()), 1); - return InitializeViews(propTimeGeometry, type, preserveRoughOrientationInWorldSpace); + return InitializeViews(propTimeGeometry, type, resetCamera); } bool RenderingManager::InitializeViews(const TimeGeometry *dataGeometry, RequestType type, - bool /*preserveRoughOrientationInWorldSpace*/) + bool resetCamera) { MITK_DEBUG << "initializing views"; bool boundingBoxInitialized = false; TimeGeometry::ConstPointer timeGeometry = dataGeometry; TimeGeometry::Pointer modifiedGeometry = nullptr; if (dataGeometry != nullptr) { modifiedGeometry = dataGeometry->Clone(); } int warningLevel = vtkObject::GetGlobalWarningDisplay(); vtkObject::GlobalWarningDisplayOff(); if ((timeGeometry.IsNotNull()) && (timeGeometry->GetBoundingBoxInWorld()->GetDiagonalLength2() > mitk::eps)) { boundingBoxInitialized = true; } if (timeGeometry.IsNotNull()) { // make sure bounding box has an extent bigger than zero in any direction // clone the input geometry // Old Geometry3D::Pointer modifiedGeometry = dynamic_cast( dataGeometry->Clone().GetPointer() ); assert(modifiedGeometry.IsNotNull()); for (TimeStepType step = 0; step < modifiedGeometry->CountTimeSteps(); ++step) { BaseGeometry::BoundsArrayType newBounds = modifiedGeometry->GetGeometryForTimeStep(step)->GetBounds(); for (unsigned int dimension = 0; (2 * dimension) < newBounds.Size(); dimension++) { // check for equality but for an epsilon if (Equal(newBounds[2 * dimension], newBounds[2 * dimension + 1])) { newBounds[2 * dimension + 1] += 1; if (Equal( newBounds[2 * dimension], newBounds[2 * dimension + 1])) // newBounds will still be equal if values are beyond double precision { mitkThrow() << "One dimension of object data has zero length, please make sure you're not using numbers " "beyond double precision as coordinates."; } } } modifiedGeometry->GetGeometryForTimeStep(step)->SetBounds(newBounds); } } timeGeometry = modifiedGeometry; RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { mitk::BaseRenderer *baseRenderer = mitk::BaseRenderer::GetInstance(it->first); baseRenderer->SetConstrainZoomingAndPanning(m_ConstrainedPanningZooming); int id = baseRenderer->GetMapperID(); if (((type == REQUEST_UPDATE_ALL) || ((type == REQUEST_UPDATE_2DWINDOWS) && (id == 1)) || ((type == REQUEST_UPDATE_3DWINDOWS) && (id == 2)))) { - this->InternalViewInitialization(baseRenderer, timeGeometry, boundingBoxInitialized, id); + this->InternalViewInitialization(baseRenderer, timeGeometry, boundingBoxInitialized, id, resetCamera); } } if (boundingBoxInitialized) { m_TimeNavigationController->SetInputWorldTimeGeometry(timeGeometry); } m_TimeNavigationController->Update(); this->RequestUpdateAll(type); vtkObject::SetGlobalWarningDisplay(warningLevel); // Inform listeners that views have been initialized this->InvokeEvent(mitk::RenderingManagerViewsInitializedEvent()); return boundingBoxInitialized; } bool RenderingManager::InitializeViews(RequestType type) { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { mitk::BaseRenderer *baseRenderer = mitk::BaseRenderer::GetInstance(it->first); int id = baseRenderer->GetMapperID(); if ((type == REQUEST_UPDATE_ALL) || ((type == REQUEST_UPDATE_2DWINDOWS) && (id == 1)) || ((type == REQUEST_UPDATE_3DWINDOWS) && (id == 2))) { mitk::SliceNavigationController *nc = baseRenderer->GetSliceNavigationController(); // Re-initialize view direction nc->SetViewDirectionToDefault(); // Update the SNC nc->Update(); } } this->RequestUpdateAll(type); return true; } bool RenderingManager::InitializeView(vtkRenderWindow *renderWindow, const BaseGeometry *geometry, - bool initializeGlobalTimeSNC) + bool initializeGlobalTimeSNC, + bool resetCamera) { ProportionalTimeGeometry::Pointer propTimeGeometry = ProportionalTimeGeometry::New(); propTimeGeometry->Initialize(dynamic_cast(geometry->Clone().GetPointer()), 1); - return InitializeView(renderWindow, propTimeGeometry, initializeGlobalTimeSNC); + return InitializeView(renderWindow, propTimeGeometry, initializeGlobalTimeSNC, resetCamera); } bool RenderingManager::InitializeView(vtkRenderWindow *renderWindow, const TimeGeometry *geometry, - bool initializeGlobalTimeSNC) + bool initializeGlobalTimeSNC, + bool resetCamera) { bool boundingBoxInitialized = false; int warningLevel = vtkObject::GetGlobalWarningDisplay(); vtkObject::GlobalWarningDisplayOff(); if ((geometry != nullptr) && (geometry->GetBoundingBoxInWorld()->GetDiagonalLength2() > mitk::eps)) { boundingBoxInitialized = true; } mitk::BaseRenderer *baseRenderer = mitk::BaseRenderer::GetInstance(renderWindow); int id = baseRenderer->GetMapperID(); - this->InternalViewInitialization(baseRenderer, geometry, boundingBoxInitialized, id); + this->InternalViewInitialization(baseRenderer, geometry, boundingBoxInitialized, id, resetCamera); if (boundingBoxInitialized && initializeGlobalTimeSNC) { m_TimeNavigationController->SetInputWorldTimeGeometry(geometry); } m_TimeNavigationController->Update(); this->RequestUpdate(renderWindow); vtkObject::SetGlobalWarningDisplay(warningLevel); return boundingBoxInitialized; } bool RenderingManager::InitializeView(vtkRenderWindow *renderWindow) { mitk::BaseRenderer *baseRenderer = mitk::BaseRenderer::GetInstance(renderWindow); mitk::SliceNavigationController *nc = baseRenderer->GetSliceNavigationController(); // Re-initialize view direction nc->SetViewDirectionToDefault(); // Update the SNC nc->Update(); this->RequestUpdate(renderWindow); return true; } void RenderingManager::InternalViewInitialization(mitk::BaseRenderer *baseRenderer, const mitk::TimeGeometry *geometry, bool boundingBoxInitialized, - int mapperID) + int mapperID, + bool resetCamera) { mitk::SliceNavigationController *nc = baseRenderer->GetSliceNavigationController(); // Re-initialize view direction nc->SetViewDirectionToDefault(); if (boundingBoxInitialized) { // Set geometry for NC nc->SetInputWorldTimeGeometry(geometry); nc->Update(); - if (mapperID == BaseRenderer::Standard2D) + if (resetCamera) { - // For 2D SNCs, steppers are set so that the cross is centered - // in the image - nc->GetSlice()->SetPos(nc->GetSlice()->GetSteps() / 2); + if (mapperID == BaseRenderer::Standard2D) + { + // For 2D SNCs, steppers are set so that the cross is centered in the image + nc->GetSlice()->SetPos(nc->GetSlice()->GetSteps() / 2); + baseRenderer->GetCameraController()->Fit(); + } + else if (mapperID == BaseRenderer::Standard3D) + { + baseRenderer->GetCameraController()->SetViewToAnterior(); + } } - - baseRenderer->GetCameraController()->SetViewToAnterior(); - baseRenderer->GetCameraController()->Fit(); } else { nc->Update(); } } const SliceNavigationController *RenderingManager::GetTimeNavigationController() const { return m_TimeNavigationController.GetPointer(); } SliceNavigationController *RenderingManager::GetTimeNavigationController() { return m_TimeNavigationController.GetPointer(); } void RenderingManager::ExecutePendingRequests() { m_UpdatePending = false; // Satisfy all pending update requests RenderWindowList::const_iterator it; int i = 0; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it, ++i) { if (it->second == RENDERING_REQUESTED) { this->ForceImmediateUpdate(it->first); } } } void RenderingManager::RenderingStartCallback(vtkObject *caller, unsigned long, void *, void *) { auto renderingManager = RenderingManager::GetInstance(); auto renderWindow = dynamic_cast(caller); if (nullptr != renderWindow) renderingManager->m_RenderWindowList[renderWindow] = RENDERING_INPROGRESS; renderingManager->m_UpdatePending = false; } void RenderingManager::RenderingProgressCallback(vtkObject *caller, unsigned long, void *, void *) { auto renderingManager = RenderingManager::GetInstance(); if (renderingManager->m_LODAbortMechanismEnabled) { auto renderWindow = dynamic_cast(caller); if (nullptr != renderWindow) { auto renderer = BaseRenderer::GetInstance(renderWindow); if (nullptr != renderer && 0 < renderer->GetNumberOfVisibleLODEnabledMappers()) renderingManager->DoMonitorRendering(); } } } void RenderingManager::RenderingEndCallback(vtkObject *caller, unsigned long, void *, void *) { auto renderWindow = dynamic_cast(caller); if (nullptr != renderWindow) { auto renderer = BaseRenderer::GetInstance(renderWindow); if (nullptr != renderer) { auto renderingManager = RenderingManager::GetInstance(); renderingManager->m_RenderWindowList[renderer->GetRenderWindow()] = RENDERING_INACTIVE; if (0 < renderer->GetNumberOfVisibleLODEnabledMappers()) { if (0 == renderingManager->m_NextLODMap[renderer]) { renderingManager->StartOrResetTimer(); } else { renderingManager->m_NextLODMap[renderer] = 0; } } } } } bool RenderingManager::IsRendering() const { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { if (it->second == RENDERING_INPROGRESS) { return true; } } return false; } void RenderingManager::AbortRendering() { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { if (it->second == RENDERING_INPROGRESS) { it->first->SetAbortRender(true); m_RenderingAbortedMap[BaseRenderer::GetInstance(it->first)] = true; } } } int RenderingManager::GetNextLOD(BaseRenderer *renderer) { if (renderer != nullptr) { return m_NextLODMap[renderer]; } else { return 0; } } void RenderingManager::ExecutePendingHighResRenderingRequest() { RenderWindowList::const_iterator it; for (it = m_RenderWindowList.cbegin(); it != m_RenderWindowList.cend(); ++it) { BaseRenderer *renderer = BaseRenderer::GetInstance(it->first); if (renderer->GetNumberOfVisibleLODEnabledMappers() > 0) { if (m_NextLODMap[renderer] == 0) { m_NextLODMap[renderer] = 1; RequestUpdate(it->first); } } } } void RenderingManager::SetMaximumLOD(unsigned int max) { m_MaxLOD = max; } // enable/disable shading void RenderingManager::SetShading(bool state, unsigned int lod) { if (lod > m_MaxLOD) { itkWarningMacro(<< "LOD out of range requested: " << lod << " maxLOD: " << m_MaxLOD); return; } m_ShadingEnabled[lod] = state; } bool RenderingManager::GetShading(unsigned int lod) { if (lod > m_MaxLOD) { itkWarningMacro(<< "LOD out of range requested: " << lod << " maxLOD: " << m_MaxLOD); return false; } return m_ShadingEnabled[lod]; } // enable/disable the clipping plane void RenderingManager::SetClippingPlaneStatus(bool status) { m_ClippingPlaneEnabled = status; } bool RenderingManager::GetClippingPlaneStatus() { return m_ClippingPlaneEnabled; } void RenderingManager::SetShadingValues(float ambient, float diffuse, float specular, float specpower) { m_ShadingValues[0] = ambient; m_ShadingValues[1] = diffuse; m_ShadingValues[2] = specular; m_ShadingValues[3] = specpower; } RenderingManager::FloatVector &RenderingManager::GetShadingValues() { return m_ShadingValues; } void RenderingManager::InitializePropertyList() { if (m_PropertyList.IsNull()) { m_PropertyList = PropertyList::New(); } this->SetProperty("coupled-zoom", BoolProperty::New(false)); this->SetProperty("coupled-plane-rotation", BoolProperty::New(false)); this->SetProperty("MIP-slice-rendering", BoolProperty::New(false)); } PropertyList::Pointer RenderingManager::GetPropertyList() const { return m_PropertyList; } BaseProperty *RenderingManager::GetProperty(const char *propertyKey) const { return m_PropertyList->GetProperty(propertyKey); } void RenderingManager::SetProperty(const char *propertyKey, BaseProperty *propertyValue) { m_PropertyList->SetProperty(propertyKey, propertyValue); } void RenderingManager::SetDataStorage(DataStorage *storage) { if (storage != nullptr) { m_DataStorage = storage; RenderingManager::RenderWindowVector::const_iterator iter; for (iter = m_AllRenderWindows.cbegin(); iter < m_AllRenderWindows.cend(); ++iter) { mitk::BaseRenderer::GetInstance((*iter))->SetDataStorage(m_DataStorage.GetPointer()); } } } mitk::DataStorage *RenderingManager::GetDataStorage() { return m_DataStorage; } void RenderingManager::SetRenderWindowFocus(vtkRenderWindow *focusWindow) { if (focusWindow != m_FocusedRenderWindow) { if (!focusWindow || (m_RenderWindowList.find(focusWindow) != m_RenderWindowList.cend())) { m_FocusedRenderWindow = focusWindow; this->InvokeEvent(FocusChangedEvent()); return; } MITK_ERROR << "Tried to set a RenderWindow that does not exist."; } } void RenderingManager::SetAntiAliasing(AntiAliasing antiAliasing) { if (m_AntiAliasing != antiAliasing) { auto renderingManager = mitk::RenderingManager::GetInstance(); auto renderWindows = renderingManager->GetAllRegisteredRenderWindows(); for (auto renderWindow : renderWindows) { auto renderers = renderWindow->GetRenderers(); if (nullptr != renderers) { renderers->InitTraversal(); auto renderer = renderers->GetNextItem(); while (nullptr != renderer) { renderer->SetUseFXAA(AntiAliasing::FastApproximate == antiAliasing); renderer = renderers->GetNextItem(); } renderingManager->RequestUpdate(renderWindow); } } m_AntiAliasing = antiAliasing; } } // Create and register generic RenderingManagerFactory. TestingRenderingManagerFactory renderingManagerFactory; } // namespace diff --git a/Modules/Core/src/IO/mitkIOMimeTypes.cpp b/Modules/Core/src/IO/mitkIOMimeTypes.cpp index e9cf938b29..34c558825a 100644 --- a/Modules/Core/src/IO/mitkIOMimeTypes.cpp +++ b/Modules/Core/src/IO/mitkIOMimeTypes.cpp @@ -1,359 +1,363 @@ /*============================================================================ 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 "mitkIOMimeTypes.h" #include "mitkCustomMimeType.h" #include "mitkLogMacros.h" #include "itkGDCMImageIO.h" #include "itkMetaDataObject.h" #include #include namespace mitk { IOMimeTypes::BaseDicomMimeType::BaseDicomMimeType(const std::string& name) : CustomMimeType(name) { this->AddExtension("gdcm"); this->AddExtension("dcm"); this->AddExtension("DCM"); this->AddExtension("dc3"); this->AddExtension("DC3"); this->AddExtension("ima"); this->AddExtension("img"); this->SetCategory(CATEGORY_IMAGES()); this->SetComment("DICOM"); } bool IOMimeTypes::BaseDicomMimeType::AppliesTo(const std::string &path) const { // check whether directory or file // if directory try to find first file within it instead bool pathIsDirectory = itksys::SystemTools::FileIsDirectory(path); std::string filepath = path; if (pathIsDirectory) { itksys::Directory input; input.Load(path.c_str()); std::vector files; for (unsigned long idx = 0; idxMatchesExtension(filename)) { std::string fullpath = path + "/" + std::string(input.GetFile(idx)); files.push_back(fullpath.c_str()); } } - filepath = files.front(); + if (!files.empty()) + { + filepath = files.front(); + } } // Ask the GDCM ImageIO class directly itk::GDCMImageIO::Pointer gdcmIO = itk::GDCMImageIO::New(); gdcmIO->SetFileName(filepath); try { gdcmIO->ReadImageInformation(); } catch (const itk::ExceptionObject & /*err*/) { return false; } //DICOMRT modalities have specific reader, don't read with normal DICOM readers std::string modality; itk::MetaDataDictionary& dict = gdcmIO->GetMetaDataDictionary(); itk::ExposeMetaData(dict, "0008|0060", modality); MITK_DEBUG << "DICOM Modality detected by MimeType "<< this->GetName() << " is " << modality; if (modality == "RTSTRUCT" || modality == "RTDOSE" || modality == "RTPLAN") { return false; } else { return gdcmIO->CanReadFile(filepath.c_str()); } } IOMimeTypes::BaseDicomMimeType*IOMimeTypes::BaseDicomMimeType::Clone() const { return new BaseDicomMimeType(*this); } IOMimeTypes::DicomMimeType::DicomMimeType() : BaseDicomMimeType(DICOM_MIMETYPE_NAME()) { } IOMimeTypes::DicomMimeType* IOMimeTypes::DicomMimeType::Clone() const { return new DicomMimeType(*this); } std::vector IOMimeTypes::Get() { std::vector mimeTypes; // order matters here (descending rank for mime types) mimeTypes.push_back(NRRD_MIMETYPE().Clone()); mimeTypes.push_back(NIFTI_MIMETYPE().Clone()); mimeTypes.push_back(VTK_IMAGE_MIMETYPE().Clone()); mimeTypes.push_back(VTK_PARALLEL_IMAGE_MIMETYPE().Clone()); mimeTypes.push_back(VTK_IMAGE_LEGACY_MIMETYPE().Clone()); mimeTypes.push_back(DICOM_MIMETYPE().Clone()); mimeTypes.push_back(VTK_POLYDATA_MIMETYPE().Clone()); mimeTypes.push_back(VTK_PARALLEL_POLYDATA_MIMETYPE().Clone()); mimeTypes.push_back(VTK_POLYDATA_LEGACY_MIMETYPE().Clone()); mimeTypes.push_back(STEREOLITHOGRAPHY_MIMETYPE().Clone()); mimeTypes.push_back(WAVEFRONT_OBJ_MIMETYPE().Clone()); mimeTypes.push_back(STANFORD_PLY_MIMETYPE().Clone()); mimeTypes.push_back(RAW_MIMETYPE().Clone()); mimeTypes.push_back(POINTSET_MIMETYPE().Clone()); return mimeTypes; } CustomMimeType IOMimeTypes::VTK_IMAGE_MIMETYPE() { CustomMimeType mimeType(VTK_IMAGE_NAME()); mimeType.AddExtension("vti"); mimeType.SetCategory(CATEGORY_IMAGES()); mimeType.SetComment("VTK Image"); return mimeType; } CustomMimeType IOMimeTypes::VTK_IMAGE_LEGACY_MIMETYPE() { CustomMimeType mimeType(VTK_IMAGE_LEGACY_NAME()); mimeType.AddExtension("vtk"); mimeType.SetCategory(CATEGORY_IMAGES()); mimeType.SetComment("VTK Legacy Image"); return mimeType; } CustomMimeType IOMimeTypes::VTK_PARALLEL_IMAGE_MIMETYPE() { CustomMimeType mimeType(VTK_PARALLEL_IMAGE_NAME()); mimeType.AddExtension("pvti"); mimeType.SetCategory(CATEGORY_IMAGES()); mimeType.SetComment("VTK Parallel Image"); return mimeType; } CustomMimeType IOMimeTypes::VTK_POLYDATA_MIMETYPE() { CustomMimeType mimeType(VTK_POLYDATA_NAME()); mimeType.AddExtension("vtp"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("VTK PolyData"); return mimeType; } CustomMimeType IOMimeTypes::VTK_POLYDATA_LEGACY_MIMETYPE() { CustomMimeType mimeType(VTK_POLYDATA_LEGACY_NAME()); mimeType.AddExtension("vtk"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("VTK Legacy PolyData"); return mimeType; } CustomMimeType IOMimeTypes::VTK_PARALLEL_POLYDATA_MIMETYPE() { CustomMimeType mimeType(VTK_PARALLEL_POLYDATA_NAME()); mimeType.AddExtension("pvtp"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("VTK Parallel PolyData"); return mimeType; } CustomMimeType IOMimeTypes::STEREOLITHOGRAPHY_MIMETYPE() { CustomMimeType mimeType(STEREOLITHOGRAPHY_NAME()); mimeType.AddExtension("stl"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("Stereolithography"); return mimeType; } CustomMimeType IOMimeTypes::WAVEFRONT_OBJ_MIMETYPE() { CustomMimeType mimeType(WAVEFRONT_OBJ_NAME()); mimeType.AddExtension("obj"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("Wavefront OBJ"); return mimeType; } CustomMimeType IOMimeTypes::STANFORD_PLY_MIMETYPE() { CustomMimeType mimeType(STANFORD_PLY_NAME()); mimeType.AddExtension("ply"); mimeType.SetCategory(CATEGORY_SURFACES()); mimeType.SetComment("Stanford PLY"); return mimeType; } std::string IOMimeTypes::STEREOLITHOGRAPHY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".stl"; return name; } std::string IOMimeTypes::WAVEFRONT_OBJ_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".obj"; return name; } std::string IOMimeTypes::STANFORD_PLY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".ply"; return name; } std::string IOMimeTypes::DEFAULT_BASE_NAME() { static std::string name = "application/vnd.mitk"; return name; } std::string IOMimeTypes::CATEGORY_IMAGES() { static std::string cat = "Images"; return cat; } std::string IOMimeTypes::CATEGORY_SURFACES() { static std::string cat = "Surfaces"; return cat; } std::string IOMimeTypes::VTK_IMAGE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.image"; return name; } std::string IOMimeTypes::VTK_IMAGE_LEGACY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.image.legacy"; return name; } std::string IOMimeTypes::VTK_PARALLEL_IMAGE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.parallel.image"; return name; } std::string IOMimeTypes::VTK_POLYDATA_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.polydata"; return name; } std::string IOMimeTypes::VTK_POLYDATA_LEGACY_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.polydata.legacy"; return name; } std::string IOMimeTypes::VTK_PARALLEL_POLYDATA_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".vtk.parallel.polydata"; return name; } CustomMimeType IOMimeTypes::NRRD_MIMETYPE() { CustomMimeType mimeType(NRRD_MIMETYPE_NAME()); mimeType.AddExtension("nrrd"); mimeType.AddExtension("nhdr"); mimeType.SetCategory("Images"); mimeType.SetComment("NRRD"); return mimeType; } CustomMimeType IOMimeTypes::NIFTI_MIMETYPE() { CustomMimeType mimeType(NIFTI_MIMETYPE_NAME()); mimeType.AddExtension("nii"); mimeType.AddExtension("nii.gz"); mimeType.AddExtension("hdr"); mimeType.AddExtension("hdr.gz"); mimeType.AddExtension("img"); mimeType.AddExtension("img.gz"); mimeType.AddExtension("nia"); mimeType.SetCategory("Images"); mimeType.SetComment("Nifti"); return mimeType; } CustomMimeType IOMimeTypes::RAW_MIMETYPE() { CustomMimeType mimeType(RAW_MIMETYPE_NAME()); mimeType.AddExtension("raw"); mimeType.SetCategory("Images"); mimeType.SetComment("Raw data"); return mimeType; } IOMimeTypes::DicomMimeType IOMimeTypes::DICOM_MIMETYPE() { return DicomMimeType(); } std::string IOMimeTypes::NRRD_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.nrrd"; return name; } std::string IOMimeTypes::NIFTI_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.nifti"; return name; } std::string IOMimeTypes::RAW_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.raw"; return name; } std::string IOMimeTypes::DICOM_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".image.dicom"; return name; } CustomMimeType IOMimeTypes::POINTSET_MIMETYPE() { CustomMimeType mimeType(POINTSET_MIMETYPE_NAME()); mimeType.AddExtension("mps"); mimeType.SetCategory("Point Sets"); mimeType.SetComment("MITK Point Set"); return mimeType; } std::string IOMimeTypes::POINTSET_MIMETYPE_NAME() { static std::string name = DEFAULT_BASE_NAME() + ".pointset"; return name; } CustomMimeType IOMimeTypes::GEOMETRY_DATA_MIMETYPE() { mitk::CustomMimeType mimeType(DEFAULT_BASE_NAME() + ".geometrydata"); mimeType.AddExtension("mitkgeometry"); mimeType.SetCategory("Geometries"); mimeType.SetComment("GeometryData object"); return mimeType; } } diff --git a/Modules/Core/src/IO/mitkIOUtil.cpp b/Modules/Core/src/IO/mitkIOUtil.cpp index 7c5f032f04..9089e4bd69 100644 --- a/Modules/Core/src/IO/mitkIOUtil.cpp +++ b/Modules/Core/src/IO/mitkIOUtil.cpp @@ -1,1062 +1,1063 @@ /*============================================================================ 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 "mitkIOUtil.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include // ITK #include // VTK #include #include #include #include #include #ifdef US_PLATFORM_WINDOWS #include namespace { std::wstring MultiByteToWideChar(const std::string& mbString, UINT codePage) { auto numChars = ::MultiByteToWideChar(codePage, 0, mbString.data(), mbString.size(), nullptr, 0); if (0 >= numChars) mitkThrow() << "Failure to convert multi-byte character string to wide character string"; std::wstring wString; wString.resize(numChars); ::MultiByteToWideChar(codePage, 0, mbString.data(), mbString.size(), &wString[0], static_cast(wString.size())); return wString; } std::string WideCharToMultiByte(const std::wstring& wString, UINT codePage) { auto numChars = ::WideCharToMultiByte(codePage, 0, wString.data(), wString.size(), nullptr, 0, nullptr, nullptr); if (0 >= numChars) mitkThrow() << "Failure to convert wide character string to multi-byte character string"; std::string mbString; mbString.resize(numChars); ::WideCharToMultiByte(codePage, 0, wString.data(), wString.size(), &mbString[0], static_cast(mbString.size()), nullptr, nullptr); return mbString; } } #endif static std::string GetLastErrorStr() { #ifdef US_PLATFORM_POSIX return std::string(strerror(errno)); #else // Retrieve the system error message for the last-error code LPVOID lpMsgBuf; DWORD dw = GetLastError(); FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, nullptr); std::string errMsg((LPCTSTR)lpMsgBuf); LocalFree(lpMsgBuf); return errMsg; #endif } #ifdef US_PLATFORM_WINDOWS #include #include // make the posix flags point to the obsolte bsd types on windows #define S_IRUSR S_IREAD #define S_IWUSR S_IWRITE #else #include #include #include #endif #include #include static const char validLetters[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; // A cross-platform version of the mkstemps function static int mkstemps_compat(char *tmpl, int suffixlen) { static unsigned long long value = 0; int savedErrno = errno; // Lower bound on the number of temporary files to attempt to generate. #define ATTEMPTS_MIN (62 * 62 * 62) /* The number of times to attempt to generate a temporary file. To conform to POSIX, this must be no smaller than TMP_MAX. */ #if ATTEMPTS_MIN < TMP_MAX const unsigned int attempts = TMP_MAX; #else const unsigned int attempts = ATTEMPTS_MIN; #endif const int len = strlen(tmpl); if ((len - suffixlen) < 6 || strncmp(&tmpl[len - 6 - suffixlen], "XXXXXX", 6)) { errno = EINVAL; return -1; } /* This is where the Xs start. */ char *XXXXXX = &tmpl[len - 6 - suffixlen]; /* Get some more or less random data. */ #ifdef US_PLATFORM_WINDOWS { SYSTEMTIME stNow; FILETIME ftNow; // get system time GetSystemTime(&stNow); stNow.wMilliseconds = 500; if (!SystemTimeToFileTime(&stNow, &ftNow)) { errno = -1; return -1; } unsigned long long randomTimeBits = ((static_cast(ftNow.dwHighDateTime) << 32) | static_cast(ftNow.dwLowDateTime)); value = randomTimeBits ^ static_cast(GetCurrentThreadId()); } #else { struct timeval tv; gettimeofday(&tv, nullptr); unsigned long long randomTimeBits = ((static_cast(tv.tv_usec) << 32) | static_cast(tv.tv_sec)); value = randomTimeBits ^ static_cast(getpid()); } #endif for (unsigned int count = 0; count < attempts; value += 7777, ++count) { unsigned long long v = value; /* Fill in the random bits. */ XXXXXX[0] = validLetters[v % 62]; v /= 62; XXXXXX[1] = validLetters[v % 62]; v /= 62; XXXXXX[2] = validLetters[v % 62]; v /= 62; XXXXXX[3] = validLetters[v % 62]; v /= 62; XXXXXX[4] = validLetters[v % 62]; v /= 62; XXXXXX[5] = validLetters[v % 62]; int fd = open(tmpl, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); if (fd >= 0) { errno = savedErrno; return fd; } else if (errno != EEXIST) { return -1; } } /* We got out of the loop because we ran out of combinations to try. */ errno = EEXIST; return -1; } // A cross-platform version of the POSIX mkdtemp function static char *mkdtemps_compat(char *tmpl, int suffixlen) { static unsigned long long value = 0; int savedErrno = errno; // Lower bound on the number of temporary dirs to attempt to generate. #define ATTEMPTS_MIN (62 * 62 * 62) /* The number of times to attempt to generate a temporary dir. To conform to POSIX, this must be no smaller than TMP_MAX. */ #if ATTEMPTS_MIN < TMP_MAX const unsigned int attempts = TMP_MAX; #else const unsigned int attempts = ATTEMPTS_MIN; #endif const int len = strlen(tmpl); if ((len - suffixlen) < 6 || strncmp(&tmpl[len - 6 - suffixlen], "XXXXXX", 6)) { errno = EINVAL; return nullptr; } /* This is where the Xs start. */ char *XXXXXX = &tmpl[len - 6 - suffixlen]; /* Get some more or less random data. */ #ifdef US_PLATFORM_WINDOWS { SYSTEMTIME stNow; FILETIME ftNow; // get system time GetSystemTime(&stNow); stNow.wMilliseconds = 500; if (!SystemTimeToFileTime(&stNow, &ftNow)) { errno = -1; return nullptr; } unsigned long long randomTimeBits = ((static_cast(ftNow.dwHighDateTime) << 32) | static_cast(ftNow.dwLowDateTime)); value = randomTimeBits ^ static_cast(GetCurrentThreadId()); } #else { struct timeval tv; gettimeofday(&tv, nullptr); unsigned long long randomTimeBits = ((static_cast(tv.tv_usec) << 32) | static_cast(tv.tv_sec)); value = randomTimeBits ^ static_cast(getpid()); } #endif unsigned int count = 0; for (; count < attempts; value += 7777, ++count) { unsigned long long v = value; /* Fill in the random bits. */ XXXXXX[0] = validLetters[v % 62]; v /= 62; XXXXXX[1] = validLetters[v % 62]; v /= 62; XXXXXX[2] = validLetters[v % 62]; v /= 62; XXXXXX[3] = validLetters[v % 62]; v /= 62; XXXXXX[4] = validLetters[v % 62]; v /= 62; XXXXXX[5] = validLetters[v % 62]; #ifdef US_PLATFORM_WINDOWS int fd = _mkdir(tmpl); //, _S_IREAD | _S_IWRITE | _S_IEXEC); #else int fd = mkdir(tmpl, S_IRUSR | S_IWUSR | S_IXUSR); #endif if (fd >= 0) { errno = savedErrno; return tmpl; } else if (errno != EEXIST) { return nullptr; } } /* We got out of the loop because we ran out of combinations to try. */ errno = EEXIST; return nullptr; } //#endif //************************************************************** // mitk::IOUtil method definitions namespace mitk { struct IOUtil::Impl { struct FixedReaderOptionsFunctor : public ReaderOptionsFunctorBase { FixedReaderOptionsFunctor(const IFileReader::Options &options) : m_Options(options) {} bool operator()(LoadInfo &loadInfo) const override { IFileReader *reader = loadInfo.m_ReaderSelector.GetSelected().GetReader(); if (reader) { reader->SetOptions(m_Options); } return false; } private: const IFileReader::Options &m_Options; }; struct FixedWriterOptionsFunctor : public WriterOptionsFunctorBase { FixedWriterOptionsFunctor(const IFileReader::Options &options) : m_Options(options) {} bool operator()(SaveInfo &saveInfo) const override { IFileWriter *writer = saveInfo.m_WriterSelector.GetSelected().GetWriter(); if (writer) { writer->SetOptions(m_Options); } return false; } private: const IFileWriter::Options &m_Options; }; static BaseData::Pointer LoadBaseDataFromFile(const std::string &path, const ReaderOptionsFunctorBase* optionsCallback = nullptr); }; BaseData::Pointer IOUtil::Impl::LoadBaseDataFromFile(const std::string &path, const ReaderOptionsFunctorBase *optionsCallback) { std::vector baseDataList = Load(path, optionsCallback); // The Load(path) call above should throw an exception if nothing could be loaded assert(!baseDataList.empty()); return baseDataList.front(); } std::string IOUtil::Local8BitToUtf8(const std::string& local8BitStr) { #ifdef US_PLATFORM_WINDOWS try { return WideCharToMultiByte(MultiByteToWideChar(local8BitStr, CP_ACP), CP_UTF8); } catch (const mitk::Exception&) { MITK_WARN << "String conversion from current code page to UTF-8 failed. Input string is returned unmodified."; } #endif return local8BitStr; } std::string IOUtil::Utf8ToLocal8Bit(const std::string& utf8Str) { #ifdef US_PLATFORM_WINDOWS try { return WideCharToMultiByte(MultiByteToWideChar(utf8Str, CP_UTF8), CP_ACP); } catch (const mitk::Exception&) { MITK_WARN << "String conversion from UTF-8 to current code page failed. Input string is returned unmodified."; } #endif return utf8Str; } #ifdef US_PLATFORM_WINDOWS std::string IOUtil::GetProgramPath() { char path[512]; std::size_t index = std::string(path, GetModuleFileName(nullptr, path, 512)).find_last_of('\\'); return std::string(path, index); } #elif defined(US_PLATFORM_APPLE) #include std::string IOUtil::GetProgramPath() { char path[512]; uint32_t size = sizeof(path); if (_NSGetExecutablePath(path, &size) == 0) { std::size_t index = std::string(path).find_last_of('/'); std::string strPath = std::string(path, index); // const char* execPath = strPath.c_str(); // mitk::StandardFileLocations::GetInstance()->AddDirectoryForSearch(execPath,false); return strPath; } return std::string(); } #else #include #include #include std::string IOUtil::GetProgramPath() { std::stringstream ss; ss << "/proc/" << getpid() << "/exe"; char proc[512] = {0}; ssize_t ch = readlink(ss.str().c_str(), proc, 512); if (ch == -1) return std::string(); std::size_t index = std::string(proc).find_last_of('/'); return std::string(proc, index); } #endif char IOUtil::GetDirectorySeparator() { #ifdef US_PLATFORM_WINDOWS return '\\'; #else return '/'; #endif } std::string IOUtil::GetTempPath() { static std::string result; if (result.empty()) { #ifdef US_PLATFORM_WINDOWS char tempPathTestBuffer[1]; DWORD bufferLength = ::GetTempPath(1, tempPathTestBuffer); if (bufferLength == 0) { mitkThrow() << GetLastErrorStr(); } std::vector tempPath(bufferLength); bufferLength = ::GetTempPath(bufferLength, &tempPath[0]); if (bufferLength == 0) { mitkThrow() << GetLastErrorStr(); } result.assign(tempPath.begin(), tempPath.begin() + static_cast(bufferLength)); #else result = "/tmp/"; #endif } return result; } std::string IOUtil::CreateTemporaryFile(const std::string &templateName, std::string path) { std::ofstream tmpOutputStream; std::string returnValue = CreateTemporaryFile(tmpOutputStream, templateName, path); tmpOutputStream.close(); return returnValue; } std::string IOUtil::CreateTemporaryFile(std::ofstream &f, const std::string &templateName, std::string path) { return CreateTemporaryFile(f, std::ios_base::out | std::ios_base::trunc, templateName, path); } std::string IOUtil::CreateTemporaryFile(std::ofstream &f, std::ios_base::openmode mode, const std::string &templateName, std::string path) { if (path.empty()) { path = GetTempPath(); } path += templateName; std::vector dst_path(path.begin(), path.end()); dst_path.push_back('\0'); std::size_t lastX = path.find_last_of('X'); std::size_t firstX = path.find_last_not_of('X', lastX); int firstNonX = firstX == std::string::npos ? -1 : firstX - 1; while (lastX != std::string::npos && (lastX - firstNonX) < 6) { lastX = path.find_last_of('X', firstX); firstX = path.find_last_not_of('X', lastX); firstNonX = firstX == std::string::npos ? -1 : firstX - 1; } std::size_t suffixlen = lastX == std::string::npos ? path.size() : path.size() - lastX - 1; int fd = mkstemps_compat(&dst_path[0], suffixlen); if (fd != -1) { path.assign(dst_path.begin(), dst_path.end() - 1); f.open(path.c_str(), mode | std::ios_base::out | std::ios_base::trunc); close(fd); } else { mitkThrow() << "Creating temporary file " << &dst_path[0] << " failed: " << GetLastErrorStr(); } return path; } std::string IOUtil::CreateTemporaryDirectory(const std::string &templateName, std::string path) { if (path.empty()) { path = GetTempPath(); } path += GetDirectorySeparator() + templateName; std::vector dst_path(path.begin(), path.end()); dst_path.push_back('\0'); std::size_t lastX = path.find_last_of('X'); std::size_t firstX = path.find_last_not_of('X', lastX); int firstNonX = firstX == std::string::npos ? -1 : firstX - 1; while (lastX != std::string::npos && (lastX - firstNonX) < 6) { lastX = path.find_last_of('X', firstX); firstX = path.find_last_not_of('X', lastX); firstNonX = firstX == std::string::npos ? -1 : firstX - 1; } std::size_t suffixlen = lastX == std::string::npos ? path.size() : path.size() - lastX - 1; if (mkdtemps_compat(&dst_path[0], suffixlen) == nullptr) { mitkThrow() << "Creating temporary directory " << &dst_path[0] << " failed: " << GetLastErrorStr(); } path.assign(dst_path.begin(), dst_path.end() - 1); return path; } DataStorage::SetOfObjects::Pointer IOUtil::Load(const std::string &path, DataStorage &storage, const ReaderOptionsFunctorBase *optionsCallback) { std::vector paths; paths.push_back(path); return Load(paths, storage, optionsCallback); } DataStorage::SetOfObjects::Pointer IOUtil::Load(const std::string &path, const IFileReader::Options &options, DataStorage &storage) { std::vector loadInfos; loadInfos.push_back(LoadInfo(path)); DataStorage::SetOfObjects::Pointer nodeResult = DataStorage::SetOfObjects::New(); Impl::FixedReaderOptionsFunctor optionsCallback(options); std::string errMsg = Load(loadInfos, nodeResult, &storage, &optionsCallback); if (!errMsg.empty()) { mitkThrow() << errMsg; } return nodeResult; } std::vector IOUtil::Load(const std::string &path, const ReaderOptionsFunctorBase *optionsCallback) { std::vector paths; paths.push_back(path); return Load(paths, optionsCallback); } std::vector IOUtil::Load(const std::string &path, const IFileReader::Options &options) { std::vector loadInfos; loadInfos.push_back(LoadInfo(path)); Impl::FixedReaderOptionsFunctor optionsCallback(options); std::string errMsg = Load(loadInfos, nullptr, nullptr, &optionsCallback); if (!errMsg.empty()) { mitkThrow() << errMsg; } return loadInfos.front().m_Output; } DataStorage::SetOfObjects::Pointer IOUtil::Load(const std::vector &paths, DataStorage &storage, const ReaderOptionsFunctorBase *optionsCallback) { DataStorage::SetOfObjects::Pointer nodeResult = DataStorage::SetOfObjects::New(); std::vector loadInfos; for (const auto &loadInfo : paths) { loadInfos.push_back(loadInfo); } std::string errMsg = Load(loadInfos, nodeResult, &storage, optionsCallback); if (!errMsg.empty()) { mitkThrow() << errMsg; } return nodeResult; } std::vector IOUtil::Load(const std::vector &paths, const ReaderOptionsFunctorBase *optionsCallback) { std::vector result; std::vector loadInfos; for (const auto &loadInfo : paths) { loadInfos.push_back(loadInfo); } std::string errMsg = Load(loadInfos, nullptr, nullptr, optionsCallback); if (!errMsg.empty()) { mitkThrow() << errMsg; } for (std::vector::const_iterator iter = loadInfos.begin(), iterEnd = loadInfos.end(); iter != iterEnd; ++iter) { result.insert(result.end(), iter->m_Output.begin(), iter->m_Output.end()); } return result; } std::string IOUtil::Load(std::vector &loadInfos, DataStorage::SetOfObjects *nodeResult, DataStorage *ds, const ReaderOptionsFunctorBase *optionsCallback) { if (loadInfos.empty()) { return "No input files given"; } int filesToRead = loadInfos.size(); mitk::ProgressBar::GetInstance()->AddStepsToDo(2 * filesToRead); std::string errMsg; std::map usedReaderItems; std::vector< std::string > read_files; for (auto &loadInfo : loadInfos) { if(std::find(read_files.begin(), read_files.end(), loadInfo.m_Path) != read_files.end()) continue; std::vector readers = loadInfo.m_ReaderSelector.Get(); if (readers.empty()) { if (!itksys::SystemTools::FileExists(loadInfo.m_Path.c_str())) { errMsg += "File '" + loadInfo.m_Path + "' does not exist\n"; } else { errMsg += "No reader available for '" + loadInfo.m_Path + "'\n"; } continue; } bool callOptionsCallback = readers.size() > 1 || !readers.front().GetReader()->GetOptions().empty(); // check if we already used a reader which should be re-used std::vector currMimeTypes = loadInfo.m_ReaderSelector.GetMimeTypes(); std::string selectedMimeType; for (std::vector::const_iterator mimeTypeIter = currMimeTypes.begin(), mimeTypeIterEnd = currMimeTypes.end(); mimeTypeIter != mimeTypeIterEnd; ++mimeTypeIter) { std::map::const_iterator oldSelectedItemIter = usedReaderItems.find(mimeTypeIter->GetName()); if (oldSelectedItemIter != usedReaderItems.end()) { // we found an already used item for a mime-type which is contained // in the current reader set, check all current readers if there service // id equals the old reader for (std::vector::const_iterator currReaderItem = readers.begin(), currReaderItemEnd = readers.end(); currReaderItem != currReaderItemEnd; ++currReaderItem) { if (currReaderItem->GetMimeType().GetName() == mimeTypeIter->GetName() && currReaderItem->GetServiceId() == oldSelectedItemIter->second.GetServiceId() && currReaderItem->GetConfidenceLevel() >= oldSelectedItemIter->second.GetConfidenceLevel()) { // okay, we used the same reader already, re-use its options selectedMimeType = mimeTypeIter->GetName(); callOptionsCallback = false; loadInfo.m_ReaderSelector.Select(oldSelectedItemIter->second.GetServiceId()); loadInfo.m_ReaderSelector.GetSelected().GetReader()->SetOptions( oldSelectedItemIter->second.GetReader()->GetOptions()); break; } } if (!selectedMimeType.empty()) break; } } if (callOptionsCallback && optionsCallback) { callOptionsCallback = (*optionsCallback)(loadInfo); if (!callOptionsCallback && !loadInfo.m_Cancel) { usedReaderItems.erase(selectedMimeType); FileReaderSelector::Item selectedItem = loadInfo.m_ReaderSelector.GetSelected(); usedReaderItems.insert(std::make_pair(selectedItem.GetMimeType().GetName(), selectedItem)); } } if (loadInfo.m_Cancel) { errMsg += "Reading operation(s) cancelled."; break; } IFileReader *reader = loadInfo.m_ReaderSelector.GetSelected().GetReader(); if (reader == nullptr) { errMsg += "Unexpected nullptr reader."; break; } // Do the actual reading try { DataStorage::SetOfObjects::Pointer nodes; if (ds != nullptr) { nodes = reader->Read(*ds); std::vector< std::string > new_files = reader->GetReadFiles(); read_files.insert( read_files.end(), new_files.begin(), new_files.end() ); } else { nodes = DataStorage::SetOfObjects::New(); std::vector baseData = reader->Read(); for (auto iter = baseData.begin(); iter != baseData.end(); ++iter) { if (iter->IsNotNull()) { mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(*iter); nodes->InsertElement(nodes->Size(), node); } } std::vector< std::string > new_files = reader->GetReadFiles(); read_files.insert( read_files.end(), new_files.begin(), new_files.end() ); } for (DataStorage::SetOfObjects::ConstIterator nodeIter = nodes->Begin(), nodeIterEnd = nodes->End(); nodeIter != nodeIterEnd; ++nodeIter) { const mitk::DataNode::Pointer &node = nodeIter->Value(); mitk::BaseData::Pointer data = node->GetData(); if (data.IsNull()) { continue; } auto path = Local8BitToUtf8(loadInfo.m_Path); auto pathProp = mitk::StringProperty::New(path); data->SetProperty("path", pathProp); loadInfo.m_Output.push_back(data); if (nodeResult) { nodeResult->push_back(nodeIter->Value()); } } if (loadInfo.m_Output.empty() || (nodeResult && nodeResult->Size() == 0)) { errMsg += "Unknown read error occurred reading " + loadInfo.m_Path; } } catch (const std::exception &e) { errMsg += "Exception occured when reading file " + loadInfo.m_Path + ":\n" + e.what() + "\n\n"; } mitk::ProgressBar::GetInstance()->Progress(2); --filesToRead; } if (!errMsg.empty()) { MITK_ERROR << errMsg; } mitk::ProgressBar::GetInstance()->Progress(2 * filesToRead); return errMsg; } std::vector IOUtil::Load(const us::ModuleResource &usResource, std::ios_base::openmode mode) { us::ModuleResourceStream resStream(usResource, mode); mitk::CoreServicePointer mimeTypeProvider(mitk::CoreServices::GetMimeTypeProvider()); std::vector mimetypes = mimeTypeProvider->GetMimeTypesForFile(usResource.GetResourcePath()); std::vector data; if (mimetypes.empty()) { mitkThrow() << "No mimetype for resource stream: " << usResource.GetResourcePath(); return data; } mitk::FileReaderRegistry fileReaderRegistry; std::vector> refs = fileReaderRegistry.GetReferences(mimetypes[0]); if (refs.empty()) { mitkThrow() << "No reader available for resource stream: " << usResource.GetResourcePath(); return data; } mitk::IFileReader *reader = fileReaderRegistry.GetReader(refs[0]); reader->SetInput(usResource.GetResourcePath(), &resStream); data = reader->Read(); return data; } void IOUtil::Save(const BaseData *data, const std::string &path, bool setPathProperty) { Save(data, path, IFileWriter::Options(), setPathProperty); } void IOUtil::Save(const BaseData *data, const std::string &path, const IFileWriter::Options &options, bool setPathProperty) { Save(data, std::string(), path, options, setPathProperty); } void IOUtil::Save(const BaseData *data, const std::string &mimeType, const std::string &path, bool addExtension, bool setPathProperty) { Save(data, mimeType, path, IFileWriter::Options(), addExtension, setPathProperty); } void IOUtil::Save(const BaseData *data, const std::string &mimeType, const std::string &path, const IFileWriter::Options &options, bool addExtension, bool setPathProperty) { if ((data == nullptr) || (data->IsEmpty())) mitkThrow() << "BaseData cannotbe null or empty for save methods in IOUtil.h."; std::string errMsg; if (options.empty()) { errMsg = Save(data, mimeType, path, nullptr, addExtension, setPathProperty); } else { Impl::FixedWriterOptionsFunctor optionsCallback(options); errMsg = Save(data, mimeType, path, &optionsCallback, addExtension, setPathProperty); } if (!errMsg.empty()) { mitkThrow() << errMsg; } } void IOUtil::Save(std::vector &saveInfos, bool setPathProperty) { std::string errMsg = Save(saveInfos, nullptr, setPathProperty); if (!errMsg.empty()) { mitkThrow() << errMsg; } } std::string IOUtil::Save(const BaseData *data, const std::string &mimeTypeName, const std::string &path, WriterOptionsFunctorBase *optionsCallback, bool addExtension, bool setPathProperty) { if (path.empty()) { return "No output filename given"; } mitk::CoreServicePointer mimeTypeProvider(mitk::CoreServices::GetMimeTypeProvider()); MimeType mimeType = mimeTypeProvider->GetMimeTypeForName(mimeTypeName); SaveInfo saveInfo(data, mimeType, path); std::string ext = itksys::SystemTools::GetFilenameExtension(path); if (saveInfo.m_WriterSelector.IsEmpty()) { return std::string("No suitable writer found for the current data of type ") + data->GetNameOfClass() + (mimeType.IsValid() ? (std::string(" and mime-type ") + mimeType.GetName()) : std::string()) + (ext.empty() ? std::string() : (std::string(" with extension ") + ext)); } // Add an extension if not already specified if (ext.empty() && addExtension) { - saveInfo.m_MimeType.GetExtensions().empty() ? std::string() : "." + saveInfo.m_MimeType.GetExtensions().front(); + ext = saveInfo.m_MimeType.GetExtensions().empty() ? std::string() : "." + saveInfo.m_MimeType.GetExtensions().front(); + saveInfo.m_Path += ext; } std::vector infos; infos.push_back(saveInfo); return Save(infos, optionsCallback, setPathProperty); } std::string IOUtil::Save(std::vector &saveInfos, WriterOptionsFunctorBase *optionsCallback, bool setPathProperty) { if (saveInfos.empty()) { return "No data for saving available"; } int filesToWrite = saveInfos.size(); mitk::ProgressBar::GetInstance()->AddStepsToDo(2 * filesToWrite); std::string errMsg; std::set usedSaveInfos; for (auto &saveInfo : saveInfos) { const std::string baseDataType = saveInfo.m_BaseData->GetNameOfClass(); std::vector writers = saveInfo.m_WriterSelector.Get(); // Error out if no compatible Writer was found if (writers.empty()) { errMsg += std::string("No writer available for ") + baseDataType + " data.\n"; continue; } bool callOptionsCallback = writers.size() > 1 || !writers[0].GetWriter()->GetOptions().empty(); // check if we already used a writer for this base data type // which should be re-used auto oldSaveInfoIter = usedSaveInfos.find(saveInfo); if (oldSaveInfoIter != usedSaveInfos.end()) { // we previously saved a base data object of the same data with the same mime-type, // check if the same writer is contained in the current writer set and if the // confidence level matches FileWriterSelector::Item oldSelectedItem = oldSaveInfoIter->m_WriterSelector.Get(oldSaveInfoIter->m_WriterSelector.GetSelectedId()); for (std::vector::const_iterator currWriterItem = writers.begin(), currWriterItemEnd = writers.end(); currWriterItem != currWriterItemEnd; ++currWriterItem) { if (currWriterItem->GetServiceId() == oldSelectedItem.GetServiceId() && currWriterItem->GetConfidenceLevel() >= oldSelectedItem.GetConfidenceLevel()) { // okay, we used the same writer already, re-use its options callOptionsCallback = false; saveInfo.m_WriterSelector.Select(oldSaveInfoIter->m_WriterSelector.GetSelectedId()); saveInfo.m_WriterSelector.GetSelected().GetWriter()->SetOptions(oldSelectedItem.GetWriter()->GetOptions()); break; } } } if (callOptionsCallback && optionsCallback) { callOptionsCallback = (*optionsCallback)(saveInfo); if (!callOptionsCallback && !saveInfo.m_Cancel) { usedSaveInfos.erase(saveInfo); usedSaveInfos.insert(saveInfo); } } if (saveInfo.m_Cancel) { errMsg += "Writing operation(s) cancelled."; break; } IFileWriter *writer = saveInfo.m_WriterSelector.GetSelected().GetWriter(); if (writer == nullptr) { errMsg += "Unexpected nullptr writer."; break; } // Do the actual writing try { writer->SetOutputLocation(saveInfo.m_Path); writer->Write(); } catch (const std::exception &e) { errMsg += std::string("Exception occurred when writing to ") + saveInfo.m_Path + ":\n" + e.what() + "\n"; } if (setPathProperty) saveInfo.m_BaseData->GetPropertyList()->SetStringProperty("path", Local8BitToUtf8(saveInfo.m_Path).c_str()); mitk::ProgressBar::GetInstance()->Progress(2); --filesToWrite; } if (!errMsg.empty()) { MITK_ERROR << errMsg; } mitk::ProgressBar::GetInstance()->Progress(2 * filesToWrite); return errMsg; } IOUtil::SaveInfo::SaveInfo(const BaseData *baseData, const MimeType &mimeType, const std::string &path) : m_BaseData(baseData), m_WriterSelector(baseData, mimeType.GetName(), path), m_MimeType(mimeType.IsValid() ? mimeType // use the original mime-type : (m_WriterSelector.IsEmpty() ? mimeType // no writer found, use the original invalid mime-type : m_WriterSelector.GetDefault().GetMimeType() // use the found default mime-type )), m_Path(path), m_Cancel(false) { } bool IOUtil::SaveInfo::operator<(const IOUtil::SaveInfo &other) const { int r = strcmp(m_BaseData->GetNameOfClass(), other.m_BaseData->GetNameOfClass()); if (r == 0) { return m_WriterSelector.GetSelected().GetMimeType() < other.m_WriterSelector.GetSelected().GetMimeType(); } return r < 0; } IOUtil::LoadInfo::LoadInfo(const std::string &path) : m_Path(path), m_ReaderSelector(path), m_Cancel(false) {} } diff --git a/Modules/Core/src/Interactions/mitkInteractionSchemeSwitcher.cpp b/Modules/Core/src/Interactions/mitkInteractionSchemeSwitcher.cpp index 7e5e8907ed..0b104903dc 100644 --- a/Modules/Core/src/Interactions/mitkInteractionSchemeSwitcher.cpp +++ b/Modules/Core/src/Interactions/mitkInteractionSchemeSwitcher.cpp @@ -1,102 +1,102 @@ /*============================================================================ 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 "mitkInteractionSchemeSwitcher.h" -// us -#include -#include - // mitk core #include #include mitk::InteractionSchemeSwitcher::InteractionSchemeSwitcher() - : m_InteractionScheme(MITKStandard) { // nothing here } mitk::InteractionSchemeSwitcher::~InteractionSchemeSwitcher() { // nothing here } -void mitk::InteractionSchemeSwitcher::SetInteractionScheme(mitk::InteractionEventHandler* interactionEventHandler, InteractionScheme interactionScheme) +void mitk::InteractionSchemeSwitcher::SetInteractionScheme(InteractionEventHandler* interactionEventHandler, InteractionScheme interactionScheme) { if (nullptr == interactionEventHandler) { mitkThrow() << "Not a valid interaction event handler to set the interaction scheme."; } switch (interactionScheme) { // MITK MODE case MITKStandard: { interactionEventHandler->SetEventConfig("DisplayConfigMITK.xml"); break; } case MITKRotationUncoupled: { interactionEventHandler->SetEventConfig("DisplayConfigMITKRotationUnCoupled.xml"); break; } case MITKRotationCoupled: { interactionEventHandler->SetEventConfig("DisplayConfigMITKRotation.xml"); break; } case MITKSwivel: { interactionEventHandler->SetEventConfig("DisplayConfigMITKSwivel.xml"); break; } // PACS MODE + case PACSBase: + { + interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); + break; + } case PACSStandard: { interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); + interactionEventHandler->AddEventConfig("DisplayConfigPACSCrosshair.xml"); break; } case PACSLevelWindow: { interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); interactionEventHandler->AddEventConfig("DisplayConfigPACSLevelWindow.xml"); break; } case PACSPan: { interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); interactionEventHandler->AddEventConfig("DisplayConfigPACSPan.xml"); break; } case PACSScroll: { interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); interactionEventHandler->AddEventConfig("DisplayConfigPACSScroll.xml"); break; } case PACSZoom: { interactionEventHandler->SetEventConfig("DisplayConfigPACS.xml"); interactionEventHandler->AddEventConfig("DisplayConfigPACSZoom.xml"); break; } default: { interactionEventHandler->SetEventConfig("DisplayConfigMITK.xml"); } } - m_InteractionScheme = interactionScheme; InvokeEvent(InteractionSchemeChangedEvent()); } diff --git a/Modules/Core/src/Interactions/mitkMouseModeSwitcher.cpp b/Modules/Core/src/Interactions/mitkMouseModeSwitcher.cpp deleted file mode 100644 index f9b2c7a70d..0000000000 --- a/Modules/Core/src/Interactions/mitkMouseModeSwitcher.cpp +++ /dev/null @@ -1,125 +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 "mitkMouseModeSwitcher.h" -// us -#include "usGetModuleContext.h" -#include "usModuleContext.h" - -#include "mitkInteractionEventObserver.h" - -mitk::MouseModeSwitcher::MouseModeSwitcher() - : m_ActiveInteractionScheme(MITK), m_ActiveMouseMode(MousePointer), m_CurrentObserver(nullptr) -{ - this->InitializeListeners(); - this->SetInteractionScheme(m_ActiveInteractionScheme); -} - -mitk::MouseModeSwitcher::~MouseModeSwitcher() -{ - m_ServiceRegistration.Unregister(); -} - -void mitk::MouseModeSwitcher::InitializeListeners() -{ - if (m_CurrentObserver.IsNull()) - { - m_CurrentObserver = mitk::DisplayInteractor::New(); - m_CurrentObserver->LoadStateMachine("DisplayInteraction.xml"); - m_CurrentObserver->SetEventConfig("DisplayConfigMITK.xml"); - // Register as listener via micro services - us::ServiceProperties props; - props["name"] = std::string("DisplayInteractor"); - m_ServiceRegistration = - us::GetModuleContext()->RegisterService(m_CurrentObserver.GetPointer(), props); - } -} - -void mitk::MouseModeSwitcher::SetInteractionScheme(InteractionScheme scheme) -{ - switch (scheme) - { - case MITK: - { - m_CurrentObserver->SetEventConfig("DisplayConfigMITK.xml"); - } - break; - case PACS: - { - m_CurrentObserver->SetEventConfig("DisplayConfigPACS.xml"); - } - break; - - case ROTATION: - { - m_CurrentObserver->SetEventConfig("DisplayConfigMITKRotationUnCoupled.xml"); - } - break; - - case ROTATIONLINKED: - { - m_CurrentObserver->SetEventConfig("DisplayConfigMITKRotation.xml"); - } - break; - - case SWIVEL: - { - m_CurrentObserver->SetEventConfig("DisplayConfigMITKSwivel.xml"); - } - break; - } - m_ActiveInteractionScheme = scheme; - this->InvokeEvent(MouseModeChangedEvent()); -} - -void mitk::MouseModeSwitcher::SelectMouseMode(MouseMode mode) -{ - if (m_ActiveInteractionScheme != PACS) - return; - - switch (mode) - { - case MousePointer: - { - m_CurrentObserver->SetEventConfig("DisplayConfigPACS.xml"); - break; - } // case 0 - case Scroll: - { - m_CurrentObserver->AddEventConfig("DisplayConfigPACSScroll.xml"); - - break; - } - case LevelWindow: - { - m_CurrentObserver->AddEventConfig("DisplayConfigPACSLevelWindow.xml"); - break; - } - case Zoom: - { - m_CurrentObserver->AddEventConfig("DisplayConfigPACSZoom.xml"); - break; - } - case Pan: - { - m_CurrentObserver->AddEventConfig("DisplayConfigPACSPan.xml"); - break; - } - } // end switch (mode) - m_ActiveMouseMode = mode; - this->InvokeEvent(MouseModeChangedEvent()); -} - -mitk::MouseModeSwitcher::MouseMode mitk::MouseModeSwitcher::GetCurrentMouseMode() const -{ - return m_ActiveMouseMode; -} diff --git a/Modules/Core/src/Rendering/mitkPointSetVtkMapper2D.cpp b/Modules/Core/src/Rendering/mitkPointSetVtkMapper2D.cpp index a94ac6b040..3df46593c5 100644 --- a/Modules/Core/src/Rendering/mitkPointSetVtkMapper2D.cpp +++ b/Modules/Core/src/Rendering/mitkPointSetVtkMapper2D.cpp @@ -1,745 +1,781 @@ /*============================================================================ 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 "mitkPointSetVtkMapper2D.h" // mitk includes #include "mitkVtkPropRenderer.h" #include #include #include #include // vtk includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include +namespace +{ + double GetScreenResolution(const mitk::BaseRenderer* renderer) + { + if (nullptr == renderer) + return 1.0; + + mitk::Point2D pD1, pD2; + pD1[0] = 0.0; + pD1[1] = 0.0; + pD2[0] = 0.0; + pD2[1] = 1.0; + + // Calculate world coordinates of in-plane screen pixels (0, 0) and (0, 1). + mitk::Point3D pW1, pW2; + renderer->DisplayToWorld(pD1, pW1); + renderer->DisplayToWorld(pD2, pW2); + + // For 2D renderers, the distance between these points is the screen resolution. + return pW1.EuclideanDistanceTo(pW2); + } +} + // constructor LocalStorage mitk::PointSetVtkMapper2D::LocalStorage::LocalStorage() { // points m_UnselectedPoints = vtkSmartPointer::New(); m_SelectedPoints = vtkSmartPointer::New(); m_ContourPoints = vtkSmartPointer::New(); // scales m_UnselectedScales = vtkSmartPointer::New(); m_SelectedScales = vtkSmartPointer::New(); // distances m_DistancesBetweenPoints = vtkSmartPointer::New(); // lines m_ContourLines = vtkSmartPointer::New(); // glyph source (provides the different shapes) m_UnselectedGlyphSource2D = vtkSmartPointer::New(); m_SelectedGlyphSource2D = vtkSmartPointer::New(); // glyphs m_UnselectedGlyph3D = vtkSmartPointer::New(); m_SelectedGlyph3D = vtkSmartPointer::New(); // polydata m_VtkUnselectedPointListPolyData = vtkSmartPointer::New(); m_VtkSelectedPointListPolyData = vtkSmartPointer::New(); m_VtkContourPolyData = vtkSmartPointer::New(); // actors m_UnselectedActor = vtkSmartPointer::New(); m_SelectedActor = vtkSmartPointer::New(); m_ContourActor = vtkSmartPointer::New(); // mappers m_VtkUnselectedPolyDataMapper = vtkSmartPointer::New(); m_VtkSelectedPolyDataMapper = vtkSmartPointer::New(); m_VtkContourPolyDataMapper = vtkSmartPointer::New(); // propassembly m_PropAssembly = vtkSmartPointer::New(); } // destructor LocalStorage mitk::PointSetVtkMapper2D::LocalStorage::~LocalStorage() { } // input for this mapper ( = point set) const mitk::PointSet *mitk::PointSetVtkMapper2D::GetInput() const { return static_cast(GetDataNode()->GetData()); } // constructor PointSetVtkMapper2D mitk::PointSetVtkMapper2D::PointSetVtkMapper2D() : m_ShowContour(false), m_CloseContour(false), m_ShowPoints(true), m_ShowDistances(false), m_DistancesDecimalDigits(1), m_ShowAngles(false), m_ShowDistantLines(false), m_LineWidth(1), m_PointLineWidth(1), m_Point2DSize(6), m_IDShapeProperty(mitk::PointSetShapeProperty::CROSS), m_FillShape(false), - m_DistanceToPlane(4.0f) + m_DistanceToPlane(4.0f), + m_FixedSizeOnScreen(false) { } // destructor mitk::PointSetVtkMapper2D::~PointSetVtkMapper2D() { } // reset mapper so that nothing is displayed e.g. toggle visiblity of the propassembly void mitk::PointSetVtkMapper2D::ResetMapper(BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); ls->m_PropAssembly->VisibilityOff(); } // returns propassembly vtkProp *mitk::PointSetVtkMapper2D::GetVtkProp(mitk::BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); return ls->m_PropAssembly; } static bool makePerpendicularVector2D(const mitk::Vector2D &in, mitk::Vector2D &out) { // The dot product of orthogonal vectors is zero. // In two dimensions the slopes of perpendicular lines are negative reciprocals. if ((fabs(in[0]) > 0) && ((fabs(in[0]) > fabs(in[1])) || (in[1] == 0))) { // negative reciprocal out[0] = -in[1] / in[0]; out[1] = 1; out.Normalize(); return true; } else if (fabs(in[1]) > 0) { out[0] = 1; // negative reciprocal out[1] = -in[0] / in[1]; out.Normalize(); return true; } else return false; } void mitk::PointSetVtkMapper2D::CreateVTKRenderObjects(mitk::BaseRenderer *renderer) { LocalStorage *ls = m_LSH.GetLocalStorage(renderer); unsigned i = 0; // The vtk text actors need to be removed manually from the propassembly // since the same vtk text actors are not overwriten within this function, // but new actors are added to the propassembly each time this function is executed. // Thus, the actors from the last call must be removed in the beginning. for (i = 0; i < ls->m_VtkTextLabelActors.size(); i++) { if (ls->m_PropAssembly->GetParts()->IsItemPresent(ls->m_VtkTextLabelActors.at(i))) ls->m_PropAssembly->RemovePart(ls->m_VtkTextLabelActors.at(i)); } for (i = 0; i < ls->m_VtkTextDistanceActors.size(); i++) { if (ls->m_PropAssembly->GetParts()->IsItemPresent(ls->m_VtkTextDistanceActors.at(i))) ls->m_PropAssembly->RemovePart(ls->m_VtkTextDistanceActors.at(i)); } for (i = 0; i < ls->m_VtkTextAngleActors.size(); i++) { if (ls->m_PropAssembly->GetParts()->IsItemPresent(ls->m_VtkTextAngleActors.at(i))) ls->m_PropAssembly->RemovePart(ls->m_VtkTextAngleActors.at(i)); } // initialize polydata here, otherwise we have update problems when // executing this function again ls->m_VtkUnselectedPointListPolyData = vtkSmartPointer::New(); ls->m_VtkSelectedPointListPolyData = vtkSmartPointer::New(); ls->m_VtkContourPolyData = vtkSmartPointer::New(); // get input point set and update the PointSet mitk::PointSet::Pointer input = const_cast(this->GetInput()); // only update the input data, if the property tells us to bool update = true; this->GetDataNode()->GetBoolProperty("updateDataOnRender", update); if (update == true) input->Update(); int timestep = this->GetTimestep(); mitk::PointSet::DataType::Pointer itkPointSet = input->GetPointSet(timestep); if (itkPointSet.GetPointer() == nullptr) { ls->m_PropAssembly->VisibilityOff(); return; } // iterator for point set mitk::PointSet::PointsContainer::Iterator pointsIter = itkPointSet->GetPoints()->Begin(); // PointDataContainer has additional information to each point, e.g. whether // it is selected or not mitk::PointSet::PointDataContainer::Iterator pointDataIter; pointDataIter = itkPointSet->GetPointData()->Begin(); // check if the list for the PointDataContainer is the same size as the PointsContainer. // If not, then the points were inserted manually and can not be visualized according to the PointData // (selected/unselected) bool pointDataBroken = (itkPointSet->GetPointData()->Size() != itkPointSet->GetPoints()->Size()); if (itkPointSet->GetPointData()->size() == 0 || pointDataBroken) { ls->m_PropAssembly->VisibilityOff(); return; } ls->m_PropAssembly->VisibilityOn(); // empty point sets, cellarrays, scalars ls->m_UnselectedPoints->Reset(); ls->m_SelectedPoints->Reset(); ls->m_ContourPoints->Reset(); ls->m_ContourLines->Reset(); ls->m_UnselectedScales->Reset(); ls->m_SelectedScales->Reset(); ls->m_DistancesBetweenPoints->Reset(); ls->m_VtkTextLabelActors.clear(); ls->m_VtkTextDistanceActors.clear(); ls->m_VtkTextAngleActors.clear(); ls->m_UnselectedScales->SetNumberOfComponents(3); ls->m_SelectedScales->SetNumberOfComponents(3); int NumberContourPoints = 0; bool pointsOnSameSideOfPlane = false; const int text2dDistance = 10; // initialize points with a random start value // current point in point set itk::Point point = pointsIter->Value(); mitk::Point3D p = point; // currently visited point mitk::Point3D lastP = point; // last visited point (predecessor in point set of "point") mitk::Vector3D vec; // p - lastP mitk::Vector3D lastVec; // lastP - point before lastP vec.Fill(0.0); lastVec.Fill(0.0); mitk::Point2D pt2d; pt2d[0] = point[0]; // projected_p in display coordinates pt2d[1] = point[1]; mitk::Point2D lastPt2d = pt2d; // last projected_p in display coordinates (predecessor in point set of "pt2d") mitk::Point2D preLastPt2d = pt2d; // projected_p in display coordinates before lastPt2 const mitk::PlaneGeometry *geo2D = renderer->GetCurrentWorldPlaneGeometry(); + double resolution = GetScreenResolution(renderer); vtkLinearTransform *dataNodeTransform = input->GetGeometry()->GetVtkTransform(); int count = 0; for (pointsIter = itkPointSet->GetPoints()->Begin(); pointsIter != itkPointSet->GetPoints()->End(); pointsIter++) { lastP = p; // valid for number of points count > 0 preLastPt2d = lastPt2d; // valid only for count > 1 lastPt2d = pt2d; // valid for number of points count > 0 lastVec = vec; // valid only for counter > 1 // get current point in point set point = pointsIter->Value(); // transform point { float vtkp[3]; itk2vtk(point, vtkp); dataNodeTransform->TransformPoint(vtkp, vtkp); vtk2itk(vtkp, point); } p[0] = point[0]; p[1] = point[1]; p[2] = point[2]; renderer->WorldToDisplay(p, pt2d); vec = p - lastP; // valid only for counter > 0 // compute distance to current plane float dist = geo2D->Distance(point); + // measure distance in screen pixel units if requested + if (m_FixedSizeOnScreen) + { + dist /= resolution; + } // draw markers on slices a certain distance away from the points // location according to the tolerance threshold (m_DistanceToPlane) if (dist < m_DistanceToPlane) { // is point selected or not? if (pointDataIter->Value().selected) { ls->m_SelectedPoints->InsertNextPoint(point[0], point[1], point[2]); // point is scaled according to its distance to the plane - ls->m_SelectedScales->InsertNextTuple3(std::max(0.0f, m_Point2DSize - (2 * dist)), 0, 0); + ls->m_SelectedScales->InsertNextTuple3( + std::max(0.0f, m_Point2DSize - (2 * dist)), 0, 0); } else { ls->m_UnselectedPoints->InsertNextPoint(point[0], point[1], point[2]); // point is scaled according to its distance to the plane - ls->m_UnselectedScales->InsertNextTuple3(std::max(0.0f, m_Point2DSize - (2 * dist)), 0, 0); + ls->m_UnselectedScales->InsertNextTuple3( + std::max(0.0f, m_Point2DSize - (2 * dist)), 0, 0); } //---- LABEL -----// // paint label for each point if available if (dynamic_cast(this->GetDataNode()->GetProperty("label")) != nullptr) { const char *pointLabel = dynamic_cast(this->GetDataNode()->GetProperty("label"))->GetValue(); std::string l = pointLabel; if (input->GetSize() > 1) { std::stringstream ss; ss << pointsIter->Index(); l.append(ss.str()); } ls->m_VtkTextActor = vtkSmartPointer::New(); ls->m_VtkTextActor->SetDisplayPosition(pt2d[0] + text2dDistance, pt2d[1] + text2dDistance); ls->m_VtkTextActor->SetInput(l.c_str()); ls->m_VtkTextActor->GetTextProperty()->SetOpacity(100); float unselectedColor[4] = {1.0, 1.0, 0.0, 1.0}; // check if there is a color property GetDataNode()->GetColor(unselectedColor); ls->m_VtkTextActor->GetTextProperty()->SetColor(unselectedColor[0], unselectedColor[1], unselectedColor[2]); ls->m_VtkTextLabelActors.push_back(ls->m_VtkTextActor); } } // draw contour, distance text and angle text in render window // lines between points, which intersect the current plane, are drawn if (m_ShowContour && count > 0) { ScalarType distance = renderer->GetCurrentWorldPlaneGeometry()->SignedDistance(point); ScalarType lastDistance = renderer->GetCurrentWorldPlaneGeometry()->SignedDistance(lastP); pointsOnSameSideOfPlane = (distance * lastDistance) > 0.5; // Points must be on different side of plane in order to draw a contour. // If "show distant lines" is enabled this condition is disregarded. if (!pointsOnSameSideOfPlane || m_ShowDistantLines) { vtkSmartPointer line = vtkSmartPointer::New(); ls->m_ContourPoints->InsertNextPoint(lastP[0], lastP[1], lastP[2]); line->GetPointIds()->SetId(0, NumberContourPoints); NumberContourPoints++; ls->m_ContourPoints->InsertNextPoint(point[0], point[1], point[2]); line->GetPointIds()->SetId(1, NumberContourPoints); NumberContourPoints++; ls->m_ContourLines->InsertNextCell(line); if (m_ShowDistances) // calculate and print distance between adjacent points { float distancePoints = point.EuclideanDistanceTo(lastP); std::stringstream buffer; buffer << std::fixed << std::setprecision(m_DistancesDecimalDigits) << distancePoints << " mm"; // compute desired display position of text Vector2D vec2d = pt2d - lastPt2d; makePerpendicularVector2D(vec2d, vec2d); // text is rendered within text2dDistance perpendicular to current line Vector2D pos2d = (lastPt2d.GetVectorFromOrigin() + pt2d.GetVectorFromOrigin()) * 0.5 + vec2d * text2dDistance; ls->m_VtkTextActor = vtkSmartPointer::New(); ls->m_VtkTextActor->SetDisplayPosition(pos2d[0], pos2d[1]); ls->m_VtkTextActor->SetInput(buffer.str().c_str()); ls->m_VtkTextActor->GetTextProperty()->SetColor(0.0, 1.0, 0.0); ls->m_VtkTextDistanceActors.push_back(ls->m_VtkTextActor); } if (m_ShowAngles && count > 1) // calculate and print angle between connected lines { std::stringstream buffer; buffer << angle(vec.GetVnlVector(), -lastVec.GetVnlVector()) * 180 / vnl_math::pi << "°"; // compute desired display position of text Vector2D vec2d = pt2d - lastPt2d; // first arm enclosing the angle vec2d.Normalize(); Vector2D lastVec2d = lastPt2d - preLastPt2d; // second arm enclosing the angle lastVec2d.Normalize(); vec2d = vec2d - lastVec2d; // vector connecting both arms vec2d.Normalize(); // middle between two vectors that enclose the angle Vector2D pos2d = lastPt2d.GetVectorFromOrigin() + vec2d * text2dDistance * text2dDistance; ls->m_VtkTextActor = vtkSmartPointer::New(); ls->m_VtkTextActor->SetDisplayPosition(pos2d[0], pos2d[1]); ls->m_VtkTextActor->SetInput(buffer.str().c_str()); ls->m_VtkTextActor->GetTextProperty()->SetColor(0.0, 1.0, 0.0); ls->m_VtkTextAngleActors.push_back(ls->m_VtkTextActor); } } } if (pointDataIter != itkPointSet->GetPointData()->End()) { pointDataIter++; count++; } } // add each single text actor to the assembly for (i = 0; i < ls->m_VtkTextLabelActors.size(); i++) { ls->m_PropAssembly->AddPart(ls->m_VtkTextLabelActors.at(i)); } for (i = 0; i < ls->m_VtkTextDistanceActors.size(); i++) { ls->m_PropAssembly->AddPart(ls->m_VtkTextDistanceActors.at(i)); } for (i = 0; i < ls->m_VtkTextAngleActors.size(); i++) { ls->m_PropAssembly->AddPart(ls->m_VtkTextAngleActors.at(i)); } //---- CONTOUR -----// // create lines between the points which intersect the plane if (m_ShowContour) { // draw line between first and last point which is rendered if (m_CloseContour && NumberContourPoints > 1) { vtkSmartPointer closingLine = vtkSmartPointer::New(); closingLine->GetPointIds()->SetId(0, 0); // index of first point closingLine->GetPointIds()->SetId(1, NumberContourPoints - 1); // index of last point ls->m_ContourLines->InsertNextCell(closingLine); } ls->m_VtkContourPolyData->SetPoints(ls->m_ContourPoints); ls->m_VtkContourPolyData->SetLines(ls->m_ContourLines); ls->m_VtkContourPolyDataMapper->SetInputData(ls->m_VtkContourPolyData); ls->m_ContourActor->SetMapper(ls->m_VtkContourPolyDataMapper); ls->m_ContourActor->GetProperty()->SetLineWidth(m_LineWidth); ls->m_PropAssembly->AddPart(ls->m_ContourActor); } // the point set must be transformed in order to obtain the appropriate glyph orientation // according to the current view vtkSmartPointer transform = vtkSmartPointer::New(); vtkSmartPointer a, b = vtkSmartPointer::New(); a = geo2D->GetVtkTransform()->GetMatrix(); b->DeepCopy(a); // delete transformation from matrix, only take orientation b->SetElement(3, 3, 1); b->SetElement(2, 3, 0); b->SetElement(1, 3, 0); b->SetElement(0, 3, 0); b->SetElement(3, 2, 0); b->SetElement(3, 1, 0); b->SetElement(3, 0, 0); Vector3D spacing = geo2D->GetSpacing(); // If you find a way to simplyfy the following, feel free to change! b->SetElement(0, 0, b->GetElement(0, 0) / spacing[0]); b->SetElement(1, 0, b->GetElement(1, 0) / spacing[0]); b->SetElement(2, 0, b->GetElement(2, 0) / spacing[0]); b->SetElement(1, 1, b->GetElement(1, 1) / spacing[1]); b->SetElement(2, 1, b->GetElement(2, 1) / spacing[1]); b->SetElement(0, 2, b->GetElement(0, 2) / spacing[2]); b->SetElement(1, 2, b->GetElement(1, 2) / spacing[2]); b->SetElement(2, 2, b->GetElement(2, 2) / spacing[2]); transform->SetMatrix(b); //---- UNSELECTED POINTS -----// // apply properties to glyph ls->m_UnselectedGlyphSource2D->SetGlyphType(m_IDShapeProperty); if (m_FillShape) ls->m_UnselectedGlyphSource2D->FilledOn(); else ls->m_UnselectedGlyphSource2D->FilledOff(); // apply transform vtkSmartPointer transformFilterU = vtkSmartPointer::New(); transformFilterU->SetInputConnection(ls->m_UnselectedGlyphSource2D->GetOutputPort()); transformFilterU->SetTransform(transform); ls->m_VtkUnselectedPointListPolyData->SetPoints(ls->m_UnselectedPoints); ls->m_VtkUnselectedPointListPolyData->GetPointData()->SetVectors(ls->m_UnselectedScales); // apply transform of current plane to glyphs ls->m_UnselectedGlyph3D->SetSourceConnection(transformFilterU->GetOutputPort()); ls->m_UnselectedGlyph3D->SetInputData(ls->m_VtkUnselectedPointListPolyData); + ls->m_UnselectedGlyph3D->SetScaleFactor(m_FixedSizeOnScreen ? resolution : 1.0); ls->m_UnselectedGlyph3D->SetScaleModeToScaleByVector(); ls->m_UnselectedGlyph3D->SetVectorModeToUseVector(); ls->m_VtkUnselectedPolyDataMapper->SetInputConnection(ls->m_UnselectedGlyph3D->GetOutputPort()); ls->m_UnselectedActor->SetMapper(ls->m_VtkUnselectedPolyDataMapper); ls->m_UnselectedActor->GetProperty()->SetLineWidth(m_PointLineWidth); ls->m_PropAssembly->AddPart(ls->m_UnselectedActor); //---- SELECTED POINTS -----// ls->m_SelectedGlyphSource2D->SetGlyphTypeToDiamond(); ls->m_SelectedGlyphSource2D->CrossOn(); ls->m_SelectedGlyphSource2D->FilledOff(); // apply transform vtkSmartPointer transformFilterS = vtkSmartPointer::New(); transformFilterS->SetInputConnection(ls->m_SelectedGlyphSource2D->GetOutputPort()); transformFilterS->SetTransform(transform); ls->m_VtkSelectedPointListPolyData->SetPoints(ls->m_SelectedPoints); ls->m_VtkSelectedPointListPolyData->GetPointData()->SetVectors(ls->m_SelectedScales); // apply transform of current plane to glyphs ls->m_SelectedGlyph3D->SetSourceConnection(transformFilterS->GetOutputPort()); ls->m_SelectedGlyph3D->SetInputData(ls->m_VtkSelectedPointListPolyData); + ls->m_SelectedGlyph3D->SetScaleFactor(m_FixedSizeOnScreen ? resolution : 1.0); ls->m_SelectedGlyph3D->SetScaleModeToScaleByVector(); ls->m_SelectedGlyph3D->SetVectorModeToUseVector(); ls->m_VtkSelectedPolyDataMapper->SetInputConnection(ls->m_SelectedGlyph3D->GetOutputPort()); ls->m_SelectedActor->SetMapper(ls->m_VtkSelectedPolyDataMapper); ls->m_SelectedActor->GetProperty()->SetLineWidth(m_PointLineWidth); ls->m_PropAssembly->AddPart(ls->m_SelectedActor); } void mitk::PointSetVtkMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { const mitk::DataNode *node = GetDataNode(); if (node == nullptr) return; LocalStorage *ls = m_LSH.GetLocalStorage(renderer); // check whether the input data has been changed bool needGenerateData = ls->IsGenerateDataRequired(renderer, this, GetDataNode()); // toggle visibility bool visible = true; node->GetVisibility(visible, renderer, "visible"); if (!visible) { ls->m_UnselectedActor->VisibilityOff(); ls->m_SelectedActor->VisibilityOff(); ls->m_ContourActor->VisibilityOff(); ls->m_PropAssembly->VisibilityOff(); return; } else { ls->m_PropAssembly->VisibilityOn(); } node->GetBoolProperty("show contour", m_ShowContour, renderer); node->GetBoolProperty("close contour", m_CloseContour, renderer); node->GetBoolProperty("show points", m_ShowPoints, renderer); node->GetBoolProperty("show distances", m_ShowDistances, renderer); node->GetIntProperty("distance decimal digits", m_DistancesDecimalDigits, renderer); node->GetBoolProperty("show angles", m_ShowAngles, renderer); node->GetBoolProperty("show distant lines", m_ShowDistantLines, renderer); node->GetIntProperty("line width", m_LineWidth, renderer); node->GetIntProperty("point line width", m_PointLineWidth, renderer); if (!node->GetFloatProperty( "point 2D size", m_Point2DSize, renderer)) // re-defined to float 2015-08-13, keep a fallback { int oldPointSize = m_Point2DSize; if (node->GetIntProperty("point 2D size", oldPointSize, renderer)) { m_Point2DSize = oldPointSize; } } node->GetBoolProperty("Pointset.2D.fill shape", m_FillShape, renderer); node->GetFloatProperty("Pointset.2D.distance to plane", m_DistanceToPlane, renderer); + node->GetBoolProperty("Pointset.2D.fixed size on screen", m_FixedSizeOnScreen, renderer); mitk::PointSetShapeProperty::Pointer shape = dynamic_cast(this->GetDataNode()->GetProperty("Pointset.2D.shape", renderer)); if (shape.IsNotNull()) { m_IDShapeProperty = shape->GetPointSetShape(); } // check for color props and use it for rendering of selected/unselected points and contour // due to different params in VTK (double/float) we have to convert float opacity = 1.0; GetDataNode()->GetOpacity(opacity, renderer); // apply color and opacity if (m_ShowPoints) { float unselectedColor[4]; double selectedColor[4] = {1.0f, 0.0f, 0.0f, 1.0f}; // red ls->m_UnselectedActor->VisibilityOn(); ls->m_SelectedActor->VisibilityOn(); // check if there is a color property GetDataNode()->GetColor(unselectedColor); // get selected color property if (dynamic_cast( this->GetDataNode()->GetPropertyList(renderer)->GetProperty("selectedcolor")) != nullptr) { mitk::Color tmpColor = dynamic_cast( this->GetDataNode()->GetPropertyList(renderer)->GetProperty("selectedcolor")) ->GetValue(); selectedColor[0] = tmpColor[0]; selectedColor[1] = tmpColor[1]; selectedColor[2] = tmpColor[2]; selectedColor[3] = 1.0f; // alpha value } else if (dynamic_cast( this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("selectedcolor")) != nullptr) { mitk::Color tmpColor = dynamic_cast(this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("selectedcolor")) ->GetValue(); selectedColor[0] = tmpColor[0]; selectedColor[1] = tmpColor[1]; selectedColor[2] = tmpColor[2]; selectedColor[3] = 1.0f; // alpha value } ls->m_SelectedActor->GetProperty()->SetColor(selectedColor); ls->m_SelectedActor->GetProperty()->SetOpacity(opacity); ls->m_UnselectedActor->GetProperty()->SetColor(unselectedColor[0], unselectedColor[1], unselectedColor[2]); ls->m_UnselectedActor->GetProperty()->SetOpacity(opacity); } else { ls->m_UnselectedActor->VisibilityOff(); ls->m_SelectedActor->VisibilityOff(); } if (m_ShowContour) { double contourColor[4] = {1.0f, 0.0f, 0.0f, 1.0f}; // red ls->m_ContourActor->VisibilityOn(); // get contour color property if (dynamic_cast( this->GetDataNode()->GetPropertyList(renderer)->GetProperty("contourcolor")) != nullptr) { mitk::Color tmpColor = dynamic_cast(this->GetDataNode()->GetPropertyList(renderer)->GetProperty("contourcolor")) ->GetValue(); contourColor[0] = tmpColor[0]; contourColor[1] = tmpColor[1]; contourColor[2] = tmpColor[2]; contourColor[3] = 1.0f; } else if (dynamic_cast( this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("contourcolor")) != nullptr) { mitk::Color tmpColor = dynamic_cast(this->GetDataNode()->GetPropertyList(nullptr)->GetProperty("contourcolor")) ->GetValue(); contourColor[0] = tmpColor[0]; contourColor[1] = tmpColor[1]; contourColor[2] = tmpColor[2]; contourColor[3] = 1.0f; } ls->m_ContourActor->GetProperty()->SetColor(contourColor); ls->m_ContourActor->GetProperty()->SetOpacity(opacity); } else { ls->m_ContourActor->VisibilityOff(); } if (needGenerateData) { // create new vtk render objects (e.g. a circle for a point) this->CreateVTKRenderObjects(renderer); } } void mitk::PointSetVtkMapper2D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { node->AddProperty("line width", mitk::IntProperty::New(2), renderer, overwrite); node->AddProperty("point line width", mitk::IntProperty::New(1), renderer, overwrite); node->AddProperty("point 2D size", mitk::FloatProperty::New(6), renderer, overwrite); node->AddProperty("show contour", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("close contour", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("show points", mitk::BoolProperty::New(true), renderer, overwrite); node->AddProperty("show distances", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("distance decimal digits", mitk::IntProperty::New(2), renderer, overwrite); node->AddProperty("show angles", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("show distant lines", mitk::BoolProperty::New(false), renderer, overwrite); node->AddProperty("layer", mitk::IntProperty::New(1), renderer, overwrite); node->AddProperty("Pointset.2D.fill shape", mitk::BoolProperty::New(false), renderer, overwrite); // fill or do not fill the glyph shape mitk::PointSetShapeProperty::Pointer pointsetShapeProperty = mitk::PointSetShapeProperty::New(); node->AddProperty("Pointset.2D.shape", pointsetShapeProperty, renderer, overwrite); node->AddProperty("Pointset.2D.distance to plane", mitk::FloatProperty::New(4.0f), renderer, overwrite); // show the point at a certain distance above/below the 2D imaging plane. + node->AddProperty("Pointset.2D.fixed size on screen", mitk::BoolProperty::New(false), renderer, overwrite); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/CppMicroServices/CMakeLists.txt b/Modules/CppMicroServices/CMakeLists.txt index b801ea8334..3faee251b5 100644 --- a/Modules/CppMicroServices/CMakeLists.txt +++ b/Modules/CppMicroServices/CMakeLists.txt @@ -1,444 +1,443 @@ project(CppMicroServices) set(${PROJECT_NAME}_MAJOR_VERSION 2) set(${PROJECT_NAME}_MINOR_VERSION 99) set(${PROJECT_NAME}_PATCH_VERSION 0) set(${PROJECT_NAME}_VERSION ${${PROJECT_NAME}_MAJOR_VERSION}.${${PROJECT_NAME}_MINOR_VERSION}.${${PROJECT_NAME}_PATCH_VERSION}) cmake_minimum_required(VERSION 2.8.12) cmake_policy(VERSION 2.8.12) cmake_policy(SET CMP0017 NEW) #----------------------------------------------------------------------------- # Update CMake module path #------------------------------------------------------------------------------ set(US_CMAKE_DIR ${PROJECT_SOURCE_DIR}/cmake) set(CMAKE_MODULE_PATH ${US_CMAKE_DIR} ${CMAKE_MODULE_PATH} ) #----------------------------------------------------------------------------- # CMake function(s) and macro(s) #----------------------------------------------------------------------------- include(CMakeParseArguments) include(CMakePackageConfigHelpers) include(CheckCXXSourceCompiles) include(usFunctionAddResources) include(usFunctionEmbedResources) include(usFunctionGetResourceSource) include(usFunctionCheckResourceLinking) include(usFunctionCheckCompilerFlags) include(usFunctionGetGccVersion) include(usFunctionGenerateModuleInit) include(usMacroCreateModule) if(US_BUILD_TESTING) include(usFunctionCompileSnippets) endif() #----------------------------------------------------------------------------- # Init output directories #----------------------------------------------------------------------------- set(US_ARCHIVE_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") set(US_LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/lib") set(US_RUNTIME_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/bin") foreach(_type ARCHIVE LIBRARY RUNTIME) if(NOT CMAKE_${_type}_OUTPUT_DIRECTORY) set(CMAKE_${_type}_OUTPUT_DIRECTORY ${US_${_type}_OUTPUT_DIRECTORY}) endif() endforeach() #----------------------------------------------------------------------------- # Set a default build type if none was specified #----------------------------------------------------------------------------- if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to 'Debug' as none was specified.") set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() #----------------------------------------------------------------------------- # CMake options #----------------------------------------------------------------------------- function(us_cache_var _var_name _var_default _var_type _var_help) set(_advanced 0) set(_force) foreach(_argn ${ARGN}) if(_argn STREQUAL ADVANCED) set(_advanced 1) elseif(_argn STREQUAL FORCE) set(_force FORCE) endif() endforeach() if(US_IS_EMBEDDED) if(NOT DEFINED ${_var_name} OR _force) set(${_var_name} ${_var_default} PARENT_SCOPE) endif() else() set(${_var_name} ${_var_default} CACHE ${_var_type} "${_var_help}" ${_force}) if(_advanced) mark_as_advanced(${_var_name}) endif() endif() endfunction() # Determine if we are being build inside a larger project if(NOT DEFINED US_IS_EMBEDDED) if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME) set(US_IS_EMBEDDED 0) else() set(US_IS_EMBEDDED 1) endif() endif() # Determine the name of the install component for "SDK" artifacts. # The default is "sdk" if(NOT DEFINED US_SDK_INSTALL_COMPONENT) set(US_SDK_INSTALL_COMPONENT COMPONENT sdk) elseif(US_SDK_INSTALL_COMPONENT) set(US_SDK_INSTALL_COMPONENT COMPONENT ${US_SDK_INSTALL_COMPONENT}) endif() us_cache_var(US_ENABLE_AUTOLOADING_SUPPORT OFF BOOL "Enable module auto-loading support") us_cache_var(US_ENABLE_THREADING_SUPPORT OFF BOOL "Enable threading support") us_cache_var(US_ENABLE_DEBUG_OUTPUT OFF BOOL "Enable debug messages" ADVANCED) us_cache_var(US_BUILD_SHARED_LIBS ON BOOL "Build shared libraries") us_cache_var(US_BUILD_TESTING OFF BOOL "Build tests") us_cache_var(US_BUILD_EXAMPLES OFF BOOL "Build example projects") if(US_BUILD_TESTING) enable_testing() endif() if(WIN32 AND NOT CYGWIN) set(default_runtime_install_dir bin/) set(default_library_install_dir bin/) set(default_archive_install_dir lib/) set(default_header_install_dir include/) set(default_auxiliary_install_dir share/) else() set(default_runtime_install_dir bin/) set(default_library_install_dir lib/${PROJECT_NAME}) set(default_archive_install_dir lib/${PROJECT_NAME}) set(default_header_install_dir include/${PROJECT_NAME}) set(default_auxiliary_install_dir share/${PROJECT_NAME}) endif() us_cache_var(RUNTIME_INSTALL_DIR ${default_runtime_install_dir} STRING "Relative install location for binaries" ADVANCED) us_cache_var(LIBRARY_INSTALL_DIR ${default_library_install_dir} STRING "Relative install location for libraries" ADVANCED) us_cache_var(ARCHIVE_INSTALL_DIR ${default_archive_install_dir} STRING "Relative install location for archives" ADVANCED) us_cache_var(HEADER_INSTALL_DIR ${default_header_install_dir} STRING "Relative install location for headers" ADVANCED) us_cache_var(AUXILIARY_INSTALL_DIR ${default_auxiliary_install_dir} STRING "Relative install location for auxiliary files" ADVANCED) set(AUXILIARY_CMAKE_INSTALL_DIR ${AUXILIARY_INSTALL_DIR}/cmake) us_cache_var(US_NAMESPACE "us" STRING "The namespace for the C++ Micro Services symbols") set(BUILD_SHARED_LIBS ${US_BUILD_SHARED_LIBS}) set(US_MODULE_INIT_TEMPLATE "${US_CMAKE_DIR}/usModuleInit.cpp" CACHE INTERNAL "The module initialization template code") set(US_RESOURCE_RC_TEMPLATE "${US_CMAKE_DIR}/us_resources.rc.in" CACHE INTERNAL "The Windows RC resource template") set(US_CMAKE_RESOURCE_DEPENDENCIES_CPP "${US_CMAKE_DIR}/usCMakeResourceDependencies.cpp" CACHE INTERNAL "The dummy resource dependencies code") #----------------------------------------------------------------------------- # US C/CXX Flags #----------------------------------------------------------------------------- if(US_IS_EMBEDDED) set(CMAKE_C_FLAGS) set(CMAKE_C_FLAGS_RELEASE) set(CMAKE_C_FLAGS_DEBUG) set(CMAKE_CXX_FLAGS) set(CMAKE_CXX_FLAGS_RELEASE) set(CMAKE_CXX_FLAGS_DEBUG) set(CMAKE_LINK_FLAGS) set(CMAKE_LINK_FLAGS_RELEASE) set(CMAKE_LINK_FLAGS_DEBUG) endif() # Set C++ compiler flags if(NOT MSVC) - foreach(_cxxflag -Werror -Wall -Wextra -Wpointer-arith -Winvalid-pch -Wcast-align - -Wwrite-strings -Woverloaded-virtual -Wnon-virtual-dtor -Wold-style-cast + foreach(_cxxflag -Werror -Wall -Wextra -Wpointer-arith -Winvalid-pch -Wcast-align -Wwrite-strings -Woverloaded-virtual -Wnon-virtual-dtor -Wstrict-null-sentinel -Wsign-promo -fdiagnostics-show-option -Wno-error=deprecated-copy -Wno-error=implicit-int-float-conversion) usFunctionCheckCompilerFlags(${_cxxflag} US_CXX_FLAGS) endforeach() endif() set(US_HAVE_VISIBILITY_ATTRIBUTE 0) usFunctionCheckCompilerFlags("-fvisibility=hidden -fvisibility-inlines-hidden" _have_visibility) if(_have_visibility) set(US_HAVE_VISIBILITY_ATTRIBUTE 1) endif() if(CMAKE_COMPILER_IS_GNUCXX) usFunctionGetGccVersion(${CMAKE_CXX_COMPILER} GCC_VERSION) if(${GCC_VERSION} VERSION_LESS "4.0.0") message(FATAL_ERROR "gcc version ${GCC_VERSION} not supported. Please use gcc >= 4.") endif() # With older versions of gcc the flag -fstack-protector-all requires an extra dependency to libssp.so. # If the gcc version is lower than 4.4.0 and the build type is Release let's not include the flag. if(${GCC_VERSION} VERSION_GREATER "4.4.0" OR (CMAKE_BUILD_TYPE STREQUAL "Debug" AND ${GCC_VERSION} VERSION_LESS "4.4.0")) usFunctionCheckCompilerFlags("-fstack-protector-all" US_CXX_FLAGS) endif() # Enable visibility support (only for gcc >= 4.5) # We only support hidden visibility with gcc for now. # # Clang has troubles with correctly marking template declarations and explicit template # instantiations as exported across shared library boundaries. Specifically, comparing # type_info objects from STL types does not work (used in us::Any::Type() == typeid(std::string)) # which could be related to the default visibility of STL types declared in libstdc++ and/or libc++ # but also to using RTLD_LOCAL or RTLD_GLOBAL when loading shared libraries via dlopen(). # # See http://comments.gmane.org/gmane.comp.compilers.clang.scm/50028 # and http://llvm.org/bugs/show_bug.cgi?id=10113 # if(_have_visibility AND NOT ${GCC_VERSION} VERSION_LESS "4.5") set(US_CXX_FLAGS "${US_CXX_FLAGS} ${_have_visibility}") else() set(US_GCC_RTTI_WORKAROUND_NEEDED 1) endif() usFunctionCheckCompilerFlags("-O1 -D_FORTIFY_SOURCE=2" _fortify_source_flag) if(_fortify_source_flag) set(US_CXX_FLAGS_RELEASE "${US_CXX_FLAGS_RELEASE} -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2") endif() endif() if(MSVC) set(US_CXX_FLAGS "/MP /WX /wd4180 /wd4996 /wd4251 /wd4503 ${US_CXX_FLAGS}") endif() set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${US_CXX_FLAGS}") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${US_CXX_FLAGS_RELEASE}") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${US_C_FLAGS}") set(CMAKE_C_FLAGS_REALEASE "${CMAKE_C_FLAGS_RELEASE} ${US_C_FLAGS_RELEASE}") #----------------------------------------------------------------------------- # US Link Flags #----------------------------------------------------------------------------- set(US_LINK_FLAGS ) if(CMAKE_COMPILER_IS_GNUCXX AND NOT APPLE) foreach(_linkflag -Wl,--no-undefined) set(_add_flag) usFunctionCheckCompilerFlags("${_linkflag}" _add_flag) if(_add_flag) set(US_LINK_FLAGS "${US_LINK_FLAGS} ${_linkflag}") endif() endforeach() endif() usFunctionCheckResourceLinking() #----------------------------------------------------------------------------- # US Header Checks #----------------------------------------------------------------------------- include(CheckIncludeFileCXX) include(CheckCXXSourceCompiles) CHECK_INCLUDE_FILE_CXX(cxxabi.h US_HAVE_CXXABI_H) CHECK_INCLUDE_FILE_CXX(stdint.h US_HAVE_STDINT_H) CHECK_INCLUDE_FILE_CXX(tr1/unordered_map US_HAVE_TR1_UNORDERED_MAP_H) CHECK_INCLUDE_FILE_CXX(tr1/unordered_set US_HAVE_TR1_UNORDERED_SET_H) CHECK_INCLUDE_FILE_CXX(tr1/functional US_HAVE_TR1_FUNCTIONAL_H) CHECK_INCLUDE_FILE_CXX(unordered_map US_HAVE_UNORDERED_MAP_H) CHECK_INCLUDE_FILE_CXX(unordered_set US_HAVE_UNORDERED_SET_H) CHECK_INCLUDE_FILE_CXX(functional US_HAVE_FUNCTIONAL_H) if(US_HAVE_UNORDERED_MAP_H) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::tr1::unordered_map m; return 0; }" US_HAVE_TR1_UNORDERED_MAP) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::unordered_map m; return 0; }" US_HAVE_STD_UNORDERED_MAP) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::tr1::hash(); return 0; }" US_HAVE_TR1_HASH) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::hash(); return 0; }" US_HAVE_STD_HASH) if(US_HAVE_STD_HASH) CHECK_CXX_SOURCE_COMPILES("#include \nstruct A { friend struct std::hash; }; int main() { return 0; }" US_HAVE_STD_HASH_STRUCT) CHECK_CXX_SOURCE_COMPILES("#include \nstruct A { friend class std::hash; }; int main() { return 0; }" US_HAVE_STD_HASH_CLASS) elseif(US_HAVE_TR1_HASH) CHECK_CXX_SOURCE_COMPILES("#include \nstruct A { friend struct std::tr1::hash; }; int main() { return 0; }" US_HAVE_TR1_HASH_STRUCT) CHECK_CXX_SOURCE_COMPILES("#include \nstruct A { friend class std::tr1::hash; }; int main() { return 0; }" US_HAVE_TR1_HASH_CLASS) endif() elseif(US_HAVE_TR1_UNORDERED_MAP_H) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::tr1::unordered_map m; return 0; }" US_HAVE_TR1_UNORDERED_MAP) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::unordered_map m; return 0; }" US_HAVE_STD_UNORDERED_MAP) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::tr1::hash(); return 0; }" US_HAVE_TR1_HASH) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::hash(); return 0; }" US_HAVE_STD_HASH) if(US_HAVE_STD_HASH) CHECK_CXX_SOURCE_COMPILES("#include \nstruct A { friend struct std::hash; }; int main() { return 0; }" US_HAVE_STD_HASH_STRUCT) CHECK_CXX_SOURCE_COMPILES("#include \nstruct A { friend class std::hash; }; int main() { return 0; }" US_HAVE_STD_HASH_CLASS) elseif(US_HAVE_TR1_HASH) CHECK_CXX_SOURCE_COMPILES("#include \nstruct A { friend struct std::tr1::hash; }; int main() { return 0; }" US_HAVE_TR1_HASH_STRUCT) CHECK_CXX_SOURCE_COMPILES("#include \nstruct A { friend class std::tr1::hash; }; int main() { return 0; }" US_HAVE_TR1_HASH_CLASS) endif() else() message(SEND_ERROR "The header file \"unordered_map\" is not available.") endif() if(NOT (US_HAVE_TR1_UNORDERED_MAP OR US_HAVE_STD_UNORDERED_MAP)) message(SEND_ERROR "The \"unordered_map\" type is not available.") endif() if(US_HAVE_UNORDERED_SET_H) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::tr1::unordered_set s; return 0; }" US_HAVE_TR1_UNORDERED_SET) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::unordered_set s; return 0; }" US_HAVE_STD_UNORDERED_SET) elseif(US_HAVE_TR1_UNORDERED_SET_H) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::tr1::unordered_set s; return 0; }" US_HAVE_TR1_UNORDERED_SET) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::unordered_set s; return 0; }" US_HAVE_STD_UNORDERED_SET) else() message(SEND_ERROR "The header file \"unordered_set\" is not available.") endif() if(NOT (US_HAVE_TR1_UNORDERED_SET OR US_HAVE_STD_UNORDERED_SET)) message(SEND_ERROR "The \"unordered_set\" type is not available.") endif() if(NOT (US_HAVE_FUNCTIONAL_H OR US_HAVE_TR1_FUNCTIONAL_H)) message(SEND_ERROR "The header file \"functional\" is not available.") endif() if(US_HAVE_FUNCTIONAL_H) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::tr1::function f(main); return 0; }" US_HAVE_TR1_FUNCTION) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::function f(main); return 0; }" US_HAVE_STD_FUNCTION) endif() if((NOT (US_HAVE_STD_FUNCTION OR US_HAVE_TR1_FUNCTION)) AND US_HAVE_TR1_FUNCTIONAL_H) unset(US_HAVE_TR1_FUNCTION CACHE) unset(US_HAVE_STD_FUNCTION CACHE) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::tr1::function f(main); return 0; }" US_HAVE_TR1_FUNCTION) CHECK_CXX_SOURCE_COMPILES("#include \nint main() { std::function f(main); return 0; }" US_HAVE_STD_FUNCTION) endif() if(NOT (US_HAVE_STD_FUNCTION OR US_HAVE_TR1_FUNCTION)) message(SEND_ERROR "The \"function\" type is not available.") endif() #----------------------------------------------------------------------------- # Source directory #----------------------------------------------------------------------------- set(US_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/third_party ${PROJECT_BINARY_DIR}/include) # Configure CppMicroServicesConfig.cmake for the build tree. # The file is used in sub-directories. set(PACKAGE_CONFIG_INCLUDE_DIR ${US_INCLUDE_DIRS}) set(PACKAGE_CONFIG_RUNTIME_DIR ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) set(PACKAGE_CONFIG_CMAKE_DIR ${US_CMAKE_DIR}) set(US_RCC_EXECUTABLE_NAME usResourceCompiler CACHE INTERNAL "The target name of the usResourceCompiler executable.") configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in ${PROJECT_BINARY_DIR}/${PROJECT_NAME}Config.cmake @ONLY ) set(us_global_config_h_file "${PROJECT_BINARY_DIR}/include/usGlobalConfig.h") configure_file(${US_CMAKE_DIR}/usGlobalConfig.h.in ${us_global_config_h_file}) include_directories(${US_INCLUDE_DIRS}) add_subdirectory(tools) add_subdirectory(core) #----------------------------------------------------------------------------- # Documentation #----------------------------------------------------------------------------- add_subdirectory(doc) #----------------------------------------------------------------------------- # Last configuration and install steps #----------------------------------------------------------------------------- # Version information configure_file( ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}ConfigVersion.cmake.in ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake @ONLY ) export(TARGETS ${US_RCC_EXECUTABLE_NAME} FILE ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}Targets.cmake) if(NOT US_NO_INSTALL) install(EXPORT ${PROJECT_NAME}Targets FILE ${PROJECT_NAME}Targets.cmake DESTINATION ${AUXILIARY_CMAKE_INSTALL_DIR}) set(_install_cmake_scripts ${US_MODULE_INIT_TEMPLATE} ${US_CMAKE_RESOURCE_DEPENDENCIES_CPP} ${US_CMAKE_DIR}/usFunctionGenerateModuleInit.cmake ${US_CMAKE_DIR}/usFunctionAddResources.cmake ${US_CMAKE_DIR}/usFunctionEmbedResources.cmake ${US_CMAKE_DIR}/usFunctionGetResourceSource.cmake ${US_CMAKE_DIR}/usFunctionCheckResourceLinking.cmake ${US_CMAKE_DIR}/usFunctionCheckCompilerFlags.cmake ) install(FILES ${_install_cmake_scripts} DESTINATION ${AUXILIARY_CMAKE_INSTALL_DIR}) install(FILES ${us_global_config_h_file} DESTINATION ${HEADER_INSTALL_DIR}) # Configure CppMicroServicesConfig.cmake for the install tree set(CONFIG_INCLUDE_DIR ${HEADER_INSTALL_DIR}) set(CONFIG_RUNTIME_DIR ${RUNTIME_INSTALL_DIR}) set(CONFIG_CMAKE_DIR ${AUXILIARY_CMAKE_INSTALL_DIR}) configure_package_config_file( ${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}Config.cmake.in ${PROJECT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Config.cmake INSTALL_DESTINATION ${AUXILIARY_CMAKE_INSTALL_DIR} PATH_VARS CONFIG_INCLUDE_DIR CONFIG_RUNTIME_DIR CONFIG_CMAKE_DIR NO_SET_AND_CHECK_MACRO NO_CHECK_REQUIRED_COMPONENTS_MACRO ) install(FILES ${CMAKE_CURRENT_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/${PROJECT_NAME}Config.cmake ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}ConfigVersion.cmake DESTINATION ${AUXILIARY_CMAKE_INSTALL_DIR} ${US_SDK_INSTALL_COMPONENT} ) endif() diff --git a/Modules/DataTypesExt/CMakeLists.txt b/Modules/DataTypesExt/CMakeLists.txt index 841888032d..fd7c68b54a 100644 --- a/Modules/DataTypesExt/CMakeLists.txt +++ b/Modules/DataTypesExt/CMakeLists.txt @@ -1,6 +1,6 @@ mitk_create_module( DEPENDS MitkCore - PACKAGE_DEPENDS PRIVATE ITK|ZLIB + PACKAGE_DEPENDS lz4 ) add_subdirectory(test) diff --git a/Modules/DataTypesExt/include/mitkApplyDiffImageOperation.h b/Modules/DataTypesExt/include/mitkApplyDiffImageOperation.h index ee25cbea9b..6162b23d4c 100644 --- a/Modules/DataTypesExt/include/mitkApplyDiffImageOperation.h +++ b/Modules/DataTypesExt/include/mitkApplyDiffImageOperation.h @@ -1,88 +1,88 @@ /*============================================================================ 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 mitkApplyDiffImageIIncluded #define mitkApplyDiffImageIIncluded #include "MitkDataTypesExtExports.h" #include "mitkCompressedImageContainer.h" #include "mitkOperation.h" namespace mitk { /** @brief Operation, that holds information about some image difference This class stores undo information for DiffImageApplier. - Instances of this class are created e.g. by OverwriteSliceImageFilter. + Instances of this class are created e.g. by QmitkSlicesInterpolator. This works only for images with 1 channel (gray scale images, no color images). ApplyDiffImageOperation of course refers to an image (a segmentation usually). The refered image is observed for itk::DeleteEvent, because there is no SmartPointer used to keep the image alive -- the purpose of this class is undo and the undo stack should not keep things alive forever. - To save memory, zlib compression is used via CompressedImageContainer. + To save memory, compression is used via CompressedImageContainer. @ingroup Undo @ingroup ToolManagerEtAl */ class MITKDATATYPESEXT_EXPORT ApplyDiffImageOperation : public Operation { protected: void OnImageDeleted(); Image *m_Image; unsigned int m_SliceIndex; unsigned int m_SliceDimension; unsigned int m_TimeStep; double m_Factor; bool m_ImageStillValid; unsigned long m_DeleteTag; - CompressedImageContainer::Pointer zlibContainer; + CompressedImageContainer m_CompressedImageContainer; public: /** Pass only 2D images here. \param operationType \param image \param diffImage \param timeStep \param sliceIndex brief Which slice to extract (first one has index 0). \param sliceDimension Number of the dimension which is constant for all pixels of the desired slice (e.g. 0 for axial) */ ApplyDiffImageOperation(OperationType operationType, Image *image, Image *diffImage, unsigned int timeStep = 0, unsigned int sliceDimension = 2, unsigned int sliceIndex = 0); ~ApplyDiffImageOperation() override; // Unfortunately cannot use itkGet/SetMacros here, since Operation does not inherit itk::Object unsigned int GetSliceIndex() { return m_SliceIndex; } unsigned int GetSliceDimension() { return m_SliceDimension; } unsigned int GetTimeStep() { return m_TimeStep; } void SetFactor(double factor) { m_Factor = factor; } double GetFactor() { return m_Factor; } Image *GetImage() { return m_Image; } Image::Pointer GetDiffImage(); bool IsImageStillValid() { return m_ImageStillValid; } }; } // namespace mitk #endif diff --git a/Modules/DataTypesExt/include/mitkCompressedImageContainer.h b/Modules/DataTypesExt/include/mitkCompressedImageContainer.h index 32ead859c3..0e9fd71c6c 100644 --- a/Modules/DataTypesExt/include/mitkCompressedImageContainer.h +++ b/Modules/DataTypesExt/include/mitkCompressedImageContainer.h @@ -1,80 +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 mitkCompressedImageContainer_h_Included -#define mitkCompressedImageContainer_h_Included +#ifndef mitkCompressedImageContainer_h +#define mitkCompressedImageContainer_h -#include "MitkDataTypesExtExports.h" -#include "mitkCommon.h" -#include "mitkGeometry3D.h" -#include "mitkImage.h" -#include "mitkImageDataItem.h" - -#include - -#include +#include +#include +#include +#include +#include namespace mitk { - /** - \brief Holds one (compressed) mitk::Image - - Uses zlib to compress the data of an mitk::Image. - - $Author$ - */ - class MITKDATATYPESEXT_EXPORT CompressedImageContainer : public itk::Object + class MITKDATATYPESEXT_EXPORT CompressedImageContainer { public: - mitkClassMacroItkParent(CompressedImageContainer, itk::Object); - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); + CompressedImageContainer(); + ~CompressedImageContainer(); - /** - * \brief Creates a compressed version of the image. - * - * Will not hold any further SmartPointers to the image. - * - */ - void SetImage(const Image *); + CompressedImageContainer(const CompressedImageContainer&) = delete; + CompressedImageContainer& operator=(const CompressedImageContainer&) = delete; - /** - * \brief Creates a full mitk::Image from its compressed version. - * - * This Method hold no buffer, so the uncompression algorithm will be - * executed every time you call this method. Don't overdo it. - * - */ - Image::Pointer GetImage() const; + void CompressImage(const Image* image); + Image::Pointer DecompressImage() const; - protected: - CompressedImageContainer(); // purposely hidden - ~CompressedImageContainer() override; + private: + using CompressedSliceData = std::pair; + using CompressedTimeStepData = std::vector; + using CompressedImageData = std::vector; - PixelType *m_PixelType; + void ClearCompressedImageData(); - unsigned int m_ImageDimension; - std::vector m_ImageDimensions; + CompressedImageData m_CompressedImageData; - unsigned long m_OneTimeStepImageSizeInBytes; - - unsigned int m_NumberOfTimeSteps; - - /// one for each timestep. first = pointer to compressed data; second = size of buffer in bytes - std::vector> m_ByteBuffers; - - BaseGeometry::Pointer m_ImageGeometry; + std::unique_ptr m_PixelType; + TimeGeometry::Pointer m_TimeGeometry; + std::array m_SliceDimensions; + unsigned int m_Dimension; }; - -} // namespace +} #endif diff --git a/Modules/DataTypesExt/src/mitkApplyDiffImageOperation.cpp b/Modules/DataTypesExt/src/mitkApplyDiffImageOperation.cpp index 30c99556ff..e17c5bff75 100644 --- a/Modules/DataTypesExt/src/mitkApplyDiffImageOperation.cpp +++ b/Modules/DataTypesExt/src/mitkApplyDiffImageOperation.cpp @@ -1,67 +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. ============================================================================*/ #include "mitkApplyDiffImageOperation.h" #include mitk::ApplyDiffImageOperation::ApplyDiffImageOperation(OperationType operationType, Image *image, Image *diffImage, unsigned int timeStep, unsigned int sliceDimension, unsigned int sliceIndex) : Operation(operationType), m_Image(image), m_SliceIndex(sliceIndex), m_SliceDimension(sliceDimension), m_TimeStep(timeStep), m_Factor(1.0), m_ImageStillValid(false), m_DeleteTag(0) { if (image && diffImage) { // observe 3D image for DeleteEvent m_ImageStillValid = true; itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &ApplyDiffImageOperation::OnImageDeleted); m_DeleteTag = image->AddObserver(itk::DeleteEvent(), command); // keep a compressed version of the image - zlibContainer = CompressedImageContainer::New(); - zlibContainer->SetImage(diffImage); + m_CompressedImageContainer.CompressImage(diffImage); } } mitk::ApplyDiffImageOperation::~ApplyDiffImageOperation() { if (m_ImageStillValid) { m_Image->RemoveObserver(m_DeleteTag); } } void mitk::ApplyDiffImageOperation::OnImageDeleted() { m_ImageStillValid = false; } mitk::Image::Pointer mitk::ApplyDiffImageOperation::GetDiffImage() { // uncompress image to create a valid mitk::Image - Image::Pointer image = zlibContainer->GetImage().GetPointer(); - - return image; + return m_CompressedImageContainer.DecompressImage(); } diff --git a/Modules/DataTypesExt/src/mitkCompressedImageContainer.cpp b/Modules/DataTypesExt/src/mitkCompressedImageContainer.cpp index 7d11a9e9a9..22c4c00fdc 100644 --- a/Modules/DataTypesExt/src/mitkCompressedImageContainer.cpp +++ b/Modules/DataTypesExt/src/mitkCompressedImageContainer.cpp @@ -1,178 +1,136 @@ /*============================================================================ 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 "mitkCompressedImageContainer.h" -#include "mitkImageReadAccessor.h" +#include -#include "itk_zlib.h" +#include +#include -#include +#include -mitk::CompressedImageContainer::CompressedImageContainer() : m_PixelType(nullptr), m_ImageGeometry(nullptr) +#include + +mitk::CompressedImageContainer::CompressedImageContainer() + : m_Dimension(0) { } mitk::CompressedImageContainer::~CompressedImageContainer() { - for (auto iter = m_ByteBuffers.begin(); iter != m_ByteBuffers.end(); ++iter) - { - free(iter->first); - } - - delete m_PixelType; + this->ClearCompressedImageData(); } -void mitk::CompressedImageContainer::SetImage(const Image *image) +void mitk::CompressedImageContainer::ClearCompressedImageData() { - for (auto iter = m_ByteBuffers.begin(); iter != m_ByteBuffers.end(); ++iter) + for (const auto& image : m_CompressedImageData) { - free(iter->first); + for (auto slice : image) + delete[] slice.second; } - m_ByteBuffers.clear(); + m_CompressedImageData.clear(); - // Compress diff image using zlib (will be restored on demand) - // determine memory size occupied by voxel data - m_ImageDimension = image->GetDimension(); - m_ImageDimensions.clear(); + m_PixelType = nullptr; + m_TimeGeometry = nullptr; + m_SliceDimensions[0] = 0; + m_SliceDimensions[1] = 0; + m_Dimension = 0; +} - m_PixelType = new mitk::PixelType(image->GetPixelType()); +void mitk::CompressedImageContainer::CompressImage(const Image* image) +{ + this->ClearCompressedImageData(); - m_OneTimeStepImageSizeInBytes = m_PixelType->GetSize(); // bits per element divided by 8 - for (unsigned int i = 0; i < m_ImageDimension; ++i) - { - unsigned int currentImageDimension = image->GetDimension(i); - m_ImageDimensions.push_back(currentImageDimension); - if (i < 3) - { - m_OneTimeStepImageSizeInBytes *= currentImageDimension; // only the 3D memory size - } - } + if (nullptr == image) + return; - m_ImageGeometry = image->GetGeometry(); + m_PixelType = std::make_unique(image->GetPixelType()); + m_TimeGeometry = image->GetTimeGeometry()->Clone(); + m_SliceDimensions[0] = image->GetDimension(0); + m_SliceDimensions[1] = image->GetDimension(1); + m_Dimension = image->GetDimension(); - m_NumberOfTimeSteps = 1; - if (m_ImageDimension > 3) - { - m_NumberOfTimeSteps = image->GetDimension(3); - } + const auto numTimeSteps = m_TimeGeometry->CountTimeSteps(); + const auto numSlices = image->GetDimension(2); + const auto numSliceBytes = image->GetPixelType().GetSize() * image->GetDimension(0) * image->GetDimension(1); + + m_CompressedImageData.reserve(numTimeSteps); - for (unsigned int timestep = 0; timestep < m_NumberOfTimeSteps; ++timestep) + for (std::remove_const_t t = 0; t < numTimeSteps; ++t) { - // allocate a buffer as specified by zlib - unsigned long bufferSize = - m_OneTimeStepImageSizeInBytes + static_cast(m_OneTimeStepImageSizeInBytes * 0.2) + 12; - auto *byteBuffer = (unsigned char *)malloc(bufferSize); + CompressedTimeStepData slices; + slices.reserve(numSlices); - if (itk::Object::GetDebug()) - { - // compress image here into a buffer - MITK_INFO << "Using ZLib version: '" << zlibVersion() << "'" << std::endl - << "Attempting to compress " << m_OneTimeStepImageSizeInBytes << " image bytes into a buffer of size " - << bufferSize << std::endl; - } + ImageReadAccessor accessor(image, image->GetVolumeData(t)); - ImageReadAccessor imgAcc(image, image->GetVolumeData(timestep)); - ::Bytef *dest(byteBuffer); - ::uLongf destLen(bufferSize); - auto *source((unsigned char *)imgAcc.GetData()); - ::uLongf sourceLen(m_OneTimeStepImageSizeInBytes); - int zlibRetVal = ::compress(dest, &destLen, source, sourceLen); - if (itk::Object::GetDebug()) + for (std::remove_const_t s = 0; s < numSlices; ++s) { - if (zlibRetVal == Z_OK) + const auto* src = reinterpret_cast(accessor.GetData()) + numSliceBytes * s; + char* dest = new char[numSliceBytes]; + const auto destSize = LZ4_compress_default(src, dest, static_cast(numSliceBytes), static_cast(numSliceBytes)); + + if (0 == destSize) { - MITK_INFO << "Success, using " << destLen << " bytes of the buffer (ratio " - << ((double)destLen / (double)sourceLen) << ")" << std::endl; + MITK_ERROR << "LZ4 compression failed!"; + delete[] dest; + slices.emplace_back(0, nullptr); } else { - switch (zlibRetVal) - { - case Z_MEM_ERROR: - MITK_ERROR << "not enough memory" << std::endl; - break; - case Z_BUF_ERROR: - MITK_ERROR << "output buffer too small" << std::endl; - break; - default: - MITK_ERROR << "other, unspecified error" << std::endl; - break; - } + char* shrinkedDest = new char[destSize]; + std::copy(dest, dest + destSize, shrinkedDest); + delete[] dest; + slices.emplace_back(destSize, shrinkedDest); } } - // only use the neccessary amount of memory, realloc the buffer! - byteBuffer = (unsigned char *)realloc(byteBuffer, destLen); - bufferSize = destLen; - // MITK_INFO << "Using " << bufferSize << " bytes to store compressed image (" << destLen << " needed)" << - // std::endl; - - m_ByteBuffers.push_back(std::pair(byteBuffer, bufferSize)); + m_CompressedImageData.push_back(slices); } } -mitk::Image::Pointer mitk::CompressedImageContainer::GetImage() const +mitk::Image::Pointer mitk::CompressedImageContainer::DecompressImage() const { - if (m_ByteBuffers.empty()) + if (m_CompressedImageData.empty()) return nullptr; - // uncompress image data, create an Image - Image::Pointer image = Image::New(); - unsigned int dims[20]; // more than 20 dimensions and bang - for (unsigned int dim = 0; dim < m_ImageDimension; ++dim) - dims[dim] = m_ImageDimensions[dim]; + const auto numSlices = static_cast(m_CompressedImageData[0].size()); + const auto numTimeSteps = static_cast(m_CompressedImageData.size()); + const auto numSliceBytes = m_PixelType->GetSize() * m_SliceDimensions[0] * m_SliceDimensions[1]; + + std::array dimensions; + dimensions[0] = m_SliceDimensions[0]; + dimensions[1] = m_SliceDimensions[1]; + dimensions[2] = numSlices; + dimensions[3] = numTimeSteps; - image->Initialize(*m_PixelType, m_ImageDimension, dims); // this IS needed, right ?? But it does allocate memory -> - // does create one big lump of memory (also in windows) + auto image = Image::New(); + image->Initialize(*m_PixelType, m_Dimension, dimensions.data()); - unsigned int timeStep(0); - for (auto iter = m_ByteBuffers.begin(); iter != m_ByteBuffers.end(); ++iter, ++timeStep) + for (std::remove_const_t t = 0; t < numTimeSteps; ++t) { - ImageReadAccessor imgAcc(image, image->GetVolumeData(timeStep)); - auto *dest((unsigned char *)imgAcc.GetData()); - ::uLongf destLen(m_OneTimeStepImageSizeInBytes); - ::Bytef *source(iter->first); - ::uLongf sourceLen(iter->second); - int zlibRetVal = ::uncompress(dest, &destLen, source, sourceLen); - if (itk::Object::GetDebug()) + ImageWriteAccessor accessor(image, image->GetVolumeData(static_cast(t))); + + for (std::remove_const_t s = 0; s < numSlices; ++s) { - if (zlibRetVal == Z_OK) - { - MITK_INFO << "Success, destLen now " << destLen << " bytes" << std::endl; - } - else - { - switch (zlibRetVal) - { - case Z_DATA_ERROR: - MITK_ERROR << "compressed data corrupted" << std::endl; - break; - case Z_MEM_ERROR: - MITK_ERROR << "not enough memory" << std::endl; - break; - case Z_BUF_ERROR: - MITK_ERROR << "output buffer too small" << std::endl; - break; - default: - MITK_ERROR << "other, unspecified error" << std::endl; - break; - } - } + auto* dest = reinterpret_cast(accessor.GetData()) + numSliceBytes * s; + const auto& slice = m_CompressedImageData[t][s]; + const auto destSize = LZ4_decompress_safe(slice.second, dest, slice.first, static_cast(numSliceBytes)); + + if (0 > destSize) + MITK_ERROR << "LZ4 decompression failed!"; } } - image->SetGeometry(m_ImageGeometry); - image->Modified(); + image->SetTimeGeometry(m_TimeGeometry->Clone()); return image; } diff --git a/Modules/DataTypesExt/test/mitkCompressedImageContainerTest.cpp b/Modules/DataTypesExt/test/mitkCompressedImageContainerTest.cpp index 4747eee7e4..ec6cf8ef05 100644 --- a/Modules/DataTypesExt/test/mitkCompressedImageContainerTest.cpp +++ b/Modules/DataTypesExt/test/mitkCompressedImageContainerTest.cpp @@ -1,182 +1,169 @@ /*============================================================================ 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 "mitkCompressedImageContainer.h" #include "mitkCoreObjectFactory.h" #include "mitkIOUtil.h" #include "mitkImageDataItem.h" #include "mitkImageReadAccessor.h" class mitkCompressedImageContainerTestClass { public: static void Test(mitk::CompressedImageContainer *container, mitk::Image *image, unsigned int &numberFailed) { - container->SetImage(image); // compress - mitk::Image::Pointer uncompressedImage = container->GetImage(); // uncompress + container->CompressImage(image); + mitk::Image::Pointer uncompressedImage = container->DecompressImage(); // check dimensions if (image->GetDimension() != uncompressedImage->GetDimension()) { ++numberFailed; std::cerr << " (EE) Number of image dimensions wrong after uncompression (was: " << image->GetDimension() << ", now: " << uncompressedImage->GetDimension() << ")" << std::endl; } for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { if (image->GetDimension(dim) != uncompressedImage->GetDimension(dim)) { ++numberFailed; std::cerr << " (EE) Image dimension " << dim << " differs after uncompression (was: " << image->GetDimension(dim) << ", now: " << uncompressedImage->GetDimension(dim) << ")" << std::endl; } } // check pixel type if (image->GetPixelType() != uncompressedImage->GetPixelType()) { ++numberFailed; std::cerr << " (EE) Pixel type wrong after uncompression:" << std::endl; mitk::PixelType m_PixelType = image->GetPixelType(); std::cout << "Original pixel type:" << std::endl; std::cout << " PixelType: " << m_PixelType.GetTypeAsString() << std::endl; std::cout << " BitsPerElement: " << m_PixelType.GetBpe() << std::endl; std::cout << " NumberOfComponents: " << m_PixelType.GetNumberOfComponents() << std::endl; std::cout << " BitsPerComponent: " << m_PixelType.GetBitsPerComponent() << std::endl; // m_PixelType = uncompressedImage->GetPixelType(); std::cout << "Uncompressed pixel type:" << std::endl; std::cout << " PixelType: " << uncompressedImage->GetPixelType().GetTypeAsString() << std::endl; std::cout << " BitsPerElement: " << uncompressedImage->GetPixelType().GetBpe() << std::endl; std::cout << " NumberOfComponents: " << uncompressedImage->GetPixelType().GetNumberOfComponents() << std::endl; std::cout << " BitsPerComponent: " << uncompressedImage->GetPixelType().GetBitsPerComponent() << std::endl; } // check data mitk::PixelType m_PixelType = image->GetPixelType(); unsigned long oneTimeStepSizeInBytes = m_PixelType.GetBpe() >> 3; // bits per element divided by 8 for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { if (dim < 3) { oneTimeStepSizeInBytes *= image->GetDimension(dim); } } unsigned int numberOfTimeSteps(1); if (image->GetDimension() > 3) { numberOfTimeSteps = image->GetDimension(3); } for (unsigned int timeStep = 0; timeStep < numberOfTimeSteps; ++timeStep) { mitk::ImageReadAccessor origImgAcc(image, image->GetVolumeData(timeStep)); mitk::ImageReadAccessor unCompImgAcc(uncompressedImage, uncompressedImage->GetVolumeData(timeStep)); auto *originalData((unsigned char *)origImgAcc.GetData()); auto *uncompressedData((unsigned char *)unCompImgAcc.GetData()); unsigned long difference(0); for (unsigned long byte = 0; byte < oneTimeStepSizeInBytes; ++byte) { if (originalData[byte] != uncompressedData[byte]) { ++difference; } } if (difference > 0) { ++numberFailed; std::cerr << " (EE) Pixel data in timestep " << timeStep << " not identical after uncompression. " << difference << " pixels different." << std::endl; break; // break "for timeStep" } } } }; /// ctest entry point int mitkCompressedImageContainerTest(int argc, char *argv[]) { // one big variable to tell if anything went wrong unsigned int numberFailed(0); // need one parameter (image filename) if (argc == 0) { std::cerr << "No file specified [FAILED]" << std::endl; return EXIT_FAILURE; } // load the image mitk::Image::Pointer image = nullptr; try { std::cout << "Testing with parameter '" << argv[1] << "'" << std::endl; image = mitk::IOUtil::Load(argv[1]); } catch (const mitk::Exception &) { std::cout << "File not an image - test will not be applied [PASSED]" << std::endl; std::cout << "[TEST DONE]" << std::endl; return EXIT_SUCCESS; } catch (itk::ExceptionObject &ex) { ++numberFailed; std::cerr << "Exception: " << ex << "[FAILED]" << std::endl; return EXIT_FAILURE; } std::cout << " (II) Could load image." << std::endl; - std::cout << "Testing instantiation" << std::endl; - // instantiation - mitk::CompressedImageContainer::Pointer container = mitk::CompressedImageContainer::New(); - if (container.IsNotNull()) { - std::cout << " (II) Instantiation works." << std::endl; - } - else - { - ++numberFailed; - std::cout << "Test failed, and it's the ugliest one!" << std::endl; - return EXIT_FAILURE; - } + mitk::CompressedImageContainer container; - // some real work - mitkCompressedImageContainerTestClass::Test(container, image, numberFailed); + // some real work + mitkCompressedImageContainerTestClass::Test(&container, image, numberFailed); - std::cout << "Testing destruction" << std::endl; - - // freeing - container = nullptr; + std::cout << "Testing destruction" << std::endl; + } std::cout << " (II) Freeing works." << std::endl; if (numberFailed > 0) { std::cerr << numberFailed << " tests failed." << std::endl; return EXIT_FAILURE; } else { std::cout << "PASSED all tests." << std::endl; return EXIT_SUCCESS; } } diff --git a/Modules/ImageExtraction/mitkExtractDirectedPlaneImageFilterNew.h b/Modules/ImageExtraction/mitkExtractDirectedPlaneImageFilterNew.h index db2c01511f..11daa46849 100644 --- a/Modules/ImageExtraction/mitkExtractDirectedPlaneImageFilterNew.h +++ b/Modules/ImageExtraction/mitkExtractDirectedPlaneImageFilterNew.h @@ -1,99 +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. ============================================================================*/ #ifndef mitkExtractDirectedPlaneImageFilterNew_h_Included #define mitkExtractDirectedPlaneImageFilterNew_h_Included #include "itkImage.h" #include "mitkITKImageImport.h" #include "mitkImageToImageFilter.h" #include namespace mitk { /** \deprecated This class is deprecated. Use mitk::ExtractSliceFilter instead. \sa ExtractSliceFilter \brief A filter that can extract a 2D slice from a 3D or 4D image especially if the image`s axes are rotated \sa ContourTool \sa SegTool2D \sa ExtractImageFilter - \sa OverwriteSliceImageFilter \sa OverwriteDirectedPlaneImageFilter \ingroup Process \ingroup Reliver There is a separate page describing the general design of QmitkInteractiveSegmentation: \ref QmitkSegmentationTechnicalPage This class takes an 3D or 4D mitk::Image as input and extracts a slice from it. If you work with a 4D image as input you have to specify the desired timestep at which the slice shall be extracted, otherwise the lowest given timestep is selected by default. The special feature of this filter is, that the planes of the input image can be rotated in any way. To assure a proper extraction you have to set the currentWorldPlaneGeometry with you can obtain from the BaseRenderer, respectively the positionEvent send by the renderer. The output will not be set if there was a problem with the input image $Author: fetzer $ */ class MITKIMAGEEXTRACTION_EXPORT ExtractDirectedPlaneImageFilterNew : public ImageToImageFilter { public: mitkClassMacro(ExtractDirectedPlaneImageFilterNew, ImageToImageFilter); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Set macro for the current worldgeometry \a Parameter The current wordgeometry that describes the position (rotation, translation) of the plane (and therefore the slice to be extracted) in our 3D(+t) image */ itkSetMacro(CurrentWorldPlaneGeometry, BaseGeometry *); /** * \deprecatedSince{2014_10} Please use SetCurrentWorldPlaneGeometry */ DEPRECATED(void SetCurrentWorldGeometry2D(BaseGeometry *geo)) { SetCurrentWorldPlaneGeometry(geo); }; itkSetMacro(ImageGeometry, BaseGeometry *); /** \brief Set macro for the current timestep \a Parameter The timestep of the image from which the slice shall be extracted */ itkSetMacro(ActualInputTimestep, int); protected: ExtractDirectedPlaneImageFilterNew(); ~ExtractDirectedPlaneImageFilterNew() override; void GenerateData() override; void GenerateOutputInformation() override; private: const BaseGeometry *m_CurrentWorldPlaneGeometry; const BaseGeometry *m_ImageGeometry; int m_ActualInputTimestep; template void ItkSliceExtraction(const itk::Image *inputImage); }; } // namespace #endif diff --git a/Modules/ImageExtraction/mitkExtractImageFilter.h b/Modules/ImageExtraction/mitkExtractImageFilter.h index 83b08e7009..911b5538d9 100644 --- a/Modules/ImageExtraction/mitkExtractImageFilter.h +++ b/Modules/ImageExtraction/mitkExtractImageFilter.h @@ -1,111 +1,110 @@ /*============================================================================ 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 mitkExtractImageFilter_h_Included #define mitkExtractImageFilter_h_Included #include "mitkCommon.h" #include "mitkImageToImageFilter.h" #include #include "itkImage.h" namespace mitk { /** \deprecated This class is deprecated. Use mitk::ExtractSliceFilter instead. \sa ExtractSliceFilter \brief Extracts a 2D slice from a 3D image. \sa SegTool2D - \sa OverwriteSliceImageFilter \ingroup Process \ingroup ToolManagerEtAl There is a separate page describing the general design of QmitkInteractiveSegmentation: \ref QmitkSegmentationTechnicalPage This class takes a 3D mitk::Image as input and tries to extract one slice from it. Two parameters determine which slice is extracted: the "slice dimension" is that one, which is constant for all points in the plane, e.g. axial would mean 2. The "slice index" is the slice index in the image direction you specified with "affected dimension". Indices count from zero. Output will not be set if there was a problem extracting the desired slice. Last contributor: $Author$ */ class MITKIMAGEEXTRACTION_EXPORT ExtractImageFilter : public ImageToImageFilter { public: mitkClassMacro(ExtractImageFilter, ImageToImageFilter); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Which slice to extract (first one has index 0). */ itkSetMacro(SliceIndex, unsigned int); itkGetConstMacro(SliceIndex, unsigned int); /** \brief The orientation of the slice to be extracted. \a Parameter SliceDimension Number of the dimension which is constant for all pixels of the desired slice (e.g. 2 for axial) */ itkSetMacro(SliceDimension, unsigned int); itkGetConstMacro(SliceDimension, unsigned int); /** \brief Time step of the image to be extracted. */ itkSetMacro(TimeStep, unsigned int); itkGetConstMacro(TimeStep, unsigned int); typedef enum DirectionCollapseStrategyEnum { DIRECTIONCOLLAPSETOUNKOWN = 0, DIRECTIONCOLLAPSETOIDENTITY = 1, DIRECTIONCOLLAPSETOSUBMATRIX = 2, DIRECTIONCOLLAPSETOGUESS = 3 } DIRECTIONCOLLAPSESTRATEGY; /** \brief Collapse strategy to be used. */ itkSetMacro(DirectionCollapseToStrategy, DIRECTIONCOLLAPSESTRATEGY); itkGetConstMacro(DirectionCollapseToStrategy, DIRECTIONCOLLAPSESTRATEGY); protected: ExtractImageFilter(); // purposely hidden ~ExtractImageFilter() override; void GenerateOutputInformation() override; void GenerateInputRequestedRegion() override; void GenerateData() override; template void ItkImageProcessing(const itk::Image *image); unsigned int m_SliceIndex; unsigned int m_SliceDimension; unsigned int m_TimeStep; DIRECTIONCOLLAPSESTRATEGY m_DirectionCollapseToStrategy; }; } // namespace #endif diff --git a/Modules/QtWidgets/files.cmake b/Modules/QtWidgets/files.cmake index 8352fe4632..ff8a6a8204 100644 --- a/Modules/QtWidgets/files.cmake +++ b/Modules/QtWidgets/files.cmake @@ -1,139 +1,137 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES QmitkAbstractDataStorageModel.cpp QmitkAbstractMultiWidget.cpp QmitkAbstractNodeSelectionWidget.cpp QmitkApplicationCursor.cpp QmitkDataStorageComboBox.cpp QmitkDataStorageDefaultListModel.cpp QmitkDataStorageHistoryModel.cpp QmitkDataStorageListModel.cpp QmitkDataStorageTableModel.cpp QmitkDataStorageSimpleTreeModel.cpp QmitkDataStorageTreeModel.cpp QmitkDataStorageTreeModelInternalItem.cpp QmitkDnDDataNodeWidget.cpp QmitkFileReaderOptionsDialog.cpp QmitkFileReaderWriterOptionsWidget.cpp QmitkFileWriterOptionsDialog.cpp QmitkInteractionSchemeToolBar.cpp QmitkIOUtil.cpp QmitkLevelWindowPresetDefinitionDialog.cpp QmitkLevelWindowRangeChangeDialog.cpp QmitkLevelWindowWidgetContextMenu.cpp QmitkLevelWindowWidget.cpp QmitkLineEditLevelWindowWidget.cpp QmitkMemoryUsageIndicatorView.cpp - QmitkMouseModeSwitcher.cpp QmitkMimeTypes.cpp QmitkMultiWidgetConfigurationToolBar.cpp QmitkMultiWidgetLayoutManager.cpp QmitkMultiWidgetLayoutSelectionWidget.cpp QmitkNodeDescriptor.cpp QmitkColoredNodeDescriptor.cpp QmitkNodeDescriptorManager.cpp QmitkProgressBar.cpp QmitkPropertiesTableEditor.cpp QmitkPropertiesTableModel.cpp QmitkPropertyDelegate.cpp QmitkRegisterClasses.cpp QmitkRenderingManager.cpp QmitkRenderingManagerFactory.cpp QmitkRenderWindow.cpp QmitkRenderWindowMenu.cpp QmitkRenderWindowWidget.cpp QmitkServiceListWidget.cpp QmitkSliderLevelWindowWidget.cpp QmitkStdMultiWidget.cpp QmitkMxNMultiWidget.cpp QmitkDataStorageComboBoxWithSelectNone.cpp QmitkDataStorageFilterProxyModel.cpp QmitkPropertyItem.cpp QmitkPropertyItemDelegate.cpp QmitkPropertyItemModel.cpp QmitkStyleManager.cpp QmitkAbstractDataStorageInspector.cpp QmitkDataStorageFavoriteNodesInspector.cpp QmitkDataStorageListInspector.cpp QmitkDataStorageTreeInspector.cpp QmitkDataStorageSelectionHistoryInspector.cpp QmitkModelViewSelectionConnector.cpp mitkIDataStorageInspectorProvider.cpp mitkQtWidgetsActivator.cpp mitkDataStorageInspectorGenerator.cpp QmitkOverlayWidget.cpp QmitkSimpleTextOverlayWidget.cpp QmitkNodeDetailsDialog.cpp ) set(MOC_H_FILES include/QmitkAbstractDataStorageModel.h include/QmitkAbstractMultiWidget.h include/QmitkAbstractNodeSelectionWidget.h include/QmitkDataStorageComboBox.h include/QmitkDataStorageTableModel.h include/QmitkDataStorageTreeModel.h include/QmitkDataStorageSimpleTreeModel.h include/QmitkDataStorageDefaultListModel.h include/QmitkDnDDataNodeWidget.h include/QmitkFileReaderOptionsDialog.h include/QmitkFileReaderWriterOptionsWidget.h include/QmitkFileWriterOptionsDialog.h include/QmitkInteractionSchemeToolBar.h include/QmitkLevelWindowPresetDefinitionDialog.h include/QmitkLevelWindowRangeChangeDialog.h include/QmitkLevelWindowWidgetContextMenu.h include/QmitkLevelWindowWidget.h include/QmitkLineEditLevelWindowWidget.h include/QmitkMemoryUsageIndicatorView.h - include/QmitkMouseModeSwitcher.h include/QmitkMultiWidgetConfigurationToolBar.h include/QmitkMultiWidgetLayoutManager.h include/QmitkMultiWidgetLayoutSelectionWidget.h include/QmitkNodeDescriptor.h include/QmitkColoredNodeDescriptor.h include/QmitkNodeDescriptorManager.h include/QmitkProgressBar.h include/QmitkPropertiesTableEditor.h include/QmitkPropertyDelegate.h include/QmitkRenderingManager.h include/QmitkRenderWindow.h include/QmitkRenderWindowMenu.h include/QmitkRenderWindowWidget.h include/QmitkServiceListWidget.h include/QmitkSliderLevelWindowWidget.h include/QmitkStdMultiWidget.h include/QmitkMxNMultiWidget.h include/QmitkDataStorageComboBoxWithSelectNone.h include/QmitkPropertyItemDelegate.h include/QmitkPropertyItemModel.h include/QmitkAbstractDataStorageInspector.h include/QmitkDataStorageFavoriteNodesInspector.h include/QmitkDataStorageListInspector.h include/QmitkDataStorageTreeInspector.h include/QmitkDataStorageHistoryModel.h include/QmitkDataStorageSelectionHistoryInspector.h include/QmitkModelViewSelectionConnector.h include/QmitkOverlayWidget.h include/QmitkSimpleTextOverlayWidget.h include/QmitkNodeDetailsDialog.h ) set(UI_FILES src/QmitkFileReaderOptionsDialog.ui src/QmitkFileWriterOptionsDialog.ui src/QmitkLevelWindowPresetDefinition.ui src/QmitkLevelWindowWidget.ui src/QmitkLevelWindowRangeChange.ui src/QmitkMemoryUsageIndicator.ui src/QmitkMultiWidgetLayoutSelectionWidget.ui src/QmitkServiceListWidgetControls.ui src/QmitkDataStorageListInspector.ui src/QmitkDataStorageTreeInspector.ui src/QmitkDataStorageSelectionHistoryInspector.ui ) set(QRC_FILES resource/Qmitk.qrc ) diff --git a/Modules/QtWidgets/include/QmitkInteractionSchemeToolBar.h b/Modules/QtWidgets/include/QmitkInteractionSchemeToolBar.h index 49f1c58eec..cda66228aa 100644 --- a/Modules/QtWidgets/include/QmitkInteractionSchemeToolBar.h +++ b/Modules/QtWidgets/include/QmitkInteractionSchemeToolBar.h @@ -1,56 +1,55 @@ /*============================================================================ 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 QMITKINTERACTIONSCHEMETOOLBAR_H #define QMITKINTERACTIONSCHEMETOOLBAR_H #include "MitkQtWidgetsExports.h" // mitk core #include "mitkInteractionSchemeSwitcher.h" #include #include /** * @brief * * */ class MITKQTWIDGETS_EXPORT QmitkInteractionSchemeToolBar : public QToolBar { Q_OBJECT public: using InteractionScheme = mitk::InteractionSchemeSwitcher::InteractionScheme; QmitkInteractionSchemeToolBar(QWidget* parent = nullptr); ~QmitkInteractionSchemeToolBar() override; void SetInteractionEventHandler(mitk::InteractionEventHandler::Pointer interactionEventHandler); -protected slots: +protected Q_SLOTS: - void OnInteractionSchemeChanged(); void AddButton(InteractionScheme id, const QString& toolName, const QIcon& icon, bool on = false); + void OnInteractionSchemeChanged(); private: QActionGroup* m_ActionGroup; - mitk::InteractionSchemeSwitcher::Pointer m_InteractionSchemeSwitcher; mitk::InteractionEventHandler::Pointer m_InteractionEventHandler; }; #endif // QMITKINTERACTIONSCHEMETOOLBAR_H diff --git a/Modules/QtWidgets/include/QmitkMouseModeSwitcher.h b/Modules/QtWidgets/include/QmitkMouseModeSwitcher.h deleted file mode 100644 index c88ab5c62f..0000000000 --- a/Modules/QtWidgets/include/QmitkMouseModeSwitcher.h +++ /dev/null @@ -1,84 +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 QmitkMouseModeSwitcher_h -#define QmitkMouseModeSwitcher_h - -#include "MitkQtWidgetsExports.h" - -#include "mitkMouseModeSwitcher.h" - -#include -#include - -/** - * \ingroup QmitkModule - * \brief Qt toolbar representing mitk::MouseModeSwitcher. - * - * Provides buttons for the interaction modes defined in mitk::MouseModeSwitcher - * and communicates with this non-graphical class. - * - * Can be used in a GUI to provide a mouse mode selector to the user. - */ -class MITKQTWIDGETS_EXPORT QmitkMouseModeSwitcher : public QToolBar -{ - Q_OBJECT - -public: - - QmitkMouseModeSwitcher(QWidget* parent = nullptr); - ~QmitkMouseModeSwitcher() override; - - typedef mitk::MouseModeSwitcher::MouseMode MouseMode; - -public slots: - - /** - \brief Connect to non-GUI class. - - When a button is pressed, given mitk::MouseModeSwitcher is informed to adapt interactors. - - \todo QmitkMouseModeSwitcher could be enhanced to actively observe mitk::MouseModeSwitcher and change available - actions or visibility appropriately. - */ - void setMouseModeSwitcher(mitk::MouseModeSwitcher*); - -signals: - - /** - \brief Mode activated. - - This signal is needed for other GUI element to react appropriately. - Sadly this is needed to provide "normal" functionality of QmitkStdMultiWidget, - because this must enable/disable automatic reaction of SliceNavigationControllers - to mouse clicks - depending on which mode is active. - */ - void MouseModeSelected(MouseMode id); - -protected slots: - - void OnMouseModeChangedViaButton(); - void addButton(MouseMode id, const QString& toolName, const QIcon& icon, bool on = false); - -protected: - - void OnMouseModeChangedViaCommand(const itk::EventObject&); - - QActionGroup* m_ActionGroup; - mitk::MouseModeSwitcher* m_MouseModeSwitcher; - - unsigned long m_ObserverTag; - - bool m_ActionButtonBlocked; -}; - -#endif diff --git a/Modules/QtWidgets/src/QmitkAbstractMultiWidget.cpp b/Modules/QtWidgets/src/QmitkAbstractMultiWidget.cpp index 71cac19f4d..9c96ffb57c 100644 --- a/Modules/QtWidgets/src/QmitkAbstractMultiWidget.cpp +++ b/Modules/QtWidgets/src/QmitkAbstractMultiWidget.cpp @@ -1,385 +1,384 @@ /*============================================================================ 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 qt widgets module #include "QmitkAbstractMultiWidget.h" #include "QmitkLevelWindowWidget.h" #include "QmitkMultiWidgetLayoutManager.h" #include "QmitkRenderWindowWidget.h" // mitk core #include #include // qt #include // c++ #include struct QmitkAbstractMultiWidget::Impl final { Impl(QmitkAbstractMultiWidget* multiWidget, const QString& multiWidgetName); void SetDataStorage(mitk::DataStorage* dataStorage) { if (dataStorage == m_DataStorage) { return; } m_DataStorage = dataStorage; // set new data storage for the render window widgets for (const auto& renderWindowWidget : m_RenderWindowWidgets) { renderWindowWidget.second->SetDataStorage(m_DataStorage); } } void InitializeDisplayActionEventHandling() { m_DisplayActionEventBroadcast = mitk::DisplayActionEventBroadcast::New(); m_DisplayActionEventBroadcast->LoadStateMachine("DisplayInteraction.xml"); - m_DisplayActionEventBroadcast->SetEventConfig("DisplayConfigPACS.xml"); } mitk::DataStorage::Pointer m_DataStorage; QString m_MultiWidgetName; RenderWindowWidgetMap m_RenderWindowWidgets; RenderWindowWidgetPointer m_ActiveRenderWindowWidget; int m_MultiWidgetRows; int m_MultiWidgetColumns; // interaction mitk::DisplayActionEventBroadcast::Pointer m_DisplayActionEventBroadcast; std::unique_ptr m_DisplayActionEventHandler; QmitkMultiWidgetLayoutManager* m_LayoutManager; }; QmitkAbstractMultiWidget::Impl::Impl(QmitkAbstractMultiWidget* multiWidget, const QString& multiWidgetName) : m_DataStorage(nullptr) , m_MultiWidgetName(multiWidgetName) , m_MultiWidgetRows(0) , m_MultiWidgetColumns(0) , m_DisplayActionEventBroadcast(nullptr) , m_DisplayActionEventHandler(nullptr) , m_LayoutManager(new QmitkMultiWidgetLayoutManager(multiWidget)) { InitializeDisplayActionEventHandling(); } QmitkAbstractMultiWidget::QmitkAbstractMultiWidget(QWidget* parent, Qt::WindowFlags f/* = 0*/, const QString& multiWidgetName/* = "multiwidget"*/) : QWidget(parent, f) , m_Impl(std::make_unique(this, multiWidgetName)) { // nothing here } QmitkAbstractMultiWidget::~QmitkAbstractMultiWidget() { } void QmitkAbstractMultiWidget::SetDataStorage(mitk::DataStorage* dataStorage) { m_Impl->SetDataStorage(dataStorage); } mitk::DataStorage* QmitkAbstractMultiWidget::GetDataStorage() const { return m_Impl->m_DataStorage; } int QmitkAbstractMultiWidget::GetRowCount() const { return m_Impl->m_MultiWidgetRows; } int QmitkAbstractMultiWidget::GetColumnCount() const { return m_Impl->m_MultiWidgetColumns; } void QmitkAbstractMultiWidget::SetLayout(int row, int column) { m_Impl->m_MultiWidgetRows = row; m_Impl->m_MultiWidgetColumns = column; SetLayoutImpl(); } void QmitkAbstractMultiWidget::SetInteractionScheme(mitk::InteractionSchemeSwitcher::InteractionScheme scheme) { auto interactionSchemeSwitcher = mitk::InteractionSchemeSwitcher::New(); auto interactionEventHandler = GetInteractionEventHandler(); try { interactionSchemeSwitcher->SetInteractionScheme(interactionEventHandler, scheme); } catch (const mitk::Exception&) { return; } SetInteractionSchemeImpl(); } mitk::InteractionEventHandler* QmitkAbstractMultiWidget::GetInteractionEventHandler() { return m_Impl->m_DisplayActionEventBroadcast.GetPointer(); } void QmitkAbstractMultiWidget::SetDisplayActionEventHandler(std::unique_ptr displayActionEventHandler) { m_Impl->m_DisplayActionEventHandler = std::move(displayActionEventHandler); m_Impl->m_DisplayActionEventHandler->SetObservableBroadcast(m_Impl->m_DisplayActionEventBroadcast); } mitk::DisplayActionEventHandler* QmitkAbstractMultiWidget::GetDisplayActionEventHandler() { return m_Impl->m_DisplayActionEventHandler.get(); } QmitkAbstractMultiWidget::RenderWindowWidgetMap QmitkAbstractMultiWidget::GetRenderWindowWidgets() const { return m_Impl->m_RenderWindowWidgets; } QmitkAbstractMultiWidget::RenderWindowWidgetMap QmitkAbstractMultiWidget::Get2DRenderWindowWidgets() const { RenderWindowWidgetMap renderWindowWidgets2D; auto renderWindowWidgets = GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { auto renderWindow = renderWindowWidget.second->GetRenderWindow(); if(mitk::BaseRenderer::Standard2D == mitk::BaseRenderer::GetInstance(renderWindow->GetVtkRenderWindow())->GetMapperID()) { renderWindowWidgets2D.insert(std::make_pair(renderWindowWidget.first, renderWindowWidget.second)); } } return renderWindowWidgets2D; } QmitkAbstractMultiWidget::RenderWindowWidgetMap QmitkAbstractMultiWidget::Get3DRenderWindowWidgets() const { RenderWindowWidgetMap renderWindowWidgets3D; auto renderWindowWidgets = GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { auto renderWindow = renderWindowWidget.second->GetRenderWindow(); if (mitk::BaseRenderer::Standard3D == mitk::BaseRenderer::GetInstance(renderWindow->GetVtkRenderWindow())->GetMapperID()) { renderWindowWidgets3D.insert(std::make_pair(renderWindowWidget.first, renderWindowWidget.second)); } } return renderWindowWidgets3D; } QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkAbstractMultiWidget::GetRenderWindowWidget(int row, int column) const { return GetRenderWindowWidget(GetNameFromIndex(row, column)); } QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkAbstractMultiWidget::GetRenderWindowWidget(const QString& widgetName) const { RenderWindowWidgetMap::const_iterator it = m_Impl->m_RenderWindowWidgets.find(widgetName); if (it != m_Impl->m_RenderWindowWidgets.end()) { return it->second; } return nullptr; } QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkAbstractMultiWidget::GetRenderWindowWidget(const QmitkRenderWindow* renderWindow) const { auto renderWindowWidgets = GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { if (renderWindowWidget.second->GetRenderWindow() == renderWindow) { return renderWindowWidget.second; } } return nullptr; } QmitkAbstractMultiWidget::RenderWindowHash QmitkAbstractMultiWidget::GetRenderWindows() const { RenderWindowHash result; // create QHash on demand auto renderWindowWidgets = GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { result.insert(renderWindowWidget.first, renderWindowWidget.second->GetRenderWindow()); } return result; } QmitkRenderWindow* QmitkAbstractMultiWidget::GetRenderWindow(int row, int column) const { return GetRenderWindow(GetNameFromIndex(row, column)); } QmitkRenderWindow* QmitkAbstractMultiWidget::GetRenderWindow(const QString& widgetName) const { RenderWindowWidgetPointer renderWindowWidget = GetRenderWindowWidget(widgetName); if (nullptr != renderWindowWidget) { return renderWindowWidget->GetRenderWindow(); } return nullptr; } void QmitkAbstractMultiWidget::SetActiveRenderWindowWidget(RenderWindowWidgetPointer activeRenderWindowWidget) { m_Impl->m_ActiveRenderWindowWidget = activeRenderWindowWidget; emit ActiveRenderWindowChanged(); } QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkAbstractMultiWidget::GetActiveRenderWindowWidget() const { return m_Impl->m_ActiveRenderWindowWidget; } QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkAbstractMultiWidget::GetFirstRenderWindowWidget() const { if (!m_Impl->m_RenderWindowWidgets.empty()) { return m_Impl->m_RenderWindowWidgets.begin()->second; } else { return nullptr; } } QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkAbstractMultiWidget::GetLastRenderWindowWidget() const { if (!m_Impl->m_RenderWindowWidgets.empty()) { return m_Impl->m_RenderWindowWidgets.rbegin()->second; } else { return nullptr; } } QString QmitkAbstractMultiWidget::GetNameFromIndex(int row, int column) const { if (0 <= row && m_Impl->m_MultiWidgetRows > row && 0 <= column && m_Impl->m_MultiWidgetColumns > column) { return GetNameFromIndex(row * m_Impl->m_MultiWidgetColumns + column); } return QString(); } QString QmitkAbstractMultiWidget::GetNameFromIndex(size_t index) const { if (index <= m_Impl->m_RenderWindowWidgets.size()) { return m_Impl->m_MultiWidgetName + ".widget" + QString::number(index); } return QString(); } unsigned int QmitkAbstractMultiWidget::GetNumberOfRenderWindowWidgets() const { return m_Impl->m_RenderWindowWidgets.size(); } void QmitkAbstractMultiWidget::RequestUpdate(const QString& widgetName) { RenderWindowWidgetPointer renderWindowWidget = GetRenderWindowWidget(widgetName); if (nullptr != renderWindowWidget) { return renderWindowWidget->RequestUpdate(); } } void QmitkAbstractMultiWidget::RequestUpdateAll() { for (const auto& renderWindowWidget : m_Impl->m_RenderWindowWidgets) { renderWindowWidget.second->RequestUpdate(); } } void QmitkAbstractMultiWidget::ForceImmediateUpdate(const QString& widgetName) { RenderWindowWidgetPointer renderWindowWidget = GetRenderWindowWidget(widgetName); if (nullptr != renderWindowWidget) { renderWindowWidget->ForceImmediateUpdate(); } } void QmitkAbstractMultiWidget::ForceImmediateUpdateAll() { for (const auto& renderWindowWidget : m_Impl->m_RenderWindowWidgets) { renderWindowWidget.second->ForceImmediateUpdate(); } } void QmitkAbstractMultiWidget::ActivateMenuWidget(bool state) { for (const auto& renderWindowWidget : m_Impl->m_RenderWindowWidgets) { auto renderWindow = renderWindowWidget.second->GetRenderWindow(); renderWindow->ActivateMenuWidget(state); } } bool QmitkAbstractMultiWidget::IsMenuWidgetEnabled() const { return m_Impl->m_ActiveRenderWindowWidget->GetRenderWindow()->GetActivateMenuWidgetFlag(); } QmitkMultiWidgetLayoutManager* QmitkAbstractMultiWidget::GetMultiWidgetLayoutManager() const { return m_Impl->m_LayoutManager; } void QmitkAbstractMultiWidget::AddRenderWindowWidget(const QString& widgetName, RenderWindowWidgetPointer renderWindowWidget) { m_Impl->m_RenderWindowWidgets.insert(std::make_pair(widgetName, renderWindowWidget)); } void QmitkAbstractMultiWidget::RemoveRenderWindowWidget() { auto iterator = m_Impl->m_RenderWindowWidgets.find(GetNameFromIndex(GetRenderWindowWidgets().size() - 1)); if (iterator == m_Impl->m_RenderWindowWidgets.end()) { return; } // disconnect each signal of this render window widget RenderWindowWidgetPointer renderWindowWidgetToRemove = iterator->second; disconnect(renderWindowWidgetToRemove.get(), 0, 0, 0); // erase the render window from the map m_Impl->m_RenderWindowWidgets.erase(iterator); } diff --git a/Modules/QtWidgets/src/QmitkInteractionSchemeToolBar.cpp b/Modules/QtWidgets/src/QmitkInteractionSchemeToolBar.cpp index dbb54e0899..adb126da5e 100644 --- a/Modules/QtWidgets/src/QmitkInteractionSchemeToolBar.cpp +++ b/Modules/QtWidgets/src/QmitkInteractionSchemeToolBar.cpp @@ -1,86 +1,90 @@ /*============================================================================ 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 "QmitkInteractionSchemeToolBar.h" #include QmitkInteractionSchemeToolBar::QmitkInteractionSchemeToolBar(QWidget* parent/* = nullptr*/) : QToolBar(parent) , m_ActionGroup(new QActionGroup(this)) - , m_InteractionSchemeSwitcher(nullptr) , m_InteractionEventHandler(nullptr) { QToolBar::setOrientation(Qt::Vertical); QToolBar::setIconSize(QSize(17, 17)); - m_ActionGroup->setExclusive(true); // only one selectable action + QToolBar::setFixedWidth(33); + m_ActionGroup->setExclusive(false); // allow having no action selected AddButton(InteractionScheme::PACSStandard, tr("Pointer"), QIcon(":/Qmitk/mm_pointer.png"), true); AddButton(InteractionScheme::PACSLevelWindow, tr("Level/Window"), QIcon(":/Qmitk/mm_contrast.png")); AddButton(InteractionScheme::PACSPan, tr("Pan"), QIcon(":/Qmitk/mm_pan.png")); AddButton(InteractionScheme::PACSScroll, tr("Scroll"), QIcon(":/Qmitk/mm_scroll.png")); AddButton(InteractionScheme::PACSZoom, tr("Zoom"), QIcon(":/Qmitk/mm_zoom.png")); +} - m_InteractionSchemeSwitcher = mitk::InteractionSchemeSwitcher::New(); +QmitkInteractionSchemeToolBar::~QmitkInteractionSchemeToolBar() +{ + // nothing here } void QmitkInteractionSchemeToolBar::SetInteractionEventHandler(mitk::InteractionEventHandler::Pointer interactionEventHandler) { if (interactionEventHandler == m_InteractionEventHandler) { return; } m_InteractionEventHandler = interactionEventHandler; - try - { - m_InteractionSchemeSwitcher->SetInteractionScheme(m_InteractionEventHandler, InteractionScheme::PACSStandard); - } - catch (const mitk::Exception&) - { - return; - } } void QmitkInteractionSchemeToolBar::AddButton(InteractionScheme interactionScheme, const QString& toolName, const QIcon& icon, bool on) { QAction* action = new QAction(icon, toolName, this); action->setCheckable(true); action->setActionGroup(m_ActionGroup); action->setChecked(on); action->setData(interactionScheme); - connect(action, SIGNAL(triggered()), this, SLOT(OnInteractionSchemeChanged())); + connect(action, &QAction::triggered, this, &QmitkInteractionSchemeToolBar::OnInteractionSchemeChanged); QToolBar::addAction(action); } -QmitkInteractionSchemeToolBar::~QmitkInteractionSchemeToolBar() -{ - // nothing here -} - void QmitkInteractionSchemeToolBar::OnInteractionSchemeChanged() { QAction* action = dynamic_cast(sender()); - if (action) + if (nullptr != action) { + for (auto actionIter : m_ActionGroup->actions()) + { + if (actionIter != action) + { + actionIter->setChecked(false); + } + } + InteractionScheme interactionScheme = static_cast(action->data().toInt()); + // If the selected option is unchecked, use the base interaction with no primary tool + if (!action->isChecked()) + { + interactionScheme = InteractionScheme::PACSBase; + } + auto interactionSchemeSwitcher = mitk::InteractionSchemeSwitcher::New(); try { - m_InteractionSchemeSwitcher->SetInteractionScheme(m_InteractionEventHandler, interactionScheme); + interactionSchemeSwitcher->SetInteractionScheme(m_InteractionEventHandler, interactionScheme); } - catch (const mitk::Exception&) + catch (const mitk::Exception &) { return; } } } diff --git a/Modules/QtWidgets/src/QmitkMouseModeSwitcher.cpp b/Modules/QtWidgets/src/QmitkMouseModeSwitcher.cpp deleted file mode 100644 index 5b46e055dd..0000000000 --- a/Modules/QtWidgets/src/QmitkMouseModeSwitcher.cpp +++ /dev/null @@ -1,108 +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 "QmitkMouseModeSwitcher.h" -#include - -QmitkMouseModeSwitcher::QmitkMouseModeSwitcher(QWidget* parent/* = nullptr*/) - : QToolBar(parent) - , m_ActionGroup(new QActionGroup(this)) - , m_MouseModeSwitcher(nullptr) - , m_ObserverTag(0) - , m_ActionButtonBlocked(false) -{ - QToolBar::setOrientation(Qt::Vertical); - QToolBar::setIconSize(QSize(17, 17)); - m_ActionGroup->setExclusive(true); // only one selectable action - - addButton(mitk::MouseModeSwitcher::MousePointer, tr("Pointer"), QIcon(":/Qmitk/mm_pointer.png"), true); // toggle ON - addButton(mitk::MouseModeSwitcher::Scroll, tr("Scroll"), QIcon(":/Qmitk/mm_scroll.png")); - addButton(mitk::MouseModeSwitcher::LevelWindow, tr("Level/Window"), QIcon(":/Qmitk/mm_contrast.png")); - addButton(mitk::MouseModeSwitcher::Zoom, tr("Zoom"), QIcon(":/Qmitk/mm_zoom.png")); - addButton(mitk::MouseModeSwitcher::Pan, tr("Pan"), QIcon(":/Qmitk/mm_pan.png")); -} - -void QmitkMouseModeSwitcher::addButton(MouseMode id, const QString &toolName, const QIcon &icon, bool on) -{ - QAction* action = new QAction(icon, toolName, this); - action->setCheckable(true); - action->setActionGroup(m_ActionGroup); - action->setChecked(on); - action->setData(id); - connect(action, SIGNAL(triggered()), this, SLOT(OnMouseModeChangedViaButton())); - QToolBar::addAction(action); -} - -QmitkMouseModeSwitcher::~QmitkMouseModeSwitcher() -{ - if (nullptr != m_MouseModeSwitcher) - { - m_MouseModeSwitcher->RemoveObserver(m_ObserverTag); - } -} - -void QmitkMouseModeSwitcher::setMouseModeSwitcher(mitk::MouseModeSwitcher *mms) -{ - // goodbye / welcome ceremonies - if (nullptr != m_MouseModeSwitcher) - { - m_MouseModeSwitcher->RemoveObserver(m_ObserverTag); - } - - m_MouseModeSwitcher = mms; - - if (m_MouseModeSwitcher) - { - itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); - command->SetCallbackFunction(this, &QmitkMouseModeSwitcher::OnMouseModeChangedViaCommand); - m_ObserverTag = m_MouseModeSwitcher->AddObserver(mitk::MouseModeSwitcher::MouseModeChangedEvent(), command); - } -} - -void QmitkMouseModeSwitcher::OnMouseModeChangedViaButton() -{ - if (m_ActionButtonBlocked) - { - return; // blocked, since we change the checked state of the buttons in the callback function - } - - QAction* action = dynamic_cast(sender()); - if (action) - { - MouseMode id = static_cast(action->data().toInt()); - // qDebug() << "Mouse mode '" << qPrintable(action->text()) << "' selected, trigger mode id " << id; - - if (m_MouseModeSwitcher) - { - m_MouseModeSwitcher->SetInteractionScheme(mitk::MouseModeSwitcher::InteractionScheme::PACS); - m_MouseModeSwitcher->SelectMouseMode(id); - } - emit MouseModeSelected(id); - } -} - -void QmitkMouseModeSwitcher::OnMouseModeChangedViaCommand(const itk::EventObject &) -{ - m_ActionButtonBlocked = true; - - assert(m_MouseModeSwitcher); - MouseMode activeMode = m_MouseModeSwitcher->GetCurrentMouseMode(); - foreach(QAction* action, m_ActionGroup->actions()) - { - if (action->data().toInt() == activeMode) - { - action->setChecked(true); - } - } - - m_ActionButtonBlocked = false; -} diff --git a/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp b/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp index aae74f80bb..6146d1e0e7 100644 --- a/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp +++ b/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp @@ -1,272 +1,273 @@ /*============================================================================ 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 "QmitkMxNMultiWidget.h" #include "QmitkRenderWindowWidget.h" // mitk core #include #include #include // qt #include QmitkMxNMultiWidget::QmitkMxNMultiWidget(QWidget* parent, Qt::WindowFlags f/* = 0*/, const QString& multiWidgetName/* = "mxnmulti"*/) : QmitkAbstractMultiWidget(parent, f, multiWidgetName) , m_CrosshairVisibility(false) { // nothing here } void QmitkMxNMultiWidget::InitializeMultiWidget() { SetLayout(1, 1); ActivateMenuWidget(true); SetDisplayActionEventHandler(std::make_unique()); auto displayActionEventHandler = GetDisplayActionEventHandler(); if (nullptr != displayActionEventHandler) { displayActionEventHandler->InitActions(); } } void QmitkMxNMultiWidget::MultiWidgetOpened() { SetCrosshairVisibility(true); } void QmitkMxNMultiWidget::MultiWidgetClosed() { SetCrosshairVisibility(false); } void QmitkMxNMultiWidget::Synchronize(bool synchronized) { if (synchronized) { SetDisplayActionEventHandler(std::make_unique()); } else { SetDisplayActionEventHandler(std::make_unique()); } auto displayActionEventHandler = GetDisplayActionEventHandler(); if (nullptr != displayActionEventHandler) { displayActionEventHandler->InitActions(); } } QmitkRenderWindow* QmitkMxNMultiWidget::GetRenderWindow(const QString& widgetName) const { if ("axial" == widgetName || "sagittal" == widgetName || "coronal" == widgetName || "3d" == widgetName) { return GetActiveRenderWindowWidget()->GetRenderWindow(); } return QmitkAbstractMultiWidget::GetRenderWindow(widgetName); } QmitkRenderWindow* QmitkMxNMultiWidget::GetRenderWindow(const mitk::BaseRenderer::ViewDirection& /*viewDirection*/) const { // currently no mapping between view directions and render windows // simply return the currently active render window return GetActiveRenderWindowWidget()->GetRenderWindow(); } void QmitkMxNMultiWidget::SetActiveRenderWindowWidget(RenderWindowWidgetPointer activeRenderWindowWidget) { auto currentActiveRenderWindowWidget = GetActiveRenderWindowWidget(); if (currentActiveRenderWindowWidget == activeRenderWindowWidget) { return; } // reset the decoration color of the previously active render window widget if (nullptr != currentActiveRenderWindowWidget) { auto decorationColor = currentActiveRenderWindowWidget->GetDecorationColor(); QColor hexColor(decorationColor[0] * 255, decorationColor[1] * 255, decorationColor[2] * 255); - currentActiveRenderWindowWidget->setStyleSheet("border: 2px solid " + hexColor.name(QColor::HexRgb)); + currentActiveRenderWindowWidget->setStyleSheet("QmitkRenderWindowWidget { border: 2px solid " + + hexColor.name(QColor::HexRgb) + "; }"); } // set the new decoration color of the currently active render window widget if (nullptr != activeRenderWindowWidget) { - activeRenderWindowWidget->setStyleSheet("border: 2px solid #FF6464"); + activeRenderWindowWidget->setStyleSheet("QmitkRenderWindowWidget { border: 2px solid #FF6464; }"); } QmitkAbstractMultiWidget::SetActiveRenderWindowWidget(activeRenderWindowWidget); } void QmitkMxNMultiWidget::SetSelectedPosition(const mitk::Point3D& newPosition, const QString& widgetName) { RenderWindowWidgetPointer renderWindowWidget; if (widgetName.isNull()) { renderWindowWidget = GetActiveRenderWindowWidget(); } else { renderWindowWidget = GetRenderWindowWidget(widgetName); } if (nullptr != renderWindowWidget) { renderWindowWidget->GetSliceNavigationController()->SelectSliceByPoint(newPosition); renderWindowWidget->RequestUpdate(); return; } MITK_ERROR << "Position can not be set for an unknown render window widget."; } const mitk::Point3D QmitkMxNMultiWidget::GetSelectedPosition(const QString& /*widgetName*/) const { // see T26208 return mitk::Point3D(); } void QmitkMxNMultiWidget::SetCrosshairVisibility(bool activate) { auto renderWindowWidgets = GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { renderWindowWidget.second->ActivateCrosshair(activate); } m_CrosshairVisibility = activate; } void QmitkMxNMultiWidget::ResetCrosshair() { auto dataStorage = GetDataStorage(); if (nullptr == dataStorage) { return; } mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(dataStorage); SetWidgetPlaneMode(mitk::InteractionSchemeSwitcher::MITKStandard); } void QmitkMxNMultiWidget::SetWidgetPlaneMode(int userMode) { MITK_DEBUG << "Changing crosshair mode to " << userMode; switch (userMode) { case 0: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKStandard); break; case 1: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationUncoupled); break; case 2: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationCoupled); break; case 3: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKSwivel); break; } } ////////////////////////////////////////////////////////////////////////// // PUBLIC SLOTS // MOUSE EVENTS ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidget::wheelEvent(QWheelEvent* e) { emit WheelMoved(e); } void QmitkMxNMultiWidget::mousePressEvent(QMouseEvent* e) { if (QEvent::MouseButtonPress != e->type()) { return; } auto renderWindowWidget = dynamic_cast(this->sender()); if (nullptr == renderWindowWidget) { return; } auto renderWindowWidgetPointer = GetRenderWindowWidget(renderWindowWidget->GetWidgetName()); SetActiveRenderWindowWidget(renderWindowWidgetPointer); } void QmitkMxNMultiWidget::moveEvent(QMoveEvent* e) { QWidget::moveEvent(e); // it is necessary to readjust the position of the overlays as the MultiWidget has moved // unfortunately it's not done by QmitkRenderWindow::moveEvent -> must be done here emit Moved(); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidget::SetLayoutImpl() { int requiredRenderWindowWidgets = GetRowCount() * GetColumnCount(); int existingRenderWindowWidgets = GetRenderWindowWidgets().size(); int difference = requiredRenderWindowWidgets - existingRenderWindowWidgets; while (0 < difference) { // more render window widgets needed CreateRenderWindowWidget(); --difference; } while (0 > difference) { // less render window widgets needed RemoveRenderWindowWidget(); ++difference; } auto firstRenderWindowWidget = GetFirstRenderWindowWidget(); if (nullptr != firstRenderWindowWidget) { SetActiveRenderWindowWidget(firstRenderWindowWidget); } GetMultiWidgetLayoutManager()->SetLayoutDesign(QmitkMultiWidgetLayoutManager::LayoutDesign::DEFAULT); } void QmitkMxNMultiWidget::CreateRenderWindowWidget() { // create the render window widget and connect signal / slot QString renderWindowWidgetName = GetNameFromIndex(GetNumberOfRenderWindowWidgets()); RenderWindowWidgetPointer renderWindowWidget = std::make_shared(this, renderWindowWidgetName, GetDataStorage()); renderWindowWidget->SetCornerAnnotationText(renderWindowWidgetName.toStdString()); connect(renderWindowWidget.get(), &QmitkRenderWindowWidget::MouseEvent, this, &QmitkMxNMultiWidget::mousePressEvent); AddRenderWindowWidget(renderWindowWidgetName, renderWindowWidget); auto renderWindow = renderWindowWidget->GetRenderWindow(); auto layoutManager = GetMultiWidgetLayoutManager(); connect(renderWindow, &QmitkRenderWindow::LayoutDesignChanged, layoutManager, &QmitkMultiWidgetLayoutManager::SetLayoutDesign); connect(renderWindow, &QmitkRenderWindow::ResetView, this, &QmitkMxNMultiWidget::ResetCrosshair); connect(renderWindow, &QmitkRenderWindow::CrosshairVisibilityChanged, this, &QmitkMxNMultiWidget::SetCrosshairVisibility); connect(renderWindow, &QmitkRenderWindow::CrosshairRotationModeChanged, this, &QmitkMxNMultiWidget::SetWidgetPlaneMode); } diff --git a/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp b/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp index daaaadefa6..c30c15cd4f 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindowWidget.cpp @@ -1,254 +1,251 @@ /*============================================================================ 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 "QmitkRenderWindowWidget.h" // vtk #include #include QmitkRenderWindowWidget::QmitkRenderWindowWidget(QWidget* parent/* = nullptr*/, const QString& widgetName/* = ""*/, mitk::DataStorage* dataStorage/* = nullptr*/) : QFrame(parent) , m_WidgetName(widgetName) , m_DataStorage(dataStorage) , m_RenderWindow(nullptr) , m_PointSetNode(nullptr) , m_PointSet(nullptr) { this->InitializeGUI(); } QmitkRenderWindowWidget::~QmitkRenderWindowWidget() { auto sliceNavigationController = m_RenderWindow->GetSliceNavigationController(); if (nullptr != sliceNavigationController) { sliceNavigationController->SetCrosshairEvent.RemoveListener(mitk::MessageDelegate1(this, &QmitkRenderWindowWidget::SetCrosshair)); } if (nullptr != m_DataStorage) { m_DataStorage->Remove(m_PointSetNode); } } void QmitkRenderWindowWidget::SetDataStorage(mitk::DataStorage* dataStorage) { if (dataStorage == m_DataStorage) { return; } m_DataStorage = dataStorage; if (nullptr != m_RenderWindow) { mitk::BaseRenderer::GetInstance(m_RenderWindow->renderWindow())->SetDataStorage(dataStorage); } } mitk::SliceNavigationController* QmitkRenderWindowWidget::GetSliceNavigationController() const { return m_RenderWindow->GetSliceNavigationController(); } void QmitkRenderWindowWidget::RequestUpdate() { mitk::RenderingManager::GetInstance()->RequestUpdate(m_RenderWindow->renderWindow()); } void QmitkRenderWindowWidget::ForceImmediateUpdate() { mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(m_RenderWindow->renderWindow()); } void QmitkRenderWindowWidget::SetGradientBackgroundColors(const mitk::Color& upper, const mitk::Color& lower) { vtkRenderer* vtkRenderer = m_RenderWindow->GetRenderer()->GetVtkRenderer(); if (nullptr == vtkRenderer) { return; } m_GradientBackgroundColors.first = upper; m_GradientBackgroundColors.second = lower; vtkRenderer->SetBackground(lower[0], lower[1], lower[2]); vtkRenderer->SetBackground2(upper[0], upper[1], upper[2]); ShowGradientBackground(true); } void QmitkRenderWindowWidget::ShowGradientBackground(bool show) { m_RenderWindow->GetRenderer()->GetVtkRenderer()->SetGradientBackground(show); } bool QmitkRenderWindowWidget::IsGradientBackgroundOn() const { return m_RenderWindow->GetRenderer()->GetVtkRenderer()->GetGradientBackground(); } void QmitkRenderWindowWidget::SetDecorationColor(const mitk::Color& color) { m_DecorationColor = color; m_CornerAnnotation->GetTextProperty()->SetColor(m_DecorationColor[0], m_DecorationColor[1], m_DecorationColor[2]); QColor hexColor(m_DecorationColor[0] * 255, m_DecorationColor[1] * 255, m_DecorationColor[2] * 255); setStyleSheet("QmitkRenderWindowWidget { border: 2px solid " + hexColor.name(QColor::HexRgb) + "; }"); } void QmitkRenderWindowWidget::ShowColoredRectangle(bool show) { if (show) { setFrameStyle(QFrame::Box | QFrame::Plain); } else { setFrameStyle(NoFrame); } } bool QmitkRenderWindowWidget::IsColoredRectangleVisible() const { return frameStyle() > 0; } void QmitkRenderWindowWidget::ShowCornerAnnotation(bool show) { m_CornerAnnotation->SetVisibility(show); } bool QmitkRenderWindowWidget::IsCornerAnnotationVisible() const { return m_CornerAnnotation->GetVisibility() > 0; } void QmitkRenderWindowWidget::SetCornerAnnotationText(const std::string& cornerAnnotation) { m_CornerAnnotation->SetText(0, cornerAnnotation.c_str()); } std::string QmitkRenderWindowWidget::GetCornerAnnotationText() const { return std::string(m_CornerAnnotation->GetText(0)); } bool QmitkRenderWindowWidget::IsRenderWindowMenuActivated() const { return m_RenderWindow->GetActivateMenuWidgetFlag(); } void QmitkRenderWindowWidget::ActivateCrosshair(bool activate) { if (nullptr == m_DataStorage) { return; } if (activate) { try { m_DataStorage->Add(m_PointSetNode); } catch(std::invalid_argument& /*e*/) { // crosshair already existing return; } } else { m_DataStorage->Remove(m_PointSetNode); } } void QmitkRenderWindowWidget::InitializeGUI() { m_Layout = new QHBoxLayout(this); m_Layout->setMargin(0); setLayout(m_Layout); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setContentsMargins(0, 0, 0, 0); if (nullptr == m_DataStorage) { return; } mitk::RenderingManager::GetInstance()->SetDataStorage(m_DataStorage); // create render window for this render window widget m_RenderWindow = new QmitkRenderWindow(this, m_WidgetName, nullptr); m_RenderWindow->SetLayoutIndex(mitk::BaseRenderer::ViewDirection::SAGITTAL); m_RenderWindow->GetSliceNavigationController()->SetDefaultViewDirection(mitk::SliceNavigationController::Sagittal); m_RenderWindow->GetSliceNavigationController()->SetCrosshairEvent.AddListener(mitk::MessageDelegate1(this, &QmitkRenderWindowWidget::SetCrosshair)); connect(m_RenderWindow, &QmitkRenderWindow::mouseEvent, this, &QmitkRenderWindowWidget::MouseEvent); mitk::TimeGeometry::ConstPointer timeGeometry = m_DataStorage->ComputeBoundingGeometry3D(m_DataStorage->GetAll()); mitk::RenderingManager::GetInstance()->InitializeViews(timeGeometry); m_Layout->addWidget(m_RenderWindow); // add point set as a crosshair m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->SetProperty("name", mitk::StringProperty::New("Crosshair of render window " + m_WidgetName.toStdString())); m_PointSetNode->SetProperty("helper object", mitk::BoolProperty::New(true)); // crosshair-node should typically be invisible // set the crosshair only visible for this specific renderer m_PointSetNode->SetBoolProperty("fixedLayer", true, m_RenderWindow->GetRenderer()); m_PointSetNode->SetVisibility(true, m_RenderWindow->GetRenderer()); m_PointSetNode->SetVisibility(false); m_PointSet = mitk::PointSet::New(); m_PointSetNode->SetData(m_PointSet); // set colors and corner annotation InitializeDecorations(); } void QmitkRenderWindowWidget::InitializeDecorations() { vtkRenderer* vtkRenderer = m_RenderWindow->GetRenderer()->GetVtkRenderer(); if (nullptr == vtkRenderer) { return; } // initialize background color gradients float black[3] = { 0.0f, 0.0f, 0.0f }; SetGradientBackgroundColors(black, black); - // initialize decoration color, rectangle and annotation text - float white[3] = { 1.0f, 1.0f, 1.0f }; - m_DecorationColor = white; - + // initialize annotation text and decoration color setFrameStyle(QFrame::Box | QFrame::Plain); - QColor hexColor(m_DecorationColor[0] * 255, m_DecorationColor[1] * 255, m_DecorationColor[2] * 255); - setStyleSheet("border: 2px solid " + hexColor.name(QColor::HexRgb)); m_CornerAnnotation = vtkSmartPointer::New(); m_CornerAnnotation->SetText(0, "Sagittal"); m_CornerAnnotation->SetMaximumFontSize(12); - m_CornerAnnotation->GetTextProperty()->SetColor(m_DecorationColor[0], m_DecorationColor[1], m_DecorationColor[2]); if (0 == vtkRenderer->HasViewProp(m_CornerAnnotation)) { vtkRenderer->AddViewProp(m_CornerAnnotation); } + + float white[3] = { 1.0f, 1.0f, 1.0f }; + SetDecorationColor(mitk::Color(white)); } void QmitkRenderWindowWidget::SetCrosshair(mitk::Point3D selectedPoint) { m_PointSet->SetPoint(1, selectedPoint, 0); mitk::RenderingManager::GetInstance()->RequestUpdate(m_RenderWindow->renderWindow()); } diff --git a/Modules/Segmentation/Algorithms/mitkDiffImageApplier.h b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.h index 956fe8961c..6f28dbf217 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffImageApplier.h +++ b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.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 mitkDiffImageApplier_h_Included #define mitkDiffImageApplier_h_Included #include "mitkCommon.h" #include "mitkImage.h" #include "mitkOperationActor.h" #include #include #include namespace mitk { /** \brief Applies difference images to 3D images. - This class is supposed to execute ApplyDiffImageOperations, which contain information about pixel changes within one - image slice. - Class should be called from the undo stack. At the moment, ApplyDiffImageOperations are only created by - OverwriteSliceImageFilter. + This class is supposed to execute ApplyDiffImageOperations, which contain information about + pixel changes within one image slice. + Class should be called from the undo stack. + At the moment, ApplyDiffImageOperations are only created by QmitkSlicesInterpolator. $Author: maleike $ */ class MITKSEGMENTATION_EXPORT DiffImageApplier : public itk::Object, public OperationActor { public: mitkClassMacroItkParent(DiffImageApplier, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void ExecuteOperation(Operation *operation) override; static DiffImageApplier *GetInstanceForUndo(); protected: DiffImageApplier(); // purposely hidden ~DiffImageApplier() override; template void ItkImageSwitch2DDiff(itk::Image *image); template void ItkImageSwitch3DDiff(itk::Image *image); template void ItkImageProcessing2DDiff(itk::Image *itkImage1, itk::Image *itkImage2); template void ItkImageProcessing3DDiff(itk::Image *itkImage1, itk::Image *itkImage2); template void ItkInvertPixelValues(itk::Image *itkImage); Image::Pointer m_Image; Image::Pointer m_SliceDifferenceImage; unsigned int m_SliceIndex; unsigned int m_SliceDimension; unsigned int m_TimeStep; unsigned int m_Dimension0; unsigned int m_Dimension1; double m_Factor; }; } // namespace #endif diff --git a/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp b/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp index bab4084f3e..61962f21f9 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp +++ b/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp @@ -1,100 +1,96 @@ /*============================================================================ 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 "mitkDiffSliceOperation.h" #include #include mitk::DiffSliceOperation::DiffSliceOperation() : Operation(1) { m_TimeStep = 0; - m_zlibSliceContainer = nullptr; m_Image = nullptr; m_WorldGeometry = nullptr; m_SliceGeometry = nullptr; m_ImageIsValid = false; m_DeleteObserverTag = 0; } mitk::DiffSliceOperation::DiffSliceOperation(Image *imageVolume, const Image *slice, const SlicedGeometry3D *sliceGeometry, TimeStepType timestep, const BaseGeometry *currentWorldGeometry) : Operation(1) { m_WorldGeometry = currentWorldGeometry->Clone(); /* Quick fix for bug 12338. Guard object - fix this when clone method of PlaneGeometry is cloning the reference geometry (see bug 13392)*/ // xxxx m_GuardReferenceGeometry = mitk::BaseGeometry::New(); m_GuardReferenceGeometry = dynamic_cast(m_WorldGeometry.GetPointer())->GetReferenceGeometry(); /*---------------------------------------------------------------------------------------------------*/ m_SliceGeometry = sliceGeometry->Clone(); m_TimeStep = timestep; - m_zlibSliceContainer = CompressedImageContainer::New(); - m_zlibSliceContainer->SetImage(slice); + m_CompressedImageContainer.CompressImage(slice); m_Image = imageVolume; m_DeleteObserverTag = 0; if (m_Image) { /*add an observer to listen to the delete event of the image, this is necessary because the operation is then * invalid*/ itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &DiffSliceOperation::OnImageDeleted); // get the id of the observer, used to remove it later on m_DeleteObserverTag = imageVolume->AddObserver(itk::DeleteEvent(), command); m_ImageIsValid = true; } else m_ImageIsValid = false; } mitk::DiffSliceOperation::~DiffSliceOperation() { m_WorldGeometry = nullptr; - m_zlibSliceContainer = nullptr; if (m_ImageIsValid) { // if the image is still there, we have to remove the observer from it m_Image->RemoveObserver(m_DeleteObserverTag); } m_Image = nullptr; } mitk::Image::Pointer mitk::DiffSliceOperation::GetSlice() { - Image::Pointer image = m_zlibSliceContainer->GetImage(); - return image; + return m_CompressedImageContainer.DecompressImage(); } bool mitk::DiffSliceOperation::IsValid() { - return m_ImageIsValid && m_zlibSliceContainer.IsNotNull() && (m_WorldGeometry.IsNotNull()); // TODO improve + return m_ImageIsValid && m_WorldGeometry.IsNotNull(); // TODO improve } void mitk::DiffSliceOperation::OnImageDeleted() { // if our imageVolume is removed e.g. from the datastorage the operation is no lnger valid m_ImageIsValid = false; } diff --git a/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.h b/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.h index 9647976b40..2909af129e 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.h +++ b/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.h @@ -1,101 +1,101 @@ /*============================================================================ 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 mitkDiffSliceOperation_h_Included #define mitkDiffSliceOperation_h_Included #include "mitkCompressedImageContainer.h" #include #include #include namespace mitk { class Image; /** \brief An Operation for applying an edited slice to the volume. \sa DiffSliceOperationApplier The information for the operation is specified by properties: imageVolume the volume where the slice was extracted from. slice the slice to be applied. timestep the timestep in an 4D image. currentWorldGeometry specifies the axis where the slice has to be applied in the volume. This Operation can be used to realize undo-redo functionality for e.g. segmentation purposes. */ class MITKSEGMENTATION_EXPORT DiffSliceOperation : public Operation { public: mitkClassMacro(DiffSliceOperation, OperationActor); // itkFactorylessNewMacro(Self) // itkCloneMacro(Self) // mitkNewMacro4Param(DiffSliceOperation,mitk::Image,mitk::Image,unsigned int, mitk::PlaneGeometry); /** \brief Creates an empty instance. Note that it is not valid yet. The properties of the object have to be set. */ DiffSliceOperation(); /** \brief */ DiffSliceOperation(mitk::Image *imageVolume, const mitk::Image *slice, const SlicedGeometry3D *sliceGeometry, const TimeStepType timestep, const BaseGeometry *currentWorldGeometry); /** \brief Check if it is a valid operation.*/ bool IsValid(); /** \brief Get th image volume.*/ mitk::Image *GetImage() { return this->m_Image; } const mitk::Image* GetImage() const { return this->m_Image; } /** \brief Get the slice that is applied in the operation.*/ Image::Pointer GetSlice(); /** \brief Set timeStep*/ TimeStepType GetTimeStep() const { return this->m_TimeStep; } /** \brief Get the axis where the slice has to be applied in the volume.*/ const SlicedGeometry3D *GetSliceGeometry() const { return this->m_SliceGeometry; } /** \brief Get the axis where the slice has to be applied in the volume.*/ const BaseGeometry *GetWorldGeometry() const { return this->m_WorldGeometry; } protected: ~DiffSliceOperation() override; /** \brief Callback for image observer.*/ void OnImageDeleted(); - CompressedImageContainer::Pointer m_zlibSliceContainer; + CompressedImageContainer m_CompressedImageContainer; mitk::Image *m_Image; vtkSmartPointer m_Slice; SlicedGeometry3D::ConstPointer m_SliceGeometry; TimeStepType m_TimeStep; BaseGeometry::ConstPointer m_WorldGeometry; bool m_ImageIsValid; unsigned long m_DeleteObserverTag; mitk::BaseGeometry::ConstPointer m_GuardReferenceGeometry; }; } #endif diff --git a/Modules/Segmentation/Algorithms/mitkOverwriteDirectedPlaneImageFilter.cpp b/Modules/Segmentation/Algorithms/mitkOverwriteDirectedPlaneImageFilter.cpp deleted file mode 100644 index a732bc5414..0000000000 --- a/Modules/Segmentation/Algorithms/mitkOverwriteDirectedPlaneImageFilter.cpp +++ /dev/null @@ -1,511 +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 "mitkOverwriteDirectedPlaneImageFilter.h" -#include "mitkApplyDiffImageOperation.h" -#include "mitkDiffImageApplier.h" -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkImageTimeSelector.h" -#include "mitkInteractionConst.h" -#include "mitkOperationEvent.h" -#include "mitkSegmentationInterpolationController.h" -#include "mitkUndoController.h" - -#include -#include - -mitk::OverwriteDirectedPlaneImageFilter::OverwriteDirectedPlaneImageFilter() - : m_PlaneGeometry(nullptr), - m_ImageGeometry3D(nullptr), - m_TimeStep(0), - m_Dimension0(0), - m_Dimension1(1), - m_CreateUndoInformation(false) -{ - MITK_WARN << "Class is deprecated! Use mitkVtkImageOverwrite instead."; -} - -mitk::OverwriteDirectedPlaneImageFilter::~OverwriteDirectedPlaneImageFilter() -{ -} - -void mitk::OverwriteDirectedPlaneImageFilter::GenerateData() -{ - // - // this is the place to implement the major part of undo functionality (bug #491) - // here we have to create undo/do operations - // - // WHO is the operation actor? This object may not be destroyed ever (design of undo stack)! - // -> some singleton method of this filter? - // - // neccessary additional objects: - // - something that executes the operations - // - the operation class (must hold a binary diff or something) - // - observer commands to know when the image is deleted (no further action then, perhaps even remove the operations - // from the undo stack) - // - // Image::ConstPointer input = ImageToImageFilter::GetInput(0); - Image::Pointer input3D = ImageToImageFilter::GetInput(0); - - // Image::ConstPointer slice = m_SliceImage; - - if (input3D.IsNull() || m_SliceImage.IsNull()) - return; - - if (input3D->GetDimension() == 4) - { - ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(input3D); - timeSelector->SetTimeNr(m_TimeStep); - timeSelector->UpdateLargestPossibleRegion(); - input3D = timeSelector->GetOutput(); - } - - m_ImageGeometry3D = input3D->GetGeometry(); - /* - if ( m_SliceDifferenceImage.IsNull() || - m_SliceDifferenceImage->GetDimension(0) != m_SliceImage->GetDimension(0) || - m_SliceDifferenceImage->GetDimension(1) != m_SliceImage->GetDimension(1) ) - { - m_SliceDifferenceImage = mitk::Image::New(); - mitk::PixelType pixelType( typeid(short signed int) ); - m_SliceDifferenceImage->Initialize( pixelType, 2, m_SliceImage->GetDimensions() ); - } - */ - // MITK_INFO << "Overwriting slice " << m_SliceIndex << " in dimension " << m_SliceDimension << " at time step " << - // m_TimeStep << std::endl; - // this will do a long long if/else to find out both pixel types - /*AccessFixedDimensionByItk( input3D, ItkImageSwitch, 3 );*/ - AccessFixedDimensionByItk(input3D, ItkSliceOverwriting, 3); - - // SegmentationInterpolationController* interpolator = SegmentationInterpolationController::InterpolatorForImage( - // input3D ); - // if (interpolator) - //{ - // interpolator->BlockModified(true); - // //interpolator->SetChangedSlice( m_SliceDifferenceImage, m_SliceDimension, m_SliceIndex, m_TimeStep ); - //} - /* - if ( m_CreateUndoInformation ) - { - // create do/undo operations (we don't execute the doOp here, because it has already been executed during calculation - of the diff image - ApplyDiffImageOperation* doOp = new ApplyDiffImageOperation( OpTEST, const_cast(input.GetPointer()), - m_SliceDifferenceImage, m_TimeStep, m_SliceDimension, m_SliceIndex ); - ApplyDiffImageOperation* undoOp = new ApplyDiffImageOperation( OpTEST, const_cast(input.GetPointer()), - m_SliceDifferenceImage, m_TimeStep, m_SliceDimension, m_SliceIndex ); - undoOp->SetFactor( -1.0 ); - OperationEvent* undoStackItem = new OperationEvent( DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, - this->EventDescription(m_SliceDimension, m_SliceIndex, m_TimeStep) ); - UndoController::GetCurrentUndoModel()->SetOperationEvent( undoStackItem ); - } - */ - // this image is modified (good to know for the renderer) - input3D->Modified(); - - /*if (interpolator) - { - interpolator->BlockModified(false); - }*/ -} - -template -void mitk::OverwriteDirectedPlaneImageFilter::ItkSliceOverwriting(itk::Image *input3D) -{ - typedef itk::Image SliceImageType; - typedef itk::ImageRegionConstIterator SliceIteratorType; - - typename SliceImageType::Pointer sliceImage = SliceImageType::New(); - CastToItkImage(m_SliceImage, sliceImage); - - SliceIteratorType sliceIterator(sliceImage, sliceImage->GetLargestPossibleRegion()); - - sliceIterator.GoToBegin(); - - Point3D currentPointIn2D; - Point3D worldPointIn3D; - - // Here we just iterate over the slice which must be written into the 3D volumen and set the corresponding pixel in - // our 3D volume - while (!sliceIterator.IsAtEnd()) - { - currentPointIn2D[0] = sliceIterator.GetIndex()[0] + 0.5; - currentPointIn2D[1] = sliceIterator.GetIndex()[1] + 0.5; - currentPointIn2D[2] = 0; - - m_PlaneGeometry->IndexToWorld(currentPointIn2D, worldPointIn3D); - - typename itk::Image::IndexType outputIndex; - m_ImageGeometry3D->WorldToIndex(worldPointIn3D, outputIndex); - - // Only access ITK image if it's inside - if (m_ImageGeometry3D->IsIndexInside(outputIndex)) - { - input3D->SetPixel(outputIndex, (TPixel)sliceIterator.Get()); - } - - ++sliceIterator; - } -} - -/****TEST***/ -// Maybe a bit more efficient but doesn`t already work. See also ExtractCirectedPlaneImageFilter - -// typename itk::Image::IndexType outputIndex; - -// if ( columns == extent[0] ) -//{ -////If we are at the end of a row, then we have to go to the beginning of the next row -// currentImagePointIn3D = origin; -// currentImagePointIn3D += spacing[1]*bottom*currentPointIn2D[1]; -// columns = 0; -// m_ImageGeometry3D->WorldToIndex(currentImagePointIn3D, outputIndex); -//} -// else -//{ -// if ( columns != 0 ) -//{ -// currentImagePointIn3D += spacing[0]*right; -//} -// m_ImageGeometry3D->WorldToIndex(currentImagePointIn3D, outputIndex); -//} - -// if ( m_ImageGeometry3D->IsIndexInside( outputIndex )) -//{ -// outputImage->SetPixel( outputIndex, (TPixel2)inputIterator.Get() ); -//} -// else if (currentImagePointIn3D == origin) -//{ -// Point3D temp; -// temp[0] = bottom[0]*spacing[0]*0.5; -// temp[1] = bottom[1]*spacing[1]*0.5; -// temp[2] = bottom[2]*spacing[2]*0.5; -// origin[0] += temp[0]; -// origin[1] += temp[1]; -// origin[2] += temp[2]; -// currentImagePointIn3D = origin; -// m_ImageGeometry3D->WorldToIndex(currentImagePointIn3D, outputIndex); -// if ( m_ImageGeometry3D->IsIndexInside( outputIndex )) -//{ -// outputImage->SetPixel( outputIndex, (TPixel2)inputIterator.Get() ); -//} -//} -// columns++; - -/****TEST ENDE****/ - -//* -// // Offset the world coordinate by one pixel to compensate for -// // index/world origin differences. -// Point3D offsetIndex; -// offsetIndex.Fill( 1 ); -// Point3D offsetWorld; -// offsetWorld.Fill( 0 ); -// m_PlaneGeometry->IndexToWorld( offsetIndex, offsetWorld ); -// // remove origin shift -// const Point3D origin = m_PlaneGeometry->GetOrigin(); -// offsetWorld[0] -= origin[0]; -// offsetWorld[1] -= origin[1]; -// offsetWorld[2] -= origin[2]; -// // offset world coordinate -// worldPointIn3D[ 0 ] += offsetWorld[0]; -// worldPointIn3D[ 1 ] += offsetWorld[1]; -// worldPointIn3D[ 2 ] += offsetWorld[2]; -//*/ - -// basically copied from mitk/Core/Algorithms/mitkImageAccessByItk.h -/*#define myMITKOverwriteDirectedPlaneImageFilterAccessByItk(mitkImage, itkImageTypeFunction, pixeltype, dimension, -itkimage2) \ -// if ( typeId == typeid(pixeltype) ) \ -//{ \ -// typedef itk::Image ImageType; \ -// typedef mitk::ImageToItk ImageToItkType; \ -// itk::SmartPointer imagetoitk = ImageToItkType::New(); \ -// imagetoitk->SetInput(mitkImage); \ -// imagetoitk->Update(); \ -// itkImageTypeFunction(imagetoitk->GetOutput(), itkimage2); \ -//} -// -//#define myMITKOverwriteDirectedPlaneImageFilterAccessAllTypesByItk(mitkImage, itkImageTypeFunction, dimension, -itkimage2) \ -//{ \ -// myMITKOverwriteDirectedPlaneImageFilterAccessByItk(mitkImage, itkImageTypeFunction, double, dimension, -itkimage2) else \ -// myMITKOverwriteDirectedPlaneImageFilterAccessByItk(mitkImage, itkImageTypeFunction, float, dimension, -itkimage2) else \ -// myMITKOverwriteDirectedPlaneImageFilterAccessByItk(mitkImage, itkImageTypeFunction, int, dimension, -itkimage2) else \ -// myMITKOverwriteDirectedPlaneImageFilterAccessByItk(mitkImage, itkImageTypeFunction, unsigned int, dimension, -itkimage2) else \ -// myMITKOverwriteDirectedPlaneImageFilterAccessByItk(mitkImage, itkImageTypeFunction, short, dimension, -itkimage2) else \ -// myMITKOverwriteDirectedPlaneImageFilterAccessByItk(mitkImage, itkImageTypeFunction, unsigned short, dimension, -itkimage2) else \ -// myMITKOverwriteDirectedPlaneImageFilterAccessByItk(mitkImage, itkImageTypeFunction, char, dimension, -itkimage2) else \ -// myMITKOverwriteDirectedPlaneImageFilterAccessByItk(mitkImage, itkImageTypeFunction, unsigned char, dimension, -itkimage2) \ -//}*/ -// -// -// template -// void mitk::OverwriteDirectedPlaneImageFilter::ItkImageSwitch( itk::Image* itkImage ) -//{ -// const std::type_info& typeId=*(m_SliceImage->GetPixelType().GetTypeId()); -// -// myMITKOverwriteDirectedPlaneImageFilterAccessAllTypesByItk( m_SliceImage, ItkImageProcessing, 2, itkImage ); -//} - -// template -// void mitk::OverwriteDirectedPlaneImageFilter::ItkImageProcessing( itk::Image* inputImage, -// itk::Image* outputImage ) -//{ -// typedef itk::Image SliceImageType; -// typedef itk::Image DiffImageType; -// typedef itk::Image VolumeImageType; -// -// typedef itk::ImageSliceIteratorWithIndex< VolumeImageType > OutputSliceIteratorType; -// typedef itk::ImageRegionConstIterator< SliceImageType > InputSliceIteratorType; -// //typedef itk::ImageRegionIterator< DiffImageType > DiffSliceIteratorType; -// -// InputSliceIteratorType inputIterator( inputImage, inputImage->GetLargestPossibleRegion() ); -// -// //typename DiffImageType::Pointer diffImage; -// //CastToItkImage( m_SliceDifferenceImage, diffImage ); -// //DiffSliceIteratorType diffIterator( diffImage, diffImage->GetLargestPossibleRegion() ); -// -// inputIterator.GoToBegin(); -// //diffIterator.GoToBegin(); -// -// //TEST -// Point3D origin = m_PlaneGeometry->GetOrigin(); -// Vector3D right = m_PlaneGeometry->GetAxisVector(0); -// Vector3D bottom = m_PlaneGeometry->GetAxisVector(1); -// right.Normalize(); -// bottom.Normalize(); -// -// Vector2D spacing = inputImage->GetSpacing(); -// -// Vector2D extentInMM; -// extentInMM[0] = m_PlaneGeometry->GetExtentInMM(0); -// extentInMM[1] = m_PlaneGeometry->GetExtentInMM(1); -// -// Vector2D extent; -// extent[0] = m_PlaneGeometry->GetExtent(0); -// extent[1] = m_PlaneGeometry->GetExtent(1); -// //TEST ENDE -// -// Point3D currentPointIn2D, worldPointIn3D; -// TPixel2 outputPixel = 0; -// -// int debugCounter( 0 ); -// -// std::ofstream geometryFile; -// geometryFile.precision(30); -// geometryFile.open("C:/Users/fetzer/Desktop/TEST/geometryFileOv.txt"); -// -// geometryFile<<"Offset: [ "<GetIndexToWorldTransform()->GetOffset()[0]<<", -// "<GetIndexToWorldTransform()->GetOffset()[1]<<", -// "<GetIndexToWorldTransform()->GetOffset()[2]<<" ]"<GetIndexToWorldTransform()->GetMatrix()<IndexToWorld( currentPointIn2D, worldPointIn3D ); -// -// typename itk::Image::IndexType outputIndex; -// m_ImageGeometry3D->WorldToIndex( worldPointIn3D, outputIndex ); -// -// // Only access ITK image if it's inside -// if ( m_ImageGeometry3D->IsIndexInside( outputIndex ) ) -// { -// //outputPixel = outputImage->GetPixel( outputIndex ); -// outputImage->SetPixel( outputIndex, (TPixel2)inputIterator.Get() ); -// /*if( inputIterator.Get() == mitk::paint::addPixelValue ) -// { -// outputImage->SetPixel( outputIndex, (TPixel2)( 1 ) ); -// } -// else if( inputIterator.Get() == mitk::paint::subPixelValue ) -// { -// outputImage->SetPixel( outputIndex, (TPixel2)( 0 ) ); -// }*/ -// } -// -///****TEST***/ -// -////typename itk::Image::IndexType outputIndex; -// -////if ( columns == extent[0] ) -////{ -//////If we are at the end of a row, then we have to go to the beginning of the next row -////currentImagePointIn3D = origin; -////currentImagePointIn3D += spacing[1]*bottom*currentPointIn2D[1]; -// columns = 0; -////m_ImageGeometry3D->WorldToIndex(currentImagePointIn3D, outputIndex); -////} -////else -////{ -////if ( columns != 0 ) -////{ -////currentImagePointIn3D += spacing[0]*right; -////} -////m_ImageGeometry3D->WorldToIndex(currentImagePointIn3D, outputIndex); -////} -// -////if ( m_ImageGeometry3D->IsIndexInside( outputIndex )) -////{ -////outputImage->SetPixel( outputIndex, (TPixel2)inputIterator.Get() ); -////} -////else if (currentImagePointIn3D == origin) -////{ -////Point3D temp; -////temp[0] = bottom[0]*spacing[0]*0.5; -////temp[1] = bottom[1]*spacing[1]*0.5; -////temp[2] = bottom[2]*spacing[2]*0.5; -////origin[0] += temp[0]; -////origin[1] += temp[1]; -////origin[2] += temp[2]; -////currentImagePointIn3D = origin; -////m_ImageGeometry3D->WorldToIndex(currentImagePointIn3D, outputIndex); -////if ( m_ImageGeometry3D->IsIndexInside( outputIndex )) -////{ -////outputImage->SetPixel( outputIndex, (TPixel2)inputIterator.Get() ); -////} -////} -////columns++; -// -///****TEST ENDE****/ -// -////* -//// // Offset the world coordinate by one pixel to compensate for -//// // index/world origin differences. -//// Point3D offsetIndex; -//// offsetIndex.Fill( 1 ); -//// Point3D offsetWorld; -//// offsetWorld.Fill( 0 ); -//// m_PlaneGeometry->IndexToWorld( offsetIndex, offsetWorld ); -//// // remove origin shift -//// const Point3D origin = m_PlaneGeometry->GetOrigin(); -//// offsetWorld[0] -= origin[0]; -//// offsetWorld[1] -= origin[1]; -//// offsetWorld[2] -= origin[2]; -//// // offset world coordinate -//// worldPointIn3D[ 0 ] += offsetWorld[0]; -//// worldPointIn3D[ 1 ] += offsetWorld[1]; -//// worldPointIn3D[ 2 ] += offsetWorld[2]; -////*/ -// // Output index -// -// -////For the purpose of debug -////if( debugCounter%100 == 0) -//////{ -////Point3D contIndex; -////m_ImageGeometry3D->WorldToIndex(worldPointIn3D,contIndex); -////overriderFile.precision(10); -////overriderFile<<"2D-Index: [ "<(inputIterator.Get() - outputPixel ) ); // oh oh, not good for -// bigger values -// ++inputIterator; -////++debugCounter; -// //++diffIterator; -// } -// /*overriderFile.close(); -// overriderFileIndex.close();*/ -// geometryFile.close(); -// -///* -// typename VolumeImageType::RegionType sliceInVolumeRegion; -// -// sliceInVolumeRegion = outputImage->GetLargestPossibleRegion(); -// sliceInVolumeRegion.SetSize( m_SliceDimension, 1 ); // just one slice -// sliceInVolumeRegion.SetIndex( m_SliceDimension, m_SliceIndex ); // exactly this slice, please -// -// OutputSliceIteratorType outputIterator( outputImage, sliceInVolumeRegion ); -// outputIterator.SetFirstDirection(m_Dimension0); -// outputIterator.SetSecondDirection(m_Dimension1); -// -// // iterate over output slice (and over input slice simultaneously) -// outputIterator.GoToBegin(); -// while ( !outputIterator.IsAtEnd() ) -// { -// while ( !outputIterator.IsAtEndOfSlice() ) -// { -// while ( !outputIterator.IsAtEndOfLine() ) -// { -// diffIterator.Set( static_cast(inputIterator.Get() - outputIterator.Get()) ); // oh oh, not -// good for bigger values -// outputIterator.Set( (TPixel2) inputIterator.Get() ); -// ++outputIterator; -// ++inputIterator; -// ++diffIterator; -// } -// outputIterator.NextLine(); -// } -// outputIterator.NextSlice(); -// } -// */ -//} -/* -std::string mitk::OverwriteDirectedPlaneImageFilter::EventDescription( unsigned int sliceDimension, unsigned int -sliceIndex, unsigned int timeStep ) -{ -std::stringstream s; - -s << "Changed slice ("; - -switch (sliceDimension) -{ -default: -case 2: -s << "T"; -break; -case 1: -s << "C"; -break; -case 0: -s << "S"; -break; -} - -s << " " << sliceIndex << " " << timeStep << ")"; - -return s.str(); -} -*/ diff --git a/Modules/Segmentation/Algorithms/mitkOverwriteDirectedPlaneImageFilter.h b/Modules/Segmentation/Algorithms/mitkOverwriteDirectedPlaneImageFilter.h deleted file mode 100644 index c87c74a6dd..0000000000 --- a/Modules/Segmentation/Algorithms/mitkOverwriteDirectedPlaneImageFilter.h +++ /dev/null @@ -1,117 +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 mitkOverwriteDirectedPlaneImageFilter_h_Included -#define mitkOverwriteDirectedPlaneImageFilter_h_Included - -#include "mitkCommon.h" -#include "mitkImageToImageFilter.h" -#include - -#include - -namespace mitk -{ - /** - \deprecated This class is deprecated. Use mitkVtkImageOverwrite instead. - \sa mitkVtkImageOverwrite - - \brief Writes a 2D slice into a 3D image. - - \sa SegTool2D - \sa ContourTool - \sa ExtractImageFilter - - \ingroup Process - \ingroup Reliver - - This class takes a 3D mitk::Image as input and tries to replace one slice in it with the second input image, which - is specified - by calling SetSliceImage with a 2D mitk::Image. - - Two parameters determine which slice is replaced: the "slice dimension" is that one, which is constant for all - points in the plane, e.g. axial would mean 2. - The "slice index" is the slice index in the image direction you specified with "affected dimension". Indices count - from zero. - - This class works with all kind of image types, the only restrictions being that the input is 3D, and the slice image - is 2D. - - If requested by SetCreateUndoInformation(true), this class will create instances of ApplyDiffImageOperation for the - undo stack. - These operations will (on user request) be executed by DiffImageApplier to perform undo. - - Last contributor: $Author: maleike $ - */ - class MITKSEGMENTATION_EXPORT OverwriteDirectedPlaneImageFilter : public ImageToImageFilter - { - public: - mitkClassMacro(OverwriteDirectedPlaneImageFilter, ImageToImageFilter); - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); - - /** - \brief Which plane to overwrite - */ - const BaseGeometry *GetPlaneGeometry3D() const - { - return m_PlaneGeometry; - } - void SetPlaneGeometry3D(const BaseGeometry *geometry) { m_PlaneGeometry = geometry; } - /** - \brief Time step of the slice to overwrite - */ - itkSetMacro(TimeStep, unsigned int); - itkGetConstMacro(TimeStep, unsigned int); - - /** - \brief Whether to create undo operation in the MITK undo stack - */ - itkSetMacro(CreateUndoInformation, bool); - itkGetConstMacro(CreateUndoInformation, bool); - - itkSetObjectMacro(SliceImage, Image); - const Image *GetSliceImage() { return m_SliceImage.GetPointer(); } - const Image *GetLastDifferenceImage() { return m_SliceDifferenceImage.GetPointer(); } - protected: - OverwriteDirectedPlaneImageFilter(); // purposely hidden - ~OverwriteDirectedPlaneImageFilter() override; - - void GenerateData() override; - - template - void ItkSliceOverwriting(itk::Image *input3D); - - template - void ItkImageSwitch(itk::Image *image); - - template - void ItkImageProcessing(itk::Image *itkImage1, - itk::Image *itkImage2); - - // std::string EventDescription( unsigned int sliceDimension, unsigned int sliceIndex, unsigned int timeStep ); - - Image::ConstPointer m_SliceImage; - Image::Pointer m_SliceDifferenceImage; - - const BaseGeometry *m_PlaneGeometry; - const BaseGeometry *m_ImageGeometry3D; - unsigned int m_TimeStep; - unsigned int m_Dimension0; - unsigned int m_Dimension1; - - bool m_CreateUndoInformation; - }; - -} // namespace - -#endif diff --git a/Modules/Segmentation/Algorithms/mitkOverwriteSliceImageFilter.cpp b/Modules/Segmentation/Algorithms/mitkOverwriteSliceImageFilter.cpp deleted file mode 100644 index 53fb98bbb2..0000000000 --- a/Modules/Segmentation/Algorithms/mitkOverwriteSliceImageFilter.cpp +++ /dev/null @@ -1,284 +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 "mitkOverwriteSliceImageFilter.h" -#include "mitkApplyDiffImageOperation.h" -#include "mitkDiffImageApplier.h" -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkImageTimeSelector.h" -#include "mitkInteractionConst.h" -#include "mitkOperationEvent.h" -#include "mitkSegmentationInterpolationController.h" -#include "mitkUndoController.h" - -#include -#include - -mitk::OverwriteSliceImageFilter::OverwriteSliceImageFilter() - : m_SliceIndex(0), - m_SliceDimension(0), - m_TimeStep(0), - m_Dimension0(0), - m_Dimension1(1), - m_CreateUndoInformation(false) -{ - MITK_WARN << "Class is deprecated! Use mitkVtkImageOverwrite instead."; -} - -mitk::OverwriteSliceImageFilter::~OverwriteSliceImageFilter() -{ -} - -void mitk::OverwriteSliceImageFilter::GenerateData() -{ - // - // this is the place to implement the major part of undo functionality (bug #491) - // here we have to create undo/do operations - // - // WHO is the operation actor? This object may not be destroyed ever (design of undo stack)! - // -> some singleton method of this filter? - // - // neccessary additional objects: - // - something that executes the operations - // - the operation class (must hold a binary diff or something) - // - observer commands to know when the image is deleted (no further action then, perhaps even remove the operations - // from the undo stack) - // - Image::ConstPointer input = ImageToImageFilter::GetInput(0); - Image::Pointer input3D = ImageToImageFilter::GetInput(0); - - Image::ConstPointer slice = m_SliceImage; - - if (input.IsNull() || slice.IsNull()) - return; - - switch (m_SliceDimension) - { - default: - case 2: - m_Dimension0 = 0; - m_Dimension1 = 1; - break; - case 1: - m_Dimension0 = 0; - m_Dimension1 = 2; - break; - case 0: - m_Dimension0 = 1; - m_Dimension1 = 2; - break; - } - - if (slice->GetDimension() < 2 || input->GetDimension() > 4 || - slice->GetDimension(0) != input->GetDimension(m_Dimension0) || - slice->GetDimension(1) != input->GetDimension(m_Dimension1) || - m_SliceIndex >= input->GetDimension(m_SliceDimension)) - { - itkExceptionMacro("Slice and image dimensions differ or slice index is too large. Sorry, cannot work like this."); - return; - } - - if (input->GetDimension() == 4) - { - ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(input); - timeSelector->SetTimeNr(m_TimeStep); - timeSelector->UpdateLargestPossibleRegion(); - input3D = timeSelector->GetOutput(); - } - - if (m_SliceDifferenceImage.IsNull() || m_SliceDifferenceImage->GetDimension(0) != m_SliceImage->GetDimension(0) || - m_SliceDifferenceImage->GetDimension(1) != m_SliceImage->GetDimension(1)) - { - m_SliceDifferenceImage = mitk::Image::New(); - mitk::PixelType pixelType(mitk::MakeScalarPixelType()); - m_SliceDifferenceImage->Initialize(pixelType, 2, m_SliceImage->GetDimensions()); - } - - // MITK_INFO << "Overwriting slice " << m_SliceIndex << " in dimension " << m_SliceDimension << " at time step " << - // m_TimeStep << std::endl; - // this will do a long long if/else to find out both pixel types - AccessFixedDimensionByItk(input3D, ItkImageSwitch, 3); - - SegmentationInterpolationController *interpolator = SegmentationInterpolationController::InterpolatorForImage(input); - if (interpolator) - { - interpolator->BlockModified(true); - interpolator->SetChangedSlice(m_SliceDifferenceImage, m_SliceDimension, m_SliceIndex, m_TimeStep); - } - - if (m_CreateUndoInformation) - { - // create do/undo operations (we don't execute the doOp here, because it has already been executed during - // calculation of the diff image - auto doOp = new ApplyDiffImageOperation(OpTEST, - const_cast(input.GetPointer()), - m_SliceDifferenceImage, - m_TimeStep, - m_SliceDimension, - m_SliceIndex); - auto undoOp = new ApplyDiffImageOperation(OpTEST, - const_cast(input.GetPointer()), - m_SliceDifferenceImage, - m_TimeStep, - m_SliceDimension, - m_SliceIndex); - undoOp->SetFactor(-1.0); - OperationEvent *undoStackItem = - new OperationEvent(DiffImageApplier::GetInstanceForUndo(), - doOp, - undoOp, - this->EventDescription(m_SliceDimension, m_SliceIndex, m_TimeStep)); - OperationEvent::IncCurrObjectEventId(); - UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); - } - - // this image is modified (good to know for the renderer) - input->Modified(); - - if (interpolator) - { - interpolator->BlockModified(false); - } -} - -// basically copied from mitk/Core/Algorithms/mitkImageAccessByItk.h -#define myMITKOverwriteSliceImageFilterAccessByItk(mitkImage, itkImageTypeFunction, pixeltype, dimension, itkimage2) \ - if (typeId == MapPixelComponentType::value) \ - \ -{ \ - typedef itk::Image ImageType; \ - typedef mitk::ImageToItk ImageToItkType; \ - itk::SmartPointer imagetoitk = ImageToItkType::New(); \ - const mitk::Image *constImage = mitkImage; \ - mitk::Image *nonConstImage = const_cast(constImage); \ - nonConstImage->Update(); \ - imagetoitk->SetInput(nonConstImage); \ - imagetoitk->Update(); \ - itkImageTypeFunction(imagetoitk->GetOutput(), itkimage2); \ - \ -} - -#define myMITKOverwriteSliceImageFilterAccessAllTypesByItk(mitkImage, itkImageTypeFunction, dimension, itkimage2) \ - \ -{ \ - myMITKOverwriteSliceImageFilterAccessByItk( \ - mitkImage, \ - itkImageTypeFunction, \ - double, \ - dimension, \ - itkimage2) else myMITKOverwriteSliceImageFilterAccessByItk(mitkImage, \ - itkImageTypeFunction, \ - float, \ - dimension, \ - itkimage2) else myMITKOverwriteSliceImageFilterAccessByItk(mitkImage, itkImageTypeFunction, int, dimension, itkimage2) else myMITKOverwriteSliceImageFilterAccessByItk(mitkImage, itkImageTypeFunction, unsigned int, dimension, itkimage2) else myMITKOverwriteSliceImageFilterAccessByItk(mitkImage, \ - itkImageTypeFunction, \ - short, \ - dimension, \ - itkimage2) else myMITKOverwriteSliceImageFilterAccessByItk(mitkImage, itkImageTypeFunction, unsigned short, dimension, itkimage2) else myMITKOverwriteSliceImageFilterAccessByItk(mitkImage, \ - itkImageTypeFunction, \ - char, \ - dimension, \ - itkimage2) else myMITKOverwriteSliceImageFilterAccessByItk(mitkImage, \ - itkImageTypeFunction, \ - unsigned char, \ - dimension, \ - itkimage2) \ - \ -} - -template -void mitk::OverwriteSliceImageFilter::ItkImageSwitch(itk::Image *itkImage) -{ - const int typeId = m_SliceImage->GetPixelType().GetComponentType(); - - myMITKOverwriteSliceImageFilterAccessAllTypesByItk(m_SliceImage, ItkImageProcessing, 2, itkImage); -} - -template -void mitk::OverwriteSliceImageFilter::ItkImageProcessing(const itk::Image *inputImage, - itk::Image *outputImage) -{ - typedef itk::Image SliceImageType; - typedef itk::Image DiffImageType; - typedef itk::Image VolumeImageType; - - typedef itk::ImageSliceIteratorWithIndex OutputSliceIteratorType; - typedef itk::ImageRegionConstIterator InputSliceIteratorType; - typedef itk::ImageRegionIterator DiffSliceIteratorType; - - typename VolumeImageType::RegionType sliceInVolumeRegion; - - sliceInVolumeRegion = outputImage->GetLargestPossibleRegion(); - sliceInVolumeRegion.SetSize(m_SliceDimension, 1); // just one slice - sliceInVolumeRegion.SetIndex(m_SliceDimension, m_SliceIndex); // exactly this slice, please - - OutputSliceIteratorType outputIterator(outputImage, sliceInVolumeRegion); - outputIterator.SetFirstDirection(m_Dimension0); - outputIterator.SetSecondDirection(m_Dimension1); - - InputSliceIteratorType inputIterator(inputImage, inputImage->GetLargestPossibleRegion()); - - typename DiffImageType::Pointer diffImage; - CastToItkImage(m_SliceDifferenceImage, diffImage); - DiffSliceIteratorType diffIterator(diffImage, diffImage->GetLargestPossibleRegion()); - - // iterate over output slice (and over input slice simultaneously) - outputIterator.GoToBegin(); - inputIterator.GoToBegin(); - diffIterator.GoToBegin(); - while (!outputIterator.IsAtEnd()) - { - while (!outputIterator.IsAtEndOfSlice()) - { - while (!outputIterator.IsAtEndOfLine()) - { - diffIterator.Set(static_cast(inputIterator.Get() - - outputIterator.Get())); // oh oh, not good for bigger values - outputIterator.Set((TPixel2)inputIterator.Get()); - ++outputIterator; - ++inputIterator; - ++diffIterator; - } - outputIterator.NextLine(); - } - outputIterator.NextSlice(); - } -} - -std::string mitk::OverwriteSliceImageFilter::EventDescription(unsigned int sliceDimension, - unsigned int sliceIndex, - unsigned int timeStep) -{ - std::stringstream s; - - s << "Changed slice ("; - - switch (sliceDimension) - { - default: - case 2: - s << "T"; - break; - case 1: - s << "C"; - break; - case 0: - s << "S"; - break; - } - - s << " " << sliceIndex << " " << timeStep << ")"; - - return s.str(); -} diff --git a/Modules/Segmentation/Algorithms/mitkOverwriteSliceImageFilter.h b/Modules/Segmentation/Algorithms/mitkOverwriteSliceImageFilter.h deleted file mode 100644 index 97db8915ef..0000000000 --- a/Modules/Segmentation/Algorithms/mitkOverwriteSliceImageFilter.h +++ /dev/null @@ -1,121 +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 mitkOverwriteSliceImageFilter_h_Included -#define mitkOverwriteSliceImageFilter_h_Included - -#include "mitkCommon.h" -#include "mitkImageToImageFilter.h" -#include - -#include - -namespace mitk -{ - /** - \deprecated This class is deprecated. Use mitkVtkImageOverwrite instead. - \sa mitkVtkImageOverwrite - - \brief Writes a 2D slice into a 3D image. - - \sa SegTool2D - \sa ContourTool - \sa ExtractImageFilter - - \ingroup Process - \ingroup ToolManagerEtAl - - This class takes a 3D mitk::Image as input and tries to replace one slice in it with the second input image, which - is specified - by calling SetSliceImage with a 2D mitk::Image. - - Two parameters determine which slice is replaced: the "slice dimension" is that one, which is constant for all - points in the plane, e.g. axial would mean 2. - The "slice index" is the slice index in the image direction you specified with "affected dimension". Indices count - from zero. - - This class works with all kind of image types, the only restrictions being that the input is 3D, and the slice image - is 2D. - - If requested by SetCreateUndoInformation(true), this class will create instances of ApplyDiffImageOperation for the - undo stack. - These operations will (on user request) be executed by DiffImageApplier to perform undo. - - Last contributor: $Author$ - */ - class MITKSEGMENTATION_EXPORT OverwriteSliceImageFilter : public ImageToImageFilter - { - public: - mitkClassMacro(OverwriteSliceImageFilter, ImageToImageFilter); - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); - - /** - \brief Which slice to overwrite (first one has index 0). - */ - itkSetMacro(SliceIndex, unsigned int); - itkGetConstMacro(SliceIndex, unsigned int); - - /** - \brief The orientation of the slice to overwrite. - - \a Parameter \a SliceDimension Number of the dimension which is constant for all pixels of the desired slices - (e.g. 0 for axial) - */ - itkSetMacro(SliceDimension, unsigned int); - itkGetConstMacro(SliceDimension, unsigned int); - - /** - \brief Time step of the slice to overwrite - */ - itkSetMacro(TimeStep, unsigned int); - itkGetConstMacro(TimeStep, unsigned int); - - /** - \brief Whether to create undo operation in the MITK undo stack - */ - itkSetMacro(CreateUndoInformation, bool); - itkGetConstMacro(CreateUndoInformation, bool); - - itkSetObjectMacro(SliceImage, Image); - const Image *GetSliceImage() { return m_SliceImage.GetPointer(); } - const Image *GetLastDifferenceImage() { return m_SliceDifferenceImage.GetPointer(); } - protected: - OverwriteSliceImageFilter(); // purposely hidden - ~OverwriteSliceImageFilter() override; - - void GenerateData() override; - - template - void ItkImageSwitch(itk::Image *image); - - template - void ItkImageProcessing(const itk::Image *itkImage1, - itk::Image *itkImage2); - - std::string EventDescription(unsigned int sliceDimension, unsigned int sliceIndex, unsigned int timeStep); - - Image::ConstPointer m_SliceImage; - Image::Pointer m_SliceDifferenceImage; - - unsigned int m_SliceIndex; - unsigned int m_SliceDimension; - unsigned int m_TimeStep; - unsigned int m_Dimension0; - unsigned int m_Dimension1; - - bool m_CreateUndoInformation; - }; - -} // namespace - -#endif diff --git a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp index c016754e06..3fe619bf30 100644 --- a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp +++ b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.cpp @@ -1,602 +1,626 @@ /*============================================================================ 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 #include #include #include +namespace +{ + // itk::Object provides a const version of AddObserver() (which uses const_cast internally) + // but not a const version of RemoveObserver(). + void RemoveObserverFromConstObject(const itk::Object* constObject, unsigned long observerTag) + { + if (nullptr != constObject) + { + auto* object = const_cast(constObject); + object->RemoveObserver(observerTag); + } + } +} + 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_EnableSliceImageCache(false) + : m_SegmentationModifiedObserverTag(std::make_pair(0UL, 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) + if (m_SegmentationModifiedObserverTag.second) { - itkExceptionMacro("SegmentationInterpolationController needs a 3D-segmentation or 3D+t, not 2D."); + RemoveObserverFromConstObject(m_Segmentation, m_SegmentationModifiedObserverTag.first); + m_SegmentationModifiedObserverTag.second = false; } - if (m_Segmentation != segmentation) + if (nullptr == segmentation || !segmentation->IsInitialized()) { - // observe Modified() event of image - itk::ReceptorMemberCommand::Pointer command = - itk::ReceptorMemberCommand::New(); - command->SetCallbackFunction(this, &SegmentationInterpolationController::OnImageModified); - segmentation->AddObserver(itk::ModifiedEvent(), command); + m_Segmentation = nullptr; + this->InvokeEvent(itk::AbortEvent()); + return; + } + + if (segmentation->GetDimension() > 4 || segmentation->GetDimension() < 3) + { + itkExceptionMacro("SegmentationInterpolationController needs a 3D-segmentation or 3D+t."); } m_Segmentation = segmentation; + auto command = itk::ReceptorMemberCommand::New(); + command->SetCallbackFunction(this, &SegmentationInterpolationController::OnImageModified); + m_SegmentationModifiedObserverTag.first = segmentation->AddObserver(itk::ModifiedEvent(), command); + m_SegmentationModifiedObserverTag.second = true; + 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, ShapeBasedInterpolationAlgorithm::Pointer algorithm) { if (m_Segmentation.IsNull() || nullptr == currentPlane) return nullptr; if (timeStep >= m_SegmentationCountInSlice.size()) return nullptr; if (sliceDimension > 2) 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 segmentation, nothing to interopolate unsigned int lowerBound = 0; unsigned int upperBound = 0; bool bounds = false; for (lowerBound = sliceIndex - 1; ; --lowerBound) { if (m_SegmentationCountInSlice[timeStep][sliceDimension][lowerBound] > 0) { bounds = true; break; } if (0 == lowerBound) break; } if (!bounds) return nullptr; bounds = false; for (upperBound = sliceIndex + 1; upperBound <= lastSliceIndex; ++upperBound) { if (m_SegmentationCountInSlice[timeStep][sliceDimension][upperBound] > 0) { bounds = true; break; } } if (!bounds) return nullptr; // We have found two neighboring slices with segmentations and made sure that the current slice does not contain anything mitk::Image::Pointer lowerSlice; mitk::Image::Pointer upperSlice; mitk::Image::Pointer resultImage; try { // Extract current slice resultImage = this->ExtractSlice(currentPlane, sliceIndex, timeStep); // Creating PlaneGeometry for lower slice auto reslicePlane = currentPlane->Clone(); // Transforming the current origin so that it matches the lower slice 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 lower slice lowerSlice = this->ExtractSlice(reslicePlane, lowerBound, timeStep, true); 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 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 inputs: // - Two segmentations (guaranteed to be of the same data type) // - Orientation of the segmentations (sliceDimension) // - Position of the two slices (sliceIndices) // - Reference image // // 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 5ac3839fa2..3ac7f7cba2 100644 --- a/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h +++ b/Modules/Segmentation/Controllers/mitkSegmentationInterpolationController.h @@ -1,242 +1,243 @@ /*============================================================================ 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, 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; + std::pair m_SegmentationModifiedObserverTag; // first: actual tag, second: tag assigned / valid? 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/mitkContourTool.h b/Modules/Segmentation/Interactions/mitkContourTool.h index 23e7d7a79c..601aa226d0 100644 --- a/Modules/Segmentation/Interactions/mitkContourTool.h +++ b/Modules/Segmentation/Interactions/mitkContourTool.h @@ -1,71 +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 mitkContourTool_h_Included #define mitkContourTool_h_Included #include "mitkCommon.h" #include "mitkFeedbackContourTool.h" #include namespace mitk { class Image; class StateMachineAction; class InteractionEvent; /** \brief Simple contour filling tool. \sa FeedbackContourTool \sa ExtractImageFilter - \sa OverwriteSliceImageFilter \ingroup Interaction \ingroup ToolManagerEtAl Fills a visible contour (from FeedbackContourTool) during mouse dragging. When the mouse button is released, ContourTool tries to extract a slice from the working image and fill in the (filled) contour as a binary image. The painting "color" is defined by m_PaintingPixelValue, which is set in the constructor (by sub-classes) or during some event (e.g. in OnInvertLogic - when CTRL is pressed). \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT ContourTool : public FeedbackContourTool { public: mitkClassMacro(ContourTool, FeedbackContourTool); protected: ContourTool(int paintingPixelValue = 1); // purposely hidden ~ContourTool() override; void Activated() override; void Deactivated() override; virtual void OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent); virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); virtual void OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent); virtual void OnInvertLogic(StateMachineAction *, InteractionEvent *interactionEvent); void ConnectActionsAndFunctions() override; int m_PaintingPixelValue; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.h b/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.h index b86a9a3613..441d02480c 100644 --- a/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.h +++ b/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.h @@ -1,63 +1,62 @@ /*============================================================================ 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 mitkPaintContourTool_h_Included #define mitkPaintContourTool_h_Included #include "mitkPaintbrushTool.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief Paintbrush tool for InteractiveSegmentation \sa FeedbackContourTool \sa ExtractImageFilter - \sa OverwriteSliceImageFilter \ingroup Interaction \ingroup ToolManagerEtAl Simple paintbrush drawing tool. Right now there are only circular pens of varying size. This class specified only the drawing "color" for the super class PaintbrushTool. \warning Only to be instantiated by mitk::ToolManager. $Author: maleike $ */ class MITKSEGMENTATION_EXPORT DrawPaintbrushTool : public PaintbrushTool { public: mitkClassMacro(DrawPaintbrushTool, PaintbrushTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char **GetXPM() const override; us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; protected: DrawPaintbrushTool(); // purposely hidden ~DrawPaintbrushTool() override; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkErasePaintbrushTool.h b/Modules/Segmentation/Interactions/mitkErasePaintbrushTool.h index 0111688c0b..3f99fdd22c 100644 --- a/Modules/Segmentation/Interactions/mitkErasePaintbrushTool.h +++ b/Modules/Segmentation/Interactions/mitkErasePaintbrushTool.h @@ -1,63 +1,62 @@ /*============================================================================ 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 mitkErasePaintbrushTool_h_Included #define mitkErasePaintbrushTool_h_Included #include "mitkPaintbrushTool.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief Paintbrush tool for InteractiveSegmentation \sa FeedbackContourTool \sa ExtractImageFilter - \sa OverwriteSliceImageFilter \ingroup Interaction \ingroup ToolManagerEtAl Simple paintbrush drawing tool. Right now there are only circular pens of varying size. This class specified only the drawing "color" for the super class PaintbrushTool. \warning Only to be instantiated by mitk::ToolManager. $Author: maleike $ */ class MITKSEGMENTATION_EXPORT ErasePaintbrushTool : public PaintbrushTool { public: mitkClassMacro(ErasePaintbrushTool, PaintbrushTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char **GetXPM() const override; us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; protected: ErasePaintbrushTool(); // purposely hidden ~ErasePaintbrushTool() override; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkPaintbrushTool.h b/Modules/Segmentation/Interactions/mitkPaintbrushTool.h index b8c4074376..d0d5731aae 100644 --- a/Modules/Segmentation/Interactions/mitkPaintbrushTool.h +++ b/Modules/Segmentation/Interactions/mitkPaintbrushTool.h @@ -1,101 +1,100 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkPaintbrushTool_h_Included #define mitkPaintbrushTool_h_Included #include "mitkCommon.h" #include "mitkFeedbackContourTool.h" #include namespace mitk { class StateMachineAction; class InteractionEvent; class InteractionPositionEvent; /** \brief Paintbrush tool for InteractiveSegmentation \sa FeedbackContourTool \sa ExtractImageFilter - \sa OverwriteSliceImageFilter \ingroup Interaction \ingroup ToolManagerEtAl Simple paintbrush drawing tool. Right now there are only circular pens of varying size. \warning Only to be instantiated by mitk::ToolManager. $Author: maleike $ */ class MITKSEGMENTATION_EXPORT PaintbrushTool : public FeedbackContourTool { public: // sent when the pen size is changed or should be updated in a GUI. Message1 SizeChanged; mitkClassMacro(PaintbrushTool, FeedbackContourTool); void SetSize(int value); protected: PaintbrushTool(int paintingPixelValue = 1); // purposely hidden ~PaintbrushTool() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; virtual void OnMousePressed(StateMachineAction *, InteractionEvent *); virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *); virtual void OnPrimaryButtonPressedMoved(StateMachineAction *, InteractionEvent *); virtual void MouseMoved(mitk::InteractionEvent *interactionEvent, bool leftMouseButtonPressed); virtual void OnMouseReleased(StateMachineAction *, InteractionEvent *); virtual void OnInvertLogic(StateMachineAction *, InteractionEvent *); /** * \todo This is a possible place where to introduce * different types of pens */ void UpdateContour(const InteractionPositionEvent *); /** * Little helper function. Returns the upper left corner of the given pixel. */ mitk::Point2D upperLeft(mitk::Point2D p); /** * Checks if the current slice has changed */ void CheckIfCurrentSliceHasChanged(const InteractionPositionEvent *event); void OnToolManagerWorkingDataModified(); int m_PaintingPixelValue; static int m_Size; ContourModel::Pointer m_MasterContour; int m_LastContourSize; Image::Pointer m_WorkingSlice; PlaneGeometry::ConstPointer m_CurrentPlane; DataNode::Pointer m_WorkingNode; mitk::Point3D m_LastPosition; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index ab3ce6c2a5..5ccc2dc29b 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"; + Tool::m_EventConfig = "DisplayConfigBlockLMB.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(); this->GetToolManager()->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); m_LastTimePointTriggered = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); } void mitk::SegTool2D::Deactivated() { 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 != this->GetToolManager()) { 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 != this->GetToolManager()) { 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) { this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } else { mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer markers = 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; } } 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/mitkSetRegionTool.h b/Modules/Segmentation/Interactions/mitkSetRegionTool.h index b1fc705dab..fbaa1c2ecb 100644 --- a/Modules/Segmentation/Interactions/mitkSetRegionTool.h +++ b/Modules/Segmentation/Interactions/mitkSetRegionTool.h @@ -1,64 +1,63 @@ /*============================================================================ 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 mitkSetRegionTool_h_Included #define mitkSetRegionTool_h_Included #include "mitkCommon.h" #include "mitkFeedbackContourTool.h" #include namespace mitk { class Image; class StateMachineAction; class InteractionEvent; /** \brief Fills or erases a 2D region \sa FeedbackContourTool \sa ExtractImageFilter - \sa OverwriteSliceImageFilter \ingroup Interaction \ingroup ToolManagerEtAl Finds the outer contour of a shape in 2D (possibly including holes) and sets all the inside pixels to a specified value. This might fill holes or erase segmentations. \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT SetRegionTool : public FeedbackContourTool { public: mitkClassMacro(SetRegionTool, FeedbackContourTool); protected: SetRegionTool(int paintingPixelValue = 1); // purposely hidden ~SetRegionTool() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; virtual void OnMousePressed(StateMachineAction *, InteractionEvent *); virtual void OnMouseReleased(StateMachineAction *, InteractionEvent *); virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *); int m_PaintingPixelValue; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkTool.cpp b/Modules/Segmentation/Interactions/mitkTool.cpp index e17746d67a..fb9721e97c 100644 --- a/Modules/Segmentation/Interactions/mitkTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTool.cpp @@ -1,348 +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_EventConfig(""), 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()); + displayInteractor->AddEventConfig(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()); + displayActionEventBroadcast->AddEventConfig(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/Testing/files.cmake b/Modules/Segmentation/Testing/files.cmake index 9330f21eb2..c707e4775d 100644 --- a/Modules/Segmentation/Testing/files.cmake +++ b/Modules/Segmentation/Testing/files.cmake @@ -1,29 +1,26 @@ set(MODULE_TESTS mitkContourMapper2DTest.cpp mitkContourTest.cpp mitkContourModelSetToImageFilterTest.cpp mitkDataNodeSegmentationTest.cpp mitkFeatureBasedEdgeDetectionFilterTest.cpp mitkImageToContourFilterTest.cpp mitkSegmentationInterpolationTest.cpp mitkOverwriteSliceFilterTest.cpp mitkOverwriteSliceFilterObliquePlaneTest.cpp # mitkToolManagerTest.cpp mitkToolManagerProviderTest.cpp mitkManualSegmentationToSurfaceFilterTest.cpp #new cpp unit style mitkToolInteractionTest.cpp ) -set(MODULE_IMAGE_TESTS - mitkOverwriteSliceImageFilterTest.cpp #only runs on images -) set(MODULE_CUSTOM_TESTS ) set(MODULE_TESTIMAGE US4DCyl.nrrd Pic3D.nrrd Pic2DplusT.nrrd BallBinary30x30x30.nrrd Png2D-bw.png ) diff --git a/Modules/Segmentation/Testing/mitkOverwriteSliceImageFilterTest.cpp b/Modules/Segmentation/Testing/mitkOverwriteSliceImageFilterTest.cpp deleted file mode 100644 index 604d647f6f..0000000000 --- a/Modules/Segmentation/Testing/mitkOverwriteSliceImageFilterTest.cpp +++ /dev/null @@ -1,366 +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 "mitkCompareImageSliceTestHelper.h" -#include "mitkCoreObjectFactory.h" -#include "mitkExtractImageFilter.h" -#include "mitkOverwriteSliceImageFilter.h" - -#include - -unsigned int CompareImageSliceTestHelper::m_Dimension0 = 0; -unsigned int CompareImageSliceTestHelper::m_Dimension1 = 0; -unsigned int CompareImageSliceTestHelper::m_SliceDimension = 0; -unsigned int CompareImageSliceTestHelper::m_SliceIndex = 0; -bool CompareImageSliceTestHelper::m_ComparisonResult = false; -mitk::Image *CompareImageSliceTestHelper::m_SliceImage = nullptr; - -class mitkOverwriteSliceImageFilterTestClass -{ -public: - static void Test3D(mitk::OverwriteSliceImageFilter *filter, mitk::Image *image, unsigned int &numberFailed) - { - assert(filter); - assert(image); - - filter->SetInput(image); - - unsigned int initialNumberFailed = numberFailed; - bool exception = false; - // first extract slices and rewrite them - for (unsigned int sliceDimension = 0; sliceDimension < 3; ++sliceDimension) - { - mitk::ExtractImageFilter::Pointer extractor = mitk::ExtractImageFilter::New(); - extractor->SetInput(image); - extractor->SetSliceDimension(sliceDimension); - extractor->SetSliceIndex(2); // third slice in that direction - - try - { - extractor->Update(); - } - catch (...) - { - if (sliceDimension < 3) - { - // probably no sliceindex 2 there or extractor just doesn't work (check the corresponding test) - std::cout << " (WW) Couldn't extract slice number 3 from a 3D image. This could be a problem if the image " - "is not only two slices big." - << std::endl; - continue; - } - else - { - continue; // good - } - } - - mitk::Image::Pointer slice = extractor->GetOutput()->Clone(); - - filter->SetSliceDimension(sliceDimension); - filter->SetSliceIndex(1); // second slice in that direction - filter->SetSliceImage(slice); - - try - { - filter->Update(); // try to overwrite - } - catch (...) - { - if (sliceDimension < 3) - { - ++numberFailed; - std::cerr << " (EE) Couln't overwrite a slice with data from a neigbor in a " << image->GetDimension() - << "-dimensional image, sliceDimension " << sliceDimension << " sliceIndex 1-2." - << "(l. " << __LINE__ << ")" << std::endl; - } - else - { - // this was expected and is nice to see - continue; - } - } - - mitk::Image::Pointer output = filter->GetOutput(); - - if (output.IsNull()) - { - ++numberFailed; - std::cerr << " (EE) Overwrite filter has output nullptr and gave no exception for an " << image->GetDimension() - << "-dimensional image, sliceDimension " << sliceDimension << " sliceIndex 1-2." - << "(l. " << __LINE__ << ")" << std::endl; - continue; - } - - if (!CompareImageSliceTestHelper::CompareSlice(image, sliceDimension, 1, slice)) - { - ++numberFailed; - std::cerr << " (EE) Overwriting a slice seemed to work, but the pixels are not correct for an " - << image->GetDimension() << "-dimensional image, sliceDimension " << sliceDimension - << " sliceIndex 1-2." - << "(l. " << __LINE__ << ")" << std::endl; - } - - // try inserting at a position outside the image - filter->SetSliceDimension(sliceDimension); - filter->SetSliceIndex(image->GetDimension(sliceDimension)); // last possible slice index + 1 - filter->SetSliceImage(slice); - - exception = false; - try - { - filter->Update(); // try to overwrite - } - catch (...) - { - exception = true; - } - - if (!exception) - { - ++numberFailed; - std::cerr << " (EE) Inserting a slice outside the 3D volume did NOT throw an exception for an " - << image->GetDimension() << "-dimensional image, sliceDimension " << sliceDimension - << " sliceIndex 1-2." - << "(l. " << __LINE__ << ")" << std::endl; - } - - mitk::Image::Pointer originalSlice = slice; - - // now test slices that just don't fit (slice too big) - { - unsigned int dim[] = {slice->GetDimension(0) + 2, slice->GetDimension(1) + 2}; - slice = mitk::Image::New(); - slice->Initialize(mitk::MakeScalarPixelType(), 2, dim); - unsigned int i; - mitk::ImageWriteAccessor accessor(slice); - auto *p = (signed int *)accessor.GetData(); - unsigned int size = dim[0] * dim[1]; - for (i = 0; i < size; ++i, ++p) - *p = (signed int)i; - - // try to insert this bad slice - filter->SetSliceImage(slice); - exception = false; - try - { - filter->Update(); // try to overwrite - } - catch (...) - { - exception = true; - } - - if (!exception) - { - ++numberFailed; - std::cerr << " (EE) Trying to insert a slice of bad dimensions (larger) did NOT throw an exception in an " - << image->GetDimension() << "-dimensional image, sliceDimension " << sliceDimension - << " sliceIndex 1-2." - << "(l. " << __LINE__ << ")" << std::endl; - } - } - - // now test slices that just don't fit (slice too small) - { - slice = originalSlice; - if ((slice->GetDimension(0) < 3) || (slice->GetDimension(1) < 3)) - continue; // not possible shrink the image much further - unsigned int dim[] = {slice->GetDimension(0) - 2, slice->GetDimension(1) - 2}; - slice = mitk::Image::New(); - slice->Initialize(mitk::MakeScalarPixelType(), 2, dim); - unsigned int i; - mitk::ImageWriteAccessor accessor(slice); - auto *p = (signed int *)accessor.GetData(); - unsigned int size = dim[0] * dim[1]; - for (i = 0; i < size; ++i, ++p) - *p = (signed int)i; - - // try to insert this bad slice - filter->SetSliceImage(slice); - exception = false; - try - { - filter->Update(); // try to overwrite - } - catch (...) - { - exception = true; - } - - if (!exception) - { - ++numberFailed; - std::cerr << " (EE) Trying to insert a slice of bad dimensions (smaller) did NOT throw an exception in an " - << image->GetDimension() << "-dimensional image, sliceDimension " << sliceDimension - << " sliceIndex 1-2." - << "(l. " << __LINE__ << ")" << std::endl; - } - } - } - - if (numberFailed == initialNumberFailed) - { - std::cout << " (II) Overwriting works nicely (gives result, pixels are good) " << image->GetDimension() - << "-dimensional image." - << "(l. " << __LINE__ << ")" << std::endl; - } - } - - static void Test2D(mitk::OverwriteSliceImageFilter *filter, mitk::Image *image, unsigned int &numberFailed) - { - assert(filter); - assert(image); - - filter->SetInput(image); - filter->SetSliceImage(image); - bool exception = false; - try - { - filter->Update(); - } - catch (...) - { - exception = true; - } - - if (!exception) - { - std::cerr << " (EE) Using OverwriteImageFilter for 2D -> 2D did not throw an exception " - << "(l. " << __LINE__ << ")" << std::endl; - } - - unsigned int initialNumberFailed = numberFailed; - if (numberFailed == initialNumberFailed) - { - std::cout << " (II) Overwriting works nicely (gives result, pixels are good) " << image->GetDimension() - << "-dimensional image." - << "(l. " << __LINE__ << ")" << std::endl; - } - } - - static void TestOtherD(mitk::OverwriteSliceImageFilter *filter, mitk::Image *image, unsigned int &numberFailed) - { - assert(filter); - assert(image); - - filter->SetInput(image); - filter->SetSliceImage(image); - bool exception = false; - try - { - filter->Update(); - } - catch (...) - { - exception = true; - } - - if (!exception) - { - std::cerr << " (EE) Using OverwriteImageFilter did not throw an exception " - << "(l. " << __LINE__ << ")" << std::endl; - } - - unsigned int initialNumberFailed = numberFailed; - if (numberFailed == initialNumberFailed) - { - std::cout << " (II) Overwriting works nicely (gives result, pixels are good) " << image->GetDimension() - << "-dimensional image." - << "(l. " << __LINE__ << ")" << std::endl; - } - } -}; - -/// ctest entry point -int mitkOverwriteSliceImageFilterTest(int argc, char *argv[]) -{ - // one big variable to tell if anything went wrong - unsigned int numberFailed(0); - - // need one parameter (image filename) - if (argc == 0) - { - std::cerr << "No file specified [FAILED]" << std::endl; - return EXIT_FAILURE; - } - - // load the image - - mitk::Image::Pointer image = nullptr; - try - { - MITK_INFO << "Testing with parameter '" << argv[1] << "'"; - - std::string pathToImage(argv[1]); - image = mitk::IOUtil::Load(pathToImage); - if (image.IsNull()) - { - MITK_INFO << "File not an image - test will not be applied"; - return EXIT_FAILURE; - } - } - catch (itk::ExceptionObject &ex) - { - ++numberFailed; - std::cerr << "Exception: " << ex << "[FAILED]" << std::endl; - return EXIT_FAILURE; - } - - std::cout << " (II) Could load image." << std::endl; - std::cout << "Testing filter instantiation" << std::endl; - - // instantiation - mitk::OverwriteSliceImageFilter::Pointer filter = mitk::OverwriteSliceImageFilter::New(); - if (filter.IsNotNull()) - { - std::cout << " (II) Instantiation works." << std::endl; - } - else - { - ++numberFailed; - std::cout << "Test failed, and it's the ugliest one!" << std::endl; - return EXIT_FAILURE; - } - - // some real work - if (image->GetDimension() == 2) - { - mitkOverwriteSliceImageFilterTestClass::Test2D(filter, image, numberFailed); - } - else if (image->GetDimension() == 3) - { - mitkOverwriteSliceImageFilterTestClass::Test3D(filter, image, numberFailed); - } - else - { - mitkOverwriteSliceImageFilterTestClass::TestOtherD(filter, image, numberFailed); - } - - std::cout << "Testing filter destruction" << std::endl; - - // freeing - filter = nullptr; - - std::cout << " (II) Freeing works." << std::endl; - - if (numberFailed > 0) - { - std::cerr << numberFailed << " tests failed." << std::endl; - return EXIT_FAILURE; - } - else - { - std::cout << "PASSED all tests." << std::endl; - return EXIT_SUCCESS; - } -} diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index b22a1b73cc..02b759d7b4 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,119 +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/mitknnUnetTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO Interactions/mitkProcessExecutor.cpp 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/QmitkFastMarchingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp index 08670906f7..d37040dc8b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp @@ -1,31 +1,29 @@ /*============================================================================ 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 "QmitkFastMarchingToolGUI.h" -#include "QmitkNewSegmentationDialog.h" - #include "mitkBaseRenderer.h" #include "mitkStepper.h" #include #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkFastMarchingToolGUI, "") QmitkFastMarchingToolGUI::QmitkFastMarchingToolGUI() : QmitkFastMarchingToolGUIBase(true) {} diff --git a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp index f90802a78c..34eb6dfb82 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkLiveWireTool2DGUI.cpp @@ -1,62 +1,60 @@ /*============================================================================ 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 "QmitkLiveWireTool2DGUI.h" -#include "QmitkNewSegmentationDialog.h" - #include "mitkBaseRenderer.h" #include "mitkStepper.h" #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkLiveWireTool2DGUI, "") QmitkLiveWireTool2DGUI::QmitkLiveWireTool2DGUI() : QmitkToolGUI() { m_Controls.setupUi(this); m_Controls.m_Information->hide(); connect(m_Controls.m_ConfirmButton, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); connect(m_Controls.m_ClearButton, SIGNAL(clicked()), this, SLOT(OnClearSegmentation())); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); connect(m_Controls.m_InformationCheckBox, SIGNAL(toggled(bool)), this, SLOT(OnShowInformation(bool))); } QmitkLiveWireTool2DGUI::~QmitkLiveWireTool2DGUI() { } void QmitkLiveWireTool2DGUI::OnNewToolAssociated(mitk::Tool *tool) { m_LiveWireTool = dynamic_cast(tool); } void QmitkLiveWireTool2DGUI::OnConfirmSegmentation() { if (m_LiveWireTool.IsNotNull()) m_LiveWireTool->ConfirmSegmentation(); } void QmitkLiveWireTool2DGUI::OnClearSegmentation() { if (m_LiveWireTool.IsNotNull()) m_LiveWireTool->ClearSegmentation(); } void QmitkLiveWireTool2DGUI::OnShowInformation(bool on) { m_Controls.m_Information->setVisible(on); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkPaintbrushToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkPaintbrushToolGUI.cpp index 084197aa7b..fd92685a7f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkPaintbrushToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkPaintbrushToolGUI.cpp @@ -1,119 +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. ============================================================================*/ #include "QmitkPaintbrushToolGUI.h" -#include "QmitkNewSegmentationDialog.h" - #include #include #include #include #include QmitkPaintbrushToolGUI::QmitkPaintbrushToolGUI() : QmitkToolGUI(), m_Slider(nullptr) { // create the visible widgets QBoxLayout *layout = new QHBoxLayout(this); this->setContentsMargins(0, 0, 0, 0); QLabel *label = new QLabel("Size ", this); QFont f = label->font(); f.setBold(false); label->setFont(f); layout->addWidget(label); m_SizeLabel = new QLabel(" 10", this); f = m_SizeLabel->font(); f.setBold(false); m_SizeLabel->setFont(f); layout->addWidget(m_SizeLabel); // m_Slider = new QSlider( 1, 50, 1, 10, Qt::Horizontal, this ); m_Slider = new QSlider(Qt::Horizontal, this); m_Slider->setMinimum(1); m_Slider->setMaximum(50); m_Slider->setPageStep(1); m_Slider->setValue(10); connect(m_Slider, SIGNAL(valueChanged(int)), this, SLOT(OnSliderValueChanged(int))); layout->addWidget(m_Slider); /* m_Frame = new QFrame( this ); m_Frame->setMinimumSize( QSize(50, 50) ); m_Frame->setFrameStyle( QFrame::Box || QFrame::Plain ); m_Frame->show(); layout->addWidget( m_Frame ); */ connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkPaintbrushToolGUI::~QmitkPaintbrushToolGUI() { // !!! if (m_PaintbrushTool.IsNotNull()) { m_PaintbrushTool->SizeChanged -= mitk::MessageDelegate1(this, &QmitkPaintbrushToolGUI::OnSizeChanged); } } void QmitkPaintbrushToolGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_PaintbrushTool.IsNotNull()) { m_PaintbrushTool->SizeChanged -= mitk::MessageDelegate1(this, &QmitkPaintbrushToolGUI::OnSizeChanged); } m_PaintbrushTool = dynamic_cast(tool); if (m_PaintbrushTool.IsNotNull()) { m_PaintbrushTool->SizeChanged += mitk::MessageDelegate1(this, &QmitkPaintbrushToolGUI::OnSizeChanged); } } void QmitkPaintbrushToolGUI::OnSliderValueChanged(int value) { if (m_PaintbrushTool.IsNotNull()) { m_PaintbrushTool->SetSize(value); } VisualizePaintbrushSize(value); } void QmitkPaintbrushToolGUI::VisualizePaintbrushSize(int size) { m_SizeLabel->setText(QString("%1 ").arg(size)); /* nice, but useless. scale does not correspond to the image in a render window, so this is more or less useless */ /* QPainter p( m_Frame ); p.eraseRect( m_Frame->rect() ); int width = size; int height = size; int x = m_Frame->width() / 2 - width / 2; int y = m_Frame->height() / 2 - height / 2; p.drawEllipse( x, y, width, height ); */ } void QmitkPaintbrushToolGUI::OnSizeChanged(int current) { m_Slider->setValue(current); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index 55cb106313..0090146b9c 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1432 +1,1442 @@ /*============================================================================ 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); + auto command3 = itk::ReceptorMemberCommand::New(); + command3->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationAborted); + InterpolationAbortedObserverTag = m_Interpolator->AddObserver(itk::AbortEvent(), command3); + // 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(InterpolationAbortedObserverTag); 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 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 segmentation3D = m_Segmentation; unsigned int timeStep = 0; const auto timePoint = slicer->GetSelectedTimePoint(); if (4 == m_Segmentation->GetDimension()) { 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 = geometry->TimePointToTimeStep(timePoint); auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); segmentation3D = timeSelector->GetOutput(); } // 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 accessor(diffImage); // Set all pixels to zero auto pixelType = mitk::MakeScalarPixelType(); // For legacy purpose support former pixel type of segmentations (before multilabel) if (itk::ImageIOBase::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType(); 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 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; // Reuse interpolation algorithm instance for each slice to cache boundary calculations auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); // Distribute slice interpolations to multiple threads const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); std::vector> sliceIndices(numThreads); for (std::remove_const_t sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); std::vector threads; threads.reserve(numThreads); // This lambda will be executed by the threads auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { auto clonedPlaneGeometry = planeGeometry->Clone(); auto origin = clonedPlaneGeometry->GetOrigin(); for (auto sliceIndex : sliceIndices[threadIndex]) { 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(); } }; 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) { // 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(); } 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::OnInterpolationAborted(const itk::EventObject& /*e*/) +{ + m_CmbInterpolation->setCurrentIndex(0); + m_FeedbackNode->SetData(nullptr); +} + 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/Qmitk/QmitkSlicesInterpolator.h b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h index e42072bde1..a069e29020 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h @@ -1,292 +1,298 @@ /*============================================================================ 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 QmitkSlicesInterpolator_h_Included #define QmitkSlicesInterpolator_h_Included #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkSegmentationInterpolationController.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceInterpolationController.h" #include "mitkToolManager.h" #include #include "mitkFeatureBasedEdgeDetectionFilter.h" #include "mitkPointCloudScoringFilter.h" #include #include #include #include #include #include #include #include "mitkVtkRepresentationProperty.h" #include "vtkProperty.h" // For running 3D interpolation in background #include #include #include #include namespace mitk { class PlaneGeometry; class SliceNavigationController; } class QPushButton; /** \brief GUI for slices interpolation. \ingroup ToolManagerEtAl \ingroup Widgets \sa QmitkInteractiveSegmentation \sa mitk::SegmentationInterpolation While mitk::SegmentationInterpolation does the bookkeeping of interpolation (keeping track of which slices contain how much segmentation) and the algorithmic work, QmitkSlicesInterpolator is responsible to watch the GUI, to notice, which slice is currently visible. It triggers generation of interpolation suggestions and also triggers acception of suggestions. \todo show/hide feedback on demand Last contributor: $Author: maleike $ */ class MITKSEGMENTATIONUI_EXPORT QmitkSlicesInterpolator : public QWidget { Q_OBJECT public: QmitkSlicesInterpolator(QWidget *parent = nullptr, const char *name = nullptr); /** To be called once before real use. */ void Initialize(mitk::ToolManager *toolManager, const QList &controllers); void Uninitialize(); ~QmitkSlicesInterpolator() override; void SetDataStorage(mitk::DataStorage::Pointer storage); mitk::DataStorage *GetDataStorage(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerWorkingDataModified(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerReferenceDataModified(); void OnTimeChanged(itk::Object *sender, const itk::EventObject &); void OnSliceChanged(itk::Object *sender, const itk::EventObject &); void OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnInterpolationInfoChanged(const itk::EventObject &); + /** + Just public because it is called by itk::Commands. You should not need to call this. + */ + void OnInterpolationAborted(const itk::EventObject &); + /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnSurfaceInterpolationInfoChanged(const itk::EventObject &); /** * @brief Set the visibility of the 3d interpolation */ void Show3DInterpolationResult(bool); signals: void SignalRememberContourPositions(bool); void SignalShowMarkerNodes(bool); public slots: virtual void setEnabled(bool); /** Call this from the outside to enable/disable interpolation */ void EnableInterpolation(bool); void Enable3DInterpolation(bool); /** Call this from the outside to accept all interpolations */ void FinishInterpolation(mitk::SliceNavigationController *slicer = nullptr); protected slots: /** Reaction to button clicks. */ void OnAcceptInterpolationClicked(); /* Opens popup to ask about which orientation should be interpolated */ void OnAcceptAllInterpolationsClicked(); /* Reaction to button clicks */ void OnAccept3DInterpolationClicked(); void OnReinit3DInterpolation(); void OnSuggestPlaneClicked(); /* * Will trigger interpolation for all slices in given orientation (called from popup menu of * OnAcceptAllInterpolationsClicked) */ void OnAcceptAllPopupActivated(QAction *action); /** Called on activation/deactivation */ void OnInterpolationActivated(bool); void On3DInterpolationActivated(bool); void OnInterpolationMethodChanged(int index); // Enhancement for 3D interpolation void On2DInterpolationEnabled(bool); void On3DInterpolationEnabled(bool); void OnInterpolationDisabled(bool); void OnShowMarkers(bool); void Run3DInterpolation(); void RunPlaneSuggestion(); void OnSurfaceInterpolationFinished(); void StartUpdateInterpolationTimer(); void StopUpdateInterpolationTimer(); void ChangeSurfaceColor(); protected: const std::map createActionToSliceDimension(); std::map ACTION_TO_SLICEDIMENSION; void AcceptAllInterpolations(mitk::SliceNavigationController *slicer); /** Retrieves the currently selected PlaneGeometry from a SlicedGeometry3D that is generated by a SliceNavigationController and calls Interpolate to further process this PlaneGeometry into an interpolation. \param e is a actually a mitk::SliceNavigationController::GeometrySliceEvent, sent by a SliceNavigationController \param slicer the SliceNavigationController */ bool TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer); /** Given a PlaneGeometry, this method figures out which slice of the first working image (of the associated ToolManager) should be interpolated. The actual work is then done by our SegmentationInterpolation object. */ void Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer); // void InterpolateSurface(); /** Called internally to update the interpolation suggestion. Finds out about the focused render window and requests an interpolation. */ void UpdateVisibleSuggestion(); void SetCurrentContourListID(); private: void HideAllInterpolationControls(); void Show2DInterpolationControls(bool show); void Show3DInterpolationControls(bool show); void CheckSupportedImageDimension(); void WaitForFutures(); void NodeRemoved(const mitk::DataNode* node); mitk::SegmentationInterpolationController::Pointer m_Interpolator; mitk::SurfaceInterpolationController::Pointer m_SurfaceInterpolator; mitk::FeatureBasedEdgeDetectionFilter::Pointer m_EdgeDetector; mitk::PointCloudScoringFilter::Pointer m_PointScorer; mitk::ToolManager::Pointer m_ToolManager; bool m_Initialized; QHash m_ControllerToTimeObserverTag; QHash m_ControllerToSliceObserverTag; QHash m_ControllerToDeleteObserverTag; unsigned int InterpolationInfoChangedObserverTag; unsigned int SurfaceInterpolationInfoChangedObserverTag; + unsigned int InterpolationAbortedObserverTag; QGroupBox *m_GroupBoxEnableExclusiveInterpolationMode; QComboBox *m_CmbInterpolation; QPushButton *m_BtnApply2D; QPushButton *m_BtnApplyForAllSlices2D; QPushButton *m_BtnApply3D; // T28261 // QPushButton *m_BtnSuggestPlane; QCheckBox *m_ChkShowPositionNodes; QPushButton *m_BtnReinit3DInterpolation; mitk::DataNode::Pointer m_FeedbackNode; mitk::DataNode::Pointer m_InterpolatedSurfaceNode; mitk::DataNode::Pointer m_3DContourNode; mitk::Image *m_Segmentation; mitk::SliceNavigationController *m_LastSNC; unsigned int m_LastSliceIndex; QHash m_TimePoints; bool m_2DInterpolationEnabled; bool m_3DInterpolationEnabled; // unsigned int m_CurrentListID; mitk::DataStorage::Pointer m_DataStorage; QFuture m_Future; QFutureWatcher m_Watcher; QTimer *m_Timer; QFuture m_PlaneFuture; QFutureWatcher m_PlaneWatcher; bool m_FirstRun; }; #endif diff --git a/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeReinitAction.cpp b/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeReinitAction.cpp index cb3606e661..4cc09949e7 100644 --- a/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeReinitAction.cpp +++ b/Plugins/org.mitk.gui.qt.application/src/QmitkDataNodeReinitAction.cpp @@ -1,133 +1,133 @@ /*============================================================================ 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 #include #include #include #include // mitk gui common plugin #include // namespace that contains the concrete action namespace ReinitAction { void Run(berry::IWorkbenchPartSite::Pointer workbenchPartSite, mitk::DataStorage::Pointer dataStorage, const QList& selectedNodes /*= QList()*/, mitk::BaseRenderer* baseRenderer /*= nullptr*/) { if (selectedNodes.empty()) { return; } if (workbenchPartSite.IsNotNull()) { auto renderWindow = mitk::WorkbenchUtil::GetRenderWindowPart(workbenchPartSite->GetPage(), mitk::WorkbenchUtil::NONE); if (nullptr == renderWindow) { renderWindow = mitk::WorkbenchUtil::OpenRenderWindowPart(workbenchPartSite->GetPage(), false); if (nullptr == renderWindow) { // no render window available return; } } } auto boundingBoxPredicate = mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("includeInBoundingBox", mitk::BoolProperty::New(false), baseRenderer)); mitk::DataStorage::SetOfObjects::Pointer nodes = mitk::DataStorage::SetOfObjects::New(); for (const auto& dataNode : selectedNodes) { if (boundingBoxPredicate->CheckNode(dataNode)) { nodes->InsertElement(nodes->Size(), dataNode); } } if (nodes->empty()) { return; } if (1 == nodes->Size()) // Special case: If exactly one ... { auto image = dynamic_cast(nodes->ElementAt(0)->GetData()); if (nullptr != image) // ... image is selected, reinit is expected to rectify askew images. { if (nullptr == baseRenderer) { - mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL); } else { mitk::RenderingManager::GetInstance()->InitializeView(baseRenderer->GetRenderWindow(), image->GetTimeGeometry(), true); } return; } } auto boundingGeometry = dataStorage->ComputeBoundingGeometry3D(nodes, "visible", baseRenderer); if (nullptr == baseRenderer) { mitk::RenderingManager::GetInstance()->InitializeViews(boundingGeometry); } else { mitk::RenderingManager::GetInstance()->InitializeView(baseRenderer->GetRenderWindow(), boundingGeometry); } } } QmitkDataNodeReinitAction::QmitkDataNodeReinitAction(QWidget* parent, berry::IWorkbenchPartSite::Pointer workbenchpartSite) : QAction(parent) , QmitkAbstractDataNodeAction(workbenchpartSite) { setText(tr("Reinit")); InitializeAction(); } QmitkDataNodeReinitAction::QmitkDataNodeReinitAction(QWidget* parent, berry::IWorkbenchPartSite* workbenchpartSite) : QAction(parent) , QmitkAbstractDataNodeAction(berry::IWorkbenchPartSite::Pointer(workbenchpartSite)) { setText(tr("Reinit")); InitializeAction(); } void QmitkDataNodeReinitAction::InitializeAction() { connect(this, &QmitkDataNodeReinitAction::triggered, this, &QmitkDataNodeReinitAction::OnActionTriggered); } void QmitkDataNodeReinitAction::OnActionTriggered(bool /*checked*/) { if (m_WorkbenchPartSite.Expired()) { return; } if (m_DataStorage.IsExpired()) { return; } mitk::BaseRenderer::Pointer baseRenderer = GetBaseRenderer(); auto selectedNodes = GetSelectedNodes(); ReinitAction::Run(m_WorkbenchPartSite.Lock(), m_DataStorage.Lock(), selectedNodes, baseRenderer); } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.cpp index e333ed3532..7639cb1f42 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.cpp @@ -1,522 +1,563 @@ /*============================================================================ 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 "QmitkAbstractView.h" #include "QmitkDataNodeSelectionProvider.h" #include "internal/QmitkCommonActivator.h" #include "internal/QmitkDataNodeItemModel.h" // mitk Includes #include #include #include #include #include // berry Includes #include #include #include #include #include // CTK Includes #include // Qt Includes #include #include #include #include #include class QmitkAbstractViewPrivate { public: QmitkAbstractViewPrivate(QmitkAbstractView* qq) : q(qq) , m_PrefServiceTracker(QmitkCommonActivator::GetContext()) , m_DataStorageServiceTracker(QmitkCommonActivator::GetContext()) , m_Parent(nullptr) , m_DataNodeItemModel(new QmitkDataNodeItemModel) , m_DataNodeSelectionModel(new QItemSelectionModel(m_DataNodeItemModel)) , m_InDataStorageChanged(false) { m_PrefServiceTracker.open(); m_DataStorageServiceTracker.open(); } ~QmitkAbstractViewPrivate() { delete m_DataNodeSelectionModel; delete m_DataNodeItemModel; m_PrefServiceTracker.close(); m_DataStorageServiceTracker.close(); } /** * Called when a DataStorage Add Event was thrown. Sets * m_InDataStorageChanged to true and calls NodeAdded afterwards. * \see m_InDataStorageChanged */ void NodeAddedProxy(const mitk::DataNode* node) { // garantuee no recursions when a new node event is thrown in NodeAdded() if(!m_InDataStorageChanged) { m_InDataStorageChanged = true; q->NodeAdded(node); q->DataStorageModified(); m_InDataStorageChanged = false; } } /** * Called when a DataStorage remove event was thrown. Sets * m_InDataStorageChanged to true and calls NodeRemoved afterwards. * \see m_InDataStorageChanged */ void NodeRemovedProxy(const mitk::DataNode* node) { // garantuee no recursions when a new node event is thrown in NodeAdded() if(!m_InDataStorageChanged) { m_InDataStorageChanged = true; q->NodeRemoved(node); q->DataStorageModified(); m_InDataStorageChanged = false; } } /** * Called when a DataStorage changed event was thrown. Sets * m_InDataStorageChanged to true and calls NodeChanged afterwards. * \see m_InDataStorageChanged */ void NodeChangedProxy(const mitk::DataNode* node) { // garantuee no recursions when a new node event is thrown in NodeAdded() if(!m_InDataStorageChanged) { m_InDataStorageChanged = true; q->NodeChanged(node); q->DataStorageModified(); m_InDataStorageChanged = false; } } /** * reactions to selection events from views */ void BlueBerrySelectionChanged(const berry::IWorkbenchPart::Pointer& sourcepart, const berry::ISelection::ConstPointer& selection) { if(sourcepart.IsNull() || sourcepart.GetPointer() == static_cast(q)) return; if(selection.IsNull()) { q->OnNullSelection(sourcepart); return; } mitk::DataNodeSelection::ConstPointer _DataNodeSelection = selection.Cast(); q->OnSelectionChanged(sourcepart, this->DataNodeSelectionToQList(_DataNodeSelection)); } /** * Converts a mitk::DataNodeSelection to a QList (possibly empty) */ QList DataNodeSelectionToQList(mitk::DataNodeSelection::ConstPointer currentSelection) const; QmitkAbstractView* const q; ctkServiceTracker m_PrefServiceTracker; ctkServiceTracker m_DataStorageServiceTracker; /** * Saves the parent of this view (this is the scrollarea created in CreatePartControl(QWidget*) * \see CreatePartControl(QWidget*) */ QWidget* m_Parent; /** * Holds the current selection (selection made by this View !!!) */ QmitkDataNodeSelectionProvider::Pointer m_SelectionProvider; /** * Holds a helper model for firing selection events. */ QmitkDataNodeItemModel* m_DataNodeItemModel; /** * The selection model for the QmitkDataNodeItemModel; */ QItemSelectionModel* m_DataNodeSelectionModel; /** * object to observe BlueBerry selections */ QScopedPointer m_BlueBerrySelectionListener; /** * Saves if this class is currently working on DataStorage changes. * This is a protector variable to avoid recursive calls on event listener functions. */ bool m_InDataStorageChanged; }; QmitkAbstractView::QmitkAbstractView() : d(new QmitkAbstractViewPrivate(this)) { } void QmitkAbstractView::CreatePartControl(QWidget* parent) { // scrollArea auto scrollArea = new QScrollArea; //QVBoxLayout* scrollAreaLayout = new QVBoxLayout(scrollArea); scrollArea->setFrameShadow(QFrame::Plain); scrollArea->setFrameShape(QFrame::NoFrame); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); // m_Parent d->m_Parent = new QWidget; //m_Parent->setSizePolicy(QSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding)); this->CreateQtPartControl(d->m_Parent); //scrollAreaLayout->addWidget(m_Parent); //scrollArea->setLayout(scrollAreaLayout); // set the widget now scrollArea->setWidgetResizable(true); scrollArea->setWidget(d->m_Parent); // add the scroll area to the real parent (the view tabbar) QWidget* parentQWidget = static_cast(parent); auto parentLayout = new QVBoxLayout(parentQWidget); parentLayout->setMargin(0); parentLayout->setSpacing(0); parentLayout->addWidget(scrollArea); // finally set the layout containing the scroll area to the parent widget (= show it) parentQWidget->setLayout(parentLayout); this->AfterCreateQtPartControl(); } void QmitkAbstractView::AfterCreateQtPartControl() { this->SetSelectionProvider(); // REGISTER DATASTORAGE LISTENER this->GetDataStorage()->AddNodeEvent.AddListener( mitk::MessageDelegate1 ( d.data(), &QmitkAbstractViewPrivate::NodeAddedProxy ) ); this->GetDataStorage()->ChangedNodeEvent.AddListener( mitk::MessageDelegate1 ( d.data(), &QmitkAbstractViewPrivate::NodeChangedProxy ) ); this->GetDataStorage()->RemoveNodeEvent.AddListener( mitk::MessageDelegate1 ( d.data(), &QmitkAbstractViewPrivate::NodeRemovedProxy ) ); // REGISTER PREFERENCES LISTENER berry::IBerryPreferences::Pointer prefs = this->GetPreferences().Cast(); if(prefs.IsNotNull()) prefs->OnChanged.AddListener( berry::MessageDelegate1(this, &QmitkAbstractView::OnPreferencesChanged)); // REGISTER FOR WORKBENCH SELECTION EVENTS d->m_BlueBerrySelectionListener.reset(new berry::NullSelectionChangedAdapter( d.data(), &QmitkAbstractViewPrivate::BlueBerrySelectionChanged)); this->GetSite()->GetWorkbenchWindow()->GetSelectionService()->AddPostSelectionListener(d->m_BlueBerrySelectionListener.data()); // EMULATE INITIAL SELECTION EVENTS // send the current selection berry::IWorkbenchPart::Pointer activePart = this->GetSite()->GetPage()->GetActivePart(); if (activePart.IsNotNull()) { this->OnSelectionChanged(activePart, this->GetCurrentSelection()); } // send preferences changed event this->OnPreferencesChanged(this->GetPreferences().Cast().GetPointer()); } QmitkAbstractView::~QmitkAbstractView() { this->Register(); this->GetDataStorage()->AddNodeEvent.RemoveListener( mitk::MessageDelegate1 ( d.data(), &QmitkAbstractViewPrivate::NodeAddedProxy ) ); this->GetDataStorage()->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1 ( d.data(), &QmitkAbstractViewPrivate::NodeRemovedProxy) ); this->GetDataStorage()->ChangedNodeEvent.RemoveListener( mitk::MessageDelegate1 ( d.data(), &QmitkAbstractViewPrivate::NodeChangedProxy ) ); berry::IBerryPreferences::Pointer prefs = this->GetPreferences().Cast(); if(prefs.IsNotNull()) { prefs->OnChanged.RemoveListener( berry::MessageDelegate1(this, &QmitkAbstractView::OnPreferencesChanged)); // flush the preferences here (disabled, everyone should flush them by themselves at the right moment) // prefs->Flush(); } // REMOVE SELECTION PROVIDER this->GetSite()->SetSelectionProvider(berry::ISelectionProvider::Pointer(nullptr)); berry::ISelectionService* s = GetSite()->GetWorkbenchWindow()->GetSelectionService(); if(s) { s->RemovePostSelectionListener(d->m_BlueBerrySelectionListener.data()); } this->UnRegister(false); } void QmitkAbstractView::SetSelectionProvider() { // REGISTER A SELECTION PROVIDER d->m_SelectionProvider = QmitkDataNodeSelectionProvider::Pointer(new QmitkDataNodeSelectionProvider); d->m_SelectionProvider->SetItemSelectionModel(GetDataNodeSelectionModel()); this->GetSite()->SetSelectionProvider(berry::ISelectionProvider::Pointer(d->m_SelectionProvider)); } QItemSelectionModel *QmitkAbstractView::GetDataNodeSelectionModel() const { return nullptr; } void QmitkAbstractView::OnPreferencesChanged( const berry::IBerryPreferences* ) { } void QmitkAbstractView::DataStorageModified() { } void QmitkAbstractView::DataStorageChanged(mitk::IDataStorageReference::Pointer /*dsRef*/) { } mitk::IRenderWindowPart* QmitkAbstractView::GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategies strategies) const { berry::IWorkbenchPage::Pointer page = GetSite()->GetPage(); return mitk::WorkbenchUtil::GetRenderWindowPart(page, strategies); } void QmitkAbstractView::RequestRenderWindowUpdate(mitk::RenderingManager::RequestType requestType) { mitk::IRenderWindowPart* renderPart = this->GetRenderWindowPart(); - if (renderPart == nullptr) return; + if (renderPart == nullptr) + return; if (mitk::IRenderingManager* renderingManager = renderPart->GetRenderingManager()) { renderingManager->RequestUpdateAll(requestType); } else { renderPart->RequestUpdate(requestType); } } +void QmitkAbstractView::InitializeRenderWindows(const mitk::TimeGeometry *referenceGeometry, + mitk::RenderingManager::RequestType requestType, + bool resetCamera) +{ + mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); + if (nullptr == renderWindowPart) + { + return; + } + + mitk::IRenderingManager* renderingManager = renderWindowPart->GetRenderingManager(); + if (nullptr == renderingManager) + { + return; + } + + mitk::Point3D currentPosition = mitk::Point3D(); + unsigned int imageTimeStep = 0; + if (!resetCamera) + { + // store the current position to set it again later, if the camera should not be reset + currentPosition = renderWindowPart->GetSelectedPosition(); + + // store the current time step to set it again later, if the camera should not be reset + const auto currentTimePoint = renderingManager->GetTimeNavigationController()->GetSelectedTimePoint(); + if (referenceGeometry->IsValidTimePoint(currentTimePoint)) + { + imageTimeStep = referenceGeometry->TimePointToTimeStep(currentTimePoint); + } + } + + // initialize render windows + renderingManager->InitializeViews(referenceGeometry, requestType, resetCamera); + + if (!resetCamera) + { + renderWindowPart->SetSelectedPosition(currentPosition); + renderingManager->GetTimeNavigationController()->GetTime()->SetPos(imageTimeStep); + } +} + void QmitkAbstractView::HandleException( const char* str, QWidget* parent, bool showDialog ) const { //itkGenericOutputMacro( << "Exception caught: " << str ); MITK_ERROR << str; if ( showDialog ) { QMessageBox::critical ( parent, "Exception caught!", str ); } } void QmitkAbstractView::HandleException( std::exception& e, QWidget* parent, bool showDialog ) const { HandleException( e.what(), parent, showDialog ); } void QmitkAbstractView::WaitCursorOn() { QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) ); } void QmitkAbstractView::BusyCursorOn() { QApplication::setOverrideCursor( QCursor(Qt::BusyCursor) ); } void QmitkAbstractView::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkAbstractView::BusyCursorOff() { this->RestoreOverrideCursor(); } void QmitkAbstractView::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } berry::IPreferences::Pointer QmitkAbstractView::GetPreferences() const { berry::IPreferencesService* prefService = d->m_PrefServiceTracker.getService(); // const_cast workaround for bad programming: const uncorrectness this->GetViewSite() should be const QString id = "/" + (const_cast(this))->GetViewSite()->GetId(); return prefService ? prefService->GetSystemPreferences()->Node(id): berry::IPreferences::Pointer(nullptr); } -mitk::DataStorage::Pointer -QmitkAbstractView::GetDataStorage() const +mitk::DataStorage::Pointer QmitkAbstractView::GetDataStorage() const { mitk::IDataStorageService* dsService = d->m_DataStorageServiceTracker.getService(); if (dsService != nullptr) { return dsService->GetDataStorage()->GetDataStorage(); } return nullptr; } mitk::IDataStorageReference::Pointer QmitkAbstractView::GetDataStorageReference() const { mitk::IDataStorageService* dsService = d->m_DataStorageServiceTracker.getService(); if (dsService != nullptr) { return dsService->GetDataStorage(); } return mitk::IDataStorageReference::Pointer(nullptr); } QList QmitkAbstractView::GetCurrentSelection() const { berry::ISelection::ConstPointer selection( this->GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection()); mitk::DataNodeSelection::ConstPointer currentSelection = selection.Cast(); return d->DataNodeSelectionToQList(currentSelection); } bool QmitkAbstractView::IsCurrentSelectionValid() const { return this->GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection(); } QList QmitkAbstractView::GetDataManagerSelection() const { berry::ISelection::ConstPointer selection( this->GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection("org.mitk.views.datamanager")); mitk::DataNodeSelection::ConstPointer currentSelection = selection.Cast(); return d->DataNodeSelectionToQList(currentSelection); } bool QmitkAbstractView::IsDataManagerSelectionValid() const { return this->GetSite()->GetWorkbenchWindow()->GetSelectionService()->GetSelection("org.mitk.views.datamanager"); } void QmitkAbstractView::SetDataManagerSelection(const berry::ISelection::ConstPointer &selection, QItemSelectionModel::SelectionFlags flags) const { berry::IViewPart::Pointer datamanagerView = this->GetSite()->GetWorkbenchWindow()->GetActivePage()->FindView("org.mitk.views.datamanager"); if (datamanagerView.IsNull()) return; datamanagerView->GetSite()->GetSelectionProvider().Cast()->SetSelection(selection, flags); } void QmitkAbstractView::SynchronizeDataManagerSelection() const { berry::ISelection::ConstPointer currentSelection = this->GetSite()->GetSelectionProvider()->GetSelection(); if (currentSelection.IsNull()) return; SetDataManagerSelection(currentSelection); } void QmitkAbstractView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& /*nodes*/) { } void QmitkAbstractView::OnNullSelection(berry::IWorkbenchPart::Pointer /*part*/) { } QList QmitkAbstractViewPrivate::DataNodeSelectionToQList(mitk::DataNodeSelection::ConstPointer currentSelection) const { if (currentSelection.IsNull()) return QList(); return QList::fromStdList(currentSelection->GetSelectedDataNodes()); } void QmitkAbstractView::NodeAdded( const mitk::DataNode* /*node*/ ) { } void QmitkAbstractView::NodeRemoved( const mitk::DataNode* /*node*/ ) { } void QmitkAbstractView::NodeChanged( const mitk::DataNode* /*node*/ ) { } void QmitkAbstractView::FireNodeSelected( mitk::DataNode::Pointer node ) { QList nodes; nodes << node; this->FireNodesSelected(nodes); } void QmitkAbstractView::FireNodesSelected( const QList& nodes ) { // if this is the first call to FireNodesSelected and the selection provider has no QItemSelectiomMode // yet, set our helper model if (d->m_SelectionProvider->GetItemSelectionModel() == nullptr) { d->m_SelectionProvider->SetItemSelectionModel(d->m_DataNodeSelectionModel); } else if (d->m_SelectionProvider->GetItemSelectionModel() != d->m_DataNodeSelectionModel) { MITK_WARN << "A custom data node selection model has been set. Ignoring call to FireNodesSelected()."; return; } if (nodes.empty()) { d->m_DataNodeSelectionModel->clearSelection(); d->m_DataNodeItemModel->clear(); } else { // The helper data node model is just used for sending selection events. // We add the to be selected nodes and set the selection range to everything. d->m_DataNodeItemModel->clear(); foreach(mitk::DataNode::Pointer node, nodes) { d->m_DataNodeItemModel->AddDataNode(node); } d->m_DataNodeSelectionModel->select(QItemSelection(d->m_DataNodeItemModel->index(0,0), d->m_DataNodeItemModel->index(nodes.size()-1, 0)), QItemSelectionModel::ClearAndSelect); } } diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.h b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.h index 6fb4f65f85..1566bb53f5 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkAbstractView.h @@ -1,363 +1,375 @@ /*============================================================================ 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 QMITKABSTRACTVIEW_H_ #define QMITKABSTRACTVIEW_H_ //# blueberry stuff #include #include #include #include //# mitk stuff #include #include "mitkDataNodeSelection.h" #include "mitkIRenderWindowPart.h" #include #include #include #include #include namespace mitk { class DataNode; } namespace berry { struct IBerryPreferences; } class QmitkAbstractViewPrivate; class QmitkAbstractViewSelectionProvider; /** * \ingroup org_mitk_gui_qt_common * * \brief A convenient base class for MITK related BlueBerry Views. * * QmitkAbstractView provides several convenience methods that ease the introduction of a new view: * *
    *
  1. Access to the DataStorage (~ the shared data repository) *
  2. Access to the active IRenderWindowPart *
  3. Access to and update notification for the view's preferences *
  4. Access to and update notification for the current DataNode selection / to DataNode selection events send through the SelectionService *
  5. Access to and update notification for DataNode events (added/removed/modified) *
  6. Methods to send DataNode selections through the SelectionService *
  7. Some minor important convenience methods (like changing the mouse cursor/exception handling) *
* * Usually all MITK Views inherit from QmitkAbstractView to achieve a consistent Workbench behavior. * * When inheriting from QmitkAbstractView, you must implement the following methods: *
    *
  • void CreateQtPartControl(QWidget* parent) *
  • void SetFocus() *
* * You may reimplement the following private virtual methods to customize your View's behavior: *
    *
  • void SetSelectionProvider() *
  • QItemSelectionModel* GetDataNodeSelectionModel() const *
* * You may reimplement the following private virtual methods to be notified about certain changes: *
    *
  • void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList &nodes) *
  • void OnNullSelection(berry::IWorkbenchPart::Pointer part) *
  • void OnPreferencesChanged(const berry::IBerryPreferences*) *
  • void NodeAdded(const mitk::DataNode* node) *
  • void NodeChanged(const mitk::DataNode* node) *
  • void NodeRemoved(const mitk::DataNode* node) *
  • void DataStorageModified() *
  • void DataStorageChanged(mitk::IDataStorageReference::Pointer dsRef) *
* * \see mitk::ILifecycleAwarePart * \see mitk::IZombieViewPart * \see mitk::IRenderWindowPartListener */ class MITK_QT_COMMON QmitkAbstractView : public berry::QtViewPart { public: /** * Creates smartpointer typedefs */ berryObjectMacro(QmitkAbstractView); /** * Nothing to do in the standard ctor. Initiliaze your GUI in CreateQtPartControl(QWidget*) * \see berry::QtViewPart::CreateQtPartControl(QWidget*) */ QmitkAbstractView(); /** * Disconnects all standard event listeners */ ~QmitkAbstractView() override; protected: /** * Informs other parts of the workbench that node is selected via the blueberry selection service. * * \note This method should not be used if you have set your own selection provider via * SetSelectionProvider() or your own QItemSelectionModel via GetDataNodeSelectionModel(). */ void FireNodeSelected(mitk::DataNode::Pointer node); /** * Informs other parts of the workbench that the nodes are selected via the blueberry selection service. * * \note This method should not be used if you have set your own selection provider via * SetSelectionProvider() or your own QItemSelectionModel via GetDataNodeSelectionModel(). */ virtual void FireNodesSelected(const QList& nodes); /** * \return The selection of the currently active part of the workbench or an empty list * if there is no selection or if it is empty. * * \see IsCurrentSelectionValid */ QList GetCurrentSelection() const; /** * Queries the state of the current selection. * * \return If the current selection is nullptr, this method returns * false and true otherwise. */ bool IsCurrentSelectionValid() const; /** * Returns the current selection made in the datamanager bundle or an empty list * if there is no selection or if it is empty. * * \see IsDataManagerSelectionValid */ QList GetDataManagerSelection() const; /** * Queries the state of the current selection of the data manager view. * * \return If the current data manager selection is nullptr, this method returns * false and true otherwise. */ bool IsDataManagerSelectionValid() const; /** * Sets the selection of the data manager view if available. * * \param selection The new selection for the data manager. * \param flags The Qt selection flags for controlling the way how the selection is updated. */ void SetDataManagerSelection(const berry::ISelection::ConstPointer& selection, QItemSelectionModel::SelectionFlags flags = QItemSelectionModel::ClearAndSelect) const; /** * Takes the current selection and sets it on the data manager. Only matching nodes in the * data manager view will be selected. */ void SynchronizeDataManagerSelection() const; /** * Returns the Preferences object for this View. * Important: When refering to this preferences, e.g. in a PreferencePage: The ID * for this preferences object is "/", e.g. "/org.mitk.views.datamanager" */ berry::IPreferences::Pointer GetPreferences() const; /** * Returns a reference to the currently active DataStorage. */ mitk::IDataStorageReference::Pointer GetDataStorageReference() const; /** * Returns the currently active DataStorage. */ mitk::DataStorage::Pointer GetDataStorage() const; /** * Returns the currently active mitk::IRenderWindowPart. * * \param strategies Strategies for returning a mitk::IRenderWindowPart instance if there * is currently no active one. * \return The active mitk::IRenderWindowPart. */ mitk::IRenderWindowPart* GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategies strategies = mitk::WorkbenchUtil::NONE) const; /** * Request an update of all render windows of the currently active IRenderWindowPart. * - * \param requestType Specifies the type of render windows for which an update - * will be requested. + * \param requestType Specifies the type of render windows for which an update will be requested. */ void RequestRenderWindowUpdate(mitk::RenderingManager::RequestType requestType = mitk::RenderingManager::REQUEST_UPDATE_ALL); + /** + * Initialize the specified render windows to the given reference geometry. + * + * \param referenceGeometry The reference geometry which is used for updating the current world geometry + * \param requestType Specifies the type of render windows for which an update will be requested + * \param resetCamera If true, the camera and crosshair will be reset to the default view (centered, no zoom). + * If false, the current crosshair position will be stored and reset after the views have been + * initialized. The camera zoom will be kept after the view initialization. + */ + void InitializeRenderWindows(const mitk::TimeGeometry *referenceGeometry, + mitk::RenderingManager::RequestType requestType, + bool resetCamera); + /** * Outputs an error message to the console and displays a message box containing * the exception description. * \param e the exception which should be handled * \param parent * \param showDialog controls, whether additionally a message box should be * displayed to inform the user that something went wrong */ void HandleException( std::exception& e, QWidget* parent = nullptr, bool showDialog = true ) const; /** * Calls HandleException ( std::exception&, QWidget*, bool ) internally * \see HandleException ( std::exception&, QWidget*, bool ) */ void HandleException( const char* str, QWidget* parent = nullptr, bool showDialog = true ) const; /** * Convenient method to set and reset a wait cursor ("hourglass") */ void WaitCursorOn(); /** * Convenient method to restore the standard cursor */ void WaitCursorOff(); /** * Convenient method to set and reset a busy cursor */ void BusyCursorOn(); /** * Convenient method to restore the standard cursor */ void BusyCursorOff(); /** * Convenient method to restore the standard cursor */ void RestoreOverrideCursor(); private: /** * Reimplement this method to set a custom selection provider. This method is * called once after CreateQtPartControl(). * * The default implementation registers a QmitkDataNodeSelectionProvider with * a QItemSelectionModel returned by GetDataNodeSelectionModel(). */ virtual void SetSelectionProvider(); /** * Reimplement this method to supply a custom Qt selection model. The custom * model will be used with the default selection provider QmitkDataNodeSelectionProvider * to inform the MITK Workbench about selection changes. * * If you reimplement this method, the methods FireNodeSelected() and FireNodesSelected() * will have no effect. Use your custom selection model to notify the MITK Workbench * about selection changes. * * The Qt item model used with the custom selection model must return mitk::DataNode::Pointer * objects for model indexes when the role is QmitkDataNodeRole. */ virtual QItemSelectionModel* GetDataNodeSelectionModel() const; /** * Called when the selection in the workbench changed. * May be reimplemented by deriving classes. * * \param part The source part responsible for the selection change. * \param nodes A list of selected nodes. * * \see OnNullSelection */ virtual void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList &nodes); /** * Called when a nullptr selection occurs. * * \param part The source part responsible for the selection change. */ virtual void OnNullSelection(berry::IWorkbenchPart::Pointer part); /** * Called when the preferences object of this view changed. * May be reimplemented by deriving classes. * * \see GetPreferences() */ virtual void OnPreferencesChanged(const berry::IBerryPreferences*); /** * Called when a DataStorage Add event was thrown. May be reimplemented * by deriving classes. */ virtual void NodeAdded(const mitk::DataNode* node); /** * Called when a DataStorage Changed event was thrown. May be reimplemented * by deriving classes. */ virtual void NodeChanged(const mitk::DataNode* node); /** * Called when a DataStorage Remove event was thrown. May be reimplemented * by deriving classes. */ virtual void NodeRemoved(const mitk::DataNode* node); /** * Called when a DataStorage add *or* remove *or* change event from the currently active * data storage is thrown. * * May be reimplemented by deriving classes. */ virtual void DataStorageModified(); /** * Called when the currently active DataStorage changed. * May be reimplemented by deriving classes. * * \param dsRef A reference to the new active DataStorage. */ virtual void DataStorageChanged(mitk::IDataStorageReference::Pointer dsRef); /** * Creates a scroll area for this view and calls CreateQtPartControl then */ void CreatePartControl(QWidget* parent) override; /** * Called immediately after CreateQtPartControl(). * Here standard event listeners for a QmitkAbstractView are registered */ void AfterCreateQtPartControl(); private: friend class QmitkAbstractViewPrivate; friend class QmitkViewCoordinator; Q_DISABLE_COPY(QmitkAbstractView) const QScopedPointer d; }; #endif /*QMITKABSTRACTVIEW_H_*/ diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp index 51b28638a5..594695ef6f 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.cpp @@ -1,231 +1,241 @@ /*============================================================================ 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 "QmitkSingleNodeSelectionWidget.h" #include #include "mitkNodePredicateFunction.h" #include "mitkNodePredicateAnd.h" #include #include "QmitkNodeSelectionDialog.h" #include "QmitkNodeDetailsDialog.h" QmitkSingleNodeSelectionWidget::QmitkSingleNodeSelectionWidget(QWidget* parent) : QmitkAbstractNodeSelectionWidget(parent) - , m_AutoSelectNewNodes(false) + , m_AutoSelectNodes(false) { m_Controls.setupUi(this); m_Controls.btnSelect->installEventFilter(this); m_Controls.btnSelect->setVisible(true); m_Controls.btnClear->setVisible(false); m_Controls.btnClear->setIcon(berry::QtStyleManager::ThemeIcon(QStringLiteral(":/org.mitk.gui.qt.common/times.svg"))); this->UpdateInfo(); connect(m_Controls.btnClear, SIGNAL(clicked(bool)), this, SLOT(OnClearSelection())); } void QmitkSingleNodeSelectionWidget::ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) { if (newInternalSelection.empty()) { - if (m_AutoSelectNewNodes) + if (m_AutoSelectNodes) { auto autoSelectedNode = this->DetermineAutoSelectNode(oldInternalSelection); if (autoSelectedNode.IsNotNull()) { newInternalSelection.append(autoSelectedNode); } } } else if (newInternalSelection.size()>1) { //this widget only allows one internal selected node. newInternalSelection = { newInternalSelection.front() }; } } void QmitkSingleNodeSelectionWidget::OnClearSelection() { if (m_IsOptional) { this->SetCurrentSelection({}); } this->UpdateInfo(); } mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::GetSelectedNode() const { mitk::DataNode::Pointer result; auto selection = GetCurrentInternalSelection(); if (!selection.empty()) { result = selection.front(); } return result; } bool QmitkSingleNodeSelectionWidget::eventFilter(QObject *obj, QEvent *ev) { if (obj == m_Controls.btnSelect) { if (ev->type() == QEvent::MouseButtonRelease) { auto mouseEv = dynamic_cast(ev); if (!mouseEv) { return false; } if (mouseEv->button() == Qt::LeftButton) { if (this->isEnabled()) { this->EditSelection(); return true; } } else { auto selection = this->CompileEmitSelection(); if (!selection.empty()) { QmitkNodeDetailsDialog infoDialog(selection, this); infoDialog.exec(); return true; } } } } return false; } void QmitkSingleNodeSelectionWidget::EditSelection() { QmitkNodeSelectionDialog* dialog = new QmitkNodeSelectionDialog(this, m_PopUpTitel, m_PopUpHint); dialog->SetDataStorage(m_DataStorage.Lock()); dialog->SetNodePredicate(m_NodePredicate); dialog->SetCurrentSelection(this->GetCurrentInternalSelection()); dialog->SetSelectOnlyVisibleNodes(m_SelectOnlyVisibleNodes); dialog->SetSelectionMode(QAbstractItemView::SingleSelection); m_Controls.btnSelect->setChecked(true); if (dialog->exec()) { this->HandleChangeOfInternalSelection(dialog->GetSelectedNodes()); } m_Controls.btnSelect->setChecked(false); delete dialog; } void QmitkSingleNodeSelectionWidget::UpdateInfo() { if (this->GetSelectedNode().IsNull()) { if (m_IsOptional) { m_Controls.btnSelect->SetNodeInfo(m_EmptyInfo); } else { m_Controls.btnSelect->SetNodeInfo(m_InvalidInfo); } m_Controls.btnSelect->SetSelectionIsOptional(m_IsOptional); m_Controls.btnClear->setVisible(false); } else { m_Controls.btnClear->setVisible(m_IsOptional); } m_Controls.btnSelect->SetSelectedNode(this->GetSelectedNode()); } +void QmitkSingleNodeSelectionWidget::SetCurrentSelectedNode(mitk::DataNode* selectedNode) +{ + NodeList selection; + if (selectedNode) + { + selection.append(selectedNode); + } + this->SetCurrentSelection(selection); +} + +void QmitkSingleNodeSelectionWidget::OnDataStorageChanged() +{ + this->AutoSelectNodes(); +} + +void QmitkSingleNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* /*node*/) +{ + this->AutoSelectNodes(); +} + +bool QmitkSingleNodeSelectionWidget::GetAutoSelectNewNodes() const +{ + return m_AutoSelectNodes; +} + +void QmitkSingleNodeSelectionWidget::SetAutoSelectNewNodes(bool autoSelect) +{ + m_AutoSelectNodes = autoSelect; + this->AutoSelectNodes(); +} + +void QmitkSingleNodeSelectionWidget::AutoSelectNodes() +{ + if (this->GetSelectedNode().IsNull() && m_AutoSelectNodes) + { + auto autoNode = this->DetermineAutoSelectNode(); + + if (autoNode.IsNotNull()) + { + this->HandleChangeOfInternalSelection({ autoNode }); + } + } +} + mitk::DataNode::Pointer QmitkSingleNodeSelectionWidget::DetermineAutoSelectNode(const NodeList& ignoreNodes) { mitk::DataNode::Pointer result; auto storage = m_DataStorage.Lock(); if (storage.IsNotNull()) { auto ignoreCheck = [ignoreNodes](const mitk::DataNode * node) { bool result = true; for (const auto& ignoreNode : ignoreNodes) { if (node == ignoreNode) { result = false; break; } } return result; }; mitk::NodePredicateFunction::Pointer isNotIgnoredNode = mitk::NodePredicateFunction::New(ignoreCheck); mitk::NodePredicateBase::Pointer predicate = isNotIgnoredNode.GetPointer(); if (m_NodePredicate.IsNotNull()) { predicate = mitk::NodePredicateAnd::New(m_NodePredicate.GetPointer(), predicate.GetPointer()).GetPointer(); } result = storage->GetNode(predicate); } return result; } - -void QmitkSingleNodeSelectionWidget::SetCurrentSelectedNode(mitk::DataNode* selectedNode) -{ - NodeList selection; - if (selectedNode) - { - selection.append(selectedNode); - } - this->SetCurrentSelection(selection); -} - -void QmitkSingleNodeSelectionWidget::OnNodeAddedToStorage(const mitk::DataNode* /*node*/) -{ - if (this->GetSelectedNode().IsNull() && m_AutoSelectNewNodes) - { - auto autoNode = this->DetermineAutoSelectNode(); - - if (autoNode.IsNotNull()) - { - this->HandleChangeOfInternalSelection({ autoNode }); - } - } -} - -bool QmitkSingleNodeSelectionWidget::GetAutoSelectNewNodes() const -{ - return m_AutoSelectNewNodes; -} - -void QmitkSingleNodeSelectionWidget::SetAutoSelectNewNodes(bool autoSelect) -{ - m_AutoSelectNewNodes = autoSelect; - this->OnNodeAddedToStorage(nullptr); -} diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h index ed0edc443b..52ab4d7bb6 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkSingleNodeSelectionWidget.h @@ -1,94 +1,97 @@ /*============================================================================ 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 QMITK_SINGLE_NODE_SELECTION_WIDGET_H #define QMITK_SINGLE_NODE_SELECTION_WIDGET_H #include #include #include #include "org_mitk_gui_qt_common_Export.h" #include "ui_QmitkSingleNodeSelectionWidget.h" #include #include class QmitkAbstractDataStorageModel; /** * @class QmitkSingleNodeSelectionWidget * @brief Widget that represents a node selection of (max) one node. It acts like a button. Clicking on it * allows to change the selection. * * @remark This class provides a public function 'SetAutoSelectNewNodes' that can be used to enable * the auto selection mode (default is false). * The user of this class calling this function has to make sure that the base-class Q_SIGNAL * 'CurrentSelectionChanged', which will be emitted by this function, is already * connected to a receiving slot, if the initial valid auto selection should not get lost. */ class MITK_QT_COMMON QmitkSingleNodeSelectionWidget : public QmitkAbstractNodeSelectionWidget { Q_OBJECT public: explicit QmitkSingleNodeSelectionWidget(QWidget* parent = nullptr); mitk::DataNode::Pointer GetSelectedNode() const; bool GetAutoSelectNewNodes() const; using NodeList = QmitkAbstractNodeSelectionWidget::NodeList; public Q_SLOTS: void SetCurrentSelectedNode(mitk::DataNode* selectedNode); /** * Sets the auto selection mode (default is false). * If auto select is true and the following conditions are fullfilled, the widget will * select a node automatically from the data storage: * - a data storage is set * - data storage contains at least one node that matches the given predicate * - no selection is set * * @remark Enabling the auto selection mode by calling 'SetAutoSelectNewNodes(true)' * will directly emit a 'QmitkSingleNodeSelectionWidget::CurrentSelectionChanged' Q_SIGNAL * if a valid auto selection was made. * If this initial emission should not get lost, auto selection mode needs to be enabled after this * selection widget has been connected via the 'QmitkSingleNodeSelectionWidget::CurrentSelectionChanged' * Q_SIGNAL to a receiving function. */ void SetAutoSelectNewNodes(bool autoSelect); protected Q_SLOTS: virtual void OnClearSelection(); protected: void ReviseSelectionChanged(const NodeList& oldInternalSelection, NodeList& newInternalSelection) override; bool eventFilter(QObject *obj, QEvent *ev) override; void EditSelection(); void UpdateInfo() override; + void OnDataStorageChanged() override; void OnNodeAddedToStorage(const mitk::DataNode* node) override; + void AutoSelectNodes(); + /** Helper function that gets a suitable auto selected node from the datastorage that fits to the predicate settings. @param ignoreNodes You may pass a list of nodes that must not be choosen as auto selected node. */ mitk::DataNode::Pointer DetermineAutoSelectNode(const NodeList& ignoreNodes = {}); /** See documentation of SetAutoSelectNewNodes for details*/ - bool m_AutoSelectNewNodes; + bool m_AutoSelectNodes; Ui_QmitkSingleNodeSelectionWidget m_Controls; }; #endif // QMITK_SINGLE_NODE_SELECTION_WIDGET_H diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/Common/QmitkDataSelectionWidget.cpp b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/Common/QmitkDataSelectionWidget.cpp index 4e32b042d5..1f41da6364 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/Common/QmitkDataSelectionWidget.cpp +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/Common/QmitkDataSelectionWidget.cpp @@ -1,249 +1,249 @@ /*============================================================================ 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 "QmitkDataSelectionWidget.h" #include "internal/mitkPluginActivator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static mitk::NodePredicateBase::Pointer CreatePredicate(QmitkDataSelectionWidget::PredicateType predicateType) { auto nonHelperObject = mitk::NodePredicateNot::New(mitk::NodePredicateOr::New( mitk::NodePredicateProperty::New("helper object"), mitk::NodePredicateProperty::New("hidden object"))); auto imageType = mitk::TNodePredicateDataType::New(); auto contourModelType = mitk::TNodePredicateDataType::New(); auto contourModelSetType = mitk::TNodePredicateDataType::New(); auto segmentationPredicate = mitk::TNodePredicateDataType::New(); auto maskPredicate = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isImage = mitk::TNodePredicateDataType::New(); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(isImage); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); auto imagePredicate = mitk::NodePredicateAnd::New(); imagePredicate->AddPredicate(validImages); imagePredicate->AddPredicate(mitk::NodePredicateNot::New(segmentationPredicate)); imagePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)))); auto surfacePredicate = mitk::TNodePredicateDataType::New(); mitk::NodePredicateBase::Pointer result; switch(predicateType) { case QmitkDataSelectionWidget::ImagePredicate: result = imagePredicate.GetPointer(); break; case QmitkDataSelectionWidget::SegmentationPredicate: result = segmentationPredicate.GetPointer(); break; case QmitkDataSelectionWidget::SurfacePredicate: result = surfacePredicate.GetPointer(); break; case QmitkDataSelectionWidget::ImageAndSegmentationPredicate: result = imageType.GetPointer(); break; case QmitkDataSelectionWidget::ContourModelPredicate: result = mitk::NodePredicateOr::New( contourModelSetType, contourModelSetType).GetPointer(); break; case QmitkDataSelectionWidget::MaskPredicate: result = maskPredicate.GetPointer(); break; default: assert(false && "Unknown predefined predicate!"); return nullptr; } return mitk::NodePredicateAnd::New(result, nonHelperObject).GetPointer(); } QmitkDataSelectionWidget::QmitkDataSelectionWidget(QWidget* parent) : QWidget(parent) { m_Controls.setupUi(this); m_Controls.helpLabel->hide(); } QmitkDataSelectionWidget::~QmitkDataSelectionWidget() { } unsigned int QmitkDataSelectionWidget::AddDataSelection(QmitkDataSelectionWidget::PredicateType predicate) { QString hint = "Select node"; switch (predicate) { case QmitkDataSelectionWidget::ImagePredicate: hint = "Select an image"; break; case QmitkDataSelectionWidget::MaskPredicate: hint = "Select a binary mask"; break; case QmitkDataSelectionWidget::SegmentationPredicate: hint = "Select an ML segmentation"; break; case QmitkDataSelectionWidget::SurfacePredicate: hint = "Select a surface"; break; case QmitkDataSelectionWidget::ImageAndSegmentationPredicate: hint = "Select an image or segmentation"; break; case QmitkDataSelectionWidget::ContourModelPredicate: hint = "Select a contour model"; break; } return this->AddDataSelection("", hint, hint, "", predicate); } unsigned int QmitkDataSelectionWidget::AddDataSelection(mitk::NodePredicateBase* predicate) { return this->AddDataSelection("", "Select a node", "Select a node", "", predicate); } unsigned int QmitkDataSelectionWidget::AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, QmitkDataSelectionWidget::PredicateType predicate) { return this->AddDataSelection(labelText, info, popupHint, popupTitel, CreatePredicate(predicate)); } unsigned int QmitkDataSelectionWidget::AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, mitk::NodePredicateBase* predicate) { int row = m_Controls.gridLayout->rowCount(); if (!labelText.isEmpty()) { QLabel* label = new QLabel(labelText, m_Controls.dataSelectionWidget); label->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); m_Controls.gridLayout->addWidget(label, row, 0); } QmitkSingleNodeSelectionWidget* nodeSelection = new QmitkSingleNodeSelectionWidget(m_Controls.dataSelectionWidget); nodeSelection->SetSelectionIsOptional(false); - nodeSelection->SetAutoSelectNewNodes(false); nodeSelection->SetInvalidInfo(info); nodeSelection->SetPopUpTitel(popupTitel); nodeSelection->SetPopUpHint(popupHint); nodeSelection->SetDataStorage(this->GetDataStorage()); nodeSelection->SetNodePredicate(predicate); + nodeSelection->SetAutoSelectNewNodes(true); nodeSelection->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); nodeSelection->setMinimumSize(0, 40); connect(nodeSelection, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkDataSelectionWidget::OnSelectionChanged); m_Controls.gridLayout->addWidget(nodeSelection, row, 1); m_NodeSelectionWidgets.push_back(nodeSelection); return static_cast(m_NodeSelectionWidgets.size() - 1); } mitk::DataStorage::Pointer QmitkDataSelectionWidget::GetDataStorage() const { ctkServiceReference ref = mitk::PluginActivator::getContext()->getServiceReference(); assert(ref == true); mitk::IDataStorageService* service = mitk::PluginActivator::getContext()->getService(ref); assert(service); return service->GetDefaultDataStorage()->GetDataStorage(); } mitk::DataNode::Pointer QmitkDataSelectionWidget::GetSelection(unsigned int index) { assert(index < m_NodeSelectionWidgets.size()); return m_NodeSelectionWidgets[index]->GetSelectedNode(); } void QmitkDataSelectionWidget::SetPredicate(unsigned int index, PredicateType predicate) { this->SetPredicate(index, CreatePredicate(predicate)); } void QmitkDataSelectionWidget::SetPredicate(unsigned int index, mitk::NodePredicateBase* predicate) { assert(index < m_NodeSelectionWidgets.size()); m_NodeSelectionWidgets[index]->SetNodePredicate(predicate); } void QmitkDataSelectionWidget::SetHelpText(const QString& text) { if (!text.isEmpty()) { m_Controls.helpLabel->setText(text); if (!m_Controls.helpLabel->isVisible()) m_Controls.helpLabel->show(); } else { m_Controls.helpLabel->hide(); } } void QmitkDataSelectionWidget::OnSelectionChanged(QList selection) { std::vector::iterator it = std::find(m_NodeSelectionWidgets.begin(), m_NodeSelectionWidgets.end(), sender()); assert(it != m_NodeSelectionWidgets.end()); const mitk::DataNode* result = nullptr; if (!selection.empty()) { result = selection.front(); } emit SelectionChanged(std::distance(m_NodeSelectionWidgets.begin(), it), result); } diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp index 83489b5ac7..7287bb2cef 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.cpp @@ -1,1093 +1,1093 @@ /*============================================================================ 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 "QmitkMultiLabelSegmentationView.h" // blueberry #include #include // mitk #include "mitkApplicationCursor.h" #include "mitkLabelSetImage.h" #include "mitkStatusBar.h" #include "mitkToolManagerProvider.h" #include "mitkInteractionEventObserver.h" #include "mitkPlanePositionManager.h" #include "mitkPluginActivator.h" #include "mitkSegTool2D.h" #include "mitkImageTimeSelector.h" #include "mitkNodePredicateSubGeometry.h" // Qmitk #include #include "QmitkNewSegmentationDialog.h" #include "QmitkRenderWindow.h" #include "QmitkSegmentationOrganNamesHandling.cpp" #include "QmitkCreateMultiLabelPresetAction.h" #include "QmitkLoadMultiLabelPresetAction.h" // us #include #include #include #include #include // Qt #include #include #include #include #include #include #include const std::string QmitkMultiLabelSegmentationView::VIEW_ID = "org.mitk.views.multilabelsegmentation"; QmitkMultiLabelSegmentationView::QmitkMultiLabelSegmentationView() : m_Parent(nullptr), m_IRenderWindowPart(nullptr), m_ToolManager(nullptr), m_ReferenceNode(nullptr), m_WorkingNode(nullptr), m_AutoSelectionEnabled(false), m_MouseCursorSet(false) { m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType::New()); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::NodePredicateProperty::Pointer isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateAnd::Pointer isMask = mitk::NodePredicateAnd::New(isBinary, isImage); mitk::NodePredicateDataType::Pointer isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); mitk::NodePredicateDataType::Pointer isDti = mitk::NodePredicateDataType::New("TensorImage"); mitk::NodePredicateDataType::Pointer isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); mitk::NodePredicateOr::Pointer validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(isMask)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkMultiLabelSegmentationView::~QmitkMultiLabelSegmentationView() { // Loose LabelSetConnections OnLooseLabelSetConnection(); } void QmitkMultiLabelSegmentationView::CreateQtPartControl(QWidget *parent) { // setup the basic GUI of this view m_Parent = parent; m_Controls.setupUi(parent); m_Controls.m_tbSavePreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls.m_tbLoadPreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); // *------------------------ // * Shortcuts // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence("CTRL+H"), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkMultiLabelSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence("CTRL+L"), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkMultiLabelSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls.m_ReferenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls.m_ReferenceNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.m_ReferenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls.m_ReferenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls.m_ReferenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls.m_WorkingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls.m_WorkingNodeSelector->SetDataStorage(this->GetDataStorage()); m_Controls.m_WorkingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls.m_WorkingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls.m_WorkingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls.m_ReferenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this,&QmitkMultiLabelSegmentationView::OnReferenceSelectionChanged); connect(m_Controls.m_WorkingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this,&QmitkMultiLabelSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * ToolManager // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(mitk::ToolManagerProvider::MULTILABEL_SEGMENTATION); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); m_Controls.m_ManualToolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls.m_ManualToolSelectionBox3D->SetToolManager(*m_ToolManager); // *------------------------ // * LabelSetWidget // *------------------------ m_Controls.m_LabelSetWidget->SetDataStorage(this->GetDataStorage()); m_Controls.m_LabelSetWidget->SetOrganColors(mitk::OrganNamesHandling::GetDefaultOrganColorString()); m_Controls.m_LabelSetWidget->hide(); // *------------------------ // * Interpolation // *------------------------ m_Controls.m_SurfaceBasedInterpolatorWidget->SetDataStorage(*(this->GetDataStorage())); m_Controls.m_SliceBasedInterpolatorWidget->SetDataStorage(*(this->GetDataStorage())); connect(m_Controls.m_cbInterpolation, SIGNAL(activated(int)), this, SLOT(OnInterpolationSelectionChanged(int))); m_Controls.m_cbInterpolation->setCurrentIndex(0); m_Controls.m_swInterpolation->hide(); m_Controls.m_gbInterpolation->hide(); // See T27436 QString segTools2D = tr("Add Subtract Fill Erase Paint Wipe 'Region Growing' FastMarching2D 'Live Wire'"); QString segTools3D = tr("Threshold 'Two Thresholds' 'Auto Threshold' 'Multiple Otsu'"); std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // *------------------------ // * ToolSelection 2D // *------------------------ m_Controls.m_ManualToolSelectionBox2D->SetGenerateAccelerators(true); m_Controls.m_ManualToolSelectionBox2D->SetToolGUIArea(m_Controls.m_ManualToolGUIContainer2D); m_Controls.m_ManualToolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); // todo: "Correction // 'Live Wire'" m_Controls.m_ManualToolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls.m_ManualToolSelectionBox2D, SIGNAL(ToolSelected(int)), this, SLOT(OnManualTool2DSelected(int))); // *------------------------ // * ToolSelection 3D // *------------------------ m_Controls.m_ManualToolSelectionBox3D->SetGenerateAccelerators(true); m_Controls.m_ManualToolSelectionBox3D->SetToolGUIArea(m_Controls.m_ManualToolGUIContainer3D); m_Controls.m_ManualToolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); // todo add : FastMarching3D RegionGrowing Watershed m_Controls.m_ManualToolSelectionBox3D->SetLayoutColumns(2); m_Controls.m_ManualToolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); // *------------------------* // * Connect PushButtons (pb) // *------------------------* connect(m_Controls.m_pbNewLabel, SIGNAL(clicked()), this, SLOT(OnNewLabel())); connect(m_Controls.m_tbSavePreset, SIGNAL(clicked()), this, SLOT(OnSavePreset())); connect(m_Controls.m_tbLoadPreset, SIGNAL(clicked()), this, SLOT(OnLoadPreset())); connect(m_Controls.m_pbNewSegmentationSession, SIGNAL(clicked()), this, SLOT(OnNewSegmentationSession())); connect(m_Controls.m_pbShowLabelTable, SIGNAL(toggled(bool)), this, SLOT(OnShowLabelTable(bool))); // *------------------------* // * Connect LabelSetWidget // *------------------------* connect(m_Controls.m_LabelSetWidget, SIGNAL(goToLabel(const mitk::Point3D &)), this, SLOT(OnGoToLabel(const mitk::Point3D &))); connect(m_Controls.m_LabelSetWidget, SIGNAL(resetView()), this, SLOT(OnResetView())); // *------------------------* // * DATA SLECTION WIDGET // *------------------------* m_IRenderWindowPart = this->GetRenderWindowPart(); if (m_IRenderWindowPart) { QList controllers; controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls.m_SliceBasedInterpolatorWidget->SetSliceNavigationControllers(controllers); } // this->InitializeListeners(); connect(m_Controls.m_btAddLayer, SIGNAL(clicked()), this, SLOT(OnAddLayer())); connect(m_Controls.m_btDeleteLayer, SIGNAL(clicked()), this, SLOT(OnDeleteLayer())); connect(m_Controls.m_btPreviousLayer, SIGNAL(clicked()), this, SLOT(OnPreviousLayer())); connect(m_Controls.m_btNextLayer, SIGNAL(clicked()), this, SLOT(OnNextLayer())); connect(m_Controls.m_btLockExterior, SIGNAL(toggled(bool)), this, SLOT(OnLockExteriorToggled(bool))); connect(m_Controls.m_cbActiveLayer, SIGNAL(currentIndexChanged(int)), this, SLOT(OnChangeLayer(int))); m_Controls.m_btAddLayer->setEnabled(false); m_Controls.m_btDeleteLayer->setEnabled(false); m_Controls.m_btNextLayer->setEnabled(false); m_Controls.m_btPreviousLayer->setEnabled(false); m_Controls.m_cbActiveLayer->setEnabled(false); m_Controls.m_pbNewLabel->setEnabled(false); m_Controls.m_btLockExterior->setEnabled(false); m_Controls.m_tbSavePreset->setEnabled(false); m_Controls.m_tbLoadPreset->setEnabled(false); m_Controls.m_pbShowLabelTable->setEnabled(false); // Make sure the GUI notices if appropriate data is already present on creation m_Controls.m_ReferenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls.m_WorkingNodeSelector->SetAutoSelectNewNodes(true); } void QmitkMultiLabelSegmentationView::Activated() { m_ToolManager->SetReferenceData(m_Controls.m_ReferenceNodeSelector->GetSelectedNode()); m_ToolManager->SetWorkingData(m_Controls.m_WorkingNodeSelector->GetSelectedNode()); } void QmitkMultiLabelSegmentationView::Deactivated() { // Not yet implemented } void QmitkMultiLabelSegmentationView::Visible() { // Not yet implemented } void QmitkMultiLabelSegmentationView::Hidden() { // Not yet implemented } int QmitkMultiLabelSegmentationView::GetSizeFlags(bool width) { if (!width) { return berry::Constants::MIN | berry::Constants::MAX | berry::Constants::FILL; } else { return 0; } } int QmitkMultiLabelSegmentationView::ComputePreferredSize(bool width, int /*availableParallel*/, int /*availablePerpendicular*/, int preferredResult) { if (width == false) { return 100; } else { return preferredResult; } } /************************************************************************/ /* protected slots */ /************************************************************************/ void QmitkMultiLabelSegmentationView::OnVisibilityShortcutActivated() { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); bool isVisible = false; workingNode->GetBoolProperty("visible", isVisible); workingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::OnLabelToggleShortcutActivated() { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkMultiLabelSegmentationView::OnNewLabel() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { QMessageBox::information( m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); return; } - if (nullptr == m_ReferenceNode->GetData()) + mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); + if (referenceImage.IsNull()) { QMessageBox::information( - m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); + m_Parent, "New Segmentation Session", "Reference data needs to be an image in order to create a new segmentation."); return; } mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); if (!workingNode) { QMessageBox::information( m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); return; } mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { QMessageBox::information( m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); return; } QmitkNewSegmentationDialog* dialog = new QmitkNewSegmentationDialog(m_Parent); dialog->SetSuggestionList(mitk::OrganNamesHandling::GetDefaultOrganColorString()); dialog->setWindowTitle("New Label"); int dialogReturnValue = dialog->exec(); if (dialogReturnValue == QDialog::Rejected) { return; } QString segName = dialog->GetSegmentationName(); if (segName.isEmpty()) { segName = "Unnamed"; } workingImage->GetActiveLabelSet()->AddLabel(segName.toStdString(), dialog->GetColor()); UpdateControls(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); - this->ReinitializeViews(); + this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } void QmitkMultiLabelSegmentationView::OnSavePreset() { QmitkAbstractNodeSelectionWidget::NodeList nodes; nodes.append(m_WorkingNode); QmitkCreateMultiLabelPresetAction action; action.Run(nodes); } void QmitkMultiLabelSegmentationView::OnLoadPreset() { QmitkAbstractNodeSelectionWidget::NodeList nodes; nodes.append(m_WorkingNode); QmitkLoadMultiLabelPresetAction action; action.Run(nodes); } void QmitkMultiLabelSegmentationView::OnShowLabelTable(bool value) { if (value) m_Controls.m_LabelSetWidget->show(); else m_Controls.m_LabelSetWidget->hide(); } void QmitkMultiLabelSegmentationView::OnNewSegmentationSession() { mitk::DataNode *referenceNode = m_Controls.m_ReferenceNodeSelector->GetSelectedNode(); if (!referenceNode) { QMessageBox::information( m_Parent, "New Segmentation Session", "Please load and select a patient image before starting some action."); return; } m_ToolManager->ActivateTool(-1); mitk::Image::ConstPointer referenceImage = dynamic_cast(referenceNode->GetData()); assert(referenceImage); const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); unsigned int imageTimeStep = 0; if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) { imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { auto result = QMessageBox::question(m_Parent, tr("Create a static or dynamic segmentation?"), tr("The patient image has multiple time steps.\n\nDo you want to create a static segmentation that is identical for all time steps or do you want to create a dynamic segmentation to segment individual time steps?"), tr("Create static segmentation"), tr("Create dynamic segmentation"), QString(), 0, 0); if (result == 0) { auto selector = mitk::ImageTimeSelector::New(); selector->SetInput(referenceImage); selector->SetTimeNr(0); selector->Update(); const auto refTimeGeometry = referenceImage->GetTimeGeometry(); auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); mitk::Image::Pointer newImage = selector->GetOutput(); newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); newImage->SetTimeGeometry(newTimeGeometry); segTemplateImage = newImage; } } QString newName = QString::fromStdString(referenceNode->GetName()); newName.append("-labels"); bool ok = false; newName = QInputDialog::getText(m_Parent, "New Segmentation Session", "New name:", QLineEdit::Normal, newName, &ok); if (!ok) { return; } this->WaitCursorOn(); mitk::LabelSetImage::Pointer workingImage = mitk::LabelSetImage::New(); try { workingImage->Initialize(segTemplateImage); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(m_Parent, "New Segmentation Session", "Could not create a new segmentation session.\n"); return; } this->WaitCursorOff(); mitk::DataNode::Pointer workingNode = mitk::DataNode::New(); workingNode->SetData(workingImage); workingNode->SetName(newName.toStdString()); workingImage->GetExteriorLabel()->SetProperty("name.parent", mitk::StringProperty::New(referenceNode->GetName().c_str())); workingImage->GetExteriorLabel()->SetProperty("name.image", mitk::StringProperty::New(newName.toStdString().c_str())); if (!GetDataStorage()->Exists(workingNode)) { GetDataStorage()->Add(workingNode, referenceNode); } m_Controls.m_WorkingNodeSelector->SetCurrentSelectedNode(workingNode); OnNewLabel(); } void QmitkMultiLabelSegmentationView::OnGoToLabel(const mitk::Point3D& pos) { if (m_IRenderWindowPart) m_IRenderWindowPart->SetSelectedPosition(pos); } void QmitkMultiLabelSegmentationView::OnResetView() { if (m_IRenderWindowPart) m_IRenderWindowPart->ForceImmediateUpdate(); } void QmitkMultiLabelSegmentationView::OnAddLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); QString question = "Do you really want to add a layer to the current segmentation session?"; QMessageBox::StandardButton answerButton = QMessageBox::question( m_Controls.m_LabelSetWidget, "Add layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) return; try { WaitCursorOn(); workingImage->AddLayer(); WaitCursorOff(); } catch ( mitk::Exception& e ) { WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information( m_Controls.m_LabelSetWidget, "Add Layer", "Could not add a new layer. See error log for details.\n"); return; } OnNewLabel(); } void QmitkMultiLabelSegmentationView::OnDeleteLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); if (workingImage->GetNumberOfLayers() < 2) return; QString question = "Do you really want to delete the current layer?"; QMessageBox::StandardButton answerButton = QMessageBox::question( m_Controls.m_LabelSetWidget, "Delete layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton != QMessageBox::Yes) { return; } try { this->WaitCursorOn(); workingImage->RemoveLayer(); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(m_Controls.m_LabelSetWidget, "Delete Layer", "Could not delete the currently active layer. See error log for details.\n"); return; } UpdateControls(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); } void QmitkMultiLabelSegmentationView::OnPreviousLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); OnChangeLayer(workingImage->GetActiveLayer() - 1); } void QmitkMultiLabelSegmentationView::OnNextLayer() { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); OnChangeLayer(workingImage->GetActiveLayer() + 1); } void QmitkMultiLabelSegmentationView::OnChangeLayer(int layer) { m_ToolManager->ActivateTool(-1); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); this->WaitCursorOn(); workingImage->SetActiveLayer(layer); this->WaitCursorOff(); UpdateControls(); m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); } void QmitkMultiLabelSegmentationView::OnDeactivateActiveTool() { m_ToolManager->ActivateTool(-1); } void QmitkMultiLabelSegmentationView::OnLockExteriorToggled(bool checked) { mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); assert(workingNode); mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); workingImage->GetLabel(0)->SetLocked(checked); } void QmitkMultiLabelSegmentationView::OnReferenceSelectionChanged(QList /*nodes*/) { m_ToolManager->ActivateTool(-1); auto refNode = m_Controls.m_ReferenceNodeSelector->GetSelectedNode(); m_ReferenceNode = refNode; m_ToolManager->SetReferenceData(m_ReferenceNode); if (m_ReferenceNode.IsNotNull()) { auto segPredicate = mitk::NodePredicateAnd::New(m_SegmentationPredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(refNode->GetData()->GetGeometry())); m_Controls.m_WorkingNodeSelector->SetNodePredicate(segPredicate); if (m_AutoSelectionEnabled) { // hide all image nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer patientNodes = GetDataStorage()->GetSubset(m_ReferencePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = patientNodes->begin(); iter != patientNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_ReferenceNode->SetVisibility(true); } UpdateControls(); } void QmitkMultiLabelSegmentationView::OnSegmentationSelectionChanged(QList /*nodes*/) { m_ToolManager->ActivateTool(-1); + if (m_ReferenceNode.IsNull()) + { + QMessageBox::information( + m_Parent, "Selected segmentation changed", "Please load and select a patient image before starting some action."); + return; + } + + mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); + if (referenceImage.IsNull()) + { + QMessageBox::information( + m_Parent, "Selected segmentation changed", "Reference data needs to be an image in order to create a new segmentation."); + return; + } + if (m_WorkingNode.IsNotNull()) OnLooseLabelSetConnection(); m_WorkingNode = m_Controls.m_WorkingNodeSelector->GetSelectedNode(); m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { OnEstablishLabelSetConnection(); if (m_AutoSelectionEnabled) { // hide all segmentation nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = segmentationNodes->begin(); iter != segmentationNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_WorkingNode->SetVisibility(true); } UpdateControls(); if (m_WorkingNode.IsNotNull()) { m_Controls.m_LabelSetWidget->ResetAllTableWidgetItems(); - this->ReinitializeViews(); + this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } } void QmitkMultiLabelSegmentationView::OnInterpolationSelectionChanged(int index) { if (index == 1) { m_Controls.m_SurfaceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false);//OnToggleWidgetActivation(false); m_Controls.m_swInterpolation->setCurrentIndex(0); m_Controls.m_swInterpolation->show(); } else if (index == 2) { m_Controls.m_SliceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_swInterpolation->setCurrentIndex(1); m_Controls.m_swInterpolation->show(); } else { m_Controls.m_SurfaceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_SliceBasedInterpolatorWidget->m_Controls.m_btStart->setChecked(false); m_Controls.m_swInterpolation->setCurrentIndex(2); m_Controls.m_swInterpolation->hide(); } } /************************************************************************/ /* protected */ /************************************************************************/ void QmitkMultiLabelSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { m_AutoSelectionEnabled = prefs->GetBool("auto selection", false); mitk::BoolProperty::Pointer drawOutline = mitk::BoolProperty::New(prefs->GetBool("draw outline", true)); mitk::LabelSetImage* labelSetImage; mitk::DataNode* segmentation; // iterate all segmentations (binary (single label) and LabelSetImages) mitk::NodePredicateProperty::Pointer isBinaryPredicate = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateOr::Pointer allSegmentationsPredicate = mitk::NodePredicateOr::New(isBinaryPredicate, m_SegmentationPredicate); mitk::DataStorage::SetOfObjects::ConstPointer allSegmentations = GetDataStorage()->GetSubset(allSegmentationsPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator it = allSegmentations->begin(); it != allSegmentations->end(); ++it) { segmentation = *it; labelSetImage = dynamic_cast(segmentation->GetData()); if (nullptr != labelSetImage) { // segmentation node is a multi label segmentation segmentation->SetProperty("labelset.contour.active", drawOutline); //segmentation->SetProperty("opacity", mitk::FloatProperty::New(drawOutline->GetValue() ? 1.0f : 0.3f)); // force render window update to show outline segmentation->GetData()->Modified(); } else if (nullptr != segmentation->GetData()) { // node is actually a 'single label' segmentation, // but its outline property can be set in the 'multi label' segmentation preference page as well bool isBinary = false; segmentation->GetBoolProperty("binary", isBinary); if (isBinary) { segmentation->SetProperty("outline binary", drawOutline); segmentation->SetProperty("outline width", mitk::FloatProperty::New(2.0)); //segmentation->SetProperty("opacity", mitk::FloatProperty::New(drawOutline->GetValue() ? 1.0f : 0.3f)); // force render window update to show outline segmentation->GetData()->Modified(); } } } } void QmitkMultiLabelSegmentationView::NodeRemoved(const mitk::DataNode *node) { bool isHelperObject(false); node->GetBoolProperty("helper object", isHelperObject); if (isHelperObject) { return; } if (m_ReferenceNode.IsNotNull() && dynamic_cast(node->GetData())) { // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext *context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService *service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; } } void QmitkMultiLabelSegmentationView::OnEstablishLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } mitk::LabelSetImage *workingImage = dynamic_cast(m_WorkingNode->GetData()); assert(workingImage); workingImage->GetActiveLabelSet()->AddLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent += mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent += mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkMultiLabelSegmentationView::UpdateControls); } void QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection() { if (m_WorkingNode.IsNull()) return; auto* workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) return; // data (type) was changed in-place, e.g. LabelSetImage -> Image // Reset LabelSetWidget Events workingImage->GetActiveLabelSet()->AddLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent -= mitk::MessageDelegate( m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1(m_Controls.m_LabelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkMultiLabelSegmentationView::UpdateControls); } void QmitkMultiLabelSegmentationView::SetFocus() { } void QmitkMultiLabelSegmentationView::UpdateControls() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasValidWorkingNode = workingNode != nullptr; m_Controls.m_pbNewLabel->setEnabled(false); m_Controls.m_gbInterpolation->setEnabled(false); m_Controls.m_SliceBasedInterpolatorWidget->setEnabled(false); m_Controls.m_SurfaceBasedInterpolatorWidget->setEnabled(false); m_Controls.m_LabelSetWidget->setEnabled(false); m_Controls.m_btAddLayer->setEnabled(false); m_Controls.m_btDeleteLayer->setEnabled(false); m_Controls.m_cbActiveLayer->setEnabled(false); m_Controls.m_btPreviousLayer->setEnabled(false); m_Controls.m_btNextLayer->setEnabled(false); m_Controls.m_btLockExterior->setChecked(false); m_Controls.m_btLockExterior->setEnabled(false); m_Controls.m_tbSavePreset->setEnabled(false); m_Controls.m_tbLoadPreset->setEnabled(false); m_Controls.m_pbShowLabelTable->setChecked(false); m_Controls.m_pbShowLabelTable->setEnabled(false); m_Controls.m_ManualToolSelectionBox3D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); m_Controls.m_ManualToolSelectionBox2D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); if (hasValidWorkingNode) { // TODO adapt tool manager so that this check is done there, e.g. convenience function mitk::LabelSetImage* workingImage = dynamic_cast(workingNode->GetData()); hasValidWorkingNode = workingImage != nullptr; if (hasValidWorkingNode) { m_Controls.m_pbNewLabel->setEnabled(true); m_Controls.m_btLockExterior->setEnabled(true); m_Controls.m_tbSavePreset->setEnabled(true); m_Controls.m_tbLoadPreset->setEnabled(true); m_Controls.m_pbShowLabelTable->setEnabled(true); m_Controls.m_gbInterpolation->setEnabled(true); m_Controls.m_SliceBasedInterpolatorWidget->setEnabled(true); m_Controls.m_SurfaceBasedInterpolatorWidget->setEnabled(true); m_Controls.m_LabelSetWidget->setEnabled(true); m_Controls.m_btAddLayer->setEnabled(true); int activeLayer = workingImage->GetActiveLayer(); int numberOfLayers = workingImage->GetNumberOfLayers(); m_Controls.m_cbActiveLayer->blockSignals(true); m_Controls.m_cbActiveLayer->clear(); for (unsigned int lidx = 0; lidx < workingImage->GetNumberOfLayers(); ++lidx) { m_Controls.m_cbActiveLayer->addItem(QString::number(lidx)); } m_Controls.m_cbActiveLayer->setCurrentIndex(activeLayer); m_Controls.m_cbActiveLayer->blockSignals(false); m_Controls.m_cbActiveLayer->setEnabled(numberOfLayers > 1); m_Controls.m_btDeleteLayer->setEnabled(numberOfLayers > 1); m_Controls.m_btPreviousLayer->setEnabled(activeLayer > 0); m_Controls.m_btNextLayer->setEnabled(activeLayer != numberOfLayers - 1); m_Controls.m_btLockExterior->setChecked(workingImage->GetLabel(0, activeLayer)->GetLocked()); m_Controls.m_pbShowLabelTable->setChecked(workingImage->GetNumberOfLabels() > 1 /*1st is exterior*/); //MLI TODO //m_Controls.m_ManualToolSelectionBox2D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithWorkingDataVisible); } } if (hasValidWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } this->RequestRenderWindowUpdate(mitk::RenderingManager::REQUEST_UPDATE_ALL); } void QmitkMultiLabelSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_IRenderWindowPart != renderWindowPart) { m_IRenderWindowPart = renderWindowPart; m_Parent->setEnabled(true); QList controllers; controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(m_IRenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls.m_SliceBasedInterpolatorWidget->SetSliceNavigationControllers(controllers); } } void QmitkMultiLabelSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_ToolManager->ActivateTool(-1); m_IRenderWindowPart = nullptr; m_Parent->setEnabled(false); } void QmitkMultiLabelSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkMultiLabelSegmentationView::SetMouseCursor(const us::ModuleResource resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) this->ResetMouseCursor(); if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkMultiLabelSegmentationView::InitializeListeners() { if (m_Interactor.IsNull()) { us::Module* module = us::GetModuleContext()->GetModule(); std::vector resources = module->FindResources("/", "*", true); for (std::vector::iterator iter = resources.begin(); iter != resources.end(); ++iter) { MITK_INFO << iter->GetResourcePath(); } m_Interactor = mitk::SegmentationInteractor::New(); if (!m_Interactor->LoadStateMachine("SegmentationInteraction.xml", module)) { MITK_WARN << "Error loading state machine"; } if (!m_Interactor->SetEventConfig("ConfigSegmentation.xml", module)) { MITK_WARN << "Error loading state machine configuration"; } // Register as listener via micro services us::ServiceProperties props; props["name"] = std::string("SegmentationInteraction"); m_ServiceRegistration = us::GetModuleContext()->RegisterService(m_Interactor.GetPointer(), props); } } - -void QmitkMultiLabelSegmentationView::ReinitializeViews() const -{ - if (m_ReferenceNode.IsNotNull() && nullptr != m_ReferenceNode->GetData()) - { - const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - unsigned int imageTimeStep = 0; - if (m_ReferenceNode->GetData()->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) - { - imageTimeStep = m_ReferenceNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); - } - - mitk::RenderingManager::GetInstance()->InitializeViews(m_ReferenceNode->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); - mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime()->SetPos(imageTimeStep); - } -} diff --git a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.h b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.h index c57e243de0..afef29785d 100644 --- a/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.h +++ b/Plugins/org.mitk.gui.qt.multilabelsegmentation/src/internal/QmitkMultiLabelSegmentationView.h @@ -1,177 +1,175 @@ /*============================================================================ 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 QmitkMultiLabelSegmentationView_h #define QmitkMultiLabelSegmentationView_h #include #include "mitkSegmentationInteractor.h" #include #include "ui_QmitkMultiLabelSegmentationControls.h" // berry #include class QmitkRenderWindow; /** * \ingroup ToolManagerEtAl * \ingroup org_mitk_gui_qt_multilabelsegmentation_internal */ class QmitkMultiLabelSegmentationView : public QmitkAbstractView, public mitk::ILifecycleAwarePart { Q_OBJECT public: static const std::string VIEW_ID; QmitkMultiLabelSegmentationView(); ~QmitkMultiLabelSegmentationView() override; typedef std::map NodeTagMapType; // GUI setup void CreateQtPartControl(QWidget *parent) override; // ILifecycleAwarePart interface public: void Activated() override; void Deactivated() override; void Visible() override; void Hidden() override; virtual int GetSizeFlags(bool width); virtual int ComputePreferredSize(bool width, int /*availableParallel*/, int /*availablePerpendicular*/, int preferredResult); protected slots: // reaction to the shortcut for toggling the visibility of the working node void OnVisibilityShortcutActivated(); // reaction to the shortcut for iterating over all labels void OnLabelToggleShortcutActivated(); // reaction to the selection of any 2D segmentation tool void OnManualTool2DSelected(int id); // reaction to button "New Label" void OnNewLabel(); // reaction to button "Save Preset" void OnSavePreset(); // reaction to button "Load Preset" void OnLoadPreset(); // reaction to button "Show Label Table" void OnShowLabelTable(bool value); // reaction to button "New Segmentation Session" void OnNewSegmentationSession(); // reaction to signal "goToLabel" from labelset widget void OnGoToLabel(const mitk::Point3D &pos); void OnResetView(); // reaction to the button "Add Layer" void OnAddLayer(); // reaction to the button "Delete Layer" void OnDeleteLayer(); // reaction to the button "Previous Layer" void OnPreviousLayer(); // reaction to the button "Next Layer" void OnNextLayer(); // reaction to the combobox change "Change Layer" void OnChangeLayer(int); // reaction to the button "Deactive Active Tool" void OnDeactivateActiveTool(); // reaction to the button "Lock exterior" void OnLockExteriorToggled(bool); // reaction to the selection of a new patient (reference) image in the DataStorage combobox void OnReferenceSelectionChanged(QList nodes); // reaction to the selection of a new Segmentation (working) image in the DataStorage combobox void OnSegmentationSelectionChanged(QList nodes); // reaction to ... void OnInterpolationSelectionChanged(int); protected: // reimplemented from QmitkAbstractView void OnPreferencesChanged(const berry::IBerryPreferences* prefs) override; // reimplemented from QmitkAbstractView void NodeRemoved(const mitk::DataNode* node) override; void OnEstablishLabelSetConnection(); void OnLooseLabelSetConnection(); void SetFocus() override; void UpdateControls(); void RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart); void RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart); void ResetMouseCursor(); void SetMouseCursor(const us::ModuleResource, int hotspotX, int hotspotY); void InitializeListeners(); - void ReinitializeViews() const; - /// \brief the Qt parent of our GUI (NOT of this object) QWidget *m_Parent; /// \brief Qt GUI file Ui::QmitkMultiLabelSegmentationControls m_Controls; mitk::IRenderWindowPart *m_IRenderWindowPart; mitk::ToolManager *m_ToolManager; mitk::DataNode::Pointer m_ReferenceNode; mitk::DataNode::Pointer m_WorkingNode; mitk::NodePredicateAnd::Pointer m_ReferencePredicate; mitk::NodePredicateAnd::Pointer m_SegmentationPredicate; bool m_AutoSelectionEnabled; bool m_MouseCursorSet; mitk::SegmentationInteractor::Pointer m_Interactor; /** * Reference to the service registration of the observer, * it is needed to unregister the observer on unload. */ us::ServiceRegistration m_ServiceRegistration; }; #endif // QmitkMultiLabelSegmentationView_h diff --git a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp index 01578a825b..aa05008ddd 100644 --- a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp +++ b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp @@ -1,179 +1,174 @@ /*============================================================================ 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 "QmitkMxNMultiWidgetEditor.h" #include #include #include #include #include // mxn multi widget editor plugin #include "QmitkMultiWidgetDecorationManager.h" // mitk qt widgets module #include #include #include // qt #include const QString QmitkMxNMultiWidgetEditor::EDITOR_ID = "org.mitk.editors.mxnmultiwidget"; struct QmitkMxNMultiWidgetEditor::Impl final { Impl(); ~Impl() = default; QmitkInteractionSchemeToolBar* m_InteractionSchemeToolBar; QmitkMultiWidgetConfigurationToolBar* m_ConfigurationToolBar; }; QmitkMxNMultiWidgetEditor::Impl::Impl() : m_InteractionSchemeToolBar(nullptr) , m_ConfigurationToolBar(nullptr) { // nothing here } ////////////////////////////////////////////////////////////////////////// // QmitkMxNMultiWidgetEditor ////////////////////////////////////////////////////////////////////////// QmitkMxNMultiWidgetEditor::QmitkMxNMultiWidgetEditor() : QmitkAbstractMultiWidgetEditor() , m_Impl(std::make_unique()) { // nothing here } QmitkMxNMultiWidgetEditor::~QmitkMxNMultiWidgetEditor() { GetSite()->GetPage()->RemovePartListener(this); } void QmitkMxNMultiWidgetEditor::OnLayoutSet(int row, int column) { const auto &multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->SetCrosshairVisibility(true); QmitkAbstractMultiWidgetEditor::OnLayoutSet(row, column); } } void QmitkMxNMultiWidgetEditor::OnInteractionSchemeChanged(mitk::InteractionSchemeSwitcher::InteractionScheme scheme) { const auto &multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { return; } if (mitk::InteractionSchemeSwitcher::PACSStandard == scheme) { m_Impl->m_InteractionSchemeToolBar->setVisible(true); } else { m_Impl->m_InteractionSchemeToolBar->setVisible(false); } QmitkAbstractMultiWidgetEditor::OnInteractionSchemeChanged(scheme); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidgetEditor::SetFocus() { const auto& multiWidget = GetMultiWidget(); if (nullptr != multiWidget) { multiWidget->setFocus(); } } void QmitkMxNMultiWidgetEditor::CreateQtPartControl(QWidget* parent) { QHBoxLayout *layout = new QHBoxLayout(parent); layout->setContentsMargins(0, 0, 0, 0); berry::IBerryPreferences *preferences = dynamic_cast(GetPreferences().GetPointer()); auto multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { multiWidget = new QmitkMxNMultiWidget(parent, 0, nullptr); // create left toolbar: interaction scheme toolbar to switch how the render window navigation behaves in PACS mode if (nullptr == m_Impl->m_InteractionSchemeToolBar) { m_Impl->m_InteractionSchemeToolBar = new QmitkInteractionSchemeToolBar(parent); layout->addWidget(m_Impl->m_InteractionSchemeToolBar); } m_Impl->m_InteractionSchemeToolBar->SetInteractionEventHandler(multiWidget->GetInteractionEventHandler()); - // show / hide PACS mouse mode interaction scheme toolbar - bool PACSInteractionScheme = preferences->GetBool("PACS like mouse interaction", false); - m_Impl->m_InteractionSchemeToolBar->setVisible(PACSInteractionScheme); - multiWidget->SetDataStorage(GetDataStorage()); multiWidget->InitializeMultiWidget(); SetMultiWidget(multiWidget); } layout->addWidget(multiWidget); // create right toolbar: configuration toolbar to change the render window widget layout if (nullptr == m_Impl->m_ConfigurationToolBar) { m_Impl->m_ConfigurationToolBar = new QmitkMultiWidgetConfigurationToolBar(multiWidget); layout->addWidget(m_Impl->m_ConfigurationToolBar); } connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::LayoutSet, this, &QmitkMxNMultiWidgetEditor::OnLayoutSet); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::Synchronized, this, &QmitkMxNMultiWidgetEditor::OnSynchronize); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::InteractionSchemeChanged, this, &QmitkMxNMultiWidgetEditor::OnInteractionSchemeChanged); GetSite()->GetPage()->AddPartListener(this); OnPreferencesChanged(preferences); } void QmitkMxNMultiWidgetEditor::OnPreferencesChanged(const berry::IBerryPreferences* preferences) { const auto& multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { return; } // update decoration preferences //m_Impl->m_MultiWidgetDecorationManager->DecorationPreferencesChanged(preferences); // zooming and panning preferences bool constrainedZooming = preferences->GetBool("Use constrained zooming and panning", true); mitk::RenderingManager::GetInstance()->SetConstrainedPanningZooming(constrainedZooming); bool PACSInteractionScheme = preferences->GetBool("PACS like mouse interaction", false); OnInteractionSchemeChanged(PACSInteractionScheme ? mitk::InteractionSchemeSwitcher::PACSStandard : mitk::InteractionSchemeSwitcher::MITKStandard); - mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(GetDataStorage()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox index 91ea443d33..1ed993e8e2 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/Manual.dox @@ -1,104 +1,105 @@ /** \page org_mitk_views_pharmacokinetics_mri The DCE MR Perfusion DataFit View \imageMacro{pharmacokinetics_mri_doc.svg,"Icon of the DCE MR Perfusion View",3.0} \tableofcontents \section FIT_DCE_Introduction Introduction In dynamic contrast-enhanced (DCE) MRI, pharmacokinetic (PK) modeling can be used to quantify tissue physiology. Parameters describing the tissue microvasculature can be derived by fitting a pharmacokinetic model, e.g. a compartment model, to the dynamic data. This view offers a comprehensive set of tools to perform pharmacokinetic analysis. \section FIT_DCE_Contact Contact information If you have any questions, need support, find a bug or have a feature request, feel free to contact us at www.mitk.org. \subsection FIT_DCE_Cite Citation information If you use the view for your research please cite our work as reference:\n\n Debus C and Floca R, Ingrisch M, Kompan I, Maier-Hein K, Abdollahi A, Nolden M, MITK-ModelFit: generic open-source framework for model fits and their exploration in medical imaging – design, implementation and application on the example of DCE-MRI. https://doi.org/10.1186/s12859-018-2588-1 (BMC Bioinformatics 2019 20:31) \section FIT_DCE_Data_and_ROI_Selection Time series and mask selection \imageMacro{dce_mri_maskAndFittingStrategy.png, "Time series and mask selection.", 10} In principle, every model can be fitted on the entire image. However, for model configuration reasons (e.g. AIF required) and computational time cost, this is often not advisable. Therefore, apart from the image to be fitted (Selected Time Series), a ROI segmentation can be defined (Selected Mask), within which model fitting is performed. The view currently offers Pixel based and/or ROI based averaged fits of time-varying curves. The ROI based fitting option becomes enabled, if a mask is selected. \section FIT_DCE_General_models Supported models Currently the following pharmacokinetic models for gadolinium-based contrast agent are available: - The Descriptive Brix model \ref FIT_DCE_lit_ref1 "[1]" - A semi-quantitative two/three segment linear model (2SL/3SL) - The standard tofts model \ref FIT_DCE_lit_ref2 "[2]" - The extended Tofts model \ref FIT_DCE_lit_ref3 "[3]" - The two compartment exchange model (2CXM) \ref FIT_DCE_lit_ref4 "[4, 5]" \section FIT_DCE_Settings Model settings \imageMacro{dce_mri_modelSettings.png, "Model settings of the view for the standard Tofts model.", 10} \subsection FIT_DCE_Settings_model Model specific settings Selecting one of the \ref FIT_DCE_General_models "supported models" will open below tabs for further configuration of the model. - The descriptive Brix model requires only definition of the duration of the bolus, i.e. the overall time of the injection (Injection Time [min]). - The 3SL is a semi-quantitative descriptive model that distinguishes three different segments of the signal: A constant baseline, the initial fast rise (wash-in) and the final slow rise / signal decrease (washout). Each of these segments is approximated by a linear curve, with change points in-between. It requires no further configuration. - The standard Tofts model, the extended Tofts model and the 2CXM are compartment models that require the input of the concentration time curve in the tissue feeding artery, the arterial input function (AIF). In the DCE MR Perfusion Datafit View, the arterial input function can be defined in several ways. For patient individual image derived AIFs, select the radio button Select AIF from Image. In that case, a segmentation ROI for the artery has to be selected. This can be done by clicking on the AIF Mask selection widget and selecting a suitable AIF segmentation from the data loaded in the Data Manager. In cases where the respective artery does not lie in the same image as the investigated tissue (e.g. in animal experiments, where a slice through the heart is used for AIF extraction), a dedicated AIF image can be selected using the corresponding Dedicated AIF image selection widget. An alternative option is to define the AIF via an external file by selecting Select AIF from File (e.g. for population derived AIFs or AIFs from blood sampling). By clicking the Browse button, one can select a csv file that holds the AIF values and corresponding timepoints (in tuple format (Time, Value)). Caution: the file must not contain a header line, but the first line must start with Time and Intensity values. Furthermore, the Hematocrit Level has to be set (from 0 to 1) for conversion from whole blood to plasma concentration. It is set as default to the literature value of 0.45. \subsection FIT_DCE_Settings_start Start parameter \imageMacro{dce_mri_start.png, "Example screenshot for start parameter settings.", 10} In cases of noisy data it can be useful to define the initial starting values of the parameter estimates, at which optimization starts, in order to prevent optimization results in local optima. Each model has default scalar values (applied to every voxel) for initial values of each parameter, however these can be adjusted. Moreover, initial values can also be defined locally for each individual voxel via starting value images. To load a starting value image, change the Type from scalar to image. This can be done by double-clicking on the type cell. In the Value column, selection of a starting value image will be available. \subsection FIT_DCE_Settings_constraint Constraints settings \imageMacro{dce_mri_constraints.png, "Example screenshot for constraints settings.", 10} To limit the fitting search space and to exclude unphysical/illogical results for model parameter estimates, constraints to individual parameters as well as combinations can be imposed. Each model has default constraints, however, new ones can be defined or removed by the + and – buttons in the table. The first column specifies the parameter(s) involved in the constraint (if multiple parameters are selected, their sum will be used) by selection in the drop down menu. The second column Type defines whether the constraint defines an upper or lower boundary. Value defines the actual constraint value, that should not be crossed, and Width allows for a certain tolerance width. \subsection FIT_DCE_Settings_concentration Signal to concentration conversion settings \imageMacro{dce_mri_concentration.png, "Example screenshot for concentration conversion settings.", 10} Most models require contrast agent concentration values as input rather than raw signal intensities (i.e. all compartment models). The DCE MR Perfusion DataFit View offers a variety of tools for the conversion from signal to concentration: by means of relative and absolute signal enhancement, via a T1-map calculated by the variable flip angle method, as well as a special conversion for turbo flash sequences. +A more detailed description of these conversion methods, for example the exact formulas used for the absolute and relative signal enhancement calculations, can be found here: \ref org_mitk_views_pharmacokinetics_concentration_mri. For the conversion methods, a baseline image prior to contrast agent arrival is required. In many data sets, multiple baseline images are available. The Baseline Range Selection allows for selection of a range of time frames, from which the average image (along the time dimension) is calculated and set as baseline input image. Remark: The number of the first time frame is 0. \section FIT_DCE_Fitting Executing a fit In order to distinguish results from different model fits to the data, a Fitting name can be defined. As default, the name of the model and the fitting strategy (pixel/ROI) are given. This name will then be appended by the respective parameter name.\n\n For development purposes and evaluation of the fits, the option Generate debug parameter images is available. Enabling this option will result in additional parameter maps displaying the status of the optimizer at fit termination. In the following definitions, an evaluation describes the process of cost function calculation and evaluation by the optimizer for a given parameter set. - Stop condition: Reasons for the fit termination, i.e. criterion reached, maximum number of iterations,... - Optimization time: The overall time from fitting start to termination. - Number of iterations: The number of iterations from fitting start to termination. - Constraint penalty ratio: Ratio between evaluations that were penalized and all evaluations. 0.0 means no evaluation was penalized; 1.0 all evaluations were. Evaluations that hit the failure threshold count as penalized, too. - Constraint last failed parameter: Ratio between evaluations that were beyond the failure threshold. 0.0 means no evaluation was a failure (but some may be penalized). - Constraint failure ratio: Index of the first (in terms of index position) parameter, which failed the constraints in the last evaluation. After all necessary configurations are set, the button Start Modelling is enabled, which starts the fitting routine. Progress can be seen in the message box on the bottom. Resulting parameter maps will afterwards be added to the Data Manager as sub-nodes of the analyzed 4D image. \section FIT_DCE_lit References/Literature - \anchor FIT_DCE_lit_ref1 [1] Brix G, Semmler W, Port R, Schad LR, Layer G, Lorenz WJ. Pharmacokinetic parameters in CNS Gd-DTPA enhanced MR imaging. J Comput Assist Tomogr. 1991;15:621–8. - \anchor FIT_DCE_lit_ref2 [2] Tofts PS, Kermode AG. Measurement of the blood-brain barrier permeability and leakage space using dynamic MR imaging. 1. Fundamental concepts. Magn Reson Med. 1991;17:357–67. - \anchor FIT_DCE_lit_ref3 [3] Sourbron SP, Buckley DL. On the scope and interpretation of the Tofts models for DCE-MRI. Magn Reson Med. 2011;66:735–45. - \anchor FIT_DCE_lit_ref4 [4] Brix G, Kiessling F, Lucht R, Darai S, Wasser K, Delorme S, et al. Microcirculation and microvasculature in breast tumors: Pharmacokinetic analysis of dynamic MR image series. Magn Reson Med. 2004;52:420–9. - \anchor FIT_DCE_lit_ref5 [5] Sourbron, Buckley. Tracer kinetic modelling in MRI: estimating perfusion and capillary permeability - pdf. Phys Med Biol. 2012. https://iopscience.iop.org/article/10.1088/0031-9155/57/2/R1/pdf. Accessed 1 May 2016. */ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_Technical.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_Technical.dox index c3a27ea842..346754a8f0 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_Technical.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_Technical.dox @@ -1,100 +1,98 @@ /** \page QmitkSegmentationTechnicalPage Technical design of QmitkSegmentation \li \ref QmitkSegmentationTechnicalPage2 \li \ref QmitkSegmentationTechnicalPage3 \li \ref QmitkSegmentationTechnicalPage4 \section QmitkSegmentationTechnicalPage2 Introduction QmitkSegmentation was designed for the liver resection planning project "ReLiver". The goal was a stable, well-documented, extensible, and testable re-implementation of a functionality called "ERIS", which was used for manual segmentation in 2D slices of 3D or 3D+t images. Re-implementation was chosen because it seemed to be easier to write documentation and tests for newly developed code. In addition, the old code had some design weaknesses (e.g. a monolithic class), which would be hard to maintain in the future. By now Segmentation is a well tested and easily extensible vehicle for all kinds of interactive segmentation applications. A separate page describes how you can extend Segmentation with new tools in a shared object (DLL): \ref toolextensions. \section QmitkSegmentationTechnicalPage3 Overview of tasks We identified the following major tasks:
  1. Management of images: what is the original patient image, what images are the active segmentations?
  2. Management of drawing tools: there is a set of drawing tools, one at a time is active, that is, someone has to decide which tool will receive mouse (and other) events.
  3. Drawing tools: each tool can modify a segmentation in reaction to user interaction. To do so, the tools have to know about the relevant images.
  4. Slice manipulation: drawing tools need to have means to extract a single slice from an image volume and to write a single slice back into an image volume.
  5. Interpolation of unsegmented slices: some class has to keep track of all the segmentations in a volume and generate suggestions for missing slices. This should be possible in all three orthogonal slice direction.
  6. Undo: Slice manipulations should be undoable, no matter whether a tool or the interpolation mechanism changed something.
  7. GUI: Integration of everything.
\section QmitkSegmentationTechnicalPage4 Classes involved The above blocks correspond to a number of classes. Here is an overview of all related classes with their responsibilities and relations: \imageMacro{QmitkSegmentation_InteractiveSegmentationClasses.png,"",16.00}
  1. Management of images: mitk::ToolManager has a set of reference data (original images) and a second set of working data (segmentations). mitk::Tool objects know a ToolManager and can ask the manager for the currently relevant images. GUI and non-GUI classes are coupled by itk::Events (non-GUI to GUI) and direct method calls (GUI to non-GUI).
  2. Management of drawing tools: As a second task, ToolManager manages all available tools and makes sure that one at a time is able to receive MITK events. The GUI for selecting tools is implemented in QmitkToolSelectionBox.
  3. Drawing tools: Drawing tools all inherit from mitk::Tool, which is a mitk::StateMachine. There is a number of derivations from Tool, each offering some helper methods for specific sub-classes, like manipulation of 2D slices. Tools are instantiated through the itk::ObjectFactory, which means that there is also one factory for each tool (e.g. mitk::AddContourToolFactory). For the GUI representation, each tool has an identification, consisting of a name and an icon (XPM). The actual drawing methods are mainly implemented in mitk::SegTool2D (helper methods) and its sub-classes for region growing, freehand drawing, etc. -
  4. Slice manipulation: There are two filters for manipulation of slices +
  5. Slice manipulation: There are two ways to manipulate slices inside a 3D image volume. mitk::ExtractImageFilter retrieves a single 2D slice -from a 3D volume. mitk::OverwriteSliceImageFilter replaces a slice inside a 3D -volume with a second slice which is a parameter to the filter. These classes are -used extensively by most of the tools to fulfill their task. -mitk::OverwriteSliceImageFilter cooperates with the interpolation classes to -inform them of single slice modifications. +from a 3D volume. mitkVtkImageOverwrite replaces a slice inside a 3D +volume with a second slice. These classes are used extensively by most of the tools +to fulfill their task.
  6. Interpolation of unsegmented slices: There are two classes involved in interpolation: mitk::SegmentationInterpolationController knows a mitk::Image (the segmentation) and scans its contents for slices with non-zero pixels. It keeps track of changes in the image and is always able to tell, which neighbors of a slice (in the three orthogonal slice directions) contain segmentations. The class also performs this interpolation for single slices on demand. Again, we have a second class responsible for the GUI: QmitkSlicesInterpolator enables/disables interpolation and offers to accept interpolations for one or all slices. -
  7. Undo: Undo functionality is implemented in mitk::OverwriteSliceImageFilter, -since this is the central place where all image modifications are made. The filter stores a binary difference image -to the undo stack as a mitk::ApplyDiffImageOperation. When the user requests undo, this ApplyDiffImageOperation -will be executed by a singleton class DiffImageApplier. The operation itself observes the image, which it refers to, -for itk::DeleteEvent, so no undo operation will be executed on/for images that have already been destroyed. +
  8. Undo: Undo functionality is implemented using mitk::DiffSliceOperation. +mitk::SegTool2D::WriteSliceToVolume stores the original image and the modified slice to the undo stack as an +mitk::DiffSliceOperation. When the user requests undo, this mitk::DiffSliceOperation will be executed by a singleton +class DiffSliceOperationApplier. The operation itself observes the image, which it refers to, for itk::DeleteEvent, +so no undo operation will be executed on/for images that have already been destroyed.
  9. GUI: The top-level GUI is the view QmitkSegmentation, which is very thin in comparison to ERIS. There are separate widgets for image and tool selection, for interpolation. Additionaly, there are some methods to create, delete, crop, load and save segmentations.
**/ diff --git a/Plugins/org.mitk.gui.qt.segmentation/files.cmake b/Plugins/org.mitk.gui.qt.segmentation/files.cmake index dd452ef63f..a9b25997b4 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/files.cmake +++ b/Plugins/org.mitk.gui.qt.segmentation/files.cmake @@ -1,74 +1,70 @@ set(SRC_CPP_FILES QmitkSegmentationPreferencePage.cpp ) set(INTERNAL_CPP_FILES mitkPluginActivator.cpp QmitkSegmentationView.cpp - QmitkThresholdAction.cpp QmitkCreatePolygonModelAction.cpp - #QmitkStatisticsAction.cpp QmitkAutocropAction.cpp QmitkAutocropLabelSetImageAction.cpp Common/QmitkDataSelectionWidget.cpp SegmentationUtilities/QmitkSegmentationUtilitiesView.cpp SegmentationUtilities/QmitkSegmentationUtilityWidget.cpp SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.cpp SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidget.cpp SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidget.cpp ) set(UI_FILES src/internal/QmitkSegmentationControls.ui src/internal/Common/QmitkDataSelectionWidgetControls.ui src/internal/SegmentationUtilities/QmitkSegmentationUtilitiesViewControls.ui src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidgetControls.ui src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidgetControls.ui src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidgetControls.ui src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidgetControls.ui ) set(MOC_H_FILES src/QmitkSegmentationPreferencePage.h src/internal/mitkPluginActivator.h src/internal/QmitkSegmentationView.h - src/internal/QmitkThresholdAction.h src/internal/QmitkCreatePolygonModelAction.h - #src/internal/QmitkStatisticsAction.h src/internal/QmitkAutocropAction.h src/internal/QmitkAutocropLabelSetImageAction.h src/internal/Common/QmitkDataSelectionWidget.h src/internal/SegmentationUtilities/QmitkSegmentationUtilitiesView.h src/internal/SegmentationUtilities/QmitkSegmentationUtilityWidget.h src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.h src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.h src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.h src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidget.h src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidget.h ) set(CACHED_RESOURCE_FILES resources/segmentation.svg resources/segmentation_utilities.svg plugin.xml ) set(QRC_FILES resources/segmentation.qrc resources/SegmentationUtilities.qrc resources/BooleanOperationsWidget.qrc resources/MorphologicalOperationsWidget.qrc ) set(CPP_FILES) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.cpp index 5ca2bbd0d5..936217f330 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkDataSelectionWidget.cpp @@ -1,236 +1,236 @@ /*============================================================================ 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 "QmitkDataSelectionWidget.h" #include "../mitkPluginActivator.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static mitk::NodePredicateBase::Pointer CreatePredicate(QmitkDataSelectionWidget::Predicate predicate) { auto imageType = mitk::TNodePredicateDataType::New(); auto labelSetImageType = mitk::TNodePredicateDataType::New(); auto surfaceType = mitk::TNodePredicateDataType::New(); auto contourModelType = mitk::TNodePredicateDataType::New(); auto contourModelSetType = mitk::TNodePredicateDataType::New(); auto nonLabelSetImageType = mitk::NodePredicateAnd::New(imageType, mitk::NodePredicateNot::New(labelSetImageType)); auto nonHelperObject = mitk::NodePredicateNot::New(mitk::NodePredicateOr::New( mitk::NodePredicateProperty::New("helper object"), mitk::NodePredicateProperty::New("hidden object"))); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isSegmentation = mitk::NodePredicateProperty::New("segmentation", mitk::BoolProperty::New(true)); auto isBinaryOrSegmentation = mitk::NodePredicateOr::New(isBinary, isSegmentation); mitk::NodePredicateBase::Pointer returnValue; switch(predicate) { case QmitkDataSelectionWidget::ImagePredicate: returnValue = mitk::NodePredicateAnd::New( mitk::NodePredicateNot::New(isBinaryOrSegmentation), nonLabelSetImageType).GetPointer(); break; case QmitkDataSelectionWidget::SegmentationPredicate: returnValue = mitk::NodePredicateOr::New( mitk::NodePredicateAnd::New(imageType, isBinaryOrSegmentation), labelSetImageType).GetPointer(); break; case QmitkDataSelectionWidget::SurfacePredicate: returnValue = surfaceType.GetPointer(); break; case QmitkDataSelectionWidget::ImageAndSegmentationPredicate: returnValue = imageType.GetPointer(); break; case QmitkDataSelectionWidget::ContourModelPredicate: returnValue = mitk::NodePredicateOr::New( contourModelSetType, contourModelSetType).GetPointer(); break; case QmitkDataSelectionWidget::SegmentationOrSurfacePredicate: returnValue = mitk::NodePredicateOr::New( mitk::NodePredicateAnd::New(imageType, isBinaryOrSegmentation), labelSetImageType).GetPointer(); returnValue = mitk::NodePredicateOr::New(returnValue, surfaceType).GetPointer(); break; default: assert(false && "Unknown predefined predicate!"); return nullptr; } return mitk::NodePredicateAnd::New(returnValue, nonHelperObject).GetPointer(); } QmitkDataSelectionWidget::QmitkDataSelectionWidget(QWidget* parent) : QWidget(parent) { m_Controls.setupUi(this); m_Controls.helpLabel->hide(); } QmitkDataSelectionWidget::~QmitkDataSelectionWidget() { } unsigned int QmitkDataSelectionWidget::AddDataSelection(QmitkDataSelectionWidget::Predicate predicate) { QString hint = "Select node"; switch (predicate) { case QmitkDataSelectionWidget::ImagePredicate: hint = "Select an image"; break; case QmitkDataSelectionWidget::SegmentationPredicate: hint = "Select a segmentation"; break; case QmitkDataSelectionWidget::SurfacePredicate: hint = "Select a surface"; break; case QmitkDataSelectionWidget::ImageAndSegmentationPredicate: hint = "Select an image or segmentation"; break; case QmitkDataSelectionWidget::ContourModelPredicate: hint = "Select a contour model"; break; case QmitkDataSelectionWidget::SegmentationOrSurfacePredicate: hint = "Select a segmentation or surface"; break; } return this->AddDataSelection("", hint, hint, "", predicate); } unsigned int QmitkDataSelectionWidget::AddDataSelection(mitk::NodePredicateBase* predicate) { return this->AddDataSelection("", "Select a node", "Select a node", "", predicate); } unsigned int QmitkDataSelectionWidget::AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, QmitkDataSelectionWidget::Predicate predicate) { return this->AddDataSelection(labelText, info, popupHint, popupTitel, CreatePredicate(predicate)); } unsigned int QmitkDataSelectionWidget::AddDataSelection(const QString &labelText, const QString &info, const QString &popupTitel, const QString &popupHint, mitk::NodePredicateBase* predicate) { int row = m_Controls.gridLayout->rowCount(); if (!labelText.isEmpty()) { QLabel* label = new QLabel(labelText, m_Controls.dataSelectionWidget); label->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); m_Controls.gridLayout->addWidget(label, row, 0); } QmitkSingleNodeSelectionWidget* nodeSelection = new QmitkSingleNodeSelectionWidget(m_Controls.dataSelectionWidget); nodeSelection->SetSelectionIsOptional(false); - nodeSelection->SetAutoSelectNewNodes(false); nodeSelection->SetInvalidInfo(info); nodeSelection->SetPopUpTitel(popupTitel); nodeSelection->SetPopUpHint(popupHint); nodeSelection->SetDataStorage(this->GetDataStorage()); nodeSelection->SetNodePredicate(predicate); + nodeSelection->SetAutoSelectNewNodes(true); nodeSelection->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum); nodeSelection->setMinimumSize(0, 40); connect(nodeSelection, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkDataSelectionWidget::OnSelectionChanged); m_Controls.gridLayout->addWidget(nodeSelection, row, 1); m_NodeSelectionWidgets.push_back(nodeSelection); return static_cast(m_NodeSelectionWidgets.size() - 1); } mitk::DataStorage::Pointer QmitkDataSelectionWidget::GetDataStorage() const { ctkServiceReference ref = mitk::PluginActivator::getContext()->getServiceReference(); assert(ref == true); mitk::IDataStorageService* service = mitk::PluginActivator::getContext()->getService(ref); assert(service); return service->GetDefaultDataStorage()->GetDataStorage(); } mitk::DataNode::Pointer QmitkDataSelectionWidget::GetSelection(unsigned int index) { assert(index < m_NodeSelectionWidgets.size()); return m_NodeSelectionWidgets[index]->GetSelectedNode(); } void QmitkDataSelectionWidget::SetPredicate(unsigned int index, Predicate predicate) { this->SetPredicate(index, CreatePredicate(predicate)); } void QmitkDataSelectionWidget::SetPredicate(unsigned int index, mitk::NodePredicateBase* predicate) { assert(index < m_NodeSelectionWidgets.size()); m_NodeSelectionWidgets[index]->SetNodePredicate(predicate); } void QmitkDataSelectionWidget::SetHelpText(const QString& text) { if (!text.isEmpty()) { m_Controls.helpLabel->setText(text); if (!m_Controls.helpLabel->isVisible()) m_Controls.helpLabel->show(); } else { m_Controls.helpLabel->hide(); } } void QmitkDataSelectionWidget::OnSelectionChanged(QList selection) { std::vector::iterator it = std::find(m_NodeSelectionWidgets.begin(), m_NodeSelectionWidgets.end(), sender()); assert(it != m_NodeSelectionWidgets.end()); const mitk::DataNode* result = nullptr; if (!selection.empty()) { result = selection.front(); } emit SelectionChanged(std::distance(m_NodeSelectionWidgets.begin(), it), result); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropAction.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropAction.cpp index aaf3dc6f53..5cf185edc8 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropAction.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropAction.cpp @@ -1,194 +1,192 @@ /*============================================================================ 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 "QmitkAutocropAction.h" #include "mitkAutoCropImageFilter.h" #include "mitkImageCast.h" #include "mitkImageWriteAccessor.h" #include "mitkRenderingManager.h" #include "mitkProgressBar.h" #include //needed for qApp #include QmitkAutocropAction::QmitkAutocropAction() { } QmitkAutocropAction::~QmitkAutocropAction() { } void QmitkAutocropAction::Run( const QList &selectedNodes ) { foreach ( mitk::DataNode::Pointer node, selectedNodes ) { if (node) { mitk::Image::Pointer image = dynamic_cast( node->GetData() ); if (image.IsNull()) return; mitk::ProgressBar::GetInstance()->AddStepsToDo(10); mitk::ProgressBar::GetInstance()->Progress(2); qApp->processEvents(); mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); cropFilter->SetInput( image ); cropFilter->SetBackgroundValue( 0 ); try { cropFilter->Update(); image = cropFilter->GetOutput(); if (image.IsNotNull()) { if (image->GetDimension() == 4) { MITK_INFO << "4D AUTOCROP DOES NOT WORK AT THE MOMENT"; throw "4D AUTOCROP DOES NOT WORK AT THE MOMENT"; unsigned int timesteps = image->GetDimension(3); for (unsigned int i = 0; i < timesteps; i++) { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(i); imageTimeSelector->UpdateLargestPossibleRegion(); // We split a long nested code line into separate calls for debugging: mitk::ImageSource::OutputImageType *_3dSlice = imageTimeSelector->GetOutput(); mitk::Image::Pointer _cropped3dSlice = this->IncreaseCroppedImageSize(_3dSlice); // +++ BUG +++ BUG +++ BUG +++ BUG +++ BUG +++ BUG +++ BUG +++ mitk::ImageWriteAccessor imAccess(_cropped3dSlice); void *_data = imAccess.GetData(); // // We write some stripes into the image if ((i & 1) == 0) { int depth = _cropped3dSlice->GetDimension(2); int height = _cropped3dSlice->GetDimension(1); int width = _cropped3dSlice->GetDimension(0); for (int z = 0; z < depth; ++z) for (int y = 0; y < height; ++y) for (int x = 0; x < width; ++x) reinterpret_cast(_data)[(width * height * z) + (width * y) + x] = x & 1; // } image->SetVolume(_data, i); } node->SetData( image ); // bug fix 3145 } else { node->SetData( this->IncreaseCroppedImageSize(image) ); // bug fix 3145 } // Reinit node mitk::RenderingManager::GetInstance()->InitializeViews( - node->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true ); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - + node->GetData()->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); } } catch(...) { MITK_ERROR << "Cropping image failed..."; } mitk::ProgressBar::GetInstance()->Progress(8); } else { MITK_INFO << " a nullptr node selected"; } } } mitk::Image::Pointer QmitkAutocropAction::IncreaseCroppedImageSize( mitk::Image::Pointer image ) { typedef itk::Image< short, 3 > ImageType; typedef itk::Image< unsigned char, 3 > PADOutputImageType; ImageType::Pointer itkTransformImage = ImageType::New(); mitk::CastToItkImage( image, itkTransformImage ); typedef itk::ConstantPadImageFilter< ImageType, PADOutputImageType > PadFilterType; PadFilterType::Pointer padFilter = PadFilterType::New(); itk::SizeValueType upperPad[3]; itk::SizeValueType lowerPad[3]; int borderLiner = 3; mitk::Point3D mitkOriginPoint; double origin[3]; origin[0]=0; origin[1]=0; origin[2]=0; itkTransformImage->SetOrigin(origin); lowerPad[0]=borderLiner; lowerPad[1]=borderLiner; lowerPad[2]=borderLiner; upperPad[0]=borderLiner; upperPad[1]=borderLiner; upperPad[2]=borderLiner; padFilter->SetInput(itkTransformImage); padFilter->SetConstant(0); padFilter->SetPadUpperBound(upperPad); padFilter->SetPadLowerBound(lowerPad); padFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer paddedImage = mitk::Image::New(); paddedImage->InitializeByItk(padFilter->GetOutput()); mitk::CastToMitkImage(padFilter->GetOutput(), paddedImage); //calculate translation according to padding to get the new origin mitk::Point3D paddedOrigin = image->GetGeometry()->GetOrigin(); mitk::Vector3D spacing = image->GetGeometry()->GetSpacing(); paddedOrigin[0] -= (borderLiner)*spacing[0]; paddedOrigin[1] -= (borderLiner)*spacing[1]; paddedOrigin[2] -= (borderLiner)*spacing[2]; paddedImage->GetGeometry()->SetOrigin( paddedOrigin ); return paddedImage; } void QmitkAutocropAction::SetSmoothed(bool /*smoothed*/) { //not needed } void QmitkAutocropAction::SetDecimated(bool /*decimated*/) { //not needed } void QmitkAutocropAction::SetDataStorage(mitk::DataStorage* /*dataStorage*/) { //not needed } void QmitkAutocropAction::SetFunctionality(berry::QtViewPart* /*view*/) { //not needed } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkOtsuAction.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkOtsuAction.cpp deleted file mode 100644 index 4cec41b4c0..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkOtsuAction.cpp +++ /dev/null @@ -1,193 +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 "QmitkOtsuAction.h" - -// MITK -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -// ITK -#include - -// Qt -#include -#include -#include -#include -#include -#include - -using namespace berry; -using namespace mitk; -using namespace std; - -QmitkOtsuAction::QmitkOtsuAction() -: m_OtsuSegmentationDialog(nullptr) -{ -} - -QmitkOtsuAction::~QmitkOtsuAction() -{ -} - -void QmitkOtsuAction::Run(const QList &selectedNodes) -{ - this->m_DataNode = selectedNodes[0]; - //this->m_selectedNodes = selectedNodes; - - m_OtsuSegmentationDialog = new QDialog(QApplication::activeWindow(),Qt::WindowTitleHint | Qt::WindowSystemMenuHint); - - - QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins(0, 0, 0, 0); - QHBoxLayout* spinBoxLayout = new QHBoxLayout; - QHBoxLayout* buttonLayout = new QHBoxLayout; - - m_OtsuSpinBox = new QSpinBox; - m_OtsuSpinBox->setRange(2, 32); - m_OtsuSpinBox->setValue(2); - - m_OtsuPushButton = new QPushButton("OK"); - QPushButton* CancelButton = new QPushButton("Cancel"); - - connect(m_OtsuPushButton, SIGNAL(clicked()), this, SLOT(OtsuSegmentationDone())); - connect(CancelButton, SIGNAL(clicked()), m_OtsuSegmentationDialog, SLOT(reject())); - - QLabel* numberOfThresholdsLabel = new QLabel("Select number of Regions of Interest:"); - numberOfThresholdsLabel->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter); - layout->addWidget(numberOfThresholdsLabel); - layout->addLayout(spinBoxLayout); - spinBoxLayout->addSpacing(50); - spinBoxLayout->addWidget(m_OtsuSpinBox); - spinBoxLayout->addSpacing(50); - layout->addLayout(buttonLayout); - buttonLayout->addWidget(m_OtsuPushButton); - buttonLayout->addWidget(CancelButton); - - m_OtsuSegmentationDialog->setLayout(layout); - m_OtsuSegmentationDialog->setFixedSize(300, 80); - - m_OtsuSegmentationDialog->open(); -} - -void QmitkOtsuAction::OtsuSegmentationDone() -{ - this->PerformOtsuSegmentation(); - - m_OtsuSegmentationDialog->deleteLater(); - m_OtsuSegmentationDialog = nullptr; - - RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkOtsuAction::SetDataStorage(DataStorage *dataStorage) -{ - m_DataStorage = dataStorage; -} - -void QmitkOtsuAction::SetFunctionality(QtViewPart* /*view*/) -{ -} - -void QmitkOtsuAction::PerformOtsuSegmentation() -{ - this->m_OtsuSegmentationDialog->setCursor(Qt::WaitCursor); - - int numberOfThresholds = this->m_OtsuSpinBox->value() - 1; - int proceed; - - QMessageBox* messageBox = new QMessageBox(QMessageBox::Question, nullptr, "The otsu segmentation computation may take several minutes depending on the number of Regions you selected. Proceed anyway?", QMessageBox::Ok | QMessageBox::Cancel); - if (numberOfThresholds >= 5) - { - proceed = messageBox->exec(); - if (proceed != QMessageBox::Ok) return; - } - - mitk::Image::Pointer mitkImage = 0; - - mitkImage = dynamic_cast( this->m_DataNode->GetData() ); - - try - { - // get selected mitk image - const unsigned short dim = 3; - typedef short InputPixelType; - typedef unsigned char OutputPixelType; - - typedef itk::Image< InputPixelType, dim > InputImageType; - typedef itk::Image< OutputPixelType, dim > OutputImageType; - - typedef itk::OtsuMultipleThresholdsImageFilter< InputImageType, OutputImageType > FilterType; - - FilterType::Pointer filter = FilterType::New(); - - filter->SetNumberOfThresholds(numberOfThresholds); - - InputImageType::Pointer itkImage; - mitk::CastToItkImage(mitkImage, itkImage); - - filter->SetInput( itkImage ); - - filter->Update(); - - mitk::DataNode::Pointer resultNode = mitk::DataNode::New(); - std::string nameOfResultImage = this->m_DataNode->GetName(); - nameOfResultImage.append("Otsu"); - resultNode->SetProperty("name", mitk::StringProperty::New(nameOfResultImage) ); - resultNode->SetProperty("binary", mitk::BoolProperty::New(false) ); - mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); - renderingMode->SetValue( mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR ); - resultNode->SetProperty("Image Rendering.Mode", renderingMode); - - mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); - - mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); - - vtkLookupTable *lookupTable = vtkLookupTable::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); - resultNode->SetProperty("LookupTable",prop); - - mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); - mitk::LevelWindow levelwindow; - levelwindow.SetRangeMinMax(0, numberOfThresholds+1); - levWinProp->SetLevelWindow( levelwindow ); - resultNode->SetProperty( "levelwindow", levWinProp ); - - resultNode->SetData( mitk::GrabItkImageMemory( filter->GetOutput() ) ); - - - this->m_DataStorage->Add(resultNode, this->m_DataNode); - - this->m_OtsuSegmentationDialog->setCursor(Qt::ArrowCursor); - - } - catch( std::exception& err ) - { - MITK_ERROR(this->GetClassName()) << err.what(); - } -} diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkOtsuAction.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkOtsuAction.h deleted file mode 100644 index 143fd164f2..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkOtsuAction.h +++ /dev/null @@ -1,66 +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 QMITKOTSUACTION_H -#define QMITKOTSUACTION_H - -#include - -// Parent classes -#include -#include - -// Data members -#include -#include -#include - -// Mitk classes -#include - -class QDialog; - -/** \deprecatedSince{2013_09} The interaction for the Otsu image filter was revised and moved to the segmentation plugin view. */ -class DEPRECATED() MITK_QT_SEGMENTATION QmitkOtsuAction : public QObject, public mitk::IContextMenuAction -{ - Q_OBJECT - Q_INTERFACES(mitk::IContextMenuAction) - -public: - QmitkOtsuAction(); - ~QmitkOtsuAction(); - - // IContextMenuAction - void Run(const QList &selectedNodes); - void SetDataStorage(mitk::DataStorage *dataStorage); - void SetFunctionality(berry::QtViewPart* view); - void SetSmoothed(bool smoothed){} - void SetDecimated(bool decimated){} - -private slots: - void OtsuSegmentationDone(); - -private: - QmitkOtsuAction(const QmitkOtsuAction &); - QmitkOtsuAction & operator=(const QmitkOtsuAction &); - - void PerformOtsuSegmentation(); - - mitk::DataStorage::Pointer m_DataStorage; - QDialog *m_OtsuSegmentationDialog; - - QSpinBox* m_OtsuSpinBox; - QPushButton* m_OtsuPushButton; - - mitk::DataNode::Pointer m_DataNode; -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 91cfe486e2..57a52ba698 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,907 +1,912 @@ /*============================================================================ 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 "mitkProperties.h" #include "mitkSegTool2D.h" #include "mitkStatusBar.h" #include "QmitkNewSegmentationDialog.h" #include #include #include #include "QmitkSegmentationView.h" #include #include "mitkVtkResliceInterpolationProperty.h" #include "mitkApplicationCursor.h" #include "mitkSegmentationObjectFactory.h" #include "mitkPluginActivator.h" #include "mitkCameraController.h" #include "mitkLabelSetImage.h" #include "mitkImageTimeSelector.h" #include "mitkNodePredicateSubGeometry.h" #include #include "usModuleResource.h" #include "usModuleResourceStream.h" //micro service to get the ToolManager instance #include "mitkToolManagerProvider.h" #include #include const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_MouseCursorSet(false) , m_DataSelectionChanged(false) { mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); mitk::NodePredicateDataType::Pointer isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); mitk::NodePredicateDataType::Pointer isDti = mitk::NodePredicateDataType::New("TensorImage"); mitk::NodePredicateDataType::Pointer isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); mitk::NodePredicateOr::Pointer validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_IsNotAHelperObject = mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(true))); m_IsOfTypeImagePredicate = mitk::NodePredicateAnd::New(validImages, m_IsNotAHelperObject); mitk::NodePredicateProperty::Pointer isBinaryPredicate = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateNot::Pointer isNotBinaryPredicate = mitk::NodePredicateNot::New(isBinaryPredicate); mitk::NodePredicateAnd::Pointer isABinaryImagePredicate = mitk::NodePredicateAnd::New(m_IsOfTypeImagePredicate, isBinaryPredicate); mitk::NodePredicateAnd::Pointer isNotABinaryImagePredicate = mitk::NodePredicateAnd::New(m_IsOfTypeImagePredicate, isNotBinaryPredicate); auto isMLImageType = mitk::TNodePredicateDataType::New(); mitk::NodePredicateAnd::Pointer isAMLImagePredicate = mitk::NodePredicateAnd::New(isMLImageType, m_IsNotAHelperObject); mitk::NodePredicateAnd::Pointer isNotAMLImagePredicate = mitk::NodePredicateAnd::New(mitk::NodePredicateNot::New(isMLImageType), m_IsNotAHelperObject); m_IsASegmentationImagePredicate = mitk::NodePredicateOr::New(isABinaryImagePredicate, isAMLImagePredicate); m_IsAPatientImagePredicate = mitk::NodePredicateAnd::New(isNotABinaryImagePredicate, isNotAMLImagePredicate); } QmitkSegmentationView::~QmitkSegmentationView() { if (m_Controls) { SetToolSelectionBoxesEnabled(false); // deactivate all tools mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); // removing all observers for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); SetToolManagerSelection(nullptr, nullptr); } delete m_Controls; } void QmitkSegmentationView::NewNodeObjectsGenerated(mitk::ToolManager::DataVectorType* nodes) { if (!nodes) return; mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); if (!toolManager) return; for (mitk::ToolManager::DataVectorType::iterator iter = nodes->begin(); iter != nodes->end(); ++iter) { this->FireNodeSelected( *iter ); // only last iteration meaningful, multiple generated objects are not taken into account here } } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (m_Parent) { m_Parent->setEnabled(true); } // tell the interpolation about tool manager, data storage and render window part if (m_Controls) { mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_Controls->m_SlicesInterpolator->SetDataStorage(this->GetDataStorage()); QList controllers; controllers.push_back(renderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(renderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls->m_SlicesInterpolator->Initialize(toolManager, controllers); } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (m_Parent) { m_Parent->setEnabled(false); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { if (m_Controls != nullptr) { bool slimView = prefs->GetBool("slim view", false); m_Controls->m_ManualToolSelectionBox2D->SetShowNames(!slimView); m_Controls->m_ManualToolSelectionBox3D->SetShowNames(!slimView); m_Controls->btnNewSegmentation->setToolButtonStyle(slimView ? Qt::ToolButtonIconOnly : Qt::ToolButtonTextOnly); } auto autoSelectionEnabled = prefs->GetBool("auto selection", true); m_Controls->patImageSelector->SetAutoSelectNewNodes(autoSelectionEnabled); m_Controls->segImageSelector->SetAutoSelectNewNodes(autoSelectionEnabled); this->ForceDisplayPreferencesUponAllImages(); } void QmitkSegmentationView::CreateNewSegmentation() { - mitk::DataNode::Pointer node = mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetReferenceData(0); - if (node.IsNotNull()) - { - mitk::Image::ConstPointer referenceImage = dynamic_cast(node->GetData()); - if (referenceImage.IsNotNull()) - { - if (referenceImage->GetDimension() > 1) - { - // ask about the name and organ type of the new segmentation - QmitkNewSegmentationDialog* dialog = new QmitkNewSegmentationDialog(m_Parent); // needs a QWidget as parent, "this" is not QWidget - QStringList organColors = mitk::OrganNamesHandling::GetDefaultOrganColorString();; + mitk::DataNode::Pointer node = mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetReferenceData(0); + if (node.IsNull()) + { + MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; + return; + } - dialog->SetSuggestionList(organColors); + mitk::Image::ConstPointer referenceImage = dynamic_cast(node->GetData()); + if (referenceImage.IsNull()) + { + MITK_ERROR << "Reference data needs to be an image in order to create a new segmentation."; + return; + } - int dialogReturnValue = dialog->exec(); - if (dialogReturnValue == QDialog::Rejected) - { - // user clicked cancel or pressed Esc or something similar - return; - } + if (referenceImage->GetDimension() <= 1) + { + QMessageBox::information(nullptr, tr("Segmentation"), tr("Segmentation is currently not supported for 2D images")); + return; + } - const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - unsigned int imageTimeStep = 0; - if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) - { - imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); - } + // ask about the name and organ type of the new segmentation + auto dialog = new QmitkNewSegmentationDialog(m_Parent); // needs a QWidget as parent, "this" is not QWidget + QStringList organColors = mitk::OrganNamesHandling::GetDefaultOrganColorString(); + dialog->SetSuggestionList(organColors); - auto segTemplateImage = referenceImage; - if (referenceImage->GetDimension() > 3) - { - auto result = QMessageBox::question(m_Parent, tr("Create a static or dynamic segmentation?"), tr("The patient image has multiple time steps.\n\nDo you want to create a static segmentation that is identical for all time steps or do you want to create a dynamic segmentation to segment individual time steps?"), tr("Create static segmentation"), tr("Create dynamic segmentation"), QString(), 0,0); - if (result == 0) - { - auto selector = mitk::ImageTimeSelector::New(); - selector->SetInput(referenceImage); - selector->SetTimeNr(0); - selector->Update(); - - const auto refTimeGeometry = referenceImage->GetTimeGeometry(); - auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); - newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); - newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); - - mitk::Image::Pointer newImage = selector->GetOutput(); - newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); - newImage->SetTimeGeometry(newTimeGeometry); - segTemplateImage = newImage; - } - } + int dialogReturnValue = dialog->exec(); + if (dialogReturnValue == QDialog::Rejected) + { + // user clicked Cancel or pressed Esc or something similar + return; + } - // ask the user about an organ type and name, add this information to the image's (!) propertylist - // create a new image of the same dimensions and smallest possible pixel type - mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); - mitk::Tool* firstTool = toolManager->GetToolById(0); - if (firstTool) - { - try - { - std::string newNodeName = dialog->GetSegmentationName().toStdString(); - if (newNodeName.empty()) - { - newNodeName = "no_name"; - } - - mitk::DataNode::Pointer emptySegmentation = firstTool->CreateEmptySegmentationNode(segTemplateImage, newNodeName, dialog->GetColor()); - // initialize showVolume to false to prevent recalculating the volume while working on the segmentation - emptySegmentation->SetProperty("showVolume", mitk::BoolProperty::New(false)); - if (!emptySegmentation) - { - return; // could be aborted by user - } - - mitk::OrganNamesHandling::UpdateOrganList(organColors, dialog->GetSegmentationName(), dialog->GetColor()); - - // escape ';' here (replace by '\;'), see longer comment above - QString stringForStorage = organColors.replaceInStrings(";", "\\;").join(";"); - MITK_DEBUG << "Will store: " << stringForStorage; - this->GetPreferences()->Put("Organ-Color-List", stringForStorage); - this->GetPreferences()->Flush(); - - if (mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetWorkingData(0)) - { - mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetWorkingData(0)->SetSelected(false); - } - emptySegmentation->SetSelected(true); - this->GetDataStorage()->Add(emptySegmentation, node); // add as a child, because the segmentation "derives" from the original - - m_Controls->segImageSelector->SetCurrentSelectedNode(emptySegmentation); - mitk::RenderingManager::GetInstance()->InitializeViews(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, true); - mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetTime()->SetPos(imageTimeStep); - } - catch (const std::bad_alloc&) - { - QMessageBox::warning(nullptr, tr("Create new segmentation"), tr("Could not allocate memory for new segmentation")); - } - } - } - else - { - QMessageBox::information(nullptr, tr("Segmentation"), tr("Segmentation is currently not supported for 2D images")); - } - } - } - else - { - MITK_ERROR << "'Create new segmentation' button should never be clickable unless a patient image is selected..."; - } + const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + unsigned int imageTimeStep = 0; + if (referenceImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) + { + imageTimeStep = referenceImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); + } + + auto segTemplateImage = referenceImage; + if (referenceImage->GetDimension() > 3) + { + auto result = QMessageBox::question(m_Parent, + tr("Create a static or dynamic segmentation?"), + tr("The patient image has multiple time steps.\n\nDo you want to create a static " + "segmentation that is identical for all time steps or do you want to create a " + "dynamic segmentation to segment individual time steps?"), + tr("Create static segmentation"), + tr("Create dynamic segmentation"), + QString(), 0, 0); + if (result == 0) + { + auto selector = mitk::ImageTimeSelector::New(); + selector->SetInput(referenceImage); + selector->SetTimeNr(0); + selector->Update(); + + const auto refTimeGeometry = referenceImage->GetTimeGeometry(); + auto newTimeGeometry = mitk::ProportionalTimeGeometry::New(); + newTimeGeometry->SetFirstTimePoint(refTimeGeometry->GetMinimumTimePoint()); + newTimeGeometry->SetStepDuration(refTimeGeometry->GetMaximumTimePoint() - refTimeGeometry->GetMinimumTimePoint()); + + mitk::Image::Pointer newImage = selector->GetOutput(); + newTimeGeometry->SetTimeStepGeometry(referenceImage->GetGeometry(imageTimeStep), 0); + newImage->SetTimeGeometry(newTimeGeometry); + segTemplateImage = newImage; + } + } + + // create a new image of the same dimensions and smallest possible pixel type + auto toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); + auto firstTool = toolManager->GetToolById(0); + if (nullptr == firstTool) + { + return; + } + + std::string newNodeName = dialog->GetSegmentationName().toStdString(); + if (newNodeName.empty()) + { + newNodeName = "no_name"; + } + + mitk::DataNode::Pointer emptySegmentation = nullptr; + try + { + emptySegmentation = firstTool->CreateEmptySegmentationNode(segTemplateImage, newNodeName, dialog->GetColor()); + } + catch (const std::bad_alloc &) + { + QMessageBox::warning(nullptr, tr("Create new segmentation"), tr("Could not allocate memory for new segmentation")); + } + + if (nullptr == emptySegmentation) + { + return; // could have been aborted by user + } + + // initialize "showVolume"-property to false to prevent recalculating the volume while working on the segmentation + emptySegmentation->SetProperty("showVolume", mitk::BoolProperty::New(false)); + + mitk::OrganNamesHandling::UpdateOrganList(organColors, dialog->GetSegmentationName(), dialog->GetColor()); + + // escape ';' here (replace by '\;') + QString stringForStorage = organColors.replaceInStrings(";", "\\;").join(";"); + MITK_DEBUG << "Will store: " << stringForStorage; + this->GetPreferences()->Put("Organ-Color-List", stringForStorage); + this->GetPreferences()->Flush(); + + this->GetDataStorage()->Add(emptySegmentation, node); + + if (mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetWorkingData(0)) + { + mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetWorkingData(0)->SetSelected(false); + } + emptySegmentation->SetSelected(true); + m_Controls->segImageSelector->SetCurrentSelectedNode(emptySegmentation); + + this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } void QmitkSegmentationView::OnVisiblePropertyChanged() { this->CheckRenderingState(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode *node) { if (!m_IsASegmentationImagePredicate->CheckNode(node)) { return; } itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::OnVisiblePropertyChanged); m_WorkingDataObserverTags.insert(std::pair(const_cast(node), node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); ApplyDisplayOptions(const_cast(node)); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (m_IsASegmentationImagePredicate->CheckNode(node)) { //First of all remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations(node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; if ((mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetWorkingData(0) == node) && m_Controls->patImageSelector->GetSelectedNode().IsNotNull()) { this->SetToolManagerSelection(mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetReferenceData(0), nullptr); this->UpdateWarningLabel(tr("Select or create a segmentation")); } mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } mitk::DataNode* tempNode = const_cast(node); //Remove observer if one was registered auto finding = m_WorkingDataObserverTags.find(tempNode); if (finding != m_WorkingDataObserverTags.end()) { node->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[tempNode]); m_WorkingDataObserverTags.erase(tempNode); } } void QmitkSegmentationView::OnPatientSelectionChanged(QList nodes) { if(! nodes.empty()) { this->UpdateWarningLabel(""); auto node = nodes.first(); auto segPredicate = mitk::NodePredicateAnd::New(m_IsASegmentationImagePredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(node->GetData()->GetGeometry())); m_Controls->segImageSelector->SetNodePredicate(segPredicate); mitk::DataNode* segNode = m_Controls->segImageSelector->GetSelectedNode(); this->SetToolManagerSelection(node, segNode); if (segNode) { //Doing this we can assure that the segmentation is always visible if the segmentation and the patient image are //loaded separately int layer(10); node->GetIntProperty("layer", layer); layer++; segNode->SetProperty("layer", mitk::IntProperty::New(layer)); this->CheckRenderingState(); } else { this->SetToolSelectionBoxesEnabled( false ); this->UpdateWarningLabel(tr("Select or create a segmentation")); } } else { m_Controls->segImageSelector->SetNodePredicate(m_IsASegmentationImagePredicate); this->UpdateWarningLabel(tr("Please select an image!")); this->SetToolSelectionBoxesEnabled( false ); } } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList nodes) { if (nodes.empty()) { this->UpdateWarningLabel(tr("Select or create a segmentation")); this->SetToolSelectionBoxesEnabled( false ); return; } auto refNode = m_Controls->patImageSelector->GetSelectedNode(); auto segNode = nodes.front(); if (!refNode) { this->UpdateWarningLabel(tr("Please select the matching patient image!")); this->SetToolSelectionBoxesEnabled(false); this->SetToolManagerSelection(nullptr, segNode); return; } this->CheckRenderingState(); if ( m_Controls->lblSegmentationWarnings->isVisible()) // "this->CheckRenderingState()" caused a warning. we do not need to go any further return; this->SetToolManagerSelection(refNode, segNode); if (segNode) { //Doing this we can assure that the segmenation is always visible if the segmentation and the patient image are //loaded separately int layer(10); refNode->GetIntProperty("layer", layer); layer++; segNode->SetProperty("layer", mitk::IntProperty::New(layer)); } else { this->SetToolSelectionBoxesEnabled(false); this->UpdateWarningLabel(tr("Select or create a segmentation")); } mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); if (!renderWindowPart || !segNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer())) { this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); this->SetToolSelectionBoxesEnabled( false ); } } void QmitkSegmentationView::OnShowMarkerNodes (bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetTools().size(); for(unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(mitk::ToolManagerProvider::GetInstance()->GetToolManager()->GetToolById(i)); if (manualSegmentationTool) { if(state == true) { manualSegmentationTool->SetShowMarkerNodes( true ); } else { manualSegmentationTool->SetShowMarkerNodes( false ); } } } } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode *node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* _3DRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, _3DRenderWindow->GetRenderer())) { selectedRenderWindow = _3DRenderWindow; } // make node visible if (selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t+1).c_str())-1; { ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); } selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList &nodes) { if (nodes.size() != 0) { std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } } void QmitkSegmentationView::OnTabWidgetChanged(int id) { //always disable tools on tab changed mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); //2D Tab ID = 0 //3D Tab ID = 1 if (id == 0) { //Hide 3D selection box, show 2D selection box m_Controls->m_ManualToolSelectionBox3D->hide(); m_Controls->m_ManualToolSelectionBox2D->show(); //Deactivate possible active tool //TODO Remove possible visible interpolations -> Maybe changes in SlicesInterpolator } else { //Hide 3D selection box, show 2D selection box m_Controls->m_ManualToolSelectionBox2D->hide(); m_Controls->m_ManualToolSelectionBox3D->show(); //Deactivate possible active tool } } void QmitkSegmentationView::SetToolManagerSelection(mitk::DataNode* referenceData, mitk::DataNode* workingData) { // called as a result of new BlueBerry selections // tells the ToolManager for manual segmentation about new selections // updates GUI information about what the user should select mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); toolManager->SetReferenceData(const_cast(referenceData)); toolManager->SetWorkingData(const_cast(workingData)); m_Controls->btnNewSegmentation->setEnabled(referenceData != nullptr); } void QmitkSegmentationView::ForceDisplayPreferencesUponAllImages() { if (!m_Parent) { return; } // check all images and segmentations in DataStorage: // (items in brackets are implicitly done by previous steps) // 1. // if a reference image is selected, // show the reference image // and hide all other images (orignal and segmentation), // (and hide all segmentations of the other original images) // and show all the reference's segmentations // if no reference image is selected, do do nothing // // 2. // if a segmentation is selected, // show it // (and hide all all its siblings (childs of the same parent, incl, nullptr parent)) // if no segmentation is selected, do nothing if (!m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); mitk::DataNode::Pointer referenceData = toolManager->GetReferenceData(0); mitk::DataNode::Pointer workingData = toolManager->GetWorkingData(0); // 1. if (referenceData.IsNotNull()) { // iterate all images mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_IsASegmentationImagePredicate); for ( mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { mitk::DataNode* node = *iter; // apply display preferences ApplyDisplayOptions(node); // set visibility node->SetVisibility(node == referenceData); } } // 2. if (workingData.IsNotNull()) workingData->SetVisibility(true); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (!node) { return; } mitk::BoolProperty::Pointer drawOutline = mitk::BoolProperty::New(GetPreferences()->GetBool("draw outline", true)); mitk::LabelSetImage* labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is actually a multi label segmentation, // but its outline property can be set in the 'single label' segmentation preference page as well node->SetProperty("labelset.contour.active", drawOutline); //node->SetProperty("opacity", mitk::FloatProperty::New(drawOutline->GetValue() ? 1.0f : 0.3f)); // force render window update to show outline node->GetData()->Modified(); } else { // node is a 'single label' segmentation bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", drawOutline); node->SetProperty("outline width", mitk::FloatProperty::New(2.0)); //node->SetProperty("opacity", mitk::FloatProperty::New(drawOutline->GetValue() ? 1.0f : 0.3f)); // force render window update to show outline node->GetData()->Modified(); } } } void QmitkSegmentationView::CheckRenderingState() { mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); mitk::DataNode* workingNode = m_Controls->segImageSelector->GetSelectedNode(); if (!workingNode) { this->SetToolSelectionBoxesEnabled(false); this->UpdateWarningLabel(tr("Select or create a segmentation")); return; } bool selectedNodeIsVisible = renderWindowPart && workingNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); - if (!selectedNodeIsVisible) { this->SetToolSelectionBoxesEnabled(false); this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); return; } /* * Here we check whether the geometry of the selected segmentation image if aligned with the worldgeometry * At the moment it is not supported to use a geometry different from the selected image for reslicing. * For further information see Bug 16063 */ + const mitk::BaseGeometry *workingNodeGeo = workingNode->GetData()->GetGeometry(); + const mitk::BaseGeometry* worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); + if (nullptr != workingNodeGeo && nullptr != worldGeo) + { + if (mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)) + { + this->SetToolManagerSelection(m_Controls->patImageSelector->GetSelectedNode(), workingNode); + this->SetToolSelectionBoxesEnabled(true); + this->UpdateWarningLabel(""); + return; + } + } - const mitk::BaseGeometry* worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); - - if (workingNode && worldGeo) - { - - const mitk::BaseGeometry* workingNodeGeo = workingNode->GetData()->GetGeometry(); - const mitk::BaseGeometry* worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); - - if (mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)) - { - this->SetToolManagerSelection(m_Controls->patImageSelector->GetSelectedNode(), workingNode); - this->SetToolSelectionBoxesEnabled(true); - this->UpdateWarningLabel(""); - return; - } - } - - this->SetToolManagerSelection(m_Controls->patImageSelector->GetSelectedNode(), nullptr); - this->SetToolSelectionBoxesEnabled(false); - this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); + this->SetToolManagerSelection(m_Controls->patImageSelector->GetSelectedNode(), nullptr); + this->SetToolSelectionBoxesEnabled(false); + this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.size() == 0) m_Controls->lblSegmentationWarnings->hide(); else m_Controls->lblSegmentationWarnings->show(); m_Controls->lblSegmentationWarnings->setText("" + text + ""); } void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { // setup the basic GUI of this view m_Parent = parent; m_Controls = new Ui::QmitkSegmentationControls; m_Controls->setupUi(parent); m_Controls->patImageSelector->SetDataStorage(GetDataStorage()); m_Controls->patImageSelector->SetNodePredicate(m_IsAPatientImagePredicate); m_Controls->patImageSelector->SetSelectionIsOptional(false); m_Controls->patImageSelector->SetInvalidInfo("Select an image."); m_Controls->patImageSelector->SetPopUpTitel("Select an image."); m_Controls->patImageSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); UpdateWarningLabel(tr("Please select an image")); if (m_Controls->patImageSelector->GetSelectedNode().IsNotNull()) { UpdateWarningLabel(tr("Select or create a new segmentation")); } m_Controls->segImageSelector->SetDataStorage(GetDataStorage()); m_Controls->segImageSelector->SetNodePredicate(m_IsASegmentationImagePredicate); m_Controls->segImageSelector->SetSelectionIsOptional(false); m_Controls->segImageSelector->SetInvalidInfo("Select a segmentation."); m_Controls->segImageSelector->SetPopUpTitel("Select a segmentation."); m_Controls->segImageSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); if (m_Controls->segImageSelector->GetSelectedNode().IsNotNull()) { UpdateWarningLabel(""); } mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); assert(toolManager); toolManager->SetDataStorage(*(GetDataStorage())); toolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Paint Wipe 'Region Growing' Fill Erase 'Live Wire' '2D Fast Marching'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Fast Marching 3D' 'Region Growing 3D' Watershed Picking"); #ifndef __APPLE__ segTools3D.append(" nnUNet"); // plugin not enabled for MacOS #endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = toolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // all part of open source MITK m_Controls->m_ManualToolSelectionBox2D->setEnabled(true); m_Controls->m_ManualToolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox2D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer2D); m_Controls->m_ManualToolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->m_ManualToolSelectionBox2D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox2D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls->m_ManualToolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); //setup 3D Tools m_Controls->m_ManualToolSelectionBox3D->setEnabled(true); m_Controls->m_ManualToolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->m_ManualToolSelectionBox3D->SetToolGUIArea(m_Controls->m_ManualToolGUIContainer3D); //specify tools to be added to 3D Tool area m_Controls->m_ManualToolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->m_ManualToolSelectionBox3D->SetLayoutColumns(3); m_Controls->m_ManualToolSelectionBox3D->SetEnabledMode(QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); //Hide 3D selection box, show 2D selection box m_Controls->m_ManualToolSelectionBox3D->hide(); m_Controls->m_ManualToolSelectionBox2D->show(); // update the list of segmentations toolManager->NewNodeObjectsGenerated += mitk::MessageDelegate1(this, &QmitkSegmentationView::NewNodeObjectsGenerated); // create signal/slot connections connect(m_Controls->patImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnPatientSelectionChanged); connect(m_Controls->segImageSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); connect(m_Controls->btnNewSegmentation, &QToolButton::clicked, this, &QmitkSegmentationView::CreateNewSegmentation); connect(m_Controls->tabWidgetSegmentationTools, &QTabWidget::currentChanged, this, &QmitkSegmentationView::OnTabWidgetChanged); connect(m_Controls->m_SlicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); // set callback function for already existing nodes (images & segmentations) mitk::DataStorage::SetOfObjects::ConstPointer allImages = GetDataStorage()->GetSubset(m_IsOfTypeImagePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { mitk::DataNode* node = *iter; itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::OnVisiblePropertyChanged); m_WorkingDataObserverTags.insert(std::pair(node, node->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command))); } itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::CheckRenderingState); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); SetToolManagerSelection(m_Controls->patImageSelector->GetSelectedNode(), m_Controls->segImageSelector->GetSelectedNode()); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) this->RenderWindowPartActivated(m_RenderWindowPart); //Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->patImageSelector->SetAutoSelectNewNodes(true); m_Controls->segImageSelector->SetAutoSelectNewNodes(true); } void QmitkSegmentationView::SetFocus() { m_Controls->btnNewSegmentation->setFocus(); } void QmitkSegmentationView::OnManualTool2DSelected(int id) { if (id >= 0) { std::string text = "Active Tool: \""; mitk::ToolManager* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); text += toolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = toolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } else { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); } } void QmitkSegmentationView::ResetMouseCursor() { if ( m_MouseCursorSet ) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor( const us::ModuleResource& resource, int hotspotX, int hotspotY ) { // Remove previously set mouse cursor if (m_MouseCursorSet) this->ResetMouseCursor(); if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::SetToolSelectionBoxesEnabled(bool status) { if (status) { m_Controls->m_ManualToolSelectionBox2D->RecreateButtons(); m_Controls->m_ManualToolSelectionBox3D->RecreateButtons(); } m_Controls->m_ManualToolSelectionBox2D->setEnabled(status); m_Controls->m_ManualToolSelectionBox3D->setEnabled(status); m_Controls->m_SlicesInterpolator->setEnabled(status); } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkThresholdAction.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkThresholdAction.cpp deleted file mode 100644 index 01413c81a0..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkThresholdAction.cpp +++ /dev/null @@ -1,113 +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 "QmitkThresholdAction.h" - -// MITK -#include -#include -#include - -// Qt -#include -#include -#include -#include - -using namespace berry; -using namespace mitk; -using namespace std; - -QmitkThresholdAction::QmitkThresholdAction() -{ -} - -QmitkThresholdAction::~QmitkThresholdAction() -{ -} - -void QmitkThresholdAction::Run(const QList &selectedNodes) -{ - m_ThresholdingToolManager = ToolManager::New(m_DataStorage); - - m_ThresholdingToolManager->RegisterClient(); - - Tool *binaryThresholdTool = m_ThresholdingToolManager->GetToolById(m_ThresholdingToolManager->GetToolIdByToolType()); - if (binaryThresholdTool != nullptr) - { - QmitkBinaryThresholdToolGUI *gui = dynamic_cast(binaryThresholdTool->GetGUI("Qmitk", "GUI").GetPointer()); - if (gui != nullptr) - { - QDialog thresholdingDialog(QApplication::activeWindow(), Qt::Window | Qt::WindowStaysOnTopHint); - - thresholdingDialog.setWindowFlags(thresholdingDialog.windowFlags() & ~Qt::WindowMinimizeButtonHint); - - QDialogButtonBox* buttonBox = new QDialogButtonBox(QDialogButtonBox::Cancel); - connect(buttonBox, SIGNAL(rejected()), &thresholdingDialog, SLOT(reject())); - connect(gui, SIGNAL(thresholdAccepted()), &thresholdingDialog, SLOT(reject())); - - QVBoxLayout *layout = new QVBoxLayout; - layout->setContentsMargins(3, 3, 3, 3); - - gui->SetTool(binaryThresholdTool); - gui->setParent(&thresholdingDialog); - - layout->addWidget(gui); - layout->addWidget(buttonBox); - - thresholdingDialog.setLayout(layout); - thresholdingDialog.setMinimumWidth(350); - - m_SelectedNode = selectedNodes[0]; - m_ThresholdingToolManager->SetReferenceData(selectedNodes[0]); - m_ThresholdingToolManager->ActivateTool(m_ThresholdingToolManager->GetToolIdByToolType()); - - m_ThresholdingToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkThresholdAction::OnThresholdingToolManagerToolModified); - thresholdingDialog.exec(); - m_ThresholdingToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkThresholdAction::OnThresholdingToolManagerToolModified); - - m_ThresholdingToolManager->SetReferenceData(nullptr); - m_ThresholdingToolManager->ActivateTool(-1); - m_SelectedNode = nullptr; - } - } - - m_ThresholdingToolManager->UnregisterClient(); -} - -void QmitkThresholdAction::OnThresholdingToolManagerToolModified() -{ - if (m_ThresholdingToolManager.IsNotNull()) - { - if (m_ThresholdingToolManager->GetActiveToolID() < 0) - { - m_ThresholdingToolManager->SetReferenceData(m_SelectedNode); - m_ThresholdingToolManager->ActivateTool(m_ThresholdingToolManager->GetToolIdByToolType()); - } - } -} - -void QmitkThresholdAction::SetDataStorage(DataStorage *dataStorage) -{ - m_DataStorage = dataStorage; -} - -void QmitkThresholdAction::SetSmoothed(bool) -{ -} - -void QmitkThresholdAction::SetDecimated(bool) -{ -} - -void QmitkThresholdAction::SetFunctionality(QtViewPart* /*view*/) -{ -} diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkThresholdAction.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkThresholdAction.h deleted file mode 100644 index 99e6cab6d1..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkThresholdAction.h +++ /dev/null @@ -1,53 +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 QMITKTHRESHOLDACTION_H -#define QMITKTHRESHOLDACTION_H - -#include - -// Parent classes -#include -#include - -// Data members -#include -#include - - -class MITK_QT_SEGMENTATION QmitkThresholdAction : public QObject, public mitk::IContextMenuAction -{ - Q_OBJECT - Q_INTERFACES(mitk::IContextMenuAction) - -public: - QmitkThresholdAction(); - ~QmitkThresholdAction() override; - - // IContextMenuAction - void Run(const QList &selectedNodes) override; - void SetDataStorage(mitk::DataStorage *dataStorage) override; - void SetSmoothed(bool smoothed) override; - void SetDecimated(bool decimated) override; - void SetFunctionality(berry::QtViewPart* view) override; - - void OnThresholdingToolManagerToolModified(); - -private: - QmitkThresholdAction(const QmitkThresholdAction &); - QmitkThresholdAction & operator=(const QmitkThresholdAction &); - - mitk::DataNode::Pointer m_SelectedNode; - mitk::DataStorage::Pointer m_DataStorage; - mitk::ToolManager::Pointer m_ThresholdingToolManager; -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp index 75912cd1a5..6d27f32fde 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp @@ -1,63 +1,61 @@ /*============================================================================ 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 "QmitkSegmentationView.h" -#include "QmitkThresholdAction.h" #include "QmitkCreatePolygonModelAction.h" #include "QmitkAutocropAction.h" #include "QmitkAutocropLabelSetImageAction.h" #include "QmitkSegmentationPreferencePage.h" #include "SegmentationUtilities/QmitkSegmentationUtilitiesView.h" using namespace mitk; ctkPluginContext* PluginActivator::m_context = nullptr; PluginActivator* PluginActivator::m_Instance = nullptr; PluginActivator::PluginActivator() { m_Instance = this; } PluginActivator::~PluginActivator() { m_Instance = nullptr; } void PluginActivator::start(ctkPluginContext *context) { BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationView, context) - BERRY_REGISTER_EXTENSION_CLASS(QmitkThresholdAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkCreatePolygonModelAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkAutocropAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkAutocropLabelSetImageAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationPreferencePage, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationUtilitiesView, context) this->m_context = context; } void PluginActivator::stop(ctkPluginContext *) { this->m_context = nullptr; } PluginActivator* PluginActivator::getDefault() { return m_Instance; } ctkPluginContext*PluginActivator::getContext() { return m_context; } diff --git a/Plugins/org.mitk.gui.qt.stdmultiwidgeteditor/src/QmitkStdMultiWidgetEditor.cpp b/Plugins/org.mitk.gui.qt.stdmultiwidgeteditor/src/QmitkStdMultiWidgetEditor.cpp index 1d86091865..efa5d48b7c 100644 --- a/Plugins/org.mitk.gui.qt.stdmultiwidgeteditor/src/QmitkStdMultiWidgetEditor.cpp +++ b/Plugins/org.mitk.gui.qt.stdmultiwidgeteditor/src/QmitkStdMultiWidgetEditor.cpp @@ -1,399 +1,398 @@ /*============================================================================ 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 "QmitkStdMultiWidgetEditor.h" #include #include #include #include #include #include #include #include #include // mitk qt widgets module #include #include #include #include // mitk gui qt common plugin #include const QString QmitkStdMultiWidgetEditor::EDITOR_ID = "org.mitk.editors.stdmultiwidget"; struct QmitkStdMultiWidgetEditor::Impl final { Impl(); ~Impl() = default; QmitkInteractionSchemeToolBar* m_InteractionSchemeToolBar; QmitkLevelWindowWidget* m_LevelWindowWidget; std::unique_ptr m_MultiWidgetDecorationManager; }; QmitkStdMultiWidgetEditor::Impl::Impl() : m_InteractionSchemeToolBar(nullptr) , m_LevelWindowWidget(nullptr) { // nothing here } ////////////////////////////////////////////////////////////////////////// // QmitkStdMultiWidgetEditor ////////////////////////////////////////////////////////////////////////// QmitkStdMultiWidgetEditor::QmitkStdMultiWidgetEditor() : QmitkAbstractMultiWidgetEditor() , m_Impl(std::make_unique()) { // nothing here } QmitkStdMultiWidgetEditor::~QmitkStdMultiWidgetEditor() { GetSite()->GetPage()->RemovePartListener(this); } berry::IPartListener::Events::Types QmitkStdMultiWidgetEditor::GetPartEventTypes() const { return Events::CLOSED | Events::OPENED | Events::HIDDEN | Events::VISIBLE; } void QmitkStdMultiWidgetEditor::PartClosed(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkStdMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->RemovePlanesFromDataStorage(); multiWidget->ActivateMenuWidget(false); } } } void QmitkStdMultiWidgetEditor::PartOpened(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkStdMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->AddPlanesToDataStorage(); multiWidget->ActivateMenuWidget(true); } } } void QmitkStdMultiWidgetEditor::PartHidden(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkStdMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(false); } } } void QmitkStdMultiWidgetEditor::PartVisible(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkStdMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(true); } } } QmitkLevelWindowWidget* QmitkStdMultiWidgetEditor::GetLevelWindowWidget() const { return m_Impl->m_LevelWindowWidget; } void QmitkStdMultiWidgetEditor::EnableSlicingPlanes(bool enable) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr == multiWidget) { return; } multiWidget->SetWidgetPlanesVisibility(enable); } bool QmitkStdMultiWidgetEditor::IsSlicingPlanesEnabled() const { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr == multiWidget) { return false; } mitk::DataNode::Pointer node = multiWidget->GetWidgetPlane1(); if (node.IsNotNull()) { bool visible = false; node->GetVisibility(visible, nullptr); return visible; } else { return false; } } void QmitkStdMultiWidgetEditor::OnInteractionSchemeChanged(mitk::InteractionSchemeSwitcher::InteractionScheme scheme) { const auto& multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { return; } if (mitk::InteractionSchemeSwitcher::PACSStandard == scheme) { m_Impl->m_InteractionSchemeToolBar->setVisible(true); } else { m_Impl->m_InteractionSchemeToolBar->setVisible(false); } QmitkAbstractMultiWidgetEditor::OnInteractionSchemeChanged(scheme); } void QmitkStdMultiWidgetEditor::ShowLevelWindowWidget(bool show) { if (show) { m_Impl->m_LevelWindowWidget->disconnect(this); m_Impl->m_LevelWindowWidget->SetDataStorage(GetDataStorage()); m_Impl->m_LevelWindowWidget->show(); } else { m_Impl->m_LevelWindowWidget->disconnect(this); m_Impl->m_LevelWindowWidget->hide(); } } void QmitkStdMultiWidgetEditor::SetFocus() { const auto& multiWidget = GetMultiWidget(); if (nullptr != multiWidget) { multiWidget->setFocus(); } } void QmitkStdMultiWidgetEditor::CreateQtPartControl(QWidget* parent) { QHBoxLayout* layout = new QHBoxLayout(parent); layout->setContentsMargins(0, 0, 0, 0); berry::IBerryPreferences* preferences = dynamic_cast(GetPreferences().GetPointer()); auto multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { multiWidget = new QmitkStdMultiWidget(parent); // create left toolbar: interaction scheme toolbar to switch how the render window navigation behaves (in PACS mode) if (nullptr == m_Impl->m_InteractionSchemeToolBar) { m_Impl->m_InteractionSchemeToolBar = new QmitkInteractionSchemeToolBar(parent); layout->addWidget(m_Impl->m_InteractionSchemeToolBar); } m_Impl->m_InteractionSchemeToolBar->SetInteractionEventHandler(multiWidget->GetInteractionEventHandler()); multiWidget->SetDataStorage(GetDataStorage()); multiWidget->InitializeMultiWidget(); SetMultiWidget(multiWidget); } layout->addWidget(multiWidget); // create level window slider on the right side if (nullptr == m_Impl->m_LevelWindowWidget) { m_Impl->m_LevelWindowWidget = new QmitkLevelWindowWidget(parent); m_Impl->m_LevelWindowWidget->setObjectName(QString::fromUtf8("levelWindowWidget")); QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth(m_Impl->m_LevelWindowWidget->sizePolicy().hasHeightForWidth()); m_Impl->m_LevelWindowWidget->setSizePolicy(sizePolicy); m_Impl->m_LevelWindowWidget->setMaximumWidth(50); } layout->addWidget(m_Impl->m_LevelWindowWidget); m_Impl->m_MultiWidgetDecorationManager = std::make_unique(multiWidget); GetSite()->GetPage()->AddPartListener(this); InitializePreferences(preferences); OnPreferencesChanged(preferences); } void QmitkStdMultiWidgetEditor::OnPreferencesChanged(const berry::IBerryPreferences* preferences) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr == multiWidget) { return; } // change and apply decoration preferences GetPreferenceDecorations(preferences); m_Impl->m_MultiWidgetDecorationManager->DecorationPreferencesChanged(preferences); QmitkAbstractMultiWidget::RenderWindowWidgetMap renderWindowWidgets = multiWidget->GetRenderWindowWidgets(); int i = 0; for (const auto& renderWindowWidget : renderWindowWidgets) { auto decorationColor = renderWindowWidget.second->GetDecorationColor(); multiWidget->SetDecorationColor(i, decorationColor); ++i; } int crosshairgapsize = preferences->GetInt("crosshair gap size", 32); multiWidget->GetWidgetPlane1()->SetIntProperty("Crosshair.Gap Size", crosshairgapsize); multiWidget->GetWidgetPlane2()->SetIntProperty("Crosshair.Gap Size", crosshairgapsize); multiWidget->GetWidgetPlane3()->SetIntProperty("Crosshair.Gap Size", crosshairgapsize); // zooming and panning preferences bool constrainedZooming = preferences->GetBool("Use constrained zooming and panning", true); mitk::RenderingManager::GetInstance()->SetConstrainedPanningZooming(constrainedZooming); // mouse modes switcher toolbar bool PACSInteractionScheme = preferences->GetBool("PACS like mouse interaction", false); OnInteractionSchemeChanged(PACSInteractionScheme ? mitk::InteractionSchemeSwitcher::PACSStandard : mitk::InteractionSchemeSwitcher::MITKStandard); // level window setting bool showLevelWindowWidget = preferences->GetBool("Show level/window widget", true); ShowLevelWindowWidget(showLevelWindowWidget); - mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(GetDataStorage()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkStdMultiWidgetEditor::InitializePreferences(berry::IBerryPreferences * preferences) { auto multiWidget = this->GetMultiWidget(); if (nullptr == multiWidget) return; this->GetPreferenceDecorations(preferences); // Override if preferences are defined for (const auto& renderWindowWidget : multiWidget->GetRenderWindowWidgets()) { auto widgetName = renderWindowWidget.second->GetWidgetName(); auto gradientBackgroundColors = renderWindowWidget.second->GetGradientBackgroundColors(); preferences->Put(widgetName + " first background color", this->MitkColorToHex(gradientBackgroundColors.first)); preferences->Put(widgetName + " second background color", this->MitkColorToHex(gradientBackgroundColors.second)); auto decorationColor = renderWindowWidget.second->GetDecorationColor(); preferences->Put(widgetName + " decoration color", this->MitkColorToHex(decorationColor)); auto cornerAnnotation = renderWindowWidget.second->GetCornerAnnotationText(); preferences->Put(widgetName + " corner annotation", QString::fromStdString(cornerAnnotation)); } } void QmitkStdMultiWidgetEditor::GetPreferenceDecorations(const berry::IBerryPreferences * preferences) { auto multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr == multiWidget) return; auto hexBlack = "#000000"; auto gradientBlack = "#191919"; auto gradientGray = "#7F7F7F"; auto renderWindowWidgets = multiWidget->GetRenderWindowWidgets(); int i = 0; for (const auto& renderWindowWidget : renderWindowWidgets) { auto widgetName = renderWindowWidget.second->GetWidgetName(); if (mitk::BaseRenderer::Standard3D == mitk::BaseRenderer::GetInstance(renderWindowWidget.second->GetRenderWindow()->GetVtkRenderWindow())->GetMapperID()) { auto upper = preferences->Get(widgetName + " first background color", gradientBlack); auto lower = preferences->Get(widgetName + " second background color", gradientGray); renderWindowWidget.second->SetGradientBackgroundColors(HexColorToMitkColor(upper), HexColorToMitkColor(lower)); } else { auto upper = preferences->Get(widgetName + " first background color", hexBlack); auto lower = preferences->Get(widgetName + " second background color", hexBlack); renderWindowWidget.second->SetGradientBackgroundColors(HexColorToMitkColor(upper), HexColorToMitkColor(lower)); } auto defaultDecorationColor = multiWidget->GetDecorationColor(i); auto decorationColor = preferences->Get(widgetName + " decoration color", MitkColorToHex(defaultDecorationColor)); renderWindowWidget.second->SetDecorationColor(HexColorToMitkColor(decorationColor)); auto defaultCornerAnnotation = renderWindowWidget.second->GetCornerAnnotationText(); auto cornerAnnotation = preferences->Get(widgetName + " corner annotation", QString::fromStdString(defaultCornerAnnotation)); renderWindowWidget.second->SetCornerAnnotationText(cornerAnnotation.toStdString()); ++i; } } mitk::Color QmitkStdMultiWidgetEditor::HexColorToMitkColor(const QString& hexColor) { QColor qColor(hexColor); mitk::Color returnColor; float colorMax = 255.0f; if (hexColor.isEmpty()) // default value { returnColor[0] = 1.0; returnColor[1] = 1.0; returnColor[2] = 1.0; MITK_ERROR << "Using default color for unknown hex color " << qPrintable(hexColor); } else { returnColor[0] = qColor.red() / colorMax; returnColor[1] = qColor.green() / colorMax; returnColor[2] = qColor.blue() / colorMax; } return returnColor; } QString QmitkStdMultiWidgetEditor::MitkColorToHex(const mitk::Color& color) { QColor returnColor; float colorMax = 255.0f; returnColor.setRed(static_cast(color[0] * colorMax + 0.5)); returnColor.setGreen(static_cast(color[1] * colorMax + 0.5)); returnColor.setBlue(static_cast(color[2] * colorMax + 0.5)); return returnColor.name(); }