diff --git a/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp b/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp index a34c556545..a6a9b99fb7 100644 --- a/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp @@ -1,333 +1,330 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkEditableContourTool.h" #include mitk::EditableContourTool::EditableContourTool(const char *type) : FeedbackContourTool(type) {} mitk::EditableContourTool::~EditableContourTool() { } 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("DeletePoint", OnLastSegmentDelete); CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); } void mitk::EditableContourTool::Activated() { Superclass::Activated(); this->ResetToStartState(); this->EnableContourInteraction(true); } void mitk::EditableContourTool::Deactivated() { this->ConfirmSegmentation(); Superclass::Deactivated(); } void mitk::EditableContourTool::ConfirmSegmentation() { auto referenceImage = this->GetReferenceData(); auto workingImage = this->GetWorkingData(); if (nullptr != referenceImage && nullptr != workingImage) { std::vector sliceInfos; sliceInfos.reserve(m_WorkingContours.size()); const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); for (const auto &workingContour : m_WorkingContours) { auto contour = dynamic_cast(workingContour.first->GetData()); if (nullptr == contour || contour->IsEmpty()) continue; auto sameSlicePredicate = [&workingContour, workingImageTimeStep](const SliceInformation &si) { return workingContour.second->IsOnPlane(si.plane) && workingImageTimeStep == si.timestep; }; auto finding = std::find_if(sliceInfos.begin(), sliceInfos.end(), sameSlicePredicate); if (finding == sliceInfos.end()) { auto workingSlice = this->GetAffectedImageSliceAs2DImage(workingContour.second, workingImage, workingImageTimeStep)->Clone(); sliceInfos.emplace_back(workingSlice, workingContour.second, workingImageTimeStep); finding = std::prev(sliceInfos.end()); } // cast const away is OK in this case, because these are all slices created and manipulated // localy in this function call. And we want to keep the high constness of SliceInformation for // public interfaces. auto workingSlice = const_cast(finding->slice.GetPointer()); auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); ContourModelUtils::FillContourInSlice(projectedContour, workingSlice, workingImage, activePixelValue); } this->WriteBackSegmentationResults(sliceInfos); } this->ClearSegmentation(); } void mitk::EditableContourTool::ClearSegmentation() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); this->ResetToStartState(); } 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; } void mitk::EditableContourTool::OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; auto workingDataNode = this->GetWorkingDataNode(); if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) { this->ResetToStartState(); return; } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_Contour = this->CreateNewContour(); m_ContourNode = mitk::DataNode::New(); m_ContourNode->SetData(m_Contour); m_ContourNode->SetName("working contour node"); m_ContourNode->SetProperty("layer", IntProperty::New(100)); m_ContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_ContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_ContourNode->AddProperty("contour.color", ColorProperty::New(1.0f, 1.0f, 0.0f), nullptr, true); m_ContourNode->AddProperty("contour.points.color", ColorProperty::New(1.0f, 0.0f, 0.1f), nullptr, true); m_ContourNode->AddProperty("contour.controlpoints.show", BoolProperty::New(true), nullptr, true); m_PreviewContour = this->CreateNewContour(); m_PreviewContourNode = mitk::DataNode::New(); m_PreviewContourNode->SetData(m_PreviewContour); m_PreviewContourNode->SetName("active livewire 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_EditingContour = this->CreateNewContour(); m_EditingContourNode = mitk::DataNode::New(); m_EditingContourNode->SetData(m_EditingContour); m_EditingContourNode->SetName("editing node"); m_EditingContourNode->SetProperty("layer", IntProperty::New(102)); m_EditingContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_EditingContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_EditingContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_EditingContourNode->AddProperty("contour.points.color", ColorProperty::New(0.0f, 0.0f, 1.0f), nullptr, true); m_EditingContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); m_CurrentRestrictedArea = this->CreateNewContour(); auto dataStorage = this->GetToolManager()->GetDataStorage(); dataStorage->Add(m_ContourNode, workingDataNode); dataStorage->Add(m_PreviewContourNode, workingDataNode); dataStorage->Add(m_ClosureContourNode, workingDataNode); dataStorage->Add(m_EditingContourNode, 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(); } bool mitk::EditableContourTool::OnCheckPoint(const InteractionEvent *interactionEvent) { return false; } 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; } // remove green connection between mouse position and start point m_ClosureContour->Clear(); // Save contour and corresponding plane geometry to list this->m_WorkingContours.emplace_back(std::make_pair(m_ContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); this->m_EditingContours.emplace_back(std::make_pair(m_EditingContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); } -void mitk::EditableContourTool::OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) {} - void mitk::EditableContourTool::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) {} void mitk::EditableContourTool::FinishTool() { auto numberOfTimesteps = static_cast(m_Contour->GetTimeSteps()); for (int i = 0; i <= numberOfTimesteps; ++i) m_Contour->Close(i); this->GetToolManager()->GetDataStorage()->Remove(m_PreviewContourNode); m_PreviewContourNode = nullptr; m_PreviewContour = nullptr; } void mitk::EditableContourTool::ReleaseHelperObjects() { this->RemoveHelperObjects(); m_EditingContours.clear(); m_WorkingContours.clear(); m_RestrictedAreas.clear(); m_EditingContourNode = nullptr; m_EditingContour = nullptr; m_PreviewContourNode = nullptr; m_PreviewContour = nullptr; m_ClosureContourNode = nullptr; m_ClosureContour = nullptr; m_ContourNode = nullptr; m_Contour = nullptr; m_CurrentRestrictedArea = nullptr; } void mitk::EditableContourTool::RemoveHelperObjects() { auto dataStorage = this->GetToolManager()->GetDataStorage(); if (nullptr == dataStorage) return; for (const auto &editingContour : m_EditingContours) dataStorage->Remove(editingContour.first); for (const auto &workingContour : m_WorkingContours) dataStorage->Remove(workingContour.first); if (m_EditingContourNode.IsNotNull()) dataStorage->Remove(m_EditingContourNode); if (m_PreviewContourNode.IsNotNull()) dataStorage->Remove(m_PreviewContourNode); if (m_ClosureContourNode.IsNotNull()) dataStorage->Remove(m_ClosureContourNode); if (m_ContourNode.IsNotNull()) dataStorage->Remove(m_ContourNode); 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(); } if (m_ClosureContour->GetNumberOfVertices() == 0) { m_ClosureContour->AddVertex(m_Contour->GetVertexAt(0)->Coordinates); m_ClosureContour->Update(); } if (m_ClosureContour->GetNumberOfVertices() == 2) { m_ClosureContour->RemoveVertexAt(0); } m_ClosureContour->AddVertexAtFront(endpoint); } diff --git a/Modules/Segmentation/Interactions/mitkEditableContourTool.h b/Modules/Segmentation/Interactions/mitkEditableContourTool.h index 6fb914a6b9..22c36b2158 100644 --- a/Modules/Segmentation/Interactions/mitkEditableContourTool.h +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.h @@ -1,110 +1,107 @@ /*============================================================================ 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 { class MITKSEGMENTATION_EXPORT EditableContourTool : public FeedbackContourTool { public: mitkClassMacro(EditableContourTool, FeedbackContourTool); /// \brief Convert all current contours to binary segmentations. virtual void ConfirmSegmentation(); /// \brief Delete all current contours. virtual void ClearSegmentation(); protected: EditableContourTool(const char *type); ~EditableContourTool() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; /// \brief Initialize tool. virtual void OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Add a control point and finish current segment. virtual void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) = 0; /// \brief Draw a contour according to the mouse movement when mouse button is pressed and mouse is moved. virtual void OnDrawing(StateMachineAction *, InteractionEvent *interactionEvent) = 0; virtual void OnEndDrawing(StateMachineAction *, InteractionEvent *interactionEvent) = 0; /// \brief Actual LiveWire computation. virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) = 0; /// \brief Check double click on first control point to finish the LiveWire tool. virtual bool OnCheckPoint(const InteractionEvent *interactionEvent); /// \brief Finish LiveWire tool. virtual void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); - /// \brief Close the contour. - virtual void OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent); - /// \brief Don't use dynamic cost map for LiveWire calculation. virtual void OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Finish contour interaction. virtual void FinishTool(); virtual void EnableContourInteraction(bool on) = 0; virtual void ReleaseInteractors() = 0; virtual void ReleaseHelperObjects(); virtual void RemoveHelperObjects(); ContourModel::Pointer CreateNewContour() const; virtual void UpdateClosureContour(mitk::Point3D endpoint); bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); mitk::ContourModel::Pointer m_Contour; 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_EditingContour; mitk::DataNode::Pointer m_EditingContourNode; mitk::ContourModel::Pointer m_CurrentRestrictedArea; std::vector m_RestrictedAreas; /** Slice of the reference data the tool is currently actively working on to define contours.*/ mitk::Image::Pointer m_ReferenceDataSlice; std::vector> m_WorkingContours; std::vector> m_EditingContours; PlaneGeometry::ConstPointer m_PlaneGeometry; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp index af6d750ee4..4b7c959c82 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -1,517 +1,456 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, LiveWireTool2D, "LiveWire tool"); } mitk::LiveWireTool2D::LiveWireTool2D() : EditableContourTool("LiveWireTool"), m_SnapClosureContour(false), m_CreateAndUseDynamicCosts(false) { } mitk::LiveWireTool2D::~LiveWireTool2D() { this->ClearSegmentation(); } void mitk::LiveWireTool2D::ConnectActionsAndFunctions() { mitk::EditableContourTool::ConnectActionsAndFunctions(); CONNECT_FUNCTION("MovePoint", OnMouseMoveNoDynamicCosts); } void mitk::LiveWireTool2D::ReleaseInteractors() { this->EnableContourInteraction(false); m_LiveWireInteractors.clear(); } const char **mitk::LiveWireTool2D::GetXPM() const { return mitkLiveWireTool2D_xpm; } us::ModuleResource mitk::LiveWireTool2D::GetIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_48x48.png"); } us::ModuleResource mitk::LiveWireTool2D::GetCursorIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_Cursor_32x32.png"); } const char *mitk::LiveWireTool2D::GetName() const { return "Live Wire"; } void mitk::LiveWireTool2D::UpdateLiveWireContour() { if (m_Contour.IsNotNull()) { auto timeGeometry = m_Contour->GetTimeGeometry()->Clone(); m_PreviewContour = this->m_LiveWireFilter->GetOutput(); m_PreviewContour->SetTimeGeometry(timeGeometry); // needed because the results of the filter are always from 0 ms // to 1 ms and the filter also resets its outputs. m_PreviewContourNode->SetData(this->m_PreviewContour); if (m_SnapClosureContour) { m_ClosureContour = this->m_LiveWireFilterClosure->GetOutput(); } m_ClosureContour->SetTimeGeometry(timeGeometry); // needed because the results of the filter are always from 0 ms // to 1 ms and the filter also resets its outputs. m_ClosureContourNode->SetData(this->m_ClosureContour); } } void mitk::LiveWireTool2D::OnTimePointChanged() { auto reference = this->GetReferenceData(); if (nullptr == reference || m_PlaneGeometry.IsNull() || m_LiveWireFilter.IsNull() || m_PreviewContourNode.IsNull()) return; auto timeStep = reference->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); m_ReferenceDataSlice = GetAffectedImageSliceAs2DImageByTimePoint(m_PlaneGeometry, reference, timeStep); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); m_LiveWireFilter->Update(); m_LiveWireFilterClosure->SetInput(m_ReferenceDataSlice); m_LiveWireFilterClosure->Update(); this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdateAll(); }; void mitk::LiveWireTool2D::EnableContourInteraction(bool on) { for (const auto &interactor : m_LiveWireInteractors) interactor->EnableInteraction(on); } void mitk::LiveWireTool2D::SetSnapClosureContour(bool snap) { m_SnapClosureContour = snap; if (!m_PreviewContour || !m_Contour) { return; } if (m_PreviewContour->GetNumberOfVertices() > 0) { UpdateClosureContour(m_PreviewContour->GetVertexAt(m_PreviewContour->GetNumberOfVertices() - 1)->Coordinates); } else if (m_Contour->GetNumberOfVertices() > 0) { UpdateClosureContour(m_Contour->GetVertexAt(m_Contour->GetNumberOfVertices() - 1)->Coordinates); } this->UpdateLiveWireContour(); } void mitk::LiveWireTool2D::OnInitContour(StateMachineAction *s, InteractionEvent *interactionEvent) { mitk::EditableContourTool::OnInitContour(s, interactionEvent); auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; auto workingDataNode = this->GetWorkingDataNode(); if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) { this->ResetToStartState(); return; } // Set current slice as input for ImageToLiveWireContourFilter m_LiveWireFilter = ImageLiveWireContourModelFilter::New(); m_LiveWireFilter->SetUseCostFunction(true); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); m_LiveWireFilterClosure = ImageLiveWireContourModelFilter::New(); m_LiveWireFilterClosure->SetUseCostFunction(true); m_LiveWireFilterClosure->SetInput(m_ReferenceDataSlice); // Map click to pixel coordinates auto click = positionEvent->GetPositionInWorld(); itk::Index<3> idx; m_ReferenceDataSlice->GetGeometry()->WorldToIndex(click, idx); // Get the pixel with the highest gradient in a 7x7 region itk::Index<3> indexWithHighestGradient; AccessFixedDimensionByItk_2(m_ReferenceDataSlice, FindHighestGradientMagnitudeByITK, 2, idx, indexWithHighestGradient); click[0] = indexWithHighestGradient[0]; click[1] = indexWithHighestGradient[1]; click[2] = indexWithHighestGradient[2]; m_ReferenceDataSlice->GetGeometry()->IndexToWorld(click, click); // Set initial start point m_Contour->AddVertex(click, true); m_LiveWireFilter->SetStartPoint(click); m_LiveWireFilterClosure->SetEndPoint(click); if (!m_SnapClosureContour) { m_ClosureContour->AddVertex(click); } m_CreateAndUseDynamicCosts = true; mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { // Complete LiveWire interaction for the last segment. Add current LiveWire contour to // the finished contour and reset to start a new segment and computation. auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } // Add repulsive points to avoid getting the same path again std::for_each(m_PreviewContour->IteratorBegin(), m_PreviewContour->IteratorEnd(), [this](ContourElement::VertexType *vertex) { ImageLiveWireContourModelFilter::InternalImageType::IndexType idx; this->m_ReferenceDataSlice->GetGeometry()->WorldToIndex(vertex->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); this->m_LiveWireFilterClosure->AddRepulsivePoint(idx); }); // Remove duplicate first vertex, it's already contained in m_Contour m_PreviewContour->RemoveVertexAt(0); // Set last point as control point m_PreviewContour->SetControlVertexAt(m_PreviewContour->GetNumberOfVertices() - 1); // Merge contours m_Contour->Concatenate(m_PreviewContour); // Clear the LiveWire contour and reset the corresponding DataNode m_PreviewContour->Clear(); // Set new start point m_LiveWireFilter->SetStartPoint(positionEvent->GetPositionInWorld()); m_LiveWireFilterClosure->SetStartPoint(positionEvent->GetPositionInWorld()); if (m_CreateAndUseDynamicCosts) { // Use dynamic cost map for next update m_LiveWireFilter->CreateDynamicCostMap(m_Contour); m_LiveWireFilter->SetUseDynamicCostMap(true); m_LiveWireFilterClosure->CreateDynamicCostMap(m_Contour); m_LiveWireFilterClosure->SetUseDynamicCostMap(true); } mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnDrawing(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_PreviewContour->AddVertex(positionEvent->GetPositionInWorld()); m_LiveWireFilter->Update(); UpdateClosureContour(positionEvent->GetPositionInWorld()); m_CurrentRestrictedArea->AddVertex(positionEvent->GetPositionInWorld()); this->UpdateLiveWireContour(); assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnEndDrawing(StateMachineAction *s, InteractionEvent *interactionEvent) { if (m_CurrentRestrictedArea->GetNumberOfVertices() > 1) { auto restrictedArea = m_CurrentRestrictedArea->Clone(); m_RestrictedAreas.push_back(restrictedArea); OnAddPoint(s, interactionEvent); } m_CurrentRestrictedArea = this->CreateNewContour(); } void mitk::LiveWireTool2D::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { // Compute LiveWire segment from last control point to current mouse position auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } m_LiveWireFilter->SetEndPoint(positionEvent->GetPositionInWorld()); m_LiveWireFilter->Update(); UpdateClosureContour(positionEvent->GetPositionInWorld()); this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::UpdateClosureContour(mitk::Point3D endpoint) { if (m_SnapClosureContour) { m_LiveWireFilterClosure->SetStartPoint(endpoint); m_LiveWireFilterClosure->Update(); } else { mitk::EditableContourTool::UpdateClosureContour(endpoint); } } void mitk::LiveWireTool2D::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) { m_LiveWireFilter->SetUseDynamicCostMap(false); m_LiveWireFilterClosure->SetUseDynamicCostMap(false); this->OnMouseMoved(nullptr, interactionEvent); m_LiveWireFilter->SetUseDynamicCostMap(true); m_LiveWireFilterClosure->SetUseDynamicCostMap(true); } bool mitk::LiveWireTool2D::OnCheckPoint(const InteractionEvent *interactionEvent) { // Check double click on first control point to finish the LiveWire tool auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return false; mitk::Point3D click = positionEvent->GetPositionInWorld(); mitk::Point3D first = this->m_Contour->GetVertexAt(0)->Coordinates; return first.EuclideanDistanceTo(click) < 4.5; } void mitk::LiveWireTool2D::OnFinish(StateMachineAction *s, InteractionEvent *interactionEvent) { // Finish LiveWire tool interaction if (!OnCheckPoint(interactionEvent)) { m_Contour->Concatenate(m_ClosureContour); } mitk::EditableContourTool::OnFinish(s, interactionEvent); m_LiveWireFilter->SetUseDynamicCostMap(false); m_LiveWireFilterClosure->SetUseDynamicCostMap(false); this->FinishTool(); } void mitk::LiveWireTool2D::FinishTool() { mitk::EditableContourTool::FinishTool(); m_ContourInteractor = mitk::ContourModelLiveWireInteractor::New(); m_ContourInteractor->SetDataNode(m_ContourNode); m_ContourInteractor->LoadStateMachine("ContourModelModificationInteractor.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetEventConfig("ContourModelModificationConfig.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetWorkingImage(this->m_ReferenceDataSlice); m_ContourInteractor->SetEditingContourModelNode(this->m_EditingContourNode); m_ContourInteractor->SetRestrictedAreas(this->m_RestrictedAreas); m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); this->m_LiveWireInteractors.push_back(m_ContourInteractor); } -void mitk::LiveWireTool2D::OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) -{ - // If last point of current contour will be removed go to start state and remove nodes - if (m_Contour->GetNumberOfVertices() <= 1) - { - auto dataStorage = this->GetToolManager()->GetDataStorage(); - - dataStorage->Remove(m_PreviewContourNode); - dataStorage->Remove(m_ContourNode); - dataStorage->Remove(m_EditingContourNode); - - m_PreviewContour = this->CreateNewContour(); - m_PreviewContourNode->SetData(m_PreviewContour); - - m_Contour = this->CreateNewContour(); - m_ContourNode->SetData(m_Contour); - - this->ResetToStartState(); - } - else // Remove last segment from contour and reset LiveWire contour - { - m_PreviewContour = this->CreateNewContour(); - m_PreviewContourNode->SetData(m_PreviewContour); - - auto newContour = this->CreateNewContour(); - - auto begin = m_Contour->IteratorBegin(); - - // Iterate from last point to next active point - auto newLast = m_Contour->IteratorBegin() + (m_Contour->GetNumberOfVertices() - 1); - - // Go at least one down - if (newLast != begin) - --newLast; - - // Search next active control point - while (newLast != begin && !((*newLast)->IsControlPoint)) - --newLast; - - // Set position of start point for LiveWire filter to coordinates of the new last point - m_LiveWireFilter->SetStartPoint((*newLast)->Coordinates); - //m_LiveWireFilterClosure->SetStartPoint((*newLast)->Coordinates); - - auto it = m_Contour->IteratorBegin(); - - // Fll new Contour - while (it <= newLast) - { - newContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint); - ++it; - } - - newContour->SetClosed(m_Contour->IsClosed()); - - m_ContourNode->SetData(newContour); - m_Contour = newContour; - - mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); - } -} - template void mitk::LiveWireTool2D::FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; const auto MAX_X = inputImage->GetLargestPossibleRegion().GetSize()[0]; const auto MAX_Y = inputImage->GetLargestPossibleRegion().GetSize()[1]; returnIndex[0] = index[0]; returnIndex[1] = index[1]; returnIndex[2] = 0.0; double gradientMagnitude = 0.0; double maxGradientMagnitude = 0.0; // The size and thus the region of 7x7 is only used to calculate the gradient magnitude in that region, // not for searching the maximum value. // Maximum value in each direction for size typename InputImageType::SizeType size; size[0] = 7; size[1] = 7; // Minimum value in each direction for startRegion IndexType startRegion; startRegion[0] = index[0] - 3; startRegion[1] = index[1] - 3; if (startRegion[0] < 0) startRegion[0] = 0; if (startRegion[1] < 0) startRegion[1] = 0; if (MAX_X - index[0] < 7) startRegion[0] = MAX_X - 7; if (MAX_Y - index[1] < 7) startRegion[1] = MAX_Y - 7; index[0] = startRegion[0] + 3; index[1] = startRegion[1] + 3; typename InputImageType::RegionType region; region.SetSize(size); region.SetIndex(startRegion); typedef typename itk::GradientMagnitudeImageFilter GradientMagnitudeFilterType; typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); gradientFilter->SetInput(inputImage); gradientFilter->GetOutput()->SetRequestedRegion(region); gradientFilter->Update(); typename InputImageType::Pointer gradientMagnitudeImage; gradientMagnitudeImage = gradientFilter->GetOutput(); IndexType currentIndex; currentIndex[0] = 0; currentIndex[1] = 0; // Search max (approximate) gradient magnitude for (int x = -1; x <= 1; ++x) { currentIndex[0] = index[0] + x; for (int y = -1; y <= 1; ++y) { currentIndex[1] = index[1] + y; gradientMagnitude = gradientMagnitudeImage->GetPixel(currentIndex); // Check for new max if (maxGradientMagnitude < gradientMagnitude) { maxGradientMagnitude = gradientMagnitude; returnIndex[0] = currentIndex[0]; returnIndex[1] = currentIndex[1]; returnIndex[2] = 0.0; } } currentIndex[1] = index[1]; } } diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h index bf9d91f17b..c37fd32b57 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h @@ -1,120 +1,117 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkLiveWireTool2D_h #define mitkLiveWireTool2D_h #include #include namespace mitk { /** \brief A 2D segmentation tool based on a LiveWire approach. The contour between the last point and the current mouse position is computed by searching the shortest path according to specific features of the image. The contour thus tends to snap to the boundary of objects. The tool always assumes that unconfirmed contours are always defined for the current time point. So the time step in which the contours will be stored as segmentations will be determined when the contours got confirmed. Then they will be transfered to the slices of the currently selected time step. Changing the time point/time step while tool is active will updated the working slice the live wire filter. So the behavior of the active live wire contour is always WYSIWYG (What you see is what you get). \sa SegTool2D \sa ImageLiveWireContourModelFilter \ingroup Interaction \ingroup ToolManagerEtAl \warning Only to be instantiated by mitk::ToolManager. */ class MITKSEGMENTATION_EXPORT LiveWireTool2D : public EditableContourTool { public: mitkClassMacro(LiveWireTool2D, SegTool2D); itkFactorylessNewMacro(Self); us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; const char **GetXPM() const override; void SetSnapClosureContour(bool snap); protected: LiveWireTool2D(); ~LiveWireTool2D() override; void ConnectActionsAndFunctions() override; void UpdateLiveWireContour(); void OnTimePointChanged() override; private: /// \brief Initialize tool. void OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Add a control point and finish current segment. void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Draw a contour according to the mouse movement when mouse button is pressed and mouse is moved. void OnDrawing(StateMachineAction *, InteractionEvent *interactionEvent) override; void OnEndDrawing(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Actual LiveWire computation. void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Check double click on first control point to finish the LiveWire tool. bool OnCheckPoint(const InteractionEvent *interactionEvent) override; /// \brief Finish LiveWire tool. void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) override; - /// \brief Close the contour. - void OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) override; - /// \brief Don't use dynamic cost map for LiveWire calculation. void OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) override; /// \brief Finish contour interaction. void FinishTool() override; void EnableContourInteraction(bool on) override; void ReleaseInteractors() override; template void FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex); void UpdateClosureContour(mitk::Point3D endpoint) override; bool m_SnapClosureContour; mitk::ContourModelLiveWireInteractor::Pointer m_ContourInteractor; mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilter; mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilterClosure; bool m_CreateAndUseDynamicCosts; std::vector m_LiveWireInteractors; }; } #endif diff --git a/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml b/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml index 1388d1d8c9..bc6ab3f44f 100644 --- a/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml +++ b/Modules/Segmentation/Resources/Interactions/LiveWireTool.xml @@ -1,38 +1,32 @@ - - - - - -