diff --git a/Modules/QtWidgets/include/QmitkMxNMultiWidget.h b/Modules/QtWidgets/include/QmitkMxNMultiWidget.h index 702191a81e..2724e47848 100644 --- a/Modules/QtWidgets/include/QmitkMxNMultiWidget.h +++ b/Modules/QtWidgets/include/QmitkMxNMultiWidget.h @@ -1,135 +1,136 @@ /*============================================================================ 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 QmitkMxNMultiWidget_h #define QmitkMxNMultiWidget_h #include "MitkQtWidgetsExports.h" // qt widgets module #include "QmitkAbstractMultiWidget.h" #include #include #include class QSplitter; /** * @brief The 'QmitkMxNMultiWidget' is a 'QmitkAbstractMultiWidget' that is used to display multiple render windows at once. * Render windows can dynamically be added and removed to change the layout of the multi widget. This * is done by using the 'SetLayout'-function to define a layout. This will automatically add or remove * the appropriate number of render window widgets. */ class MITKQTWIDGETS_EXPORT QmitkMxNMultiWidget : public QmitkAbstractMultiWidget { Q_OBJECT public: QmitkMxNMultiWidget(QWidget* parent = nullptr, Qt::WindowFlags f = 0, const QString& multiWidgetName = "mxnmulti"); ~QmitkMxNMultiWidget(); void InitializeMultiWidget() override; void Synchronize(bool synchronized) override; QmitkRenderWindow* GetRenderWindow(const QString& widgetName) const override; QmitkRenderWindow* GetRenderWindow(const mitk::AnatomicalPlane& orientation) const override; void SetActiveRenderWindowWidget(RenderWindowWidgetPointer activeRenderWindowWidget) override; /** * @brief Initialize the active render windows of the MxNMultiWidget to the given geometry. * * @param geometry The geometry to be used to initialize / update the * active render window's time and slice navigation controller. * @param resetCamera If true, the camera and crosshair will be reset to the default view (centered, no zoom). * If false, the current crosshair position and the camera zoom will be stored and reset * after the reference geometry has been updated. */ void InitializeViews(const mitk::TimeGeometry* geometry, bool resetCamera) override; /** * @brief Forward the given time geometry to all base renderers, so that they can store it as their * interaction reference geometry. * This will update the alignment status of the reference geometry for each base renderer. * For more details, see 'BaseRenderer::SetInteractionReferenceGeometry'. * Overridem from 'QmitkAbstractMultiWidget'. */ void SetInteractionReferenceGeometry(const mitk::TimeGeometry* referenceGeometry) override; /** * @brief Returns true if the render windows are coupled; false if not. * * For the MxNMultiWidget the render windows are typically decoupled. */ bool HasCoupledRenderWindows() const override; void SetSelectedPosition(const mitk::Point3D& newPosition, const QString& widgetName) override; const mitk::Point3D GetSelectedPosition(const QString& widgetName) const override; void SetCrosshairVisibility(bool visible) override; bool GetCrosshairVisibility() const override; void SetCrosshairGap(unsigned int gapSize) override; void ResetCrosshair() override; void SetWidgetPlaneMode(int userMode) override; mitk::SliceNavigationController* GetTimeNavigationController(); void EnableCrosshair(); void DisableCrosshair(); public Q_SLOTS: // mouse events void wheelEvent(QWheelEvent* e) override; void mousePressEvent(QMouseEvent* e) override; void moveEvent(QMoveEvent* e) override; void LoadLayout(const nlohmann::json* jsonData); void SaveLayout(std::ostream* outStream); Q_SIGNALS: void WheelMoved(QWheelEvent *); void Moved(); void UpdateUtilityWidgetViewPlanes(); + void LayoutChanged(); protected: void RemoveRenderWindowWidget() override; private: void SetLayoutImpl() override; void SetInteractionSchemeImpl() override { } QmitkAbstractMultiWidget::RenderWindowWidgetPointer CreateRenderWindowWidget(); void SetInitialSelection(); void ToggleSynchronization(QmitkSynchronizedNodeSelectionWidget* synchronizedWidget); static nlohmann::json BuildJSONFromLayout(const QSplitter* splitter); QSplitter* BuildLayoutFromJSON(const nlohmann::json* jsonData, unsigned int* windowCounter, QSplitter* parentSplitter = nullptr); mitk::SliceNavigationController* m_TimeNavigationController; std::unique_ptr m_SynchronizedWidgetConnector; bool m_CrosshairVisibility; }; #endif diff --git a/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp b/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp index b95255b87c..3b2f599304 100644 --- a/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp +++ b/Modules/QtWidgets/src/QmitkMxNMultiWidget.cpp @@ -1,632 +1,633 @@ /*============================================================================ 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 "QmitkMxNMultiWidget.h" // mitk core #include #include #include #include #include #include // mitk qt widget #include #include // qt #include #include #include #include QmitkMxNMultiWidget::QmitkMxNMultiWidget(QWidget* parent, Qt::WindowFlags f/* = 0*/, const QString& multiWidgetName/* = "mxnmulti"*/) : QmitkAbstractMultiWidget(parent, f, multiWidgetName) , m_TimeNavigationController(nullptr) , m_SynchronizedWidgetConnector(std::make_unique()) , m_CrosshairVisibility(false) { m_TimeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); } QmitkMxNMultiWidget::~QmitkMxNMultiWidget() { auto allRenderWindows = this->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { m_TimeNavigationController->Disconnect(renderWindow->GetSliceNavigationController()); } } void QmitkMxNMultiWidget::InitializeMultiWidget() { SetLayout(1, 1); SetDisplayActionEventHandler(std::make_unique()); auto displayActionEventHandler = GetDisplayActionEventHandler(); if (nullptr != displayActionEventHandler) { displayActionEventHandler->InitActions(); } this->SetInitialSelection(); } void QmitkMxNMultiWidget::Synchronize(bool synchronized) { if (synchronized) { SetDisplayActionEventHandler(std::make_unique()); } else { SetDisplayActionEventHandler(std::make_unique()); } auto displayActionEventHandler = GetDisplayActionEventHandler(); if (nullptr != displayActionEventHandler) { displayActionEventHandler->InitActions(); } } QmitkRenderWindow* QmitkMxNMultiWidget::GetRenderWindow(const QString& widgetName) const { if ("axial" == widgetName || "sagittal" == widgetName || "coronal" == widgetName || "3d" == widgetName) { return GetActiveRenderWindowWidget()->GetRenderWindow(); } return QmitkAbstractMultiWidget::GetRenderWindow(widgetName); } QmitkRenderWindow* QmitkMxNMultiWidget::GetRenderWindow(const mitk::AnatomicalPlane& /*orientation*/) const { // currently no mapping between plane orientation and render windows // simply return the currently active render window return GetActiveRenderWindowWidget()->GetRenderWindow(); } void QmitkMxNMultiWidget::SetActiveRenderWindowWidget(RenderWindowWidgetPointer activeRenderWindowWidget) { auto currentActiveRenderWindowWidget = GetActiveRenderWindowWidget(); if (currentActiveRenderWindowWidget == activeRenderWindowWidget) { return; } // reset the decoration color of the previously active render window widget if (nullptr != currentActiveRenderWindowWidget) { auto decorationColor = currentActiveRenderWindowWidget->GetDecorationColor(); QColor hexColor(decorationColor[0] * 255, decorationColor[1] * 255, decorationColor[2] * 255); currentActiveRenderWindowWidget->setStyleSheet("QmitkRenderWindowWidget { border: 2px solid " + hexColor.name(QColor::HexRgb) + "; }"); } // set the new decoration color of the currently active render window widget if (nullptr != activeRenderWindowWidget) { activeRenderWindowWidget->setStyleSheet("QmitkRenderWindowWidget { border: 2px solid #FF6464; }"); } QmitkAbstractMultiWidget::SetActiveRenderWindowWidget(activeRenderWindowWidget); } void QmitkMxNMultiWidget::InitializeViews(const mitk::TimeGeometry* geometry, bool resetCamera) { auto* renderingManager = mitk::RenderingManager::GetInstance(); mitk::Point3D currentPosition = mitk::Point3D(); unsigned int imageTimeStep = 0; if (!resetCamera) { // store the current position to set it again later, if the camera should not be reset currentPosition = this->GetSelectedPosition(""); // store the current time step to set it again later, if the camera should not be reset const auto currentTimePoint = renderingManager->GetTimeNavigationController()->GetSelectedTimePoint(); if (geometry->IsValidTimePoint(currentTimePoint)) { imageTimeStep = geometry->TimePointToTimeStep(currentTimePoint); } } // initialize active render window renderingManager->InitializeView( this->GetActiveRenderWindowWidget()->GetRenderWindow()->GetVtkRenderWindow(), geometry, resetCamera); if (!resetCamera) { this->SetSelectedPosition(currentPosition, ""); renderingManager->GetTimeNavigationController()->GetTime()->SetPos(imageTimeStep); } } void QmitkMxNMultiWidget::SetInteractionReferenceGeometry(const mitk::TimeGeometry* referenceGeometry) { // Set the interaction reference referenceGeometry for all render windows. auto allRenderWindows = this->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { auto* baseRenderer = mitk::BaseRenderer::GetInstance(renderWindow->GetRenderWindow()); baseRenderer->SetInteractionReferenceGeometry(referenceGeometry); } } bool QmitkMxNMultiWidget::HasCoupledRenderWindows() const { return false; } void QmitkMxNMultiWidget::SetSelectedPosition(const mitk::Point3D& newPosition, const QString& widgetName) { RenderWindowWidgetPointer renderWindowWidget; if (widgetName.isNull() || widgetName.isEmpty()) { renderWindowWidget = GetActiveRenderWindowWidget(); } else { renderWindowWidget = GetRenderWindowWidget(widgetName); } if (nullptr != renderWindowWidget) { renderWindowWidget->GetSliceNavigationController()->SelectSliceByPoint(newPosition); return; } MITK_ERROR << "Position can not be set for an unknown render window widget."; } const mitk::Point3D QmitkMxNMultiWidget::GetSelectedPosition(const QString& widgetName) const { RenderWindowWidgetPointer renderWindowWidget; if (widgetName.isNull() || widgetName.isEmpty()) { renderWindowWidget = GetActiveRenderWindowWidget(); } else { renderWindowWidget = GetRenderWindowWidget(widgetName); } if (nullptr != renderWindowWidget) { return renderWindowWidget->GetCrosshairPosition(); } MITK_ERROR << "Crosshair position can not be retrieved."; return mitk::Point3D(0.0); } void QmitkMxNMultiWidget::SetCrosshairVisibility(bool visible) { // get the specific render window that sent the signal QmitkRenderWindow* renderWindow = qobject_cast(sender()); if (nullptr == renderWindow) { return; } auto renderWindowWidget = this->GetRenderWindowWidget(renderWindow); renderWindowWidget->SetCrosshairVisibility(visible); } bool QmitkMxNMultiWidget::GetCrosshairVisibility() const { // get the specific render window that sent the signal QmitkRenderWindow* renderWindow = qobject_cast(sender()); if (nullptr == renderWindow) { return false; } auto renderWindowWidget = this->GetRenderWindowWidget(renderWindow); return renderWindowWidget->GetCrosshairVisibility(); } void QmitkMxNMultiWidget::SetCrosshairGap(unsigned int gapSize) { auto renderWindowWidgets = this->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { renderWindowWidget.second->SetCrosshairGap(gapSize); } } void QmitkMxNMultiWidget::ResetCrosshair() { auto dataStorage = GetDataStorage(); if (nullptr == dataStorage) { return; } // get the specific render window that sent the signal QmitkRenderWindow* renderWindow = qobject_cast(sender()); if (nullptr == renderWindow) { return; } mitk::RenderingManager::GetInstance()->InitializeViewByBoundingObjects(renderWindow->GetRenderWindow(), dataStorage); SetWidgetPlaneMode(mitk::InteractionSchemeSwitcher::MITKStandard); } void QmitkMxNMultiWidget::SetWidgetPlaneMode(int userMode) { MITK_DEBUG << "Changing crosshair mode to " << userMode; switch (userMode) { case 0: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKStandard); break; case 1: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationUncoupled); break; case 2: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationCoupled); break; case 3: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKSwivel); break; } } mitk::SliceNavigationController* QmitkMxNMultiWidget::GetTimeNavigationController() { return m_TimeNavigationController; } void QmitkMxNMultiWidget::EnableCrosshair() { auto renderWindowWidgets = this->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { renderWindowWidget.second->EnableCrosshair(); } } void QmitkMxNMultiWidget::DisableCrosshair() { auto renderWindowWidgets = this->GetRenderWindowWidgets(); for (const auto& renderWindowWidget : renderWindowWidgets) { renderWindowWidget.second->DisableCrosshair(); } } ////////////////////////////////////////////////////////////////////////// // PUBLIC SLOTS // MOUSE EVENTS ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidget::wheelEvent(QWheelEvent* e) { emit WheelMoved(e); } void QmitkMxNMultiWidget::mousePressEvent(QMouseEvent*) { // nothing here, but necessary for mouse interactions (.xml-configuration files) } void QmitkMxNMultiWidget::moveEvent(QMoveEvent* e) { QWidget::moveEvent(e); // it is necessary to readjust the position of the overlays as the MultiWidget has moved // unfortunately it's not done by QmitkRenderWindow::moveEvent -> must be done here emit Moved(); } void QmitkMxNMultiWidget::RemoveRenderWindowWidget() { auto renderWindowWidgets = this->GetRenderWindowWidgets(); auto iterator = renderWindowWidgets.find(this->GetNameFromIndex(this->GetNumberOfRenderWindowWidgets() - 1)); if (iterator == renderWindowWidgets.end()) { return; } // disconnect each signal of this render window widget RenderWindowWidgetPointer renderWindowWidgetToRemove = iterator->second; m_TimeNavigationController->Disconnect(renderWindowWidgetToRemove->GetSliceNavigationController()); QmitkAbstractMultiWidget::RemoveRenderWindowWidget(); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidget::SetLayoutImpl() { int requiredRenderWindowWidgets = GetRowCount() * GetColumnCount(); int existingRenderWindowWidgets = GetRenderWindowWidgets().size(); int difference = requiredRenderWindowWidgets - existingRenderWindowWidgets; while (0 < difference) { // more render window widgets needed CreateRenderWindowWidget(); --difference; } while (0 > difference) { // less render window widgets needed RemoveRenderWindowWidget(); ++difference; } auto firstRenderWindowWidget = GetFirstRenderWindowWidget(); if (nullptr != firstRenderWindowWidget) { SetActiveRenderWindowWidget(firstRenderWindowWidget); } GetMultiWidgetLayoutManager()->SetLayoutDesign(QmitkMultiWidgetLayoutManager::LayoutDesign::DEFAULT); } QmitkAbstractMultiWidget::RenderWindowWidgetPointer QmitkMxNMultiWidget::CreateRenderWindowWidget() { // create the render window widget and connect signal / slot QString renderWindowWidgetName = GetNameFromIndex(GetNumberOfRenderWindowWidgets()); RenderWindowWidgetPointer renderWindowWidget = std::make_shared(this, renderWindowWidgetName, GetDataStorage()); renderWindowWidget->SetCornerAnnotationText(renderWindowWidgetName.toStdString()); AddRenderWindowWidget(renderWindowWidgetName, renderWindowWidget); auto renderWindow = renderWindowWidget->GetRenderWindow(); QmitkRenderWindowUtilityWidget* utilityWidget = new QmitkRenderWindowUtilityWidget(this, renderWindow, GetDataStorage()); renderWindowWidget->AddUtilityWidget(utilityWidget); connect(utilityWidget, &QmitkRenderWindowUtilityWidget::SynchronizationToggled, this, &QmitkMxNMultiWidget::ToggleSynchronization); connect(this, &QmitkMxNMultiWidget::UpdateUtilityWidgetViewPlanes, utilityWidget, &QmitkRenderWindowUtilityWidget::UpdateViewPlaneSelection); // needs to be done after 'QmitkRenderWindowUtilityWidget::ToggleSynchronization' has been connected // initially synchronize the node selection widget utilityWidget->ToggleSynchronization(true); auto layoutManager = GetMultiWidgetLayoutManager(); connect(renderWindow, &QmitkRenderWindow::LayoutDesignChanged, layoutManager, &QmitkMultiWidgetLayoutManager::SetLayoutDesign); connect(renderWindow, &QmitkRenderWindow::ResetView, this, &QmitkMxNMultiWidget::ResetCrosshair); connect(renderWindow, &QmitkRenderWindow::CrosshairVisibilityChanged, this, &QmitkMxNMultiWidget::SetCrosshairVisibility); connect(renderWindow, &QmitkRenderWindow::CrosshairRotationModeChanged, this, &QmitkMxNMultiWidget::SetWidgetPlaneMode); // connect time navigation controller to react on referenceGeometry time events with the render window's slice naviation controller m_TimeNavigationController->ConnectGeometryTimeEvent(renderWindow->GetSliceNavigationController()); // reverse connection between the render window's slice navigation controller and the time navigation controller renderWindow->GetSliceNavigationController()->ConnectGeometryTimeEvent(m_TimeNavigationController); return renderWindowWidget; } void QmitkMxNMultiWidget::LoadLayout(const nlohmann::json* jsonData) { if ((*jsonData).is_null()) { QMessageBox::warning(this, "Load layout", "Could not read window layout"); return; } unsigned int windowCounter = 0; try { auto version = jsonData->at("version").get(); if (version != "1.0") { QMessageBox::warning(this, "Load layout", "Unknown layout version, could not load"); return; } delete this->layout(); auto content = BuildLayoutFromJSON(jsonData, &windowCounter); auto hBoxLayout = new QHBoxLayout(this); this->setLayout(hBoxLayout); hBoxLayout->addWidget(content); emit UpdateUtilityWidgetViewPlanes(); } catch (nlohmann::json::out_of_range& e) { MITK_ERROR << "Error in loading window layout from JSON: " << e.what(); return; } while (GetNumberOfRenderWindowWidgets() > windowCounter) { RemoveRenderWindowWidget(); } EnableCrosshair(); + emit LayoutChanged(); } void QmitkMxNMultiWidget::SaveLayout(std::ostream* outStream) { if (outStream == nullptr) { return; } auto layout = this->layout(); if (layout == nullptr) return; // There should only ever be one item: a splitter auto widget = layout->itemAt(0)->widget(); auto splitter = dynamic_cast(widget); if (!splitter) { MITK_ERROR << "Tried to save unexpected layout format. Make sure the layout of this instance contains a single QSplitter."; return; } auto layoutJSON = BuildJSONFromLayout(splitter); layoutJSON["version"] = "1.0"; layoutJSON["name"] = "Custom Layout"; *outStream << std::setw(4) << layoutJSON << std::endl; } nlohmann::json QmitkMxNMultiWidget::BuildJSONFromLayout(const QSplitter* splitter) { nlohmann::json resultJSON; resultJSON["isWindow"] = false; resultJSON["vertical"] = (splitter->orientation() == Qt::Vertical) ? true : false; auto sizes = splitter->sizes(); auto content = nlohmann::json::array(); auto countSplitter = splitter->count(); for (int i = 0; i < countSplitter; ++i) { auto widget = splitter->widget(i); nlohmann::json widgetJSON; if (auto widgetSplitter = dynamic_cast(widget); widgetSplitter) { widgetJSON = BuildJSONFromLayout(widgetSplitter); } else if (auto widgetWindow = dynamic_cast(widget); widgetWindow) { widgetJSON["isWindow"] = true; widgetJSON["viewDirection"] = widgetWindow->GetSliceNavigationController()->GetViewDirectionAsString(); } widgetJSON["size"] = sizes[i]; content.push_back(widgetJSON); } resultJSON["content"] = content; return resultJSON; } QSplitter* QmitkMxNMultiWidget::BuildLayoutFromJSON(const nlohmann::json* jsonData, unsigned int* windowCounter, QSplitter* parentSplitter) { bool vertical = jsonData->at("vertical").get(); auto orientation = vertical ? Qt::Vertical : Qt::Horizontal; auto split = new QSplitter(orientation, parentSplitter); QList sizes; for (auto object : jsonData->at("content")) { bool isWindow = object["isWindow"].get(); int size = object["size"].get(); sizes.append(size); if (isWindow) { auto viewDirection = object["viewDirection"].get(); mitk::AnatomicalPlane viewPlane = mitk::AnatomicalPlane::Sagittal; if (viewDirection == "Axial") { viewPlane = mitk::AnatomicalPlane::Axial; } else if (viewDirection == "Coronal") { viewPlane = mitk::AnatomicalPlane::Coronal; } else if (viewDirection == "Original") { viewPlane = mitk::AnatomicalPlane::Original; } else if (viewDirection == "Sagittal") { viewPlane = mitk::AnatomicalPlane::Sagittal; } QmitkAbstractMultiWidget::RenderWindowWidgetPointer window = nullptr; QString renderWindowName; QmitkAbstractMultiWidget::RenderWindowWidgetMap::iterator it; // repurpose existing render windows as far as they already exist if (*windowCounter < GetRenderWindowWidgets().size()) { renderWindowName = this->GetNameFromIndex(*windowCounter); auto renderWindowWidgets = GetRenderWindowWidgets(); it = renderWindowWidgets.find(renderWindowName); if (it != renderWindowWidgets.end()) { window = it->second; } else { MITK_ERROR << "Could not find render window " << renderWindowName.toStdString() << ", although it should be there."; } } if (window == nullptr) { window = CreateRenderWindowWidget(); } window->GetSliceNavigationController()->SetDefaultViewDirection(viewPlane); window->GetSliceNavigationController()->Update(); split->addWidget(window.get()); window->show(); (*windowCounter)++; } else { auto subSplitter = BuildLayoutFromJSON(&object, windowCounter, split); split->addWidget(subSplitter); } } split->setSizes(sizes); return split; } void QmitkMxNMultiWidget::SetInitialSelection() { auto dataStorage = this->GetDataStorage(); if (nullptr == dataStorage) { return; } mitk::NodePredicateAnd::Pointer noHelperObjects = mitk::NodePredicateAnd::New(); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); noHelperObjects->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); auto allNodes = dataStorage->GetSubset(noHelperObjects); QmitkSynchronizedNodeSelectionWidget::NodeList currentSelection; for (auto& node : *allNodes) { currentSelection.append(node); } m_SynchronizedWidgetConnector->ChangeSelection(currentSelection); } void QmitkMxNMultiWidget::ToggleSynchronization(QmitkSynchronizedNodeSelectionWidget* synchronizedWidget) { bool synchronized = synchronizedWidget->IsSynchronized(); if (synchronized) { m_SynchronizedWidgetConnector->ConnectWidget(synchronizedWidget); m_SynchronizedWidgetConnector->SynchronizeWidget(synchronizedWidget); } else { m_SynchronizedWidgetConnector->DisconnectWidget(synchronizedWidget); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index 0a59ce0e4a..7d7d58668d 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,1968 +1,1985 @@ /*============================================================================ 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 "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 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 std::map QmitkSlicesInterpolator::createActionToSliceDimension() +const std::map QmitkSlicesInterpolator::createActionToSlicer(const QList& windows) { std::map actionToSliceDimension; - foreach (mitk::SliceNavigationController *slicer, m_ControllerToDeleteObserverTag.keys()) + for (auto* window : windows) { - actionToSliceDimension[new QAction(QString::fromStdString(slicer->GetViewDirectionAsString()), nullptr)] = slicer; + 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; } // Check whether the given contours are coplanar bool AreContoursCoplanar(mitk::SurfaceInterpolationController::ContourPositionInformation leftHandSide, mitk::SurfaceInterpolationController::ContourPositionInformation rightHandSide) { // Here we check two things: // 1. Whether the normals of both contours are at least parallel // 2. Whether both contours lie in the same plane // Check for coplanarity: // a. Span a vector between two points one from each contour // b. Calculate dot product for the vector and one of the normals // c. If the dot is zero the two vectors are orthogonal and the contours are coplanar double vec[3]; vec[0] = leftHandSide.ContourPoint[0] - rightHandSide.ContourPoint[0]; vec[1] = leftHandSide.ContourPoint[1] - rightHandSide.ContourPoint[1]; vec[2] = leftHandSide.ContourPoint[2] - rightHandSide.ContourPoint[2]; double n[3]; n[0] = rightHandSide.ContourNormal[0]; n[1] = rightHandSide.ContourNormal[1]; n[2] = rightHandSide.ContourNormal[2]; double dot = vtkMath::Dot(n, vec); double n2[3]; n2[0] = leftHandSide.ContourNormal[0]; n2[1] = leftHandSide.ContourNormal[1]; n2[2] = leftHandSide.ContourNormal[2]; // The normals of both contours have to be parallel but not of the same orientation double lengthLHS = leftHandSide.ContourNormal.GetNorm(); double lengthRHS = rightHandSide.ContourNormal.GetNorm(); double dot2 = vtkMath::Dot(n, n2); bool contoursParallel = mitk::Equal(fabs(lengthLHS * lengthRHS), fabs(dot2), 0.001); if (mitk::Equal(dot, 0.0, 0.001) && contoursParallel) return true; else return false; } 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; } template std::vector GetPixelValuesPresentInImage(mitk::LabelSetImage* labelSetImage) { std::vector pixelsPresent; mitk::ImagePixelReadAccessor readAccessor(labelSetImage); std::size_t numberOfPixels = 1; for (size_t dim = 0; dim < VImageDimension; ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { mitk::Label::PixelType pixelVal = *(src + i); if ( (std::find(pixelsPresent.begin(), pixelsPresent.end(), pixelVal) == pixelsPresent.end()) && (pixelVal != labelSetImage->GetExteriorLabel()->GetValue()) ) pixelsPresent.push_back(pixelVal); } return pixelsPresent; } template ModifyLabelActionTrigerred ModifyLabelProcessing(mitk::LabelSetImage* labelSetImage, mitk::SurfaceInterpolationController::Pointer surfaceInterpolator, unsigned int timePoint) { auto currentLayerID = labelSetImage->GetActiveLayer(); auto numTimeSteps = labelSetImage->GetTimeSteps(); ModifyLabelActionTrigerred actionTriggered = ModifyLabelActionTrigerred::Null; mitk::SurfaceInterpolationController::ContourPositionInformationList ¤tContourList = surfaceInterpolator->GetContours(timePoint, currentLayerID); mitk::LabelSetImage::Pointer labelSetImage2 = labelSetImage->Clone(); mitk::ImagePixelReadAccessor readAccessor(labelSetImage2.GetPointer()); for (auto& contour : currentContourList) { mitk::Label::PixelType contourPixelValue; itk::Index<3> itkIndex; labelSetImage2->GetGeometry()->WorldToIndex(contour.ContourPoint, itkIndex); if (VImageDimension == 4) { itk::Index time3DIndex; for (size_t i = 0; i < itkIndex.size(); ++i) time3DIndex[i] = itkIndex[i]; time3DIndex[3] = timePoint; contourPixelValue = readAccessor.GetPixelByIndexSafe(time3DIndex); } else if (VImageDimension == 3) { itk::Index geomIndex; for (size_t i = 0; i < itkIndex.size(); ++i) geomIndex[i] = itkIndex[i]; contourPixelValue = readAccessor.GetPixelByIndexSafe(geomIndex); } if (contour.LabelValue != contourPixelValue) { if (contourPixelValue == 0) // Erase label { for (size_t t = 0; t < numTimeSteps; ++t) surfaceInterpolator->RemoveContours(contour.LabelValue, t, currentLayerID); actionTriggered = ModifyLabelActionTrigerred::Erase; } else { contour.LabelValue = contourPixelValue; actionTriggered = ModifyLabelActionTrigerred::Merge; } } } return actionTriggered; } QmitkSlicesInterpolator::QmitkSlicesInterpolator(QWidget *parent, const char * /*name*/) : QWidget(parent), - // ACTION_TO_SLICEDIMENSION( createActionToSliceDimension() ), 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_PreviousActiveLabelValue(0), m_CurrentActiveLabelValue(0), m_PreviousLayerIndex(0), m_CurrentLayerIndex(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); // T28261 // m_BtnSuggestPlane = new QPushButton("Suggest a plane", m_GroupBoxEnableExclusiveInterpolationMode); // vboxLayout->addWidget(m_BtnSuggestPlane); 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); m_3DContourNode = mitk::DataNode::New(); m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(0.0, 0.0, 0.0)); m_3DContourNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("name", mitk::StringProperty::New("Drawn Contours")); m_3DContourNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); m_3DContourNode->SetProperty("material.wireframeLineWidth", mitk::FloatProperty::New(2.0f)); m_3DContourNode->SetProperty("3DContourContainer", mitk::BoolProperty::New(true)); m_3DContourNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_3DContourNode->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) ); } } 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; + m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); + + itk::MemberCommand::Pointer deleteCommand = + itk::MemberCommand::New(); + deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); + m_ControllerToDeleteObserverTag[slicer] = slicer->AddObserver(itk::DeleteEvent(), deleteCommand); + + itk::MemberCommand::Pointer timeChangedCommand = + itk::MemberCommand::New(); + timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); + m_ControllerToTimeObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand); + + 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 &controllers) + const QList& windows) { - Q_ASSERT(!controllers.empty()); + 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); // connect to the slice navigation controller. after each change, call the interpolator - foreach (mitk::SliceNavigationController *slicer, controllers) + for (auto* window : windows) { - // Has to be initialized - m_LastSNC = slicer; - m_TimePoints.insert(slicer, slicer->GetSelectedTimePoint()); - - itk::MemberCommand::Pointer deleteCommand = - itk::MemberCommand::New(); - deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); - m_ControllerToDeleteObserverTag[slicer] = slicer->AddObserver(itk::DeleteEvent(), deleteCommand); - - itk::MemberCommand::Pointer timeChangedCommand = - itk::MemberCommand::New(); - timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); - m_ControllerToTimeObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::TimeGeometryEvent(nullptr, 0), timeChangedCommand); - - itk::MemberCommand::Pointer sliceChangedCommand = - itk::MemberCommand::New(); - sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); - m_ControllerToSliceObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand); + this->InitializeWindow(window); } - ACTION_TO_SLICEDIMENSION = createActionToSliceDimension(); + m_ActionToSlicer = 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); } - foreach (mitk::SliceNavigationController *slicer, m_ControllerToSliceObserverTag.keys()) + for (auto* slicer : m_ControllerToTimeObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToTimeObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } auto dataIter = m_SegmentationObserverTags.begin(); while (dataIter != m_SegmentationObserverTags.end()) { auto labelSetImage = (*dataIter).first; labelSetImage->RemoveObserver((*dataIter).second); for (size_t layerID = 0; layerID < labelSetImage->GetNumberOfLayers(); ++layerID) { this->OnRemoveLabelSetConnection(labelSetImage, layerID); } ++dataIter; } m_SegmentationObserverTags.clear(); - ACTION_TO_SLICEDIMENSION.clear(); + m_ActionToSlicer.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_3DContourNode)) m_DataStorage->Remove(m_3DContourNode); 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->UnsetSelectedImage(); 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); // T28261 // m_BtnSuggestPlane->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); 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()); auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_BtnReinit3DInterpolation->setEnabled(true); try { if (m_SegmentationObserverTags.find(labelSetImage) == m_SegmentationObserverTags.end()) { auto command2 = itk::MemberCommand::New(); command2->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnModifyLabelChanged); auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); m_SegmentationObserverTags[workingImage] = workingImage->AddObserver(itk::ModifiedEvent(), command2); } } catch (const std::exception& e) { MITK_ERROR << "Error casting node data to LabelSetImage\n"; } } else { // If no workingdata is set, remove the interpolation feedback this->GetDataStorage()->Remove(m_FeedbackNode); m_FeedbackNode->SetData(nullptr); this->GetDataStorage()->Remove(m_3DContourNode); m_3DContourNode->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) { // Check if we really have a GeometryTimeEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); Q_ASSERT(slicer); const auto timePoint = slicer->GetSelectedTimePoint(); m_TimePoints[slicer] = timePoint; if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (timePoint != m_SurfaceInterpolator->GetCurrentTimePoint()) { m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); if (m_3DInterpolationEnabled) { m_3DContourNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); } m_SurfaceInterpolator->Modified(); } if (m_LastSNC == slicer) { slicer->SendSlice(); // will trigger a new interpolation } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { // Check whether we really have a GeometrySliceEvent if (!dynamic_cast(&e)) return; mitk::SliceNavigationController *slicer = dynamic_cast(sender); if(m_2DInterpolationEnabled) { this->On2DInterpolationEnabled(m_2DInterpolationEnabled); } if (TranslateAndInterpolateChangedSlice(e, slicer)) { slicer->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer) { if (!m_2DInterpolationEnabled) return false; try { const mitk::SliceNavigationController::GeometrySliceEvent &event = dynamic_cast(e); mitk::TimeGeometry *tsg = event.GetTimeGeometry(); if (tsg && m_TimePoints.contains(slicer) && tsg->IsValidTimePoint(m_TimePoints[slicer])) { mitk::SlicedGeometry3D *slicedGeometry = dynamic_cast(tsg->GetGeometryForTimePoint(m_TimePoints[slicer]).GetPointer()); if (slicedGeometry) { m_LastSNC = slicer; mitk::PlaneGeometry *plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(event.GetPos())); if (plane) { Interpolate(plane, m_TimePoints[slicer], slicer); } return true; } } } catch (const std::bad_cast &) { return false; // so what } return false; } void QmitkSlicesInterpolator::OnLayerChanged() { auto* workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { m_3DContourNode->SetData(nullptr); this->Show3DInterpolationResult(false); } 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::Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer) { if (m_ToolManager) { mitk::DataNode *node = m_ToolManager->GetWorkingData(0); if (node) { m_Segmentation = dynamic_cast(node->GetData()); if (m_Segmentation) { if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot interpolate segmentation. Passed time point is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(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 activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabelSet()->GetActiveLabel()->GetColor(); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } } m_LastSNC = slicer; m_LastSliceIndex = clickedSliceIndex; } } } } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(); mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); mitk::PlaneGeometry::Pointer slicingPlane = mitk::PlaneGeometry::New(); mitk::Vector3D slicingPlaneNormalVector; FillVector3D(slicingPlaneNormalVector,0.0,1.0,0.0); mitk::Point3D origin; FillVector3D(origin, 0.0, 0.0, 0.0); slicingPlane->InitializePlane(origin, slicingPlaneNormalVector); - if (interpolatedSurface.IsNotNull() && workingNode && - workingNode->IsVisible( - mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2")))) + if (interpolatedSurface.IsNotNull() && workingNode) { m_BtnApply3D->setEnabled(true); // T28261 // m_BtnSuggestPlane->setEnabled(true); m_InterpolatedSurfaceNode->SetData(interpolatedSurface); m_3DContourNode->SetData(m_SurfaceInterpolator->GetContoursAsSurface()); this->Show3DInterpolationResult(true); if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { m_DataStorage->Add(m_InterpolatedSurfaceNode); } } else if (interpolatedSurface.IsNull()) { m_BtnApply3D->setEnabled(false); // T28261 // m_BtnSuggestPlane->setEnabled(false); if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) { this->Show3DInterpolationResult(false); } } m_BtnReinit3DInterpolation->setEnabled(true); - foreach (mitk::SliceNavigationController *slicer, m_ControllerToTimeObserverTag.keys()) + for (auto* slicer : m_ControllerToTimeObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } m_SurfaceInterpolator->ReinitializeInterpolation(); } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { if (m_Segmentation && m_FeedbackNode->GetData()) { // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set slice as input mitk::Image::Pointer slice = dynamic_cast(m_FeedbackNode->GetData()); reslice->SetInputSlice(slice->GetSliceData()->GetVtkImageAccessor(slice)->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(m_Segmentation); const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(m_LastSNC->GetCurrentPlaneGeometry()); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(m_Segmentation->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so m_Segmentation->Modified(); m_Segmentation->GetVtkImageData()->Modified(); m_FeedbackNode->SetData(nullptr); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } 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; const auto timePoint = slicer->GetSelectedTimePoint(); if (4 == m_Segmentation->GetDimension()) { const auto* geometry = m_Segmentation->GetTimeGeometry(); if (!geometry->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not within the time bounds of segmentation. Time point: " << timePoint; return; } mitk::Image::Pointer activeLabelImage; try { auto labelSetImage = dynamic_cast(m_Segmentation); activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(), true, 0); } catch (const std::exception& e) { MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; } m_Interpolator->SetSegmentationVolume(activeLabelImage); timeStep = geometry->TimePointToTimeStep(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); // const auto numThreads = 1; 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)->GetActiveLabelSet()->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); - std::map::const_iterator it; - for (it = ACTION_TO_SLICEDIMENSION.begin(); it != ACTION_TO_SLICEDIMENSION.end(); it++) + for (auto it = m_ActionToSlicer.begin(); it != m_ActionToSlicer.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->GetActiveLabelSet()->GetActiveLabel()->GetColor(); std::string activeLabelName = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetName(); auto segmentation = GetData(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(timePoint) || !segmentationGeometry->IsValidTimePoint(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(timePoint); const mitk::Label::PixelType newDestinationLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); TransferLabelContent( interpolatedSegmentation, labelSetImage, labelSetImage->GetActiveLabelSet(), 0, 0, false, {{1, newDestinationLabel}}, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks, timeStep); // m_CmbInterpolation->setCurrentIndex(0); 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) { std::vector contourPlanes; std::vector contourList; if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); auto activeLayerID = labelSetImage->GetActiveLayer(); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } // Adding layer, label and timeStep information for the contourNodes. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto contourNode = it->Value(); auto layerID = dynamic_cast(contourNode->GetProperty("layerID"))->GetValue(); auto labelID = dynamic_cast(contourNode->GetProperty("labelID"))->GetValue(); auto timeStep = dynamic_cast(contourNode->GetProperty("timeStep"))->GetValue(); auto px = dynamic_cast(contourNode->GetProperty("px"))->GetValue(); auto py = dynamic_cast(contourNode->GetProperty("py"))->GetValue(); auto pz = dynamic_cast(contourNode->GetProperty("pz"))->GetValue(); // auto layerImage = labelSetImage->GetLayerImage(layerID); auto planeGeometry = dynamic_cast(contourNode->GetData())->GetPlaneGeometry(); labelSetImage->SetActiveLayer(layerID); auto sliceImage = ExtractSliceFromImage(labelSetImage, planeGeometry, timeStep); labelSetImage->SetActiveLayer(activeLayerID); 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; vtkSmartPointer intArray = vtkSmartPointer::New(); intArray->InsertNextValue(labelID); intArray->InsertNextValue(layerID); intArray->InsertNextValue(timeStep); contour->GetVtkPolyData()->GetFieldData()->AddArray(intArray); vtkSmartPointer doubleArray = vtkSmartPointer::New(); doubleArray->InsertNextValue(px); doubleArray->InsertNextValue(py); doubleArray->InsertNextValue(pz); contour->GetVtkPolyData()->GetFieldData()->AddArray(doubleArray); contour->DisconnectPipeline(); contourList.push_back(contour); contourPlanes.push_back(planeGeometry); } labelSetImage->SetActiveLayer(activeLayerID); // size_t activeLayer = labelSetImage->GetActiveLayer(); for (size_t l = 0; l < labelSetImage->GetNumberOfLayers(); ++l) { this->OnAddLabelSetConnection(l); } // labelSetImage->SetActiveLayer(activeLayer); m_SurfaceInterpolator->CompleteReinitialization(contourList, contourPlanes); } 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 { - std::map::const_iterator iter = ACTION_TO_SLICEDIMENSION.find(action); - if (iter != ACTION_TO_SLICEDIMENSION.end()) + auto iter = m_ActionToSlicer.find(action); + if (iter != m_ActionToSlicer.end()) { mitk::SliceNavigationController *slicer = iter->second; 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(); // additional error message on std::cerr 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) { mitk::Image *segmentation = dynamic_cast(workingNode->GetData()); mitk::Image::Pointer activeLabelImage; try { auto labelSetImage = dynamic_cast(workingNode->GetData()); activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(), true, 0); } catch (const std::exception& e) { MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; } if (segmentation) { 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() { m_SurfaceInterpolator->Interpolate(); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { if(m_ToolManager) { auto* workingNode = m_ToolManager->GetWorkingData(0); auto activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabelSet()->GetActiveLabel()->GetColor(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } m_Timer->stop(); - mitk::RenderingManager::GetInstance()->RequestUpdate( - mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))->GetRenderWindow()); } 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::PrepareInputsFor3DInterpolation() { if (m_DataStorage.IsNotNull() && m_ToolManager && m_3DInterpolationEnabled) { auto *workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { - if ((workingNode->IsVisible(mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget2"))))) - { - int ret = QMessageBox::Yes; + int ret = QMessageBox::Yes; - if (m_SurfaceInterpolator->EstimatePortionOfNeededMemory() > 0.5) - { - QMessageBox msgBox; - msgBox.setText("Due to short handed system memory the 3D interpolation may be very slow!"); - msgBox.setInformativeText("Are you sure you want to activate the 3D interpolation?"); - msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); - ret = msgBox.exec(); - } + if (m_SurfaceInterpolator->EstimatePortionOfNeededMemory() > 0.5) + { + QMessageBox msgBox; + msgBox.setText("Due to short handed system memory the 3D interpolation may be very slow!"); + msgBox.setInformativeText("Are you sure you want to activate the 3D interpolation?"); + msgBox.setStandardButtons(QMessageBox::No | QMessageBox::Yes); + ret = msgBox.exec(); + } - auto labelSetImage = dynamic_cast(workingNode->GetData()); - auto activeLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); + auto labelSetImage = dynamic_cast(workingNode->GetData()); + auto activeLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); - m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(activeLabel); + m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(activeLabel); - if (m_Watcher.isRunning()) - m_Watcher.waitForFinished(); + if (m_Watcher.isRunning()) + m_Watcher.waitForFinished(); - if (ret == QMessageBox::Yes) - { - // Maybe set the segmentation node here - m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); - m_Watcher.setFuture(m_Future); - } - else - { - m_CmbInterpolation->setCurrentIndex(0); - } + if (ret == QMessageBox::Yes) + { + // Maybe set the segmentation node here + m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); + m_Watcher.setFuture(m_Future); + } + else + { + m_CmbInterpolation->setCurrentIndex(0); } } else { QWidget::setEnabled(false); m_ChkShowPositionNodes->setEnabled(m_3DInterpolationEnabled); } } if (!m_3DInterpolationEnabled) { this->Show3DInterpolationResult(false); m_BtnApply3D->setEnabled(m_3DInterpolationEnabled); // T28261 // m_BtnSuggestPlane->setEnabled(m_3DInterpolationEnabled); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } 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*/) { if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); if (m_3DInterpolationEnabled) { m_3DContourNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); auto *workingNode = m_ToolManager->GetWorkingData(0); auto labelSetImage = dynamic_cast(workingNode->GetData()); auto activeLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(activeLabel); m_Future = QtConcurrent::run(this, &QmitkSlicesInterpolator::Run3DInterpolation); 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); try{ auto labelSetImage = dynamic_cast(workingNode->GetData()); for (size_t layerID = 0; layerID < labelSetImage->GetNumberOfLayers(); ++layerID) { this->OnAddLabelSetConnection(layerID); } } catch (std::exception &e) { MITK_ERROR << e.what() << "\n"; } if (workingNode) { QWidget::setEnabled(true); const auto timePoint = m_LastSNC->GetSelectedTimePoint(); // In case the time is not valid use 0 to access the time geometry of the working node unsigned int time_position = 0; if (!workingNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << timePoint; return; } // Sets up the surface interpolator to accept time_position = workingNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); mitk::Vector3D spacing = workingNode->GetData()->GetGeometry(time_position)->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]; } } m_SurfaceInterpolator->SetMaxSpacing(maxSpacing); m_SurfaceInterpolator->SetMinSpacing(minSpacing); m_SurfaceInterpolator->SetDistanceImageVolume(50000); mitk::Image::Pointer segmentationImage; segmentationImage = dynamic_cast(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); m_SurfaceInterpolator->SetCurrentTimePoint(timePoint); } else { QWidget::setEnabled(false); } } } void QmitkSlicesInterpolator::Show3DInterpolationResult(bool status) { if (m_InterpolatedSurfaceNode.IsNotNull()) m_InterpolatedSurfaceNode->SetVisibility(status); if (m_3DContourNode.IsNotNull()) { auto allRenderWindows = mitk::BaseRenderer::GetAll3DRenderWindows(); for (auto mapit = allRenderWindows.begin(); mapit != allRenderWindows.end(); ++mapit) { m_3DContourNode->SetVisibility(status, mapit->second); } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::OnActiveLabelChanged(mitk::Label::PixelType) { m_3DContourNode->SetData(nullptr); 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_ControllerToTimeObserverTag.remove(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_3DContourNode || node == m_FeedbackNode || node == m_InterpolatedSurfaceNode) { WaitForFutures(); } } void QmitkSlicesInterpolator::OnAddLabelSetConnection(unsigned int layerID) { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); auto labelSet = workingImage->GetLabelSet(layerID); labelSet->RemoveLabelEvent += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnRemoveLabel); labelSet->ActiveLabelEvent += mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnActiveLabelChanged); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnLayerChanged); m_SurfaceInterpolator->AddLabelSetConnection(layerID); } catch(const std::exception& e) { MITK_ERROR << e.what() << '\n'; } } } void QmitkSlicesInterpolator::OnAddLabelSetConnection() { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnRemoveLabel); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnActiveLabelChanged); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnLayerChanged); m_SurfaceInterpolator->AddLabelSetConnection(); } catch(const std::exception& e) { MITK_ERROR << e.what() << '\n'; } } } void QmitkSlicesInterpolator::OnRemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID) { size_t previousLayerID = labelSetImage->GetActiveLayer(); labelSetImage->SetActiveLayer(layerID); labelSetImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnRemoveLabel); labelSetImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnActiveLabelChanged); labelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnLayerChanged); m_SurfaceInterpolator->RemoveLabelSetConnection(labelSetImage, layerID); labelSetImage->SetActiveLayer(previousLayerID); } void QmitkSlicesInterpolator::OnRemoveLabelSetConnection() { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnRemoveLabel); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( this, &QmitkSlicesInterpolator::OnActiveLabelChanged); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnLayerChanged); } catch(const std::exception& e) { MITK_ERROR << e.what() << '\n'; } } } void QmitkSlicesInterpolator::OnRemoveLabel() { if (m_ToolManager->GetWorkingData(0) != nullptr) { try { auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); auto currentLayerID = labelSetImage->GetActiveLayer(); auto numTimeSteps = labelSetImage->GetTimeGeometry()->CountTimeSteps(); for (size_t t = 0; t < numTimeSteps; ++t) { m_SurfaceInterpolator->RemoveContours(m_PreviousActiveLabelValue,t,currentLayerID); } } catch(const std::exception& e) { MITK_ERROR << "Bad cast error for labelSetImage"; } } } void QmitkSlicesInterpolator::OnModifyLabelChanged(const itk::Object *caller, const itk::EventObject & /*event*/) { auto *tempImage = dynamic_cast(const_cast(caller) ) ; if( tempImage == nullptr) { MITK_ERROR << "Unable to cast caller to LabelSetImage."; return; } ModifyLabelActionTrigerred actionTriggered = ModifyLabelActionTrigerred::Null; if(m_ToolManager->GetWorkingData(0) != nullptr) { auto labelSetImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (labelSetImage == tempImage) { const auto timePoint = m_LastSNC->GetSelectedTimePoint(); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(timePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } auto timeStep = labelSetImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); auto numLayersInCurrentSegmentation = m_SurfaceInterpolator->GetNumberOfLayersInCurrentSegmentation(); // This handles the add layer or remove layer operation. if (labelSetImage->GetNumberOfLayers() != numLayersInCurrentSegmentation) { bool addLayer = (labelSetImage->GetNumberOfLayers() == (numLayersInCurrentSegmentation +1) ); bool removeLayer = (labelSetImage->GetNumberOfLayers() == (numLayersInCurrentSegmentation - 1) ); m_SurfaceInterpolator->SetNumberOfLayersInCurrentSegmentation(labelSetImage->GetNumberOfLayers()); if (addLayer) { m_SurfaceInterpolator->OnAddLayer(); this->OnAddLabelSetConnection(); } if (removeLayer) { m_SurfaceInterpolator->OnRemoveLayer(); } return; } // Get the pixels present in the image. // This portion of the code deals with the merge and erase labels operations. auto imageDimension = labelSetImage->GetDimension(); if (imageDimension == 4) { actionTriggered = ModifyLabelProcessing<4>(labelSetImage, m_SurfaceInterpolator, timeStep); } else { actionTriggered = ModifyLabelProcessing<3>(labelSetImage, m_SurfaceInterpolator, timeStep); } if (actionTriggered == ModifyLabelActionTrigerred::Erase) { m_InterpolatedSurfaceNode->SetData(nullptr); } auto currentLayerID = labelSetImage->GetActiveLayer(); if (actionTriggered == ModifyLabelActionTrigerred::Merge) { this->MergeContours(timeStep, currentLayerID); m_SurfaceInterpolator->Modified(); } } } } void QmitkSlicesInterpolator::MergeContours(unsigned int timeStep, unsigned int layerID) { std::vector& contours = m_SurfaceInterpolator->GetContours(timeStep,layerID); std::this_thread::sleep_for(std::chrono::milliseconds(1000)); for (size_t i = 0; i < contours.size(); ++i) { for (size_t j = i+1; j < contours.size(); ++j) { // And Labels are the same and Layers are the same. bool areContoursCoplanar = AreContoursCoplanar(contours[i],contours[j]); if ( areContoursCoplanar && (contours[i].LabelValue == contours[j].LabelValue) ) { // Update the contour by re-extracting the slice from the corresponding plane. mitk::Image::Pointer slice = ExtractSliceFromImage(m_Segmentation, contours[i].Plane, timeStep); mitk::ImageToContourFilter::Pointer contourExtractor = mitk::ImageToContourFilter::New(); contourExtractor->SetInput(slice); contourExtractor->SetContourValue(contours[i].LabelValue); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); contours[i].Contour = contour; // Update the interior point of the contour contours[i].ContourPoint = m_SurfaceInterpolator->ComputeInteriorPointOfContour(contours[i],dynamic_cast(m_Segmentation)); // Setting the contour polygon data to an empty vtkPolyData, // as source label is empty after merge operation. contours[j].Contour->SetVtkPolyData(vtkSmartPointer::New()); } } } auto segmentationNode = m_SurfaceInterpolator->GetSegmentationImageNode(); if (segmentationNode == nullptr) { MITK_ERROR << "segmentation Image Node not found\n"; } auto isContourPlaneGeometry = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(segmentationNode, isContourPlaneGeometry); // Remove empty contour nodes. auto isContourEmpty = [] (const mitk::SurfaceInterpolationController::ContourPositionInformation& contour) { return (contour.Contour->GetVtkPolyData()->GetNumberOfPoints() == 0); }; auto it = std::remove_if(contours.begin(), contours.end(), isContourEmpty); contours.erase(it, contours.end()); } \ No newline at end of file diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h index 1d9803100c..5c8efb50e6 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h @@ -1,423 +1,425 @@ /*============================================================================ 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 QmitkSlicesInterpolator_h #define QmitkSlicesInterpolator_h #include "mitkDataNode.h" #include "mitkDataStorage.h" #include "mitkSegmentationInterpolationController.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceInterpolationController.h" #include "mitkToolManager.h" #include #include "mitkFeatureBasedEdgeDetectionFilter.h" #include "mitkPointCloudScoringFilter.h" #include #include #include #include #include #include #include #include "mitkVtkRepresentationProperty.h" #include "vtkProperty.h" // For running 3D interpolation in background #include #include #include #include namespace mitk { class PlaneGeometry; class SliceNavigationController; } class QPushButton; +class QmitkRenderWindow; enum ModifyLabelActionTrigerred { Null, Erase, Merge }; /** \brief GUI for slices interpolation. \ingroup ToolManagerEtAl \ingroup Widgets \sa QmitkInteractiveSegmentation \sa mitk::SegmentationInterpolation While mitk::SegmentationInterpolation does the bookkeeping of interpolation (keeping track of which slices contain how much segmentation) and the algorithmic work, QmitkSlicesInterpolator is responsible to watch the GUI, to notice, which slice is currently visible. It triggers generation of interpolation suggestions and also triggers acception of suggestions. \todo show/hide feedback on demand Last contributor: $Author: maleike $ */ class MITKSEGMENTATIONUI_EXPORT QmitkSlicesInterpolator : public QWidget { Q_OBJECT public: QmitkSlicesInterpolator(QWidget *parent = nullptr, const char *name = nullptr); /** To be called once before real use. */ - void Initialize(mitk::ToolManager *toolManager, const QList &controllers); + void Initialize(mitk::ToolManager *toolManager, const QList& windows); /** * @brief * */ void Uninitialize(); ~QmitkSlicesInterpolator() override; /** * @brief Set the Data Storage object * * @param storage */ void SetDataStorage(mitk::DataStorage::Pointer storage); /** * @brief Get the Data Storage object * * @return mitk::DataStorage* */ mitk::DataStorage *GetDataStorage(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerWorkingDataModified(); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnToolManagerReferenceDataModified(); /** * @brief Reacts to the time changed event. * * @param sender */ void OnTimeChanged(itk::Object *sender, const itk::EventObject &); /** * @brief Reacts to the slice changed event * * @param sender */ void OnSliceChanged(itk::Object *sender, const itk::EventObject &); void OnSliceNavigationControllerDeleted(const itk::Object *sender, const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnInterpolationInfoChanged(const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnInterpolationAborted(const itk::EventObject &); /** Just public because it is called by itk::Commands. You should not need to call this. */ void OnSurfaceInterpolationInfoChanged(const itk::EventObject &); private: /** * @brief Set the visibility of the 3d interpolation */ void Show3DInterpolationResult(bool); /** * @brief Function that reacts to a change in the activeLabel of the working segmentation image. * */ void OnActiveLabelChanged(mitk::Label::PixelType); /** * @brief Function that reacts to a change in the layer. * */ void OnLayerChanged(); /** * @brief Function that handles label removal from the segmentation image. * */ void OnRemoveLabel(); /** * @brief Function that to changes in the segmentation image. It handles the layer removal, addition, label erasure, * */ void OnModifyLabelChanged(const itk::Object *caller, const itk::EventObject & /*event*/); /** * @brief Add the necessary subscribers to the label set image, for UI responsiveness. * It deals with remove label, change active label, layer changes and change in the label. * */ void OnAddLabelSetConnection(); /** * @brief Add the necessary subscribers to the current LabelSetImage at the layer input, for UI responsiveness. * It deals with remove label, change active label, layer changes and change in the label. * * @param layerID */ void OnAddLabelSetConnection(unsigned int layerID); /** * @brief Remove the subscribers for the different events to the segmentation image. * */ void OnRemoveLabelSetConnection(); /** * @brief Merge contours for the current layerID and current timeStep. * * @param timeStep * @param layerID */ void MergeContours(unsigned int timeStep, unsigned int layerID); /** * @brief Prepare Inputs for 3D Interpolation. * */ void PrepareInputsFor3DInterpolation(); signals: void SignalRememberContourPositions(bool); void SignalShowMarkerNodes(bool); public slots: virtual void setEnabled(bool); /** Call this from the outside to enable/disable interpolation */ void EnableInterpolation(bool); void Enable3DInterpolation(bool); /** Call this from the outside to accept all interpolations */ void FinishInterpolation(mitk::SliceNavigationController *slicer = nullptr); protected slots: /** Reaction to button clicks. */ void OnAcceptInterpolationClicked(); /* Opens popup to ask about which orientation should be interpolated */ void OnAcceptAllInterpolationsClicked(); /* Reaction to button clicks */ void OnAccept3DInterpolationClicked(); /** * @brief Reaction to reinit 3D Interpolation. Re-reads the plane geometries of the image * that should have generated the * */ void OnReinit3DInterpolation(); /* * Will trigger interpolation for all slices in given orientation (called from popup menu of * OnAcceptAllInterpolationsClicked) */ void OnAcceptAllPopupActivated(QAction *action); /** Called on activation/deactivation */ void OnInterpolationActivated(bool); void On3DInterpolationActivated(bool); void OnInterpolationMethodChanged(int index); // Enhancement for 3D interpolation void On2DInterpolationEnabled(bool); void On3DInterpolationEnabled(bool); void OnInterpolationDisabled(bool); void OnShowMarkers(bool); void Run3DInterpolation(); /** * @brief Function triggers when the surface interpolation thread completes running. * It is responsible for retrieving the data, rendering it in the active color label, * storing the surface information in the feedback node. * */ void OnSurfaceInterpolationFinished(); void StartUpdateInterpolationTimer(); void StopUpdateInterpolationTimer(); void ChangeSurfaceColor(); /** * @brief Removes all observers to the labelSetImage at the layerID specified. * Is used when changing the segmentation image. * * @param labelSetImage * @param layerID */ void OnRemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID); protected: - const std::map createActionToSliceDimension(); - std::map ACTION_TO_SLICEDIMENSION; + const std::map createActionToSlicer(const QList& windows); + std::map m_ActionToSlicer; void AcceptAllInterpolations(mitk::SliceNavigationController *slicer); /** Retrieves the currently selected PlaneGeometry from a SlicedGeometry3D that is generated by a SliceNavigationController and calls Interpolate to further process this PlaneGeometry into an interpolation. \param e is a actually a mitk::SliceNavigationController::GeometrySliceEvent, sent by a SliceNavigationController \param slicer the SliceNavigationController */ bool TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *slicer); /** Given a PlaneGeometry, this method figures out which slice of the first working image (of the associated ToolManager) should be interpolated. The actual work is then done by our SegmentationInterpolation object. */ void Interpolate(mitk::PlaneGeometry *plane, mitk::TimePointType timePoint, mitk::SliceNavigationController *slicer); // void InterpolateSurface(); /** Called internally to update the interpolation suggestion. Finds out about the focused render window and requests an interpolation. */ void UpdateVisibleSuggestion(); void SetCurrentContourListID(); private: + void InitializeWindow(QmitkRenderWindow* window); void HideAllInterpolationControls(); void Show2DInterpolationControls(bool show); void Show3DInterpolationControls(bool show); void CheckSupportedImageDimension(); void WaitForFutures(); void NodeRemoved(const mitk::DataNode* node); mitk::SegmentationInterpolationController::Pointer m_Interpolator; mitk::SurfaceInterpolationController::Pointer m_SurfaceInterpolator; mitk::FeatureBasedEdgeDetectionFilter::Pointer m_EdgeDetector; mitk::PointCloudScoringFilter::Pointer m_PointScorer; mitk::ToolManager::Pointer m_ToolManager; bool m_Initialized; QHash m_ControllerToTimeObserverTag; QHash m_ControllerToSliceObserverTag; QHash m_ControllerToDeleteObserverTag; std::map m_SegmentationObserverTags; unsigned int InterpolationInfoChangedObserverTag; unsigned int SurfaceInterpolationInfoChangedObserverTag; unsigned int InterpolationAbortedObserverTag; QGroupBox *m_GroupBoxEnableExclusiveInterpolationMode; QComboBox *m_CmbInterpolation; QPushButton *m_BtnApply2D; QPushButton *m_BtnApplyForAllSlices2D; QPushButton *m_BtnApply3D; // T28261 // QPushButton *m_BtnSuggestPlane; QCheckBox *m_ChkShowPositionNodes; QPushButton *m_BtnReinit3DInterpolation; mitk::DataNode::Pointer m_FeedbackNode; mitk::DataNode::Pointer m_InterpolatedSurfaceNode; mitk::DataNode::Pointer m_3DContourNode; mitk::Image *m_Segmentation; mitk::SliceNavigationController *m_LastSNC; unsigned int m_LastSliceIndex; QHash m_TimePoints; bool m_2DInterpolationEnabled; bool m_3DInterpolationEnabled; unsigned int m_numTimesLabelSetConnectionAdded; mitk::DataStorage::Pointer m_DataStorage; QFuture m_Future; QFutureWatcher m_Watcher; QFuture m_ModifyFuture; QFutureWatcher m_ModifyWatcher; QTimer *m_Timer; QFuture m_PlaneFuture; QFutureWatcher m_PlaneWatcher; mitk::Label::PixelType m_PreviousActiveLabelValue; mitk::Label::PixelType m_CurrentActiveLabelValue; unsigned int m_PreviousLayerIndex; unsigned int m_CurrentLayerIndex; bool m_FirstRun; }; #endif diff --git a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp index 7675c4257e..59772bcbac 100644 --- a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp +++ b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.cpp @@ -1,237 +1,244 @@ /*============================================================================ 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 "QmitkMxNMultiWidgetEditor.h" #include #include #include #include #include #include // mxn multi widget editor plugin #include "QmitkMultiWidgetDecorationManager.h" // mitk qt widgets module #include #include #include // qt #include const QString QmitkMxNMultiWidgetEditor::EDITOR_ID = "org.mitk.editors.mxnmultiwidget"; struct QmitkMxNMultiWidgetEditor::Impl final { Impl(); ~Impl() = default; QmitkInteractionSchemeToolBar* m_InteractionSchemeToolBar; QmitkMultiWidgetConfigurationToolBar* m_ConfigurationToolBar; }; QmitkMxNMultiWidgetEditor::Impl::Impl() : m_InteractionSchemeToolBar(nullptr) , m_ConfigurationToolBar(nullptr) { // nothing here } ////////////////////////////////////////////////////////////////////////// // QmitkMxNMultiWidgetEditor ////////////////////////////////////////////////////////////////////////// QmitkMxNMultiWidgetEditor::QmitkMxNMultiWidgetEditor() : QmitkAbstractMultiWidgetEditor() , m_Impl(std::make_unique()) { // nothing here } QmitkMxNMultiWidgetEditor::~QmitkMxNMultiWidgetEditor() { GetSite()->GetPage()->RemovePartListener(this); } berry::IPartListener::Events::Types QmitkMxNMultiWidgetEditor::GetPartEventTypes() const { return Events::CLOSED | Events::OPENED | Events::HIDDEN | Events::VISIBLE; } void QmitkMxNMultiWidgetEditor::PartClosed(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(false); } } } void QmitkMxNMultiWidgetEditor::PartOpened(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->EnableCrosshair(); multiWidget->ActivateMenuWidget(true); } } } void QmitkMxNMultiWidgetEditor::PartHidden(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(false); } } } void QmitkMxNMultiWidgetEditor::PartVisible(const berry::IWorkbenchPartReference::Pointer& partRef) { if (partRef->GetId() == QmitkMxNMultiWidgetEditor::EDITOR_ID) { const auto& multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { multiWidget->ActivateMenuWidget(true); } } } void QmitkMxNMultiWidgetEditor::OnLayoutSet(int row, int column) { const auto &multiWidget = dynamic_cast(GetMultiWidget()); if (nullptr != multiWidget) { QmitkAbstractMultiWidgetEditor::OnLayoutSet(row, column); multiWidget->EnableCrosshair(); } } void QmitkMxNMultiWidgetEditor::OnInteractionSchemeChanged(mitk::InteractionSchemeSwitcher::InteractionScheme scheme) { const auto &multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { return; } if (mitk::InteractionSchemeSwitcher::PACSStandard == scheme) { m_Impl->m_InteractionSchemeToolBar->setVisible(true); } else { m_Impl->m_InteractionSchemeToolBar->setVisible(false); } QmitkAbstractMultiWidgetEditor::OnInteractionSchemeChanged(scheme); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkMxNMultiWidgetEditor::SetFocus() { const auto& multiWidget = GetMultiWidget(); if (nullptr != multiWidget) { multiWidget->setFocus(); } } void QmitkMxNMultiWidgetEditor::CreateQtPartControl(QWidget* parent) { QHBoxLayout *layout = new QHBoxLayout(parent); layout->setContentsMargins(0, 0, 0, 0); auto* preferences = this->GetPreferences(); auto multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { multiWidget = new QmitkMxNMultiWidget(parent, 0, nullptr); // create left toolbar: interaction scheme toolbar to switch how the render window navigation behaves in PACS mode if (nullptr == m_Impl->m_InteractionSchemeToolBar) { m_Impl->m_InteractionSchemeToolBar = new QmitkInteractionSchemeToolBar(parent); layout->addWidget(m_Impl->m_InteractionSchemeToolBar); } m_Impl->m_InteractionSchemeToolBar->SetInteractionEventHandler(multiWidget->GetInteractionEventHandler()); multiWidget->SetDataStorage(GetDataStorage()); multiWidget->InitializeMultiWidget(); SetMultiWidget(multiWidget); + connect(static_cast(multiWidget), &QmitkMxNMultiWidget::LayoutChanged, + this, &QmitkMxNMultiWidgetEditor::OnLayoutChanged); } layout->addWidget(multiWidget); // create right toolbar: configuration toolbar to change the render window widget layout if (nullptr == m_Impl->m_ConfigurationToolBar) { m_Impl->m_ConfigurationToolBar = new QmitkMultiWidgetConfigurationToolBar(multiWidget); layout->addWidget(m_Impl->m_ConfigurationToolBar); } connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::LayoutSet, this, &QmitkMxNMultiWidgetEditor::OnLayoutSet); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::Synchronized, this, &QmitkMxNMultiWidgetEditor::OnSynchronize); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::InteractionSchemeChanged, this, &QmitkMxNMultiWidgetEditor::OnInteractionSchemeChanged); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::SaveLayout, static_cast(GetMultiWidget()), &QmitkMxNMultiWidget::SaveLayout, Qt::DirectConnection); connect(m_Impl->m_ConfigurationToolBar, &QmitkMultiWidgetConfigurationToolBar::LoadLayout, static_cast(GetMultiWidget()), &QmitkMxNMultiWidget::LoadLayout); GetSite()->GetPage()->AddPartListener(this); OnPreferencesChanged(preferences); } void QmitkMxNMultiWidgetEditor::OnPreferencesChanged(const mitk::IPreferences* preferences) { const auto& multiWidget = GetMultiWidget(); if (nullptr == multiWidget) { return; } // update decoration preferences //m_Impl->m_MultiWidgetDecorationManager->DecorationPreferencesChanged(preferences); int crosshairGapSize = preferences->GetInt("crosshair gap size", 32); multiWidget->SetCrosshairGap(crosshairGapSize); // zooming and panning preferences bool constrainedZooming = preferences->GetBool("Use constrained zooming and panning", true); mitk::RenderingManager::GetInstance()->SetConstrainedPanningZooming(constrainedZooming); bool PACSInteractionScheme = preferences->GetBool("PACS like mouse interaction", false); OnInteractionSchemeChanged(PACSInteractionScheme ? mitk::InteractionSchemeSwitcher::PACSStandard : mitk::InteractionSchemeSwitcher::MITKStandard); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } + +void QmitkMxNMultiWidgetEditor::OnLayoutChanged() +{ + FirePropertyChange(berry::IWorkbenchPartConstants::PROP_INPUT); +} diff --git a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.h b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.h index 0bedcb650e..efb70fded2 100644 --- a/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.h +++ b/Plugins/org.mitk.gui.qt.mxnmultiwidgeteditor/src/QmitkMxNMultiWidgetEditor.h @@ -1,81 +1,85 @@ /*============================================================================ 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 QmitkMxNMultiWidgetEditor_h #define QmitkMxNMultiWidgetEditor_h #include #include // c++ #include class QmitkMxNMultiWidget; class MXNMULTIWIDGETEDITOR_EXPORT QmitkMxNMultiWidgetEditor final : public QmitkAbstractMultiWidgetEditor { Q_OBJECT public: static const QString EDITOR_ID; QmitkMxNMultiWidgetEditor(); ~QmitkMxNMultiWidgetEditor() override; virtual QmitkLevelWindowWidget* GetLevelWindowWidget() const override { return nullptr; } /** * @brief Overridden from berry::IPartListener */ virtual berry::IPartListener::Events::Types GetPartEventTypes() const override; /** * @brief Overridden from berry::IPartListener */ virtual void PartClosed(const berry::IWorkbenchPartReference::Pointer& partRef) override; /** * @brief Overridden from berry::IPartListener */ virtual void PartOpened(const berry::IWorkbenchPartReference::Pointer& partRef) override; /** * @brief Overridden from berry::IPartListener */ virtual void PartHidden(const berry::IWorkbenchPartReference::Pointer& partRef) override; /** * @brief Overridden from berry::IPartListener */ virtual void PartVisible(const berry::IWorkbenchPartReference::Pointer& partRef) override; void OnLayoutSet(int row, int column) override; void OnInteractionSchemeChanged(mitk::InteractionSchemeSwitcher::InteractionScheme scheme) override; +private Q_SLOTS: + + void OnLayoutChanged(); + private: /** * @brief Overridden from QmitkAbstractRenderEditor */ void SetFocus() override; /** * @brief Overridden from QmitkAbstractRenderEditor */ void CreateQtPartControl(QWidget* parent) override; /** * @brief Overridden from QmitkAbstractRenderEditor */ void OnPreferencesChanged(const mitk::IPreferences* preferences) override; struct Impl; std::unique_ptr m_Impl; }; #endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp index 36720dfc03..e67b0781cd 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1056 +1,1080 @@ /*============================================================================ 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 "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include // us #include #include // Qt #include #include #include // vtk #include #include +namespace +{ + QList Get2DWindows(const QList allWindows) + { + QList all2DWindows; + for (auto* window : allWindows) + { + if (window->GetRenderer()->GetMapperID() == mitk::BaseRenderer::Standard2D) + { + all2DWindows.append(window); + } + } + return all2DWindows; + } +} + const std::string QmitkSegmentationView::VIEW_ID = "org.mitk.views.segmentation"; QmitkSegmentationView::QmitkSegmentationView() : m_Parent(nullptr) , m_Controls(nullptr) , m_RenderWindowPart(nullptr) , m_ToolManager(nullptr) , m_ReferenceNode(nullptr) , m_WorkingNode(nullptr) , m_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) , m_SelectionChangeIsAlreadyBeingHandled(false) { auto isImage = mitk::TNodePredicateDataType::New(); auto isDwi = mitk::NodePredicateDataType::New("DiffusionImage"); auto isDti = mitk::NodePredicateDataType::New("TensorImage"); auto isOdf = mitk::NodePredicateDataType::New("OdfImage"); auto isSegment = mitk::NodePredicateDataType::New("Segment"); auto validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegment))); validImages->AddPredicate(isDwi); validImages->AddPredicate(isDti); validImages->AddPredicate(isOdf); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(mitk::TNodePredicateDataType::New()); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_SegmentationPredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); m_ReferencePredicate = mitk::NodePredicateAnd::New(); m_ReferencePredicate->AddPredicate(validImages); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(m_SegmentationPredicate)); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object"))); m_ReferencePredicate->AddPredicate(mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("hidden object"))); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { this->LooseLabelSetConnection(); // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers from working data for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); // removing all observers from reference data for (NodeTagMapType::iterator dataIter = m_ReferenceDataObserverTags.begin(); dataIter != m_ReferenceDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_ReferenceDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList) { this->OnAnySelectionChanged(); } void QmitkSegmentationView::OnAnySelectionChanged() { // When only a segmentation has been selected and the method is then called by a reference image selection, // the already selected segmentation may not match the geometry predicate of the new reference image anymore. // This will trigger a recursive call of this method further below. While it would be resolved gracefully, we // can spare the extra call with an early-out. The original call of this method will handle the segmentation // selection change afterwards anyway. if (m_SelectionChangeIsAlreadyBeingHandled) return; auto selectedReferenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); bool referenceNodeChanged = false; m_ToolManager->ActivateTool(-1); if (m_ReferenceNode != selectedReferenceNode) { referenceNodeChanged = true; // Remove visibility observer for the current reference node if (m_ReferenceDataObserverTags.find(m_ReferenceNode) != m_ReferenceDataObserverTags.end()) { m_ReferenceNode->GetProperty("visible")->RemoveObserver(m_ReferenceDataObserverTags[m_ReferenceNode]); m_ReferenceDataObserverTags.erase(m_ReferenceNode); } // Set new reference node m_ReferenceNode = selectedReferenceNode; m_ToolManager->SetReferenceData(m_ReferenceNode); // Prepare for a potential recursive call when changing node predicates of the working node selector m_SelectionChangeIsAlreadyBeingHandled = true; if (m_ReferenceNode.IsNull()) { // Without a reference image, allow all segmentations to be selected m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_SelectionChangeIsAlreadyBeingHandled = false; } else { // With a reference image, only allow segmentations that fit the geometry of the reference image to be selected. m_Controls->workingNodeSelector->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry()), m_SegmentationPredicate.GetPointer())); m_SelectionChangeIsAlreadyBeingHandled = false; this->ApplySelectionModeOnReferenceNode(); // Add visibility observer for the new reference node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_ReferenceDataObserverTags[m_ReferenceNode] = m_ReferenceNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } auto selectedWorkingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool workingNodeChanged = false; if (m_WorkingNode != selectedWorkingNode) { workingNodeChanged = true; // Remove visibility observer for the current working node if (m_WorkingDataObserverTags.find(m_WorkingNode) != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } // Disconnect from current label set image this->LooseLabelSetConnection(); // Set new working node m_WorkingNode = selectedWorkingNode; m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { this->ApplySelectionModeOnWorkingNode(); // Connect to new label set image this->EstablishLabelSetConnection(); m_Controls->labelSetWidget->ResetAllTableWidgetItems(); // Add visibility observer for the new segmentation node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } } // Reset camera if any selection changed but only if both reference node and working node are set if ((referenceNodeChanged || workingNodeChanged) && (m_ReferenceNode.IsNotNull() && m_WorkingNode.IsNotNull())) { if (nullptr != m_RenderWindowPart) { m_RenderWindowPart->InitializeViews(m_ReferenceNode->GetData()->GetTimeGeometry(), false); } } this->UpdateGUI(); } void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information( m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::LabelSetIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) { QmitkNewSegmentationDialog dialog(m_Parent); dialog.SetName(QString::fromStdString(newLabel->GetName())); dialog.SetColor(newLabel->GetColor()); if (QDialog::Rejected == dialog.exec()) return; auto name = dialog.GetName(); if (!name.isEmpty()) newLabel->SetName(name.toStdString()); newLabel->SetColor(dialog.GetColor()); } newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnLayersChanged() { this->EstablishLabelSetConnection(); m_Controls->labelSetWidget->ResetAllTableWidgetItems(); } void QmitkSegmentationView::OnShowLabelTable(bool value) { m_Controls->labelSetWidget->setVisible(value); } void QmitkSegmentationView::OnGoToLabel(const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelSetWidgetReset() { this->ValidateSelectionInput(); } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_H), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut"); #ifdef __linux__ segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows #endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->toolSelectionBox2D->SetLayoutColumns(3); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->toolSelectionBox3D->SetLayoutColumns(3); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &QmitkSegmentationView::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); connect(m_Controls->layersWidget, &QmitkLayersWidget::LayersChanged, this, &QmitkSegmentationView::OnLayersChanged); connect(m_Controls->labelsWidget, &QmitkLabelsWidget::ShowLabelTable, this, &QmitkSegmentationView::OnShowLabelTable); // *------------------------ // * LABELSETWIDGET // *------------------------ connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::goToLabel, this, &QmitkSegmentationView::OnGoToLabel); connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::LabelSetWidgetReset, this, &QmitkSegmentationView::OnLabelSetWidgetReset); m_Controls->labelSetWidget->SetDataStorage(this->GetDataStorage()); m_Controls->labelSetWidget->hide(); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::ActiveToolChanged() { if (nullptr == m_RenderWindowPart) { return; } mitk::TimeGeometry* interactionReferenceGeometry = nullptr; auto activeTool = m_ToolManager->GetActiveTool(); if (nullptr != activeTool && m_ReferenceNode.IsNotNull()) { mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNotNull()) { // tool activated, reference image available: set reference geometry interactionReferenceGeometry = m_ReferenceNode->GetData()->GetTimeGeometry(); } } // set the interaction reference geometry for the render window part (might be nullptr) m_RenderWindowPart->SetInteractionReferenceGeometry(interactionReferenceGeometry); } void QmitkSegmentationView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; } if (nullptr != m_Parent) { m_Parent->setEnabled(true); } if (nullptr == m_Controls) { return; } if (nullptr != m_RenderWindowPart) { - // tell the interpolation about tool manager, data storage and render window part - QList controllers; - controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); - controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); - controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); - m_Controls->slicesInterpolator->Initialize(m_ToolManager, controllers); + auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); + m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); if (!m_RenderWindowPart->HasCoupledRenderWindows()) { // react if the active tool changed, only if a render window part with decoupled render windows is used m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); } } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } // remove message-connection to make sure no message is processed if no render window part is available m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); m_Controls->slicesInterpolator->Uninitialize(); } +void QmitkSegmentationView::RenderWindowPartInputChanged(mitk::IRenderWindowPart* /*renderWindowPart*/) +{ + if (nullptr == m_RenderWindowPart) + { + return; + } + + m_Controls->slicesInterpolator->Uninitialize(); + auto all2DWindows = Get2DWindows(m_RenderWindowPart->GetQmitkRenderWindows().values()); + m_Controls->slicesInterpolator->Initialize(m_ToolManager, all2DWindows); +} + void QmitkSegmentationView::OnPreferencesChanged(const mitk::IPreferences* prefs) { auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); m_DefaultLabelNaming = labelSuggestions.empty() ? prefs->GetBool("default label naming", true) : false; // No default label naming when label suggestions are enforced via command-line argument if (nullptr != m_Controls) { m_Controls->labelsWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool slimView = prefs->GetBool("slim view", false); m_Controls->toolSelectionBox2D->SetShowNames(!slimView); m_Controls->toolSelectionBox3D->SetShowNames(!slimView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = QString::fromStdString(prefs->Get("label set preset", "")); this->ApplyDisplayOptions(); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) this->ApplyDisplayOptions(const_cast(node)); this->ApplySelectionMode(); } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } void QmitkSegmentationView::EstablishLabelSetConnection() { if (m_WorkingNode.IsNull()) return; auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) return; workingImage->GetActiveLabelSet()->AddLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1(m_Controls->labelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::LooseLabelSetConnection() { if (m_WorkingNode.IsNull()) return; auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) return; workingImage->GetActiveLabelSet()->AddLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1(m_Controls->labelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr == labelSetImage) { return; } // the outline property can be set in the segmentation preference page node->SetProperty("labelset.contour.active", mitk::BoolProperty::New(m_DrawOutline)); // force render window update to show outline mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplySelectionMode() { if (!m_SelectionMode) return; this->ApplySelectionModeOnReferenceNode(); this->ApplySelectionModeOnWorkingNode(); } void QmitkSegmentationView::ApplySelectionModeOnReferenceNode() { this->ApplySelectionMode(m_ReferenceNode, m_ReferencePredicate); } void QmitkSegmentationView::ApplySelectionModeOnWorkingNode() { this->ApplySelectionMode(m_WorkingNode, m_SegmentationPredicate); } void QmitkSegmentationView::ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate) { if (!m_SelectionMode || node == nullptr || predicate == nullptr) return; auto nodes = this->GetDataStorage()->GetSubset(predicate); for (auto iter = nodes->begin(); iter != nodes->end(); ++iter) (*iter)->SetVisibility(*iter == node); } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* threeDRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (0 == nodes.size()) { return; } std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) { this->ResetMouseCursor(); } if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } m_Controls->layersWidget->UpdateGUI(); m_Controls->labelsWidget->UpdateGUI(); this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { auto referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); bool hasReferenceNode = referenceNode.IsNotNull(); bool hasWorkingNode = workingNode.IsNotNull(); bool hasBothNodes = hasReferenceNode && hasWorkingNode; QString warning; bool toolSelectionBoxesEnabled = hasReferenceNode && hasWorkingNode; unsigned int numberOfLabels = 0; m_Controls->layersWidget->setEnabled(hasWorkingNode); m_Controls->labelsWidget->setEnabled(hasWorkingNode); m_Controls->labelSetWidget->setEnabled(hasWorkingNode); m_Controls->toolSelectionBox2D->setEnabled(hasBothNodes); m_Controls->toolSelectionBox3D->setEnabled(hasBothNodes); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); if (hasReferenceNode) { if (!referenceNode->IsVisible(nullptr)) { warning += tr("The selected reference image is currently not visible!"); toolSelectionBoxesEnabled = false; } } if (hasWorkingNode) { if (!workingNode->IsVisible(nullptr)) { warning += (!warning.isEmpty() ? "
" : "") + tr("The selected segmentation is currently not visible!"); toolSelectionBoxesEnabled = false; } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->layersWidget->setEnabled(true); m_Controls->labelsWidget->setEnabled(true); m_Controls->labelSetWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); auto activeLayer = labelSetImage->GetActiveLayer(); numberOfLabels = labelSetImage->GetNumberOfLabels(activeLayer); if (numberOfLabels > 1) m_Controls->slicesInterpolator->setEnabled(true); } toolSelectionBoxesEnabled &= numberOfLabels > 1; // Here we need to check whether the geometry of the selected segmentation image (working image geometry) // is aligned with the geometry of the 3D render window. // It is not allowed to use a geometry different from the working image geometry for segmenting. // We only need to this if the tool selection box would be enabled without this check. if (toolSelectionBoxesEnabled && nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows()) { const mitk::BaseGeometry* workingNodeGeometry = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* renderWindowGeometry = m_RenderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeometry && nullptr != renderWindowGeometry) { if (!mitk::Equal(*workingNodeGeometry->GetBoundingBox(), *renderWindowGeometry->GetBoundingBox(), mitk::eps, true)) { warning += (!warning.isEmpty() ? "
" : "") + tr("Please reinitialize the selected segmentation image!"); toolSelectionBoxesEnabled = false; } } } m_Controls->toolSelectionBox2D->setEnabled(toolSelectionBoxesEnabled); m_Controls->toolSelectionBox3D->setEnabled(toolSelectionBoxesEnabled); this->UpdateWarningLabel(warning); m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.isEmpty()) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->setText("" + text + ""); m_Controls->selectionWarningLabel->show(); } -} \ No newline at end of file +} diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h index c3908f03a4..2f04a734cd 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h @@ -1,188 +1,189 @@ /*============================================================================ 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 QmitkSegmentationView_h #define QmitkSegmentationView_h #include "ui_QmitkSegmentationViewControls.h" #include #include /** * @brief The segmentation view provides a set of tool to use different segmentation algorithms. * It provides two selection widgets to load an image node and a segmentation node * on which to perform the segmentation. Creating new segmentation nodes is also possible. * The available segmentation tools are grouped into "2D"- and "3D"-tools. * * Most segmentation tools / algorithms need some kind of user interaction, where the * user is asked to draw something in the image display or set some seed points / start values. * The tools also often provide additional propeties so that a user can modify the * algorithm's behavior. * * This class additionally provides options to work with different layers (create new layers, * switch between layers). * Moreover, a multilabel widget displays all the existing labels of a multilabel segmentation * for the currently active layer. * The multilabel widget allows to control the labels by creatin new one, removing existing ones, * showing / hiding single labels, merging labels, (re-)naming them etc. * * Additionally the view provides an option to create "2D"- and "3D"-interpolations between * neighboring segmentation masks on unsegmented slices. * Interpolation for multilabel segmentations is currently not implemented. */ class QmitkSegmentationView : public QmitkAbstractView, public mitk::IRenderWindowPartListener { Q_OBJECT public: static const std::string VIEW_ID; QmitkSegmentationView(); ~QmitkSegmentationView() override; private Q_SLOTS: // reaction to the selection of a new reference image in the selection widget void OnReferenceSelectionChanged(QList nodes); // reaction to the selection of a new segmentation image in the selection widget void OnSegmentationSelectionChanged(QList nodes); // reaction to the shortcut ("CTRL+H") for toggling the visibility of the working node void OnVisibilityShortcutActivated(); // reaction to the shortcut ("CTRL+L") for iterating over all labels void OnLabelToggleShortcutActivated(); // reaction to the button "New segmentation" void OnNewSegmentation(); void OnManualTool2DSelected(int id); void OnShowMarkerNodes(bool); void OnLayersChanged(); void OnShowLabelTable(bool); void OnGoToLabel(const mitk::Point3D &pos); void OnLabelSetWidgetReset(); private: void CreateQtPartControl(QWidget* parent) override; void SetFocus() override {} /** * @brief Enable or disable the SegmentationInteractor. * * The active tool is retrieved from the tool manager. * If the active tool is valid, the SegmentationInteractor is enabled * to listen to 'SegmentationInteractionEvent's. */ void ActiveToolChanged(); /** * @brief Test whether the geometry of the reference image * fits the world geometry of the respective renderer. * * The respective renderer is retrieved from the given event, which * needs to be a 'SegmentationInteractionEvent'. * This event provides not only the sending base renderer but also a * bool that indicates whether the mouse cursor entered or left the * base renderer. * This information is used to compare the renderer's world geometry * with the oriented time geometry of the current reference image. * If the geometries align, the renderer is not blocked anymore and the * view's warning message is removed. * If the geometries do not align, 'ShowRenderWindowWarning' is called * and a warning message is added to the top of this plugin view. * * @param event The observed mitk::SegmentationInteractionEvent. */ void ValidateRendererGeometry(const itk::EventObject& event); void RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) override; + void RenderWindowPartInputChanged(mitk::IRenderWindowPart* renderWindowPart) override; void OnPreferencesChanged(const mitk::IPreferences* prefs) override; void NodeAdded(const mitk::DataNode* node) override; void NodeRemoved(const mitk::DataNode* node) override; void EstablishLabelSetConnection(); void LooseLabelSetConnection(); void OnAnySelectionChanged(); // make sure all images / segmentations look according to the user preference settings void ApplyDisplayOptions(); // decorates a DataNode according to the user preference settings void ApplyDisplayOptions(mitk::DataNode* node); void ApplySelectionMode(); void ApplySelectionModeOnReferenceNode(); void ApplySelectionModeOnWorkingNode(); void ApplySelectionMode(mitk::DataNode* node, mitk::NodePredicateBase* predicate); // If a contourmarker is selected, the plane in the related widget will be reoriented according to the marker`s geometry void OnContourMarkerSelected(const mitk::DataNode* node); void OnSelectionChanged(berry::IWorkbenchPart::Pointer part, const QList &nodes) override; void ResetMouseCursor(); void SetMouseCursor(const us::ModuleResource&, int hotspotX, int hotspotY); void UpdateGUI(); void ValidateSelectionInput(); void UpdateWarningLabel(QString text); std::string GetDefaultLabelSetPreset() const; QWidget* m_Parent; Ui::QmitkSegmentationViewControls* m_Controls; mitk::IRenderWindowPart* m_RenderWindowPart; mitk::ToolManager* m_ToolManager; mitk::DataNode::Pointer m_ReferenceNode; mitk::DataNode::Pointer m_WorkingNode; typedef std::map NodeTagMapType; NodeTagMapType m_WorkingDataObserverTags; NodeTagMapType m_ReferenceDataObserverTags; unsigned int m_RenderingManagerObserverTag; mitk::NodePredicateAnd::Pointer m_ReferencePredicate; mitk::NodePredicateAnd::Pointer m_SegmentationPredicate; bool m_DrawOutline; bool m_SelectionMode; bool m_MouseCursorSet; QString m_LabelSetPresetPreference; bool m_DefaultLabelNaming; bool m_SelectionChangeIsAlreadyBeingHandled; }; #endif