diff --git a/Documentation/Doxygen/2-UserManual/MITKPluginGeneralManualsList.dox b/Documentation/Doxygen/2-UserManual/MITKPluginGeneralManualsList.dox index 1b37183912..1621c5b272 100644 --- a/Documentation/Doxygen/2-UserManual/MITKPluginGeneralManualsList.dox +++ b/Documentation/Doxygen/2-UserManual/MITKPluginGeneralManualsList.dox @@ -1,32 +1,32 @@ /** \page PluginListGeneralPage List of General Purpose Plugins \li \subpage org_mitk_views_basicimageprocessing \li \subpage org_mitk_views_datamanager \li \subpage org_mitk_views_properties - \li \subpage org_mitk_gui_qt_dicom + \li \subpage org_mitk_editors_dicombrowser \li \subpage org_mitk_gui_qt_dicominspector - \li \subpage org_mitk_gui_qt_imagecropper + \li \subpage org_mitk_views_imagecropper \li \subpage org_mitk_views_imagenavigator \li \subpage org_blueberry_views_logview \li \subpage org_mitk_gui_qt_matchpoint_algorithm_batch \li \subpage org_mitk_gui_qt_matchpoint_algorithm_browser \li \subpage org_mitk_gui_qt_matchpoint_algorithm_control \li \subpage org_mitk_gui_qt_matchpoint_evaluator \li \subpage org_mitk_gui_qt_matchpoint_framereg \li \subpage org_mitk_gui_qt_matchpoint_manipulator \li \subpage org_mitk_gui_qt_matchpoint_mapper \li \subpage org_mitk_gui_qt_matchpoint_visualizer \li \subpage org_mitk_gui_qt_measurementtoolbox \li \subpage org_mitk_views_moviemaker \li \subpage org_mitk_views_multilabelsegmentation \li \subpage org_mitk_views_pointsetinteraction \li \subpage org_mitk_gui_qt_python \li \subpage org_mitk_gui_qt_remeshing \li \subpage org_mitk_views_screenshotmaker \li \subpage org_mitk_views_segmentation \li \subpage org_mitk_gui_qt_flow_segmentation \li \subpage org_mitk_gui_qt_viewnavigator \li \subpage org_mitk_views_volumevisualization */ diff --git a/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox b/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox index 89a86430bf..e191939e7c 100644 --- a/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox +++ b/Documentation/Doxygen/2-UserManual/MITKPluginManualsList.dox @@ -1,84 +1,84 @@ /** \page PluginListPage MITK Plugin Manuals The plugins and bundles provide much of the extended functionality of MITK. Each encapsulates a solution to a problem and associated features. This way one can easily assemble the necessary capabilites for a workflow without adding a lot of bloat, by combining plugins as needed. \subpage PluginListGeneralPage \subpage PluginListSpecificPage */ diff --git a/Documentation/Doxygen/2-UserManual/MITKPluginSpecificManualsList.dox b/Documentation/Doxygen/2-UserManual/MITKPluginSpecificManualsList.dox index 1edb2f4d54..8be8a5f620 100644 --- a/Documentation/Doxygen/2-UserManual/MITKPluginSpecificManualsList.dox +++ b/Documentation/Doxygen/2-UserManual/MITKPluginSpecificManualsList.dox @@ -1,45 +1,45 @@ /** \page PluginListSpecificPage List of Application-specific Plugins \li \subpage org_mitk_gui_qt_aicpregistration \li \subpage org_mitk_gui_qt_cest \li \subpage org_mitk_gui_qt_classificationsegmentation \li \subpage org_mitk_gui_qt_flowapplication \li \subpage org_mitk_views_cmdlinemodules \li \subpage org_mitk_gui_qt_pharmacokinetics_concentration_mri - \li \subpage org_mitk_gui_qt_pharmacokinetics_mri + \li \subpage org_mitk_views_pharmacokinetics_mri \li \subpage org_mitk_gui_qt_pharmacokinetics_pet \li \subpage org_mitk_gui_qt_eventrecorder \li \subpage org_mitk_gui_qt_examples \li \subpage org_mitk_gui_qt_geometrytools \li \subpage org_mitk_gui_qt_igtexample \li \subpage org_mitk_gui_qt_igtlplugin \li \subpage org_mitk_gui_qt_igttracking \li \subpage org_mitk_gui_qt_igttrackingsemiautomaticmeasurement \li \subpage org_mitk_views_imagestatistics \li \subpage org_mitk_gui_qt_lasercontrol \li \subpage org_mitk_gui_qt_fit_demo \li \subpage org_mitk_gui_qt_fit_genericfitting \li \subpage org_mitk_gui_qt_fit_inspector \li \subpage org_mitkexamplesopencv \li \subpage org_mitk_gui_qt_overlaymanager \li \subpage org_mitk_gui_qt_mitkphenotyping \li \subpage org_mitk_gui_qt_photoacoustics_pausmotioncompensation \li \subpage org_mitk_example_gui_pcaexample \li \subpage org_mitk_gui_qt_preprocessing_resampling \li \subpage org_mitk_gui_qt_pharmacokinetics_curvedescriptor \li \subpage org_mitk_gui_qt_photoacoustics_imageprocessing \li \subpage org_mitk_gui_qt_pharmacokinetics_simulation \li \subpage org_mitk_gui_qt_pointsetinteractionmultispectrum \li \subpage org_mitk_gui_qt_renderwindowmanager \li \subpage org_mitk_gui_qt_photoacoustics_spectralunmixing \li \subpage org_mitk_gui_qt_spectrocamrecorder \li \subpage org_surfacematerialeditor \li \subpage org_blueberry_ui_qt_objectinspector \li \subpage org_toftutorial \li \subpage org_mitk_gui_qt_ultrasound \li \subpage org_mitk_gui_qt_igt_app_echotrack \li \subpage org_mitk_gui_qt_xnat */ \ No newline at end of file diff --git a/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h b/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h index 69a7cf348b..6a22193017 100644 --- a/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h +++ b/Modules/PlanarFigure/include/mitkPlanarFigureInteractor.h @@ -1,201 +1,197 @@ /*============================================================================ 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 MITKPLANARFIGUREINTERACTOR_H_HEADER_INCLUDED -#define MITKPLANARFIGUREINTERACTOR_H_HEADER_INCLUDED +#ifndef MITKPLANARFIGUREINTERACTOR_H +#define MITKPLANARFIGUREINTERACTOR_H #include #include "mitkCommon.h" #include "mitkDataInteractor.h" #include "mitkNumericTypes.h" #pragma GCC visibility push(default) #include #pragma GCC visibility pop namespace mitk { class DataNode; class PlaneGeometry; class PlanarFigure; class PositionEvent; class BaseRenderer; class InteractionPositionEvent; class StateMachineAction; #pragma GCC visibility push(default) // Define events for PlanarFigure interaction notifications itkEventMacro(PlanarFigureEvent, itk::AnyEvent); itkEventMacro(StartPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(EndPlacementPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(SelectPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(StartInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(EndInteractionPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(StartHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(EndHoverPlanarFigureEvent, PlanarFigureEvent); itkEventMacro(ContextMenuPlanarFigureEvent, PlanarFigureEvent); #pragma GCC visibility pop /** * \brief Interaction with mitk::PlanarFigure objects via control-points * * @ingroup MitkPlanarFigureModule */ class MITKPLANARFIGURE_EXPORT PlanarFigureInteractor : public DataInteractor { public: mitkClassMacro(PlanarFigureInteractor, DataInteractor); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - /** \brief Sets the amount of precision */ - void SetPrecision(ScalarType precision); + /** \brief Sets the amount of precision */ + void SetPrecision(ScalarType precision); /** \brief Sets the minimal distance between two control points. */ void SetMinimumPointDistance(ScalarType minimumDistance); protected: PlanarFigureInteractor(); ~PlanarFigureInteractor() override; void ConnectActionsAndFunctions() override; //////// Conditions //////// bool CheckFigurePlaced(const InteractionEvent *interactionEvent); bool CheckFigureHovering(const InteractionEvent *interactionEvent); bool CheckControlPointHovering(const InteractionEvent *interactionEvent); bool CheckSelection(const InteractionEvent *interactionEvent); bool CheckPointValidity(const InteractionEvent *interactionEvent); bool CheckFigureFinished(const InteractionEvent *interactionEvent); bool CheckResetOnPointSelect(const InteractionEvent *interactionEvent); bool CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent); bool CheckMinimalFigureFinished(const InteractionEvent *interactionEvent); bool CheckFigureIsExtendable(const InteractionEvent *interactionEvent); bool CheckFigureIsDeletable(const InteractionEvent *interactionEvent); bool CheckFigureIsEditable(const InteractionEvent *interactionEvent); //////// Actions //////// void FinalizeFigure(StateMachineAction *, InteractionEvent *interactionEvent); void MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent); void DeselectPoint(StateMachineAction *, InteractionEvent *interactionEvent); void AddPoint(StateMachineAction *, InteractionEvent *interactionEvent); void AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent); void StartHovering(StateMachineAction *, InteractionEvent *interactionEvent); void EndHovering(StateMachineAction *, InteractionEvent *interactionEvent); void DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent); void PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *interactionEvent); void SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent); void HidePreviewPoint(StateMachineAction *, InteractionEvent *interactionEvent); void HideControlPoints(StateMachineAction *, InteractionEvent *interactionEvent); void RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent); void RequestContextMenu(StateMachineAction *, InteractionEvent *interactionEvent); void SelectFigure(StateMachineAction *, InteractionEvent *interactionEvent); void SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent); void EndInteraction(StateMachineAction *, InteractionEvent *interactionEvent); bool FilterEvents(InteractionEvent *interactionEvent, DataNode *) override; /** \brief Used when clicking to determine if a point is too close to the previous point. */ bool IsMousePositionAcceptableAsNewControlPoint(const mitk::InteractionPositionEvent *positionEvent, const PlanarFigure *); bool TransformPositionEventToPoint2D(const InteractionPositionEvent *positionEvent, const PlaneGeometry *planarFigureGeometry, Point2D &point2D); bool TransformObjectToDisplay(const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const; /** \brief Returns true if the first specified point is in proximity of the line defined * the other two point; false otherwise. * * Proximity is defined as the rectangle around the line with pre-defined distance * from the line. */ bool IsPointNearLine(const mitk::Point2D &point, const mitk::Point2D &startPoint, const mitk::Point2D &endPoint, mitk::Point2D &projectedPoint) const; /** \brief Returns true if the point contained in the passed event (in display coordinates) * is over the planar figure (with a pre-defined tolerance range); false otherwise. */ int IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, Point2D &pointProjectedOntoLine) const; /** \brief Returns the index of the marker (control point) over which the point contained * in the passed event (in display coordinates) currently is; -1 if the point is not over * a marker. */ int IsPositionInsideMarker(const InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const; void LogPrintPlanarFigureQuantities(const PlanarFigure *planarFigure); void ConfigurationChanged() override; private: /** \brief to store the value of precision to pick a point */ ScalarType m_Precision; /** \brief Store the minimal distance between two control points. */ ScalarType m_MinimumPointDistance; /** \brief True if the mouse is currently hovering over the image. */ bool m_IsHovering; - - bool m_LastPointWasValid; - - // mitk::PlanarFigure::Pointer m_PlanarFigure; }; } -#endif // MITKPLANARFIGUREINTERACTOR_H_HEADER_INCLUDED +#endif // MITKPLANARFIGUREINTERACTOR_H diff --git a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp index 82f60c6d4c..363c3ef628 100644 --- a/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp +++ b/Modules/PlanarFigure/src/Interactions/mitkPlanarFigureInteractor.cpp @@ -1,949 +1,1113 @@ /*============================================================================ 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. ============================================================================*/ #define PLANARFIGUREINTERACTOR_DBG MITK_DEBUG("PlanarFigureInteractor") << __LINE__ << ": " #include "mitkPlanarFigureInteractor.h" #include "mitkPlanarBezierCurve.h" #include "mitkPlanarCircle.h" #include "mitkPlanarFigure.h" #include "mitkPlanarPolygon.h" #include "mitkInteractionPositionEvent.h" #include "mitkInternalEvent.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include "mitkAbstractTransformGeometry.h" #include "mitkPlaneGeometry.h" mitk::PlanarFigureInteractor::PlanarFigureInteractor() - : DataInteractor(), m_Precision(6.5), m_MinimumPointDistance(25.0), m_IsHovering(false), m_LastPointWasValid(false) + : DataInteractor() + , m_Precision(6.5) + , m_MinimumPointDistance(25.0) + , m_IsHovering(false) { } mitk::PlanarFigureInteractor::~PlanarFigureInteractor() { } void mitk::PlanarFigureInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("figure_is_on_current_slice", CheckFigureOnRenderingGeometry); CONNECT_CONDITION("figure_is_placed", CheckFigurePlaced); CONNECT_CONDITION("minimal_figure_is_finished", CheckMinimalFigureFinished); CONNECT_CONDITION("hovering_above_figure", CheckFigureHovering); CONNECT_CONDITION("hovering_above_point", CheckControlPointHovering); CONNECT_CONDITION("figure_is_selected", CheckSelection); CONNECT_CONDITION("point_is_valid", CheckPointValidity); CONNECT_CONDITION("figure_is_finished", CheckFigureFinished); CONNECT_CONDITION("reset_on_point_select_needed", CheckResetOnPointSelect); CONNECT_CONDITION("points_can_be_added_or_removed", CheckFigureIsExtendable); CONNECT_CONDITION("figure_can_be_deleted", CheckFigureIsDeletable); CONNECT_CONDITION("figure_is_editable", CheckFigureIsEditable); CONNECT_FUNCTION("finalize_figure", FinalizeFigure); CONNECT_FUNCTION("hide_preview_point", HidePreviewPoint) CONNECT_FUNCTION("hide_control_points", HideControlPoints) CONNECT_FUNCTION("set_preview_point_position", SetPreviewPointPosition) CONNECT_FUNCTION("move_current_point", MoveCurrentPoint); CONNECT_FUNCTION("deselect_point", DeselectPoint); CONNECT_FUNCTION("add_new_point", AddPoint); CONNECT_FUNCTION("add_initial_point", AddInitialPoint); CONNECT_FUNCTION("remove_selected_point", RemoveSelectedPoint); CONNECT_FUNCTION("request_context_menu", RequestContextMenu); CONNECT_FUNCTION("select_figure", SelectFigure); CONNECT_FUNCTION("select_point", SelectPoint); CONNECT_FUNCTION("end_interaction", EndInteraction); CONNECT_FUNCTION("start_hovering", StartHovering) CONNECT_FUNCTION("end_hovering", EndHovering); CONNECT_FUNCTION("delete_figure", DeleteFigure); CONNECT_FUNCTION("reset_on_point_select", PerformPointResetOnSelect); } bool mitk::PlanarFigureInteractor::CheckFigurePlaced(const InteractionEvent * /*interactionEvent*/) { - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } bool isFigureFinished = false; planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); return planarFigure->IsPlaced() && isFigureFinished; } void mitk::PlanarFigureInteractor::MoveCurrentPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - auto *positionEvent = dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } - const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return; + } - // Extract point in 2D world coordinates (relative to PlaneGeometry of - // PlanarFigure) + // Extract point in 2D world coordinates (relative to PlaneGeometry of PlanarFigure) Point2D point2D; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D) || !isEditable) { return; } planarFigure->InvokeEvent(StartInteractionPlanarFigureEvent()); // check if the control points shall be hidden during interaction bool hidecontrolpointsduringinteraction = false; GetDataNode()->GetBoolProperty("planarfigure.hidecontrolpointsduringinteraction", hidecontrolpointsduringinteraction); // hide the control points if necessary // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( true ); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", !hidecontrolpointsduringinteraction); // interactionEvent->GetSender()->GetDataStorage()->BlockNodeModifiedEvents( false ); // Move current control point to this point planarFigure->SetCurrentControlPoint(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::FinalizeFigure(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } planarFigure->Modified(); planarFigure->DeselectControlPoint(); planarFigure->RemoveLastControlPoint(); planarFigure->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); GetDataNode()->Modified(); planarFigure->InvokeEvent(EndPlacementPlanarFigureEvent()); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); // Shape might change when figure is finalized, e.g., smoothing of subdivision polygon planarFigure->EvaluateFeatures(); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::EndInteraction(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::FilterEvents(InteractionEvent *interactionEvent, mitk::DataNode * /*dataNode*/) { if (interactionEvent->GetSender() == nullptr) return false; if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard3D) return false; return true; } void mitk::PlanarFigureInteractor::EndHovering(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->ResetPreviewContolPoint(); // Invoke end-hover event once the mouse is exiting the figure area m_IsHovering = false; planarFigure->InvokeEvent(EndHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is no longer in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", false); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::DeleteFigure(StateMachineAction *, InteractionEvent *interactionEvent) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->RemoveAllObservers(); GetDataNode()->RemoveAllObservers(); interactionEvent->GetSender()->GetDataStorage()->Remove(GetDataNode()); RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::PerformPointResetOnSelect(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->ResetOnPointSelect(); } bool mitk::PlanarFigureInteractor::CheckMinimalFigureFinished(const InteractionEvent * /*interactionEvent*/) { - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); - return (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMinimumNumberOfControlPoints()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } + + return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMinimumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureFinished(const InteractionEvent * /*interactionEvent*/) { - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); - return (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } + + return planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints(); } bool mitk::PlanarFigureInteractor::CheckFigureIsExtendable(const InteractionEvent * /*interactionEvent*/) { bool isExtendable(false); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); return isExtendable; } bool mitk::PlanarFigureInteractor::CheckFigureIsDeletable(const InteractionEvent * /*interactionEvent*/) { bool isDeletable(true); GetDataNode()->GetBoolProperty("planarfigure.isdeletable", isDeletable); return isDeletable; } bool mitk::PlanarFigureInteractor::CheckFigureIsEditable(const InteractionEvent * /*interactionEvent*/) { bool isEditable(true); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); return isEditable; } void mitk::PlanarFigureInteractor::DeselectPoint(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } const bool wasSelected = planarFigure->DeselectControlPoint(); if (wasSelected) { // Issue event so that listeners may update themselves planarFigure->Modified(); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); - // GetDataNode()->SetBoolProperty( "planarfigure.ishovering", false ); GetDataNode()->Modified(); } } void mitk::PlanarFigureInteractor::AddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; - - const DataNode::Pointer node = this->GetDataNode(); - const BaseData::Pointer data = node->GetData(); + } /* * Added check for "initiallyplaced" due to bug 13097: * * There are two possible cases in which a point can be inserted into a PlanarPolygon: * * 1. The figure is currently drawn -> the point will be appended at the end of the figure * 2. A point is inserted at a userdefined position after the initial placement of the figure is finished * * In the second case we need to determine the proper insertion index. In the first case the index always has * to be -1 so that the point is appended to the end. * * These changes are necessary because of a macOS specific issue: If a users draws a PlanarPolygon then the * next point to be added moves according to the mouse position. If then the user left clicks in order to add * a point one would assume the last move position is identical to the left click position. This is actually the * case for windows and linux but somehow NOT for mac. Because of the insertion logic of a new point in the * PlanarFigure then for mac the wrong current selected point is determined. * * With this check here this problem can be avoided. However a redesign of the insertion logic should be considered */ + const DataNode::Pointer node = this->GetDataNode(); + const BaseData::Pointer data = node->GetData(); + bool isFigureFinished = false; data->GetPropertyList()->GetBoolProperty("initiallyplaced", isFigureFinished); bool selected = false; bool isEditable = true; node->GetBoolProperty("selected", selected); node->GetBoolProperty("planarfigure.iseditable", isEditable); if (!selected || !isEditable) { return; } - auto *planarFigure = dynamic_cast(data.GetPointer()); + auto planarFigure = dynamic_cast(data.GetPointer()); + if (nullptr == planarFigure) + { + return; + } // We can't derive a new control point from a polyline of a Bezier curve // as all control points contribute to each polyline point. if (dynamic_cast(planarFigure) != nullptr && isFigureFinished) return; - const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return; + } // If the planarFigure already has reached the maximum number if (planarFigure->GetNumberOfControlPoints() >= planarFigure->GetMaximumNumberOfControlPoints()) { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D, projectedPoint; if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return; } // TODO: check segment of polyline we clicked in int nextIndex = -1; // We only need to check which position to insert the control point // when interacting with a PlanarPolygon. For all other types // new control points will always be appended const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); if (dynamic_cast(planarFigure) && isFigureFinished) { nextIndex = this->IsPositionOverFigure(positionEvent, planarFigure, planarFigureGeometry, projectionPlane, projectedPoint); } // Add point as new control point if (planarFigure->IsPreviewControlPointVisible()) { point2D = planarFigure->GetPreviewControlPoint(); } planarFigure->AddControlPoint(point2D, planarFigure->GetControlPointForPolylinePoint(nextIndex, 0)); if (planarFigure->IsPreviewControlPointVisible()) { planarFigure->SelectControlPoint(nextIndex); planarFigure->ResetPreviewContolPoint(); } // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::AddInitialPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } + + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - auto *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); // Invoke event to notify listeners that placement of this PF starts now planarFigure->InvokeEvent(StartPlacementPlanarFigureEvent()); // Use PlaneGeometry of the renderer clicked on for this PlanarFigure auto *planeGeometry = const_cast( dynamic_cast(renderer->GetSliceNavigationController()->GetCurrentPlaneGeometry())); if (planeGeometry != nullptr && abstractTransformGeometry == nullptr) { - planarFigureGeometry = planeGeometry; planarFigure->SetPlaneGeometry(planeGeometry); } else { return; } // Extract point in 2D world coordinates (relative to PlaneGeometry of // PlanarFigure) Point2D point2D; - if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) + if (!this->TransformPositionEventToPoint2D(positionEvent, planeGeometry, point2D)) { return; } // Place PlanarFigure at this point planarFigure->PlaceFigure(point2D); // Re-evaluate features planarFigure->EvaluateFeatures(); // this->LogPrintPlanarFigureQuantities( planarFigure ); // Set a bool property indicating that the figure has been placed in // the current RenderWindow. This is required so that the same render // window can be re-aligned to the PlaneGeometry of the PlanarFigure later // on in an application. GetDataNode()->SetBoolProperty("PlanarFigureInitializedWindow", true, renderer); // Update rendered scene RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::StartHovering(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } if (!m_IsHovering) { // Invoke hover event once when the mouse is entering the figure area m_IsHovering = true; planarFigure->InvokeEvent(StartHoverPlanarFigureEvent()); // Set bool property to indicate that planar figure is currently in "hovering" mode GetDataNode()->SetBoolProperty("planarfigure.ishovering", true); RenderingManager::GetInstance()->RequestUpdateAll(); } } void mitk::PlanarFigureInteractor::SetPreviewPointPosition(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { + return; + } + + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); planarFigure->DeselectControlPoint(); mitk::Point2D pointProjectedOntoLine = positionEvent->GetPointerPositionOnScreen(); bool selected(false); bool isExtendable(false); bool isEditable(true); GetDataNode()->GetBoolProperty("selected", selected); GetDataNode()->GetBoolProperty("planarfigure.isextendable", isExtendable); GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); if (selected && isExtendable && isEditable) { renderer->DisplayToPlane(pointProjectedOntoLine, pointProjectedOntoLine); planarFigure->SetPreviewControlPoint(pointProjectedOntoLine); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PlanarFigureInteractor::HideControlPoints(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", false); } void mitk::PlanarFigureInteractor::HidePreviewPoint(StateMachineAction *, InteractionEvent *) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->ResetPreviewContolPoint(); RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PlanarFigureInteractor::CheckFigureHovering(const InteractionEvent *interactionEvent) { - const auto *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return false; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); - const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::PlaneGeometry *planarFigureGeometry = planarFigure->GetPlaneGeometry(); - auto *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); - const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } + + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return false; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return false; + } + + const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); + const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); mitk::Point2D pointProjectedOntoLine; int previousControlPoint = this->IsPositionOverFigure( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, pointProjectedOntoLine); bool isHovering = (previousControlPoint != -1); + return isHovering; +} - if (isHovering) +bool mitk::PlanarFigureInteractor::CheckControlPointHovering(const InteractionEvent *interactionEvent) +{ + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) { - return true; + return false; } - else + + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) { return false; } - return false; -} + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { + return false; + } -bool mitk::PlanarFigureInteractor::CheckControlPointHovering(const InteractionEvent *interactionEvent) -{ - const auto *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { return false; + } - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::PlaneGeometry *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); - if (abstractTransformGeometry != nullptr) - return false; - int pointIndex = -1; pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); - if (pointIndex >= 0) - { - return true; - } - else - { - return false; - } + return pointIndex >= 0; } bool mitk::PlanarFigureInteractor::CheckSelection(const InteractionEvent * /*interactionEvent*/) { bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); return selected; } void mitk::PlanarFigureInteractor::SelectFigure(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } + planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } void mitk::PlanarFigureInteractor::SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - const mitk::InteractionPositionEvent *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return; + } - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); - const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); - const mitk::PlaneGeometry *planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); - const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } - if (abstractTransformGeometry != nullptr) + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { return; + } + + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) + { + return; + } + + const mitk::BaseRenderer *renderer = interactionEvent->GetSender(); + const PlaneGeometry *projectionPlane = renderer->GetCurrentWorldPlaneGeometry(); const int pointIndex = mitk::PlanarFigureInteractor::IsPositionInsideMarker( positionEvent, planarFigure, planarFigureGeometry, projectionPlane, renderer); if (pointIndex >= 0) { // If mouse is above control point, mark it as selected planarFigure->SelectControlPoint(pointIndex); } else { planarFigure->DeselectControlPoint(); } } bool mitk::PlanarFigureInteractor::CheckPointValidity(const InteractionEvent *interactionEvent) { // Check if the distance of the current point to the previously set point in display coordinates // is sufficient (if a previous point exists) - // Extract display position - const auto *positionEvent = - dynamic_cast(interactionEvent); - if (positionEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return false; + } - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } - m_LastPointWasValid = IsMousePositionAcceptableAsNewControlPoint(positionEvent, planarFigure); - return m_LastPointWasValid; + return IsMousePositionAcceptableAsNewControlPoint(positionEvent, planarFigure); } void mitk::PlanarFigureInteractor::RemoveSelectedPoint(StateMachineAction *, InteractionEvent *interactionEvent) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); - mitk::BaseRenderer *renderer = interactionEvent->GetSender(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); planarFigure->RemoveControlPoint(selectedControlPoint); // Re-evaluate features planarFigure->EvaluateFeatures(); planarFigure->Modified(); GetDataNode()->SetBoolProperty("planarfigure.drawcontrolpoints", true); planarFigure->InvokeEvent(EndInteractionPlanarFigureEvent()); RenderingManager::GetInstance()->RequestUpdateAll(); + mitk::BaseRenderer *renderer = interactionEvent->GetSender(); HandleEvent(mitk::InternalEvent::New(renderer, this, "Dummy-Event"), GetDataNode()); } void mitk::PlanarFigureInteractor::RequestContextMenu(StateMachineAction *, InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return; + } bool selected = false; GetDataNode()->GetBoolProperty("selected", selected); // no need to invoke this if the figure is already selected if (!selected) { planarFigure->InvokeEvent(SelectPlanarFigureEvent()); } planarFigure->InvokeEvent(ContextMenuPlanarFigureEvent()); } bool mitk::PlanarFigureInteractor::CheckResetOnPointSelect(const InteractionEvent * /*interactionEvent*/) { - auto *planarFigure = dynamic_cast(GetDataNode()->GetData()); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } bool isEditable = true; GetDataNode()->GetBoolProperty("planarfigure.iseditable", isEditable); // Reset the PlanarFigure if required return isEditable && planarFigure->ResetOnPointSelectNeeded(); } bool mitk::PlanarFigureInteractor::CheckFigureOnRenderingGeometry(const InteractionEvent *interactionEvent) { - const auto *posEvent = - dynamic_cast(interactionEvent); - - if (posEvent == nullptr) + auto positionEvent = dynamic_cast(interactionEvent); + if (nullptr == positionEvent) + { return false; + } - const mitk::Point3D worldPoint3D = posEvent->GetPositionInWorld(); - const mitk::PlanarFigure *planarFigure = dynamic_cast(GetDataNode()->GetData()); - - const mitk::PlaneGeometry *planarFigurePlaneGeometry = dynamic_cast(planarFigure->GetGeometry(0)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(0)); + const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); + auto planarFigure = dynamic_cast(GetDataNode()->GetData()); + if (nullptr == planarFigure) + { + return false; + } - if (abstractTransformGeometry != nullptr) + auto planarFigureGeometry = planarFigure->GetPlaneGeometry(); + if (nullptr == planarFigureGeometry) + { return false; + } - const double planeThickness = planarFigurePlaneGeometry->GetExtentInMM(2); - if (planarFigurePlaneGeometry->Distance(worldPoint3D) > planeThickness) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(0)); + if (nullptr != abstractTransformGeometry) { - // don't react, when interaction is too far away return false; } - return true; + + const double planeThickness = planarFigureGeometry->GetExtentInMM(2); + return planarFigureGeometry->Distance(worldPoint3D) <= planeThickness; } void mitk::PlanarFigureInteractor::SetPrecision(mitk::ScalarType precision) { m_Precision = precision; } void mitk::PlanarFigureInteractor::SetMinimumPointDistance(ScalarType minimumDistance) { m_MinimumPointDistance = minimumDistance; } bool mitk::PlanarFigureInteractor::TransformPositionEventToPoint2D(const InteractionPositionEvent *positionEvent, const PlaneGeometry *planarFigureGeometry, Point2D &point2D) { + if (nullptr == positionEvent || nullptr == planarFigureGeometry) + { + return false; + } + const mitk::Point3D worldPoint3D = positionEvent->GetPositionInWorld(); // TODO: proper handling of distance tolerance if (planarFigureGeometry->Distance(worldPoint3D) > 0.1) { return false; } // Project point onto plane of this PlanarFigure planarFigureGeometry->Map(worldPoint3D, point2D); return true; } bool mitk::PlanarFigureInteractor::TransformObjectToDisplay(const mitk::Point2D &point2D, mitk::Point2D &displayPoint, const mitk::PlaneGeometry *objectGeometry, const mitk::PlaneGeometry *rendererGeometry, const mitk::BaseRenderer *renderer) const { + if (nullptr == objectGeometry || nullptr == rendererGeometry || nullptr == renderer) + { + return false; + } + mitk::Point3D point3D; // Map circle point from local 2D geometry into 3D world space objectGeometry->Map(point2D, point3D); const double planeThickness = objectGeometry->GetExtentInMM(2); // TODO: proper handling of distance tolerance if (rendererGeometry->Distance(point3D) < planeThickness / 3.0) { // Project 3D world point onto display geometry renderer->WorldToDisplay(point3D, displayPoint); return true; } return false; } bool mitk::PlanarFigureInteractor::IsPointNearLine(const mitk::Point2D &point, const mitk::Point2D &startPoint, const mitk::Point2D &endPoint, mitk::Point2D &projectedPoint) const { mitk::Vector2D n1 = endPoint - startPoint; n1.Normalize(); // Determine dot products between line vector and startpoint-point / endpoint-point vectors const double l1 = n1 * (point - startPoint); const double l2 = -n1 * (point - endPoint); // Determine projection of specified point onto line defined by start / end point const mitk::Point2D crossPoint = startPoint + n1 * l1; projectedPoint = crossPoint; const float dist1 = crossPoint.SquaredEuclideanDistanceTo(point); const float dist2 = endPoint.SquaredEuclideanDistanceTo(point); const float dist3 = startPoint.SquaredEuclideanDistanceTo(point); // Point is inside encompassing rectangle IF // - its distance to its projected point is small enough // - it is not further outside of the line than the defined tolerance if (((dist1 < 20.0) && (l1 > 0.0) && (l2 > 0.0)) || dist2 < 20.0 || dist3 < 20.0) { return true; } return false; } int mitk::PlanarFigureInteractor::IsPositionOverFigure(const InteractionPositionEvent *positionEvent, PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, Point2D &pointProjectedOntoLine) const { + if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry + || nullptr == rendererGeometry) + { + return -1; + } + mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all polylines of planar figure, and check if // any one is close to the current display position typedef mitk::PlanarFigure::PolyLineType VertexContainerType; Point2D polyLinePoint; Point2D firstPolyLinePoint; Point2D previousPolyLinePoint; for (unsigned short loop = 0; loop < planarFigure->GetPolyLinesSize(); ++loop) { const VertexContainerType polyLine = planarFigure->GetPolyLine(loop); bool firstPoint(true); for (auto it = polyLine.begin(); it != polyLine.end(); ++it) { // Get plane coordinates of this point of polyline (if possible) if (!this->TransformObjectToDisplay( *it, polyLinePoint, planarFigureGeometry, rendererGeometry, positionEvent->GetSender())) { break; // Poly line invalid (not on current 2D plane) --> skip it } if (firstPoint) { firstPolyLinePoint = polyLinePoint; firstPoint = false; } else if (this->IsPointNearLine(displayPosition, previousPolyLinePoint, polyLinePoint, pointProjectedOntoLine)) { // Point is close enough to line segment --> Return index of the segment return std::distance(polyLine.begin(), it); } previousPolyLinePoint = polyLinePoint; } // For closed figures, also check last line segment if (planarFigure->IsClosed() && this->IsPointNearLine(displayPosition, polyLinePoint, firstPolyLinePoint, pointProjectedOntoLine)) { return 0; // Return index of first control point } } return -1; } int mitk::PlanarFigureInteractor::IsPositionInsideMarker(const InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure, const PlaneGeometry *planarFigureGeometry, const PlaneGeometry *rendererGeometry, const BaseRenderer *renderer) const { + if (nullptr == positionEvent || nullptr == planarFigure || nullptr == planarFigureGeometry + || nullptr == rendererGeometry || nullptr == renderer) + { + return -1; + } + const mitk::Point2D displayPosition = positionEvent->GetPointerPositionOnScreen(); // Iterate over all control points of planar figure, and check if // any one is close to the current display position mitk::Point2D displayControlPoint; const int numberOfControlPoints = planarFigure->GetNumberOfControlPoints(); for (int i = 0; i < numberOfControlPoints; i++) { if (this->TransformObjectToDisplay( planarFigure->GetControlPoint(i), displayControlPoint, planarFigureGeometry, rendererGeometry, renderer)) { // TODO: variable size of markers if (displayPosition.SquaredEuclideanDistanceTo(displayControlPoint) < 20.0) { return i; } } } return -1; } void mitk::PlanarFigureInteractor::LogPrintPlanarFigureQuantities(const PlanarFigure *planarFigure) { + if (nullptr == planarFigure) + { + MITK_INFO << "PlanarFigure invalid."; + } + MITK_INFO << "PlanarFigure: " << planarFigure->GetNameOfClass(); for (unsigned int i = 0; i < planarFigure->GetNumberOfFeatures(); ++i) { MITK_INFO << "* " << planarFigure->GetFeatureName(i) << ": " << planarFigure->GetQuantity(i) << " " << planarFigure->GetFeatureUnit(i); } } bool mitk::PlanarFigureInteractor::IsMousePositionAcceptableAsNewControlPoint( const mitk::InteractionPositionEvent *positionEvent, const PlanarFigure *planarFigure) { - assert(positionEvent && planarFigure); + if (nullptr == positionEvent || nullptr == planarFigure) + { + return false; + + } const BaseRenderer *renderer = positionEvent->GetSender(); - assert(renderer); + if (nullptr == renderer) + { + return false; + } // Get the timestep to support 3D+t const int timeStep(renderer->GetTimeStep(planarFigure)); bool tooClose(false); - const mitk::PlaneGeometry *planarFigureGeometry = - dynamic_cast(planarFigure->GetGeometry(timeStep)); - const mitk::AbstractTransformGeometry *abstractTransformGeometry = - dynamic_cast(planarFigure->GetGeometry(timeStep)); + auto planarFigureGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); + if (nullptr == planarFigureGeometry) + { + return false; + } - if (abstractTransformGeometry != nullptr) + auto abstractTransformGeometry = dynamic_cast(planarFigure->GetGeometry(timeStep)); + if (nullptr != abstractTransformGeometry) + { return false; + } Point2D point2D; // Get the point2D from the positionEvent if (!this->TransformPositionEventToPoint2D(positionEvent, planarFigureGeometry, point2D)) { return false; } // apply the controlPoint constraints of the planarFigure to get the // coordinates that would actually be used. const Point2D correctedPoint = const_cast(planarFigure)->ApplyControlPointConstraints(0, point2D); // map the 2D coordinates of the new point to world-coordinates // and transform those to display-coordinates mitk::Point3D newPoint3D; planarFigureGeometry->Map(correctedPoint, newPoint3D); mitk::Point2D newDisplayPosition; renderer->WorldToDisplay(newPoint3D, newDisplayPosition); const int selectedControlPoint = planarFigure->GetSelectedControlPoint(); for (int i = 0; i < (int)planarFigure->GetNumberOfControlPoints(); ++i) { if (i != selectedControlPoint) { // Try to convert previous point to current display coordinates mitk::Point3D previousPoint3D; // map the 2D coordinates of the control-point to world-coordinates planarFigureGeometry->Map(planarFigure->GetControlPoint(i), previousPoint3D); if (renderer->GetCurrentWorldPlaneGeometry()->Distance(previousPoint3D) < 0.1) // ugly, but assert makes this work { mitk::Point2D previousDisplayPosition; // transform the world-coordinates into display-coordinates renderer->WorldToDisplay(previousPoint3D, previousDisplayPosition); // Calculate the distance. We use display-coordinates here to make // the check independent of the zoom-level of the rendering scene. const double a = newDisplayPosition[0] - previousDisplayPosition[0]; const double b = newDisplayPosition[1] - previousDisplayPosition[1]; // If point is to close, do not set a new point tooClose = (a * a + b * b < m_MinimumPointDistance); } if (tooClose) return false; // abort loop early } } return !tooClose; // default } void mitk::PlanarFigureInteractor::ConfigurationChanged() { const mitk::PropertyList::Pointer properties = GetAttributes(); std::string precision = ""; if (properties->GetStringProperty("precision", precision)) { m_Precision = atof(precision.c_str()); } else { m_Precision = (ScalarType)6.5; } std::string minPointDistance = ""; if (properties->GetStringProperty("minPointDistance", minPointDistance)) { m_MinimumPointDistance = atof(minPointDistance.c_str()); } else { m_MinimumPointDistance = (ScalarType)25.0; } } diff --git a/Modules/Segmentation/Controllers/mitkToolManager.cpp b/Modules/Segmentation/Controllers/mitkToolManager.cpp index 6e2fbd852c..4a71cac9a9 100644 --- a/Modules/Segmentation/Controllers/mitkToolManager.cpp +++ b/Modules/Segmentation/Controllers/mitkToolManager.cpp @@ -1,589 +1,603 @@ /*============================================================================ 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 "mitkToolManager.h" #include "mitkToolManagerProvider.h" #include "mitkCoreObjectFactory.h" #include #include #include #include "mitkInteractionEventObserver.h" #include "mitkSegTool2D.h" #include "mitkRenderingManager.h" #include "mitkSliceNavigationController.h" #include "usGetModuleContext.h" #include "usModuleContext.h" mitk::ToolManager::ToolManager(DataStorage *storage) : m_ActiveTool(nullptr), m_ActiveToolID(-1), m_RegisteredClients(0), m_DataStorage(storage) { CoreObjectFactory::GetInstance(); // to make sure a CoreObjectFactory was instantiated (and in turn, possible tools // are registered) - bug 1029 this->InitializeTools(); } void mitk::ToolManager::EnsureTimeObservation() { if (nullptr != mitk::RenderingManager::GetInstance() && nullptr != mitk::RenderingManager::GetInstance()->GetTimeNavigationController()) { auto timeController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); m_LastTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto currentTimeController = m_CurrentTimeNavigationController.Lock(); if (timeController != currentTimeController) { if (currentTimeController.IsNotNull()) { currentTimeController->RemoveObserver(m_TimePointObserverTag); } itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnTimeChanged); command->SetCallbackFunction(this, &ToolManager::OnTimeChangedConst); m_CurrentTimeNavigationController = timeController; m_TimePointObserverTag = timeController->AddObserver(SliceNavigationController::GeometryTimeEvent(nullptr,0), command); } } } void mitk::ToolManager::StopTimeObservation() { auto currentTimeController = m_CurrentTimeNavigationController.Lock(); if (currentTimeController.IsNotNull()) { currentTimeController->RemoveObserver(m_TimePointObserverTag); m_CurrentTimeNavigationController = nullptr; m_TimePointObserverTag = 0; } } mitk::ToolManager::~ToolManager() { for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) (*dataIter)->RemoveObserver(m_WorkingDataObserverTags[(*dataIter)]); if (this->GetDataStorage() != nullptr) this->GetDataStorage()->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } for (auto observerTagMapIter = m_ReferenceDataObserverTags.begin(); observerTagMapIter != m_ReferenceDataObserverTags.end(); ++observerTagMapIter) { observerTagMapIter->first->RemoveObserver(observerTagMapIter->second); } this->StopTimeObservation(); } void mitk::ToolManager::InitializeTools() { // clear all previous tool pointers (tools may be still activated from another recently used plugin) if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); m_ActiveTool = nullptr; m_ActiveToolID = -1; // no tool active ActiveToolChanged.Send(); } m_Tools.clear(); // get a list of all known mitk::Tools std::list thingsThatClaimToBeATool = itk::ObjectFactoryBase::CreateAllInstance("mitkTool"); // remember these tools for (auto iter = thingsThatClaimToBeATool.begin(); iter != thingsThatClaimToBeATool.end(); ++iter) { if (auto *tool = dynamic_cast(iter->GetPointer())) { tool->InitializeStateMachine(); tool->SetToolManager(this); // important to call right after instantiation tool->ErrorMessage += MessageDelegate1(this, &ToolManager::OnToolErrorMessage); tool->GeneralMessage += MessageDelegate1(this, &ToolManager::OnGeneralToolMessage); m_Tools.push_back(tool); } } } void mitk::ToolManager::OnToolErrorMessage(std::string s) { this->ToolErrorMessage(s); } void mitk::ToolManager::OnGeneralToolMessage(std::string s) { this->GeneralToolMessage(s); } const mitk::ToolManager::ToolVectorTypeConst mitk::ToolManager::GetTools() { ToolVectorTypeConst resultList; for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter) { resultList.push_back(iter->GetPointer()); } return resultList; } mitk::Tool *mitk::ToolManager::GetToolById(int id) { try { return m_Tools.at(id); } catch (const std::exception &) { return nullptr; } } bool mitk::ToolManager::ActivateTool(int id) { - if (id != -1 && !this->GetToolById(id)->CanHandle(this->GetReferenceData(0)->GetData())) + const auto workingDataNode = this->GetWorkingData(0); + const mitk::BaseData* workingData = nullptr; + if (nullptr != workingDataNode) + { + workingData = workingDataNode->GetData(); + } + + const auto referenceDataNode = this->GetReferenceData(0); + const mitk::BaseData* referenceData = nullptr; + if (nullptr != referenceDataNode) + { + referenceData = referenceDataNode->GetData(); + } + + if (id != -1 && !this->GetToolById(id)->CanHandle(referenceData, workingData)) return false; if (this->GetDataStorage()) { this->GetDataStorage()->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &ToolManager::OnNodeRemoved)); } if (GetToolById(id) == m_ActiveTool) return true; // no change needed static int nextTool = -1; nextTool = id; static bool inActivateTool = false; if (inActivateTool) { return true; } inActivateTool = true; while (nextTool != m_ActiveToolID) { // Deactivate all other active tools to ensure a globally single active tool for (const auto& toolManager : ToolManagerProvider::GetInstance()->GetToolManagers()) { if (nullptr != toolManager.second->m_ActiveTool) { toolManager.second->m_ActiveTool->Deactivated(); toolManager.second->m_ActiveToolRegistration.Unregister(); // The active tool of *this* ToolManager is handled below this loop if (this != toolManager.second) { toolManager.second->m_ActiveTool = nullptr; toolManager.second->m_ActiveToolID = -1; toolManager.second->ActiveToolChanged.Send(); } } } m_ActiveTool = GetToolById(nextTool); m_ActiveToolID = m_ActiveTool ? nextTool : -1; // current ID if tool is valid, otherwise -1 ActiveToolChanged.Send(); if (m_ActiveTool) { this->EnsureTimeObservation(); if (m_RegisteredClients > 0) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } } inActivateTool = false; return (m_ActiveTool != nullptr); } void mitk::ToolManager::SetReferenceData(DataVectorType data) { if (data != m_ReferenceData) { // remove observers from old nodes for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { auto searchIter = m_ReferenceDataObserverTags.find(*dataIter); if (searchIter != m_ReferenceDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_ReferenceData = data; // TODO tell active tool? // attach new observers m_ReferenceDataObserverTags.clear(); for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheReferenceDataDeletedConst); m_ReferenceDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } ReferenceDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheReferenceDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheReferenceDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheReferenceDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_ReferenceData.begin(); dataIter != m_ReferenceData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_ReferenceDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetReferenceData(v); } void mitk::ToolManager::SetReferenceData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } SetReferenceData(v); } void mitk::ToolManager::SetWorkingData(DataVectorType data) { if (data != m_WorkingData) { // remove observers from old nodes for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { auto searchIter = m_WorkingDataObserverTags.find(*dataIter); if (searchIter != m_WorkingDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_WorkingData = data; // TODO tell active tool? // Quick workaround for bug #16598 if (m_WorkingData.empty()) this->ActivateTool(-1); // workaround end // attach new observers m_WorkingDataObserverTags.clear(); for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheWorkingDataDeletedConst); m_WorkingDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } WorkingDataChanged.Send(); } } void mitk::ToolManager::OnOneOfTheWorkingDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheWorkingDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheWorkingDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_WorkingData.begin(); dataIter != m_WorkingData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_WorkingDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetWorkingData(v); } void mitk::ToolManager::SetWorkingData(DataNode *data) { DataVectorType v; if (data) // don't allow for nullptr nodes { v.push_back(data); } SetWorkingData(v); } void mitk::ToolManager::SetRoiData(DataVectorType data) { if (data != m_RoiData) { // remove observers from old nodes for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { auto searchIter = m_RoiDataObserverTags.find(*dataIter); if (searchIter != m_RoiDataObserverTags.end()) { (*dataIter)->RemoveObserver(searchIter->second); } } m_RoiData = data; // TODO tell active tool? // attach new observers m_RoiDataObserverTags.clear(); for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { itk::MemberCommand::Pointer command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeleted); command->SetCallbackFunction(this, &ToolManager::OnOneOfTheRoiDataDeletedConst); m_RoiDataObserverTags.insert( std::pair((*dataIter), (*dataIter)->AddObserver(itk::DeleteEvent(), command))); } RoiDataChanged.Send(); } } void mitk::ToolManager::SetRoiData(DataNode *data) { DataVectorType v; if (data) { v.push_back(data); } this->SetRoiData(v); } void mitk::ToolManager::OnOneOfTheRoiDataDeletedConst(const itk::Object *caller, const itk::EventObject &e) { OnOneOfTheRoiDataDeleted(const_cast(caller), e); } void mitk::ToolManager::OnOneOfTheRoiDataDeleted(itk::Object *caller, const itk::EventObject &itkNotUsed(e)) { DataVectorType v; for (auto dataIter = m_RoiData.begin(); dataIter != m_RoiData.end(); ++dataIter) { if ((void *)(*dataIter) != (void *)caller) { v.push_back(*dataIter); } else { m_RoiDataObserverTags.erase(*dataIter); // no tag to remove anymore } } this->SetRoiData(v); } mitk::ToolManager::DataVectorType mitk::ToolManager::GetReferenceData() { return m_ReferenceData; } mitk::DataNode *mitk::ToolManager::GetReferenceData(int idx) { try { return m_ReferenceData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::ToolManager::DataVectorType mitk::ToolManager::GetWorkingData() { return m_WorkingData; } mitk::ToolManager::DataVectorType mitk::ToolManager::GetRoiData() { return m_RoiData; } mitk::DataNode *mitk::ToolManager::GetRoiData(int idx) { try { return m_RoiData.at(idx); } catch (const std::exception &) { return nullptr; } } mitk::DataStorage *mitk::ToolManager::GetDataStorage() { if (!m_DataStorage.IsExpired()) { return m_DataStorage.Lock(); } else { return nullptr; } } void mitk::ToolManager::SetDataStorage(DataStorage &storage) { m_DataStorage = &storage; } mitk::DataNode *mitk::ToolManager::GetWorkingData(unsigned int idx) { if (m_WorkingData.empty()) return nullptr; if (m_WorkingData.size() > idx) return m_WorkingData[idx]; return nullptr; } int mitk::ToolManager::GetActiveToolID() { return m_ActiveToolID; } mitk::Tool *mitk::ToolManager::GetActiveTool() { return m_ActiveTool; } void mitk::ToolManager::RegisterClient() { if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Activated(); m_ActiveToolRegistration = us::GetModuleContext()->RegisterService(m_ActiveTool, us::ServiceProperties()); } } ++m_RegisteredClients; } void mitk::ToolManager::UnregisterClient() { if (m_RegisteredClients < 1) return; --m_RegisteredClients; if (m_RegisteredClients < 1) { if (m_ActiveTool) { m_ActiveTool->Deactivated(); m_ActiveToolRegistration.Unregister(); } } } int mitk::ToolManager::GetToolID(const Tool *tool) { int id(0); for (auto iter = m_Tools.begin(); iter != m_Tools.end(); ++iter, ++id) { if (tool == iter->GetPointer()) { return id; } } return -1; } void mitk::ToolManager::OnNodeRemoved(const mitk::DataNode *node) { // check all storage vectors OnOneOfTheReferenceDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheRoiDataDeleted(const_cast(node), itk::DeleteEvent()); OnOneOfTheWorkingDataDeleted(const_cast(node), itk::DeleteEvent()); } void mitk::ToolManager::OnTimeChanged(itk::Object* caller, const itk::EventObject& e) { this->OnTimeChangedConst(caller, e); } void mitk::ToolManager::OnTimeChangedConst(const itk::Object* caller, const itk::EventObject& /*e*/) { auto currentController = m_CurrentTimeNavigationController.Lock(); if (caller == currentController) { const auto currentTimePoint = currentController->GetSelectedTimePoint(); if (currentTimePoint != m_LastTimePoint) { m_LastTimePoint = currentTimePoint; SelectedTimePointChanged.Send(); } } } mitk::TimePointType mitk::ToolManager::GetCurrentTimePoint() const { return m_LastTimePoint; } diff --git a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp index 306d0fcac8..64de93da55 100644 --- a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.cpp @@ -1,117 +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 "mitkAdaptiveRegionGrowingTool.h" #include "mitkImage.h" #include "mitkProperties.h" #include "mitkToolManager.h" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, AdaptiveRegionGrowingTool, "AdaptiveRegionGrowingTool"); } mitk::AdaptiveRegionGrowingTool::AdaptiveRegionGrowingTool() { m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->GetPropertyList()->SetProperty("name", mitk::StringProperty::New("3D_Regiongrowing_Seedpoint")); m_PointSetNode->GetPropertyList()->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PointSet = mitk::PointSet::New(); m_PointSetNode->SetData(m_PointSet); } mitk::AdaptiveRegionGrowingTool::~AdaptiveRegionGrowingTool() { } -bool mitk::AdaptiveRegionGrowingTool::CanHandle(BaseData *referenceData) const +bool mitk::AdaptiveRegionGrowingTool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { if (referenceData == nullptr) return false; - auto *image = dynamic_cast(referenceData); + auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; return true; } const char **mitk::AdaptiveRegionGrowingTool::GetXPM() const { return nullptr; } const char *mitk::AdaptiveRegionGrowingTool::GetName() const { return "Region Growing 3D"; } us::ModuleResource mitk::AdaptiveRegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); return resource; } void mitk::AdaptiveRegionGrowingTool::Activated() { Superclass::Activated(); if (!GetDataStorage()->Exists(m_PointSetNode)) GetDataStorage()->Add(m_PointSetNode, GetWorkingData()); m_SeedPointInteractor = mitk::SinglePointDataInteractor::New(); m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); m_SeedPointInteractor->SetDataNode(m_PointSetNode); } void mitk::AdaptiveRegionGrowingTool::Deactivated() { m_PointSet->Clear(); GetDataStorage()->Remove(m_PointSetNode); Superclass::Deactivated(); } void mitk::AdaptiveRegionGrowingTool::ConfirmSegmentation() { m_ToolManager->ActivateTool(-1); } mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetReferenceData() { return this->m_ToolManager->GetReferenceData(0); } mitk::DataStorage *mitk::AdaptiveRegionGrowingTool::GetDataStorage() { return this->m_ToolManager->GetDataStorage(); } mitk::DataNode *mitk::AdaptiveRegionGrowingTool::GetWorkingData() { return this->m_ToolManager->GetWorkingData(0); } mitk::DataNode::Pointer mitk::AdaptiveRegionGrowingTool::GetPointSetNode() { return m_PointSetNode; } diff --git a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h index 31797bffbc..7924978442 100644 --- a/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h +++ b/Modules/Segmentation/Interactions/mitkAdaptiveRegionGrowingTool.h @@ -1,128 +1,128 @@ /*============================================================================ 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 mitkAdaptiveRegionGrowingTool_h_Included #define mitkAdaptiveRegionGrowingTool_h_Included #include "mitkAutoSegmentationTool.h" #include "mitkCommon.h" #include "mitkDataStorage.h" #include "mitkPointSet.h" #include "mitkSinglePointDataInteractor.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief Dummy Tool for AdaptiveRegionGrowingToolGUI to get Tool functionality for AdaptiveRegionGrowing. The actual logic is implemented in QmitkAdaptiveRegionGrowingToolGUI. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AdaptiveRegionGrowingTool : public AutoSegmentationTool { public: /** * @brief mitkClassMacro */ mitkClassMacro(AdaptiveRegionGrowingTool, AutoSegmentationTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - bool CanHandle(BaseData *referenceData) const override; + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /** * @brief Get XPM * @return nullptr */ const char **GetXPM() const override; /** * @brief Get name * @return name of the Tool */ const char *GetName() const override; /** * @brief Get icon resource * @return the resource Object of the Icon */ us::ModuleResource GetIconResource() const override; /** * @brief Adds interactor for the seedpoint and creates a seedpoint if neccessary. * * */ void Activated() override; /** * @brief Removes all set points and interactors. * * */ void Deactivated() override; /** * @brief get pointset node * @return the point set node */ virtual DataNode::Pointer GetPointSetNode(); /** * @brief get reference data * @return the current reference data. */ mitk::DataNode *GetReferenceData(); /** * @brief Get working data * @return a list of all working data. */ mitk::DataNode *GetWorkingData(); /** * @brief Get datastorage * @return the current data storage. */ mitk::DataStorage *GetDataStorage(); void ConfirmSegmentation(); protected: /** * @brief constructor */ AdaptiveRegionGrowingTool(); // purposely hidden /** * @brief destructor */ ~AdaptiveRegionGrowingTool() override; private: PointSet::Pointer m_PointSet; SinglePointDataInteractor::Pointer m_SeedPointInteractor; DataNode::Pointer m_PointSetNode; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp index 0539cb7df1..96ba360d0f 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp @@ -1,97 +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. ============================================================================*/ #include "mitkAutoSegmentationTool.h" #include "mitkImage.h" #include "mitkToolManager.h" #include mitk::AutoSegmentationTool::AutoSegmentationTool() : Tool("dummy"), m_OverwriteExistingSegmentation(false) { } mitk::AutoSegmentationTool::~AutoSegmentationTool() { } const char *mitk::AutoSegmentationTool::GetGroup() const { return "autoSegmentation"; } -mitk::Image::ConstPointer mitk::AutoSegmentationTool::Get3DImage(const mitk::Image* image, unsigned int timestep) const +mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimeStep(const mitk::Image* image, unsigned int timestep) { if (nullptr == image) return image; if (image->GetDimension() != 4) return image; mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(timestep)); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } -mitk::Image::ConstPointer mitk::AutoSegmentationTool::Get3DImageByTimePoint(const mitk::Image* image, TimePointType timePoint) const +mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; - return this->Get3DImage(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); + return AutoSegmentationTool::GetImageByTimeStep(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); } void mitk::AutoSegmentationTool::SetOverwriteExistingSegmentation(bool overwrite) { m_OverwriteExistingSegmentation = overwrite; } std::string mitk::AutoSegmentationTool::GetCurrentSegmentationName() { if (m_ToolManager->GetWorkingData(0)) return m_ToolManager->GetWorkingData(0)->GetName(); else return ""; } mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() { mitk::DataNode::Pointer segmentationNode = m_ToolManager->GetWorkingData(0); if (!m_OverwriteExistingSegmentation) { mitk::DataNode::Pointer refNode = m_ToolManager->GetReferenceData(0); if (refNode.IsNull()) { // TODO create and use segmentation exceptions instead!! MITK_ERROR << "No valid reference data!"; return nullptr; } std::string nodename = refNode->GetName() + "_" + this->GetName(); mitk::Color color; color.SetRed(1); color.SetBlue(0); color.SetGreen(0); //create a new segmentation node based on the current segmentation as template segmentationNode = CreateEmptySegmentationNode(dynamic_cast(segmentationNode->GetData()), nodename, color); m_ToolManager->GetDataStorage()->Add(segmentationNode, refNode); } return segmentationNode; } diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h index 1a2d458950..dfb36b908c 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h @@ -1,65 +1,71 @@ /*============================================================================ 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 mitkAutoSegmentationTool_h_Included #define mitkAutoSegmentationTool_h_Included #include "mitkCommon.h" #include "mitkTool.h" #include namespace mitk { class Image; /** \brief Superclass for tool that create a new segmentation without user interaction in render windows This class is undocumented. Ask the creator ($Author$) to supply useful comments. */ class MITKSEGMENTATION_EXPORT AutoSegmentationTool : public Tool { public: mitkClassMacro(AutoSegmentationTool, Tool); + /** This function controls wether a confirmed segmentation should replace the old + * segmentation/working node (true) or if it should be stored as new and additional + * node (false). + */ void SetOverwriteExistingSegmentation(bool overwrite); /** * @brief Gets the name of the currently selected segmentation node * @return the name of the segmentation node or an empty string if * none is selected */ std::string GetCurrentSegmentationName(); /** * @brief Depending on the selected mode either returns the currently selected segmentation * or creates a new one from the selected reference data and adds the new segmentation * to the datastorage * @return a mitk::DataNode which contains a segmentation image */ virtual mitk::DataNode *GetTargetSegmentationNode(); protected: AutoSegmentationTool(); // purposely hidden ~AutoSegmentationTool() override; const char *GetGroup() const override; - virtual Image::ConstPointer Get3DImage(const Image* image, unsigned int timestep) const; - virtual Image::ConstPointer Get3DImageByTimePoint(const Image* image, TimePointType timePoint) const; + /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ + static Image::ConstPointer GetImageByTimeStep(const Image* image, unsigned int timestep); + /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ + static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); bool m_OverwriteExistingSegmentation; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp new file mode 100644 index 0000000000..8e9b4291f7 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp @@ -0,0 +1,474 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkAutoSegmentationWithPreviewTool.h" + +#include "mitkToolManager.h" + +#include "mitkColorProperty.h" +#include "mitkLevelWindowProperty.h" +#include "mitkProperties.h" + +#include "mitkDataStorage.h" +#include "mitkRenderingManager.h" +#include + +#include "mitkImageAccessByItk.h" +#include "mitkImageCast.h" +#include "mitkImageStatisticsHolder.h" +#include "mitkImageTimeSelector.h" +#include "mitkLabelSetImage.h" +#include "mitkMaskAndCutRoiImageFilter.h" +#include "mitkPadImageFilter.h" +#include "mitkNodePredicateGeometry.h" + +mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews): m_LazyDynamicPreviews(lazyDynamicPreviews) +{ + m_ProgressCommand = mitk::ToolCommand::New(); +} + +mitk::AutoSegmentationWithPreviewTool::~AutoSegmentationWithPreviewTool() +{ +} + +bool mitk::AutoSegmentationWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const +{ + if (!Superclass::CanHandle(referenceData, workingData)) + return false; + + if (workingData == nullptr) + return true; + + auto* labelSet = dynamic_cast(workingData); + + if (labelSet != nullptr) + return true; + + auto* image = dynamic_cast(workingData); + + if (image == nullptr) + return false; + + //if it is a normal image and not a label set image is used as working data + //it must have the same pixel type as a label set. + return MakeScalarPixelType< DefaultSegmentationDataType >() == image->GetPixelType(); +} + +void mitk::AutoSegmentationWithPreviewTool::Activated() +{ + Superclass::Activated(); + + m_ToolManager->RoiDataChanged += + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); + + m_ToolManager->SelectedTimePointChanged += + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); + + m_ReferenceDataNode = m_ToolManager->GetReferenceData(0); + m_SegmentationInputNode = m_ReferenceDataNode; + + m_LastTimePointOfUpdate = 0; + + if (m_PreviewSegmentationNode.IsNull()) + { + m_PreviewSegmentationNode = DataNode::New(); + m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); + m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); + m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); + m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); + m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); + } + + if (m_SegmentationInputNode.IsNotNull()) + { + this->ResetPreviewNode(); + this->InitiateToolByInput(); + } + else + { + m_ToolManager->ActivateTool(-1); + } +} + +void mitk::AutoSegmentationWithPreviewTool::Deactivated() +{ + m_ToolManager->RoiDataChanged -= + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); + + m_ToolManager->SelectedTimePointChanged -= + mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); + + m_SegmentationInputNode = nullptr; + m_ReferenceDataNode = nullptr; + + try + { + if (DataStorage *storage = m_ToolManager->GetDataStorage()) + { + storage->Remove(m_PreviewSegmentationNode); + RenderingManager::GetInstance()->RequestUpdateAll(); + } + } + catch (...) + { + // don't care + } + + m_PreviewSegmentationNode->SetData(nullptr); + + Superclass::Deactivated(); +} + +void mitk::AutoSegmentationWithPreviewTool::ConfirmSegmentation() +{ + if (m_LazyDynamicPreviews && m_CreateAllTimeSteps) + { // The tool should create all time steps but is currently in lazy mode, + // thus ensure that a preview for all time steps is available. + this->UpdatePreview(true); + } + + CreateResultSegmentationFromPreview(); + + RenderingManager::GetInstance()->RequestUpdateAll(); + + if (!m_KeepActiveAfterAccept) + { + m_ToolManager->ActivateTool(-1); + } +} + +void mitk::AutoSegmentationWithPreviewTool::InitiateToolByInput() +{ + //default implementation does nothing. + //implement in derived classes to change behavior +} + +mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() +{ + if (m_PreviewSegmentationNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_PreviewSegmentationNode->GetData()); +} + +mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentationNode() +{ + return m_PreviewSegmentationNode; +} + +const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetSegmentationInput() const +{ + if (m_SegmentationInputNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_SegmentationInputNode->GetData()); +} + +const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetReferenceData() const +{ + if (m_ReferenceDataNode.IsNull()) + { + return nullptr; + } + + return dynamic_cast(m_ReferenceDataNode->GetData()); +} + +void mitk::AutoSegmentationWithPreviewTool::ResetPreviewNode() +{ + itk::RGBPixel previewColor; + previewColor[0] = 0.0f; + previewColor[1] = 1.0f; + previewColor[2] = 0.0f; + + const auto image = this->GetSegmentationInput(); + if (nullptr != image) + { + mitk::LabelSetImage::ConstPointer workingImage = + dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + + if (workingImage.IsNotNull()) + { + auto newPreviewImage = workingImage->Clone(); + if (newPreviewImage.IsNull()) + { + MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; + return; + } + + m_PreviewSegmentationNode->SetData(newPreviewImage); + + // Let's paint the feedback node green... + newPreviewImage->GetActiveLabel()->SetColor(previewColor); + newPreviewImage->GetActiveLabelSet()->UpdateLookupTable(newPreviewImage->GetActiveLabel()->GetValue()); + } + else + { + mitk::Image::ConstPointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); + if (workingImageBin.IsNotNull()) + { + auto newPreviewImage = workingImageBin->Clone(); + if (newPreviewImage.IsNull()) + { + MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; + return; + } + + m_PreviewSegmentationNode->SetData(newPreviewImage->Clone()); + } + else + { + mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; + } + } + + m_PreviewSegmentationNode->SetColor(previewColor); + m_PreviewSegmentationNode->SetOpacity(0.5); + + int layer(50); + m_ReferenceDataNode->GetIntProperty("layer", layer); + m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); + + if (DataStorage *ds = m_ToolManager->GetDataStorage()) + { + if (!ds->Exists(m_PreviewSegmentationNode)) + ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); + } + } +} + +template +static void ITKSetVolume(const itk::Image *originalImage, + mitk::Image *segmentation, + unsigned int timeStep) +{ + auto constPixelContainer = originalImage->GetPixelContainer(); + //have to make a const cast because itk::PixelContainer does not provide a const correct access :( + auto pixelContainer = const_cast::PixelContainer*>(constPixelContainer); + + segmentation->SetVolume((void *)pixelContainer->GetBufferPointer(), timeStep); +} + +void mitk::AutoSegmentationWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) +{ + try + { + Image::ConstPointer image3D = this->GetImageByTimeStep(sourceImage, timeStep); + + if (image3D->GetPixelType() != destinationImage->GetPixelType()) + { + mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " + << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() + << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); + } + + if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_PRECISION, false)) + { + mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; + } + + if (image3D->GetDimension() == 2) + { + AccessFixedDimensionByItk_2( + image3D, ITKSetVolume, 2, destinationImage, timeStep); + } + else + { + AccessFixedDimensionByItk_2( + image3D, ITKSetVolume, 3, destinationImage, timeStep); + } + } + catch (...) + { + Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); + throw; + } +} + +void mitk::AutoSegmentationWithPreviewTool::CreateResultSegmentationFromPreview() +{ + const auto segInput = this->GetSegmentationInput(); + auto previewImage = this->GetPreviewSegmentation(); + if (nullptr != segInput && nullptr != previewImage) + { + DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); + + if (resultSegmentationNode.IsNotNull()) + { + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); + + // REMARK: the following code in this scope assumes that previewImage and resultSegmentation + // are clones of the working image (segmentation provided to the tool). Therefore they have + // the same time geometry. + if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) + { + mitkThrow() << "Cannot perform threshold. Internal tool state is invalid." + << " Preview segmentation and segmentation result image have different time geometries."; + } + + if (m_CreateAllTimeSteps) + { + for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) + { + TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + } + } + else + { + const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); + TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + } + + // since we are maybe working on a smaller image, pad it to the size of the original image + if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) + { + mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); + + padFilter->SetInput(0, resultSegmentation); + padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); + padFilter->SetBinaryFilter(true); + padFilter->SetUpperThreshold(1); + padFilter->SetLowerThreshold(1); + padFilter->Update(); + + resultSegmentationNode->SetData(padFilter->GetOutput()); + } + + m_ToolManager->SetWorkingData(resultSegmentationNode); + m_ToolManager->GetWorkingData(0)->Modified(); + } + } +} + +void mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged() +{ + mitk::DataNode::ConstPointer node = m_ToolManager->GetRoiData(0); + + if (node.IsNotNull()) + { + mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); + mitk::Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); + + if (image.IsNull()) + return; + + roiFilter->SetInput(image); + roiFilter->SetRegionOfInterest(node->GetData()); + roiFilter->Update(); + + mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); + tmpNode->SetData(roiFilter->GetOutput()); + + m_SegmentationInputNode = tmpNode; + } + else + m_SegmentationInputNode = m_ReferenceDataNode; + + this->ResetPreviewNode(); + this->InitiateToolByInput(); + this->UpdatePreview(); +} + +void mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged() +{ + if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) + { + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; + if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) + { //we only need to update either because we are lazzy + //or because we have a static segmentation with a dynamic image + this->UpdatePreview(); + } + } +} + +void mitk::AutoSegmentationWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) +{ + const auto inputImage = this->GetSegmentationInput(); + auto previewImage = this->GetPreviewSegmentation(); + int progress_steps = 200; + + this->UpdatePrepare(); + + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + + try + { + this->CurrentlyBusy.Send(true); + if (nullptr != inputImage && nullptr != previewImage) + { + m_ProgressCommand->AddStepsToDo(progress_steps); + + if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) + { + for (unsigned int timeStep = 0; timeStep < inputImage->GetTimeSteps(); ++timeStep) + { + auto feedBackImage3D = this->GetImageByTimeStep(inputImage, timeStep); + + this->DoUpdatePreview(feedBackImage3D, previewImage, timeStep); + } + } + else + { + auto feedBackImage3D = this->GetImageByTimePoint(inputImage, timePoint); + auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); + + this->DoUpdatePreview(feedBackImage3D, previewImage, timeStep); + } + RenderingManager::GetInstance()->RequestUpdateAll(); + } + } + catch (itk::ExceptionObject & excep) + { + MITK_ERROR << "Exception caught: " << excep.GetDescription(); + + m_ProgressCommand->SetProgress(progress_steps); + + std::string msg = excep.GetDescription(); + ErrorMessage.Send(msg); + } + catch (...) + { + m_ProgressCommand->SetProgress(progress_steps); + CurrentlyBusy.Send(false); + throw; + } + + this->UpdateCleanUp(); + m_LastTimePointOfUpdate = timePoint; + m_ProgressCommand->SetProgress(progress_steps); + CurrentlyBusy.Send(false); +} + +void mitk::AutoSegmentationWithPreviewTool::UpdatePrepare() +{ + // default implementation does nothing + //reimplement in derived classes for special behavior +} + +void mitk::AutoSegmentationWithPreviewTool::UpdateCleanUp() +{ + // default implementation does nothing + //reimplement in derived classes for special behavior +} + +mitk::TimePointType mitk::AutoSegmentationWithPreviewTool::GetLastTimePointOfUpdate() const +{ + return m_LastTimePointOfUpdate; +} diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h new file mode 100644 index 0000000000..848ff0c005 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h @@ -0,0 +1,152 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkAutoSegmentationWithPreviewTool_h_Included +#define mitkAutoSegmentationWithPreviewTool_h_Included + +#include "mitkAutoSegmentationTool.h" +#include "mitkCommon.h" +#include "mitkDataNode.h" +#include "mitkToolCommand.h" +#include + +namespace mitk +{ + /** + \brief Base class for any auto segmentation tool that provides a preview of the new segmentation. + + This tool class implements a lot basic logic to handle auto segmentation tools with preview, + Time point and ROI support. Derived classes will ask to update the segmentation preview if needed + (e.g. because the ROI or the current time point has changed) or because derived tools + indicated the need to update themselves. + This class also takes care to properly transfer a confirmed preview into the segementation + result. + + \ingroup ToolManagerEtAl + \sa mitk::Tool + \sa QmitkInteractiveSegmentation + */ + class MITKSEGMENTATION_EXPORT AutoSegmentationWithPreviewTool : public AutoSegmentationTool + { + public: + + mitkClassMacro(AutoSegmentationWithPreviewTool, AutoSegmentationTool); + + void Activated() override; + void Deactivated() override; + + void ConfirmSegmentation(); + + itkSetMacro(CreateAllTimeSteps, bool); + itkGetMacro(CreateAllTimeSteps, bool); + itkBooleanMacro(CreateAllTimeSteps); + + itkSetMacro(KeepActiveAfterAccept, bool); + itkGetMacro(KeepActiveAfterAccept, bool); + itkBooleanMacro(KeepActiveAfterAccept); + + itkSetMacro(IsTimePointChangeAware, bool); + itkGetMacro(IsTimePointChangeAware, bool); + itkBooleanMacro(IsTimePointChangeAware); + + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; + + /** Triggers the actualization of the preview + * @param ignoreLazyPreviewSetting If set true UpdatePreview will always + * generate the preview for all time steps. If set to false, UpdatePreview + * will regard the setting specified by the constructor. + * To define the update generation for time steps implement DoUpdatePreview. + * To alter what should be done directly before or after the update of the preview, + * reimplement UpdatePrepare() or UpdateCleanUp().*/ + void UpdatePreview(bool ignoreLazyPreviewSetting = false); + + protected: + mitk::ToolCommand::Pointer m_ProgressCommand; + + /** Member is always called if GetSegmentationInput() has changed + * (e.g. because a new ROI was defined, or on activation) to give derived + * classes the posibility to initiate their state accordingly. + * Reimplement this function to implement special behavior. + */ + virtual void InitiateToolByInput(); + + virtual void UpdatePrepare(); + virtual void UpdateCleanUp(); + + /** This function does the real work. Here the preview for a given + * input image should be computed and stored in the also passed + * preview image at the passed time step. + */ + virtual void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) = 0; + + AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews = false); + ~AutoSegmentationWithPreviewTool() override; + + /** Returns the image that contains the preview of the current segmentation. + * Returns null if the node is not set or does not contain an image.*/ + Image* GetPreviewSegmentation(); + DataNode* GetPreviewSegmentationNode(); + + /** Returns the input that should be used for any segmentation/preview or tool update. + * It is either the data of ReferenceDataNode itself or a part of it defined by a ROI mask + * provided by the tool manager. Derived classes should regard this as the relevant + * input data for any processing. + * Returns null if the node is not set or does not contain an image.*/ + const Image* GetSegmentationInput() const; + + /** Returns the image that is provided by the ReferenceDataNode. + * Returns null if the node is not set or does not contain an image.*/ + const Image* GetReferenceData() const; + + /** Resets the preview node so it is empty and ready to be filled by the tool*/ + void ResetPreviewNode(); + + TimePointType GetLastTimePointOfUpdate() const; + + private: + void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); + + void CreateResultSegmentationFromPreview(); + + void OnRoiDataChanged(); + void OnTimePointChanged(); + + /** Node that containes the preview data generated and managed by this class or derived ones.*/ + DataNode::Pointer m_PreviewSegmentationNode; + /** The reference data recieved from ToolManager::GetReferenceData when tool was activated.*/ + DataNode::Pointer m_ReferenceDataNode; + /** Node that containes the data that should be used as input for any auto segmentation. It might + * be the same like m_ReferenceDataNode (if no ROI is set) or a sub region (if ROI is set).*/ + DataNode::Pointer m_SegmentationInputNode; + + /** Indicates if Accepting the threshold should transfer/create the segmentations + of all time steps (true) or only of the currently selected timepoint (false).*/ + bool m_CreateAllTimeSteps = false; + + /** Indicates if the tool should kept active after accepting the segmentation or not.*/ + bool m_KeepActiveAfterAccept = false; + + /** Relevant if the working data / preview image has multiple time steps (dynamic segmentations). + * This flag has to be set by derived classes accordingly to there way to generate dynamic previews. + * If LazyDynamicPreview is true, the tool generates only the preview for the current time step. + * Therefore it always has to update the preview if current time point has changed and it has to (re)compute + * all timeframes if ConfirmSegmentation() is called.*/ + bool m_LazyDynamicPreviews = false; + + bool m_IsTimePointChangeAware = true; + + TimePointType m_LastTimePointOfUpdate = 0.; + }; + +} // namespace + +#endif diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp index f48232e055..a8ee655db0 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.cpp @@ -1,466 +1,126 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkBinaryThresholdBaseTool.h" -#include "mitkToolManager.h" - -#include "mitkColorProperty.h" -#include "mitkLevelWindowProperty.h" -#include "mitkProperties.h" - -#include "mitkDataStorage.h" -#include "mitkRenderingManager.h" -#include - #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" -#include "mitkImageTimeSelector.h" #include "mitkLabelSetImage.h" -#include "mitkMaskAndCutRoiImageFilter.h" -#include "mitkPadImageFilter.h" #include #include mitk::BinaryThresholdBaseTool::BinaryThresholdBaseTool() : m_SensibleMinimumThresholdValue(-100), m_SensibleMaximumThresholdValue(+100), m_CurrentLowerThresholdValue(1), m_CurrentUpperThresholdValue(1) { - m_ThresholdFeedbackNode = DataNode::New(); - m_ThresholdFeedbackNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); - m_ThresholdFeedbackNode->SetProperty("name", StringProperty::New("Thresholding feedback")); - m_ThresholdFeedbackNode->SetProperty("opacity", FloatProperty::New(0.3)); - m_ThresholdFeedbackNode->SetProperty("binary", BoolProperty::New(true)); - m_ThresholdFeedbackNode->SetProperty("helper object", BoolProperty::New(true)); } mitk::BinaryThresholdBaseTool::~BinaryThresholdBaseTool() { } -void mitk::BinaryThresholdBaseTool::Activated() -{ - Superclass::Activated(); - - m_ToolManager->RoiDataChanged += - mitk::MessageDelegate(this, &mitk::BinaryThresholdBaseTool::OnRoiDataChanged); - - m_ToolManager->SelectedTimePointChanged += - mitk::MessageDelegate(this, &mitk::BinaryThresholdBaseTool::OnTimePointChanged); - - m_OriginalImageNode = m_ToolManager->GetReferenceData(0); - m_NodeForThresholding = m_OriginalImageNode; - - if (m_NodeForThresholding.IsNotNull()) - { - SetupPreviewNode(); - } - else - { - m_ToolManager->ActivateTool(-1); - } -} - -void mitk::BinaryThresholdBaseTool::Deactivated() -{ - m_ToolManager->RoiDataChanged -= - mitk::MessageDelegate(this, &mitk::BinaryThresholdBaseTool::OnRoiDataChanged); - - m_ToolManager->SelectedTimePointChanged -= - mitk::MessageDelegate(this, &mitk::BinaryThresholdBaseTool::OnTimePointChanged); - - m_NodeForThresholding = nullptr; - m_OriginalImageNode = nullptr; - try - { - if (DataStorage *storage = m_ToolManager->GetDataStorage()) - { - storage->Remove(m_ThresholdFeedbackNode); - RenderingManager::GetInstance()->RequestUpdateAll(); - } - } - catch (...) - { - // don't care - } - m_ThresholdFeedbackNode->SetData(nullptr); - - Superclass::Deactivated(); -} - void mitk::BinaryThresholdBaseTool::SetThresholdValues(double lower, double upper) { /* If value is not in the min/max range, do nothing. In that case, this method will be called again with a proper value right after. The only known case where this happens is with an [0.0, 1.0[ image, where value could be an epsilon greater than the max. */ if (lower < m_SensibleMinimumThresholdValue || lower > m_SensibleMaximumThresholdValue || upper < m_SensibleMinimumThresholdValue || upper > m_SensibleMaximumThresholdValue) { return; } m_CurrentLowerThresholdValue = lower; m_CurrentUpperThresholdValue = upper; - if (m_ThresholdFeedbackNode.IsNotNull()) + if (nullptr != this->GetPreviewSegmentation()) { - UpdatePreview(); } } -void mitk::BinaryThresholdBaseTool::AcceptCurrentThresholdValue() -{ - CreateNewSegmentationFromThreshold(); - - RenderingManager::GetInstance()->RequestUpdateAll(); - m_ToolManager->ActivateTool(-1); -} - -void mitk::BinaryThresholdBaseTool::CancelThresholding() +void mitk::BinaryThresholdBaseTool::InitiateToolByInput() { - m_ToolManager->ActivateTool(-1); -} - -void mitk::BinaryThresholdBaseTool::SetupPreviewNode() -{ - itk::RGBPixel pixel; - pixel[0] = 0.0f; - pixel[1] = 1.0f; - pixel[2] = 0.0f; - - if (m_NodeForThresholding.IsNotNull()) + const auto referenceImage = this->GetReferenceData(); + if (nullptr != referenceImage) { - Image::Pointer image = dynamic_cast(m_NodeForThresholding->GetData()); - Image::Pointer originalImage = dynamic_cast(m_OriginalImageNode->GetData()); - - if (image.IsNotNull()) + m_SensibleMinimumThresholdValue = std::numeric_limits::max(); + m_SensibleMaximumThresholdValue = std::numeric_limits::lowest(); + Image::StatisticsHolderPointer statistics = referenceImage->GetStatistics(); + for (unsigned int ts = 0; ts < referenceImage->GetTimeSteps(); ++ts) { - mitk::LabelSetImage::Pointer workingImage = - dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); - - if (workingImage.IsNotNull()) - { - m_ThresholdFeedbackNode->SetData(workingImage->Clone()); - m_IsOldBinary = false; - - // Let's paint the feedback node green... - mitk::LabelSetImage::Pointer previewImage = - dynamic_cast(m_ThresholdFeedbackNode->GetData()); - - if (previewImage.IsNull()) - { - MITK_ERROR << "Cannot create helper objects."; - return; - } - - previewImage->GetActiveLabel()->SetColor(pixel); - previewImage->GetActiveLabelSet()->UpdateLookupTable(previewImage->GetActiveLabel()->GetValue()); - } - else - { - mitk::Image::Pointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); - if (workingImageBin) - { - m_ThresholdFeedbackNode->SetData(workingImageBin->Clone()); - m_IsOldBinary = true; - } - else - m_ThresholdFeedbackNode->SetData(mitk::Image::New()); - } - - m_ThresholdFeedbackNode->SetColor(pixel); - m_ThresholdFeedbackNode->SetOpacity(0.5); - - int layer(50); - m_NodeForThresholding->GetIntProperty("layer", layer); - m_ThresholdFeedbackNode->SetIntProperty("layer", layer + 1); - - if (DataStorage *ds = m_ToolManager->GetDataStorage()) - { - if (!ds->Exists(m_ThresholdFeedbackNode)) - ds->Add(m_ThresholdFeedbackNode, m_OriginalImageNode); - } - - if (image.GetPointer() == originalImage.GetPointer()) - { - m_SensibleMinimumThresholdValue = std::numeric_limits::max(); - m_SensibleMaximumThresholdValue = std::numeric_limits::lowest(); - Image::StatisticsHolderPointer statistics = originalImage->GetStatistics(); - for (unsigned int ts = 0; ts < originalImage->GetTimeSteps(); ++ts) - { - m_SensibleMinimumThresholdValue = std::min(m_SensibleMinimumThresholdValue, static_cast(statistics->GetScalarValueMin())); - m_SensibleMaximumThresholdValue = std::max(m_SensibleMaximumThresholdValue, static_cast(statistics->GetScalarValueMax())); - } - } - - if (m_LockedUpperThreshold) - { - m_CurrentLowerThresholdValue = (m_SensibleMaximumThresholdValue + m_SensibleMinimumThresholdValue) / 2.0; - m_CurrentUpperThresholdValue = m_SensibleMaximumThresholdValue; - } - else - { - double range = m_SensibleMaximumThresholdValue - m_SensibleMinimumThresholdValue; - m_CurrentLowerThresholdValue = m_SensibleMinimumThresholdValue + range / 3.0; - m_CurrentUpperThresholdValue = m_SensibleMinimumThresholdValue + 2 * range / 3.0; - } - - bool isFloatImage = false; - if ((originalImage->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) && - (originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT || - originalImage->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE)) - { - isFloatImage = true; - } - - IntervalBordersChanged.Send(m_SensibleMinimumThresholdValue, m_SensibleMaximumThresholdValue, isFloatImage); - ThresholdingValuesChanged.Send(m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue); + m_SensibleMinimumThresholdValue = std::min(m_SensibleMinimumThresholdValue, static_cast(statistics->GetScalarValueMin())); + m_SensibleMaximumThresholdValue = std::max(m_SensibleMaximumThresholdValue, static_cast(statistics->GetScalarValueMax())); } - } -} -template -static void ITKSetVolume(const itk::Image *originalImage, - mitk::Image *segmentation, - unsigned int timeStep) -{ - auto constPixelContainer = originalImage->GetPixelContainer(); - //have to make a const cast because itk::PixelContainer does not provide a const correct access :( - auto pixelContainer = const_cast::PixelContainer*>(constPixelContainer); - - segmentation->SetVolume((void *)pixelContainer->GetBufferPointer(), timeStep); -} - -void mitk::BinaryThresholdBaseTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) -{ - try - { - Image::ConstPointer image3D = this->Get3DImage(sourceImage, timeStep); - - if (image3D->GetDimension() == 2) + if (m_LockedUpperThreshold) { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 2, destinationImage, timeStep); + m_CurrentLowerThresholdValue = (m_SensibleMaximumThresholdValue + m_SensibleMinimumThresholdValue) / 2.0; + m_CurrentUpperThresholdValue = m_SensibleMaximumThresholdValue; } else { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 3, destinationImage, timeStep); + double range = m_SensibleMaximumThresholdValue - m_SensibleMinimumThresholdValue; + m_CurrentLowerThresholdValue = m_SensibleMinimumThresholdValue + range / 3.0; + m_CurrentUpperThresholdValue = m_SensibleMinimumThresholdValue + 2 * range / 3.0; } - } - catch (...) - { - Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); - } -} -void mitk::BinaryThresholdBaseTool::CreateNewSegmentationFromThreshold() -{ - if (m_NodeForThresholding.IsNotNull() && m_ThresholdFeedbackNode.IsNotNull()) - { - Image::Pointer feedBackImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); - if (feedBackImage.IsNotNull()) + bool isFloatImage = false; + if ((referenceImage->GetPixelType().GetPixelType() == itk::ImageIOBase::SCALAR) && + (referenceImage->GetPixelType().GetComponentType() == itk::ImageIOBase::FLOAT || + referenceImage->GetPixelType().GetComponentType() == itk::ImageIOBase::DOUBLE)) { - // create a new image of the same dimensions and smallest possible pixel type - DataNode::Pointer emptySegmentationNode = GetTargetSegmentationNode(); - - if (emptySegmentationNode) - { - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto emptySegmentation = dynamic_cast(emptySegmentationNode->GetData()); - - // actually perform a thresholding - // REMARK: the following code in this scope assumes that feedBackImage and emptySegmentationImage - // are clones of the working image (segmentation provided to the tool). Therefor the have the same - // time geometry. - if (feedBackImage->GetTimeSteps() != emptySegmentation->GetTimeSteps()) - { - mitkThrow() << "Cannot performe threshold. Internal tool state is invalid." - << " Preview segmentation and segmentation result image have different time geometries."; - } - - if (m_CreateAllTimeSteps) - { - for (unsigned int timeStep = 0; timeStep < feedBackImage->GetTimeSteps(); ++timeStep) - { - TransferImageAtTimeStep(feedBackImage, emptySegmentation, timeStep); - } - } - else - { - const auto timeStep = emptySegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); - TransferImageAtTimeStep(feedBackImage, emptySegmentation, timeStep); - } - - // since we are maybe working on a smaller image, pad it to the size of the original image - if (m_OriginalImageNode.GetPointer() != m_NodeForThresholding.GetPointer()) - { - mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); - - padFilter->SetInput(0, emptySegmentation); - padFilter->SetInput(1, dynamic_cast(m_OriginalImageNode->GetData())); - padFilter->SetBinaryFilter(true); - padFilter->SetUpperThreshold(1); - padFilter->SetLowerThreshold(1); - padFilter->Update(); - - emptySegmentationNode->SetData(padFilter->GetOutput()); - } - - m_ToolManager->SetWorkingData(emptySegmentationNode); - m_ToolManager->GetWorkingData(0)->Modified(); - } + isFloatImage = true; } - } -} - -void mitk::BinaryThresholdBaseTool::OnRoiDataChanged() -{ - mitk::DataNode::Pointer node = m_ToolManager->GetRoiData(0); - - if (node.IsNotNull()) - { - mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); - mitk::Image::Pointer image = dynamic_cast(m_NodeForThresholding->GetData()); - - if (image.IsNull()) - return; - roiFilter->SetInput(image); - roiFilter->SetRegionOfInterest(node->GetData()); - roiFilter->Update(); - - mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); - tmpNode->SetData(roiFilter->GetOutput()); - - m_SensibleMinimumThresholdValue = static_cast(roiFilter->GetMinValue()); - m_SensibleMaximumThresholdValue = static_cast(roiFilter->GetMaxValue()); - - m_NodeForThresholding = tmpNode; - } - else - m_NodeForThresholding = m_OriginalImageNode; - - this->SetupPreviewNode(); - this->UpdatePreview(); -} - -void mitk::BinaryThresholdBaseTool::OnTimePointChanged() -{ - if (m_ThresholdFeedbackNode.IsNotNull() && m_NodeForThresholding.IsNotNull()) - { - if (m_ThresholdFeedbackNode->GetData()->GetTimeSteps() == 1) - { - this->UpdatePreview(); - } + IntervalBordersChanged.Send(m_SensibleMinimumThresholdValue, m_SensibleMaximumThresholdValue, isFloatImage); + ThresholdingValuesChanged.Send(m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue); } } template static void ITKThresholding(const itk::Image *originalImage, mitk::Image *segmentation, double lower, double upper, unsigned int timeStep) { typedef itk::Image ImageType; typedef itk::Image SegmentationType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); filter->SetInput(originalImage); filter->SetLowerThreshold(lower); filter->SetUpperThreshold(upper); filter->SetInsideValue(1); filter->SetOutsideValue(0); filter->Update(); segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } -template -static void ITKThresholdingOldBinary(const itk::Image *originalImage, - mitk::Image *segmentation, - double lower, - double upper, - unsigned int timeStep) +void mitk::BinaryThresholdBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) { - typedef itk::Image ImageType; - typedef itk::Image SegmentationType; - typedef itk::BinaryThresholdImageFilter ThresholdFilterType; - - typename ThresholdFilterType::Pointer filter = ThresholdFilterType::New(); - filter->SetInput(originalImage); - filter->SetLowerThreshold(lower); - filter->SetUpperThreshold(upper); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - - segmentation->SetVolume((void *)(filter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); -} - -void mitk::BinaryThresholdBaseTool::UpdatePreview() -{ - mitk::Image::Pointer thresholdImage = dynamic_cast(m_NodeForThresholding->GetData()); - mitk::Image::Pointer previewImage = dynamic_cast(m_ThresholdFeedbackNode->GetData()); - if (thresholdImage && previewImage) + if (nullptr != inputAtTimeStep && nullptr != previewImage) { - if (previewImage->GetTimeSteps() > 1) - { - for (unsigned int timeStep = 0; timeStep < thresholdImage->GetTimeSteps(); ++timeStep) - { - auto feedBackImage3D = this->Get3DImage(thresholdImage, timeStep); - - if (m_IsOldBinary) - { - AccessByItk_n(feedBackImage3D, - ITKThresholdingOldBinary, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - else - { - AccessByItk_n(feedBackImage3D, - ITKThresholding, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - } - } - else - { - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto feedBackImage3D = this->Get3DImageByTimePoint(thresholdImage, timePoint); - auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); - - if (m_IsOldBinary) - { - AccessByItk_n(feedBackImage3D, - ITKThresholdingOldBinary, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - else - { - AccessByItk_n(feedBackImage3D, - ITKThresholding, - (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); - } - } - RenderingManager::GetInstance()->RequestUpdateAll(); + AccessByItk_n(inputAtTimeStep, + ITKThresholding, + (previewImage, m_CurrentLowerThresholdValue, m_CurrentUpperThresholdValue, timeStep)); } } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h index dece9ae498..cdc15f18be 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdBaseTool.h @@ -1,98 +1,70 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkBinaryThresholdBaseTool_h_Included #define mitkBinaryThresholdBaseTool_h_Included -#include "mitkAutoSegmentationTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include #include #include namespace mitk { /** \brief Base class for binary threshold tools. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ - class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT BinaryThresholdBaseTool : public AutoSegmentationWithPreviewTool { public: Message3 IntervalBordersChanged; Message2 ThresholdingValuesChanged; - mitkClassMacro(BinaryThresholdBaseTool, AutoSegmentationTool); - - void Activated() override; - void Deactivated() override; + mitkClassMacro(BinaryThresholdBaseTool, AutoSegmentationWithPreviewTool); virtual void SetThresholdValues(double lower, double upper); - virtual void AcceptCurrentThresholdValue(); - virtual void CancelThresholding(); - - itkSetMacro(CreateAllTimeSteps, bool); - itkGetMacro(CreateAllTimeSteps, bool); - itkBooleanMacro(CreateAllTimeSteps); - protected: BinaryThresholdBaseTool(); // purposely hidden ~BinaryThresholdBaseTool() override; itkSetMacro(LockedUpperThreshold, bool); itkGetMacro(LockedUpperThreshold, bool); itkBooleanMacro(LockedUpperThreshold); itkGetMacro(SensibleMinimumThresholdValue, ScalarType); itkGetMacro(SensibleMaximumThresholdValue, ScalarType); - private: - void SetupPreviewNode(); - - void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); - - void CreateNewSegmentationFromThreshold(); - - void OnRoiDataChanged(); - void OnTimePointChanged(); - - void UpdatePreview(); - - DataNode::Pointer m_ThresholdFeedbackNode; - DataNode::Pointer m_OriginalImageNode; - DataNode::Pointer m_NodeForThresholding; + void InitiateToolByInput() override; + void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + private: ScalarType m_SensibleMinimumThresholdValue; ScalarType m_SensibleMaximumThresholdValue; ScalarType m_CurrentLowerThresholdValue; ScalarType m_CurrentUpperThresholdValue; - bool m_IsOldBinary = false; - - /** Indicates if Accepting the threshold should transfer/create the segmentations - of all time steps (true) or only of the currently selected timepoint (false).*/ - bool m_CreateAllTimeSteps = false; - /** Indicates if the tool should behave like a single threshold tool (true) or like a upper/lower threshold tool (false)*/ bool m_LockedUpperThreshold = false; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp index 4e5191b32f..48a1301bf6 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp @@ -1,460 +1,334 @@ /*============================================================================ 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 "mitkFastMarchingTool3D.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkInteractionConst.h" #include "mitkRenderingManager.h" -#include "itkOrImageFilter.h" -#include "mitkImageCast.h" -#include "mitkImageTimeSelector.h" +#include "mitkImageAccessByItk.h" + +// itk filter +#include "itkBinaryThresholdImageFilter.h" +#include "itkCurvatureAnisotropicDiffusionImageFilter.h" +#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" +#include "itkSigmoidImageFilter.h" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, FastMarchingTool3D, "FastMarching3D tool"); } mitk::FastMarchingTool3D::FastMarchingTool3D() - : /*FeedbackContourTool*/ AutoSegmentationTool(), - m_NeedUpdate(true), - m_CurrentTimeStep(0), + : AutoSegmentationWithPreviewTool(), m_LowerThreshold(0), m_UpperThreshold(200), m_StoppingValue(100), m_Sigma(1.0), m_Alpha(-0.5), m_Beta(3.0), m_PointSetAddObserverTag(0), m_PointSetRemoveObserverTag(0) { } mitk::FastMarchingTool3D::~FastMarchingTool3D() { } -bool mitk::FastMarchingTool3D::CanHandle(BaseData *referenceData) const +bool mitk::FastMarchingTool3D::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { + if(!Superclass::CanHandle(referenceData, workingData)) + return false; + if (referenceData == nullptr) return false; - auto *image = dynamic_cast(referenceData); + auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; return true; } const char **mitk::FastMarchingTool3D::GetXPM() const { return nullptr; // mitkFastMarchingTool3D_xpm; } us::ModuleResource mitk::FastMarchingTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); return resource; } const char *mitk::FastMarchingTool3D::GetName() const { return "Fast Marching 3D"; } void mitk::FastMarchingTool3D::SetUpperThreshold(double value) { m_UpperThreshold = value / 10.0; - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_NeedUpdate = true; } void mitk::FastMarchingTool3D::SetLowerThreshold(double value) { m_LowerThreshold = value / 10.0; - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_NeedUpdate = true; } void mitk::FastMarchingTool3D::SetBeta(double value) { if (m_Beta != value) { m_Beta = value; - m_SigmoidFilter->SetBeta(m_Beta); - m_NeedUpdate = true; } } void mitk::FastMarchingTool3D::SetSigma(double value) { if (m_Sigma != value) { if (value > 0.0) { m_Sigma = value; - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - m_NeedUpdate = true; } } } void mitk::FastMarchingTool3D::SetAlpha(double value) { if (m_Alpha != value) { m_Alpha = value; - m_SigmoidFilter->SetAlpha(m_Alpha); - m_NeedUpdate = true; } } void mitk::FastMarchingTool3D::SetStoppingValue(double value) { if (m_StoppingValue != value) { m_StoppingValue = value; - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); - m_NeedUpdate = true; } } void mitk::FastMarchingTool3D::Activated() { Superclass::Activated(); - m_ResultImageNode = mitk::DataNode::New(); - m_ResultImageNode->SetName("FastMarching_Preview"); - m_ResultImageNode->SetBoolProperty("helper object", true); - m_ResultImageNode->SetColor(0.0, 1.0, 0.0); - m_ResultImageNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_ResultImageNode, m_ToolManager->GetReferenceData(0)); - m_SeedsAsPointSet = mitk::PointSet::New(); m_SeedsAsPointSetNode = mitk::DataNode::New(); m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); m_SeedsAsPointSetNode->SetName("3D_FastMarching_PointSet"); m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); m_SeedsAsPointSetNode->SetVisibility(true); // Create PointSetData Interactor m_SeedPointInteractor = mitk::PointSetDataInteractor::New(); // Load the according state machine for regular point set interaction m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); // Set the configuration file that defines the triggers for the transitions m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); // set the DataNode (which already is added to the DataStorage m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); - m_ReferenceImageAsITK = InternalImageType::New(); - - m_ProgressCommand = mitk::ToolCommand::New(); - - m_ThresholdFilter = ThresholdingFilterType::New(); - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_ThresholdFilter->SetOutsideValue(0); - m_ThresholdFilter->SetInsideValue(1.0); - - m_SmoothFilter = SmoothingFilterType::New(); - m_SmoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_SmoothFilter->SetTimeStep(0.05); - m_SmoothFilter->SetNumberOfIterations(2); - m_SmoothFilter->SetConductanceParameter(9.0); - - m_GradientMagnitudeFilter = GradientFilterType::New(); - m_GradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - - m_SigmoidFilter = SigmoidFilterType::New(); - m_SigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_SigmoidFilter->SetAlpha(m_Alpha); - m_SigmoidFilter->SetBeta(m_Beta); - m_SigmoidFilter->SetOutputMinimum(0.0); - m_SigmoidFilter->SetOutputMaximum(1.0); - - m_FastMarchingFilter = FastMarchingFilterType::New(); - m_FastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); + m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); m_SeedContainer = NodeContainer::New(); m_SeedContainer->Initialize(); - m_FastMarchingFilter->SetTrialPoints(m_SeedContainer); - - // set up pipeline - m_SmoothFilter->SetInput(m_ReferenceImageAsITK); - m_GradientMagnitudeFilter->SetInput(m_SmoothFilter->GetOutput()); - m_SigmoidFilter->SetInput(m_GradientMagnitudeFilter->GetOutput()); - m_FastMarchingFilter->SetInput(m_SigmoidFilter->GetOutput()); - m_ThresholdFilter->SetInput(m_FastMarchingFilter->GetOutput()); - - m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); itk::SimpleMemberCommand::Pointer pointRemovedCommand = itk::SimpleMemberCommand::New(); pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); - - this->Initialize(); } void mitk::FastMarchingTool3D::Deactivated() { - m_ToolManager->GetDataStorage()->Remove(this->m_ResultImageNode); - m_ToolManager->GetDataStorage()->Remove(this->m_SeedsAsPointSetNode); this->ClearSeeds(); - this->m_SmoothFilter->RemoveAllObservers(); - this->m_SigmoidFilter->RemoveAllObservers(); - this->m_GradientMagnitudeFilter->RemoveAllObservers(); - this->m_FastMarchingFilter->RemoveAllObservers(); - m_ResultImageNode = nullptr; - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - unsigned int numberOfPoints = m_SeedsAsPointSet->GetSize(); - for (unsigned int i = 0; i < numberOfPoints; ++i) - { - mitk::Point3D point = m_SeedsAsPointSet->GetPoint(i); - auto *doOp = new mitk::PointOperation(mitk::OpREMOVE, point, 0); - m_SeedsAsPointSet->ExecuteOperation(doOp); - } // Deactivate Interaction m_SeedPointInteractor->SetDataNode(nullptr); m_ToolManager->GetDataStorage()->Remove(m_SeedsAsPointSetNode); m_SeedsAsPointSetNode = nullptr; m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); + m_SeedsAsPointSet = nullptr; Superclass::Deactivated(); } -void mitk::FastMarchingTool3D::Initialize() -{ - m_ReferenceImage = dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData()); - if (m_ReferenceImage->GetTimeGeometry()->CountTimeSteps() > 1) - { - mitk::ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(m_ReferenceImage); - timeSelector->SetTimeNr(m_CurrentTimeStep); - timeSelector->UpdateLargestPossibleRegion(); - m_ReferenceImage = timeSelector->GetOutput(); - } - CastToItkImage(m_ReferenceImage, m_ReferenceImageAsITK); - m_SmoothFilter->SetInput(m_ReferenceImageAsITK); - m_NeedUpdate = true; -} - -void mitk::FastMarchingTool3D::ConfirmSegmentation() -{ - // combine preview image with current working segmentation - if (dynamic_cast(m_ResultImageNode->GetData())) - { - // logical or combination of preview and segmentation slice - OutputImageType::Pointer segmentationImageInITK = OutputImageType::New(); - - mitk::Image::Pointer workingImage = dynamic_cast(GetTargetSegmentationNode()->GetData()); - if (workingImage->GetTimeGeometry()->CountTimeSteps() > 1) - { - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(workingImage); - timeSelector->SetTimeNr(m_CurrentTimeStep); - timeSelector->UpdateLargestPossibleRegion(); - CastToItkImage(timeSelector->GetOutput(), segmentationImageInITK); - } - else - { - CastToItkImage(workingImage, segmentationImageInITK); - } - - typedef itk::OrImageFilter OrImageFilterType; - OrImageFilterType::Pointer orFilter = OrImageFilterType::New(); - - orFilter->SetInput(0, m_ThresholdFilter->GetOutput()); - orFilter->SetInput(1, segmentationImageInITK); - orFilter->Update(); - - // set image volume in current time step from itk image - workingImage->SetVolume((void *)(m_ThresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), - m_CurrentTimeStep); - this->m_ResultImageNode->SetVisibility(false); - this->ClearSeeds(); - workingImage->Modified(); - } - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_ToolManager->ActivateTool(-1); -} - void mitk::FastMarchingTool3D::OnAddPoint() { // Add a new seed point for FastMarching algorithm mitk::Point3D clickInIndex; - m_ReferenceImage->GetGeometry()->WorldToIndex(m_SeedsAsPointSet->GetPoint(m_SeedsAsPointSet->GetSize() - 1), + this->GetReferenceData()->GetGeometry()->WorldToIndex(m_SeedsAsPointSet->GetPoint(m_SeedsAsPointSet->GetSize() - 1), clickInIndex); itk::Index<3> seedPosition; seedPosition[0] = clickInIndex[0]; seedPosition[1] = clickInIndex[1]; seedPosition[2] = clickInIndex[2]; NodeType node; const double seedValue = 0.0; node.SetValue(seedValue); node.SetIndex(seedPosition); this->m_SeedContainer->InsertElement(this->m_SeedContainer->Size(), node); - m_FastMarchingFilter->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_NeedUpdate = true; - - this->Update(); - - m_ReadyMessage.Send(); + this->UpdatePreview(); } void mitk::FastMarchingTool3D::OnDelete() { // delete last seed point if (!(this->m_SeedContainer->empty())) { // delete last element of seeds container this->m_SeedContainer->pop_back(); - m_FastMarchingFilter->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_NeedUpdate = true; - - this->Update(); - } -} - -void mitk::FastMarchingTool3D::Update() -{ - const unsigned int progress_steps = 200; - - if (m_NeedUpdate) - { - m_ProgressCommand->AddStepsToDo(progress_steps); - - // remove interaction with poinset while updating - m_SeedPointInteractor->SetDataNode(nullptr); - CurrentlyBusy.Send(true); - try - { - m_ThresholdFilter->Update(); - } - catch (itk::ExceptionObject &excep) - { - MITK_ERROR << "Exception caught: " << excep.GetDescription(); - - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - std::string msg = excep.GetDescription(); - ErrorMessage.Send(msg); - - return; - } - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - // make output visible - mitk::Image::Pointer result = mitk::Image::New(); - CastToMitkImage(m_ThresholdFilter->GetOutput(), result); - result->GetGeometry()->SetOrigin(m_ReferenceImage->GetGeometry()->GetOrigin()); - result->GetGeometry()->SetIndexToWorldTransform(m_ReferenceImage->GetGeometry()->GetIndexToWorldTransform()); - m_ResultImageNode->SetData(result); - m_ResultImageNode->SetVisibility(true); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - // add interaction with poinset again - m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); + this->UpdatePreview(); } } void mitk::FastMarchingTool3D::ClearSeeds() { // clear seeds for FastMarching as well as the PointSet for visualization if (this->m_SeedContainer.IsNotNull()) this->m_SeedContainer->Initialize(); if (this->m_SeedsAsPointSet.IsNotNull()) { // remove observers from current pointset m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); // renew pointset this->m_SeedsAsPointSet = mitk::PointSet::New(); this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); m_SeedsAsPointSetNode->SetName("Seeds_Preview"); m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); m_SeedsAsPointSetNode->SetVisibility(true); // add callback function for adding and removing points itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); itk::SimpleMemberCommand::Pointer pointRemovedCommand = itk::SimpleMemberCommand::New(); pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); } +} - if (this->m_FastMarchingFilter.IsNotNull()) - m_FastMarchingFilter->Modified(); +template +void mitk::FastMarchingTool3D::DoITKFastMarching(const itk::Image* inputImage, + mitk::Image* segmentation, unsigned int timeStep) +{ + typedef itk::Image InputImageType; + + /* typedefs for itk pipeline */ + + typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; + typedef itk::Image OutputImageType; + + typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; + typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; + typedef itk::SigmoidImageFilter SigmoidFilterType; + typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; + + auto smoothFilter = SmoothingFilterType::New(); + smoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + smoothFilter->SetTimeStep(0.05); + smoothFilter->SetNumberOfIterations(2); + smoothFilter->SetConductanceParameter(9.0); + + auto gradientMagnitudeFilter = GradientFilterType::New(); + gradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + gradientMagnitudeFilter->SetSigma(m_Sigma); + + auto sigmoidFilter = SigmoidFilterType::New(); + sigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + sigmoidFilter->SetAlpha(m_Alpha); + sigmoidFilter->SetBeta(m_Beta); + sigmoidFilter->SetOutputMinimum(0.0); + sigmoidFilter->SetOutputMaximum(1.0); + + auto fastMarchingFilter = FastMarchingFilterType::New(); + fastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + fastMarchingFilter->SetStoppingValue(m_StoppingValue); + fastMarchingFilter->SetTrialPoints(m_SeedContainer); + + auto thresholdFilter = ThresholdingFilterType::New(); + thresholdFilter->SetLowerThreshold(m_LowerThreshold); + thresholdFilter->SetUpperThreshold(m_UpperThreshold); + thresholdFilter->SetOutsideValue(0); + thresholdFilter->SetInsideValue(1.0); - this->m_NeedUpdate = true; + // set up pipeline + smoothFilter->SetInput(inputImage); + gradientMagnitudeFilter->SetInput(smoothFilter->GetOutput()); + sigmoidFilter->SetInput(gradientMagnitudeFilter->GetOutput()); + fastMarchingFilter->SetInput(sigmoidFilter->GetOutput()); + thresholdFilter->SetInput(fastMarchingFilter->GetOutput()); + thresholdFilter->Update(); + + segmentation->SetVolume((void*)(thresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } -void mitk::FastMarchingTool3D::Reset() +void mitk::FastMarchingTool3D::UpdatePrepare() { - // clear all seeds and preview empty result - this->ClearSeeds(); - - m_ResultImageNode->SetVisibility(false); + // remove interaction with poinset while updating + if (m_SeedPointInteractor.IsNotNull()) + m_SeedPointInteractor->SetDataNode(nullptr); +} - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); +void mitk::FastMarchingTool3D::UpdateCleanUp() +{ + // add interaction with poinset again + if (m_SeedPointInteractor.IsNotNull()) + m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); } -void mitk::FastMarchingTool3D::SetCurrentTimeStep(int t) +void mitk::FastMarchingTool3D::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) { - if (m_CurrentTimeStep != t) + if (nullptr != inputAtTimeStep && nullptr != previewImage && m_SeedContainer.IsNotNull() && !m_SeedContainer->empty()) { - m_CurrentTimeStep = t; - - this->Initialize(); + AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 3, (previewImage, timeStep)); } } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h index eeefc20b4f..652e0d550b 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h @@ -1,164 +1,124 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkFastMarchingTool3D_h_Included #define mitkFastMarchingTool3D_h_Included -#include "mitkAutoSegmentationTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkPointSet.h" #include "mitkPointSetDataInteractor.h" #include "mitkToolCommand.h" -#include - -#include "mitkMessage.h" #include "itkImage.h" - -// itk filter -#include "itkBinaryThresholdImageFilter.h" -#include "itkCurvatureAnisotropicDiffusionImageFilter.h" #include "itkFastMarchingImageFilter.h" -#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" -#include "itkSigmoidImageFilter.h" + +#include namespace us { class ModuleResource; } namespace mitk { /** \brief FastMarching semgentation tool. The segmentation is done by setting one or more seed points on the image and adapting the time range and threshold. The pipeline is: Smoothing->GradientMagnitude->SigmoidFunction->FastMarching->Threshold The resulting binary image is seen as a segmentation of an object. For detailed documentation see ITK Software Guide section 9.3.1 Fast Marching Segmentation. */ - class MITKSEGMENTATION_EXPORT FastMarchingTool3D : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT FastMarchingTool3D : public AutoSegmentationWithPreviewTool { - mitkNewMessageMacro(Ready); - public: - mitkClassMacro(FastMarchingTool3D, AutoSegmentationTool); + mitkClassMacro(FastMarchingTool3D, AutoSegmentationWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - /* typedefs for itk pipeline */ - typedef float InternalPixelType; - typedef itk::Image InternalImageType; - typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; - typedef itk::Image OutputImageType; - - typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; - typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; - typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; - typedef itk::SigmoidImageFilter SigmoidFilterType; - typedef itk::FastMarchingImageFilter FastMarchingFilterType; - typedef FastMarchingFilterType::NodeContainer NodeContainer; - typedef FastMarchingFilterType::NodeType NodeType; - - bool CanHandle(BaseData *referenceData) const override; + bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /* icon stuff */ const char **GetXPM() const override; const char *GetName() const override; us::ModuleResource GetIconResource() const override; + void Activated() override; + void Deactivated() override; + /// \brief Set parameter used in Threshold filter. void SetUpperThreshold(double); /// \brief Set parameter used in Threshold filter. void SetLowerThreshold(double); /// \brief Set parameter used in Fast Marching filter. void SetStoppingValue(double); /// \brief Set parameter used in Gradient Magnitude filter. void SetSigma(double); /// \brief Set parameter used in Fast Marching filter. void SetAlpha(double); /// \brief Set parameter used in Fast Marching filter. void SetBeta(double); - /// \brief Adds the feedback image to the current working image. - virtual void ConfirmSegmentation(); - - /// \brief Set the working time step. - virtual void SetCurrentTimeStep(int t); - /// \brief Clear all seed points. void ClearSeeds(); - /// \brief Updates the itk pipeline and shows the result of FastMarching. - void Update(); - protected: FastMarchingTool3D(); ~FastMarchingTool3D() override; - void Activated() override; - void Deactivated() override; - virtual void Initialize(); - /// \brief Add point action of StateMachine pattern virtual void OnAddPoint(); /// \brief Delete action of StateMachine pattern virtual void OnDelete(); - /// \brief Reset all relevant inputs of the itk pipeline. - void Reset(); - - mitk::ToolCommand::Pointer m_ProgressCommand; - - Image::Pointer m_ReferenceImage; - - bool m_NeedUpdate; + void UpdatePrepare() override; + void UpdateCleanUp() override; + void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; - int m_CurrentTimeStep; + template + void DoITKFastMarching(const itk::Image* inputImage, + mitk::Image* segmentation, unsigned int timeStep); float m_LowerThreshold; // used in Threshold filter float m_UpperThreshold; // used in Threshold filter float m_StoppingValue; // used in Fast Marching filter float m_Sigma; // used in GradientMagnitude filter float m_Alpha; // used in Sigmoid filter float m_Beta; // used in Sigmoid filter - NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching - - InternalImageType::Pointer m_ReferenceImageAsITK; // the reference image as itk::Image + typedef float InternalPixelType; + typedef itk::Image InternalImageType; + typedef itk::FastMarchingImageFilter FastMarchingFilterType; + typedef FastMarchingFilterType::NodeContainer NodeContainer; + typedef FastMarchingFilterType::NodeType NodeType; - mitk::DataNode::Pointer m_ResultImageNode; // holds the result as a preview image + NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points mitk::PointSet::Pointer m_SeedsAsPointSet; mitk::PointSetDataInteractor::Pointer m_SeedPointInteractor; unsigned int m_PointSetAddObserverTag; unsigned int m_PointSetRemoveObserverTag; - - ThresholdingFilterType::Pointer m_ThresholdFilter; - SmoothingFilterType::Pointer m_SmoothFilter; - GradientFilterType::Pointer m_GradientMagnitudeFilter; - SigmoidFilterType::Pointer m_SigmoidFilter; - FastMarchingFilterType::Pointer m_FastMarchingFilter; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index 917451a48c..9a5f4cc388 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp @@ -1,277 +1,253 @@ /*============================================================================ 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 "mitkOtsuTool3D.h" #include "mitkImageAccessByItk.h" #include "mitkLabelSetImage.h" #include "mitkOtsuSegmentationFilter.h" #include "mitkRenderingManager.h" #include "mitkToolManager.h" #include #include #include #include #include #include // ITK #include #include #include // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } -mitk::OtsuTool3D::OtsuTool3D() +mitk::OtsuTool3D::OtsuTool3D() : AutoSegmentationWithPreviewTool(true) { } mitk::OtsuTool3D::~OtsuTool3D() { } +void mitk::OtsuTool3D::SetSelectedRegions(const SelectedRegionVectorType& regions) +{ + if (m_SelectedRegions != regions) + { + m_SelectedRegions = regions; + //Note: we do not call this->Modified() on puprose. Reason: changing the + //selected regions should not force to run otsu filter in DoUpdatePreview due to changed MTime. + } +} + +mitk::OtsuTool3D::SelectedRegionVectorType mitk::OtsuTool3D::GetSelectedRegions() const +{ + return this->m_SelectedRegions; +} + void mitk::OtsuTool3D::Activated() { Superclass::Activated(); - if (m_ToolManager) - { - m_OriginalImage = dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData()); - - m_BinaryPreviewNode = mitk::DataNode::New(); - m_BinaryPreviewNode->SetName("Binary_Preview"); - m_BinaryPreviewNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); - m_BinaryPreviewNode->SetProperty("opacity", FloatProperty::New(0.3)); - // m_BinaryPreviewNode->SetBoolProperty("helper object", true); - // m_BinaryPreviewNode->SetProperty("binary", mitk::BoolProperty::New(true)); - m_ToolManager->GetDataStorage()->Add(this->m_BinaryPreviewNode); - - m_MultiLabelResultNode = mitk::DataNode::New(); - m_MultiLabelResultNode->SetName("Otsu_Preview"); - // m_MultiLabelResultNode->SetBoolProperty("helper object", true); - m_MultiLabelResultNode->SetVisibility(true); - - m_MaskedImagePreviewNode = mitk::DataNode::New(); - m_MaskedImagePreviewNode->SetName("Volume_Preview"); - // m_MultiLabelResultNode->SetBoolProperty("helper object", true); - m_MaskedImagePreviewNode->SetVisibility(false); - - m_ToolManager->GetDataStorage()->Add(this->m_MultiLabelResultNode); - } + m_SelectedRegions = {}; + m_NumberOfBins = 128; + m_NumberOfRegions = 2; + m_UseValley = false; + + m_OtsuResultNode = mitk::DataNode::New(); + m_OtsuResultNode->SetName("Otsu_Preview"); + // m_MultiLabelResultNode->SetBoolProperty("helper object", true); + m_OtsuResultNode->SetVisibility(true); + m_OtsuResultNode->SetOpacity(1.0); + + m_ToolManager->GetDataStorage()->Add(m_OtsuResultNode); } void mitk::OtsuTool3D::Deactivated() { - m_ToolManager->GetDataStorage()->Remove(this->m_MultiLabelResultNode); - m_MultiLabelResultNode = nullptr; - m_ToolManager->GetDataStorage()->Remove(this->m_BinaryPreviewNode); - m_BinaryPreviewNode = nullptr; - m_ToolManager->GetDataStorage()->Remove(this->m_MaskedImagePreviewNode); - m_MaskedImagePreviewNode = nullptr; + m_ToolManager->GetDataStorage()->Remove(m_OtsuResultNode); + m_OtsuResultNode = nullptr; Superclass::Deactivated(); } const char **mitk::OtsuTool3D::GetXPM() const { return nullptr; } us::ModuleResource mitk::OtsuTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Otsu_48x48.png"); return resource; } -void mitk::OtsuTool3D::RunSegmentation(int regions, bool useValley, int numberOfBins) +const char* mitk::OtsuTool3D::GetName() const { - int numberOfThresholds = regions - 1; + return "Otsu"; +} +void mitk::OtsuTool3D::UpdateCleanUp() +{ + if (m_OtsuResultNode.IsNotNull()) + m_OtsuResultNode->SetVisibility(m_SelectedRegions.empty()); - const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto image3D = Get3DImageByTimePoint(m_OriginalImage, timePoint); + if (nullptr != this->GetPreviewSegmentationNode()) + this->GetPreviewSegmentationNode()->SetVisibility(!m_SelectedRegions.empty()); - if (nullptr == image3D) + if (m_SelectedRegions.empty()) { - MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected reference image. Time point: " << timePoint; - return; + this->ResetPreviewNode(); } +} - mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); - otsuFilter->SetNumberOfThresholds(numberOfThresholds); - otsuFilter->SetValleyEmphasis(useValley); - otsuFilter->SetNumberOfBins(numberOfBins); - otsuFilter->SetInput(image3D); +void mitk::OtsuTool3D::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) +{ + int numberOfThresholds = m_NumberOfRegions - 1; - try + const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + mitk::LabelSetImage::Pointer otsuResultImage = dynamic_cast(this->m_OtsuResultNode->GetData()); + + if (nullptr == m_OtsuResultNode->GetData() + || this->GetMTime() > m_OtsuResultNode->GetData()->GetMTime() + || this->m_LastOtsuTimeStep != timeStep //this covers the case where dynamic + //segmentations have to compute a preview + //for all time steps on confirmation + || this->GetLastTimePointOfUpdate() != timePoint //this ensures that static seg + //previews work with dynamic images + //with avoiding unnecessary other otsu computations + ) { - otsuFilter->Update(); + if (nullptr == inputAtTimeStep) + { + MITK_WARN << "Cannot run segementation. Currently selected input image is not set."; + return; + } + + this->m_LastOtsuTimeStep = timeStep; + + mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); + otsuFilter->SetNumberOfThresholds(numberOfThresholds); + otsuFilter->SetValleyEmphasis(m_UseValley); + otsuFilter->SetNumberOfBins(m_NumberOfBins); + otsuFilter->SetInput(inputAtTimeStep); + otsuFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + + try + { + otsuFilter->Update(); + } + catch (...) + { + mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; + } + + otsuResultImage = mitk::LabelSetImage::New(); + otsuResultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); + this->m_OtsuResultNode->SetData(otsuResultImage); + this->m_OtsuResultNode->SetProperty("binary", mitk::BoolProperty::New(false)); + mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); + renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); + this->m_OtsuResultNode->SetProperty("Image Rendering.Mode", renderingMode); + mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); + mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); + vtkSmartPointer lookupTable = vtkSmartPointer::New(); + lookupTable->SetHueRange(1.0, 0.0); + lookupTable->SetSaturationRange(1.0, 1.0); + lookupTable->SetValueRange(1.0, 1.0); + lookupTable->SetTableRange(-1.0, 1.0); + lookupTable->Build(); + lut->SetVtkLookupTable(lookupTable); + prop->SetLookupTable(lut); + this->m_OtsuResultNode->SetProperty("LookupTable", prop); + mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); + mitk::LevelWindow levelwindow; + levelwindow.SetRangeMinMax(0, numberOfThresholds + 1); + levWinProp->SetLevelWindow(levelwindow); + this->m_OtsuResultNode->SetProperty("levelwindow", levWinProp); } - catch (...) + + if (!m_SelectedRegions.empty()) { - mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; + AccessByItk_n(otsuResultImage, CalculatePreview, (previewImage, timeStep)); } - - m_ToolManager->GetDataStorage()->Remove(this->m_MultiLabelResultNode); - m_MultiLabelResultNode = nullptr; - m_MultiLabelResultNode = mitk::DataNode::New(); - m_MultiLabelResultNode->SetName("Otsu_Preview"); - m_MultiLabelResultNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_MultiLabelResultNode); - m_MultiLabelResultNode->SetOpacity(1.0); - - mitk::LabelSetImage::Pointer resultImage = mitk::LabelSetImage::New(); - resultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); - this->m_MultiLabelResultNode->SetData(resultImage); - m_MultiLabelResultNode->SetProperty("binary", mitk::BoolProperty::New(false)); - mitk::RenderingModeProperty::Pointer renderingMode = mitk::RenderingModeProperty::New(); - renderingMode->SetValue(mitk::RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); - m_MultiLabelResultNode->SetProperty("Image Rendering.Mode", renderingMode); - mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); - mitk::LookupTableProperty::Pointer prop = mitk::LookupTableProperty::New(lut); - vtkSmartPointer lookupTable = vtkSmartPointer::New(); - lookupTable->SetHueRange(1.0, 0.0); - lookupTable->SetSaturationRange(1.0, 1.0); - lookupTable->SetValueRange(1.0, 1.0); - lookupTable->SetTableRange(-1.0, 1.0); - lookupTable->Build(); - lut->SetVtkLookupTable(lookupTable); - prop->SetLookupTable(lut); - m_MultiLabelResultNode->SetProperty("LookupTable", prop); - mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(); - mitk::LevelWindow levelwindow; - levelwindow.SetRangeMinMax(0, numberOfThresholds + 1); - levWinProp->SetLevelWindow(levelwindow); - m_MultiLabelResultNode->SetProperty("levelwindow", levWinProp); - - // m_BinaryPreviewNode->SetVisibility(false); - // m_MultiLabelResultNode->SetVisibility(true); - // this->m_OtsuSegmentationDialog->setCursor(Qt::ArrowCursor); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::OtsuTool3D::ConfirmSegmentation() -{ - mitk::LabelSetImage::Pointer resultImage = mitk::LabelSetImage::New(); - resultImage->InitializeByLabeledImage(dynamic_cast(m_BinaryPreviewNode->GetData())); - GetTargetSegmentationNode()->SetData(resultImage); - - m_ToolManager->ActivateTool(-1); -} - -void mitk::OtsuTool3D::UpdateBinaryPreview(std::vector regionIDs) -{ - m_MultiLabelResultNode->SetVisibility(false); - mitk::Image::Pointer multiLabelSegmentation = dynamic_cast(m_MultiLabelResultNode->GetData()); - AccessByItk_1(multiLabelSegmentation, CalculatePreview, regionIDs); } template -void mitk::OtsuTool3D::CalculatePreview(itk::Image *itkImage, std::vector regionIDs) +void mitk::OtsuTool3D::CalculatePreview(itk::Image *itkImage, mitk::Image* segmentation, unsigned int timeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::BinaryThresholdImageFilter FilterType; typename FilterType::Pointer filter = FilterType::New(); // InputImageType::Pointer itkImage; - typename OutputImageType::Pointer itkBinaryTempImage1; - typename OutputImageType::Pointer itkBinaryTempImage2; typename OutputImageType::Pointer itkBinaryResultImage; - // mitk::Image::Pointer multiLabelSegmentation = dynamic_cast(m_MultiLabelResultNode->GetData()); - // mitk::CastToItkImage(multiLabelSegmentation, itkImage); - filter->SetInput(itkImage); - filter->SetLowerThreshold(regionIDs[0]); - filter->SetUpperThreshold(regionIDs[0]); + filter->SetLowerThreshold(m_SelectedRegions[0]); + filter->SetUpperThreshold(m_SelectedRegions[0]); filter->SetInsideValue(1); filter->SetOutsideValue(0); + filter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); filter->Update(); - itkBinaryTempImage2 = filter->GetOutput(); - - typename itk::OrImageFilter::Pointer orFilter = - itk::OrImageFilter::New(); + itkBinaryResultImage = filter->GetOutput(); + itkBinaryResultImage->DisconnectPipeline(); // if more than one region id is used compute the union of all given binary regions - for (auto it = regionIDs.begin(); it != regionIDs.end(); ++it) + for (const auto regionID : m_SelectedRegions) { - filter->SetLowerThreshold(*it); - filter->SetUpperThreshold(*it); - filter->SetInsideValue(1); - filter->SetOutsideValue(0); - filter->Update(); - itkBinaryTempImage1 = filter->GetOutput(); - - orFilter->SetInput1(itkBinaryTempImage1); - orFilter->SetInput2(itkBinaryTempImage2); - - orFilter->UpdateLargestPossibleRegion(); - itkBinaryResultImage = orFilter->GetOutput(); - itkBinaryTempImage2 = itkBinaryResultImage; + if (regionID != m_SelectedRegions[0]) + { + filter->SetLowerThreshold(regionID); + filter->SetUpperThreshold(regionID); + filter->SetInsideValue(1); + filter->SetOutsideValue(0); + filter->Update(); + + typename OutputImageType::Pointer tempImage = filter->GetOutput(); + + typename itk::OrImageFilter::Pointer orFilter = + itk::OrImageFilter::New(); + orFilter->SetInput1(tempImage); + orFilter->SetInput2(itkBinaryResultImage); + orFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); + + orFilter->UpdateLargestPossibleRegion(); + itkBinaryResultImage = orFilter->GetOutput(); + } } //---------------------------------------------------------------------------------------------------- - mitk::Image::Pointer binarySegmentation; - mitk::CastToMitkImage(itkBinaryResultImage, binarySegmentation); - m_BinaryPreviewNode->SetData(binarySegmentation); - m_BinaryPreviewNode->SetVisibility(true); - m_BinaryPreviewNode->SetProperty("outline binary", mitk::BoolProperty::New(false)); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -const char *mitk::OtsuTool3D::GetName() const -{ - return "Otsu"; -} -void mitk::OtsuTool3D::UpdateVolumePreview(bool volumeRendering) -{ - if (volumeRendering) - { - m_MaskedImagePreviewNode->SetBoolProperty("volumerendering", true); - m_MaskedImagePreviewNode->SetBoolProperty("volumerendering.uselod", true); - } - else - { - m_MaskedImagePreviewNode->SetBoolProperty("volumerendering", false); - } - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::OtsuTool3D::ShowMultiLabelResultNode(bool show) -{ - m_MultiLabelResultNode->SetVisibility(show); - m_BinaryPreviewNode->SetVisibility(!show); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + segmentation->SetVolume((void*)(itkBinaryResultImage->GetPixelContainer()->GetBufferPointer()), timeStep); } -int mitk::OtsuTool3D::GetNumberOfBins() +unsigned int mitk::OtsuTool3D::GetMaxNumberOfBins() const { - ScalarType min = m_OriginalImage.GetPointer()->GetStatistics()->GetScalarValueMin(); - ScalarType max = m_OriginalImage.GetPointer()->GetStatistics()->GetScalarValueMaxNoRecompute(); - return static_cast(max - min) + 1; + const auto min = this->GetReferenceData()->GetStatistics()->GetScalarValueMin(); + const auto max = this->GetReferenceData()->GetStatistics()->GetScalarValueMaxNoRecompute(); + return static_cast(max - min) + 1; } diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h index f4e22cab62..7761ec60dd 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.h +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.h @@ -1,68 +1,80 @@ /*============================================================================ 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 MITKOTSUTOOL3D_H #define MITKOTSUTOOL3D_H #include "itkImage.h" -#include "mitkAutoSegmentationTool.h" +#include "mitkAutoSegmentationWithPreviewTool.h" +#include "mitkDataNode.h" #include namespace us { class ModuleResource; } namespace mitk { class Image; - class MITKSEGMENTATION_EXPORT OtsuTool3D : public AutoSegmentationTool + class MITKSEGMENTATION_EXPORT OtsuTool3D : public AutoSegmentationWithPreviewTool { public: - mitkClassMacro(OtsuTool3D, AutoSegmentationTool); + mitkClassMacro(OtsuTool3D, AutoSegmentationWithPreviewTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - const char *GetName() const override; + const char *GetName() const override; const char **GetXPM() const override; us::ModuleResource GetIconResource() const override; void Activated() override; void Deactivated() override; - void RunSegmentation(int regions, bool useValley, int numberOfBins); - void ConfirmSegmentation(); - // void UpdateBinaryPreview(int regionID); - void UpdateBinaryPreview(std::vector regionIDs); - void UpdateVolumePreview(bool volumeRendering); - void ShowMultiLabelResultNode(bool); + itkSetMacro(NumberOfBins, unsigned int); + itkGetConstMacro(NumberOfBins, unsigned int); - int GetNumberOfBins(); + itkSetMacro(NumberOfRegions, unsigned int); + itkGetConstMacro(NumberOfRegions, unsigned int); + + itkSetMacro(UseValley, bool); + itkGetConstMacro(UseValley, bool); + itkBooleanMacro(UseValley); + + using SelectedRegionVectorType = std::vector; + void SetSelectedRegions(const SelectedRegionVectorType& regions); + SelectedRegionVectorType GetSelectedRegions() const; + + /**Returns the number of max bins based on the current input image.*/ + unsigned int GetMaxNumberOfBins() const; protected: OtsuTool3D(); ~OtsuTool3D() override; + void UpdateCleanUp() override; + void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; + template - void CalculatePreview(itk::Image *itkImage, std::vector regionIDs); + void CalculatePreview(itk::Image *itkImage, mitk::Image* segmentation, unsigned int timeStep); - itk::SmartPointer m_OriginalImage; - // holds the user selected binary segmentation - mitk::DataNode::Pointer m_BinaryPreviewNode; - // holds the multilabel result as a preview image - mitk::DataNode::Pointer m_MultiLabelResultNode; - // holds the user selected binary segmentation masked original image - mitk::DataNode::Pointer m_MaskedImagePreviewNode; + unsigned int m_NumberOfBins = 128; + unsigned int m_NumberOfRegions = 2; + bool m_UseValley = false; + SelectedRegionVectorType m_SelectedRegions = {}; + // holds the multilabel result as a preview image + mitk::DataNode::Pointer m_OtsuResultNode; + TimeStepType m_LastOtsuTimeStep = 0; }; // class } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp index 689b0dbded..988d50cc5c 100644 --- a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp @@ -1,663 +1,664 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkRegionGrowingTool.h" #include "mitkApplicationCursor.h" #include "mitkBaseRenderer.h" #include "mitkImageDataItem.h" #include "mitkImageToContourModelFilter.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkRegionGrowingTool.xpm" #include "mitkRenderingManager.h" #include "mitkToolManager.h" #include "mitkExtractDirectedPlaneImageFilterNew.h" #include "mitkLabelSetImage.h" #include "mitkOverwriteDirectedPlaneImageFilter.h" // us #include #include #include #include // ITK #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, RegionGrowingTool, "Region growing tool"); } #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) mitk::RegionGrowingTool::RegionGrowingTool() : FeedbackContourTool("PressMoveRelease"), m_SeedValue(0), m_ScreenYDifference(0), m_ScreenXDifference(0), m_MouseDistanceScaleFactor(0.5), m_PaintingPixelValue(0), m_FillFeedbackContour(true), m_ConnectedComponentValue(1) { } mitk::RegionGrowingTool::~RegionGrowingTool() { } void mitk::RegionGrowingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); } const char **mitk::RegionGrowingTool::GetXPM() const { return mitkRegionGrowingTool_xpm; } us::ModuleResource mitk::RegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); return resource; } us::ModuleResource mitk::RegionGrowingTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_Cursor_32x32.png"); return resource; } const char *mitk::RegionGrowingTool::GetName() const { return "Region Growing"; } void mitk::RegionGrowingTool::Activated() { Superclass::Activated(); } void mitk::RegionGrowingTool::Deactivated() { Superclass::Deactivated(); } // Get the average pixel value of square/cube with radius=neighborhood around index template void mitk::RegionGrowingTool::GetNeighborhoodAverage(const itk::Image *itkImage, const itk::Index& index, ScalarType *result, unsigned int neighborhood) { // maybe assert that image dimension is only 2 or 3? auto neighborhoodInt = (int)neighborhood; TPixel averageValue(0); unsigned int numberOfPixels = (2 * neighborhood + 1) * (2 * neighborhood + 1); if (imageDimension == 3) { numberOfPixels *= (2 * neighborhood + 1); } MITK_DEBUG << "Getting neighborhood of " << numberOfPixels << " pixels around " << index; itk::Index currentIndex; for (int i = (0 - neighborhoodInt); i <= neighborhoodInt; ++i) { currentIndex[0] = index[0] + i; for (int j = (0 - neighborhoodInt); j <= neighborhoodInt; ++j) { currentIndex[1] = index[1] + j; if (imageDimension == 3) { for (int k = (0 - neighborhoodInt); k <= neighborhoodInt; ++k) { currentIndex[2] = index[2] + k; if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } else { if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } } *result = (ScalarType)averageValue; *result /= numberOfPixels; } // Check whether index lies inside a segmentation template void mitk::RegionGrowingTool::IsInsideSegmentation(const itk::Image *itkImage, const itk::Index& index, bool *result) { if (itkImage->GetPixel(index) > 0) { *result = true; } else { *result = false; } } // Do the region growing (i.e. call an ITK filter that does it) template void mitk::RegionGrowingTool::StartRegionGrowing(const itk::Image *inputImage, const itk::Index& seedIndex, const std::array& thresholds, mitk::Image::Pointer &outputImage) { MITK_DEBUG << "Starting region growing at index " << seedIndex << " with lower threshold " << thresholds[0] << " and upper threshold " << thresholds[1]; typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); // perform region growing in desired segmented region regionGrower->SetInput(inputImage); regionGrower->SetSeed(seedIndex); regionGrower->SetLower(thresholds[0]); regionGrower->SetUpper(thresholds[1]); try { regionGrower->Update(); } catch (...) { return; // Should we do something? } typename OutputImageType::Pointer resultImage = regionGrower->GetOutput(); // Smooth result: Every pixel is replaced by the majority of the neighborhood typedef itk::NeighborhoodIterator NeighborhoodIteratorType; typedef itk::ImageRegionIterator ImageIteratorType; typename NeighborhoodIteratorType::RadiusType radius; radius.Fill(2); // for now, maybe make this something the user can adjust in the preferences? typedef itk::ImageDuplicator< OutputImageType > DuplicatorType; typename DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage(resultImage); duplicator->Update(); typename OutputImageType::Pointer resultDup = duplicator->GetOutput(); NeighborhoodIteratorType neighborhoodIterator(radius, resultDup, resultDup->GetRequestedRegion()); ImageIteratorType imageIterator(resultImage, resultImage->GetRequestedRegion()); for (neighborhoodIterator.GoToBegin(), imageIterator.GoToBegin(); !neighborhoodIterator.IsAtEnd(); ++neighborhoodIterator, ++imageIterator) { DefaultSegmentationDataType voteYes(0); DefaultSegmentationDataType voteNo(0); for (unsigned int i = 0; i < neighborhoodIterator.Size(); ++i) { if (neighborhoodIterator.GetPixel(i) > 0) { voteYes += 1; } else { voteNo += 1; } } if (voteYes > voteNo) { imageIterator.Set(1); } else { imageIterator.Set(0); } } if (resultImage.IsNull()) { MITK_DEBUG << "Region growing result is empty."; } // Can potentially have multiple regions, use connected component image filter to label disjunct regions typedef itk::ConnectedComponentImageFilter ConnectedComponentImageFilterType; typename ConnectedComponentImageFilterType::Pointer connectedComponentFilter = ConnectedComponentImageFilterType::New(); connectedComponentFilter->SetInput(resultImage); connectedComponentFilter->Update(); typename OutputImageType::Pointer resultImageCC = connectedComponentFilter->GetOutput(); m_ConnectedComponentValue = resultImageCC->GetPixel(seedIndex); outputImage = mitk::GrabItkImageMemory(resultImageCC); } template void mitk::RegionGrowingTool::CalculateInitialThresholds(const itk::Image*) { LevelWindow levelWindow; m_ToolManager->GetReferenceData(0)->GetLevelWindow(levelWindow); - m_ThresholdExtrema = { std::numeric_limits::lowest(), std::numeric_limits::max() }; + m_ThresholdExtrema[0] = static_cast(std::numeric_limits::lowest()); + m_ThresholdExtrema[1] = static_cast(std::numeric_limits::max()); const ScalarType lowerWindowBound = std::max(m_ThresholdExtrema[0], levelWindow.GetLowerWindowBound()); const ScalarType upperWindowBound = std::min(m_ThresholdExtrema[1], levelWindow.GetUpperWindowBound()); if (m_SeedValue < lowerWindowBound) { m_InitialThresholds = { m_ThresholdExtrema[0], lowerWindowBound }; } else if (m_SeedValue > upperWindowBound) { m_InitialThresholds = { upperWindowBound, m_ThresholdExtrema[1] }; } else { const ScalarType range = 0.1 * (upperWindowBound - lowerWindowBound); // 10% of the visible window m_InitialThresholds[0] = std::min(std::max(lowerWindowBound, m_SeedValue - 0.5 * range), upperWindowBound - range); m_InitialThresholds[1] = m_InitialThresholds[0] + range; } } void mitk::RegionGrowingTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; MITK_DEBUG << "OnMousePressed"; m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_LastScreenPosition = positionEvent->GetPointerPositionOnScreen(); // ReferenceSlice is from the underlying image, WorkingSlice from the active segmentation (can be empty) m_ReferenceSlice = FeedbackContourTool::GetAffectedReferenceSlice(positionEvent); m_WorkingSlice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); if (m_WorkingSlice.IsNotNull()) // can't do anything without a working slice (i.e. a possibly empty segmentation) { MITK_DEBUG << "OnMousePressed: got working slice"; // 2. Determine if the user clicked inside or outside of the segmentation/working slice (i.e. the whole volume) mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); workingSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), m_SeedPoint); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; if (workingSliceGeometry->IsIndexInside(m_SeedPoint)) { MITK_DEBUG << "OnMousePressed: point " << positionEvent->GetPositionInWorld() << " (index coordinates " << m_SeedPoint << ") is inside working slice"; // 3. determine the pixel value under the last click to determine what to do bool inside(true); AccessFixedDimensionByItk_2(m_WorkingSlice, IsInsideSegmentation, 2, indexInWorkingSlice2D, &inside); m_PaintingPixelValue = inside ? 0 : 1; if (inside) { MITK_DEBUG << "Clicked inside segmentation"; // For now, we're doing nothing when the user clicks inside the segmentation. Behaviour can be implemented via // OnMousePressedInside() // When you do, be sure to remove the m_PaintingPixelValue check in OnMouseMoved() and OnMouseReleased() return; } else { MITK_DEBUG << "Clicked outside of segmentation"; OnMousePressedOutside(nullptr, interactionEvent); } } } } // Use this to implement a behaviour for when the user clicks inside a segmentation (for example remove something) // Old IpPic code is kept as comment for reference void mitk::RegionGrowingTool::OnMousePressedInside() { // mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent // ); // //const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); // checked in // OnMousePressed // // 3.1.1. Create a skeletonization of the segmentation and try to find a nice cut // // apply the skeletonization-and-cut algorithm // // generate contour to remove // // set m_ReferenceSlice = nullptr so nothing will happen during mouse move // // remember to fill the contour with 0 in mouserelease // mitkIpPicDescriptor* segmentationHistory = ipMITKSegmentationCreateGrowerHistory( workingPicSlice, // m_LastWorkingSeed, nullptr ); // free again // if (segmentationHistory) // { // tCutResult cutContour = ipMITKSegmentationGetCutPoints( workingPicSlice, segmentationHistory, // initialWorkingOffset ); // tCutResult is a ipSegmentation type // mitkIpPicFree( segmentationHistory ); // if (cutContour.cutIt) // { // int timestep = positionEvent->GetSender()->GetTimeStep(); // // 3.1.2 copy point from float* to mitk::Contour // ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New(); // contourInImageIndexCoordinates->Expand(timestep + 1); // contourInImageIndexCoordinates->SetClosed(true, timestep); // Point3D newPoint; // for (int index = 0; index < cutContour.deleteSize; ++index) // { // newPoint[0] = cutContour.deleteCurve[ 2 * index + 0 ] - 0.5;//correction is needed because the // output of the algorithm is center based // newPoint[1] = cutContour.deleteCurve[ 2 * index + 1 ] - 0.5;//and we want our contour displayed // corner based. // newPoint[2] = 0.0; // contourInImageIndexCoordinates->AddVertex( newPoint, timestep ); // } // free(cutContour.traceline); // free(cutContour.deleteCurve); // perhaps visualize this for fun? // free(cutContour.onGradient); // ContourModel::Pointer contourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( // m_WorkingSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true: sub 0.5 for // ipSegmentation correction // FeedbackContourTool::SetFeedbackContour( contourInWorldCoordinates ); // FeedbackContourTool::SetFeedbackContourVisible(true); // mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); // m_FillFeedbackContour = true; // } // else // { // m_FillFeedbackContour = false; // } // } // else // { // m_FillFeedbackContour = false; // } // m_ReferenceSlice = nullptr; // return true; } void mitk::RegionGrowingTool::OnMousePressedOutside(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent) { // Get geometry and indices mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; mitk::BaseGeometry::Pointer referenceSliceGeometry; referenceSliceGeometry = m_ReferenceSlice->GetGeometry(); itk::Index<3> indexInReferenceSlice; itk::Index<2> indexInReferenceSlice2D; referenceSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), indexInReferenceSlice); indexInReferenceSlice2D[0] = indexInReferenceSlice[0]; indexInReferenceSlice2D[1] = indexInReferenceSlice[1]; // Get seed neighborhood ScalarType averageValue(0); AccessFixedDimensionByItk_3(m_ReferenceSlice, GetNeighborhoodAverage, 2, indexInReferenceSlice2D, &averageValue, 1); m_SeedValue = averageValue; MITK_DEBUG << "Seed value is " << m_SeedValue; // Calculate initial thresholds AccessFixedDimensionByItk(m_ReferenceSlice, CalculateInitialThresholds, 2); m_Thresholds[0] = m_InitialThresholds[0]; m_Thresholds[1] = m_InitialThresholds[1]; // Perform region growing mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Extract contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); // this is not a beautiful solution, just one that works, check T22412 for details auto t = positionEvent->GetSender()->GetTimeStep(); FeedbackContourTool::SetFeedbackContour(0 != t ? ContourModelUtils::MoveZerothContourTimeStep(resultContourWorld, t) : resultContourWorld); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(m_LastEventSender->GetRenderWindow()); } } } } void mitk::RegionGrowingTool::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, // i.e. when the user clicked inside the segmentation if (m_PaintingPixelValue == 0) { return; } auto *positionEvent = dynamic_cast(interactionEvent); if (m_ReferenceSlice.IsNotNull() && positionEvent) { // Get geometry and indices mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; m_ScreenYDifference += positionEvent->GetPointerPositionOnScreen()[1] - m_LastScreenPosition[1]; m_ScreenXDifference += positionEvent->GetPointerPositionOnScreen()[0] - m_LastScreenPosition[0]; m_LastScreenPosition = positionEvent->GetPointerPositionOnScreen(); // Moving the mouse up and down adjusts the width of the threshold window, // moving it left and right shifts the threshold window m_Thresholds[0] = std::min(m_SeedValue, m_InitialThresholds[0] - (m_ScreenYDifference - m_ScreenXDifference) * m_MouseDistanceScaleFactor); m_Thresholds[1] = std::max(m_SeedValue, m_InitialThresholds[1] + (m_ScreenYDifference + m_ScreenXDifference) * m_MouseDistanceScaleFactor); // Do not exceed the pixel type extrema of the reference slice, though m_Thresholds[0] = std::max(m_ThresholdExtrema[0], m_Thresholds[0]); m_Thresholds[1] = std::min(m_ThresholdExtrema[1], m_Thresholds[1]); // Perform region growing again and show the result mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Update the contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); // this is not a beautiful solution, just one that works, check T22412 for details int timestep = positionEvent->GetSender()->GetTimeStep(); if (0 != timestep) { int size = resultContourWorld->GetNumberOfVertices(0); auto resultContourTimeWorld = mitk::ContourModel::New(); resultContourTimeWorld->Expand(timestep + 1); for (int loop = 0; loop < size; ++loop) { resultContourTimeWorld->AddVertex(resultContourWorld->GetVertexAt(loop, 0), timestep); } FeedbackContourTool::SetFeedbackContour(resultContourTimeWorld); } else { FeedbackContourTool::SetFeedbackContour(resultContourWorld); } FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(positionEvent->GetSender()->GetRenderWindow()); } } } } void mitk::RegionGrowingTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, // i.e. when the user clicked inside the segmentation if (m_PaintingPixelValue == 0) { return; } auto *positionEvent = dynamic_cast(interactionEvent); if (m_WorkingSlice.IsNotNull() && m_FillFeedbackContour && positionEvent) { // Project contour into working slice ContourModel *feedbackContour(FeedbackContourTool::GetFeedbackContour()); ContourModel::Pointer projectedContour; // this is not a beautiful solution, just one that works, check T22412 for details int timestep = positionEvent->GetSender()->GetTimeStep(); if (0 != timestep) { int size = feedbackContour->GetNumberOfVertices(timestep); auto feedbackContourTime = mitk::ContourModel::New(); feedbackContourTime->Expand(timestep + 1); for (int loop = 0; loop < size; ++loop) { feedbackContourTime->AddVertex(feedbackContour->GetVertexAt(loop, timestep), 0); } projectedContour = FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, feedbackContourTime, false, false); } else { projectedContour = FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, feedbackContour, false, false); } // If there is a projected contour, fill it if (projectedContour.IsNotNull()) { // Get working data to pass to following method so we don't overwrite locked labels in a LabelSetImage mitk::DataNode *workingNode(m_ToolManager->GetWorkingData(0)); mitk::LabelSetImage *labelImage = workingNode != nullptr ? dynamic_cast(workingNode->GetData()) : nullptr; MITK_DEBUG << "Filling Segmentation"; if (labelImage != nullptr) { // m_PaintingPixelValue only decides whether to paint or not // For LabelSetImages we want to paint with the active label value auto activeLabel = labelImage->GetActiveLabel(labelImage->GetActiveLayer())->GetValue(); mitk::ContourModelUtils::FillContourInSlice(projectedContour, 0, m_WorkingSlice, labelImage, m_PaintingPixelValue * activeLabel); } else { mitk::ContourModelUtils::FillContourInSlice(projectedContour, 0, m_WorkingSlice, m_WorkingSlice, m_PaintingPixelValue); } this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice); FeedbackContourTool::SetFeedbackContourVisible(false); } m_ScreenYDifference = 0; m_ScreenXDifference = 0; } } diff --git a/Modules/Segmentation/Interactions/mitkTool.cpp b/Modules/Segmentation/Interactions/mitkTool.cpp index 6b17128971..2c2f32e1e5 100644 --- a/Modules/Segmentation/Interactions/mitkTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTool.cpp @@ -1,330 +1,333 @@ /*============================================================================ 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 // us #include #include // itk #include mitk::Tool::Tool(const char *type, const us::Module *interactorModule) : m_EventConfig("DisplayConfigMITK.xml"), m_ToolManager(nullptr), m_PredicateImages(NodePredicateDataType::New("Image")), // for reference images m_PredicateDim3(NodePredicateDimension::New(3, 1)), m_PredicateDim4(NodePredicateDimension::New(4, 1)), m_PredicateDimension(mitk::NodePredicateOr::New(m_PredicateDim3, m_PredicateDim4)), m_PredicateImage3D(NodePredicateAnd::New(m_PredicateImages, m_PredicateDimension)), m_PredicateBinary(NodePredicateProperty::New("binary", BoolProperty::New(true))), m_PredicateNotBinary(NodePredicateNot::New(m_PredicateBinary)), m_PredicateSegmentation(NodePredicateProperty::New("segmentation", BoolProperty::New(true))), m_PredicateNotSegmentation(NodePredicateNot::New(m_PredicateSegmentation)), m_PredicateHelper(NodePredicateProperty::New("helper object", BoolProperty::New(true))), m_PredicateNotHelper(NodePredicateNot::New(m_PredicateHelper)), m_PredicateImageColorful(NodePredicateAnd::New(m_PredicateNotBinary, m_PredicateNotSegmentation)), m_PredicateImageColorfulNotHelper(NodePredicateAnd::New(m_PredicateImageColorful, m_PredicateNotHelper)), m_PredicateReference(NodePredicateAnd::New(m_PredicateImage3D, m_PredicateImageColorfulNotHelper)), m_IsSegmentationPredicate( NodePredicateAnd::New(NodePredicateOr::New(m_PredicateBinary, m_PredicateSegmentation), m_PredicateNotHelper)), m_InteractorType(type), m_DisplayInteractorConfigs(), m_InteractorModule(interactorModule) { } mitk::Tool::~Tool() { } -bool mitk::Tool::CanHandle(BaseData *) const +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; } void mitk::Tool::Activated() { // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts // with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction // will still be enabled m_DisplayInteractorConfigs.clear(); std::vector> listEventObserver = us::GetModuleContext()->GetServiceReferences(); for (auto it = listEventObserver.begin(); it != listEventObserver.end(); ++it) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig(m_EventConfig.c_str()); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayActionEventBroadcast != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayActionEventBroadcast->GetEventConfig())); // here the alternative configuration is loaded displayActionEventBroadcast->SetEventConfig(m_EventConfig.c_str()); } } } void mitk::Tool::Deactivated() { // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (auto it = m_DisplayInteractorConfigs.begin(); it != m_DisplayInteractorConfigs.end(); ++it) { if (it->first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(it->first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(it->second); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(it->first)); if (displayActionEventBroadcast != nullptr) { // here the regular configuration is loaded again displayActionEventBroadcast->SetEventConfig(it->second); } } } m_DisplayInteractorConfigs.clear(); } itk::Object::Pointer mitk::Tool::GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix) { itk::Object::Pointer object; std::string classname = this->GetNameOfClass(); std::string guiClassname = toolkitPrefix + classname + toolkitPostfix; std::list allGUIs = itk::ObjectFactoryBase::CreateAllInstance(guiClassname.c_str()); for (auto iter = allGUIs.begin(); iter != allGUIs.end(); ++iter) { if (object.IsNull()) { object = dynamic_cast(iter->GetPointer()); } else { MITK_ERROR << "There is more than one GUI for " << classname << " (several factories claim ability to produce a " << guiClassname << " ) " << std::endl; return nullptr; // people should see and fix this error } } return object; } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetReferenceDataPreference() const { return m_PredicateReference.GetPointer(); } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetWorkingDataPreference() const { return m_IsSegmentationPredicate.GetPointer(); } mitk::DataNode::Pointer mitk::Tool::CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) { // 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) { if (!image) return nullptr; // decorate the datatreenode with some properties DataNode::Pointer segmentationNode = DataNode::New(); segmentationNode->SetData(image); // name segmentationNode->SetProperty("name", StringProperty::New(organName)); // visualization properties segmentationNode->SetProperty("binary", BoolProperty::New(true)); segmentationNode->SetProperty("color", ColorProperty::New(color)); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::MULTILABEL); mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(); lutProp->SetLookupTable(lut); segmentationNode->SetProperty("LookupTable", lutProp); segmentationNode->SetProperty("texture interpolation", BoolProperty::New(false)); segmentationNode->SetProperty("layer", IntProperty::New(10)); segmentationNode->SetProperty("levelwindow", LevelWindowProperty::New(LevelWindow(0.5, 1))); segmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); segmentationNode->SetProperty("segmentation", BoolProperty::New(true)); segmentationNode->SetProperty("reslice interpolation", VtkResliceInterpolationProperty::New()); // otherwise -> segmentation appears in 2 // slices sometimes (only visual effect, not // different data) // For MITK-3M3 release, the volume of all segmentations should be shown segmentationNode->SetProperty("showVolume", BoolProperty::New(true)); return segmentationNode; } us::ModuleResource mitk::Tool::GetIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } us::ModuleResource mitk::Tool::GetCursorIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } diff --git a/Modules/Segmentation/Interactions/mitkTool.h b/Modules/Segmentation/Interactions/mitkTool.h index e9110e2a7a..16b2cf15a9 100644 --- a/Modules/Segmentation/Interactions/mitkTool.h +++ b/Modules/Segmentation/Interactions/mitkTool.h @@ -1,265 +1,270 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkTool_h_Included #define mitkTool_h_Included #include "itkObjectFactoryBase.h" #include "itkVersion.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkEventStateMachine.h" #include "mitkInteractionEventObserver.h" #include "mitkLabelSetImage.h" #include "mitkMessage.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateDimension.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateProperty.h" #include "mitkToolEvents.h" #include "mitkToolFactoryMacro.h" #include #include #include #include #include #include #include "usServiceRegistration.h" namespace us { class ModuleResource; } namespace mitk { class ToolManager; /** \brief Base class of all tools used by mitk::ToolManager. \sa ToolManager \sa SegTool2D \ingroup Interaction \ingroup ToolManagerEtAl Every tool is a mitk::StateMachine, which can follow any transition pattern that it likes. One important thing to know is, that every derived tool should always call SuperClass::Deactivated() at the end of its own implementation of Deactivated, because mitk::Tool resets the StateMachine in this method. Only if you are very sure that you covered all possible things that might happen to your own tool, you should consider not to reset the StateMachine from time to time. To learn about the MITK implementation of state machines in general, have a look at \ref InteractionPage. To derive a non-abstract tool, you inherit from mitk::Tool (or some other base class further down the inheritance tree), and in your own parameterless constructor (that is called from the itkFactorylessNewMacro that you use) you pass a StateMachine pattern name to the superclass. Names for valid patterns can be found in StateMachine.xml (which might be enhanced by you). You have to implement at least GetXPM() and GetName() to provide some identification. Each Tool knows its ToolManager, which can provide the data that the tool should work on. \warning Only to be instantiated by mitk::ToolManager (because SetToolManager has to be called). All other uses are unsupported. $Author$ */ class MITKSEGMENTATION_EXPORT Tool : public EventStateMachine, public InteractionEventObserver { public: typedef mitk::Label::PixelType DefaultSegmentationDataType; /** * \brief To let GUI process new events (e.g. qApp->processEvents() ) */ Message<> GUIProcessEventsMessage; /** * \brief To send error messages (to be shown by some GUI) */ Message1 ErrorMessage; /** * \brief To send whether the tool is busy (to be shown by some GUI) */ Message1 CurrentlyBusy; /** * \brief To send general messages (to be shown by some GUI) */ Message1 GeneralMessage; mitkClassMacro(Tool, EventStateMachine); // no New(), there should only be subclasses /** \brief Returns an icon in the XPM format. This icon has to fit into some kind of button in most applications, so make it smaller than 25x25 pixels. XPM is e.g. supported by The Gimp. But if you open any XPM file in your text editor, you will see that you could also "draw" it with an editor. */ virtual const char **GetXPM() const = 0; /** * \brief Returns the path of an icon. * * This icon is preferred to the XPM icon. */ virtual std::string GetIconPath() const { return ""; } /** * \brief Returns the path of a cursor icon. * */ virtual us::ModuleResource GetCursorIconResource() const; /** * @brief Returns the tool button icon of the tool wrapped by a usModuleResource * @return a valid ModuleResource or an invalid if this function * is not reimplemented */ virtual us::ModuleResource GetIconResource() const; /** \brief Returns the name of this tool. Make it short! This name has to fit into some kind of button in most applications, so take some time to think of a good name! */ virtual const char *GetName() const = 0; /** \brief Name of a group. You can group several tools by assigning a group name. Graphical tool selectors might use this information to group tools. (What other reason could there be?) */ virtual const char *GetGroup() const; virtual void InitializeStateMachine(); /** * \brief Interface for GUI creation. * * This is the basic interface for creation of a GUI object belonging to one tool. * * Tools that support a GUI (e.g. for display/editing of parameters) should follow some rules: * * - A Tool and its GUI are two separate classes * - There may be several instances of a GUI at the same time. * - mitk::Tool is toolkit (Qt, wxWidgets, etc.) independent, the GUI part is of course dependent * - The GUI part inherits both from itk::Object and some GUI toolkit class * - The GUI class name HAS to be constructed like "toolkitPrefix" tool->GetClassName() + "toolkitPostfix", e.g. * MyTool -> wxMyToolGUI * - For each supported toolkit there is a base class for tool GUIs, which contains some convenience methods * - Tools notify the GUI about changes using ITK events. The GUI must observe interesting events. * - The GUI base class may convert all ITK events to the GUI toolkit's favoured messaging system (Qt -> signals) * - Calling methods of a tool by its GUI is done directly. * In some cases GUIs don't want to be notified by the tool when they cause a change in a tool. * There is a macro CALL_WITHOUT_NOTICE(method()), which will temporarily disable all notifications during a * method call. */ virtual itk::Object::Pointer GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix); virtual NodePredicateBase::ConstPointer GetReferenceDataPreference() const; virtual NodePredicateBase::ConstPointer GetWorkingDataPreference() const; DataNode::Pointer CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color); DataNode::Pointer CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color); - virtual bool CanHandle(BaseData *referenceData) const; + /** Function used to check if a tool can handle the referenceData and (if specified) the working data. + @pre referenceData must be a valid pointer + @param referenceData Pointer to the data that should be checked as valid reference for the tool. + @param workingData Pointer to the data that should be checked as valid working data for this tool. + This parameter can be null if no working data is specified so far.*/ + virtual bool CanHandle(const BaseData *referenceData, const BaseData *workingData) const; protected: friend class ToolManager; virtual void SetToolManager(ToolManager *); void ConnectActionsAndFunctions() override; /** \brief Called when the tool gets activated. Derived tools should call their parents implementation at the beginning of the overriding function. */ virtual void Activated(); /** \brief Called when the tool gets deactivated. Derived tools should call their parents implementation at the end of the overriding function. */ virtual void Deactivated(); /** \brief Let subclasses change their event configuration. */ std::string m_EventConfig; Tool(); // purposely hidden Tool(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~Tool() override; void Notify(InteractionEvent *interactionEvent, bool isHandled) override; bool FilterEvents(InteractionEvent *, DataNode *) override; ToolManager *m_ToolManager; private: // for reference data NodePredicateDataType::Pointer m_PredicateImages; NodePredicateDimension::Pointer m_PredicateDim3; NodePredicateDimension::Pointer m_PredicateDim4; NodePredicateOr::Pointer m_PredicateDimension; NodePredicateAnd::Pointer m_PredicateImage3D; NodePredicateProperty::Pointer m_PredicateBinary; NodePredicateNot::Pointer m_PredicateNotBinary; NodePredicateProperty::Pointer m_PredicateSegmentation; NodePredicateNot::Pointer m_PredicateNotSegmentation; NodePredicateProperty::Pointer m_PredicateHelper; NodePredicateNot::Pointer m_PredicateNotHelper; NodePredicateAnd::Pointer m_PredicateImageColorful; NodePredicateAnd::Pointer m_PredicateImageColorfulNotHelper; NodePredicateAnd::Pointer m_PredicateReference; // for working data NodePredicateAnd::Pointer m_IsSegmentationPredicate; std::string m_InteractorType; std::map m_DisplayInteractorConfigs; const us::Module *m_InteractorModule; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkWatershedTool.cpp b/Modules/Segmentation/Interactions/mitkWatershedTool.cpp index 15fd3ff488..47e29e4da4 100644 --- a/Modules/Segmentation/Interactions/mitkWatershedTool.cpp +++ b/Modules/Segmentation/Interactions/mitkWatershedTool.cpp @@ -1,188 +1,188 @@ /*============================================================================ 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 "mitkWatershedTool.h" #include "mitkIOUtil.h" #include "mitkITKImageImport.h" #include "mitkImage.h" #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkLevelWindowManager.h" #include "mitkLookupTable.h" #include "mitkLookupTableProperty.h" #include "mitkProgressBar.h" #include "mitkRenderingManager.h" #include "mitkRenderingModeProperty.h" #include "mitkToolCommand.h" #include "mitkToolManager.h" #include #include #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, WatershedTool, "Watershed tool"); } mitk::WatershedTool::WatershedTool() : m_Threshold(0.0), m_Level(0.0) { } mitk::WatershedTool::~WatershedTool() { } void mitk::WatershedTool::Activated() { Superclass::Activated(); } void mitk::WatershedTool::Deactivated() { Superclass::Deactivated(); } us::ModuleResource mitk::WatershedTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Watershed_48x48.png"); return resource; } const char **mitk::WatershedTool::GetXPM() const { return nullptr; } const char *mitk::WatershedTool::GetName() const { return "Watershed"; } void mitk::WatershedTool::DoIt() { // get image from tool manager mitk::DataNode::Pointer referenceData = m_ToolManager->GetReferenceData(0); mitk::Image::ConstPointer input = dynamic_cast(referenceData->GetData()); if (input.IsNull()) return; const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - input = Get3DImageByTimePoint(input, timePoint); + input = GetImageByTimePoint(input, timePoint); if (nullptr == input) { MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected reference image. Time point: " << timePoint; return; } mitk::Image::Pointer output; try { // create and run itk filter pipeline AccessByItk_1(input.GetPointer(), ITKWatershed, output); mitk::LabelSetImage::Pointer labelSetOutput = mitk::LabelSetImage::New(); labelSetOutput->InitializeByLabeledImage(output); // create a new datanode for output mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); dataNode->SetData(labelSetOutput); // set name of data node std::string name = referenceData->GetName() + "_Watershed"; dataNode->SetName(name); // look, if there is already a node with this name mitk::DataStorage::SetOfObjects::ConstPointer children = m_ToolManager->GetDataStorage()->GetDerivations(referenceData); mitk::DataStorage::SetOfObjects::ConstIterator currentNode = children->Begin(); mitk::DataNode::Pointer removeNode; while (currentNode != children->End()) { if (dataNode->GetName().compare(currentNode->Value()->GetName()) == 0) { removeNode = currentNode->Value(); } currentNode++; } // remove node with same name if (removeNode.IsNotNull()) m_ToolManager->GetDataStorage()->Remove(removeNode); // add output to the data storage m_ToolManager->GetDataStorage()->Add(dataNode, referenceData); } catch (itk::ExceptionObject &e) { MITK_ERROR << "Watershed Filter Error: " << e.GetDescription(); } RenderingManager::GetInstance()->RequestUpdateAll(); } template void mitk::WatershedTool::ITKWatershed(const itk::Image *originalImage, mitk::Image::Pointer &segmentation) { typedef itk::WatershedImageFilter> WatershedFilter; typedef itk::GradientMagnitudeRecursiveGaussianImageFilter, itk::Image> MagnitudeFilter; // at first add a gradient magnitude filter typename MagnitudeFilter::Pointer magnitude = MagnitudeFilter::New(); magnitude->SetInput(originalImage); magnitude->SetSigma(1.0); // use the progress bar mitk::ToolCommand::Pointer command = mitk::ToolCommand::New(); command->AddStepsToDo(60); // then add the watershed filter to the pipeline typename WatershedFilter::Pointer watershed = WatershedFilter::New(); watershed->SetInput(magnitude->GetOutput()); watershed->SetThreshold(m_Threshold); watershed->SetLevel(m_Level); watershed->AddObserver(itk::ProgressEvent(), command); watershed->Update(); // then make sure, that the output has the desired pixel type typedef itk::CastImageFilter> CastFilter; typename CastFilter::Pointer cast = CastFilter::New(); cast->SetInput(watershed->GetOutput()); // start the whole pipeline cast->Update(); // reset the progress bar by setting progress command->SetProgress(10); // since we obtain a new image from our pipeline, we have to make sure, that our mitk::Image::Pointer // is responsible for the memory management of the output image segmentation = mitk::GrabItkImageMemory(cast->GetOutput()); } diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 676362200a..1deb993fd5 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,116 +1,117 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkOverwriteDirectedPlaneImageFilter.cpp Algorithms/mitkOverwriteSliceImageFilter.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAdaptiveRegionGrowingTool.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkAutoSegmentationTool.cpp + Interactions/mitkAutoSegmentationWithPreviewTool.cpp Interactions/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/mitkCorrectorTool2D.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFastMarchingTool.cpp Interactions/mitkFastMarchingTool3D.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkPixelManipulationTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkWatershedTool.cpp Interactions/mitkPickingTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add_48x48.png Add_Cursor_32x32.png Correction_48x48.png Correction_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/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/QmitkAdaptiveRegionGrowingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp index 5ba6c5a14b..a20b0fcc50 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp @@ -1,995 +1,995 @@ /*============================================================================ 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 "QmitkAdaptiveRegionGrowingToolGUI.h" #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageTimeSelector.h" #include "mitkNodePredicateDataType.h" #include "mitkProperties.h" #include "mitkTransferFunctionProperty.h" #include "mitkImageStatisticsHolder.h" #include "itkMaskImageFilter.h" #include "itkNumericTraits.h" #include #include #include #include #include "QmitkConfirmSegmentationDialog.h" #include "itkOrImageFilter.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkPixelTypeMultiplex.h" #include "mitkImageCast.h" MITK_TOOL_GUI_MACRO(, QmitkAdaptiveRegionGrowingToolGUI, "") QmitkAdaptiveRegionGrowingToolGUI::QmitkAdaptiveRegionGrowingToolGUI(QWidget *parent) : QmitkToolGUI(), m_DataStorage(nullptr), m_UseVolumeRendering(false), m_UpdateSuggestedThreshold(true), m_SuggestedThValue(0.0) { this->setParent(parent); m_Controls.setupUi(this); m_Controls.m_ThresholdSlider->setDecimals(1); m_Controls.m_ThresholdSlider->setSpinBoxAlignment(Qt::AlignVCenter); m_Controls.m_PreviewSlider->setEnabled(false); m_Controls.m_PreviewSlider->setSingleStep(0.5); // Not yet available // m_Controls.m_PreviewSlider->InvertedAppearance(true); //3D preview doesn't work: T24430. Postponed until reimplementation of segmentation m_Controls.m_cbVolumeRendering->setVisible(false); this->CreateConnections(); this->SetDataNodeNames("labeledRGSegmentation", "RGResult", "RGFeedbackSurface", "maskedSegmentation"); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkAdaptiveRegionGrowingToolGUI::~QmitkAdaptiveRegionGrowingToolGUI() { // Removing the observer of the PointSet node if (m_RegionGrow3DTool->GetPointSetNode().IsNotNull()) { m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetAddObserverTag); m_RegionGrow3DTool->GetPointSetNode()->GetData()->RemoveObserver(m_PointSetMoveObserverTag); } this->RemoveHelperNodes(); } void QmitkAdaptiveRegionGrowingToolGUI::OnNewToolAssociated(mitk::Tool *tool) { m_RegionGrow3DTool = dynamic_cast(tool); if (m_RegionGrow3DTool.IsNotNull()) { SetInputImageNode(this->m_RegionGrow3DTool->GetReferenceData()); this->m_DataStorage = this->m_RegionGrow3DTool->GetDataStorage(); this->EnableControls(true); // Watch for point added or modified itk::SimpleMemberCommand::Pointer pointAddedCommand = itk::SimpleMemberCommand::New(); pointAddedCommand->SetCallbackFunction(this, &QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded); m_PointSetAddObserverTag = m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); m_PointSetMoveObserverTag = m_RegionGrow3DTool->GetPointSetNode()->GetData()->AddObserver(mitk::PointSetMoveEvent(), pointAddedCommand); } else { this->EnableControls(false); } } void QmitkAdaptiveRegionGrowingToolGUI::RemoveHelperNodes() { mitk::DataNode::Pointer imageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (imageNode.IsNotNull()) { m_DataStorage->Remove(imageNode); } mitk::DataNode::Pointer maskedSegmentationNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); if (maskedSegmentationNode.IsNotNull()) { m_DataStorage->Remove(maskedSegmentationNode); } } void QmitkAdaptiveRegionGrowingToolGUI::CreateConnections() { // Connecting GUI components connect((QObject *)(m_Controls.m_pbRunSegmentation), SIGNAL(clicked()), this, SLOT(RunSegmentation())); connect(m_Controls.m_PreviewSlider, SIGNAL(valueChanged(double)), this, SLOT(ChangeLevelWindow(double))); connect((QObject *)(m_Controls.m_pbConfirmSegementation), SIGNAL(clicked()), this, SLOT(ConfirmSegmentation())); connect( m_Controls.m_ThresholdSlider, SIGNAL(maximumValueChanged(double)), this, SLOT(SetUpperThresholdValue(double))); connect( m_Controls.m_ThresholdSlider, SIGNAL(minimumValueChanged(double)), this, SLOT(SetLowerThresholdValue(double))); } void QmitkAdaptiveRegionGrowingToolGUI::SetDataNodeNames(std::string labledSegmentation, std::string binaryImage, std::string surface, std::string maskedSegmentation) { m_NAMEFORLABLEDSEGMENTATIONIMAGE = labledSegmentation; m_NAMEFORBINARYIMAGE = binaryImage; m_NAMEFORSURFACE = surface; m_NAMEFORMASKEDSEGMENTATION = maskedSegmentation; } void QmitkAdaptiveRegionGrowingToolGUI::SetDataStorage(mitk::DataStorage *dataStorage) { m_DataStorage = dataStorage; } void QmitkAdaptiveRegionGrowingToolGUI::SetInputImageNode(mitk::DataNode *node) { m_InputImageNode = node; mitk::Image *inputImage = dynamic_cast(m_InputImageNode->GetData()); if (inputImage) { mitk::ScalarType max = inputImage->GetStatistics()->GetScalarValueMax(); mitk::ScalarType min = inputImage->GetStatistics()->GetScalarValueMin(); m_Controls.m_ThresholdSlider->setMaximum(max); m_Controls.m_ThresholdSlider->setMinimum(min); // Just for initialization m_Controls.m_ThresholdSlider->setMaximumValue(max); m_Controls.m_ThresholdSlider->setMinimumValue(min); } } template static void AccessPixel(mitk::PixelType /*ptype*/, mitk::Image* im, mitk::Point3D p, int& val) { mitk::ImagePixelReadAccessor access(im); val = access.GetPixelByWorldCoordinates(p); } /**Overloaded const verison*/ template static void AccessPixel(mitk::PixelType /*ptype*/, const mitk::Image* im, mitk::Point3D p, int& val) { mitk::ImagePixelReadAccessor access(im); val = access.GetPixelByWorldCoordinates(p); } void QmitkAdaptiveRegionGrowingToolGUI::OnPointAdded() { if (m_RegionGrow3DTool.IsNull()) return; mitk::DataNode *node = m_RegionGrow3DTool->GetPointSetNode(); if (node != nullptr) { mitk::PointSet::Pointer pointSet = dynamic_cast(node->GetData()); if (pointSet.IsNull()) { QMessageBox::critical(nullptr, "QmitkAdaptiveRegionGrowingToolGUI", "PointSetNode does not contain a pointset"); return; } m_Controls.m_lblSetSeedpoint->setText(""); const mitk::Image *image = dynamic_cast(m_InputImageNode->GetData()); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); - auto image3D = Get3DImageByTimePoint(image, timePoint); + auto image3D = GetImageByTimePoint(image, timePoint); if (nullptr == image3D) { MITK_WARN << "Cannot run segementation. Currently selected timepoint is not in the time bounds of the selected " "reference image. Time point: " << timePoint; return; } if (!pointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) return; mitk::Point3D seedPoint = pointSet ->GetPointSet(static_cast(pointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint))) ->GetPoints() ->ElementAt(0); if (image3D->GetGeometry()->IsInside(seedPoint)) mitkPixelTypeMultiplex3( AccessPixel, image3D->GetChannelDescriptor().GetPixelType(), image3D, seedPoint, m_SeedpointValue) else return; /* In this case the seedpoint is placed e.g. in the lung or bronchialtree * The lowerFactor sets the windowsize depending on the regiongrowing direction */ m_CurrentRGDirectionIsUpwards = true; if (m_SeedpointValue < -500) { m_CurrentRGDirectionIsUpwards = false; } // Initializing the region by the area around the seedpoint m_SeedPointValueMean = 0; itk::Index<3> currentIndex, runningIndex; mitk::ScalarType pixelValues[125]; unsigned int pos(0); image3D->GetGeometry(0)->WorldToIndex(seedPoint, currentIndex); runningIndex = currentIndex; for (int i = runningIndex[0] - 2; i <= runningIndex[0] + 2; i++) { for (int j = runningIndex[1] - 2; j <= runningIndex[1] + 2; j++) { for (int k = runningIndex[2] - 2; k <= runningIndex[2] + 2; k++) { currentIndex[0] = i; currentIndex[1] = j; currentIndex[2] = k; if (image3D->GetGeometry()->IsIndexInside(currentIndex)) { int component = 0; m_InputImageNode->GetIntProperty("Image.Displayed Component", component); mitkPixelTypeMultiplex4(mitk::FastSinglePixelAccess, image3D->GetChannelDescriptor().GetPixelType(), image3D, nullptr, currentIndex, pixelValues[pos]); pos++; } else { pixelValues[pos] = std::numeric_limits::min(); pos++; } } } } // Now calculation mean of the pixelValues // Now calculation mean of the pixelValues unsigned int numberOfValues(0); for (auto &pixelValue : pixelValues) { if (pixelValue > std::numeric_limits::min()) { m_SeedPointValueMean += pixelValue; numberOfValues++; } } m_SeedPointValueMean = m_SeedPointValueMean / numberOfValues; mitk::ScalarType var = 0; if (numberOfValues > 1) { for (auto &pixelValue : pixelValues) { if (pixelValue > std::numeric_limits::min()) { var += (pixelValue - m_SeedPointValueMean) * (pixelValue - m_SeedPointValueMean); } } var /= numberOfValues - 1; } mitk::ScalarType stdDev = sqrt(var); /* * Here the upper- and lower threshold is calculated: * The windowSize is 20% of the maximum range of the intensity values existing in the current image * If the RG direction is upwards the lower TH is meanSeedValue-0.15*windowSize and upper TH is * meanSeedValue+0.85*windowsSize * if the RG direction is downwards the lower TH is meanSeedValue-0.85*windowSize and upper TH is * meanSeedValue+0.15*windowsSize */ const auto timeStepOfImage = image->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::ScalarType min = image->GetStatistics()->GetScalarValueMin(timeStepOfImage); mitk::ScalarType max = image->GetStatistics()->GetScalarValueMax(timeStepOfImage); mitk::ScalarType windowSize = max - min; windowSize = 0.15 * windowSize; if (m_CurrentRGDirectionIsUpwards) { m_LOWERTHRESHOLD = m_SeedPointValueMean - stdDev; m_UPPERTHRESHOLD = m_SeedpointValue + windowSize; if (m_UPPERTHRESHOLD > max) m_UPPERTHRESHOLD = max; m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); } else { m_UPPERTHRESHOLD = m_SeedPointValueMean; if (m_SeedpointValue > m_SeedPointValueMean) m_UPPERTHRESHOLD = m_SeedpointValue; m_LOWERTHRESHOLD = m_SeedpointValue - windowSize; if (m_LOWERTHRESHOLD < min) m_LOWERTHRESHOLD = min; m_Controls.m_ThresholdSlider->setMinimumValue(m_LOWERTHRESHOLD); m_Controls.m_ThresholdSlider->setMaximumValue(m_UPPERTHRESHOLD); } } } -mitk::Image::ConstPointer QmitkAdaptiveRegionGrowingToolGUI::Get3DImageByTimePoint(const mitk::Image *image, +mitk::Image::ConstPointer QmitkAdaptiveRegionGrowingToolGUI::GetImageByTimePoint(const mitk::Image *image, mitk::TimePointType timePoint) const { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; if (image->GetDimension() != 4) return image; auto imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(image->GetTimeGeometry()->TimePointToTimeStep(timePoint))); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } void QmitkAdaptiveRegionGrowingToolGUI::RunSegmentation() { if (m_InputImageNode.IsNull()) { QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please specify the image in Datamanager!"); return; } mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); if (node.IsNull()) { QMessageBox::information(nullptr, "Adaptive Region Growing functionality", "Please insert a seed point inside the " "image.\n\nFirst press the \"Define Seed " "Point\" button,\nthen click left mouse " "button inside the image."); return; } // safety if no pointSet or pointSet empty mitk::PointSet::Pointer seedPointSet = dynamic_cast(node->GetData()); if (seedPointSet.IsNull()) { m_Controls.m_pbRunSegmentation->setEnabled(true); QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); return; } mitk::Image::Pointer orgImage = dynamic_cast(m_InputImageNode->GetData()); if (orgImage.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (!seedPointSet->GetTimeGeometry()->IsValidTimePoint(timePoint)) mitkThrow() << "Point set is not defined for specified time point. Time point: " << timePoint; int timeStep = static_cast(seedPointSet->GetTimeGeometry()->TimePointToTimeStep(timePoint)); if (!(seedPointSet->GetSize(timeStep))) { m_Controls.m_pbRunSegmentation->setEnabled(true); QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "The seed point is empty! Please choose a new seed point."); return; } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); mitk::PointSet::PointType seedPoint = seedPointSet->GetPointSet(timeStep)->GetPoints()->Begin().Value(); - auto image3D = Get3DImageByTimePoint(orgImage, timePoint); + auto image3D = GetImageByTimePoint(orgImage, timePoint); if (image3D.IsNotNull()) { // QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); //set the cursor to waiting AccessByItk_2(image3D, StartRegionGrowing, image3D->GetGeometry(), seedPoint); // QApplication::restoreOverrideCursor();//reset cursor } else { QApplication::restoreOverrideCursor(); // reset cursor QMessageBox::information( nullptr, "Adaptive Region Growing functionality", "Only images of dimension 3 or 4 can be processed!"); return; } } EnableControls(true); // Segmentation ran successfully, so enable all controls. node->SetVisibility(true); QApplication::restoreOverrideCursor(); // reset cursor } template void QmitkAdaptiveRegionGrowingToolGUI::StartRegionGrowing(const itk::Image *itkImage, const mitk::BaseGeometry *imageGeometry, const mitk::PointSet::PointType seedPoint) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedAdaptiveThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typedef itk::MaskImageFilter MaskImageFilterType; if (!imageGeometry->IsInside(seedPoint)) { QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor QMessageBox::information(nullptr, "Segmentation functionality", "The seed point is outside of the image! Please choose a position inside the image!"); return; } IndexType seedIndex; imageGeometry->WorldToIndex(seedPoint, seedIndex); // convert world coordinates to image indices if (m_SeedpointValue > m_UPPERTHRESHOLD || m_SeedpointValue < m_LOWERTHRESHOLD) { QApplication::restoreOverrideCursor(); // reset cursor to be able to click ok with the regular mouse cursor QMessageBox::information( nullptr, "Segmentation functionality", "The seed point is outside the defined thresholds! Please set a new seed point or adjust the thresholds."); MITK_INFO << "Mean: " << m_SeedPointValueMean; return; } // Setting the direction of the regiongrowing. For dark structures e.g. the lung the regiongrowing // is performed starting at the upper value going to the lower one regionGrower->SetGrowingDirectionIsUpwards(m_CurrentRGDirectionIsUpwards); regionGrower->SetInput(itkImage); regionGrower->AddSeed(seedIndex); // In some cases we have to subtract 1 for the lower threshold and add 1 to the upper. // Otherwise no region growing is done. Maybe a bug in the ConnectiveAdaptiveThresholdFilter regionGrower->SetLower(m_LOWERTHRESHOLD - 1); regionGrower->SetUpper(m_UPPERTHRESHOLD + 1); try { regionGrower->Update(); } catch (itk::ExceptionObject &exc) { QMessageBox errorInfo; errorInfo.setWindowTitle("Adaptive RG Segmentation Functionality"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during region growing!"); errorInfo.setDetailedText(exc.what()); errorInfo.exec(); return; // can't work } catch (...) { QMessageBox::critical(nullptr, "Adaptive RG Segmentation Functionality", "An error occurred during region growing!"); return; } mitk::Image::Pointer resultImage = mitk::ImportItkImage(regionGrower->GetOutput())->Clone(); // initialize slider m_Controls.m_PreviewSlider->setMinimum(m_LOWERTHRESHOLD); mitk::ScalarType max = m_SeedpointValue + resultImage->GetStatistics()->GetScalarValueMax(); if (max < m_UPPERTHRESHOLD) m_Controls.m_PreviewSlider->setMaximum(max); else m_Controls.m_PreviewSlider->setMaximum(m_UPPERTHRESHOLD); this->m_DetectedLeakagePoint = regionGrower->GetLeakagePoint(); if (m_CurrentRGDirectionIsUpwards) { m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean - 1); } else { m_Controls.m_PreviewSlider->setValue(m_SeedPointValueMean + 1); } this->m_SliderInitialized = true; // create new node and then delete the old one if there is one mitk::DataNode::Pointer newNode = mitk::DataNode::New(); newNode->SetData(resultImage); // set some properties newNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORLABLEDSEGMENTATIONIMAGE)); newNode->SetProperty("helper object", mitk::BoolProperty::New(true)); newNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); newNode->SetProperty("layer", mitk::IntProperty::New(1)); newNode->SetProperty("opacity", mitk::FloatProperty::New(0.7)); // delete the old image, if there was one: mitk::DataNode::Pointer binaryNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); m_DataStorage->Remove(binaryNode); // now add result to data tree m_DataStorage->Add(newNode, m_InputImageNode); typename InputImageType::Pointer inputImageItk; mitk::CastToItkImage(resultImage, inputImageItk); // volume rendering preview masking typename ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); thresholdFilter->SetInput(inputImageItk); thresholdFilter->SetInsideValue(1); thresholdFilter->SetOutsideValue(0); double sliderVal = this->m_Controls.m_PreviewSlider->value(); if (m_CurrentRGDirectionIsUpwards) { thresholdFilter->SetLowerThreshold(sliderVal); thresholdFilter->SetUpperThreshold(itk::NumericTraits::max()); } else { thresholdFilter->SetLowerThreshold(itk::NumericTraits::min()); thresholdFilter->SetUpperThreshold(sliderVal); } thresholdFilter->SetInPlace(false); typename MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); maskFilter->SetInput(inputImageItk); maskFilter->SetInPlace(false); maskFilter->SetMaskImage(thresholdFilter->GetOutput()); maskFilter->SetOutsideValue(0); maskFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkMask; mitk::CastToMitkImage(maskFilter->GetOutput(), mitkMask); mitk::DataNode::Pointer maskedNode = mitk::DataNode::New(); maskedNode->SetData(mitkMask); // set some properties maskedNode->SetProperty("name", mitk::StringProperty::New(m_NAMEFORMASKEDSEGMENTATION)); maskedNode->SetProperty("helper object", mitk::BoolProperty::New(true)); maskedNode->SetProperty("color", mitk::ColorProperty::New(0.0, 1.0, 0.0)); maskedNode->SetProperty("layer", mitk::IntProperty::New(1)); maskedNode->SetProperty("opacity", mitk::FloatProperty::New(0.0)); // delete the old image, if there was one: mitk::DataNode::Pointer deprecatedMask = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); m_DataStorage->Remove(deprecatedMask); // now add result to data tree m_DataStorage->Add(maskedNode, m_InputImageNode); this->InitializeLevelWindow(); if (m_UseVolumeRendering) this->EnableVolumeRendering(true); m_UpdateSuggestedThreshold = true; // reset first stored threshold value // Setting progress to finished mitk::ProgressBar::GetInstance()->Progress(357); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkAdaptiveRegionGrowingToolGUI::InitializeLevelWindow() { // get the preview from the datatree mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); mitk::LevelWindow tempLevelWindow; newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); mitk::ScalarType *level = new mitk::ScalarType(0.0); mitk::ScalarType *window = new mitk::ScalarType(1.0); int upper; if (m_CurrentRGDirectionIsUpwards) { upper = m_UPPERTHRESHOLD - m_SeedpointValue; } else { upper = m_SeedpointValue - m_LOWERTHRESHOLD; } tempLevelWindow.SetRangeMinMax(mitk::ScalarType(0), mitk::ScalarType(upper)); // get the suggested threshold from the detected leakage-point and adjust the slider if (m_CurrentRGDirectionIsUpwards) { this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); *level = m_UPPERTHRESHOLD - (m_SeedpointValue) + 0.5; } else { this->m_Controls.m_PreviewSlider->setValue(m_SeedpointValue); *level = (m_SeedpointValue)-m_LOWERTHRESHOLD + 0.5; } tempLevelWindow.SetLevelWindow(*level, *window); newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); // update the widgets mitk::RenderingManager::GetInstance()->RequestUpdateAll(); m_SliderInitialized = true; // inquiry need to fix bug#1828 static int lastSliderPosition = 0; if ((this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1) == lastSliderPosition) { this->ChangeLevelWindow(lastSliderPosition); } lastSliderPosition = this->m_SeedpointValue + this->m_DetectedLeakagePoint - 1; if (m_UseVolumeRendering) this->UpdateVolumeRenderingThreshold((int)(*level + 0.5)); // lower threshold for labeled image } void QmitkAdaptiveRegionGrowingToolGUI::ChangeLevelWindow(double newValue) { if (m_SliderInitialized) { // do nothing, if no preview exists mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (newNode.IsNull()) return; mitk::LevelWindow tempLevelWindow; newNode->GetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); // get the levelWindow associated with the preview mitk::ScalarType level; // = this->m_UPPERTHRESHOLD - newValue + 0.5; mitk::ScalarType *window = new mitk::ScalarType(1); // adjust the levelwindow according to the position of the slider (newvalue) if (m_CurrentRGDirectionIsUpwards) { level = m_UPPERTHRESHOLD - newValue + 0.5; tempLevelWindow.SetLevelWindow(level, *window); } else { level = newValue - m_LOWERTHRESHOLD + 0.5; tempLevelWindow.SetLevelWindow(level, *window); } newNode->SetLevelWindow(tempLevelWindow, nullptr, "levelwindow"); if (m_UseVolumeRendering) this->UpdateVolumeRenderingThreshold((int)(level - 0.5)); // lower threshold for labeled image newNode->SetVisibility(true); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkAdaptiveRegionGrowingToolGUI::DecreaseSlider() { // moves the slider one step to the left, when the "-"-button is pressed if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->minimum()) { int newValue = this->m_Controls.m_PreviewSlider->value() - 1; this->ChangeLevelWindow(newValue); this->m_Controls.m_PreviewSlider->setValue(newValue); } } void QmitkAdaptiveRegionGrowingToolGUI::IncreaseSlider() { // moves the slider one step to the right, when the "+"-button is pressed if (this->m_Controls.m_PreviewSlider->value() != this->m_Controls.m_PreviewSlider->maximum()) { int newValue = this->m_Controls.m_PreviewSlider->value() + 1; this->ChangeLevelWindow(newValue); this->m_Controls.m_PreviewSlider->setValue(newValue); } } void QmitkAdaptiveRegionGrowingToolGUI::ConfirmSegmentation() { // get image node if (m_InputImageNode.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "Please specify the image in Datamanager!"); return; } // get image data mitk::Image::Pointer orgImage = dynamic_cast(m_InputImageNode->GetData()); if (orgImage.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Image found!"); return; } // get labeled segmentation mitk::Image::Pointer labeledSeg = (mitk::Image *)m_DataStorage->GetNamedObject(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (labeledSeg.IsNull()) { QMessageBox::critical(nullptr, "Adaptive region growing functionality", "No Segmentation Preview found!"); return; } mitk::DataNode::Pointer newNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (newNode.IsNull()) return; QmitkConfirmSegmentationDialog dialog; QString segName = QString::fromStdString(m_RegionGrow3DTool->GetCurrentSegmentationName()); dialog.SetSegmentationName(segName); int result = dialog.exec(); switch (result) { case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: m_RegionGrow3DTool->SetOverwriteExistingSegmentation(false); break; case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: m_RegionGrow3DTool->SetOverwriteExistingSegmentation(true); break; case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: return; } mitk::Image::Pointer img = dynamic_cast(newNode->GetData()); AccessByItk(img, ITKThresholding); // disable volume rendering preview after the segmentation node was created this->EnableVolumeRendering(false); newNode->SetVisibility(false); m_Controls.m_cbVolumeRendering->setChecked(false); // TODO disable slider etc... if (m_RegionGrow3DTool.IsNotNull()) { m_RegionGrow3DTool->ConfirmSegmentation(); } } template void QmitkAdaptiveRegionGrowingToolGUI::ITKThresholding(itk::Image *itkImage) { mitk::Image::Pointer originalSegmentation = dynamic_cast(this->m_RegionGrow3DTool->GetTargetSegmentationNode()->GetData()); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (!originalSegmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) mitkThrow() << "Segmentation is not defined for specified time point. Time point: " << timePoint; int timeStep = static_cast(originalSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint)); if (originalSegmentation) { typedef itk::Image InputImageType; typedef itk::Image SegmentationType; // select single 3D volume if we have more than one time step typename SegmentationType::Pointer originalSegmentationInITK = SegmentationType::New(); if (originalSegmentation->GetTimeGeometry()->CountTimeSteps() > 1) { mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(originalSegmentation); timeSelector->SetTimeNr(timeStep); timeSelector->UpdateLargestPossibleRegion(); CastToItkImage(timeSelector->GetOutput(), originalSegmentationInITK); } else // use original { CastToItkImage(originalSegmentation, originalSegmentationInITK); } // Fill current preiview image in segmentation image originalSegmentationInITK->FillBuffer(0); itk::ImageRegionIterator itOutput(originalSegmentationInITK, originalSegmentationInITK->GetLargestPossibleRegion()); itk::ImageRegionIterator itInput(itkImage, itkImage->GetLargestPossibleRegion()); itOutput.GoToBegin(); itInput.GoToBegin(); // calculate threhold from slider value int currentTreshold = 0; if (m_CurrentRGDirectionIsUpwards) { currentTreshold = m_UPPERTHRESHOLD - m_Controls.m_PreviewSlider->value() + 1; } else { currentTreshold = m_Controls.m_PreviewSlider->value() - m_LOWERTHRESHOLD; } // iterate over image and set pixel in segmentation according to thresholded labeled image while (!itOutput.IsAtEnd() && !itInput.IsAtEnd()) { // Use threshold slider to determine if pixel is set to 1 if (itInput.Value() != 0 && itInput.Value() >= static_cast::PixelType>(currentTreshold)) { itOutput.Set(1); } ++itOutput; ++itInput; } // combine current working segmentation image with our region growing result originalSegmentation->SetVolume((void *)(originalSegmentationInITK->GetPixelContainer()->GetBufferPointer()), timeStep); originalSegmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkAdaptiveRegionGrowingToolGUI::EnableControls(bool enable) { if (m_RegionGrow3DTool.IsNull()) return; // Check if seed point is already set, if not leave RunSegmentation disabled // if even m_DataStorage is nullptr leave node nullptr mitk::DataNode::Pointer node = m_RegionGrow3DTool->GetPointSetNode(); if (node.IsNull()) { this->m_Controls.m_pbRunSegmentation->setEnabled(false); } else { this->m_Controls.m_pbRunSegmentation->setEnabled(enable); } // Check if a segmentation exists, if not leave segmentation dependent disabled. // if even m_DataStorage is nullptr leave node nullptr node = m_DataStorage ? m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE) : nullptr; if (node.IsNull()) { this->m_Controls.m_PreviewSlider->setEnabled(false); this->m_Controls.m_pbConfirmSegementation->setEnabled(false); } else { this->m_Controls.m_PreviewSlider->setEnabled(enable); this->m_Controls.m_pbConfirmSegementation->setEnabled(enable); } this->m_Controls.m_cbVolumeRendering->setEnabled(enable); } void QmitkAdaptiveRegionGrowingToolGUI::EnableVolumeRendering(bool enable) { mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); if (node.IsNull()) return; if (enable) { node->SetBoolProperty("volumerendering", enable); node->SetBoolProperty("volumerendering.uselod", true); } else { node->SetBoolProperty("volumerendering", enable); } double val = this->m_Controls.m_PreviewSlider->value(); this->ChangeLevelWindow(val); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkAdaptiveRegionGrowingToolGUI::UpdateVolumeRenderingThreshold(int) { typedef short PixelType; typedef itk::Image InputImageType; typedef itk::BinaryThresholdImageFilter ThresholdFilterType; typedef itk::MaskImageFilter MaskImageFilterType; mitk::DataNode::Pointer grownImageNode = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); mitk::Image::Pointer grownImage = dynamic_cast(grownImageNode->GetData()); if (!grownImage) { MITK_ERROR << "Missing data node for labeled segmentation image."; return; } InputImageType::Pointer itkGrownImage; mitk::CastToItkImage(grownImage, itkGrownImage); ThresholdFilterType::Pointer thresholdFilter = ThresholdFilterType::New(); thresholdFilter->SetInput(itkGrownImage); thresholdFilter->SetInPlace(false); double sliderVal = this->m_Controls.m_PreviewSlider->value(); PixelType threshold = itk::NumericTraits::min(); if (m_CurrentRGDirectionIsUpwards) { threshold = static_cast(m_UPPERTHRESHOLD - sliderVal + 0.5); thresholdFilter->SetLowerThreshold(threshold); thresholdFilter->SetUpperThreshold(itk::NumericTraits::max()); } else { threshold = sliderVal - m_LOWERTHRESHOLD + 0.5; thresholdFilter->SetLowerThreshold(itk::NumericTraits::min()); thresholdFilter->SetUpperThreshold(threshold); } thresholdFilter->UpdateLargestPossibleRegion(); MaskImageFilterType::Pointer maskFilter = MaskImageFilterType::New(); maskFilter->SetInput(itkGrownImage); maskFilter->SetInPlace(false); maskFilter->SetMaskImage(thresholdFilter->GetOutput()); maskFilter->SetOutsideValue(0); maskFilter->UpdateLargestPossibleRegion(); mitk::Image::Pointer mitkMaskedImage; mitk::CastToMitkImage(maskFilter->GetOutput(), mitkMaskedImage); mitk::DataNode::Pointer maskNode = m_DataStorage->GetNamedNode(m_NAMEFORMASKEDSEGMENTATION); maskNode->SetData(mitkMaskedImage); } void QmitkAdaptiveRegionGrowingToolGUI::UseVolumeRendering(bool on) { m_UseVolumeRendering = on; this->EnableVolumeRendering(on); } void QmitkAdaptiveRegionGrowingToolGUI::SetLowerThresholdValue(double lowerThreshold) { m_LOWERTHRESHOLD = lowerThreshold; } void QmitkAdaptiveRegionGrowingToolGUI::SetUpperThresholdValue(double upperThreshold) { m_UPPERTHRESHOLD = upperThreshold; } void QmitkAdaptiveRegionGrowingToolGUI::Deactivated() { // make the segmentation preview node invisible mitk::DataNode::Pointer node = m_DataStorage->GetNamedNode(m_NAMEFORLABLEDSEGMENTATIONIMAGE); if (node.IsNotNull()) { node->SetVisibility(false); } // disable volume rendering preview after the segmentation node was created this->EnableVolumeRendering(false); m_Controls.m_cbVolumeRendering->setChecked(false); } void QmitkAdaptiveRegionGrowingToolGUI::Activated() { } diff --git a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h index 12d71f09b4..c0d645772d 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h @@ -1,228 +1,228 @@ /*============================================================================ 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_QmitkAdaptiveRegionGrowingToolGUI_H #define QMITK_QmitkAdaptiveRegionGrowingToolGUI_H #include "itkImage.h" #include "mitkDataStorage.h" #include "mitkGeometry3D.h" #include "mitkPointSet.h" #include "qwidget.h" #include "ui_QmitkAdaptiveRegionGrowingToolGUIControls.h" #include #include "QmitkToolGUI.h" #include "mitkAdaptiveRegionGrowingTool.h" class DataNode; class QmitkAdaptiveRegionGrowingToolGUIControls; /*! * * \brief QmitkAdaptiveRegionGrowingToolGUI * * Adaptive Region Growing View class of the segmentation. * */ class MITKSEGMENTATIONUI_EXPORT QmitkAdaptiveRegionGrowingToolGUI : public QmitkToolGUI { Q_OBJECT public: /** * @brief mitkClassMacro */ mitkClassMacro(QmitkAdaptiveRegionGrowingToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); QmitkAdaptiveRegionGrowingToolGUI(QWidget *parent = nullptr); /** \brief Method to create the connections for the component. This Method is obligatory even if no connections is * needed*/ virtual void CreateConnections(); ///** \brief Method to set the default data storage.*/ virtual void SetDataStorage(mitk::DataStorage *dataStorage); /** * @brief Method to set the name of a data node. * @param labledSegmentation Name of the labeled segmentation * @param binaryImage Name of the binary image * @param surface Name of the surface */ void SetDataNodeNames(std::string labledSegmentation, std::string binaryImage, /*std::string vesselTree,*/ std::string surface, std::string maskedSegmentation); /** * @brief Method to enable/disable controls for region growing * * This method checks if a seed point is set and a segmentation exists. * @param enable/disable controls */ void EnableControls(bool enable); /** * @brief Method to set the input image node * @param data node */ void SetInputImageNode(mitk::DataNode *node); void Deactivated(); void Activated(); /** * @brief The created GUI from the .ui-File. This Attribute is obligatory */ Ui::QmitkAdaptiveRegionGrowingToolGUIControls m_Controls; protected slots: /** * @brief Method to start the segmentation * * This method is called, when the "Start Segmentation" button is clicked. */ void RunSegmentation(); /** * @brief Method to change the level window * * This method is called, when the level window slider is changed via the slider in the control widget * @param new value */ void ChangeLevelWindow(double newValue); /** * @brief Method to increase the preview slider * * This method is called, when the + button is clicked and increases the value by 1 */ void IncreaseSlider(); /** * @brief Method to decrease the preview slider * * This method is called, when the - button is clicked and decreases the value by 1 */ void DecreaseSlider(); /** * @brief Method to confirm the preview segmentation * * This method is called, when the "Confirm Segmentation" button is clicked. */ void ConfirmSegmentation(); /** * @brief Method to switch the volume rendering on/off * @param on/off */ void UseVolumeRendering(bool on); /** * @brief Method to set the lower threshold * * This method is called, when the minimum threshold slider has changed * @param lower threshold */ void SetLowerThresholdValue(double lowerThreshold); /** * @brief Method to set upper threshold * * This Method is called, when the maximum threshold slider has changed * @param upper threshold */ void SetUpperThresholdValue(double upperThreshold); /** * @brief Method to determine which tool to activate * * This method listens to the tool manager and activates this tool if requested otherwise disables this view */ void OnNewToolAssociated(mitk::Tool *); protected: mitk::AdaptiveRegionGrowingTool::Pointer m_RegionGrow3DTool; /** \brief Destructor. */ ~QmitkAdaptiveRegionGrowingToolGUI() override; mitk::DataStorage *m_DataStorage; mitk::DataNode::Pointer m_InputImageNode; /** * @brief Method to calculate parameter settings, when a seed point is set */ void OnPointAdded(); /** * @brief Method to extract a 3D image based on a given time point that can be taken from the SliceNavigationController * * This ensures that the seed point is taken from the current selected 3D image */ - mitk::Image::ConstPointer Get3DImageByTimePoint(const mitk::Image *image, + mitk::Image::ConstPointer GetImageByTimePoint(const mitk::Image *image, mitk::TimePointType timePoint) const; private: std::string m_NAMEFORORGIMAGE; std::string m_NAMEFORLABLEDSEGMENTATIONIMAGE; std::string m_NAMEFORBINARYIMAGE; std::string m_NAMEFORSURFACE; std::string m_NAMEFORMASKEDSEGMENTATION; mitk::ScalarType m_LOWERTHRESHOLD; // Hounsfield value mitk::ScalarType m_UPPERTHRESHOLD; // Hounsfield value mitk::ScalarType m_SeedPointValueMean; void RemoveHelperNodes(); int m_DetectedLeakagePoint; bool m_CurrentRGDirectionIsUpwards; // defines fixed threshold (true = LOWERTHRESHOLD fixed, false = UPPERTHRESHOLD // fixed) int m_SeedpointValue; bool m_SliderInitialized; bool m_UseVolumeRendering; bool m_UpdateSuggestedThreshold; float m_SuggestedThValue; long m_PointSetAddObserverTag; long m_PointSetMoveObserverTag; template void StartRegionGrowing(const itk::Image *itkImage, const mitk::BaseGeometry *imageGeometry, const mitk::PointSet::PointType seedPoint); template void ITKThresholding(itk::Image *inputImage); void InitializeLevelWindow(); void EnableVolumeRendering(bool enable); void UpdateVolumeRenderingThreshold(int thValue); }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp index d26c729fa2..7c720d4a58 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp @@ -1,144 +1,168 @@ /*============================================================================ 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 "QmitkBinaryThresholdToolGUI.h" #include #include #include #include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkBinaryThresholdToolGUI, "") QmitkBinaryThresholdToolGUI::QmitkBinaryThresholdToolGUI() : QmitkToolGUI() { // create the visible widgets QBoxLayout *mainLayout = new QVBoxLayout(this); QLabel *label = new QLabel("Threshold :", this); QFont f = label->font(); f.setBold(false); label->setFont(f); mainLayout->addWidget(label); QBoxLayout *layout = new QHBoxLayout(); m_ThresholdSlider = new ctkSliderWidget(); connect( m_ThresholdSlider, SIGNAL(valueChanged(double)), this, SLOT(OnSliderValueChanged(double))); layout->addWidget(m_ThresholdSlider); mainLayout->addLayout(layout); m_ThresholdSlider->setSingleStep(0.01); QPushButton *okButton = new QPushButton("Confirm Segmentation", this); connect(okButton, SIGNAL(clicked()), this, SLOT(OnAcceptThresholdPreview())); okButton->setFont(f); mainLayout->addWidget(okButton); m_CheckProcessAll = new QCheckBox("Process all time steps", this); m_CheckProcessAll->setChecked(false); + m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); + mainLayout->addWidget(m_CheckProcessAll); m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); m_CheckCreateNew->setChecked(false); + m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); mainLayout->addWidget(m_CheckCreateNew); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkBinaryThresholdToolGUI::~QmitkBinaryThresholdToolGUI() { if (m_BinaryThresholdTool.IsNotNull()) { + m_BinaryThresholdTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); m_BinaryThresholdTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); } } void QmitkBinaryThresholdToolGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_BinaryThresholdTool.IsNotNull()) { + m_BinaryThresholdTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); m_BinaryThresholdTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); } m_BinaryThresholdTool = dynamic_cast(tool); if (m_BinaryThresholdTool.IsNotNull()) { + m_BinaryThresholdTool->CurrentlyBusy += + mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); m_BinaryThresholdTool->IntervalBordersChanged += mitk::MessageDelegate3( this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdTool->ThresholdingValuesChanged += mitk::MessageDelegate2( this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); m_BinaryThresholdTool->SetOverwriteExistingSegmentation(true); m_CheckProcessAll->setVisible(m_BinaryThresholdTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps()>1); } } void QmitkBinaryThresholdToolGUI::OnAcceptThresholdPreview() { if (m_BinaryThresholdTool.IsNotNull()) { if (m_CheckCreateNew->isChecked()) { m_BinaryThresholdTool->SetOverwriteExistingSegmentation(false); } else { m_BinaryThresholdTool->SetOverwriteExistingSegmentation(true); } m_BinaryThresholdTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); this->thresholdAccepted(); - m_BinaryThresholdTool->AcceptCurrentThresholdValue(); + m_BinaryThresholdTool->ConfirmSegmentation(); } } void QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) { m_InternalUpdate = true; if (!isFloat) { m_ThresholdSlider->setRange(int(lower), int(upper)); m_ThresholdSlider->setSingleStep(1); m_ThresholdSlider->setDecimals(0); } else { m_ThresholdSlider->setRange(lower, upper); } m_InternalUpdate = false; } void QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType /*upper*/) { m_ThresholdSlider->setValue(lower); } void QmitkBinaryThresholdToolGUI::OnSliderValueChanged(double value) { if (m_BinaryThresholdTool.IsNotNull() && !m_InternalUpdate) { m_BinaryThresholdTool->SetThresholdValue(value); } } + +void QmitkBinaryThresholdToolGUI::BusyStateChanged(bool value) +{ + if (value) + { + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + } + else + { + QApplication::restoreOverrideCursor(); + } + + m_ThresholdSlider->setEnabled(!value); +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h index 3d1e396cd2..5696c1ff98 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h @@ -1,78 +1,80 @@ /*============================================================================ 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 QmitkBinaryThresholdToolGUI_h_Included #define QmitkBinaryThresholdToolGUI_h_Included #include "QmitkToolGUI.h" #include "mitkBinaryThresholdTool.h" #include #include "ctkSliderWidget.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::BinaryThresholdTool. This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. There is only a slider for INT values in QT. So, if the working image has a float/double pixeltype, we need to convert the original float intensity into a respective int value for the slider. The slider range is then between 0 and 99. If the pixeltype is INT, then we do not need any conversion. Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdToolGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkBinaryThresholdToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); signals: /// \brief Emitted when threshold Accepted void thresholdAccepted(); /// \brief Emitted when threshold Canceled void thresholdCanceled(); public slots: protected slots: void OnNewToolAssociated(mitk::Tool *); void OnAcceptThresholdPreview(); void OnSliderValueChanged(double value); protected: QmitkBinaryThresholdToolGUI(); ~QmitkBinaryThresholdToolGUI() override; + void BusyStateChanged(bool) override; + ctkSliderWidget* m_ThresholdSlider = nullptr; QCheckBox* m_CheckProcessAll = nullptr; QCheckBox* m_CheckCreateNew = nullptr; bool m_InternalUpdate = false; mitk::BinaryThresholdTool::Pointer m_BinaryThresholdTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp index 8f283ae403..5954acff4c 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp @@ -1,141 +1,163 @@ /*============================================================================ 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 "QmitkBinaryThresholdULToolGUI.h" #include #include #include #include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkBinaryThresholdULToolGUI, "") QmitkBinaryThresholdULToolGUI::QmitkBinaryThresholdULToolGUI() : QmitkToolGUI() { // create the visible widgets QBoxLayout *mainLayout = new QVBoxLayout(this); QLabel *label = new QLabel("Threshold :", this); QFont f = label->font(); f.setBold(false); label->setFont(f); mainLayout->addWidget(label); QBoxLayout *layout = new QHBoxLayout(); m_DoubleThresholdSlider = new ctkRangeWidget(); connect( m_DoubleThresholdSlider, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdsChanged(double, double))); layout->addWidget(m_DoubleThresholdSlider); mainLayout->addLayout(layout); m_DoubleThresholdSlider->setSingleStep(0.01); QPushButton *okButton = new QPushButton("Confirm Segmentation", this); connect(okButton, SIGNAL(clicked()), this, SLOT(OnAcceptThresholdPreview())); okButton->setFont(f); mainLayout->addWidget(okButton); m_CheckProcessAll = new QCheckBox("Process all time steps", this); m_CheckProcessAll->setChecked(false); + m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); mainLayout->addWidget(m_CheckProcessAll); m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); m_CheckCreateNew->setChecked(false); + m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); mainLayout->addWidget(m_CheckCreateNew); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkBinaryThresholdULToolGUI::~QmitkBinaryThresholdULToolGUI() { - // !!! if (m_BinaryThresholdULTool.IsNotNull()) { + m_BinaryThresholdULTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); m_BinaryThresholdULTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdULTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); } } void QmitkBinaryThresholdULToolGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_BinaryThresholdULTool.IsNotNull()) { + m_BinaryThresholdULTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); m_BinaryThresholdULTool->IntervalBordersChanged -= mitk::MessageDelegate3( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdULTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); } m_BinaryThresholdULTool = dynamic_cast(tool); if (m_BinaryThresholdULTool.IsNotNull()) { + m_BinaryThresholdULTool->CurrentlyBusy += + mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); m_BinaryThresholdULTool->IntervalBordersChanged += mitk::MessageDelegate3( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); m_BinaryThresholdULTool->ThresholdingValuesChanged += mitk::MessageDelegate2( this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(true); m_CheckProcessAll->setVisible(m_BinaryThresholdULTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); } } void QmitkBinaryThresholdULToolGUI::OnAcceptThresholdPreview() { if (m_BinaryThresholdULTool.IsNotNull()) { if (m_CheckCreateNew->isChecked()) { m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(false); } else { m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(true); } m_BinaryThresholdULTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); - m_BinaryThresholdULTool->AcceptCurrentThresholdValue(); + m_BinaryThresholdULTool->ConfirmSegmentation(); } } void QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) { if (!isFloat) { m_DoubleThresholdSlider->setRange(int(lower), int(upper)); m_DoubleThresholdSlider->setSingleStep(1); m_DoubleThresholdSlider->setDecimals(0); } else { m_DoubleThresholdSlider->setRange(lower, upper); } } void QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper) { m_DoubleThresholdSlider->setValues(lower, upper); } void QmitkBinaryThresholdULToolGUI::OnThresholdsChanged(double min, double max) { m_BinaryThresholdULTool->SetThresholdValues(min, max); } + +void QmitkBinaryThresholdULToolGUI::BusyStateChanged(bool value) +{ + if (value) + { + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + } + else + { + QApplication::restoreOverrideCursor(); + } + + m_DoubleThresholdSlider->setEnabled(!value); +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h index 3e178be366..ec0c130821 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h @@ -1,66 +1,68 @@ /*============================================================================ 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 QmitkBinaryThresholdULToolGUI_h_Included #define QmitkBinaryThresholdULToolGUI_h_Included #include "QmitkToolGUI.h" #include "ctkRangeWidget.h" #include #include "mitkBinaryThresholdULTool.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::BinaryThresholdTool. This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdULToolGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkBinaryThresholdULToolGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); signals: public slots: protected slots: void OnNewToolAssociated(mitk::Tool *); void OnAcceptThresholdPreview(); void OnThresholdsChanged(double min, double max); protected: QmitkBinaryThresholdULToolGUI(); ~QmitkBinaryThresholdULToolGUI() override; + void BusyStateChanged(bool) override; + ctkRangeWidget *m_DoubleThresholdSlider; QCheckBox* m_CheckProcessAll = nullptr; QCheckBox* m_CheckCreateNew = nullptr; mitk::BinaryThresholdULTool::Pointer m_BinaryThresholdULTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp index 855a67e49c..b83f50a5eb 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp @@ -1,370 +1,361 @@ /*============================================================================ 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 "QmitkFastMarchingTool3DGUI.h" #include "QmitkConfirmSegmentationDialog.h" #include "mitkBaseRenderer.h" #include "mitkStepper.h" #include #include #include #include #include #include #include #include +#include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkFastMarchingTool3DGUI, "") -QmitkFastMarchingTool3DGUI::QmitkFastMarchingTool3DGUI() : QmitkToolGUI(), m_TimeIsConnected(false) +QmitkFastMarchingTool3DGUI::QmitkFastMarchingTool3DGUI() : QmitkToolGUI() { this->setContentsMargins(0, 0, 0, 0); // create the visible widgets QVBoxLayout *widgetLayout = new QVBoxLayout(this); widgetLayout->setContentsMargins(0, 0, 0, 0); QFont fntHelp; fntHelp.setBold(true); QLabel *lblHelp = new QLabel(this); lblHelp->setText("Press shift-click to add seeds repeatedly."); lblHelp->setFont(fntHelp); widgetLayout->addWidget(lblHelp); // Sigma controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Sigma: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addItem(hlayout); } m_slSigma = new ctkSliderWidget(this); m_slSigma->setMinimum(0.1); m_slSigma->setMaximum(5.0); m_slSigma->setPageStep(0.1); m_slSigma->setSingleStep(0.01); m_slSigma->setValue(1.0); m_slSigma->setTracking(false); m_slSigma->setToolTip("The \"sigma\" parameter in the Gradient Magnitude filter."); connect(m_slSigma, SIGNAL(valueChanged(double)), this, SLOT(OnSigmaChanged(double))); widgetLayout->addWidget(m_slSigma); // Alpha controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Alpha: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addItem(hlayout); } m_slAlpha = new ctkSliderWidget(this); m_slAlpha->setMinimum(-10); m_slAlpha->setMaximum(0); m_slAlpha->setPageStep(0.1); m_slAlpha->setSingleStep(0.01); m_slAlpha->setValue(-2.5); m_slAlpha->setTracking(false); m_slAlpha->setToolTip("The \"alpha\" parameter in the Sigmoid mapping filter."); connect(m_slAlpha, SIGNAL(valueChanged(double)), this, SLOT(OnAlphaChanged(double))); widgetLayout->addWidget(m_slAlpha); // Beta controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Beta: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slBeta = new ctkSliderWidget(this); m_slBeta->setMinimum(0); m_slBeta->setMaximum(100); m_slBeta->setPageStep(0.1); m_slBeta->setSingleStep(0.01); m_slBeta->setValue(3.5); m_slBeta->setTracking(false); m_slBeta->setToolTip("The \"beta\" parameter in the Sigmoid mapping filter."); connect(m_slBeta, SIGNAL(valueChanged(double)), this, SLOT(OnBetaChanged(double))); widgetLayout->addWidget(m_slBeta); // stopping value controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Stopping value: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slStoppingValue = new ctkSliderWidget(this); m_slStoppingValue->setMinimum(0); m_slStoppingValue->setMaximum(10000); m_slStoppingValue->setPageStep(10); m_slStoppingValue->setSingleStep(1); m_slStoppingValue->setValue(2000); m_slStoppingValue->setDecimals(0); m_slStoppingValue->setTracking(false); m_slStoppingValue->setToolTip("The \"stopping value\" parameter in the fast marching 3D algorithm"); connect(m_slStoppingValue, SIGNAL(valueChanged(double)), this, SLOT(OnStoppingValueChanged(double))); widgetLayout->addWidget(m_slStoppingValue); // threshold controls { QHBoxLayout *hlayout = new QHBoxLayout(); hlayout->setSpacing(2); QLabel *lbl = new QLabel(this); lbl->setText("Threshold: "); hlayout->addWidget(lbl); QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); hlayout->addItem(sp2); widgetLayout->addLayout(hlayout); } m_slwThreshold = new ctkRangeWidget(this); m_slwThreshold->setMinimum(-100); m_slwThreshold->setMaximum(5000); m_slwThreshold->setMinimumValue(-100); m_slwThreshold->setMaximumValue(2000); m_slwThreshold->setDecimals(0); m_slwThreshold->setTracking(false); m_slwThreshold->setToolTip("The lower and upper thresholds for the final thresholding"); connect(m_slwThreshold, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdChanged(double, double))); widgetLayout->addWidget(m_slwThreshold); m_btClearSeeds = new QPushButton("Clear"); m_btClearSeeds->setToolTip("Clear current result and start over again"); m_btClearSeeds->setEnabled(false); widgetLayout->addWidget(m_btClearSeeds); connect(m_btClearSeeds, SIGNAL(clicked()), this, SLOT(OnClearSeeds())); m_btConfirm = new QPushButton("Confirm Segmentation"); m_btConfirm->setToolTip("Incorporate current result in your working session."); m_btConfirm->setEnabled(false); widgetLayout->addWidget(m_btConfirm); connect(m_btConfirm, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); + m_CheckProcessAll = new QCheckBox("Process all time steps", this); + m_CheckProcessAll->setChecked(false); + m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); + widgetLayout->addWidget(m_CheckProcessAll); + + m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); + m_CheckCreateNew->setChecked(false); + m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); + widgetLayout->addWidget(m_CheckCreateNew); + connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); m_slSigma->setDecimals(2); m_slBeta->setDecimals(2); m_slAlpha->setDecimals(2); this->EnableWidgets(false); } QmitkFastMarchingTool3DGUI::~QmitkFastMarchingTool3DGUI() { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady)); } } void QmitkFastMarchingTool3DGUI::OnNewToolAssociated(mitk::Tool *tool) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady)); } m_FastMarchingTool = dynamic_cast(tool); if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->CurrentlyBusy += mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - m_FastMarchingTool->AddReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady)); - // listen to timestep change events - mitk::BaseRenderer::Pointer renderer; - renderer = mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); - if (renderer.IsNotNull() && !m_TimeIsConnected) - { - new QmitkStepperAdapter(this, renderer->GetSliceNavigationController()->GetTime(), "stepper"); - // connect(m_TimeStepper, SIGNAL(Refetch()), this, SLOT(Refetch())); - m_TimeIsConnected = true; - } + m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); + m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); + m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); + m_FastMarchingTool->SetSigma(m_slSigma->value()); + m_FastMarchingTool->SetAlpha(m_slAlpha->value()); + m_FastMarchingTool->SetBeta(m_slBeta->value()); + m_FastMarchingTool->SetOverwriteExistingSegmentation(true); + m_FastMarchingTool->ClearSeeds(); + m_CheckProcessAll->setVisible(m_FastMarchingTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); } } void QmitkFastMarchingTool3DGUI::Update() { if (m_FastMarchingTool.IsNotNull()) { - m_FastMarchingTool->SetLowerThreshold(this->m_slwThreshold->minimumValue()); - m_FastMarchingTool->SetUpperThreshold(this->m_slwThreshold->maximumValue()); - m_FastMarchingTool->SetStoppingValue(this->m_slStoppingValue->value()); - m_FastMarchingTool->SetSigma(this->m_slSigma->value()); - m_FastMarchingTool->SetAlpha(this->m_slAlpha->value()); - m_FastMarchingTool->SetBeta(this->m_slBeta->value()); - m_FastMarchingTool->Update(); + m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); + m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); + m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); + m_FastMarchingTool->SetSigma(m_slSigma->value()); + m_FastMarchingTool->SetAlpha(m_slAlpha->value()); + m_FastMarchingTool->SetBeta(m_slBeta->value()); + m_FastMarchingTool->UpdatePreview(); } } void QmitkFastMarchingTool3DGUI::OnThresholdChanged(double lower, double upper) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetLowerThreshold(lower); m_FastMarchingTool->SetUpperThreshold(upper); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnBetaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetBeta(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnSigmaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetSigma(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnAlphaChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetAlpha(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnStoppingValueChanged(double value) { if (m_FastMarchingTool.IsNotNull()) { m_FastMarchingTool->SetStoppingValue(value); this->Update(); } } void QmitkFastMarchingTool3DGUI::OnConfirmSegmentation() { - QmitkConfirmSegmentationDialog dialog; - QString segName = QString::fromStdString(m_FastMarchingTool->GetCurrentSegmentationName()); - - dialog.SetSegmentationName(segName); - int result = dialog.exec(); - - switch (result) + if (m_FastMarchingTool.IsNotNull()) { - case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: + if (m_CheckCreateNew->isChecked()) + { m_FastMarchingTool->SetOverwriteExistingSegmentation(false); - break; - case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: + } + else + { m_FastMarchingTool->SetOverwriteExistingSegmentation(true); - break; - case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: - return; - } - if (m_FastMarchingTool.IsNotNull()) - { + } + + m_FastMarchingTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); + m_btConfirm->setEnabled(false); m_FastMarchingTool->ConfirmSegmentation(); } } -void QmitkFastMarchingTool3DGUI::SetStepper(mitk::Stepper *stepper) -{ - this->m_TimeStepper = stepper; -} - -void QmitkFastMarchingTool3DGUI::Refetch() -{ - // event from image navigator recieved - timestep has changed - m_FastMarchingTool->SetCurrentTimeStep(m_TimeStepper->GetPos()); -} - void QmitkFastMarchingTool3DGUI::OnClearSeeds() { // event from image navigator recieved - timestep has changed m_FastMarchingTool->ClearSeeds(); m_btClearSeeds->setEnabled(false); m_btConfirm->setEnabled(false); this->EnableWidgets(false); this->Update(); } void QmitkFastMarchingTool3DGUI::BusyStateChanged(bool value) { if (value) + { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + this->EnableWidgets(false); + } else + { QApplication::restoreOverrideCursor(); -} - -void QmitkFastMarchingTool3DGUI::OnFastMarchingToolReady() -{ - this->EnableWidgets(true); - this->m_btClearSeeds->setEnabled(true); - this->m_btConfirm->setEnabled(true); + this->EnableWidgets(true); + } } void QmitkFastMarchingTool3DGUI::EnableWidgets(bool enable) { m_slSigma->setEnabled(enable); m_slAlpha->setEnabled(enable); m_slBeta->setEnabled(enable); m_slStoppingValue->setEnabled(enable); m_slwThreshold->setEnabled(enable); + m_btClearSeeds->setEnabled(enable); + m_btConfirm->setEnabled(enable); + m_CheckCreateNew->setEnabled(enable); + m_CheckProcessAll->setEnabled(enable); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h index 67c8204881..a02b18de12 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h @@ -1,84 +1,79 @@ /*============================================================================ 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 QmitkFastMarchingTool3DGUI_h_Included #define QmitkFastMarchingTool3DGUI_h_Included #include "QmitkToolGUI.h" #include "mitkFastMarchingTool3D.h" #include class ctkSliderWidget; class ctkRangeWidget; class QPushButton; - -#include "QmitkStepperAdapter.h" +class QCheckBox; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::FastMarchingTool. \sa mitk::FastMarchingTool */ class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingTool3DGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkFastMarchingTool3DGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); void OnThresholdChanged(int current); protected slots: void OnNewToolAssociated(mitk::Tool *); void OnThresholdChanged(double, double); void OnAlphaChanged(double); void OnBetaChanged(double); void OnSigmaChanged(double); void OnStoppingValueChanged(double); void OnConfirmSegmentation(); - void Refetch(); - void SetStepper(mitk::Stepper *); void OnClearSeeds(); protected: QmitkFastMarchingTool3DGUI(); ~QmitkFastMarchingTool3DGUI() override; void BusyStateChanged(bool) override; void Update(); ctkRangeWidget *m_slwThreshold; ctkSliderWidget *m_slStoppingValue; ctkSliderWidget *m_slSigma; ctkSliderWidget *m_slAlpha; ctkSliderWidget *m_slBeta; QPushButton *m_btConfirm; QPushButton *m_btClearSeeds; - mitk::FastMarchingTool3D::Pointer m_FastMarchingTool; - - bool m_TimeIsConnected; - mitk::Stepper::Pointer m_TimeStepper; + QCheckBox* m_CheckProcessAll = nullptr; + QCheckBox* m_CheckCreateNew = nullptr; - void OnFastMarchingToolReady(); + mitk::FastMarchingTool3D::Pointer m_FastMarchingTool; private: void EnableWidgets(bool); }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp index 2d9b8f8032..031b353dab 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp @@ -1,184 +1,214 @@ /*============================================================================ 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 "QmitkOtsuTool3DGUI.h" #include "QmitkConfirmSegmentationDialog.h" #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkOtsuTool3DGUI, "") QmitkOtsuTool3DGUI::QmitkOtsuTool3DGUI() : QmitkToolGUI(), m_NumberOfRegions(0) { m_Controls.setupUi(this); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnSpinboxValueAccept())); connect(m_Controls.m_selectionListWidget, SIGNAL(itemSelectionChanged()), this, SLOT(OnRegionSelectionChanged())); connect(m_Controls.m_Spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnRegionSpinboxChanged(int))); connect(m_Controls.m_ConfSegButton, SIGNAL(clicked()), this, SLOT(OnSegmentationRegionAccept())); connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); connect(m_Controls.advancedSettingsButton, SIGNAL(toggled(bool)), this, SLOT(OnAdvancedSettingsButtonToggled(bool))); this->OnAdvancedSettingsButtonToggled(false); } QmitkOtsuTool3DGUI::~QmitkOtsuTool3DGUI() { + if (m_OtsuTool3DTool.IsNotNull()) + { + m_OtsuTool3DTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); + } } void QmitkOtsuTool3DGUI::OnRegionSpinboxChanged(int numberOfRegions) { // we have to change to minimum number of histogram bins accordingly int curBinValue = m_Controls.m_BinsSpinBox->value(); if (curBinValue < numberOfRegions) m_Controls.m_BinsSpinBox->setValue(numberOfRegions); } void QmitkOtsuTool3DGUI::OnRegionSelectionChanged() { m_SelectedItems = m_Controls.m_selectionListWidget->selectedItems(); - if (m_SelectedItems.size() == 0) - { - m_Controls.m_ConfSegButton->setEnabled(false); - m_OtsuTool3DTool->ShowMultiLabelResultNode(true); - return; - } - if (m_OtsuTool3DTool.IsNotNull()) { // update preview of region QList::Iterator it; std::vector regionIDs; for (it = m_SelectedItems.begin(); it != m_SelectedItems.end(); ++it) regionIDs.push_back((*it)->text().toInt()); - m_OtsuTool3DTool->UpdateBinaryPreview(regionIDs); - m_Controls.m_ConfSegButton->setEnabled(true); + + m_OtsuTool3DTool->SetSelectedRegions(regionIDs); + m_OtsuTool3DTool->UpdatePreview(); + + m_Controls.m_ConfSegButton->setEnabled(!regionIDs.empty()); } } void QmitkOtsuTool3DGUI::OnAdvancedSettingsButtonToggled(bool toggled) { m_Controls.m_ValleyCheckbox->setVisible(toggled); m_Controls.binLabel->setVisible(toggled); m_Controls.m_BinsSpinBox->setVisible(toggled); if (toggled) { - int max = m_OtsuTool3DTool->GetNumberOfBins(); + int max = m_OtsuTool3DTool->GetMaxNumberOfBins(); if (max >= m_Controls.m_BinsSpinBox->minimum()) { m_Controls.m_BinsSpinBox->setMaximum(max); } } } void QmitkOtsuTool3DGUI::OnNewToolAssociated(mitk::Tool *tool) { + if (m_OtsuTool3DTool.IsNotNull()) + { + m_OtsuTool3DTool->CurrentlyBusy -= + mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); + } + m_OtsuTool3DTool = dynamic_cast(tool); + + if (m_OtsuTool3DTool.IsNotNull()) + { + m_OtsuTool3DTool->CurrentlyBusy += + mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); + + m_OtsuTool3DTool->SetOverwriteExistingSegmentation(true); + m_OtsuTool3DTool->IsTimePointChangeAwareOff(); + m_Controls.m_CheckProcessAll->setVisible(m_OtsuTool3DTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); + } } void QmitkOtsuTool3DGUI::OnSegmentationRegionAccept() { QmitkConfirmSegmentationDialog dialog; QString segName = QString::fromStdString(m_OtsuTool3DTool->GetCurrentSegmentationName()); - dialog.SetSegmentationName(segName); - int result = dialog.exec(); - - switch (result) + if (m_OtsuTool3DTool.IsNotNull()) { - case QmitkConfirmSegmentationDialog::CREATE_NEW_SEGMENTATION: + if (this->m_Controls.m_CheckCreateNew->isChecked()) + { m_OtsuTool3DTool->SetOverwriteExistingSegmentation(false); - break; - case QmitkConfirmSegmentationDialog::OVERWRITE_SEGMENTATION: + } + else + { m_OtsuTool3DTool->SetOverwriteExistingSegmentation(true); - break; - case QmitkConfirmSegmentationDialog::CANCEL_SEGMENTATION: - return; - } + } - if (m_OtsuTool3DTool.IsNotNull() && m_Controls.m_selectionListWidget->currentItem() != nullptr) - { + m_OtsuTool3DTool->SetCreateAllTimeSteps(this->m_Controls.m_CheckProcessAll->isChecked()); + + this->m_Controls.m_ConfSegButton->setEnabled(false); m_OtsuTool3DTool->ConfirmSegmentation(); } } void QmitkOtsuTool3DGUI::OnSpinboxValueAccept() { if (m_NumberOfRegions == m_Controls.m_Spinbox->value() && m_UseValleyEmphasis == m_Controls.m_ValleyCheckbox->isChecked() && m_NumberOfBins == m_Controls.m_BinsSpinBox->value()) return; if (m_OtsuTool3DTool.IsNotNull()) { try { 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 (m_Controls.m_Spinbox->value() >= 5) { proceed = messageBox->exec(); if (proceed != QMessageBox::Ok) return; } m_NumberOfRegions = m_Controls.m_Spinbox->value(); m_UseValleyEmphasis = m_Controls.m_ValleyCheckbox->isChecked(); m_NumberOfBins = m_Controls.m_BinsSpinBox->value(); + m_OtsuTool3DTool->SetNumberOfRegions(m_NumberOfRegions); + m_OtsuTool3DTool->SetUseValley(m_UseValleyEmphasis); + m_OtsuTool3DTool->SetNumberOfBins(m_NumberOfBins); - this->setCursor(Qt::WaitCursor); - m_OtsuTool3DTool->RunSegmentation(m_NumberOfRegions, m_UseValleyEmphasis, m_NumberOfBins); - this->setCursor(Qt::ArrowCursor); + m_OtsuTool3DTool->UpdatePreview(); } catch (...) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(QMessageBox::Critical, nullptr, "itkOtsuFilter error: image dimension must be in {2, 3} and no RGB images can be handled."); messageBox->exec(); delete messageBox; return; } + // insert regions into widget QString itemName; QListWidgetItem *item; m_Controls.m_selectionListWidget->clear(); for (int i = 0; i < m_Controls.m_Spinbox->value(); ++i) { itemName = QString::number(i); item = new QListWidgetItem(itemName); m_Controls.m_selectionListWidget->addItem(item); } // deactivate 'confirm segmentation'-button m_Controls.m_ConfSegButton->setEnabled(false); + m_OtsuTool3DTool->IsTimePointChangeAwareOn(); } } -void QmitkOtsuTool3DGUI::OnVolumePreviewChecked(int state) +void QmitkOtsuTool3DGUI::BusyStateChanged(bool value) { - if (state == 1) + if (value) + { + QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); + } + else { + QApplication::restoreOverrideCursor(); } + + m_Controls.m_ValleyCheckbox->setEnabled(!value); + m_Controls.binLabel->setEnabled(!value); + m_Controls.m_BinsSpinBox->setEnabled(!value); + m_Controls.m_ConfSegButton->setEnabled(!m_OtsuTool3DTool->GetSelectedRegions().empty() && !value); + m_Controls.m_CheckProcessAll->setEnabled(!value); + m_Controls.m_CheckCreateNew->setEnabled(!value); + m_Controls.previewButton->setEnabled(!value); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h index f562f73560..ce2ad862fe 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h @@ -1,83 +1,83 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkOtsuTool3DGUI_h_Included #define QmitkOtsuTool3DGUI_h_Included #include "QmitkToolGUI.h" #include "mitkOtsuTool3D.h" #include "ui_QmitkOtsuToolWidgetControls.h" #include #include #include class QSpinBox; class QLabel; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::. \sa mitk:: This GUI shows ... Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkOtsuTool3DGUI : public QmitkToolGUI { Q_OBJECT public: mitkClassMacro(QmitkOtsuTool3DGUI, QmitkToolGUI); itkFactorylessNewMacro(Self); itkCloneMacro(Self); signals : public slots : protected slots : void OnNewToolAssociated(mitk::Tool *); void OnSpinboxValueAccept(); void OnSegmentationRegionAccept(); void OnRegionSelectionChanged(); void OnRegionSpinboxChanged(int); - void OnVolumePreviewChecked(int); - private slots: void OnAdvancedSettingsButtonToggled(bool toggled); protected: QmitkOtsuTool3DGUI(); ~QmitkOtsuTool3DGUI() override; + void BusyStateChanged(bool value) override; + mitk::OtsuTool3D::Pointer m_OtsuTool3DTool; Ui_QmitkOtsuToolWidgetControls m_Controls; int m_NumberOfRegions; bool m_UseValleyEmphasis; int m_NumberOfBins; QList m_SelectedItems; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui b/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui index e5484bf97b..27e1d87ff8 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui @@ -1,214 +1,234 @@ QmitkOtsuToolWidgetControls 0 0 192 - 293 + 300 0 0 100 0 100000 100000 QmitkOtsuToolWidget Move to adjust the segmentation QLayout::SetNoConstraint 0 0 Number of Regions: 0 0 40 16777215 2 32 0 0 0 32 Advanced settings Qt::ToolButtonTextBesideIcon true Use Valley Emphasis Number of Histogram Bins: 2 4096 128 0 0 10000000 100 0 QAbstractItemView::MultiSelection QListView::Adjust 0 0 100000 16777215 Preview false 0 0 100000 16777215 Confirm Segmentation + + + + Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step. + + + Process all time steps + + + + + + + Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected. + + + Create as new segmentation + + + ctkExpandButton QToolButton
ctkExpandButton.h
diff --git a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp index 2dcb1b21fe..43ca2e2c15 100755 --- a/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkToolSelectionBox.cpp @@ -1,716 +1,722 @@ /*============================================================================ 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. ============================================================================*/ //#define MBILOG_ENABLE_DEBUG 1 #include #include "QmitkToolSelectionBox.h" #include "QmitkToolGUI.h" #include "mitkBaseRenderer.h" #include #include #include #include #include #include #include #include "usModuleResource.h" #include "usModuleResourceStream.h" #include "mitkToolManagerProvider.h" QmitkToolSelectionBox::QmitkToolSelectionBox(QWidget *parent, mitk::DataStorage *) : QWidget(parent), m_SelfCall(false), m_DisplayedGroups("default"), m_LayoutColumns(2), m_ShowNames(true), m_GenerateAccelerators(false), m_ToolGUIWidget(nullptr), m_LastToolGUI(nullptr), m_ToolButtonGroup(nullptr), m_ButtonLayout(nullptr), m_EnabledMode(EnabledWithReferenceAndWorkingDataVisible) { QFont currentFont = QWidget::font(); currentFont.setBold(true); QWidget::setFont(currentFont); m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); // muellerm // QButtonGroup m_ToolButtonGroup = new QButtonGroup(this); // some features of QButtonGroup m_ToolButtonGroup->setExclusive(false); // mutually exclusive toggle buttons RecreateButtons(); QWidget::setContentsMargins(0, 0, 0, 0); if (layout() != nullptr) { layout()->setContentsMargins(0, 0, 0, 0); } // reactions to signals connect(m_ToolButtonGroup, SIGNAL(buttonClicked(int)), this, SLOT(toolButtonClicked(int))); // reactions to ToolManager events m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); // show active tool SetOrUnsetButtonForActiveTool(); QWidget::setEnabled(false); } QmitkToolSelectionBox::~QmitkToolSelectionBox() { m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); } void QmitkToolSelectionBox::SetEnabledMode(EnabledMode mode) { m_EnabledMode = mode; SetGUIEnabledAccordingToToolManagerState(); } mitk::ToolManager *QmitkToolSelectionBox::GetToolManager() { return m_ToolManager; } void QmitkToolSelectionBox::SetToolManager( mitk::ToolManager &newManager) // no nullptr pointer allowed here, a manager is required { // say bye to the old manager m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->UnregisterClient(); } m_ToolManager = &newManager; RecreateButtons(); // greet the new one m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerToolModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerReferenceDataModified); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkToolSelectionBox::OnToolManagerWorkingDataModified); if (QWidget::isEnabled()) { m_ToolManager->RegisterClient(); } // ask the new one what the situation is like SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::toolButtonClicked(int id) { if (!QWidget::isEnabled()) return; // this method could be triggered from the constructor, when we are still disabled MITK_DEBUG << "toolButtonClicked(" << id << "): id translates to tool ID " << m_ToolIDForButtonID[id]; // QToolButton* toolButton = dynamic_cast( Q3ButtonGroup::find(id) ); QToolButton *toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(id)); if (toolButton) { if ((m_ButtonIDForToolID.find(m_ToolManager->GetActiveToolID()) != m_ButtonIDForToolID.end()) // if we have this tool in our box && (m_ButtonIDForToolID[m_ToolManager->GetActiveToolID()] == id)) // the tool corresponding to this button is already active { // disable this button, disable all tools // mmueller toolButton->setChecked(false); m_ToolManager->ActivateTool(-1); // disable everything } else { // enable the corresponding tool m_SelfCall = true; m_ToolManager->ActivateTool(m_ToolIDForButtonID[id]); m_SelfCall = false; } } } void QmitkToolSelectionBox::OnToolManagerToolModified() { SetOrUnsetButtonForActiveTool(); } void QmitkToolSelectionBox::SetOrUnsetButtonForActiveTool() { // we want to emit a signal in any case, whether we selected ourselves or somebody else changes "our" tool manager. // --> emit before check on m_SelfCall int id = m_ToolManager->GetActiveToolID(); // don't emit signal for shape model tools bool emitSignal = true; mitk::Tool *tool = m_ToolManager->GetActiveTool(); if (tool && std::string(tool->GetGroup()) == "organ_segmentation") emitSignal = false; if (emitSignal) emit ToolSelected(id); // delete old GUI (if any) if (m_LastToolGUI && m_ToolGUIWidget) { if (m_ToolGUIWidget->layout()) { m_ToolGUIWidget->layout()->removeWidget(m_LastToolGUI); } // m_LastToolGUI->reparent(nullptr, QPoint(0,0)); // TODO: reparent <-> setParent, Daniel fragen m_LastToolGUI->setParent(nullptr); delete m_LastToolGUI; // will hopefully notify parent and layouts m_LastToolGUI = nullptr; QLayout *layout = m_ToolGUIWidget->layout(); if (layout) { layout->activate(); } } QToolButton *toolButton(nullptr); // mitk::Tool* tool = m_ToolManager->GetActiveTool(); if (m_ButtonIDForToolID.find(id) != m_ButtonIDForToolID.end()) // if this tool is in our box { // toolButton = dynamic_cast( Q3ButtonGroup::find( m_ButtonIDForToolID[id] ) ); toolButton = dynamic_cast(m_ToolButtonGroup->buttons().at(m_ButtonIDForToolID[id])); } if (toolButton) { // mmueller // uncheck all other buttons QAbstractButton *tmpBtn = nullptr; QList::iterator it; for (int i = 0; i < m_ToolButtonGroup->buttons().size(); ++i) { tmpBtn = m_ToolButtonGroup->buttons().at(i); if (tmpBtn != toolButton) dynamic_cast(tmpBtn)->setChecked(false); } toolButton->setChecked(true); if (m_ToolGUIWidget && tool) { // create and reparent new GUI (if any) itk::Object::Pointer possibleGUI = tool->GetGUI("Qmitk", "GUI").GetPointer(); // prefix and postfix if (possibleGUI.IsNull()) possibleGUI = tool->GetGUI("", "GUI").GetPointer(); QmitkToolGUI *gui = dynamic_cast(possibleGUI.GetPointer()); //! m_LastToolGUI = gui; if (gui) { gui->SetTool(tool); // mmueller // gui->reparent(m_ToolGUIWidget, gui->geometry().topLeft(), true ); gui->setParent(m_ToolGUIWidget); gui->move(gui->geometry().topLeft()); gui->show(); QLayout *layout = m_ToolGUIWidget->layout(); if (!layout) { layout = new QVBoxLayout(m_ToolGUIWidget); } if (layout) { // mmueller layout->addWidget(gui); // layout->add( gui ); layout->activate(); } } } } else { // disable all buttons QToolButton *selectedToolButton = dynamic_cast(m_ToolButtonGroup->checkedButton()); // QToolButton* selectedToolButton = dynamic_cast( Q3ButtonGroup::find( Q3ButtonGroup::selectedId() ) // ); if (selectedToolButton) { // mmueller selectedToolButton->setChecked(false); // selectedToolButton->setOn(false); } } } void QmitkToolSelectionBox::OnToolManagerReferenceDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerReferenceDataModified()"; SetGUIEnabledAccordingToToolManagerState(); } void QmitkToolSelectionBox::OnToolManagerWorkingDataModified() { if (m_SelfCall) return; MITK_DEBUG << "OnToolManagerWorkingDataModified()"; SetGUIEnabledAccordingToToolManagerState(); } /** Implementes the logic, which decides, when tools are activated/deactivated. */ void QmitkToolSelectionBox::SetGUIEnabledAccordingToToolManagerState() { mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); // MITK_DEBUG << this->name() << ": SetGUIEnabledAccordingToToolManagerState: referenceNode " << (void*)referenceNode // << " workingNode " << (void*)workingNode << " isVisible() " << isVisible(); bool enabled = true; switch (m_EnabledMode) { default: case EnabledWithReferenceAndWorkingDataVisible: enabled = referenceNode && workingNode && referenceNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))) && workingNode->IsVisible( mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0"))) && isVisible(); break; case EnabledWithReferenceData: enabled = referenceNode && isVisible(); break; case EnabledWithWorkingData: enabled = workingNode && isVisible(); break; case AlwaysEnabled: enabled = isVisible(); break; } if (QWidget::isEnabled() == enabled) return; // nothing to change QWidget::setEnabled(enabled); if (enabled) { m_ToolManager->RegisterClient(); int id = m_ToolManager->GetActiveToolID(); emit ToolSelected(id); } else { m_ToolManager->ActivateTool(-1); m_ToolManager->UnregisterClient(); emit ToolSelected(-1); } } /** External enableization... */ void QmitkToolSelectionBox::setEnabled(bool /*enable*/) { SetGUIEnabledAccordingToToolManagerState(); } void QmitkToolSelectionBox::RecreateButtons() { if (m_ToolManager.IsNull()) return; /* // remove all buttons that are there QObjectList *l = Q3ButtonGroup::queryList( "QButton" ); QObjectListIt it( *l ); // iterate over all buttons QObject *obj; while ( (obj = it.current()) != 0 ) { ++it; QButton* button = dynamic_cast(obj); if (button) { Q3ButtonGroup::remove(button); delete button; } } delete l; // delete the list, not the objects */ // mmueller Qt impl QList l = m_ToolButtonGroup->buttons(); // remove all buttons that are there QList::iterator it; QAbstractButton *btn; for (it = l.begin(); it != l.end(); ++it) { btn = *it; m_ToolButtonGroup->removeButton(btn); // this->removeChild(btn); delete btn; } // end mmueller Qt impl mitk::ToolManager::ToolVectorTypeConst allPossibleTools = m_ToolManager->GetTools(); mitk::ToolManager::ToolVectorTypeConst allTools; typedef std::pair SortPairType; typedef std::priority_queue SortedToolQueueType; SortedToolQueueType toolPositions; // clear and sort all tools // step one: find name/group of all tools in m_DisplayedGroups string. remember these positions for all tools. for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allPossibleTools.begin(); iter != allPossibleTools.end(); ++iter) { const mitk::Tool *tool = *iter; std::string::size_type namePos = m_DisplayedGroups.find(std::string("'") + tool->GetName() + "'"); std::string::size_type groupPos = m_DisplayedGroups.find(std::string("'") + tool->GetGroup() + "'"); if (!m_DisplayedGroups.empty() && namePos == std::string::npos && groupPos == std::string::npos) continue; // skip if (m_DisplayedGroups.empty() && std::string(tool->GetName()).length() > 0) { namePos = static_cast(tool->GetName()[0]); } SortPairType thisPair = std::make_pair(namePos < groupPos ? namePos : groupPos, *iter); toolPositions.push(thisPair); } // step two: sort tools according to previously found positions in m_DisplayedGroups MITK_DEBUG << "Sorting order of tools (lower number --> earlier in button group)"; while (!toolPositions.empty()) { SortPairType thisPair = toolPositions.top(); MITK_DEBUG << "Position " << thisPair.first << " : " << thisPair.second->GetName(); allTools.push_back(thisPair.second); toolPositions.pop(); } std::reverse(allTools.begin(), allTools.end()); MITK_DEBUG << "Sorted tools:"; for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { MITK_DEBUG << (*iter)->GetName(); } // try to change layout... bad? // Q3GroupBox::setColumnLayout ( m_LayoutColumns, Qt::Horizontal ); // mmueller using gridlayout instead of Q3GroupBox // this->setLayout(0); if (m_ButtonLayout == nullptr) m_ButtonLayout = new QGridLayout; /*else delete m_ButtonLayout;*/ int row(0); int column(-1); int currentButtonID(0); m_ButtonIDForToolID.clear(); m_ToolIDForButtonID.clear(); QToolButton *button = nullptr; MITK_DEBUG << "Creating buttons for tools"; // fill group box with buttons for (mitk::ToolManager::ToolVectorTypeConst::const_iterator iter = allTools.begin(); iter != allTools.end(); ++iter) { const mitk::Tool *tool = *iter; int currentToolID(m_ToolManager->GetToolID(tool)); ++column; // new line if we are at the maximum columns if (column == m_LayoutColumns) { ++row; column = 0; } button = new QToolButton; button->setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); // add new button to the group MITK_DEBUG << "Adding button with ID " << currentToolID; m_ToolButtonGroup->addButton(button, currentButtonID); // ... and to the layout MITK_DEBUG << "Adding button in row/column " << row << "/" << column; m_ButtonLayout->addWidget(button, row, column); if (m_LayoutColumns == 1) { // button->setTextPosition( QToolButton::BesideIcon ); // mmueller button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); } else { // button->setTextPosition( QToolButton::BelowIcon ); // mmueller button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); } // button->setToggleButton( true ); // mmueller button->setCheckable(true); if (currentToolID == m_ToolManager->GetActiveToolID()) button->setChecked(true); QString label; if (m_GenerateAccelerators) { label += "&"; } label += tool->GetName(); QString tooltip = tool->GetName(); MITK_DEBUG << tool->GetName() << ", " << label.toLocal8Bit().constData() << ", '" << tooltip.toLocal8Bit().constData(); if (m_ShowNames) { /* button->setUsesTextLabel(true); button->setTextLabel( label ); // a label QToolTip::add( button, tooltip ); */ // mmueller Qt button->setText(label); // a label button->setToolTip(tooltip); // mmueller QFont currentFont = button->font(); currentFont.setBold(false); button->setFont(currentFont); } us::ModuleResource iconResource = tool->GetIconResource(); if (!iconResource.IsValid()) { button->setIcon(QIcon(QPixmap(tool->GetXPM()))); } else { auto isSVG = "svg" == iconResource.GetSuffix(); auto openmode = isSVG ? std::ios_base::in : std::ios_base::binary; us::ModuleResourceStream resourceStream(iconResource, openmode); resourceStream.seekg(0, std::ios::end); std::ios::pos_type length = resourceStream.tellg(); resourceStream.seekg(0, std::ios::beg); char *data = new char[length]; resourceStream.read(data, length); if (isSVG) { button->setIcon(QmitkStyleManager::ThemeIcon(QByteArray::fromRawData(data, length))); } else { QPixmap pixmap; pixmap.loadFromData(QByteArray::fromRawData(data, length)); button->setIcon(QIcon(pixmap)); } delete[] data; if (m_ShowNames) { if (m_LayoutColumns == 1) button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); else button->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); button->setIconSize(QSize(24, 24)); } else { button->setToolButtonStyle(Qt::ToolButtonIconOnly); button->setIconSize(QSize(32, 32)); button->setToolTip(tooltip); } } if (m_GenerateAccelerators) { QString firstLetter = QString(tool->GetName()); firstLetter.truncate(1); button->setShortcut( firstLetter); // a keyboard shortcut (just the first letter of the given name w/o any CTRL or something) } mitk::DataNode *dataNode = m_ToolManager->GetReferenceData(0); + const auto workingDataNode = m_ToolManager->GetWorkingData(0); + const mitk::BaseData* workingData = nullptr; + if (nullptr != workingDataNode) + { + workingData = workingDataNode->GetData(); + } - if (dataNode != nullptr && !tool->CanHandle(dataNode->GetData())) + if (nullptr == dataNode || !tool->CanHandle(dataNode->GetData(), workingData)) button->setEnabled(false); m_ButtonIDForToolID[currentToolID] = currentButtonID; m_ToolIDForButtonID[currentButtonID] = currentToolID; MITK_DEBUG << "m_ButtonIDForToolID[" << currentToolID << "] == " << currentButtonID; MITK_DEBUG << "m_ToolIDForButtonID[" << currentButtonID << "] == " << currentToolID; tool->GUIProcessEventsMessage += mitk::MessageDelegate( this, &QmitkToolSelectionBox::OnToolGUIProcessEventsMessage); // will never add a listener twice, so we don't have // to check here tool->ErrorMessage += mitk::MessageDelegate1( this, &QmitkToolSelectionBox::OnToolErrorMessage); // will never add a listener twice, so we don't have to check here tool->GeneralMessage += mitk::MessageDelegate1(this, &QmitkToolSelectionBox::OnGeneralToolMessage); ++currentButtonID; } // setting grid layout for this groupbox this->setLayout(m_ButtonLayout); // this->update(); } void QmitkToolSelectionBox::OnToolGUIProcessEventsMessage() { qApp->processEvents(); } void QmitkToolSelectionBox::OnToolErrorMessage(std::string s) { QMessageBox::critical( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::OnGeneralToolMessage(std::string s) { QMessageBox::information( this, "MITK", QString(s.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); } void QmitkToolSelectionBox::SetDisplayedToolGroups(const std::string &toolGroups) { if (m_DisplayedGroups != toolGroups) { QString q_DisplayedGroups = toolGroups.c_str(); // quote all unquoted single words q_DisplayedGroups = q_DisplayedGroups.replace(QRegExp("\\b(\\w+)\\b|'([^']+)'"), "'\\1\\2'"); MITK_DEBUG << "m_DisplayedGroups was \"" << toolGroups << "\""; m_DisplayedGroups = q_DisplayedGroups.toLocal8Bit().constData(); MITK_DEBUG << "m_DisplayedGroups is \"" << m_DisplayedGroups << "\""; RecreateButtons(); SetOrUnsetButtonForActiveTool(); } } void QmitkToolSelectionBox::SetLayoutColumns(int columns) { if (columns > 0 && columns != m_LayoutColumns) { m_LayoutColumns = columns; RecreateButtons(); } } void QmitkToolSelectionBox::SetShowNames(bool show) { if (show != m_ShowNames) { m_ShowNames = show; RecreateButtons(); } } void QmitkToolSelectionBox::SetGenerateAccelerators(bool accel) { if (accel != m_GenerateAccelerators) { m_GenerateAccelerators = accel; RecreateButtons(); } } void QmitkToolSelectionBox::SetToolGUIArea(QWidget *parentWidget) { m_ToolGUIWidget = parentWidget; } void QmitkToolSelectionBox::setTitle(const QString & /*title*/) { } void QmitkToolSelectionBox::showEvent(QShowEvent *e) { QWidget::showEvent(e); SetGUIEnabledAccordingToToolManagerState(); } void QmitkToolSelectionBox::hideEvent(QHideEvent *e) { QWidget::hideEvent(e); SetGUIEnabledAccordingToToolManagerState(); } diff --git a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/Basic_ImageCropperView.png b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/Basic_ImageCropperView.png index 7cb45a0828..8dcec773a9 100644 Binary files a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/Basic_ImageCropperView.png and b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/Basic_ImageCropperView.png differ diff --git a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/BoundingBox_ImageCropperView.png b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/BoundingBox_ImageCropperView.png index af303e721c..f4a6453618 100644 Binary files a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/BoundingBox_ImageCropperView.png and b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/BoundingBox_ImageCropperView.png differ diff --git a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper.dox b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper.dox index 2bcd21d2bb..ef2b68b13d 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper.dox +++ b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper.dox @@ -1,37 +1,52 @@ /** -\page org_mitk_gui_qt_imagecropper The Image Cropper +\page org_mitk_views_imagecropper The Image Cropper \imageMacro{crop.svg,"Icon of the Image Cropper Plugin.",20} -\tableofcontents +\section org_mitk_views_imagecropperUsage Usage -\section org_mitk_gui_qt_imagecropperUsage Usage +The Image Cropper Plugin allows to crop and mask subvolumes out of the original image volume by defining a cubic bounding shape. -The Image Cropper Plugin allows to crop subvolumes out of your original image volume by defining a cubic bounding box. +\imageMacro{BoundingBox_ImageCropperView.png,"Bounding Shape.",12.00} -This box can be placed at an arbitrary position in the volume and can be easily adjusted by using the handles on each of the faces. -Touching the handles changes the size of the box whereas touching the box itself changes its position. +A new bounding shape can be created by selecting an image and pressing the 'New' button. The bounding shape appears as a child node in the data manager. Alternatively, an existing bounding shape can be selected. -As soon as the bounding box is placed at the desired position, pressing the button 'Crop' creates a new image assigned to the original image -as child node containing only the selected subvolume. The size of the subvolume equals the size of the bounding box. -Pressing the "Mask" button keeps the original image size but masks out the area not contained within the bounding box bounds. -In case of 3D+t images the whole time series is cropped by default. - -\imageMacro{BoundingBox_ImageCropperView.png,"Bounding Box.",12.00} \imageMacro{Basic_ImageCropperView.png,"Basic Settings.",7.09} -\section org_mitk_gui_qt_imagecropperAdvanced Advanced settings -In the advanced settings view you find additional features to manipulate the bounding box. +This bounding shape can be placed at an arbitrary position in the volume and can be easily adjusted by using the handles on each of the faces. When activated, the handles are shown in red, otherwise, they are colored white. Hovering over either the shape or a single handle allows modifying the bounding shape. Moving the handles changes the respective extent of the bounding shape, whereas moving the shape itself changes its position. + + +As soon as the bounding shape is placed at the desired position, pressing the button 'Crop' creates a new image assigned to the original image as a child node containing only the selected subvolume. The size of the subvolume equals the size of the bounding shape. +Pressing the 'Mask' button keeps the original image size but masks out the area not contained within the bounding shape bounds. +In the case of 3D+t images, the whole time series is cropped by default. + + +\section org_mitk_views_imagecropperAdvanced Advanced settings + +In the advanced settings view you find additional features: \imageMacro{Advanced_ImageCropperView.png,"Advanced Settings.",7.09} -\subsection org_mitk_gui_qt_imagecropperAdvancedOverwrite Overwrite original image +\subsection org_mitk_views_imagecropperMaskOutsidePixel Mask with outside pixel + +Assigns the value of the voxels outside of the bounding shape when 'Mask' is used. + + +\subsection org_mitk_views_imagecropperAdvancedOverwrite Overwrite original image + By enabling this checkbox the image is replaced by the cropped subvolume. Be careful to use this option since there is no undo action available. -\subsection org_mitk_gui_qt_imagecropperAdvancedTimestep Crop current time step only -If this checkbox is enabled the xD + t image is reduced to a xD image (e.g., 3D+t --> 3D) with the time step visible in the widget. This is useful if you want to extract a single image or its corresponding subvolume of the time series. The whole time series is cropped by default using the timeGeometry of the time step visible in the widget. +\subsection org_mitk_views_imagecropperAdvancedTimestep Crop current time step only + +If you have an xD + t image, the whole time series (all timesteps) is cropped by default. In this case, the 'time geometry' of the current time step is used. + +If the checkbox 'Only crop current time step' is ticked, the xD + t image is reduced to an xD image (e.g., 3D+t --> 3D) with the current time step only. That can be useful if you want to extract a single image or its corresponding subvolume of the time series. + +\section org_mitk_views_imagecropperIssues Current issues -\section org_mitk_gui_qt_imagecropperIssues Current issues Cropping 2D images is not supported unless the are 3D images containing only a single slice. The user will be notified by a warning and the input is handled as a single label image. -Right now changing the shape or rotation of the bounding box is not supported but might be integrated in the future. -*/ \ No newline at end of file +Right now changing the shape or rotation of the bounding shape is not supported but might be integrated in the future. + +Furthermore, a warning appears when the bounding shape is not aligned with the image. In this case, the handles can not be used correctly and get deactivated. You can continue to alter them by performing a 'Reinit' on the image. + +*/ diff --git a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper_Icon.png b/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper_Icon.png deleted file mode 100644 index 221f3f7ac1..0000000000 Binary files a/Plugins/org.mitk.gui.qt.imagecropper/documentation/UserManual/QmitkImageCropper_Icon.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp index 84835e20fd..3594ac014f 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp +++ b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp @@ -1,494 +1,496 @@ /*============================================================================ 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 "QmitkImageCropperView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkImageCropperView::VIEW_ID = "org.mitk.views.qmitkimagecropper"; QmitkImageCropperView::QmitkImageCropperView(QObject *) : m_ParentWidget(nullptr) , m_BoundingShapeInteractor(nullptr) , m_CropOutsideValue(0) { CreateBoundingShapeInteractor(false); } QmitkImageCropperView::~QmitkImageCropperView() { //disable interactor if (m_BoundingShapeInteractor != nullptr) { m_BoundingShapeInteractor->SetDataNode(nullptr); m_BoundingShapeInteractor->EnableInteraction(false); } } void QmitkImageCropperView::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.imageSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.imageSelectionWidget->SetNodePredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_Controls.imageSelectionWidget->SetSelectionIsOptional(true); + m_Controls.imageSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.imageSelectionWidget->SetEmptyInfo(QString("Please select an image node")); m_Controls.imageSelectionWidget->SetPopUpTitel(QString("Select image node")); connect(m_Controls.imageSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnImageSelectionChanged); m_Controls.boundingBoxSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.boundingBoxSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls.boundingBoxSelectionWidget->SetSelectionIsOptional(true); + m_Controls.boundingBoxSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.boundingBoxSelectionWidget->SetEmptyInfo(QString("Please select a bounding box")); m_Controls.boundingBoxSelectionWidget->SetPopUpTitel(QString("Select bounding box node")); connect(m_Controls.boundingBoxSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnBoundingBoxSelectionChanged); connect(m_Controls.buttonCreateNewBoundingBox, SIGNAL(clicked()), this, SLOT(OnCreateNewBoundingBox())); connect(m_Controls.buttonCropping, SIGNAL(clicked()), this, SLOT(OnCropping())); connect(m_Controls.buttonMasking, SIGNAL(clicked()), this, SLOT(OnMasking())); auto lambda = [this]() { m_Controls.groupImageSettings->setVisible(!m_Controls.groupImageSettings->isVisible()); }; connect(m_Controls.buttonAdvancedSettings, &ctkExpandButton::clicked, this, lambda); connect(m_Controls.spinBoxOutsidePixelValue, SIGNAL(valueChanged(int)), this, SLOT(OnSliderValueChanged(int))); SetDefaultGUI(); m_ParentWidget = parent; } void QmitkImageCropperView::OnImageSelectionChanged(QList) { bool rotationEnabled = false; auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { SetDefaultGUI(); return; } auto image = dynamic_cast(imageNode->GetData()); if (nullptr != image) { if (image->GetDimension() < 3) { QMessageBox::warning(nullptr, tr("Invalid image selected"), tr("ImageCropper only works with 3 or more dimensions."), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); SetDefaultGUI(); return; } m_ParentWidget->setEnabled(true); m_Controls.buttonCreateNewBoundingBox->setEnabled(true); vtkSmartPointer imageMat = image->GetGeometry()->GetVtkMatrix(); // check whether the image geometry is rotated; if so, no pixel aligned cropping or masking can be performed if ((imageMat->GetElement(1, 0) == 0.0) && (imageMat->GetElement(0, 1) == 0.0) && (imageMat->GetElement(1, 2) == 0.0) && (imageMat->GetElement(2, 1) == 0.0) && (imageMat->GetElement(2, 0) == 0.0) && (imageMat->GetElement(0, 2) == 0.0)) { rotationEnabled = false; m_Controls.labelWarningRotation->setVisible(false); } else { rotationEnabled = true; m_Controls.labelWarningRotation->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningRotation->setVisible(true); } this->CreateBoundingShapeInteractor(rotationEnabled); if (itk::ImageIOBase::SCALAR == image->GetPixelType().GetPixelType()) { // Might be changed with the upcoming new image statistics plugin //(recomputation might be very expensive for large images ;) ) auto statistics = image->GetStatistics(); auto minPixelValue = statistics->GetScalarValueMin(); auto maxPixelValue = statistics->GetScalarValueMax(); if (minPixelValue < std::numeric_limits::min()) { minPixelValue = std::numeric_limits::min(); } if (maxPixelValue > std::numeric_limits::max()) { maxPixelValue = std::numeric_limits::max(); } m_Controls.spinBoxOutsidePixelValue->setEnabled(true); m_Controls.spinBoxOutsidePixelValue->setMaximum(static_cast(maxPixelValue)); m_Controls.spinBoxOutsidePixelValue->setMinimum(static_cast(minPixelValue)); m_Controls.spinBoxOutsidePixelValue->setValue(static_cast(minPixelValue)); } else { m_Controls.spinBoxOutsidePixelValue->setEnabled(false); } unsigned int dim = image->GetDimension(); if (dim < 2 || dim > 4) { m_ParentWidget->setEnabled(false); } if (m_Controls.boundingBoxSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnBoundingBoxSelectionChanged(QList) { auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { SetDefaultGUI(); m_BoundingShapeInteractor->EnableInteraction(false); m_BoundingShapeInteractor->SetDataNode(nullptr); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCreateNewBoundingBox->setEnabled(true); } return; } auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != boundingBox) { // node newly selected boundingBoxNode->SetVisibility(true); m_BoundingShapeInteractor->EnableInteraction(true); m_BoundingShapeInteractor->SetDataNode(boundingBoxNode); mitk::RenderingManager::GetInstance()->InitializeViews(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnCreateNewBoundingBox() { auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { return; } if (nullptr == imageNode->GetData()) { return; } QString name = QString::fromStdString(imageNode->GetName() + " Bounding Shape"); auto boundingShape = this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&name](const mitk::DataNode *node) { return 0 == node->GetName().compare(name.toStdString()); })); if (nullptr != boundingShape) { name = this->AdaptBoundingObjectName(name); } // get current timestep to support 3d+t images auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const auto timePoint = renderWindowPart->GetSelectedTimePoint(); const auto imageGeometry = imageNode->GetData()->GetTimeGeometry()->GetGeometryForTimePoint(timePoint); auto boundingBox = mitk::GeometryData::New(); boundingBox->SetGeometry(static_cast(this->InitializeWithImageGeometry(imageGeometry))); auto boundingBoxNode = mitk::DataNode::New(); boundingBoxNode->SetData(boundingBox); boundingBoxNode->SetProperty("name", mitk::StringProperty::New(name.toStdString())); boundingBoxNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); boundingBoxNode->SetProperty("opacity", mitk::FloatProperty::New(0.6)); boundingBoxNode->SetProperty("layer", mitk::IntProperty::New(99)); boundingBoxNode->AddProperty("handle size factor", mitk::DoubleProperty::New(1.0 / 40.0)); boundingBoxNode->SetBoolProperty("pickable", true); if (!this->GetDataStorage()->Exists(boundingBoxNode)) { GetDataStorage()->Add(boundingBoxNode, imageNode); } m_Controls.boundingBoxSelectionWidget->SetCurrentSelectedNode(boundingBoxNode); } void QmitkImageCropperView::OnCropping() { this->ProcessImage(false); } void QmitkImageCropperView::OnMasking() { this->ProcessImage(true); } void QmitkImageCropperView::OnSliderValueChanged(int slidervalue) { m_CropOutsideValue = slidervalue; } void QmitkImageCropperView::CreateBoundingShapeInteractor(bool rotationEnabled) { if (m_BoundingShapeInteractor.IsNull()) { m_BoundingShapeInteractor = mitk::BoundingShapeInteractor::New(); m_BoundingShapeInteractor->LoadStateMachine("BoundingShapeInteraction.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); m_BoundingShapeInteractor->SetEventConfig("BoundingShapeMouseConfig.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); } m_BoundingShapeInteractor->SetRotationEnabled(rotationEnabled); } mitk::Geometry3D::Pointer QmitkImageCropperView::InitializeWithImageGeometry(const mitk::BaseGeometry* geometry) const { // convert a BaseGeometry into a Geometry3D (otherwise IO is not working properly) if (geometry == nullptr) mitkThrow() << "Geometry is not valid."; auto boundingGeometry = mitk::Geometry3D::New(); boundingGeometry->SetBounds(geometry->GetBounds()); boundingGeometry->SetImageGeometry(geometry->GetImageGeometry()); boundingGeometry->SetOrigin(geometry->GetOrigin()); boundingGeometry->SetSpacing(geometry->GetSpacing()); boundingGeometry->SetIndexToWorldTransform(geometry->GetIndexToWorldTransform()->Clone()); boundingGeometry->Modified(); return boundingGeometry; } void QmitkImageCropperView::ProcessImage(bool mask) { auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const auto timePoint = renderWindowPart->GetSelectedTimePoint(); auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); return; } auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select a cropping object before starting image processing."); return; } if (!imageNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) { QMessageBox::information(nullptr, "Warning", "Please select a time point that is within the time bounds of the selected image."); return; } const auto timeStep = imageNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); auto image = dynamic_cast(imageNode->GetData()); auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != image && nullptr != boundingBox) { QString imageName; if (mask) { imageName = QString::fromStdString(imageNode->GetName() + "_" + boundingBoxNode->GetName() + "_masked"); } else { imageName = QString::fromStdString(imageNode->GetName() + "_" + boundingBoxNode->GetName() + "_cropped"); } if (m_Controls.checkBoxCropTimeStepOnly->isChecked()) { imageName = imageName + "_T" + QString::number(timeStep); } // image and bounding shape ok, set as input auto croppedImageNode = mitk::DataNode::New(); auto cutter = mitk::BoundingShapeCropper::New(); cutter->SetGeometry(boundingBox); // adjustable in advanced settings cutter->SetUseWholeInputRegion(mask); //either mask (mask=true) or crop (mask=false) cutter->SetOutsideValue(m_CropOutsideValue); cutter->SetUseCropTimeStepOnly(m_Controls.checkBoxCropTimeStepOnly->isChecked()); cutter->SetCurrentTimeStep(timeStep); // TODO: Add support for MultiLayer (right now only Mulitlabel support) auto labelsetImageInput = dynamic_cast(image); if (nullptr != labelsetImageInput) { cutter->SetInput(labelsetImageInput); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } auto labelSetImage = mitk::LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(cutter->GetOutput()); for (unsigned int i = 0; i < labelsetImageInput->GetNumberOfLayers(); i++) { labelSetImage->AddLabelSetToLayer(i, labelsetImageInput->GetLabelSet(i)); } croppedImageNode->SetData(labelSetImage); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { imageNode->SetData(labelSetImage); imageNode->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } else { cutter->SetInput(image); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok, QMessageBox::NoButton, QMessageBox::NoButton); return; } //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { croppedImageNode->SetData(cutter->GetOutput()); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); croppedImageNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); croppedImageNode->SetProperty("layer", mitk::IntProperty::New(99)); // arbitrary, copied from segmentation functionality if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { imageNode->SetData(cutter->GetOutput()); imageNode->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } } else { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); } } void QmitkImageCropperView::SetDefaultGUI() { m_Controls.labelWarningRotation->setVisible(false); m_Controls.buttonCreateNewBoundingBox->setEnabled(false); m_Controls.buttonCropping->setEnabled(false); m_Controls.buttonMasking->setEnabled(false); m_Controls.buttonAdvancedSettings->setEnabled(false); m_Controls.groupImageSettings->setEnabled(false); m_Controls.groupImageSettings->setVisible(false); m_Controls.checkOverwriteImage->setChecked(false); m_Controls.checkBoxCropTimeStepOnly->setChecked(false); } QString QmitkImageCropperView::AdaptBoundingObjectName(const QString& name) const { unsigned int counter = 2; QString newName = QString("%1 %2").arg(name).arg(counter); while (nullptr != this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&newName](const mitk::DataNode *node) { return 0 == node->GetName().compare(newName.toStdString()); }))) { newName = QString("%1 %2").arg(name).arg(++counter); } return newName; } diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurement.dox b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurement.dox index e7ea6bd729..b71b4bc844 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurement.dox +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurement.dox @@ -1,126 +1,90 @@ /** \page org_mitk_views_measurement The Measurement View \imageMacro{measurement.svg,"Icon of the Measurement View",2.00} -\section QmitkMeasurementUserManualOverview Overview +

Overview

-The Measurement view enables the user to interact with 2D images or single slices of 3D image stacks and planar figure data types. It allows to measure distances, angels, pathes and several geometric figures on a dataset. +The Measurement view allows you to measure distances, angels, paths and several geometric figures on 2D images or image slices of 3D and 4D images. The measurement view is repeatedly useable with the same or different measurement figures, which are correlated to the chosen image and can be saved together with it for future use. -\tableofcontents -The workflow to use this view is: +\imageMacro{QmitkMeasurementToolbox_BasicScreenEdited.jpg,"Image with measurements",16.00} -\imageMacro{QmitkMeasurementToolbox_Workflow.png,"",16.00} -The workflow is repeatedly useable with the same or different measurement figures, which are correlated to the choosen image and can be saved together with it for future use. On pressing the Measurement icon (see picture below the page title) in the view button line the basic appearance of the view is as follws. +

Usage

-\imageMacro{QmitkMeasurementToolbox_BasicScreenEdited.jpg,"",16.00} +The first step to perform measurements is to select a reference image on top of the view. All resulting measurements will be assigned as child nodes of the image in the data manager. The standard working plane is 'Axial' but the other standard view planes ('Saggital' and 'Coronal') are also valid for measurements. -The standard working plane is "Axial" but the other standard viewplanes ("Saggital" and "Coronal") are also valid for measurements. To swap between the view planes refer to the application user manual. -\section QmitkMeasurementUserManualFeatures Features +\imageMacro{QmitkMeasurementToolbox_MeasurementView.jpg,"Measurement View",7.60} -The view as it is depicted below offers the following features in the order of apperance on the image from top to bottom: +After selecting an image, you can click on any of the geometrical symbols. The respective measurement will appear as soon as you click on the image location you want to measure. -\imageMacro{QmitkMeasurementToolbox_MeasurementView.jpg,"",7.60} +The view offers a variety of measurement options that are introduced in the following: -The first information is the selected image's name (here: DICOM-MRI-Image) followed by the measurement figures button line with the seven measurement figures. From left to right the buttons are connected with the following functions: +
    +
  • Line: -\subsection SubOne Draw Line Draws a line between two set points and returns the distance between these points. -\subsection SubTwo Draw Path -Draws a path between several set points (two and more) and calculates the circumference, that is all line's length summed up. Add the final point by double left click. +
  • Path: -\subsection SubThree Draw Angle -Draws two lines from three set points connected in the second set point and returns the inner angle at the second point. +Draws a line between several set points (two and more) and calculates the overall length, which is all line's length summed up. Add the final point by double left-click. -\subsection SubFour Draw Four Point Angle -Draws two lines that may but must not intersect from four set points. The returned angle is the one depicted in the icon. +
  • Angle: -\subsection SubFive Draw Circle -Draws a circle by setting two points, whereas the first set point is the center and the second the radius of the circle. The measured values are the radius and the included area. +Draws two lines from three set points connected in the second set point and return the inner angle at the second point. -\subsection SubSix Draw Rectangle -Draws a rectangle by setting two points at the opposing edges of the rectangle starting with the upper left edge. The measured values are the circumference and the included area. +
  • Four Point / Double Angle: -\subsection SubSeven Draw Polygon -Draws a polygon by setting three or more points. The measured values are the circumference and the included area. Add the final point by double left click. +Draws two lines that may but must not intersect from four set points. The returned angle is the one depicted in the icon. -Below the buttonline the statistics window is situated, it displays the results of the actual measurements from the selected measurement figures. The content of the statistics window can be copied to the clipboard with the correspondig button for further use in a table calculation programm (e.g. Open Office Calc etc.). +
  • Circle: -\imageMacro{QmitkMeasurementToolbox_ImageProcessed.jpg,"",7.56} +Draws a circle by setting two points, whereas the first set point is the center and the second the radius of the circle. The measured values are the radius and the included area. -The last row contains again a button line to swap from the measurement perspective (activated in the image) to other supported MITK perspectives. +
  • Ellipse: -\section QmitkMeasurementUserManualUsage Usage +Draws an ellipse that can be modified by three points. The middle point can be used to move the whole measurement. The lower point modifies the ellipse axes. The right point can be used to modify the radius of the ellipse. The measured values are the major and minor axes and the area. -This Section is subdivided into four subsections: -
      -
    1. Add an image -
    2. Work with measurement figures -
    3. Save the image with measurement information -
    4. Remove measurement figures or image -
    +
  • Double Ellipse: -Let's start with subsection 1 -\subsection One Add an image +Draws two ellipses by adjusting four points. The middle point can be used to move the whole measurement. The left point is used to adjust the distance between the two ellipses. The lower point modifies the ellipse axes. The right point can be used to modify the radius of the ellipse. Can be used for measuring e.g. the wall thickness of a vessel. The measured values are the major and minor axes and the thickness. -There are two possible ways to add an image to the programm. One is to grap the image with left mouse click from your prefered file browser and simply drag&drop it to the View Plane field. The other way is to use the -\imageMacro{QmitkMeasurementToolbox_OpenButton.png,"",2.01} -button in the upper left corner of the application. A dialog window appears showing the file tree of the computer. Navigate to the wanted file and select it with the left mouse click. Afterwards just use the dialog's open button. +
  • Rectangle: -The wanted image appears in the View Plane and in the Data Manager the images name appears as a new tree node. Now the image is loaded it can be adjusted in the usual way ( zoom in/out: right mouse button + moving the mouse up and down, moving the image: press mouse wheel and move the mouse to the wished direction, scroll through the slices( only on 3D images): scroll mouse wheel up and down). +Draws a rectangle by setting two points at the opposing edges of the rectangle starting with the upper left edge. The measured values are the circumference and the included area. -\imageMacro{QmitkMeasurementToolbox_ImageLoadedScreen.jpg,"",16.00} +
  • Polygon: -After the image is loaded the image's name appears in the Data Manager. By left-clicking on the image name the buttonline becomes activated. +Draws a closed polygon that can have an arbitrary number of points. New points are added by left mouse-click. To finish the drawing the user can double click on the last control point. The measured values are circumference and area. -\subsection Two Work with measurement figures -The measurement view comes with seven measurement figures(see picture below), that can be applied to the images. +
  • Bezier curve: -\imageMacro{QmitkMeasurementToolbox_MeasurementFigureButtonLine.jpg,"",7.22} +Draws a bezier curve by adding some control points with left mouse-click. To finish the drawing the user can double-click on the last control point. The measured value is the length of the bezier curve. -The results of the measurement with each of these figures is shown in the statistics window and in the lower right corner of the view plane. +
  • Subdivision Polygon: -\imageMacro{QmitkMeasurementToolbox_ImageProcessedScreen.jpg,"",6.96} +Draws a closed subdivision polygon by adding some control points with left mouse-click. To finish the drawing the user can double-click on the last control point. The measured value is the circumference of the polygon. +
-When applying more then one measurement figure to the image the actual measurement figure is depicted in red and the displayed values belong to this measurement figure. All measurement figures become part of the Data Manager as a node of the image tree. +

Fixed sizes of measurement figures

+The measurement view offers a fixed size for circle and double ellipses to preset a radius and a thickness. This is useful e.g. for disagnostic studies where you want to derive gray value statistics from a well defined region. -\subsection Three Save the image with measurement information -After applying the wanted measurement figures the entire scene consisting of the image and the measurement figures can be saved for future use. Therefore just click the right mouse button when over the image item in the Data Manager and choose the item "Save" in the opening item list. Following to that a save dialog appears where the path to the save folder can be set. Afterwards just accept your choice with the save button. +

Modify measurements

-\subsection Four Remove measurement figures or image -If the single measurement figures or the image is not needed any longer, it can be removed solely or as an entire group. The image can't be removed without simultaneously removing all the dependent measurement figures that belong to the image tree in the Data Manager. To remove just select the wanted items in the data manager list by left-click on it or if several items wanted to be removed left click on all wanted by simultaneously holding the ctrl-button pressed. +All measurements can be modified later on by moving the respective control points. Note that they can only be modified if the view is open. -For more detailed usage of the save/remove functionality refer to the Data Manager User Manual. +

Multiple measurement figures

-",16.00} +

Save the measurement information

+The entire scene containing the image and the measurement figures can be saved for future use. Scenes are saved with a '.mitk' extension by pressing 'Save Project' and contain all nodes and relevant information. Alternatively, you can just save the measurement solely (with file extension '.pf') by right-click on the node in the data manager. -*/ +The content of the measurement widget can be copied to the clipboard with the corresponding button for further use in a table calculation program (e.g. Open Office Calc etc.). +

Remove measurement figures or image

+If the single measurement figures or the image is not needed any longer, it can be removed solely or as an entire group. The image can't be removed without simultaneously removing all the dependent measurement figures that belong to the image tree in the data manager. To remove just select the wanted items in the data manager list by left-clicking on the respective node or, if several items wanted to be removed, left-click on all wanted by simultaneously holding the ctrl-button pressed. +*/ \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox.dox b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox.dox index 811c479e3e..c3f1ed2e58 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox.dox +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox.dox @@ -1,12 +1,12 @@ /** \page org_mitk_gui_qt_measurementtoolbox The Measurement Toolbox -\section QmitkmeasurementToolbox Manual +

QmitkmeasurementToolbox Manual

This plugin contains all views that provide measurement and statistics functionality.
  • \subpage org_mitk_views_measurement
  • \subpage org_mitk_views_imagestatistics
*/ \ No newline at end of file diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_BasicScreenEdited.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_BasicScreenEdited.jpg index 13446ab25f..708aa039d1 100644 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_BasicScreenEdited.jpg and b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_BasicScreenEdited.jpg differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageLoadedScreen.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageLoadedScreen.jpg deleted file mode 100644 index 71abc0236e..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageLoadedScreen.jpg and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessed.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessed.jpg deleted file mode 100644 index 88514a9d30..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessed.jpg and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessedScreen.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessedScreen.jpg deleted file mode 100644 index 01c5de3526..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_ImageProcessedScreen.jpg and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementFigureButtonLine.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementFigureButtonLine.jpg deleted file mode 100644 index 02dc8951f8..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementFigureButtonLine.jpg and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementGUI.png b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementGUI.png deleted file mode 100644 index d80789bf41..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementGUI.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementView.jpg b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementView.jpg index 12b94152bd..db04c46943 100644 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementView.jpg and b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_MeasurementView.jpg differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_OpenButton.png b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_OpenButton.png deleted file mode 100644 index 1ec53342b1..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_OpenButton.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_Workflow.png b/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_Workflow.png deleted file mode 100644 index 32fbfc5cf2..0000000000 Binary files a/Plugins/org.mitk.gui.qt.measurementtoolbox/documentation/UserManual/QmitkMeasurementToolbox_Workflow.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp index f471a8a318..2954c3715c 100644 --- a/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp +++ b/Plugins/org.mitk.gui.qt.measurementtoolbox/src/internal/QmitkMeasurementView.cpp @@ -1,871 +1,872 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkMeasurementView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ctkDoubleSpinBox.h" #include "mitkPluginActivator.h" #include "usModuleRegistry.h" #include "mitkInteractionEventObserver.h" #include "mitkDisplayInteractor.h" #include "usGetModuleContext.h" #include "usModuleContext.h" #include US_INITIALIZE_MODULE struct QmitkPlanarFigureData { QmitkPlanarFigureData() : m_EndPlacementObserverTag(0), m_SelectObserverTag(0), m_StartInteractionObserverTag(0), m_EndInteractionObserverTag(0) { } mitk::PlanarFigure::Pointer m_Figure; unsigned int m_EndPlacementObserverTag; unsigned int m_SelectObserverTag; unsigned int m_StartInteractionObserverTag; unsigned int m_EndInteractionObserverTag; }; struct QmitkMeasurementViewData { QmitkMeasurementViewData() : m_LineCounter(0), m_PathCounter(0), m_AngleCounter(0), m_FourPointAngleCounter(0), m_CircleCounter(0), m_EllipseCounter(0), m_DoubleEllipseCounter(0), m_RectangleCounter(0), m_PolygonCounter(0), m_BezierCurveCounter(0), m_SubdivisionPolygonCounter(0), m_UnintializedPlanarFigure(false), m_ScrollEnabled(true), m_Parent(nullptr), m_SingleNodeSelectionWidget(nullptr), m_DrawLine(nullptr), m_DrawPath(nullptr), m_DrawAngle(nullptr), m_DrawFourPointAngle(nullptr), m_DrawRectangle(nullptr), m_DrawPolygon(nullptr), m_DrawCircle(nullptr), m_DrawEllipse(nullptr), m_DrawDoubleEllipse(nullptr), m_DrawBezierCurve(nullptr), m_DrawSubdivisionPolygon(nullptr), m_DrawActionsToolBar(nullptr), m_DrawActionsGroup(nullptr), m_SelectedPlanarFiguresText(nullptr), m_CopyToClipboard(nullptr), m_Layout(nullptr), m_Radius(nullptr), m_Thickness(nullptr), m_FixedParameterBox(nullptr) { } unsigned int m_LineCounter; unsigned int m_PathCounter; unsigned int m_AngleCounter; unsigned int m_FourPointAngleCounter; unsigned int m_CircleCounter; unsigned int m_EllipseCounter; unsigned int m_DoubleEllipseCounter; unsigned int m_RectangleCounter; unsigned int m_PolygonCounter; unsigned int m_BezierCurveCounter; unsigned int m_SubdivisionPolygonCounter; QList m_CurrentSelection; std::map m_DataNodeToPlanarFigureData; mitk::DataNode::Pointer m_SelectedImageNode; bool m_UnintializedPlanarFigure; bool m_ScrollEnabled; QWidget* m_Parent; QmitkSingleNodeSelectionWidget* m_SingleNodeSelectionWidget; QAction* m_DrawLine; QAction* m_DrawPath; QAction* m_DrawAngle; QAction* m_DrawFourPointAngle; QAction* m_DrawRectangle; QAction* m_DrawPolygon; QAction* m_DrawCircle; QAction* m_DrawEllipse; QAction* m_DrawDoubleEllipse; QAction* m_DrawBezierCurve; QAction* m_DrawSubdivisionPolygon; QToolBar* m_DrawActionsToolBar; QActionGroup* m_DrawActionsGroup; QTextBrowser* m_SelectedPlanarFiguresText; QPushButton* m_CopyToClipboard; QGridLayout* m_Layout; ctkDoubleSpinBox* m_Radius; ctkDoubleSpinBox* m_Thickness; QGroupBox* m_FixedParameterBox; }; const std::string QmitkMeasurementView::VIEW_ID = "org.mitk.views.measurement"; QmitkMeasurementView::QmitkMeasurementView() : d(new QmitkMeasurementViewData) { } QmitkMeasurementView::~QmitkMeasurementView() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeRemoved(it.Value()); delete d; } void QmitkMeasurementView::CreateQtPartControl(QWidget* parent) { d->m_Parent = parent; d->m_SingleNodeSelectionWidget = new QmitkSingleNodeSelectionWidget(); d->m_SingleNodeSelectionWidget->SetDataStorage(GetDataStorage()); d->m_SingleNodeSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); d->m_SingleNodeSelectionWidget->SetSelectionIsOptional(true); + d->m_SingleNodeSelectionWidget->SetAutoSelectNewNodes(true); d->m_SingleNodeSelectionWidget->SetEmptyInfo(QStringLiteral("Please select a reference image")); d->m_SingleNodeSelectionWidget->SetPopUpTitel(QStringLiteral("Select a reference image")); d->m_DrawActionsToolBar = new QToolBar; d->m_DrawActionsGroup = new QActionGroup(this); d->m_DrawActionsGroup->setExclusive(true); auto* currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/line.png"), tr("Draw Line")); currentAction->setCheckable(true); d->m_DrawLine = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/path.png"), tr("Draw Path")); currentAction->setCheckable(true); d->m_DrawPath = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/angle.png"), tr("Draw Angle")); currentAction->setCheckable(true); d->m_DrawAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/four-point-angle.png"), tr("Draw Four Point Angle")); currentAction->setCheckable(true); d->m_DrawFourPointAngle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/circle.png"), tr("Draw Circle")); currentAction->setCheckable(true); d->m_DrawCircle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/ellipse.png"), tr("Draw Ellipse")); currentAction->setCheckable(true); d->m_DrawEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/doubleellipse.png"), tr("Draw Double Ellipse")); currentAction->setCheckable(true); d->m_DrawDoubleEllipse = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/rectangle.png"), tr("Draw Rectangle")); currentAction->setCheckable(true); d->m_DrawRectangle = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/polygon.png"), tr("Draw Polygon")); currentAction->setCheckable(true); d->m_DrawPolygon = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/beziercurve.png"), tr("Draw Bezier Curve")); currentAction->setCheckable(true); d->m_DrawBezierCurve = currentAction; currentAction = d->m_DrawActionsToolBar->addAction(QIcon(":/measurement/subdivisionpolygon.png"), tr("Draw Subdivision Polygon")); currentAction->setCheckable(true); d->m_DrawSubdivisionPolygon = currentAction; d->m_DrawActionsToolBar->setEnabled(false); // fixed parameter section auto fixedLayout = new QGridLayout(); d->m_FixedParameterBox = new QGroupBox(); d->m_FixedParameterBox->setCheckable(true); d->m_FixedParameterBox->setChecked(false); d->m_FixedParameterBox->setTitle("Fixed sized circle/double ellipse"); d->m_FixedParameterBox->setToolTip("If activated, circles and double ellipses (as rings) figures will always be created with the set parameters as fixed size."); d->m_FixedParameterBox->setAlignment(Qt::AlignLeft); auto labelRadius1 = new QLabel(QString("Radius")); d->m_Radius = new ctkDoubleSpinBox(); d->m_Radius->setMinimum(0); d->m_Radius->setValue(10); d->m_Radius->setSuffix(" mm"); d->m_Radius->setAlignment(Qt::AlignLeft); d->m_Radius->setToolTip("Sets the radius for following planar figures: circle, double ellipse (as ring)."); auto labelThickness = new QLabel(QString("Thickness")); d->m_Thickness = new ctkDoubleSpinBox(); d->m_Thickness->setMinimum(0); d->m_Thickness->setMaximum(10); d->m_Thickness->setValue(5); d->m_Thickness->setSuffix(" mm"); d->m_Thickness->setAlignment(Qt::AlignLeft); d->m_Thickness->setToolTip("Sets the thickness for following planar figures: double ellipse (as ring)."); fixedLayout->addWidget(labelRadius1,0,0); fixedLayout->addWidget(d->m_Radius,0,1); fixedLayout->addWidget(labelThickness,1,0); fixedLayout->addWidget(d->m_Thickness,1,1); d->m_FixedParameterBox->setLayout(fixedLayout); // planar figure details text d->m_SelectedPlanarFiguresText = new QTextBrowser; // copy to clipboard button d->m_CopyToClipboard = new QPushButton(tr("Copy to Clipboard")); d->m_Layout = new QGridLayout; d->m_Layout->addWidget(d->m_SingleNodeSelectionWidget, 0, 0, 1, 2); d->m_Layout->addWidget(d->m_DrawActionsToolBar, 1, 0, 1, 2); d->m_Layout->addWidget(d->m_FixedParameterBox, 2, 0, 1, 2); d->m_Layout->addWidget(d->m_SelectedPlanarFiguresText, 3, 0, 1, 2); d->m_Layout->addWidget(d->m_CopyToClipboard, 4, 0, 1, 2); d->m_Parent->setLayout(d->m_Layout); this->CreateConnections(); this->AddAllInteractors(); } void QmitkMeasurementView::CreateConnections() { connect(d->m_SingleNodeSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkMeasurementView::OnCurrentSelectionChanged); connect(d->m_DrawLine, SIGNAL(triggered(bool)), this, SLOT(OnDrawLineTriggered(bool))); connect(d->m_DrawPath, SIGNAL(triggered(bool)), this, SLOT(OnDrawPathTriggered(bool))); connect(d->m_DrawAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawAngleTriggered(bool))); connect(d->m_DrawFourPointAngle, SIGNAL(triggered(bool)), this, SLOT(OnDrawFourPointAngleTriggered(bool))); connect(d->m_DrawCircle, SIGNAL(triggered(bool)), this, SLOT(OnDrawCircleTriggered(bool))); connect(d->m_DrawEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawEllipseTriggered(bool))); connect(d->m_DrawDoubleEllipse, SIGNAL(triggered(bool)), this, SLOT(OnDrawDoubleEllipseTriggered(bool))); connect(d->m_DrawRectangle, SIGNAL(triggered(bool)), this, SLOT(OnDrawRectangleTriggered(bool))); connect(d->m_DrawPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawPolygonTriggered(bool))); connect(d->m_DrawBezierCurve, SIGNAL(triggered(bool)), this, SLOT(OnDrawBezierCurveTriggered(bool))); connect(d->m_DrawSubdivisionPolygon, SIGNAL(triggered(bool)), this, SLOT(OnDrawSubdivisionPolygonTriggered(bool))); connect(d->m_CopyToClipboard, SIGNAL(clicked(bool)), this, SLOT(OnCopyToClipboard(bool))); connect(d->m_Radius, QOverload::of(&ctkDoubleSpinBox::valueChanged), d->m_Thickness, &ctkDoubleSpinBox::setMaximum); } void QmitkMeasurementView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { d->m_SelectedImageNode = nullptr; d->m_DrawActionsToolBar->setEnabled(false); } else { d->m_SelectedImageNode = nodes.front(); d->m_DrawActionsToolBar->setEnabled(true); } } void QmitkMeasurementView::NodeAdded(const mitk::DataNode* node) { // add observer for selection in renderwindow mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); auto isPositionMarker = false; node->GetBoolProperty("isContourMarker", isPositionMarker); if (planarFigure.IsNotNull() && !isPositionMarker) { auto nonConstNode = const_cast(node); mitk::PlanarFigureInteractor::Pointer interactor = dynamic_cast(node->GetDataInteractor().GetPointer()); if (interactor.IsNull()) { interactor = mitk::PlanarFigureInteractor::New(); auto planarFigureModule = us::ModuleRegistry::GetModule("MitkPlanarFigure"); interactor->LoadStateMachine("PlanarFigureInteraction.xml", planarFigureModule); interactor->SetEventConfig("PlanarFigureConfig.xml", planarFigureModule); } interactor->SetDataNode(nonConstNode); QmitkPlanarFigureData data; data.m_Figure = planarFigure; typedef itk::SimpleMemberCommand SimpleCommandType; typedef itk::MemberCommand MemberCommandType; // add observer for event when figure has been placed auto initializationCommand = SimpleCommandType::New(); initializationCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureInitialized); data.m_EndPlacementObserverTag = planarFigure->AddObserver(mitk::EndPlacementPlanarFigureEvent(), initializationCommand); // add observer for event when figure is picked (selected) auto selectCommand = MemberCommandType::New(); selectCommand->SetCallbackFunction(this, &QmitkMeasurementView::PlanarFigureSelected); data.m_SelectObserverTag = planarFigure->AddObserver(mitk::SelectPlanarFigureEvent(), selectCommand); // add observer for event when interaction with figure starts auto startInteractionCommand = SimpleCommandType::New(); startInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::DisableCrosshairNavigation); data.m_StartInteractionObserverTag = planarFigure->AddObserver(mitk::StartInteractionPlanarFigureEvent(), startInteractionCommand); // add observer for event when interaction with figure starts auto endInteractionCommand = SimpleCommandType::New(); endInteractionCommand->SetCallbackFunction(this, &QmitkMeasurementView::EnableCrosshairNavigation); data.m_EndInteractionObserverTag = planarFigure->AddObserver(mitk::EndInteractionPlanarFigureEvent(), endInteractionCommand); // adding to the map of tracked planarfigures d->m_DataNodeToPlanarFigureData[nonConstNode] = data; } } void QmitkMeasurementView::NodeChanged(const mitk::DataNode* node) { auto it = std::find(d->m_CurrentSelection.begin(), d->m_CurrentSelection.end(), node); if (it != d->m_CurrentSelection.end()) { this->UpdateMeasurementText(); } } void QmitkMeasurementView::NodeRemoved(const mitk::DataNode* node) { auto nonConstNode = const_cast(node); auto it = d->m_DataNodeToPlanarFigureData.find(nonConstNode); auto isFigureFinished = false; auto isPlaced = false; if (it != d->m_DataNodeToPlanarFigureData.end()) { QmitkPlanarFigureData& data = it->second; data.m_Figure->RemoveObserver(data.m_EndPlacementObserverTag); data.m_Figure->RemoveObserver(data.m_SelectObserverTag); data.m_Figure->RemoveObserver(data.m_StartInteractionObserverTag); data.m_Figure->RemoveObserver(data.m_EndInteractionObserverTag); isFigureFinished = data.m_Figure->GetPropertyList()->GetBoolProperty("initiallyplaced", isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons d->m_DataNodeToPlanarFigureData.erase( it ); } if (nonConstNode != nullptr) nonConstNode->SetDataInteractor(nullptr); auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto nodes = this->GetDataStorage()->GetDerivations(node, isPlanarFigure); for (unsigned int x = 0; x < nodes->size(); ++x) { mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(nodes->at(x)->GetData()); if (planarFigure.IsNotNull()) { isFigureFinished = planarFigure->GetPropertyList()->GetBoolProperty("initiallyplaced",isPlaced); if (!isFigureFinished) // if the property does not yet exist or is false, drop the datanode { this->GetDataStorage()->Remove(nodes->at(x)); if (!d->m_DataNodeToPlanarFigureData.empty()) { it = d->m_DataNodeToPlanarFigureData.find(nodes->at(x)); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_DataNodeToPlanarFigureData.erase(it); this->PlanarFigureInitialized(); // normally called when a figure is finished, to reset all buttons this->EnableCrosshairNavigation(); } } } } } } void QmitkMeasurementView::PlanarFigureSelected(itk::Object* object, const itk::EventObject&) { d->m_CurrentSelection.clear(); auto lambda = [&object](const std::pair& element) { return element.second.m_Figure == object; }; auto it = std::find_if(d->m_DataNodeToPlanarFigureData.begin(), d->m_DataNodeToPlanarFigureData.end(), lambda); if (it != d->m_DataNodeToPlanarFigureData.end()) { d->m_CurrentSelection.push_back(it->first); } this->UpdateMeasurementText(); this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::PlanarFigureInitialized() { d->m_UnintializedPlanarFigure = false; d->m_DrawActionsToolBar->setEnabled(true); d->m_DrawLine->setChecked(false); d->m_DrawPath->setChecked(false); d->m_DrawAngle->setChecked(false); d->m_DrawFourPointAngle->setChecked(false); d->m_DrawCircle->setChecked(false); d->m_DrawEllipse->setChecked(false); d->m_DrawDoubleEllipse->setChecked(false); d->m_DrawRectangle->setChecked(false); d->m_DrawPolygon->setChecked(false); d->m_DrawBezierCurve->setChecked(false); d->m_DrawSubdivisionPolygon->setChecked(false); } void QmitkMeasurementView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList& nodes) { d->m_CurrentSelection = nodes; this->UpdateMeasurementText(); // bug 16600: deselecting all planarfigures by clicking on datamanager when no node is selected if (d->m_CurrentSelection.size() == 0) { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto planarFigures = this->GetDataStorage()->GetSubset(isPlanarFigure); // setting all planar figures which are not helper objects not selected for (mitk::DataStorage::SetOfObjects::ConstIterator it = planarFigures->Begin(); it != planarFigures->End(); ++it) { auto node = it.Value(); auto isHelperObject = false; node->GetBoolProperty("helper object", isHelperObject); if (!isHelperObject) node->SetSelected(false); } } for (int i = d->m_CurrentSelection.size() - 1; i >= 0; --i) { auto node = d->m_CurrentSelection[i]; mitk::PlanarFigure::Pointer planarFigure = dynamic_cast(node->GetData()); // the last selected planar figure if (planarFigure.IsNotNull() && planarFigure->GetPlaneGeometry()) { auto planarFigureInitializedWindow = false; auto linkedRenderWindow = dynamic_cast(this->GetRenderWindowPart()); QmitkRenderWindow* selectedRenderWindow; if (!linkedRenderWindow) return; auto axialRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("axial"); auto sagittalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("sagittal"); auto coronalRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("coronal"); auto threeDimRenderWindow = linkedRenderWindow->GetQmitkRenderWindow("3d"); if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } else if (node->GetBoolProperty("planarFigureInitializedWindow", planarFigureInitializedWindow, threeDimRenderWindow->GetRenderer())) { selectedRenderWindow = threeDimRenderWindow; } else { selectedRenderWindow = nullptr; } auto planeGeometry = dynamic_cast(planarFigure->GetPlaneGeometry()); auto normal = planeGeometry->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer axialPlane = axialRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto axialNormal = axialPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer sagittalPlane = sagittalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto sagittalNormal = sagittalPlane->GetNormalVnl(); mitk::PlaneGeometry::ConstPointer coronalPlane = coronalRenderWindow->GetRenderer()->GetCurrentWorldPlaneGeometry(); auto coronalNormal = coronalPlane->GetNormalVnl(); normal[0] = fabs(normal[0]); normal[1] = fabs(normal[1]); normal[2] = fabs(normal[2]); axialNormal[0] = fabs(axialNormal[0]); axialNormal[1] = fabs(axialNormal[1]); axialNormal[2] = fabs(axialNormal[2]); sagittalNormal[0] = fabs(sagittalNormal[0]); sagittalNormal[1] = fabs(sagittalNormal[1]); sagittalNormal[2] = fabs(sagittalNormal[2]); coronalNormal[0] = fabs(coronalNormal[0]); coronalNormal[1] = fabs(coronalNormal[1]); coronalNormal[2] = fabs(coronalNormal[2]); auto ang1 = angle(normal, axialNormal); auto ang2 = angle(normal, sagittalNormal); auto ang3 = angle(normal, coronalNormal); if (ang1 < ang2 && ang1 < ang3) { selectedRenderWindow = axialRenderWindow; } else { if (ang2 < ang3) { selectedRenderWindow = sagittalRenderWindow; } else { selectedRenderWindow = coronalRenderWindow; } } // re-orient view if (selectedRenderWindow) selectedRenderWindow->GetSliceNavigationController()->ReorientSlices(planeGeometry->GetOrigin(), planeGeometry->GetNormal()); } break; } this->RequestRenderWindowUpdate(); } void QmitkMeasurementView::OnDrawLineTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarLine::New(), QString("Line%1").arg(++d->m_LineCounter)); } void QmitkMeasurementView::OnDrawPathTriggered(bool) { mitk::CoreServicePointer propertyFilters(mitk::CoreServices::GetPropertyFilters()); mitk::PropertyFilter filter; filter.AddEntry("ClosedPlanarPolygon", mitk::PropertyFilter::Blacklist); propertyFilters->AddFilter(filter, "PlanarPolygon"); mitk::PlanarPolygon::Pointer planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOff(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Path%1").arg(++d->m_PathCounter)); node->SetProperty("ClosedPlanarPolygon", mitk::BoolProperty::New(false)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnDrawAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarAngle::New(), QString("Angle%1").arg(++d->m_AngleCounter)); } void QmitkMeasurementView::OnDrawFourPointAngleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarFourPointAngle::New(), QString("Four Point Angle%1").arg(++d->m_FourPointAngleCounter)); } void QmitkMeasurementView::OnDrawCircleTriggered(bool) { auto circle = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarCircle::New(d->m_Radius->value()) : mitk::PlanarCircle::New(); this->AddFigureToDataStorage(circle, QString("Circle%1").arg(++d->m_CircleCounter)); } void QmitkMeasurementView::OnDrawEllipseTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarEllipse::New(), QString("Ellipse%1").arg(++d->m_EllipseCounter)); } void QmitkMeasurementView::OnDrawDoubleEllipseTriggered(bool) { auto ellipse = (d->m_FixedParameterBox->isChecked()) ? mitk::PlanarDoubleEllipse::New(d->m_Radius->value(),d->m_Thickness->value()) : mitk::PlanarDoubleEllipse::New(); this->AddFigureToDataStorage(ellipse, QString("DoubleEllipse%1").arg(++d->m_DoubleEllipseCounter)); } void QmitkMeasurementView::OnDrawBezierCurveTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarBezierCurve::New(), QString("BezierCurve%1").arg(++d->m_BezierCurveCounter)); } void QmitkMeasurementView::OnDrawSubdivisionPolygonTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarSubdivisionPolygon::New(), QString("SubdivisionPolygon%1").arg(++d->m_SubdivisionPolygonCounter)); } void QmitkMeasurementView::OnDrawRectangleTriggered(bool) { this->AddFigureToDataStorage( mitk::PlanarRectangle::New(), QString("Rectangle%1").arg(++d->m_RectangleCounter)); } void QmitkMeasurementView::OnDrawPolygonTriggered(bool) { auto planarFigure = mitk::PlanarPolygon::New(); planarFigure->ClosedOn(); auto node = this->AddFigureToDataStorage( planarFigure, QString("Polygon%1").arg(++d->m_PolygonCounter)); node->SetProperty("planarfigure.isextendable", mitk::BoolProperty::New(true)); } void QmitkMeasurementView::OnCopyToClipboard(bool) { QApplication::clipboard()->setText(d->m_SelectedPlanarFiguresText->toPlainText(), QClipboard::Clipboard); } mitk::DataNode::Pointer QmitkMeasurementView::AddFigureToDataStorage(mitk::PlanarFigure* figure, const QString& name) { auto newNode = mitk::DataNode::New(); newNode->SetName(name.toStdString()); newNode->SetData(figure); newNode->SetSelected(true); if (d->m_SelectedImageNode.IsNotNull()) { this->GetDataStorage()->Add(newNode, d->m_SelectedImageNode); } else { this->GetDataStorage()->Add(newNode); } for (auto &node : d->m_CurrentSelection) node->SetSelected(false); d->m_CurrentSelection.clear(); d->m_CurrentSelection.push_back(newNode); this->UpdateMeasurementText(); this->DisableCrosshairNavigation(); d->m_DrawActionsToolBar->setEnabled(false); d->m_UnintializedPlanarFigure = true; return newNode; } void QmitkMeasurementView::UpdateMeasurementText() { d->m_SelectedPlanarFiguresText->clear(); QString infoText; QString plainInfoText; int j = 1; mitk::PlanarFigure::Pointer planarFigure; mitk::PlanarAngle::Pointer planarAngle; mitk::PlanarFourPointAngle::Pointer planarFourPointAngle; mitk::DataNode::Pointer node; for (int i = 0; i < d->m_CurrentSelection.size(); ++i, ++j) { plainInfoText.clear(); node = d->m_CurrentSelection[i]; planarFigure = dynamic_cast(node->GetData()); if (planarFigure.IsNull()) continue; if (j > 1) infoText.append("
"); infoText.append(QString("%1
").arg(QString::fromStdString(node->GetName()))); plainInfoText.append(QString("%1").arg(QString::fromStdString(node->GetName()))); planarAngle = dynamic_cast (planarFigure.GetPointer()); if (planarAngle.IsNull()) planarFourPointAngle = dynamic_cast (planarFigure.GetPointer()); double featureQuantity = 0.0; for (unsigned int k = 0; k < planarFigure->GetNumberOfFeatures(); ++k) { if (!planarFigure->IsFeatureActive(k)) continue; featureQuantity = planarFigure->GetQuantity(k); if ((planarAngle.IsNotNull() && k == planarAngle->FEATURE_ID_ANGLE) || (planarFourPointAngle.IsNotNull() && k == planarFourPointAngle->FEATURE_ID_ANGLE)) featureQuantity = featureQuantity * 180 / vnl_math::pi; infoText.append(QString("%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); plainInfoText.append(QString("\n%1: %2 %3") .arg(QString(planarFigure->GetFeatureName(k))) .arg(featureQuantity, 0, 'f', 2) .arg(QString(planarFigure->GetFeatureUnit(k)))); if (k + 1 != planarFigure->GetNumberOfFeatures()) infoText.append("
"); } if (j != d->m_CurrentSelection.size()) infoText.append("
"); } d->m_SelectedPlanarFiguresText->setHtml(infoText); } void QmitkMeasurementView::AddAllInteractors() { auto planarFigures = this->GetAllPlanarFigures(); for (auto it = planarFigures->Begin(); it != planarFigures->End(); ++it) this->NodeAdded(it.Value()); } void QmitkMeasurementView::EnableCrosshairNavigation() { // enable the crosshair navigation // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractorConfig : m_DisplayInteractorConfigs) { if (displayInteractorConfig.first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(displayInteractorConfig.first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(displayInteractorConfig.second); } } } m_DisplayInteractorConfigs.clear(); d->m_ScrollEnabled = true; } void QmitkMeasurementView::DisableCrosshairNavigation() { // dont deactivate twice, else we will clutter the config list ... if (d->m_ScrollEnabled == false) return; // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction will still be enabled m_DisplayInteractorConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(eventObserver)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(eventObserver, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig("DisplayConfigMITKLimited.xml"); } } d->m_ScrollEnabled = false; } mitk::DataStorage::SetOfObjects::ConstPointer QmitkMeasurementView::GetAllPlanarFigures() const { auto isPlanarFigure = mitk::TNodePredicateDataType::New(); auto isNotHelperObject = mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(false)); auto isNotHelperButPlanarFigure = mitk::NodePredicateAnd::New( isPlanarFigure, isNotHelperObject ); return this->GetDataStorage()->GetSubset(isPlanarFigure); } diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_ScreenshotMakerInterface.png b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_ScreenshotMakerInterface.png index 567bee6f1d..1c625dfb42 100644 Binary files a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_ScreenshotMakerInterface.png and b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkMovieMaker_ScreenshotMakerInterface.png differ diff --git a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkScreenshotMaker.dox b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkScreenshotMaker.dox index 71a6a17ed4..10230cc3cb 100644 --- a/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkScreenshotMaker.dox +++ b/Plugins/org.mitk.gui.qt.moviemaker/documentation/UserManual/QmitkScreenshotMaker.dox @@ -1,19 +1,18 @@ /** \page org_mitk_views_screenshotmaker The Screenshot Maker This view provides the functionality to create and save screenshots of the data. -Available sections: - - \ref QmitkScreenshotMakerUserManualUse - \imageMacro{QmitkMovieMaker_ScreenshotMakerInterface.png,"The Screenshot Maker User Interface",7.09} \section QmitkScreenshotMakerUserManualUse Usage -The first section offers the option of creating a screenshot of the last activated render window (thus the one, which was last clicked into). Upon clicking the button, the Screenshot Maker asks for a filename in which the screenshot is to be stored. The multiplanar Screenshot button asks for a folder, where screenshots of the three 2D views will be stored with default names. +The first section offers the option to create a screenshot of selected view plane (axial, sagittal or coronal). Upon clicking the 'Single' button in the '2D Screenshot' section, the Screenshot Maker asks for a filename in which the screenshot is to be stored. The 'Multiplanar' button asks for a folder, in which screenshots of the three 2D views will be stored with default names. + +If you tick the 'All Components' checkbox, you can take a screenshot of every channel of your multi-channel image. Otherwise, only the visible channel is captured. -The high resolution screenshot section works the same as the simple screenshot section, aside from the fact, that the user can choose a magnification factor. +The '3D Screenshots (High-res)' section works the same as the simple screenshot section, aside from the fact, that the user can choose a magnification ('Upsampling') factor. The 'Single' button creates a screenshot of the current 3D render window and the 'Multiplanar' button creates three screenshots from different perspectives. -In the option section one can choose the background color for the screenshots, default is black. +In the 'Options' section, one can choose the background color for the screenshots, default is black. */ 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 1a32f2174a..46579d046d 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,91 +1,104 @@ /** -\page org_mitk_gui_qt_pharmacokinetics_mri DCE MR Perfusion Datafit View +\page org_mitk_views_pharmacokinetics_mri 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 -For pharmacokinetic analysis of DCE MRI/CT data using compartment models in non-linear least square fitting the DCE MR Perfusion Datafit plugin can be used. +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 -This plug-in is being developed by Charlotte Debus and the SIDT group (Software development for Integrated Diagnostics -and Therapy) at the German Cancer Research Center (DKFZ). 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 (arXiv:1807.07353) +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_General General information -\imageMacro{dce_mri_init.png, "Example screen shot showing the view at first start.", 10} -For pharmacokinetic analysis of DCE MRI/CT data using compartment models in non-linear least square fitting the DCE MR Perfusion Datafit view can be used. +\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 voxel wise and/or ROI averaged fits of intensity-time curves with different quantitative and semi-quantitative models. -If a mask is selected, ROI-based fitting (fit average curve within ROI) is enabled (radio button Fitting Strategy – Pixel based / ROI based). +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. + -\subsection FIT_DCE_General_models Supported models +\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 three segment linear model (3SL) +- 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_config.png, "Example screenshot showing the config settings of the view.", 10} +\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 all three compartment models that require the input of the concentration time curve in the tissue feeding artery, the AIF. In the DCE MRI Model fitting plugin, 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 given to the tool (Drop-down menu AIF Mask from 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 from the Data Manager. -The other option is to define the AIF via an external 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 arterial intensity values and corresponding timepoints (in tuple format (Time, Value)). Caution: the file may 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 to the literature default value of 0.45. +- 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 screen shot for start parameter settings.", 10} +\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. +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 Constraint settings -\imageMacro{dce_mri_constraint.png, "Example screen shot for constraint settings.", 10} +\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 selected, their sum will be used) by selection in the drop down menu. -The second column defines whether the constraints defines an upper or lower boundary. -Value and Width define the actual constraint value, that should not be crossed, and a certain tolerance width. +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 screen shot for concentration conversion settings.", 10} -Most models require concentration values as input rather than raw signal intensities (i.e. all compartment models). -The DCE MR Perfusion view offers conversion to concentration by means of relative and absolute signal enhancement as well as a special conversion for turbo flash sequences. -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. +\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. +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 -After configuration of the entire fit routine, the respective time series to be fitted and eventually the ROI mask have to be selected. -If only an image is needed, selection of the respective time series in the data manager is sufficient. -If a mask is to be selected as well, image and mask have to be selected by holding the shift key and selecting them in this order from the Data manager.\n\n -In order to distinguish results from different model fits to the data, a Fitting name can be defined in the bottom field. +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, like needed optimization time, number of iterations, constraint violations and reasons for fit termination (criterion reached, maximum number of iterations, etc.).\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 to the analyzed 4D image. +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. http://iopscience.iop.org/article/10.1088/0031-9155/57/2/R1/pdf. Accessed 1 May 2016. */ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png index 5ba06abe29..bcf2d51953 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_concentration.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png index b2ac30d6da..c7ca984653 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_config.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraint.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraint.png deleted file mode 100644 index 1848b5fb8f..0000000000 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraint.png and /dev/null differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png new file mode 100644 index 0000000000..200837c59b Binary files /dev/null and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_constraints.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png new file mode 100644 index 0000000000..65f7604cd6 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_maskAndFittingStrategy.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png new file mode 100644 index 0000000000..45d1ac1ab0 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_modelSettings.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png index cc77a83553..b7c2275720 100644 Binary files a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png and b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/documentation/UserManual/dce_mri_start.png differ diff --git a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/plugin.xml b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/plugin.xml index a724f1c486..bfb82f6dd2 100644 --- a/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/plugin.xml +++ b/Plugins/org.mitk.gui.qt.pharmacokinetics.mri/plugin.xml @@ -1,12 +1,12 @@ -