diff --git a/Modules/Core/include/mitkITKEventObserverGuard.h b/Modules/Core/include/mitkITKEventObserverGuard.h index fb8903f116..c2a509286d 100644 --- a/Modules/Core/include/mitkITKEventObserverGuard.h +++ b/Modules/Core/include/mitkITKEventObserverGuard.h @@ -1,93 +1,93 @@ /*============================================================================ 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 mitkITKEventObserverGuard_h #define mitkITKEventObserverGuard_h #include #include #include "MitkCoreExports.h" namespace itk { class Object; class Command; class EventObject; } namespace mitk { /** \brief Convenience class that helps to manage the lifetime of itk event observers. This helper class can be used to ensure itk event observers are removed form a sender object at the end of a certain scope. This class behaves similar to a std::unique_ptr but for event observers. Therefore the observer will be removed from the sender when one of the following conditions are met: - the guard is destroyed - - the guard is reseted (by Reset() or operator = ) + - the guard is resetted (by Reset() or operator = ) Sample usage: \code { auto &objRef = *o.GetPointer(); auto guard = ITKEventObserverGuard(o, itk::AnyEvent(), [&objRef](const itk::EventObject &event) { std::cout << "Object: " << objRef.GetNameOfClass() << " Event: " << event << std::endl; }); //some code } //now the guard is destroyed \endcode This will add an Observer to o executing the lambda for any event as long as the guard exists. @remark If the sender is already destroyed at the moment, when the guard wants to remove the observer, the removal will be skipped. */ class MITKCORE_EXPORT ITKEventObserverGuard { public: ITKEventObserverGuard(); ITKEventObserverGuard(const itk::Object* sender, unsigned long observerTag); ITKEventObserverGuard(const itk::Object* sender, const itk::EventObject& event, itk::Command* command); ITKEventObserverGuard(const itk::Object* sender, const itk::EventObject& event, std::function function); ITKEventObserverGuard(ITKEventObserverGuard&&); ITKEventObserverGuard& operator=(ITKEventObserverGuard&&); ~ITKEventObserverGuard(); /** Resets the guard by removing the currently guarded observer. After the reset the guard is uninitialized. *@remark resetting an uninitialized guard has no effect.*/ void Reset(); /** Resets the guard by first removing the currently guarded observer. Then the passed observer tag for the * passed sender will be guarded.*/ void Reset(const itk::Object* sender, unsigned long observerTag); /** Resets the guard by first removing the currently guarded observer. Then a observer will be added for * the passed sender with the passed event and command. The new observer is now guarded.*/ void Reset(const itk::Object* sender, const itk::EventObject& event, itk::Command* command); /** Resets the guard by first removing the currently guarded observer. Then a observer will be added for * the passed sender with the passed event and lambda function. The new observer is now guarded.*/ void Reset(const itk::Object* sender, const itk::EventObject& event, std::function function); bool IsInitialized() const; private: struct Impl; std::unique_ptr m_ITKEventObserverGuardImpl; }; } #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index 6a259e5039..58dc2c7acc 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1472 +1,1499 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSlicesInterpolator.h" #include "QmitkRenderWindow.h" #include "QmitkRenderWindowWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include #include "mitkToolManager.h" #include "mitkUndoController.h" #include #include #include #include #include #include #include #include #include #include #include #include // Includes for the merge operation #include "mitkImageToContourFilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { template itk::SmartPointer GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const QmitkSlicesInterpolator::ActionToSliceDimensionMapType QmitkSlicesInterpolator::CreateActionToSlicer(const QList& windows) { std::map actionToSliceDimension; for (auto* window : windows) { std::string windowName; auto renderWindowWidget = dynamic_cast(window->parentWidget()); if (renderWindowWidget) { windowName = renderWindowWidget->GetCornerAnnotationText(); } else { windowName = window->GetRenderer()->GetName(); } auto slicer = window->GetSliceNavigationController(); actionToSliceDimension[new QAction(QString::fromStdString(windowName), nullptr)] = slicer; } return actionToSliceDimension; } mitk::Image::Pointer ExtractSliceFromImage(mitk::Image* image, const mitk::PlaneGeometry * contourPlane, unsigned int timeStep) { vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(contourPlane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Update(); mitk::Image::Pointer slice = extractor->GetOutput(); return slice; } QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), m_Interpolator(mitk::SegmentationInterpolationController::New()), m_SurfaceInterpolator(mitk::SurfaceInterpolationController::GetInstance()), m_ToolManager(nullptr), m_Initialized(false), m_LastSNC(nullptr), m_LastSliceIndex(0), m_2DInterpolationEnabled(false), m_3DInterpolationEnabled(false), m_CurrentActiveLabelValue(0), m_FirstRun(true) { m_GroupBoxEnableExclusiveInterpolationMode = new QGroupBox("Interpolation", this); QVBoxLayout *vboxLayout = new QVBoxLayout(m_GroupBoxEnableExclusiveInterpolationMode); m_EdgeDetector = mitk::FeatureBasedEdgeDetectionFilter::New(); m_PointScorer = mitk::PointCloudScoringFilter::New(); m_CmbInterpolation = new QComboBox(m_GroupBoxEnableExclusiveInterpolationMode); m_CmbInterpolation->addItem("Disabled"); m_CmbInterpolation->addItem("2-Dimensional"); m_CmbInterpolation->addItem("3-Dimensional"); vboxLayout->addWidget(m_CmbInterpolation); m_BtnApply2D = new QPushButton("Confirm for single slice", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply2D); m_BtnApplyForAllSlices2D = new QPushButton("Confirm for all slices", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApplyForAllSlices2D); m_BtnApply3D = new QPushButton("Confirm", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnApply3D); m_BtnReinit3DInterpolation = new QPushButton("Reinit Interpolation", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_BtnReinit3DInterpolation); m_ChkShowPositionNodes = new QCheckBox("Show Position Nodes", m_GroupBoxEnableExclusiveInterpolationMode); vboxLayout->addWidget(m_ChkShowPositionNodes); this->HideAllInterpolationControls(); connect(m_CmbInterpolation, SIGNAL(currentIndexChanged(int)), this, SLOT(OnInterpolationMethodChanged(int))); connect(m_BtnApply2D, SIGNAL(clicked()), this, SLOT(OnAcceptInterpolationClicked())); connect(m_BtnApplyForAllSlices2D, SIGNAL(clicked()), this, SLOT(OnAcceptAllInterpolationsClicked())); connect(m_BtnApply3D, SIGNAL(clicked()), this, SLOT(OnAccept3DInterpolationClicked())); connect(m_BtnReinit3DInterpolation, SIGNAL(clicked()), this, SLOT(OnReinit3DInterpolation())); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SLOT(OnShowMarkers(bool))); connect(m_ChkShowPositionNodes, SIGNAL(toggled(bool)), this, SIGNAL(SignalShowMarkerNodes(bool))); QHBoxLayout *layout = new QHBoxLayout(this); layout->addWidget(m_GroupBoxEnableExclusiveInterpolationMode); this->setLayout(layout); itk::ReceptorMemberCommand::Pointer command = itk::ReceptorMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationInfoChanged); InterpolationInfoChangedObserverTag = m_Interpolator->AddObserver(itk::ModifiedEvent(), command); itk::ReceptorMemberCommand::Pointer command2 = itk::ReceptorMemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged); SurfaceInterpolationInfoChangedObserverTag = m_SurfaceInterpolator->AddObserver(itk::ModifiedEvent(), command2); auto command3 = itk::ReceptorMemberCommand::New(); command3->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnInterpolationAborted); InterpolationAbortedObserverTag = m_Interpolator->AddObserver(itk::AbortEvent(), command3); // feedback node and its visualization properties m_FeedbackNode = mitk::DataNode::New(); mitk::CoreObjectFactory::GetInstance()->SetDefaultProperties(m_FeedbackNode); m_FeedbackNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(255.0, 255.0, 0.0)); m_FeedbackNode->SetProperty("texture interpolation", mitk::BoolProperty::New(false)); m_FeedbackNode->SetProperty("layer", mitk::IntProperty::New(20)); m_FeedbackNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_FeedbackNode->SetProperty("name", mitk::StringProperty::New("Interpolation feedback")); m_FeedbackNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_FeedbackNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode = mitk::DataNode::New(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->SetProperty("name", mitk::StringProperty::New("Surface Interpolation feedback")); m_InterpolatedSurfaceNode->SetProperty("opacity", mitk::FloatProperty::New(0.5)); m_InterpolatedSurfaceNode->SetProperty("line width", mitk::FloatProperty::New(4.0f)); m_InterpolatedSurfaceNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_InterpolatedSurfaceNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_InterpolatedSurfaceNode->SetVisibility(false); QWidget::setContentsMargins(0, 0, 0, 0); if (QWidget::layout() != nullptr) { QWidget::layout()->setContentsMargins(0, 0, 0, 0); } // For running 3D Interpolation in background // create a QFuture and a QFutureWatcher connect(&m_Watcher, SIGNAL(started()), this, SLOT(StartUpdateInterpolationTimer())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(OnSurfaceInterpolationFinished())); connect(&m_Watcher, SIGNAL(finished()), this, SLOT(StopUpdateInterpolationTimer())); m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(ChangeSurfaceColor())); } void QmitkSlicesInterpolator::SetDataStorage(mitk::DataStorage::Pointer storage) { if (m_DataStorage == storage) { return; } if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } m_DataStorage = storage; m_SurfaceInterpolator->SetDataStorage(storage); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.AddListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); } } void QmitkSlicesInterpolator::SetActiveLabelValue(mitk::LabelSetImage::LabelValueType labelValue) { bool changedValue = labelValue != this->m_CurrentActiveLabelValue; this->m_CurrentActiveLabelValue = labelValue; if (changedValue) this->OnActiveLabelChanged(labelValue); }; mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::InitializeWindow(QmitkRenderWindow* window) { auto slicer = window->GetSliceNavigationController(); if (slicer == nullptr) { MITK_WARN << "Tried setting up interpolation for a render window that does not have a slice navigation controller set"; return; } // Has to be initialized m_LastSNC = slicer; itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag[slicer] = slicer->AddObserver(itk::DeleteEvent(), deleteCommand); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand); } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList& windows) { Q_ASSERT(!windows.empty()); if (m_Initialized) { // remove old observers this->Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag = timeNavigationController->AddObserver(mitk::TimeNavigationController::TimeEvent(0), timeChangedCommand); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); // connect to the slice navigation controller. after each change, call the interpolator for (auto* window : windows) { this->InitializeWindow(window); } m_ActionToSlicerMap = CreateActionToSlicer(windows); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); timeNavigationController->RemoveObserver(m_ControllerToTimeObserverTag); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } m_ActionToSlicerMap.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers this->Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) m_DataStorage->Remove(m_InterpolatedSurfaceNode); } // remove observer m_Interpolator->RemoveObserver(InterpolationAbortedObserverTag); m_Interpolator->RemoveObserver(InterpolationInfoChangedObserverTag); m_SurfaceInterpolator->RemoveObserver(SurfaceInterpolationInfoChangedObserverTag); m_SurfaceInterpolator->SetCurrentInterpolationSession(nullptr); delete m_Timer; } /** External enableization... */ void QmitkSlicesInterpolator::setEnabled(bool enable) { QWidget::setEnabled(enable); // Set the gui elements of the different interpolation modi enabled if (enable) { if (m_2DInterpolationEnabled) { this->Show2DInterpolationControls(true); m_Interpolator->Activate2DInterpolation(true); } else if (m_3DInterpolationEnabled) { this->Show3DInterpolationControls(true); this->Show3DInterpolationResult(true); } } // Set all gui elements of the interpolation disabled else { this->HideAllInterpolationControls(); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::On2DInterpolationEnabled(bool status) { OnInterpolationActivated(status); m_Interpolator->Activate2DInterpolation(status); } void QmitkSlicesInterpolator::On3DInterpolationEnabled(bool status) { On3DInterpolationActivated(status); } void QmitkSlicesInterpolator::OnInterpolationDisabled(bool status) { if (status) { OnInterpolationActivated(!status); On3DInterpolationActivated(!status); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::HideAllInterpolationControls() { this->Show2DInterpolationControls(false); this->Show3DInterpolationControls(false); } void QmitkSlicesInterpolator::Show2DInterpolationControls(bool show) { m_BtnApply2D->setVisible(show); m_BtnApplyForAllSlices2D->setVisible(show); } void QmitkSlicesInterpolator::Show3DInterpolationControls(bool show) { m_BtnApply3D->setVisible(show); m_ChkShowPositionNodes->setVisible(show); m_BtnReinit3DInterpolation->setVisible(show); } void QmitkSlicesInterpolator::OnInterpolationMethodChanged(int index) { switch (index) { case 0: // Disabled m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation"); this->HideAllInterpolationControls(); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(false); break; case 1: // 2D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show2DInterpolationControls(true); this->OnInterpolationActivated(true); this->On3DInterpolationActivated(false); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { if (m_ToolManager->GetWorkingData(0) != nullptr) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_InterpolatedSurfaceNode); m_InterpolatedSurfaceNode->SetData(nullptr); m_BtnReinit3DInterpolation->setEnabled(false); m_CmbInterpolation->setCurrentIndex(0); return; } // Updating the current selected segmentation for the 3D interpolation this->SetCurrentContourListID(); if (m_2DInterpolationEnabled) { OnInterpolationActivated(true); // re-initialize if needed } } void QmitkSlicesInterpolator::OnToolManagerReferenceDataModified() { } void QmitkSlicesInterpolator::OnTimeChanged(itk::Object *sender, const itk::EventObject &e) { if (!dynamic_cast(&e)) { return; } const auto* timeNavigationController = dynamic_cast(sender); if (nullptr == timeNavigationController) { return; } bool timeChanged = m_TimePoint != timeNavigationController->GetSelectedTimePoint(); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (timeChanged) { if (m_3DInterpolationEnabled) { m_InterpolatedSurfaceNode->SetData(nullptr); } m_SurfaceInterpolator->Modified(); } if (nullptr == m_LastSNC) { return; } if (TranslateAndInterpolateChangedSlice(m_LastSNC->GetCreatedWorldGeometry())) { m_LastSNC->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { if (!dynamic_cast(&e)) { return; } auto sliceNavigationController = dynamic_cast(sender); if (nullptr == sliceNavigationController) { return; } if(m_2DInterpolationEnabled) { this->On2DInterpolationEnabled(m_2DInterpolationEnabled); } if (TranslateAndInterpolateChangedSlice(e, sliceNavigationController)) { sliceNavigationController->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject& e, mitk::SliceNavigationController* sliceNavigationController) { const mitk::SliceNavigationController::GeometrySliceEvent* event = dynamic_cast(&e); mitk::TimeGeometry* timeGeometry = event->GetTimeGeometry(); m_LastSNC = sliceNavigationController; return this->TranslateAndInterpolateChangedSlice(timeGeometry); } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const mitk::TimeGeometry* timeGeometry) { if (!m_2DInterpolationEnabled) { return false; } if (nullptr == timeGeometry) { return false; } if (!timeGeometry->IsValidTimePoint(m_TimePoint)) { return false; } mitk::SlicedGeometry3D* slicedGeometry = dynamic_cast(timeGeometry->GetGeometryForTimePoint(m_TimePoint).GetPointer()); if (nullptr == slicedGeometry) { return false; } mitk::PlaneGeometry* plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(m_LastSNC->GetStepper()->GetPos())); if (nullptr == plane) { return false; } this->Interpolate(plane); return true; } void QmitkSlicesInterpolator::Interpolate(mitk::PlaneGeometry *plane) { if (nullptr == m_ToolManager) { return; } mitk::DataNode* node = m_ToolManager->GetWorkingData(0); if (nullptr == node) { return; } m_Segmentation = dynamic_cast(node->GetData()); if (nullptr == m_Segmentation) { return; } if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot interpolate WorkingImage. Passed time point is not within the time bounds of WorkingImage. " "Time point: " << m_TimePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); int clickedSliceDimension = -1; int clickedSliceIndex = -1; // calculate real slice position, i.e. slice of the image mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_FeedbackNode->SetData(interpolation); // maybe just have a variable that stores the active label color. if (m_ToolManager) { auto* workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { auto* activeLabel = dynamic_cast(workingNode->GetData())->GetActiveLabel(); if (nullptr != activeLabel) { auto activeColor = activeLabel->GetColor(); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } } } m_LastSliceIndex = clickedSliceIndex; } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode && workingNode->GetData()) { const auto segmentation = dynamic_cast(workingNode->GetData()); if (segmentation == nullptr) { MITK_ERROR << "Run3DInterpolation triggered with no MultiLabelSegmentation as working data."; return; } mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(segmentation, m_CurrentActiveLabelValue, segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint)); if (interpolatedSurface.IsNotNull()) { m_BtnApply3D->setEnabled(true);; m_InterpolatedSurfaceNode->SetData(interpolatedSurface); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } } else { m_BtnApply3D->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } } m_BtnReinit3DInterpolation->setEnabled(true); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { auto* workingNode = m_ToolManager->GetWorkingData(0); auto* planeGeometry = m_LastSNC->GetCurrentPlaneGeometry(); auto* interpolatedPreview = dynamic_cast(m_FeedbackNode->GetData()); if (nullptr == workingNode || nullptr == interpolatedPreview) return; auto* segmentationImage = dynamic_cast(workingNode->GetData()); if (nullptr == segmentationImage) return; if (!segmentationImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the " "time bounds of segmentation. Time point: " << m_TimePoint; return; } const auto timeStep = segmentationImage->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); auto interpolatedSlice = mitk::SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, segmentationImage, timeStep)->Clone(); auto activeValue = segmentationImage->GetActiveLabel()->GetValue(); mitk::TransferLabelContentAtTimeStep( interpolatedPreview, interpolatedSlice, segmentationImage->GetConstLabelsByValue(segmentationImage->GetLabelValuesByGroup(segmentationImage->GetActiveLayer())), timeStep, 0, mitk::LabelSetImage::UNLABELED_VALUE, false, { {0, mitk::LabelSetImage::UNLABELED_VALUE}, {1, activeValue} } ); mitk::SegTool2D::WriteBackSegmentationResult(workingNode, planeGeometry, interpolatedSlice, timeStep); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { /* * What exactly is done here: * 1. We create an empty diff image for the current segmentation * 2. All interpolated slices are written into the diff image * 3. Then the diffimage is applied to the original segmentation */ if (m_Segmentation) { mitk::Image::Pointer segmentation3D = m_Segmentation; unsigned int timeStep = 0; if (4 == m_Segmentation->GetDimension()) { const auto* geometry = m_Segmentation->GetTimeGeometry(); if (!geometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not " "within the time bounds of segmentation. Time point: " << m_TimePoint; return; } mitk::Image::Pointer activeLabelImage; try { auto labelSetImage = dynamic_cast(m_Segmentation); activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabel()->GetValue()); } catch (const std::exception& e) { MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; } m_Interpolator->SetSegmentationVolume(activeLabelImage); timeStep = geometry->TimePointToTimeStep(m_TimePoint); auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); segmentation3D = timeSelector->GetOutput(); } // Create an empty diff image for the undo operation auto diffImage = mitk::Image::New(); diffImage->Initialize(segmentation3D); // Create scope for ImageWriteAccessor so that the accessor is destroyed right after use { mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero auto pixelType = mitk::MakeScalarPixelType(); // For legacy purpose support former pixel type of segmentations (before multilabel) if (itk::IOComponentEnum::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType(); memset(accessor.GetData(), 0, pixelType.GetSize() * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered auto slicedGeometry = m_Segmentation->GetSlicedGeometry(); auto planeGeometry = slicer->GetCurrentPlaneGeometry()->Clone(); int sliceDimension = -1; int sliceIndex = -1; mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, planeGeometry, sliceDimension, sliceIndex); const auto numSlices = m_Segmentation->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->AddStepsToDo(numSlices); std::atomic_uint totalChangedSlices; // Reuse interpolation algorithm instance for each slice to cache boundary calculations auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); // Distribute slice interpolations to multiple threads const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); std::vector> sliceIndices(numThreads); for (std::remove_const_t sliceIndex = 0; sliceIndex < numSlices; ++sliceIndex) sliceIndices[sliceIndex % numThreads].push_back(sliceIndex); std::vector threads; threads.reserve(numThreads); // This lambda will be executed by the threads auto interpolate = [=, &interpolator = m_Interpolator, &totalChangedSlices](unsigned int threadIndex) { auto clonedPlaneGeometry = planeGeometry->Clone(); auto origin = clonedPlaneGeometry->GetOrigin(); // Go through the sliced indices for (auto sliceIndex : sliceIndices[threadIndex]) { slicedGeometry->WorldToIndex(origin, origin); origin[sliceDimension] = sliceIndex; slicedGeometry->IndexToWorld(origin, origin); clonedPlaneGeometry->SetOrigin(origin); auto interpolation = interpolator->Interpolate(sliceDimension, sliceIndex, clonedPlaneGeometry, timeStep, algorithm); if (interpolation.IsNotNull()) { // Setting up the reslicing pipeline which allows us to write the interpolation results back into the image volume auto reslicer = vtkSmartPointer::New(); // Set overwrite mode to true to write back to the image volume reslicer->SetInputSlice(interpolation->GetSliceData()->GetVtkImageAccessor(interpolation)->GetVtkImageData()); reslicer->SetOverwriteMode(true); reslicer->Modified(); auto diffSliceWriter = mitk::ExtractSliceFilter::New(reslicer); diffSliceWriter->SetInput(diffImage); diffSliceWriter->SetTimeStep(0); diffSliceWriter->SetWorldGeometry(clonedPlaneGeometry); diffSliceWriter->SetVtkOutputRequest(true); diffSliceWriter->SetResliceTransformByGeometry(diffImage->GetTimeGeometry()->GetGeometryForTimeStep(0)); diffSliceWriter->Modified(); diffSliceWriter->Update(); ++totalChangedSlices; } mitk::ProgressBar::GetInstance()->Progress(); } }; m_Interpolator->EnableSliceImageCache(); // Do the interpolation here. for (size_t threadIndex = 0; threadIndex < numThreads; ++threadIndex) { interpolate(threadIndex); } m_Interpolator->DisableSliceImageCache(); const mitk::Label::PixelType newDestinationLabel = dynamic_cast(m_Segmentation)->GetActiveLabel()->GetValue(); // Do and Undo Operations if (totalChangedSlices > 0) { // Create do/undo operations auto* doOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); auto* undoOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); undoOp->SetFactor(-1.0); auto comment = "Confirm all interpolations (" + std::to_string(totalChangedSlices) + ")"; auto* undoStackItem = new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment); mitk::OperationEvent::IncCurrGroupEventId(); mitk::OperationEvent::IncCurrObjectEventId(); mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); mitk::DiffImageApplier::GetInstanceForUndo()->SetDestinationLabel(newDestinationLabel); // Apply the changes to the original image mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } m_FeedbackNode->SetData(nullptr); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::FinishInterpolation(mitk::SliceNavigationController *slicer) { // this redirect is for calling from outside if (slicer == nullptr) OnAcceptAllInterpolationsClicked(); else AcceptAllInterpolations(slicer); } void QmitkSlicesInterpolator::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); for (auto it = m_ActionToSlicerMap.begin(); it != m_ActionToSlicerMap.end(); ++it) { orientationPopup.addAction(it->first); } connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { auto referenceImage = GetData(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); auto labelSetImage = dynamic_cast(segmentationDataNode->GetData()); auto activeLabelColor = labelSetImage->GetActiveLabel()->GetColor(); std::string activeLabelName = labelSetImage->GetActiveLabel()->GetName(); auto segmentation = GetData(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint) || !segmentationGeometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Current time point is not within the time bounds of the patient image and segmentation."; return; } auto interpolatedSurface = GetData(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::IOComponentEnum::USHORT == segmentation->GetPixelType().GetComponentType()); surfaceToImageFilter->SetInput(interpolatedSurface); surfaceToImageFilter->Update(); mitk::Image::Pointer interpolatedSegmentation = surfaceToImageFilter->GetOutput(); auto timeStep = segmentationGeometry->TimePointToTimeStep(m_TimePoint); const mitk::Label::PixelType newDestinationLabel = labelSetImage->GetActiveLabel()->GetValue(); TransferLabelContentAtTimeStep( interpolatedSegmentation, labelSetImage, labelSetImage->GetConstLabelsByValue(labelSetImage->GetLabelValuesByGroup(labelSetImage->GetActiveLayer())), timeStep, 0, 0, false, {{1, newDestinationLabel}}, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); this->Show3DInterpolationResult(false); std::string name = segmentationDataNode->GetName() + " 3D-interpolation - " + activeLabelName; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); interpolatedSurfaceDataNode->SetColor(activeLabelColor); m_DataStorage->Add(interpolatedSurfaceDataNode, segmentationDataNode); } void QmitkSlicesInterpolator::OnReinit3DInterpolation() { // Step 1. Load from the isContourPlaneGeometry nodes the contourNodes. mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(m_ToolManager->GetWorkingData(0), pred); if (contourNodes->Size() != 0) { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } mitk::SurfaceInterpolationController::CPIVector newCPIs; // Adding label and timeStep information for the contourNodes. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto contourNode = it->Value(); auto labelID = dynamic_cast(contourNode->GetProperty("labelID"))->GetValue(); auto timeStep = dynamic_cast(contourNode->GetProperty("timeStep"))->GetValue(); auto planeGeometry = dynamic_cast(contourNode->GetData())->GetPlaneGeometry(); auto groupID = labelSetImage->GetGroupIndexOfLabel(labelID); auto sliceImage = ExtractSliceFromImage(labelSetImage->GetGroupImage(groupID), planeGeometry, timeStep); mitk::ImageToContourFilter::Pointer contourExtractor = mitk::ImageToContourFilter::New(); contourExtractor->SetInput(sliceImage); contourExtractor->SetContourValue(labelID); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) continue; contour->DisconnectPipeline(); newCPIs.emplace_back(contour, planeGeometry->Clone(),labelID,timeStep); } m_SurfaceInterpolator->CompleteReinitialization(newCPIs); } catch(const std::exception& e) { MITK_ERROR << "Exception thrown casting toolmanager working data to labelsetImage"; } } } else { m_BtnApply3D->setEnabled(false); QMessageBox errorInfo; errorInfo.setWindowTitle("Reinitialize surface interpolation"); errorInfo.setIcon(QMessageBox::Information); errorInfo.setText("No contours available for the selected segmentation!"); errorInfo.exec(); } } void QmitkSlicesInterpolator::OnAcceptAllPopupActivated(QAction *action) { try { auto iter = m_ActionToSlicerMap.find(action); if (iter != m_ActionToSlicerMap.end()) { mitk::SliceNavigationController *slicer = iter->second; this->AcceptAllInterpolations(slicer); } } catch (...) { /* Showing message box with possible memory error */ QMessageBox errorInfo; errorInfo.setWindowTitle("Interpolation Process"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during interpolation. Possible cause: Not enough memory!"); errorInfo.exec(); std::cerr << "Ill construction in " __FILE__ " l. " << __LINE__ << std::endl; } } void QmitkSlicesInterpolator::OnInterpolationActivated(bool on) { m_2DInterpolationEnabled = on; try { if (m_DataStorage.IsNotNull()) { if (on && !m_DataStorage->Exists(m_FeedbackNode)) { m_DataStorage->Add(m_FeedbackNode); } } } catch (...) { // don't care (double add/remove) } if (m_ToolManager) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::DataNode *referenceNode = m_ToolManager->GetReferenceData(0); QWidget::setEnabled(workingNode != nullptr); m_BtnApply2D->setEnabled(on); m_FeedbackNode->SetVisibility(on); if (!on) { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } if (workingNode) { auto labelSetImage = dynamic_cast(workingNode->GetData()); if (nullptr == labelSetImage) { MITK_ERROR << "NO LABELSETIMAGE IN WORKING NODE\n"; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } const auto* activeLabel = labelSetImage->GetActiveLabel(); const auto* segmentation = dynamic_cast(workingNode->GetData()); if (nullptr != activeLabel && nullptr != segmentation) { auto activeLabelImage = labelSetImage->CreateLabelMask(activeLabel->GetValue()); m_Interpolator->SetSegmentationVolume(activeLabelImage); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { auto workingNode = m_ToolManager->GetWorkingData(0); if (workingNode == nullptr) { MITK_ERROR << "Run3DInterpolation triggered with no working data set."; return; } const auto segmentation = dynamic_cast(workingNode->GetData()); if (segmentation == nullptr) { MITK_ERROR << "Run3DInterpolation triggered with no MultiLabelSegmentation as working data."; return; } + if (!segmentation->ExistLabel(m_CurrentActiveLabelValue)) + { + MITK_ERROR << "Run3DInterpolation triggered with no valid label selected. Currently selected invalid label: "<Interpolate(segmentation,m_CurrentActiveLabelValue,segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint)); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { if(m_ToolManager) { const auto* workingNode = m_ToolManager->GetWorkingData(0); const auto activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabel()->GetColor(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } m_Timer->stop(); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_3DWINDOWS); } void QmitkSlicesInterpolator::On3DInterpolationActivated(bool on) { m_3DInterpolationEnabled = on; try { // this->PrepareInputsFor3DInterpolation(); m_SurfaceInterpolator->Modified(); } catch (...) { MITK_ERROR << "Error with 3D surface interpolation!"; } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::EnableInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated OnInterpolationActivated(on); } void QmitkSlicesInterpolator::Enable3DInterpolation(bool on) { // only to be called from the outside world // just a redirection to OnInterpolationActivated this->On3DInterpolationActivated(on); } void QmitkSlicesInterpolator::UpdateVisibleSuggestion() { mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnInterpolationInfoChanged(const itk::EventObject & /*e*/) { // something (e.g. undo) changed the interpolation info, we should refresh our display this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::OnInterpolationAborted(const itk::EventObject& /*e*/) { m_CmbInterpolation->setCurrentIndex(0); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::OnSurfaceInterpolationInfoChanged(const itk::EventObject & /*e*/) { + auto workingNode = m_ToolManager->GetWorkingData(0); + + if (workingNode == nullptr) + { + MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no working data set."; + return; + } + + const auto segmentation = dynamic_cast(workingNode->GetData()); + + if (segmentation == nullptr) + { + MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no MultiLabelSegmentation as working data."; + return; + } + + if (!segmentation->ExistLabel(m_CurrentActiveLabelValue)) + { + MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no valid label selected. Currently selected invalid label: " << m_CurrentActiveLabelValue; + return; + } + if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { - m_InterpolatedSurfaceNode->SetData(nullptr); m_Future = QtConcurrent::run(&QmitkSlicesInterpolator::Run3DInterpolation, this); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode) { QWidget::setEnabled(true); if (!workingNode->GetData()->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << m_TimePoint; return; } m_SurfaceInterpolator->SetDistanceImageVolume(50000); auto segmentationImage = dynamic_cast(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnActiveLabelChanged(mitk::Label::PixelType) { m_FeedbackNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_SurfaceInterpolator->Modified(); } if (m_2DInterpolationEnabled) { m_FeedbackNode->SetData(nullptr); this->OnInterpolationActivated(true); m_LastSNC->SendSlice(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::CheckSupportedImageDimension() { if (m_ToolManager->GetWorkingData(0)) { m_Segmentation = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (m_3DInterpolationEnabled && m_Segmentation && ((m_Segmentation->GetDimension() != 3) || (m_Segmentation->GetDimension() != 4)) ) { QMessageBox info; info.setWindowTitle("3D Interpolation Process"); info.setIcon(QMessageBox::Information); info.setText("3D Interpolation is only supported for 3D/4D images at the moment!"); info.exec(); m_CmbInterpolation->setCurrentIndex(0); } } } void QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject & /*e*/) { // Don't know how to avoid const_cast here?! mitk::SliceNavigationController *slicer = dynamic_cast(const_cast(sender)); if (slicer) { m_ControllerToSliceObserverTag.remove(slicer); m_ControllerToDeleteObserverTag.remove(slicer); } } void QmitkSlicesInterpolator::WaitForFutures() { if (m_Watcher.isRunning()) { m_Watcher.waitForFinished(); } if (m_PlaneWatcher.isRunning()) { m_PlaneWatcher.waitForFinished(); } } void QmitkSlicesInterpolator::NodeRemoved(const mitk::DataNode* node) { if ((m_ToolManager && m_ToolManager->GetWorkingData(0) == node) || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } } diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp index d1f68616ec..a9011e69f7 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp @@ -1,876 +1,876 @@ /*============================================================================ 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 #include #include #include #include #include #include struct CPICache { mitk::SurfaceInterpolationController::CPIVector cpis; itk::TimeStamp cpiTimeStamp; mitk::Surface::Pointer cachedSurface; }; typedef std::map CPITimeStepMap; typedef std::map CPITimeStepLabelMap; typedef std::map CPITimeStepLabelSegMap; CPITimeStepLabelSegMap cpiMap; std::shared_mutex cpiMutex; std::map segmentationObserverTags; std::map labelRemovedObserverTags; mitk::SurfaceInterpolationController::SurfaceInterpolationController() : m_DistanceImageVolume(50000), m_SelectedSegmentation(nullptr) { } mitk::SurfaceInterpolationController::~SurfaceInterpolationController() { this->RemoveObservers(); } void mitk::SurfaceInterpolationController::RemoveObservers() { // Removing all observers while (segmentationObserverTags.size()) { this->RemoveObserversInternal(segmentationObserverTags.begin()->first); } } mitk::SurfaceInterpolationController *mitk::SurfaceInterpolationController::GetInstance() { static mitk::SurfaceInterpolationController::Pointer m_Instance; if (m_Instance.IsNull()) { m_Instance = SurfaceInterpolationController::New(); } return m_Instance; } void mitk::SurfaceInterpolationController::AddNewContours(const std::vector& newCPIs, bool reinitializationAction, bool silent) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return; for (auto cpi : newCPIs) { if (cpi.Contour->GetVtkPolyData()->GetNumberOfPoints() > 0) { this->AddToCPIMap(cpi, reinitializationAction); } } if (!silent) this->Modified(); } mitk::DataNode* GetSegmentationImageNodeInternal(mitk::DataStorage* ds, const mitk::LabelSetImage* seg) { if (nullptr == ds) return nullptr; if (nullptr == seg) return nullptr; mitk::DataNode* segmentationNode = nullptr; mitk::NodePredicateDataUID::Pointer dataUIDPredicate = mitk::NodePredicateDataUID::New(seg->GetUID()); auto dataNodeObjects = ds->GetSubset(dataUIDPredicate); if (dataNodeObjects->Size() != 0) { for (auto it = dataNodeObjects->Begin(); it != dataNodeObjects->End(); ++it) { segmentationNode = it->Value(); } } else { MITK_ERROR << "Unable to find the labelSetImage with the desired UID."; } return segmentationNode; } mitk::DataNode* mitk::SurfaceInterpolationController::GetSegmentationImageNode() const { if (m_DataStorage.IsNull()) return nullptr; auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return nullptr; return GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation); } mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const { DataStorage::SetOfObjects::Pointer relevantNodes = DataStorage::SetOfObjects::New(); if (m_DataStorage.IsNotNull()) { //remove relevant plane nodes auto nodes = this->GetPlaneGeometryNodeFromDataStorage(segNode, labelValue); for (auto it = nodes->Begin(); it != nodes->End(); ++it) { auto aTS = static_cast(dynamic_cast(it->Value()->GetProperty("timeStep"))->GetValue()); bool sameTS = (timeStep == aTS); if (sameTS) { relevantNodes->push_back(it->Value()); } } } return relevantNodes; } mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const { auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); auto isCorrectLabel = NodePredicateProperty::New("labelID", mitk::UShortProperty::New(labelValue)); auto searchPredicate = NodePredicateAnd::New(isContourPlaneGeometry, isCorrectLabel); mitk::DataStorage::SetOfObjects::ConstPointer result; if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, searchPredicate); return result; } mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const { auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer result; if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, isContourPlaneGeometry); return result; } void mitk::SurfaceInterpolationController::AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow()<< "Cannot add plane geometries. No valid segmentation selected."; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimeStep(contourInfo.TimeStep)) { MITK_ERROR << "Invalid time point requested in AddPlaneGeometryNodeToDataStorage."; return; } if (m_DataStorage.IsNull()) { MITK_DEBUG << "Cannot add plane geometry nodes. No data storage is set."; return; } auto planeGeometry = contourInfo.Plane; if (planeGeometry) { auto segmentationNode = this->GetSegmentationImageNode(); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = this->GetPlaneGeometryNodeFromDataStorage(segmentationNode, contourInfo.LabelValue, contourInfo.TimeStep); mitk::DataNode::Pointer contourPlaneGeometryDataNode; // Go through the pre-existing contours and check if the contour position matches them. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto planeData = dynamic_cast(it->Value()->GetData()); if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); if (samePlane) { contourPlaneGeometryDataNode = it->Value(); break; } } // Go through the contourPlaneGeometry Data and add the segmentationNode to it. if (contourPlaneGeometryDataNode.IsNull()) { auto planeGeometryData = mitk::PlanarCircle::New(); planeGeometryData->SetPlaneGeometry(planeGeometry->Clone()); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); planeGeometryData->PlaceFigure(p1); planeGeometryData->SetCurrentControlPoint(p1); planeGeometryData->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); std::string contourName = "contourPlane L " + std::to_string(contourInfo.LabelValue) + " T " + std::to_string(contourInfo.TimeStep); contourPlaneGeometryDataNode = mitk::DataNode::New(); contourPlaneGeometryDataNode->SetData(planeGeometryData); // No need to change properties contourPlaneGeometryDataNode->SetProperty("helper object", mitk::BoolProperty::New(false)); contourPlaneGeometryDataNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetProperty("isContourPlaneGeometry", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetVisibility(false); // Need to change properties contourPlaneGeometryDataNode->SetProperty("name", mitk::StringProperty::New(contourName) ); contourPlaneGeometryDataNode->SetProperty("labelID", mitk::UShortProperty::New(contourInfo.LabelValue)); contourPlaneGeometryDataNode->SetProperty("timeStep", mitk::IntProperty::New(contourInfo.TimeStep)); contourPlaneGeometryDataNode->SetData(planeGeometryData); m_DataStorage->Add(contourPlaneGeometryDataNode, segmentationNode); } } } void mitk::SurfaceInterpolationController::AddToCPIMap(ContourPositionInformation& contourInfo, bool reinitializationAction) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return; if (!selectedSegmentation->GetTimeGeometry()->IsValidTimeStep(contourInfo.TimeStep)) { MITK_ERROR << "Invalid time step requested for interpolation pipeline."; return; } if (contourInfo.Plane == nullptr) { MITK_ERROR << "contourInfo plane is null."; return; } if (contourInfo.Contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { this->RemoveContour(contourInfo); MITK_DEBUG << "contourInfo contour is empty."; return; } { std::lock_guard guard(cpiMutex); const auto& currentTimeStep = contourInfo.TimeStep; const auto& currentLabelValue = contourInfo.LabelValue; auto& currentImageContours = cpiMap[selectedSegmentation]; auto& currentLabelContours = currentImageContours[currentLabelValue]; auto& currentCPICache = currentLabelContours[currentTimeStep]; auto& currentContourList = currentCPICache.cpis; auto finding = std::find_if(currentContourList.begin(), currentContourList.end(), [contourInfo](const ContourPositionInformation& element) {return contourInfo.Plane->IsOnPlane(element.Plane); }); if (finding != currentContourList.end()) { MITK_DEBUG << "CPI already exists. CPI is updated. Label: "<< currentLabelValue << "; Time Step: " << currentTimeStep; *finding = contourInfo; } else { currentContourList.push_back(contourInfo); } currentCPICache.cpiTimeStamp.Modified(); } if (!reinitializationAction) { this->AddPlaneGeometryNodeToDataStorage(contourInfo); } } bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo, bool keepPlaceholderForUndo) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { return false; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimeStep(contourInfo.TimeStep)) { return false; } bool removedIt = false; { std::lock_guard cpiGuard(cpiMutex); const auto currentTimeStep = contourInfo.TimeStep; const auto currentLabel = contourInfo.LabelValue; auto& cpiCache = cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep); auto it = cpiCache.cpis.begin(); while (it != cpiCache.cpis.end()) { const ContourPositionInformation& currentContour = (*it); if (currentContour.Plane->IsOnPlane(contourInfo.Plane)) { if (keepPlaceholderForUndo) { it->Contour = nullptr; } else { cpiCache.cpis.erase(it); } cpiCache.cpiTimeStamp.Modified(); removedIt = true; if (m_DataStorage.IsNotNull()) { mitk::DataNode::Pointer contourPlaneGeometryDataNode; auto contourNodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(m_DataStorage, selectedSegmentation), currentLabel, currentTimeStep); // Go through the nodes and check if the contour position matches them. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto planeData = dynamic_cast(it->Value()->GetData()); if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); if (samePlane) { m_DataStorage->Remove(it->Value()); break; } } } break; } ++it; } } return removedIt; } void mitk::SurfaceInterpolationController::AddActiveLabelContoursForInterpolation(ReduceContourSetFilter* reduceFilter, const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { const auto& currentImageContours = cpiMap.at(segmentationImage); auto finding = currentImageContours.find(labelValue); if (finding == currentImageContours.end()) { MITK_INFO << "Contours for label don't exist. Label value: " << labelValue; return; } const auto& currentLabelContoursMap = finding->second; auto tsfinding = currentLabelContoursMap.find(timeStep); if (tsfinding == currentLabelContoursMap.end()) { MITK_INFO << "Contours for current time step don't exist."; return; } const auto& currentContours = tsfinding->second.cpis; unsigned int index = 0; for (const auto& cpi : currentContours) { if (!cpi.IsPlaceHolder()) { reduceFilter->SetInput(index, cpi.Contour); ++index; } } } bool CPICacheIsOutdated(const mitk::LabelSetImage* segmentationImage, mitk::LabelSetImage::LabelValueType labelValue, mitk::TimeStepType timeStep) { const auto& currentImageContours = cpiMap.at(segmentationImage); auto finding = currentImageContours.find(labelValue); if (finding == currentImageContours.end()) { return false; } const auto& currentLabelContoursMap = finding->second; auto tsfinding = currentLabelContoursMap.find(timeStep); if (tsfinding == currentLabelContoursMap.end()) { return false; } bool result = tsfinding->second.cachedSurface.IsNull() || tsfinding->second.cachedSurface->GetMTime() < tsfinding->second.cpiTimeStamp.GetMTime(); return result; } void SetCPICacheSurface(mitk::Surface* surface, const mitk::LabelSetImage* segmentationImage, mitk::LabelSetImage::LabelValueType labelValue, mitk::TimeStepType timeStep) { const auto& currentImageContours = cpiMap.at(segmentationImage); auto finding = currentImageContours.find(labelValue); if (finding == currentImageContours.end()) { return; } const auto& currentLabelContoursMap = finding->second; auto tsfinding = currentLabelContoursMap.find(timeStep); if (tsfinding == currentLabelContoursMap.end()) { return; } cpiMap[segmentationImage][labelValue][timeStep].cachedSurface = surface; } void mitk::SurfaceInterpolationController::Interpolate(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { if (nullptr == segmentationImage) { mitkThrow() << "Cannot interpolate contours. No valid segmentation passed."; } std::lock_guard guard(cpiMutex); auto it = cpiMap.find(segmentationImage); if (it == cpiMap.end()) { mitkThrow() << "Cannot interpolate contours. Passed segmentation is not registered at controller."; } if (!segmentationImage->ExistLabel(labelValue)) { - mitkThrow() << "Cannot interpolate contours. None existant label request. Invalid label:" << labelValue; + mitkThrow() << "Cannot interpolate contours. None existent label request. Invalid label:" << labelValue; } if (!segmentationImage->GetTimeGeometry()->IsValidTimeStep(timeStep)) { mitkThrow() << "Cannot interpolate contours. No valid time step requested. Invalid time step:" << timeStep; } if (!CPICacheIsOutdated(segmentationImage, labelValue, timeStep)) return; mitk::Surface::Pointer interpolationResult = nullptr; auto reduceFilter = ReduceContourSetFilter::New(); auto normalsFilter = ComputeContourSetNormalsFilter::New(); auto interpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); const auto spacing = segmentationImage->GetGeometry(timeStep)->GetSpacing(); double minSpacing = 100; double maxSpacing = 0; for (int i = 0; i < 3; i++) { if (spacing[i] < minSpacing) { minSpacing = spacing[i]; } if (spacing[i] > maxSpacing) { maxSpacing = spacing[i]; } } reduceFilter->SetMinSpacing(minSpacing); reduceFilter->SetMaxSpacing(maxSpacing); normalsFilter->SetMaxSpacing(maxSpacing); interpolateSurfaceFilter->SetDistanceImageVolume(m_DistanceImageVolume); reduceFilter->SetUseProgressBar(false); normalsFilter->SetUseProgressBar(true); normalsFilter->SetProgressStepSize(1); interpolateSurfaceFilter->SetUseProgressBar(true); interpolateSurfaceFilter->SetProgressStepSize(7); // Set reference image for interpolation surface filter itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(segmentationImage); timeSelector->SetTimeNr(timeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); interpolateSurfaceFilter->SetReferenceImage(itkImage.GetPointer()); try { this->AddActiveLabelContoursForInterpolation(reduceFilter, segmentationImage, labelValue, timeStep); reduceFilter->Update(); auto currentNumberOfReducedContours = reduceFilter->GetNumberOfOutputs(); if (currentNumberOfReducedContours < 2) { // If no interpolation is possible reset the interpolation result MITK_INFO << "Interpolation impossible: not enough contours."; } else { normalsFilter->SetSegmentationBinaryImage(refSegImage); for (size_t i = 0; i < currentNumberOfReducedContours; ++i) { mitk::Surface::Pointer reducedContour = reduceFilter->GetOutput(i); reducedContour->DisconnectPipeline(); normalsFilter->SetInput(i, reducedContour); interpolateSurfaceFilter->SetInput(i, normalsFilter->GetOutput(i)); } // Setting up progress bar mitk::ProgressBar::GetInstance()->AddStepsToDo(10); // create a surface from the distance-image auto imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); imageToSurfaceFilter->SetInput(interpolateSurfaceFilter->GetOutput()); imageToSurfaceFilter->SetThreshold(0); imageToSurfaceFilter->SetSmooth(true); imageToSurfaceFilter->SetSmoothIteration(1); imageToSurfaceFilter->Update(); interpolationResult = mitk::Surface::New(); interpolationResult->Expand(segmentationImage->GetTimeSteps()); auto geometry = segmentationImage->GetTimeGeometry()->Clone(); geometry->ReplaceTimeStepGeometries(mitk::Geometry3D::New()); interpolationResult->SetTimeGeometry(geometry); interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), timeStep); interpolationResult->DisconnectPipeline(); // Last progress step mitk::ProgressBar::GetInstance()->Progress(20); } } catch (const Exception& e) { MITK_ERROR << "Interpolation failed: " << e.what(); interpolationResult = nullptr; } SetCPICacheSurface(interpolationResult, segmentationImage, labelValue, timeStep); } mitk::Surface::Pointer mitk::SurfaceInterpolationController::GetInterpolationResult(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { if (nullptr == segmentationImage) { mitkThrow() << "Cannot interpolate contours. No valid segmentation passed."; } std::shared_lock guard(cpiMutex); if (cpiMap.find(segmentationImage) == cpiMap.end()) { mitkThrow() << "Cannot interpolate contours. Passed segmentation is not registered at controller."; } if (!segmentationImage->ExistLabel(labelValue)) { - mitkThrow() << "Cannot interpolate contours. None existant label request. Invalid label:" << labelValue; + mitkThrow() << "Cannot interpolate contours. None existent label request. Invalid label:" << labelValue; } if (!segmentationImage->GetTimeGeometry()->IsValidTimeStep(timeStep)) { mitkThrow() << "Cannot interpolate contours. No valid time step requested. Invalid time step:" << timeStep; } const auto& currentImageContours = cpiMap.at(segmentationImage); auto finding = currentImageContours.find(labelValue); if (finding == currentImageContours.end()) { return nullptr; } const auto& currentLabelContoursMap = finding->second; auto tsfinding = currentLabelContoursMap.find(timeStep); if (tsfinding == currentLabelContoursMap.end()) { return nullptr; } return tsfinding->second.cachedSurface; } void mitk::SurfaceInterpolationController::SetDataStorage(DataStorage::Pointer ds) { m_DataStorage = ds; } void mitk::SurfaceInterpolationController::SetDistanceImageVolume(unsigned int distImgVolume) { m_DistanceImageVolume = distImgVolume; } mitk::LabelSetImage* mitk::SurfaceInterpolationController::GetCurrentSegmentation() { return m_SelectedSegmentation.Lock(); } unsigned int mitk::SurfaceInterpolationController::GetNumberOfInterpolationSessions() { return cpiMap.size(); } template void mitk::SurfaceInterpolationController::GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result) { result->Graft(input); } void mitk::SurfaceInterpolationController::SetCurrentInterpolationSession(mitk::LabelSetImage* currentSegmentationImage) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (currentSegmentationImage == selectedSegmentation) { return; } m_SelectedSegmentation = currentSegmentationImage; selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNotNull()) { std::lock_guard guard(cpiMutex); auto it = cpiMap.find(selectedSegmentation); if (it == cpiMap.end()) { cpiMap[selectedSegmentation] = CPITimeStepLabelMap(); auto command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); segmentationObserverTags[selectedSegmentation] = selectedSegmentation->AddObserver(itk::DeleteEvent(), command); auto& controller = *this; const auto sender = selectedSegmentation.GetPointer(); labelRemovedObserverTags[selectedSegmentation] = selectedSegmentation->AddObserver(mitk::LabelRemovedEvent(), [&controller, sender](const itk::EventObject& event) { controller.OnRemoveLabel(sender, event); }); } } } void mitk::SurfaceInterpolationController::RemoveInterpolationSession(const mitk::LabelSetImage* segmentationImage) { if (nullptr != segmentationImage) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation == segmentationImage) { this->SetCurrentInterpolationSession(nullptr); } { std::lock_guard guard(cpiMutex); this->RemoveObserversInternal(segmentationImage); cpiMap.erase(segmentationImage); if (m_DataStorage.IsNotNull()) { auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, segmentationImage)); this->m_DataStorage->Remove(nodes); } } } } void mitk::SurfaceInterpolationController::RemoveObserversInternal(const mitk::LabelSetImage* segmentationImage) { auto pos = segmentationObserverTags.find(const_cast(segmentationImage)); if (pos != segmentationObserverTags.end()) { pos->first->RemoveObserver((*pos).second); segmentationObserverTags.erase(const_cast(segmentationImage)); } auto pos2 = labelRemovedObserverTags.find(const_cast(segmentationImage)); if (pos2 != labelRemovedObserverTags.end()) { pos2->first->RemoveObserver((*pos2).second); labelRemovedObserverTags.erase(const_cast(segmentationImage)); } } void mitk::SurfaceInterpolationController::RemoveAllInterpolationSessions() { while (!cpiMap.empty()) { this->RemoveInterpolationSession(cpiMap.begin()->first); } } void mitk::SurfaceInterpolationController::RemoveContours(const LabelSetImage* segmentationImage, mitk::Label::PixelType label, TimeStepType timeStep) { if (nullptr == segmentationImage) { mitkThrow() << "Cannot remove contours. No valid segmentation passed."; } std::lock_guard guard(cpiMutex); auto segfinding = cpiMap.find(segmentationImage); if (segfinding != cpiMap.end()) { auto& cpiLabelMap = cpiMap[segmentationImage]; auto finding = cpiLabelMap.find(label); if (finding != cpiLabelMap.end()) { cpiLabelMap[label].erase(timeStep); } if (m_DataStorage.IsNotNull()) { //remove relevant plane nodes auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, segmentationImage), label, timeStep); this->m_DataStorage->Remove(nodes); } this->Modified(); } } void mitk::SurfaceInterpolationController::RemoveContours(const LabelSetImage* segmentationImage, mitk::Label::PixelType label) { if (nullptr == segmentationImage) { mitkThrow() << "Cannot remove contours. No valid segmentation passed."; } std::lock_guard guard(cpiMutex); auto finding = cpiMap.find(segmentationImage); if (finding != cpiMap.end()) { cpiMap[segmentationImage].erase(label); if (m_DataStorage.IsNotNull()) { //remove relevant plane nodes auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, segmentationImage), label); this->m_DataStorage->Remove(nodes); } this->Modified(); } } void mitk::SurfaceInterpolationController::OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject & /*event*/) { auto tempImage = dynamic_cast(const_cast(caller)); if (tempImage) { this->RemoveInterpolationSession(tempImage); } } void mitk::SurfaceInterpolationController::OnRemoveLabel(const itk::Object* caller, const itk::EventObject& event) { auto sendingSegmentation = dynamic_cast(caller); auto removeEvent = dynamic_cast(&event); if (nullptr != sendingSegmentation && nullptr != removeEvent) { this->RemoveContours(sendingSegmentation, removeEvent->GetLabelValue()); } } mitk::SurfaceInterpolationController::CPIVector* mitk::SurfaceInterpolationController::GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation == nullptr) return nullptr; std::shared_lock guard(cpiMutex); auto labelFinding = cpiMap[selectedSegmentation].find(labelValue); if (labelFinding != cpiMap[selectedSegmentation].end()) { auto tsFinding = labelFinding->second.find(timeStep); if (tsFinding != labelFinding->second.end()) { return &(tsFinding->second.cpis); } } return nullptr; } std::vector mitk::SurfaceInterpolationController::GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const { std::lock_guard guard(cpiMutex); std::vector result; auto finding = cpiMap.find(seg); if (finding == cpiMap.end()) return result; const auto& currentImageContours = cpiMap[seg]; for (const auto& [label, contours] : currentImageContours) { auto tsFinding = contours.find(timeStep); if (tsFinding != contours.end()) { const auto& cpis = contours.at(timeStep).cpis; auto finding = std::find_if(cpis.begin(), cpis.end(), [plane](const ContourPositionInformation& element) {return plane->IsOnPlane(element.Plane); }); if (finding != cpis.end()) { result.push_back(label); } } } return result; } void mitk::SurfaceInterpolationController::CompleteReinitialization(const std::vector& newCPIs) { this->ClearInterpolationSession(); // Now the layers should be empty and the new layers can be added. this->AddNewContours(newCPIs, true); } void mitk::SurfaceInterpolationController::ClearInterpolationSession() { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation != nullptr) { std::lock_guard guard(cpiMutex); cpiMap[selectedSegmentation].clear(); } }