diff --git a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp index 4cea251184..c5d8e7a95a 100644 --- a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp @@ -1,140 +1,276 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkFeedbackContourTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkStringProperty.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" +#include "mitkAbstractTransformGeometry.h" + mitk::FeedbackContourTool::FeedbackContourTool(const char *type) : SegTool2D(type), m_FeedbackContourVisible(false) { - m_FeedbackContour = ContourModel::New(); - m_FeedbackContour->SetClosed(true); m_FeedbackContourNode = DataNode::New(); - m_FeedbackContourNode->SetData(m_FeedbackContour); m_FeedbackContourNode->SetProperty("name", StringProperty::New("One of FeedbackContourTool's feedback nodes")); m_FeedbackContourNode->SetProperty("visible", BoolProperty::New(true)); m_FeedbackContourNode->SetProperty("helper object", BoolProperty::New(true)); m_FeedbackContourNode->SetProperty("layer", IntProperty::New(1000)); m_FeedbackContourNode->SetProperty("contour.project-onto-plane", BoolProperty::New(false)); m_FeedbackContourNode->SetProperty("contour.width", FloatProperty::New(1.0)); // set to max short, max int doesn't work, max value somewhere hardcoded? m_FeedbackContourNode->SetProperty("layer", IntProperty::New(std::numeric_limits::max())); m_FeedbackContourNode->SetProperty("fixedLayer", BoolProperty::New(true)); - + this->InitializeFeedbackContour(true); SetFeedbackContourColorDefault(); } mitk::FeedbackContourTool::~FeedbackContourTool() { } void mitk::FeedbackContourTool::SetFeedbackContourColor(float r, float g, float b) { m_FeedbackContourNode->SetProperty("contour.color", ColorProperty::New(r, g, b)); } void mitk::FeedbackContourTool::SetFeedbackContourColorDefault() { m_FeedbackContourNode->SetProperty("contour.color", ColorProperty::New(0.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0)); } void mitk::FeedbackContourTool::Deactivated() { Superclass::Deactivated(); DataStorage *storage = m_ToolManager->GetDataStorage(); if (storage && m_FeedbackContourNode.IsNotNull()) { storage->Remove(m_FeedbackContourNode); m_FeedbackContour->Clear(); SetFeedbackContourVisible(false); } } void mitk::FeedbackContourTool::Activated() { Superclass::Activated(); - - SetFeedbackContourVisible(true); + this->InitializeFeedbackContour(true); + this->SetFeedbackContourVisible(true); } -mitk::ContourModel *mitk::FeedbackContourTool::GetFeedbackContour() +const mitk::ContourModel *mitk::FeedbackContourTool::GetFeedbackContour() const { return m_FeedbackContour; } -void mitk::FeedbackContourTool::SetFeedbackContour(ContourModel::Pointer contour) +void mitk::FeedbackContourTool::InitializeFeedbackContour(bool isClosed) { - m_FeedbackContour = contour; + m_FeedbackContour = ContourModel::New(); + m_FeedbackContour->SetClosed(isClosed); + + auto workingImage = this->GetWorkingData(); + + if (nullptr != workingImage) + { + m_FeedbackContour->Expand(workingImage->GetTimeSteps()); + + auto contourTimeGeometry = workingImage->GetTimeGeometry()->Clone(); + contourTimeGeometry->ReplaceTimeStepGeometries(m_FeedbackContour->GetGeometry()); + m_FeedbackContour->SetTimeGeometry(contourTimeGeometry); + + for (unsigned int t = 0; t < m_FeedbackContour->GetTimeSteps(); ++t) + { + m_FeedbackContour->SetClosed(isClosed, t); + } + } + m_FeedbackContourNode->SetData(m_FeedbackContour); } +void mitk::FeedbackContourTool::ClearsCurrentFeedbackContour(bool isClosed) +{ + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) + { + MITK_WARN << "Cannot clear feedback contour at current time step. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: " << this->GetLastTimePointTriggered(); + return; + } + + auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); + m_FeedbackContour->Clear(feedbackTimeStep); + m_FeedbackContour->SetClosed(isClosed, feedbackTimeStep); +} + +void mitk::FeedbackContourTool::UpdateCurrentFeedbackContour(const ContourModel* sourceModel, TimeStepType sourceTimeStep) +{ + if (nullptr == sourceModel) + return; + + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) + { + MITK_WARN << "Cannot update feedback contour. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: "<GetLastTimePointTriggered(); + return; + } + + auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); + this->UpdateFeedbackContour(sourceModel, feedbackTimeStep, sourceTimeStep); +} + +void mitk::FeedbackContourTool::UpdateFeedbackContour(const ContourModel* sourceModel, TimeStepType feedbackTimeStep, TimeStepType sourceTimeStep) +{ + if (nullptr == sourceModel) + return; + + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimeStep(feedbackTimeStep)) + { + MITK_WARN << "Cannot update feedback contour. Feedback contour time geometry does not support passed time step. Invalid time step: "<Clear(feedbackTimeStep); + + std::for_each(sourceModel->Begin(sourceTimeStep), sourceModel->End(sourceTimeStep), [this, feedbackTimeStep](ContourElement::VertexType* vertex) { + this->m_FeedbackContour->AddVertex(vertex, feedbackTimeStep); + }); +} + +void mitk::FeedbackContourTool::AddVertexToCurrentFeedbackContour(const Point3D& point) +{ + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) + { + MITK_WARN << "Cannot add vertex to feedback contour. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: " << this->GetLastTimePointTriggered(); + return; + } + + auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); + this->AddVertexToFeedbackContour(point, feedbackTimeStep); +}; + +/** Adds a vertex to the feedback contour for the passed time step. If time step is invalid, nothing will be added.*/ +void mitk::FeedbackContourTool::AddVertexToFeedbackContour(const Point3D& point, TimeStepType feedbackTimeStep) +{ + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimeStep(feedbackTimeStep)) + { + MITK_WARN << "Cannot add vertex to feedback contour. Feedback contour time geometry does not support passed time step. Invalid time step: " << feedbackTimeStep; + return; + } + + m_FeedbackContour->AddVertex(point,static_cast(feedbackTimeStep)); +} + + void mitk::FeedbackContourTool::SetFeedbackContourVisible(bool visible) { if (m_FeedbackContourVisible == visible) return; // nothing changed if (DataStorage *storage = m_ToolManager->GetDataStorage()) { if (visible) { // Add the feedback contour node as a derived node of the first working data. // If there is no working data, the node is added at the top level. - storage->Add(m_FeedbackContourNode, m_ToolManager->GetWorkingData(0)); + storage->Add(m_FeedbackContourNode, this->GetWorkingDataNode()); } else { storage->Remove(m_FeedbackContourNode); } } m_FeedbackContourVisible = visible; } -mitk::ContourModel::Pointer mitk::FeedbackContourTool::ProjectContourTo2DSlice(Image *slice, - ContourModel *contourIn3D, +mitk::ContourModel::Pointer mitk::FeedbackContourTool::ProjectContourTo2DSlice(const Image *slice, + const ContourModel *contourIn3D, bool correctionForIpSegmentation, bool constrainToInside) { return mitk::ContourModelUtils::ProjectContourTo2DSlice( slice, contourIn3D, correctionForIpSegmentation, constrainToInside); } mitk::ContourModel::Pointer mitk::FeedbackContourTool::BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, - ContourModel *contourIn2D, + const ContourModel *contourIn2D, bool correctionForIpSegmentation) { return mitk::ContourModelUtils::BackProjectContourFrom2DSlice( sliceGeometry, contourIn2D, correctionForIpSegmentation); } +void mitk::FeedbackContourTool::WriteBackFeedbackContourAsSegmentationResult(const InteractionPositionEvent* positionEvent, int paintingPixelValue, bool setInvisibleAfterSuccess) +{ + if (!positionEvent) + return; + + auto workingImage = this->GetWorkingData(); + const auto planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); + + if (!workingImage || !planeGeometry) + return; + + const auto* abstractTransformGeometry( + dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); + if (nullptr != abstractTransformGeometry) + return; + + Image::Pointer slice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); + + if (slice.IsNull()) + { + MITK_ERROR << "Unable to extract slice." << std::endl; + return; + } + + const auto feedbackContour = FeedbackContourTool::GetFeedbackContour(); + auto contourTimeStep = positionEvent->GetSender()->GetTimeStep(feedbackContour); + + ContourModel::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( + slice, feedbackContour, false, false); // false: don't add 0.5 (done by FillContourInSlice) + // false: don't constrain the contour to the image's inside + + if (projectedContour.IsNull()) + return; + + auto activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); + + mitk::ContourModelUtils::FillContourInSlice( + projectedContour, contourTimeStep, slice, workingImage, paintingPixelValue * activePixelValue); + + this->WriteBackSegmentationResult(positionEvent, slice); + + if (setInvisibleAfterSuccess) + { + this->SetFeedbackContourVisible(false); + } +} + + void mitk::FeedbackContourTool::FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, int paintingPixelValue) { this->FillContourInSlice(projectedContour, 0, sliceImage, paintingPixelValue); } void mitk::FeedbackContourTool::FillContourInSlice(ContourModel *projectedContour, unsigned int timeStep, Image *sliceImage, int paintingPixelValue) { mitk::ContourModelUtils::FillContourInSlice(projectedContour, timeStep, sliceImage, sliceImage, paintingPixelValue); } diff --git a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h index 945c4911fd..60c6b60f34 100644 --- a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h +++ b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h @@ -1,118 +1,149 @@ /*============================================================================ 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 mitkFeedbackContourTool_h_Included #define mitkFeedbackContourTool_h_Included #include "mitkCommon.h" #include "mitkContourModelUtils.h" #include "mitkContourUtils.h" //TODO remove legacy support #include "mitkImage.h" #include "mitkSegTool2D.h" #include #include "mitkDataNode.h" #include "mitkImageCast.h" namespace mitk { /** \brief Base class for tools that use a contour for feedback \sa Tool \sa ContourModel \ingroup Interaction \ingroup ToolManagerEtAl Implements helper methods, that might be of use to all kind of 2D segmentation tools that use a contour for user feedback. - Providing a feedback contour that might be added or removed from the visible scene (SetFeedbackContourVisible). - Filling of a contour into a 2D slice These helper methods are actually implemented in ContourUtils now. FeedbackContourTool only forwards such requests. \warning Only to be instantiated by mitk::ToolManager. $Author: nolden $ */ class MITKSEGMENTATION_EXPORT FeedbackContourTool : public SegTool2D { public: mitkClassMacro(FeedbackContourTool, SegTool2D); protected: FeedbackContourTool(); // purposely hidden FeedbackContourTool(const char *); // purposely hidden ~FeedbackContourTool() override; - ContourModel *GetFeedbackContour(); - void SetFeedbackContour(ContourModel::Pointer); + const ContourModel *GetFeedbackContour() const; + + /** (Re)initialize the feesback contour by creating a new instance. + * It is assured that the new instance as the same time geometry than + * the working image.*/ + void InitializeFeedbackContour(bool isClosed); + + /** Clears the current time step of the feedback contour and resets its closed state.*/ + void ClearsCurrentFeedbackContour(bool isClosed); + + /** Updates the feedback contour of the currently selected time point. The update will be done + * by clearing all existings vertices at the current time point and copying the vertics of the + * source model at the specified source time step.*/ + void UpdateCurrentFeedbackContour(const ContourModel* sourceModel, TimeStepType sourceTimeStep = 0); + /** Updates the feedback contour at the time step specified by feedbackTimeStep. The update will be done + * by clearing all existings vertices at feedbackTimeStep and copying the vertics of the + * source model at the specified source time step.*/ + void UpdateFeedbackContour(const ContourModel* sourceModel, TimeStepType feedbackTimeStep, TimeStepType sourceTimeStep = 0); + + /** Adds a vertex to the feedback contour for the current time point. */ + void AddVertexToCurrentFeedbackContour(const Point3D& point); + + /** Adds a vertex to the feedback contour for the passed time step. If time step is invalid, nothing will be added.*/ + void AddVertexToFeedbackContour(const Point3D& point, TimeStepType feedbackTimeStep); void SetFeedbackContourVisible(bool); /// Provide values from 0.0 (black) to 1.0 (full color) void SetFeedbackContourColor(float r, float g, float b); void SetFeedbackContourColorDefault(); void Deactivated() override; void Activated() override; /** \brief Projects a contour onto an image point by point. Converts from world to index coordinates. \param slice \param contourIn3D \param correctionForIpSegmentation adds 0.5 to x and y index coordinates (difference between ipSegmentation and MITK contours) \param constrainToInside */ - ContourModel::Pointer ProjectContourTo2DSlice(Image *slice, - ContourModel *contourIn3D, + ContourModel::Pointer ProjectContourTo2DSlice(const Image *slice, + const ContourModel *contourIn3D, bool correctionForIpSegmentation = false, bool constrainToInside = true); /** \brief Projects a slice index coordinates of a contour back into world coordinates. \param sliceGeometry \param contourIn2D \param correctionForIpSegmentation subtracts 0.5 to x and y index coordinates (difference between ipSegmentation and MITK contours) */ ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, - ContourModel *contourIn2D, + const ContourModel *contourIn2D, bool correctionForIpSegmentation = false); + /** Helper methods that checks all precondition and if they are fullfilled does the following: + * 1. Gets the contour of the time point specified by positionEvent. + * 2. Gets the affacted working slice of the time point specified by positionEvent. + * 3. projects the contour onto the working slice and then fills it with the passed paintingPixelValue (adjusted by the current active lable value) + * to the slice. + * 4. writes the slice back into the working image using SegTool2D::WriteBackSegmentationResult().*/ + void WriteBackFeedbackContourAsSegmentationResult(const InteractionPositionEvent* positionEvent, int paintingPixelValue, bool setInvisibleAfterSuccess = true); + /** \brief Fill a contour in a 2D slice with a specified pixel value. */ void FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, int paintingPixelValue = 1); /** \brief Fill a contour in a 2D slice with a specified pixel value at a given time step. */ void FillContourInSlice(ContourModel *projectedContour, unsigned int timeStep, Image *sliceImage, int paintingPixelValue = 1); + private: ContourModel::Pointer m_FeedbackContour; DataNode::Pointer m_FeedbackContourNode; bool m_FeedbackContourVisible; }; } // namespace #endif