diff --git a/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp b/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp index f25e261373..81db18aa4a 100644 --- a/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp @@ -1,471 +1,459 @@ /*============================================================================ 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 "mitkEditableContourTool.h" #include mitk::EditableContourTool::EditableContourTool() : FeedbackContourTool("EditableContourTool"), m_AutoConfirm(true), m_AddMode(true) {} mitk::EditableContourTool::~EditableContourTool() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); } void mitk::EditableContourTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("InitObject", OnInitContour); CONNECT_FUNCTION("AddPoint", OnAddPoint); CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); CONNECT_FUNCTION("Drawing", OnDrawing); CONNECT_FUNCTION("EndDrawing", OnEndDrawing); CONNECT_FUNCTION("FinishContour", OnFinish); CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); } void mitk::EditableContourTool::Activated() { Superclass::Activated(); this->ResetToStartState(); this->EnableContourInteraction(true); } void mitk::EditableContourTool::Deactivated() { this->ClearSegmentation(); Superclass::Deactivated(); } void mitk::EditableContourTool::ConfirmSegmentation(bool resetStatMachine) { auto referenceImage = this->GetReferenceData(); auto workingImage = this->GetWorkingData(); if (nullptr != referenceImage && nullptr != workingImage) { std::vector sliceInfos; const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); auto contour = this->GetContour(); if (nullptr == contour || contour->IsEmpty()) return; auto workingSlice = this->GetAffectedImageSliceAs2DImage(m_PlaneGeometry, workingImage, workingImageTimeStep)->Clone(); sliceInfos.emplace_back(workingSlice, m_PlaneGeometry, workingImageTimeStep); auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); if (!m_AddMode) { activePixelValue = 0; } ContourModelUtils::FillContourInSlice(projectedContour, workingSlice, workingImage, activePixelValue); this->WriteBackSegmentationResults(sliceInfos); } this->ReleaseHelperObjects(); this->ReleaseInteractors(); if (resetStatMachine) this->ResetToStartState(); } void mitk::EditableContourTool::ClearSegmentation() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); this->ResetToStartState(); } bool mitk::EditableContourTool::IsEditingContour() const { return (nullptr != GetContour()) && !this->IsDrawingContour(); }; bool mitk::EditableContourTool::IsDrawingContour() const { return m_PreviewContour.IsNotNull(); }; -bool mitk::EditableContourTool::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent *positionEvent, - mitk::BaseData *data) -{ - bool isPositionEventInsideImageRegion = - nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); - - if (!isPositionEventInsideImageRegion) - MITK_WARN("EditableContourTool") << "PositionEvent is outside ImageRegion!"; - - return isPositionEventInsideImageRegion; -} - mitk::Point3D mitk::EditableContourTool::PrepareInitContour(const Point3D& clickedPoint) { //default implementation does nothing return clickedPoint; } void mitk::EditableContourTool::OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; auto workingDataNode = this->GetWorkingDataNode(); if (nullptr != this->GetContour()) { this->ConfirmSegmentation(false); } if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) { this->ResetToStartState(); return; } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); auto contour = this->CreateNewContour(); m_ContourNode = mitk::DataNode::New(); m_ContourNode->SetData(contour); m_ContourNode->SetName("working contour node"); m_ContourNode->SetProperty("layer", IntProperty::New(100)); m_ContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_ContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_ContourNode->AddProperty("contour.color", ColorProperty::New(1.0f, 1.0f, 0.0f), nullptr, true); m_ContourNode->AddProperty("contour.points.color", ColorProperty::New(1.0f, 0.0f, 0.1f), nullptr, true); m_ContourNode->AddProperty("contour.controlpoints.show", BoolProperty::New(true), nullptr, true); m_PreviewContour = this->CreateNewContour(); m_PreviewContourNode = mitk::DataNode::New(); m_PreviewContourNode->SetData(m_PreviewContour); m_PreviewContourNode->SetName("active preview node"); m_PreviewContourNode->SetProperty("layer", IntProperty::New(101)); m_PreviewContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_PreviewContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PreviewContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_PreviewContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); m_ClosureContour = this->CreateNewContour(); m_ClosureContourNode = mitk::DataNode::New(); m_ClosureContourNode->SetData(m_ClosureContour); m_ClosureContourNode->SetName("active closure node"); m_ClosureContourNode->SetProperty("layer", IntProperty::New(101)); m_ClosureContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_ClosureContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_ClosureContourNode->AddProperty("contour.color", ColorProperty::New(0.0f, 1.0f, 0.1f), nullptr, true); m_ClosureContourNode->AddProperty("contour.width", mitk::FloatProperty::New(2.0f), nullptr, true); m_CurrentRestrictedArea = this->CreateNewContour(); auto dataStorage = this->GetToolManager()->GetDataStorage(); dataStorage->Add(m_ContourNode, workingDataNode); dataStorage->Add(m_PreviewContourNode, workingDataNode); dataStorage->Add(m_ClosureContourNode, workingDataNode); m_ReferenceDataSlice = this->GetAffectedReferenceSlice(positionEvent); auto origin = m_ReferenceDataSlice->GetSlicedGeometry()->GetOrigin(); m_ReferenceDataSlice->GetSlicedGeometry()->WorldToIndex(origin, origin); m_ReferenceDataSlice->GetSlicedGeometry()->IndexToWorld(origin, origin); m_ReferenceDataSlice->GetSlicedGeometry()->SetOrigin(origin); // Remember PlaneGeometry to determine if events were triggered in the same plane m_PlaneGeometry = interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); // Map click to pixel coordinates auto click = positionEvent->GetPositionInWorld(); click = this->PrepareInitContour(click); this->InitializePreviewContour(click); // Set initial start point contour->AddVertex(click, true); m_PreviewContour->AddVertex(click, false); m_ClosureContour->AddVertex(click); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::FinalizePreviewContour(const Point3D& /*clickedPoint*/) { // Remove duplicate first vertex, it's already contained in m_ContourNode m_PreviewContour->RemoveVertexAt(0); m_PreviewContour->SetControlVertexAt(m_PreviewContour->GetNumberOfVertices() - 1); } void mitk::EditableContourTool::InitializePreviewContour(const Point3D& clickedPoint) { //default implementation only clears the preview and sets the start point m_PreviewContour = this->CreateNewContour(); m_PreviewContour->AddVertex(clickedPoint); m_PreviewContourNode->SetData(m_PreviewContour); } void mitk::EditableContourTool::UpdatePreviewContour(const Point3D& clickedPoint) { //default implementation draws just a simple line to position if (m_PreviewContour->GetNumberOfVertices() > 2) { auto contour = this->GetContour(); this->InitializePreviewContour(contour->GetVertexAt(contour->GetNumberOfVertices() - 1)->Coordinates); } if (m_PreviewContour->GetNumberOfVertices() == 2) { m_PreviewContour->RemoveVertexAt(m_PreviewContour->GetNumberOfVertices()-1); } m_PreviewContour->AddVertex(clickedPoint); } void mitk::EditableContourTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } this->FinalizePreviewContour(positionEvent->GetPositionInWorld()); // Merge contours auto contour = this->GetContour(); contour->Concatenate(m_PreviewContour); this->InitializePreviewContour(positionEvent->GetPositionInWorld()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::OnDrawing(StateMachineAction*, InteractionEvent* interactionEvent) { auto* positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_PreviewContourNode->SetVisibility(false); auto contour = this->GetContour(); contour->AddVertex(positionEvent->GetPositionInWorld(), false); UpdateClosureContour(positionEvent->GetPositionInWorld()); assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::OnEndDrawing(StateMachineAction*, InteractionEvent* interactionEvent) { auto* positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; auto contour = this->GetContour(); auto controlVs = contour->GetControlVertices(0); if (!controlVs.empty()) { //add the last control point (after that the draw part start) m_CurrentRestrictedArea->AddVertex(controlVs.back()->Coordinates); } m_PreviewContourNode->SetVisibility(true); contour->SetControlVertexAt(contour->GetNumberOfVertices() - 1); //add the just created/set last control point (with it the draw part ends) m_CurrentRestrictedArea->AddVertex(contour->GetVertexAt(contour->GetNumberOfVertices() - 1)->Coordinates); this->InitializePreviewContour(positionEvent->GetPositionInWorld()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::OnMouseMoved(StateMachineAction*, InteractionEvent* interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } this->UpdatePreviewContour(positionEvent->GetPositionInWorld()); this->UpdateClosureContour(positionEvent->GetPositionInWorld()); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } this->FinalizePreviewContour(positionEvent->GetPositionInWorld()); this->FinishTool(); // Merge contours auto contour = this->GetContour(); contour->Concatenate(m_PreviewContour); auto numberOfTimesteps = static_cast(contour->GetTimeSteps()); for (int i = 0; i <= numberOfTimesteps; ++i) contour->Close(i); this->ReleaseHelperObjects(false); if (m_AutoConfirm) { this->ConfirmSegmentation(); } } void mitk::EditableContourTool::ReleaseHelperObjects(bool includeWorkingContour) { this->RemoveHelperObjectsFromDataStorage(includeWorkingContour); if (includeWorkingContour) { m_ContourNode = nullptr; m_CurrentRestrictedArea = nullptr; } m_PreviewContourNode = nullptr; m_PreviewContour = nullptr; m_ClosureContourNode = nullptr; m_ClosureContour = nullptr; } void mitk::EditableContourTool::RemoveHelperObjectsFromDataStorage(bool includeWorkingContour) { auto dataStorage = this->GetToolManager()->GetDataStorage(); if (nullptr == dataStorage) return; if (includeWorkingContour) { if (m_ContourNode.IsNotNull()) dataStorage->Remove(m_ContourNode); } if (m_PreviewContourNode.IsNotNull()) dataStorage->Remove(m_PreviewContourNode); if (m_ClosureContourNode.IsNotNull()) dataStorage->Remove(m_ClosureContourNode); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::ContourModel::Pointer mitk::EditableContourTool::CreateNewContour() const { auto workingData = this->GetWorkingData(); if (nullptr == workingData) { this->InteractiveSegmentationBugMessage( "Cannot create new contour. No valid working data is set. Application is in invalid state."); mitkThrow() << "Cannot create new contour. No valid working data is set. Application is in invalid state."; } auto contour = ContourModel::New(); // generate a time geometry that is always visible as the working contour should always be. auto contourTimeGeometry = ProportionalTimeGeometry::New(); contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); contour->SetTimeGeometry(contourTimeGeometry); return contour; } void mitk::EditableContourTool::UpdateClosureContour(mitk::Point3D endpoint) { if (m_ClosureContour->GetNumberOfVertices() > 2) { m_ClosureContour = this->CreateNewContour(); m_ClosureContourNode->SetData(m_ClosureContour); } if (m_ClosureContour->GetNumberOfVertices() == 0) { auto contour = this->GetContour(); m_ClosureContour->AddVertex(contour->GetVertexAt(0)->Coordinates); m_ClosureContour->Update(); } if (m_ClosureContour->GetNumberOfVertices() == 2) { m_ClosureContour->RemoveVertexAt(0); } m_ClosureContour->AddVertexAtFront(endpoint); } void mitk::EditableContourTool::EnableContourInteraction(bool on) { if (m_ContourInteractor.IsNotNull()) { m_ContourInteractor->EnableInteraction(on); } } void mitk::EditableContourTool::ReleaseInteractors() { this->EnableContourInteraction(false); m_ContourInteractor = nullptr; } mitk::ContourModel* mitk::EditableContourTool::GetContour() { if (this->m_ContourNode.IsNotNull()) { return dynamic_cast(this->m_ContourNode->GetData()); } return nullptr; }; const mitk::ContourModel* mitk::EditableContourTool::GetContour() const { if (this->m_ContourNode.IsNotNull()) { return dynamic_cast(this->m_ContourNode->GetData()); } return nullptr; }; \ No newline at end of file diff --git a/Modules/Segmentation/Interactions/mitkEditableContourTool.h b/Modules/Segmentation/Interactions/mitkEditableContourTool.h index 8be7c24ac9..1189e3b406 100644 --- a/Modules/Segmentation/Interactions/mitkEditableContourTool.h +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.h @@ -1,127 +1,125 @@ /*============================================================================ 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 mitkEditbaleContourTool_h_Included #define mitkEditbaleContourTool_h_Included #include namespace mitk { /** * Base class for lasso like tools that allow to draw closed contours with multiple ancor points. * The segments between the ancor points may be freehand contour segments or computed segments * (e.g. straight lines or live wire). Derive from the class to implement the computation of the non-freehand * segments. * @sa LassoTool LivewWireTool2D */ class MITKSEGMENTATION_EXPORT EditableContourTool : public FeedbackContourTool { public: mitkClassMacro(EditableContourTool, FeedbackContourTool); /// \brief Convert current contour to binary segmentations. virtual void ConfirmSegmentation(bool resetStatMachine = true); /// \brief Delete all current contours. virtual void ClearSegmentation(); itkBooleanMacro(AutoConfirm); itkSetMacro(AutoConfirm, bool); itkGetMacro(AutoConfirm, bool); itkBooleanMacro(AddMode); itkSetMacro(AddMode, bool); itkGetMacro(AddMode, bool); /* Indicated if a contour is drawn, but not confirmed yet. This the tool is in interactor mode to allow users to edit the contour. This state can be reached if AutoConfirm is false, after the finalizing double click before the contour is confirmed.*/ bool IsEditingContour() const; /* Indicate if a contour is currently drawn by the user (state between the initializing double click and the finalizing double click).*/ bool IsDrawingContour() const; protected: EditableContourTool(); ~EditableContourTool() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; virtual Point3D PrepareInitContour(const Point3D& clickedPoint); virtual void FinalizePreviewContour(const Point3D& clickedPoint); virtual void InitializePreviewContour(const Point3D& clickedPoint); virtual void UpdatePreviewContour(const Point3D& clickedPoint); /// \brief Initialize tool. virtual void OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Add a control point and finish current segment. virtual void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Draw a contour according to the mouse movement when mouse button is pressed and mouse is moved. virtual void OnDrawing(StateMachineAction *, InteractionEvent *interactionEvent); virtual void OnEndDrawing(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Computation of the preview contour. virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Finish EditableContour tool. virtual void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Finish contour interaction. virtual void FinishTool() = 0; void EnableContourInteraction(bool on); void ReleaseInteractors(); virtual void ReleaseHelperObjects(bool includeWorkingContour = true); virtual void RemoveHelperObjectsFromDataStorage(bool includeWorkingContour = true); ContourModel::Pointer CreateNewContour() const; virtual void UpdateClosureContour(mitk::Point3D endpoint); - bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); - mitk::ContourModel* GetContour(); const mitk::ContourModel* GetContour() const; mitk::DataNode::Pointer m_ContourNode; mitk::ContourModel::Pointer m_PreviewContour; mitk::DataNode::Pointer m_PreviewContourNode; mitk::ContourModel::Pointer m_ClosureContour; mitk::DataNode::Pointer m_ClosureContourNode; mitk::ContourModel::Pointer m_CurrentRestrictedArea; /** Slice of the reference data the tool is currently actively working on to define contours.*/ mitk::Image::Pointer m_ReferenceDataSlice; PlaneGeometry::ConstPointer m_PlaneGeometry; mitk::DataInteractor::Pointer m_ContourInteractor; bool m_AutoConfirm; bool m_AddMode; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.cpp b/Modules/Segmentation/Interactions/mitkPickingTool.cpp index 6f94213db6..d757a07db4 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPickingTool.cpp @@ -1,276 +1,275 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkPickingTool.h" #include "mitkProperties.h" #include "mitkToolManager.h" #include "mitkInteractionPositionEvent.h" // us #include #include #include #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" -#include "mitkImageTimeSelector.h" #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, PickingTool, "PickingTool"); } mitk::PickingTool::PickingTool() : SegWithPreviewTool(false, "PressMoveReleaseAndPointSetting") { this->ResetsToEmptyPreviewOn(); } mitk::PickingTool::~PickingTool() { } bool mitk::PickingTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData,workingData)) return false; auto* image = dynamic_cast(referenceData); if (image == nullptr) return false; return true; } const char **mitk::PickingTool::GetXPM() const { return nullptr; } const char *mitk::PickingTool::GetName() const { return "Picking"; } us::ModuleResource mitk::PickingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Pick_48x48.png"); return resource; } void mitk::PickingTool::Activated() { Superclass::Activated(); m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->SetData(m_PointSet); m_PointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); m_PointSetNode->SetBoolProperty("helper object", true); m_PointSetNode->SetColor(0.0, 1.0, 0.0); m_PointSetNode->SetVisibility(true); this->GetDataStorage()->Add(m_PointSetNode, this->GetToolManager()->GetWorkingData(0)); } void mitk::PickingTool::Deactivated() { this->ClearSeeds(); // remove from data storage and disable interaction GetDataStorage()->Remove(m_PointSetNode); m_PointSetNode = nullptr; m_PointSet = nullptr; Superclass::Deactivated(); } void mitk::PickingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::PickingTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSet->InsertPoint(m_PointSet->GetSize(), positionEvent->GetPositionInWorld()); this->UpdatePreview(); } } } void mitk::PickingTool::OnDelete(StateMachineAction*, InteractionEvent* /*interactionEvent*/) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { // delete last seed point if (this->m_PointSet->GetSize() > 0) { m_PointSet->RemovePointAtEnd(0); this->UpdatePreview(); } } } void mitk::PickingTool::ClearPicks() { this->ClearSeeds(); this->UpdatePreview(); } bool mitk::PickingTool::HasPicks() const { return this->m_PointSet.IsNotNull() && this->m_PointSet->GetSize()>0; } void mitk::PickingTool::ClearSeeds() { if (this->m_PointSet.IsNotNull()) { // renew pointset this->m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); this->m_PointSetNode->SetData(this->m_PointSet); } } template void DoITKRegionGrowing(const itk::Image* oldSegImage, mitk::Image* segmentation, const mitk::PointSet* seedPoints, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry, const mitk::Label::PixelType outputValue, const mitk::Label::PixelType backgroundValue, bool& emptyTimeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; using IndexMapType = std::map < mitk::Label::PixelType, std::vector >; IndexMapType indexMap; // convert world coordinates to image indices for (auto pos = seedPoints->Begin(); pos != seedPoints->End(); ++pos) { IndexType seedIndex; inputGeometry->WorldToIndex(pos->Value(), seedIndex); const auto selectedLabel = oldSegImage->GetPixel(seedIndex); if (selectedLabel != backgroundValue) { indexMap[selectedLabel].push_back(seedIndex); } } typename OutputImageType::Pointer itkResultImage; try { bool first = true; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); regionGrower->SetInput(oldSegImage); regionGrower->SetReplaceValue(outputValue); for (const auto& [label, indeces] : indexMap) { // perform region growing in desired segmented region regionGrower->ClearSeeds(); for (const auto& index : indeces) { regionGrower->AddSeed(index); } regionGrower->SetLower(label); regionGrower->SetUpper(label); regionGrower->Update(); if (first) { itkResultImage = regionGrower->GetOutput(); } else { typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(regionGrower->GetOutput()); orFilter->SetInput2(itkResultImage); orFilter->Update(); itkResultImage = orFilter->GetOutput(); } first = false; itkResultImage->DisconnectPipeline(); } } catch (const itk::ExceptionObject&) { return; // can't work } catch (...) { return; } if (itkResultImage.IsNotNull()) { segmentation->SetVolume((void*)(itkResultImage->GetPixelContainer()->GetBufferPointer()),timeStep); } emptyTimeStep = itkResultImage.IsNull(); } void mitk::PickingTool::DoUpdatePreview(const Image* /*inputAtTimeStep*/, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) { if (nullptr != oldSegAtTimeStep && nullptr != previewImage && m_PointSet.IsNotNull()) { bool emptyTimeStep = true; if (this->HasPicks()) { Label::PixelType backgroundValue = 0; auto labelSetImage = dynamic_cast(oldSegAtTimeStep); if (nullptr != labelSetImage) { backgroundValue = labelSetImage->GetExteriorLabel()->GetValue(); } AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), this->GetUserDefinedActiveLabel(), backgroundValue, emptyTimeStep)); } if (emptyTimeStep) { this->ResetPreviewContentAtTimeStep(timeStep); } } } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index ca9a769dfd..80f4bcba3a 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,757 +1,769 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegTool2D.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkPlaneGeometry.h" // Include of the new ImageExtractor #include "mitkMorphologicalOperations.h" #include "mitkPlanarCircle.h" #include "usGetModuleContext.h" // Includes for 3DSurfaceInterpolation #include "mitkImageTimeSelector.h" #include "mitkImageToContourFilter.h" #include "mitkSurfaceInterpolationController.h" // includes for resling and overwriting #include #include #include #include #include "mitkOperationEvent.h" #include "mitkUndoController.h" #include #include "mitkAbstractTransformGeometry.h" #include "mitkLabelSetImage.h" #include "mitkContourModelUtils.h" #include "itkImageRegionIterator.h" #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) bool mitk::SegTool2D::m_SurfaceInterpolationEnabled = true; mitk::SegTool2D::SliceInformation::SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep) : slice(aSlice), plane(aPlane), timestep(aTimestep) { } mitk::SegTool2D::SegTool2D(const char *type, const us::Module *interactorModule) : Tool(type, interactorModule), m_Contourmarkername("Position") { Tool::m_EventConfig = "DisplayConfigBlockLMB.xml"; } mitk::SegTool2D::~SegTool2D() { } bool mitk::SegTool2D::FilterEvents(InteractionEvent *interactionEvent, DataNode *) { const auto *positionEvent = dynamic_cast(interactionEvent); bool isValidEvent = (positionEvent && // Only events of type mitk::InteractionPositionEvent interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard2D // Only events from the 2D renderwindows ); return isValidEvent; } bool mitk::SegTool2D::DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice) { assert(image); assert(plane); // compare normal of plane to the three axis vectors of the image Vector3D normal = plane->GetNormal(); Vector3D imageNormal0 = image->GetSlicedGeometry()->GetAxisVector(0); Vector3D imageNormal1 = image->GetSlicedGeometry()->GetAxisVector(1); Vector3D imageNormal2 = image->GetSlicedGeometry()->GetAxisVector(2); normal.Normalize(); imageNormal0.Normalize(); imageNormal1.Normalize(); imageNormal2.Normalize(); imageNormal0.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal0.GetVnlVector())); imageNormal1.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal1.GetVnlVector())); imageNormal2.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal2.GetVnlVector())); double eps(0.00001); // axial if (imageNormal2.GetNorm() <= eps) { affectedDimension = 2; } // sagittal else if (imageNormal1.GetNorm() <= eps) { affectedDimension = 1; } // coronal else if (imageNormal0.GetNorm() <= eps) { affectedDimension = 0; } else { affectedDimension = -1; // no idea return false; } // determine slice number in image BaseGeometry *imageGeometry = image->GetGeometry(0); Point3D testPoint = imageGeometry->GetCenter(); Point3D projectedPoint; plane->Project(testPoint, projectedPoint); Point3D indexPoint; imageGeometry->WorldToIndex(projectedPoint, indexPoint); affectedSlice = ROUND(indexPoint[affectedDimension]); MITK_DEBUG << "indexPoint " << indexPoint << " affectedDimension " << affectedDimension << " affectedSlice " << affectedSlice; // check if this index is still within the image if (affectedSlice < 0 || affectedSlice >= static_cast(image->GetDimension(affectedDimension))) return false; return true; } void mitk::SegTool2D::UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, const PlaneGeometry *plane, bool detectIntersection) { std::vector slices = { SliceInformation(slice, plane, 0)}; Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection); } void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; contourInfo.contourNormal = sliceInfo.plane->GetNormal(); contourInfo.contourPoint = sliceInfo.plane->GetOrigin(); mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); } void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection) { if (!m_SurfaceInterpolationEnabled) return; //Remark: the ImageTimeSelector is just needed to extract a timestep/channel of //the image in order to get the image dimension (time dimension and channel dimension //stripped away). Therfore it is OK to always use time step 0 and channel 0 mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(workingImage); timeSelector->SetTimeNr(0); timeSelector->SetChannelNr(0); timeSelector->Update(); const auto dimRefImg = timeSelector->GetOutput()->GetDimension(); if (dimRefImg != 3) return; std::vector contourList; contourList.reserve(sliceInfos.size()); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); std::vector relevantSlices = sliceInfos; if (detectIntersection) { relevantSlices.clear(); for (const auto& sliceInfo : sliceInfos) { // Test whether there is something to extract or whether the slice just contains intersections of others mitk::Image::Pointer slice2 = sliceInfo.slice->Clone(); mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); contourExtractor->SetInput(slice2); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { Self::RemoveContourFromInterpolator(sliceInfo); } else { relevantSlices.push_back(sliceInfo); } } } if (relevantSlices.empty()) return; for (const auto& sliceInfo : relevantSlices) { contourExtractor->SetInput(sliceInfo.slice); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { Self::RemoveContourFromInterpolator(sliceInfo); } else { contour->DisconnectPipeline(); contourList.push_back(contour); } } mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const InteractionPositionEvent *positionEvent, const Image *image, unsigned int component /*= 0*/) { if (!positionEvent) { return nullptr; } assert(positionEvent->GetSender()); // sure, right? const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); // get the timestep of the visible part (time-wise) of the image return GetAffectedImageSliceAs2DImage(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry(), image, timeStep, component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; return SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint), component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const PlaneGeometry *planeGeometry, const Image *image, TimeStepType timeStep, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); // use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); // additionally extract the given component // default is 0; the extractor checks for multi-component images extractor->SetComponent(component); extractor->Modified(); extractor->Update(); Image::Pointer slice = extractor->GetOutput(); return slice; } mitk::Image::Pointer mitk::SegTool2D::GetAffectedWorkingSlice(const InteractionPositionEvent *positionEvent) const { const auto workingNode = this->GetWorkingDataNode(); if (!workingNode) { return nullptr; } const auto *workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { return nullptr; } return GetAffectedImageSliceAs2DImage(positionEvent, workingImage); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const InteractionPositionEvent *positionEvent) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto *referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage); } } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto* referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep); } } void mitk::SegTool2D::Activated() { Superclass::Activated(); this->GetToolManager()->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); m_LastTimePointTriggered = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); } void mitk::SegTool2D::Deactivated() { this->GetToolManager()->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); Superclass::Deactivated(); } void mitk::SegTool2D::OnTimePointChangedInternal() { if (m_IsTimePointChangeAware && nullptr != this->GetWorkingDataNode()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (timePoint != m_LastTimePointTriggered) { m_LastTimePointTriggered = timePoint; this->OnTimePointChanged(); } } } void mitk::SegTool2D::OnTimePointChanged() { //default implementation does nothing } mitk::DataNode* mitk::SegTool2D::GetWorkingDataNode() const { if (nullptr != this->GetToolManager()) { return this->GetToolManager()->GetWorkingData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetWorkingData() const { auto node = this->GetWorkingDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } mitk::DataNode* mitk::SegTool2D::GetReferenceDataNode() const { if (nullptr != this->GetToolManager()) { return this->GetToolManager()->GetReferenceData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetReferenceData() const { auto node = this->GetReferenceDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } void mitk::SegTool2D::WriteBackSegmentationResult(const InteractionPositionEvent *positionEvent, const Image * segmentationResult) { if (!positionEvent) return; const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); const auto *abstractTransformGeometry( dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (planeGeometry && segmentationResult && !abstractTransformGeometry) { const auto workingNode = this->GetWorkingDataNode(); auto *image = dynamic_cast(workingNode->GetData()); const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); this->WriteBackSegmentationResult(planeGeometry, segmentationResult, timeStep); } } void mitk::SegTool2D::WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); Self::WriteBackSegmentationResults(workingNode, { sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image * segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); WriteBackSegmentationResults({ sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResults(const std::vector &sliceList, bool writeSliceToVolume) { if (sliceList.empty()) { return; } if (nullptr == m_LastEventSender) { MITK_WARN << "Cannot write tool results. Tool seems to be in an invalid state, as no interaction event was recieved but is expected."; return; } const auto workingNode = this->GetWorkingDataNode(); mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); // the first geometry is needed otherwise restoring the position is not working const auto* plane3 = dynamic_cast(dynamic_cast( m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) ->GetPlaneGeometry(0)); unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetSlice()->GetPos(); /* A cleaner solution would be to add a contour marker for each slice info. It currently does not work as the contour markers expect that the plane is always the plane of slice 0. Had not the time to do it properly no. Should be solved by T28146*/ this->AddContourmarker(plane3, slicePosition); } void mitk::SegTool2D::WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume) { if (sliceList.empty()) { return; } if (nullptr == workingNode) { mitkThrow() << "Cannot write slice to working node. Working node is invalid."; } auto* image = dynamic_cast(workingNode->GetData()); if (nullptr == image) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } for (const auto& sliceInfo : sliceList) { if (writeSliceToVolume && nullptr != sliceInfo.plane && sliceInfo.slice.IsNotNull()) { mitk::SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } mitk::SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false); // also mark its node as modified (T27308). Can be removed if T27307 // is properly solved if (workingNode != nullptr) workingNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo) { SliceInformation sliceInfo(slice, planeGeometry, timeStep); WriteSliceToVolume(workingImage, sliceInfo , allowUndo); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo) { if (nullptr == workingImage) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } DiffSliceOperation* undoOperation = nullptr; if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // Create undo operation by caching the not yet modified slices mitk::Image::Pointer originalSlice = GetAffectedImageSliceAs2DImage(sliceInfo.plane, workingImage, sliceInfo.timestep); undoOperation = new DiffSliceOperation(workingImage, originalSlice, dynamic_cast(originalSlice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); /*============= END undo/redo feature block ========================*/ } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set the slice as 'input' // casting const away is needed and OK as long the OverwriteMode of // mitkVTKImageOverwrite is true. // Reason: because then the input slice is not touched but // used to overwrite the input of the ExtractSliceFilter. auto noneConstSlice = const_cast(sliceInfo.slice.GetPointer()); reslice->SetInputSlice(noneConstSlice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(workingImage); extractor->SetTimeStep(sliceInfo.timestep); extractor->SetWorldGeometry(sliceInfo.plane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(workingImage->GetGeometry(sliceInfo.timestep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so workingImage->Modified(); workingImage->GetVtkImageData()->Modified(); if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // specify the redo operation with the edited slice auto* doOperation = new DiffSliceOperation(workingImage, extractor->GetOutput(), dynamic_cast(sliceInfo.slice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); // create an operation event for the undo stack OperationEvent* undoStackItem = new OperationEvent(DiffSliceOperationApplier::GetInstance(), doOperation, undoOperation, "Segmentation"); // add it to the undo controller UndoStackItem::IncCurrObjectEventId(); UndoStackItem::IncCurrGroupEventId(); UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); /*============= END undo/redo feature block ========================*/ } } void mitk::SegTool2D::SetShowMarkerNodes(bool status) { m_ShowMarkerNodes = status; } void mitk::SegTool2D::SetEnable3DInterpolation(bool enabled) { m_SurfaceInterpolationEnabled = enabled; } int mitk::SegTool2D::AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex) { if (planeGeometry == nullptr) return -1; us::ServiceReference serviceRef = us::GetModuleContext()->GetServiceReference(); PlanePositionManagerService *service = us::GetModuleContext()->GetService(serviceRef); unsigned int size = service->GetNumberOfPlanePositions(); unsigned int id = service->AddNewPlanePosition(planeGeometry, sliceIndex); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); mitk::Point2D p2 = p1; p2[0] -= planeGeometry->GetSpacing()[0]; p2[1] -= planeGeometry->GetSpacing()[1]; contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); contourMarker->SetPlaneGeometry(planeGeometry->Clone()); std::stringstream markerStream; auto workingNode = this->GetWorkingDataNode(); markerStream << m_Contourmarkername; markerStream << " "; markerStream << id + 1; DataNode::Pointer rotatedContourNode = DataNode::New(); rotatedContourNode->SetData(contourMarker); rotatedContourNode->SetProperty("name", StringProperty::New(markerStream.str())); rotatedContourNode->SetProperty("isContourMarker", BoolProperty::New(true)); rotatedContourNode->SetBoolProperty("PlanarFigureInitializedWindow", true, m_LastEventSender); rotatedContourNode->SetProperty("includeInBoundingBox", BoolProperty::New(false)); rotatedContourNode->SetProperty("helper object", mitk::BoolProperty::New(!m_ShowMarkerNodes)); rotatedContourNode->SetProperty("planarfigure.drawcontrolpoints", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawname", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawoutline", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawshadow", BoolProperty::New(false)); if (planeGeometry) { if (id == size) { this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } else { mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer markers = this->GetToolManager()->GetDataStorage()->GetDerivations(workingNode, isMarker); for (auto iter = markers->begin(); iter != markers->end(); ++iter) { std::string nodeName = (*iter)->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int markerId = atof(nodeName.substr(t + 1).c_str()) - 1; if (id == markerId) { return id; } } this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } } return id; } void mitk::SegTool2D::InteractiveSegmentationBugMessage(const std::string &message) const { MITK_ERROR << "********************************************************************************" << std::endl << " " << message << std::endl << "********************************************************************************" << std::endl << " " << std::endl << " If your image is rotated or the 2D views don't really contain the patient image, try to press the " "button next to the image selection. " << std::endl << " " << std::endl << " Please file a BUG REPORT: " << std::endl << " https://phabricator.mitk.org/" << std::endl << " Contain the following information:" << std::endl << " - What image were you working on?" << std::endl << " - Which region of the image?" << std::endl << " - Which tool did you use?" << std::endl << " - What did you do?" << std::endl << " - What happened (not)? What did you expect?" << std::endl; } void mitk::SegTool2D::WritePreviewOnWorkingImage( Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue) { if (nullptr == targetSlice) { mitkThrow() << "Cannot write preview on working image. Target slice does not point to a valid instance."; } if (nullptr == sourceSlice) { mitkThrow() << "Cannot write preview on working image. Source slice does not point to a valid instance."; } if (nullptr == workingImage) { mitkThrow() << "Cannot write preview on working image. Working image does not point to a valid instance."; } auto constVtkSource = sourceSlice->GetVtkImageData(); /*Need to const cast because Vtk interface does not support const correctly. (or I am not experienced enough to use it correctly)*/ auto nonConstVtkSource = const_cast(constVtkSource); ContourModelUtils::FillSliceInSlice(nonConstVtkSource, targetSlice->GetVtkImageData(), workingImage, paintingPixelValue, 1.0); } + +bool mitk::SegTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent* positionEvent, + const mitk::BaseData* data) +{ + bool isPositionEventInsideImageRegion = + nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); + + if (!isPositionEventInsideImageRegion) + MITK_WARN("EditableContourTool") << "PositionEvent is outside ImageRegion!"; + + return isPositionEventInsideImageRegion; +} diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.h b/Modules/Segmentation/Interactions/mitkSegTool2D.h index 1c58bbf035..ed13ab6d29 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,294 +1,298 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegTool2D_h_Included #define mitkSegTool2D_h_Included #include "mitkCommon.h" #include "mitkImage.h" #include "mitkTool.h" #include #include "mitkInteractionPositionEvent.h" #include "mitkInteractionConst.h" #include "mitkPlanePositionManager.h" #include "mitkRestorePlanePositionOperation.h" #include namespace mitk { class BaseRenderer; /** \brief Abstract base class for segmentation tools. \sa Tool \ingroup Interaction \ingroup ToolManagerEtAl Implements 2D segmentation specific helper methods, that might be of use to all kind of 2D segmentation tools. At the moment these are: - Determination of the slice where the user paints upon (DetermineAffectedImageSlice) - Projection of a 3D contour onto a 2D plane/slice SegTool2D tries to structure the interaction a bit. If you pass "PressMoveRelease" as the interaction type of your derived tool, you might implement the methods OnMousePressed, OnMouseMoved, and OnMouseReleased. Yes, your guess about when they are called is correct. \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT SegTool2D : public Tool { public: mitkClassMacro(SegTool2D, Tool); /** \brief Calculates for a given Image and PlaneGeometry, which slice of the image (in index corrdinates) is meant by the plane. \return false, if no slice direction seems right (e.g. rotated planes) \param image \param plane \param affectedDimension The image dimension, which is constant for all points in the plane, e.g. Axial --> 2 \param affectedSlice The index of the image slice */ static bool DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice); /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param slice the slice from which the contour should be extracted * @param workingImage the segmentation image * @param plane the plane in which the slice lies * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, const PlaneGeometry *plane, bool detectIntersection); /** * \brief Extract the slice of an image that the user just scribbles on. The given component denotes the vector component of an vector image. * * \param positionEvent Event that specifies the plane that should be used to slice * \param image Image that should be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const InteractionPositionEvent* positionEvent, const Image* image, unsigned int component = 0); /** * \brief Extract the slice of an image cut by given plane. The given component denotes the vector component of a vector image. * * \param planeGeometry Geometry defining the slice that should be cut out. * \param image Image that should be sliced * \param timeStep TimeStep of the image that shold be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const PlaneGeometry* planeGeometry, const Image* image, TimeStepType timeStep, unsigned int component = 0); static Image::Pointer GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component = 0); /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * Calls static WriteBackSegmentationResults*/ static void WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep); /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * For more details see protected WriteSliceToVolume version.*/ static void WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo); void SetShowMarkerNodes(bool); /** * \brief Enables or disables the 3D interpolation after writing back the 2D segmentation result, and defaults to * true. */ void SetEnable3DInterpolation(bool); void Activated() override; void Deactivated() override; itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); protected: SegTool2D(); // purposely hidden SegTool2D(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~SegTool2D() override; /** * @brief returns the segmentation node that should be modified by the tool. */ DataNode* GetWorkingDataNode() const; Image* GetWorkingData() const; DataNode* GetReferenceDataNode() const; Image* GetReferenceData() const; /** * This function can be reimplemented by derived classes to react on changes of the current * time point. Default implementation does nothing.*/ virtual void OnTimePointChanged(); struct SliceInformation { mitk::Image::ConstPointer slice; const mitk::PlaneGeometry *plane = nullptr; mitk::TimeStepType timestep = 0; SliceInformation() = default; SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep); }; /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param sliceInfos vector of slice information instances from which the contours should be extracted * @param workingImage the segmentation image * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection); /** * \brief Filters events that cannot be handled by 2D segmentation tools * * Currently an event is discarded if it was not sent by a 2D renderwindow and if it is * not of type InteractionPositionEvent */ bool FilterEvents(InteractionEvent *interactionEvent, DataNode *dataNode) override; /** \brief Extract the slice of the currently selected working image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no working image is selected. */ Image::Pointer GetAffectedWorkingSlice(const InteractionPositionEvent *) const; /** \brief Extract the slice of the currently selected reference image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no reference image is selected. */ Image::Pointer GetAffectedReferenceSlice(const InteractionPositionEvent *) const; /** Overload version that gets the reference slice passed on the passed plane geometry and timestep.*/ Image::Pointer GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const; /** Convenience version that can be called for a given event (which is used to deduce timepoint and plane) and a slice image. * Calls non static WriteBackSegmentationResults*/ void WriteBackSegmentationResult(const InteractionPositionEvent *, const Image* segmentationResult); /** Convenience version that can be called for a given planeGeometry, slice image and time step. * Calls non static WriteBackSegmentationResults*/ void WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image* segmentationResult, TimeStepType timeStep); /** Overloaded version that calls the static version and also adds the contour markers. * @remark If the sliceList is empty, this function does nothing.*/ void WriteBackSegmentationResults(const std::vector &sliceList, bool writeSliceToVolume = true); /** \brief Writes all provided source slices into the data of the passed workingNode. * The function does the following: 1) for every passed slice write it to workingNode (and generate and undo/redo step); * 2) update the surface interpolation and 3) marke the node as modified. * @param workingNode Pointer to the node that contains the working image. * @param sliceList Vector of all slices that should be written into the workingNode. If the list is * empty, the function call does nothing. * @param writeSliceToVolume If set to false the write operation (WriteSliceToVolume will be skipped) * and only the surface interpolation will be updated. * @pre workingNode must point to a valid instance and contain an image instance as data.*/ static void WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume = true); /** Writes the provided source slice into the target slice with the given pixel value. * If passed workingImage is a LabelSetImage the label set rules will be applied when * writing all non zero source pixels into the target slice (e.g. locked lables will not be touched) * with the given paintingPixelValue. * @param targetSlice Pointer to the slice that should be filled with the content of the sourceSlice. * @param sourceSlice Pointer to the slice that is the source/preview every pixel will be (tried to be) transfered . * @param workingImage Will be used to check if LabeSetImageRules have to be applied and the label set state. * @param paintingPixelValue Value that will be used to paint onto target slice. * @pre targetSlice must point to a valid instance. * @pre sourceSlice must point to a valid instance. * @pre workingImage must point to a valid instance.*/ static void WritePreviewOnWorkingImage( Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue); /** Writes a provided slice into the passed working image. The content of working image that is covered * by the slice will be completly overwritten. If asked for it also generates the needed * undo/redo steps. * @param workingImage Pointer to the image that is the target of the write operation. * @param sliceInfo SliceInfo instance that containes the slice image, the defining plane geometry and time step. * @param allowUndo Indicates if undo/redo operations should be registered for the write operation * performed by this call. true: undo/redo will be generated; false: no undo/redo will be generated, so * this operation cannot be revoked by the user. * @pre workingImage must point to a valid instance.*/ static void WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo); /** \brief Adds a new node called Contourmarker to the datastorage which holds a mitk::PlanarFigure. By selecting this node the slicestack will be reoriented according to the passed PlanarFigure's Geometry */ int AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex); void InteractiveSegmentationBugMessage(const std::string &message) const; + /** Helper function to check if a position events points to a point inside the boundingbox of a passed + data instance.*/ + static bool IsPositionEventInsideImageRegion(InteractionPositionEvent* positionEvent, const BaseData* data); + BaseRenderer *m_LastEventSender = nullptr; unsigned int m_LastEventSlice = 0; itkGetMacro(LastTimePointTriggered, TimePointType); private: /** Internal method that gets triggered as soon as the tool manager indicates a * time point change. If the time point has changed since last time and tool * is set to be time point change aware, OnTimePointChanged() will be called.*/ void OnTimePointChangedInternal(); static void RemoveContourFromInterpolator(const SliceInformation& sliceInfo); // The prefix of the contourmarkername. Suffix is a consecutive number const std::string m_Contourmarkername; bool m_ShowMarkerNodes = false; static bool m_SurfaceInterpolationEnabled; bool m_IsTimePointChangeAware = true; TimePointType m_LastTimePointTriggered = 0.; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkTool.h b/Modules/Segmentation/Interactions/mitkTool.h index af0224ea3c..2eec4b6f7b 100644 --- a/Modules/Segmentation/Interactions/mitkTool.h +++ b/Modules/Segmentation/Interactions/mitkTool.h @@ -1,272 +1,273 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #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::EventStateMachine, which can follow any transition pattern that it likes. Every derived tool should always call SuperClass::Deactivated() at the end of its own implementation of Deactivated, because mitk::Tool resets the interaction configuration 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 configuration. 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 state machine name (interactor type). Names and .xml-files for valid state machines can be found in different "Interaction" directories (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. */ + [[deprecated]] virtual const char **GetXPM() const = 0; /** * \brief Returns the path of an icon. * * This icon is preferred to the XPM icon. */ virtual std::string GetIconPath() const { return ""; } /** * \brief Returns the path of a cursor icon. * */ virtual us::ModuleResource GetCursorIconResource() const; /** * @brief Returns the tool button icon of the tool wrapped by a usModuleResource * @return a valid ModuleResource or an invalid if this function * is not reimplemented */ virtual us::ModuleResource GetIconResource() const; /** \brief Returns the name of this tool. Make it short! This name has to fit into some kind of button in most applications, so take some time to think of a good name! */ virtual const char *GetName() const = 0; /** \brief Name of a group. You can group several tools by assigning a group name. Graphical tool selectors might use this information to group tools. (What other reason could there be?) */ virtual const char *GetGroup() const; virtual void InitializeStateMachine(); /** * \brief Interface for GUI creation. * * This is the basic interface for creation of a GUI object belonging to one tool. * * Tools that support a GUI (e.g. for display/editing of parameters) should follow some rules: * * - A Tool and its GUI are two separate classes * - There may be several instances of a GUI at the same time. * - mitk::Tool is toolkit (Qt, wxWidgets, etc.) independent, the GUI part is of course dependent * - The GUI part inherits both from itk::Object and some GUI toolkit class * - The GUI class name HAS to be constructed like "toolkitPrefix" tool->GetClassName() + "toolkitPostfix", e.g. * MyTool -> wxMyToolGUI * - For each supported toolkit there is a base class for tool GUIs, which contains some convenience methods * - Tools notify the GUI about changes using ITK events. The GUI must observe interesting events. * - The GUI base class may convert all ITK events to the GUI toolkit's favoured messaging system (Qt -> signals) * - Calling methods of a tool by its GUI is done directly. * In some cases GUIs don't want to be notified by the tool when they cause a change in a tool. * There is a macro CALL_WITHOUT_NOTICE(method()), which will temporarily disable all notifications during a * method call. */ virtual itk::Object::Pointer GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix); virtual NodePredicateBase::ConstPointer GetReferenceDataPreference() const; virtual NodePredicateBase::ConstPointer GetWorkingDataPreference() const; DataNode::Pointer CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) const; DataNode::Pointer CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) const; /** Function used to check if a tool can handle the referenceData and (if specified) the working data. @pre referenceData must be a valid pointer @param referenceData Pointer to the data that should be checked as valid reference for the tool. @param workingData Pointer to the data that should be checked as valid working data for this tool. This parameter can be null if no working data is specified so far.*/ virtual bool CanHandle(const BaseData *referenceData, const BaseData *workingData) const; protected: friend class ToolManager; virtual void SetToolManager(ToolManager *); /** Returns the pointer to the tool manager of the tool. May be null.*/ ToolManager* GetToolManager() const; /** Returns the data storage provided by the toolmanager. May be null (e.g. if ToolManager is not set).*/ mitk::DataStorage* GetDataStorage() const; void ConnectActionsAndFunctions() override; /** \brief Called when the tool gets activated. Derived tools should call their parents implementation at the beginning of the overriding function. */ virtual void Activated(); /** \brief Called when the tool gets deactivated. Derived tools should call their parents implementation at the end of the overriding function. */ virtual void Deactivated(); /** \brief Let subclasses change their event configuration. */ std::string m_EventConfig; Tool(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~Tool() override; void Notify(InteractionEvent *interactionEvent, bool isHandled) override; bool FilterEvents(InteractionEvent *, DataNode *) override; private: ToolManager* m_ToolManager; // for reference data NodePredicateDataType::Pointer m_PredicateImages; NodePredicateDimension::Pointer m_PredicateDim3; NodePredicateDimension::Pointer m_PredicateDim4; NodePredicateOr::Pointer m_PredicateDimension; NodePredicateAnd::Pointer m_PredicateImage3D; NodePredicateProperty::Pointer m_PredicateBinary; NodePredicateNot::Pointer m_PredicateNotBinary; NodePredicateProperty::Pointer m_PredicateSegmentation; NodePredicateNot::Pointer m_PredicateNotSegmentation; NodePredicateProperty::Pointer m_PredicateHelper; NodePredicateNot::Pointer m_PredicateNotHelper; NodePredicateAnd::Pointer m_PredicateImageColorful; NodePredicateAnd::Pointer m_PredicateImageColorfulNotHelper; NodePredicateAnd::Pointer m_PredicateReference; // for working data NodePredicateAnd::Pointer m_IsSegmentationPredicate; std::string m_InteractorType; std::map m_DisplayInteractionConfigs; const us::Module *m_InteractorModule; }; } // namespace #endif