diff --git a/Plugins/org.mitk.gui.qt.segmentation/files.cmake b/Plugins/org.mitk.gui.qt.segmentation/files.cmake index 69bc5eadc3..ae0e49a418 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/files.cmake +++ b/Plugins/org.mitk.gui.qt.segmentation/files.cmake @@ -1,79 +1,81 @@ set(SRC_CPP_FILES QmitkSegmentationPreferencePage.cpp ) set(INTERNAL_CPP_FILES mitkPluginActivator.cpp QmitkSegmentationView.cpp QmitkThresholdAction.cpp QmitkCreatePolygonModelAction.cpp #QmitkStatisticsAction.cpp QmitkAutocropAction.cpp + QmitkAutocropLabelSetImageAction.cpp QmitkDeformableClippingPlaneView.cpp Common/QmitkDataSelectionWidget.cpp SegmentationUtilities/QmitkSegmentationUtilitiesView.cpp SegmentationUtilities/QmitkSegmentationUtilityWidget.cpp SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.cpp SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.cpp SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.cpp SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidget.cpp SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidget.cpp ) set(UI_FILES src/internal/QmitkSegmentationControls.ui src/internal/QmitkDeformableClippingPlaneViewControls.ui src/internal/Common/QmitkDataSelectionWidgetControls.ui src/internal/SegmentationUtilities/QmitkSegmentationUtilitiesViewControls.ui src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidgetControls.ui src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidgetControls.ui src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidgetControls.ui src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidgetControls.ui src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidgetControls.ui ) set(MOC_H_FILES src/QmitkSegmentationPreferencePage.h src/internal/mitkPluginActivator.h src/internal/QmitkSegmentationView.h src/internal/QmitkThresholdAction.h src/internal/QmitkCreatePolygonModelAction.h #src/internal/QmitkStatisticsAction.h src/internal/QmitkAutocropAction.h + src/internal/QmitkAutocropLabelSetImageAction.h src/internal/QmitkDeformableClippingPlaneView.h src/internal/Common/QmitkDataSelectionWidget.h src/internal/SegmentationUtilities/QmitkSegmentationUtilitiesView.h src/internal/SegmentationUtilities/QmitkSegmentationUtilityWidget.h src/internal/SegmentationUtilities/BooleanOperations/QmitkBooleanOperationsWidget.h src/internal/SegmentationUtilities/ImageMasking/QmitkImageMaskingWidget.h src/internal/SegmentationUtilities/ContourModelToImage/QmitkContourModelToImageWidget.h src/internal/SegmentationUtilities/MorphologicalOperations/QmitkMorphologicalOperationsWidget.h src/internal/SegmentationUtilities/SurfaceToImage/QmitkSurfaceToImageWidget.h ) set(CACHED_RESOURCE_FILES resources/segmentation.svg resources/deformablePlane.png resources/clipping_plane_translate_48x48.png resources/clipping_plane_rotate48x48.png resources/clipping_plane_deform48x48.png resources/segmentation_utilities.svg plugin.xml ) set(QRC_FILES resources/segmentation.qrc resources/SegmentationUtilities.qrc resources/BooleanOperationsWidget.qrc resources/MorphologicalOperationsWidget.qrc ) set(CPP_FILES) foreach(file ${SRC_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/${file}) endforeach(file ${SRC_CPP_FILES}) foreach(file ${INTERNAL_CPP_FILES}) set(CPP_FILES ${CPP_FILES} src/internal/${file}) endforeach(file ${INTERNAL_CPP_FILES}) diff --git a/Plugins/org.mitk.gui.qt.segmentation/plugin.xml b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml index 56282d66a3..24f382cdeb 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/plugin.xml +++ b/Plugins/org.mitk.gui.qt.segmentation/plugin.xml @@ -1,94 +1,94 @@ Allows the segmentation of images using different tools. Allow the clipping of a volume using a deformable plane. Edit segmentations using standard operations. - + diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp new file mode 100644 index 0000000000..9789f6d240 --- /dev/null +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp @@ -0,0 +1,289 @@ +/*============================================================================ + +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 "QmitkAutocropLabelSetImageAction.h" + +#include +#include +#include +#include + +#include + +QmitkAutocropLabelSetImageAction::QmitkAutocropLabelSetImageAction() +{ +} + +QmitkAutocropLabelSetImageAction::~QmitkAutocropLabelSetImageAction() +{ +} + +void QmitkAutocropLabelSetImageAction::Run(const QList& selectedNodes) +{ + using PixelType = mitk::LabelSetImage::PixelType; + using PixelReaderType = mitk::ImagePixelReadAccessor; + + for (const auto& dataNode : selectedNodes) + { + if (nullptr == dataNode->GetData()) + continue; + + auto* labelSetImage = dynamic_cast(dataNode->GetData()); + + if (nullptr == labelSetImage) + continue; + + // Backup currently active layer as we need to restore it later + auto activeLayer = labelSetImage->GetActiveLayer(); + + const auto background = labelSetImage->GetExteriorLabel()->GetValue(); + const auto numLayers = labelSetImage->GetNumberOfLayers(); + const auto numTimeSteps = labelSetImage->GetTimeSteps(); + + // We need a time selector to handle 3d+t images. It is not used for 3d images, though. + auto timeSelector = mitk::ImageTimeSelector::New(); + timeSelector->SetInput(labelSetImage); + + // Iterate over all layers, time steps, and dimensions to determine the overall minimum + // and maximum indices of labeled pixels + + const itk::Index<3> dim = { + labelSetImage->GetDimension(0), + labelSetImage->GetDimension(1), + labelSetImage->GetDimension(2) + }; + + itk::Index<3> maxIndex = { 0, 0, 0 }; + itk::Index<3> minIndex = dim; + itk::Index<3> index; + + bool labelSetImageIsEmpty = true; + + for (std::remove_const_t layer = 0; layer < numLayers; ++layer) + { + labelSetImage->SetActiveLayer(layer); + + for (std::remove_const_t timeStep = 0; timeStep < numTimeSteps; ++timeStep) + { + const mitk::Image* image = nullptr; + + if (numTimeSteps > 1) + { + timeSelector->SetTimeNr(timeStep); + timeSelector->Update(); + image = timeSelector->GetOutput(); + } + else + { + image = labelSetImage; + } + + std::unique_ptr pixelReader; + + try + { + pixelReader = std::make_unique(image); + } + catch (const mitk::Exception&) + { + MITK_ERROR << "Autocrop was aborted: Image read access to \"" << dataNode->GetName() << "\" was denied."; + return; + } + + bool imageIsEmpty = true; + + for (index[2] = 0; index[2] < dim[2]; ++index[2]) + { + for (index[1] = 0; index[1] < dim[1]; ++index[1]) + { + for (index[0] = 0; index[0] < dim[0]; ++index[0]) + { + if (background != pixelReader->GetPixelByIndex(index)) + { + imageIsEmpty = false; + minIndex = { + std::min(minIndex[0], index[0]), + std::min(minIndex[1], index[1]), + std::min(minIndex[2], index[2]) + }; + break; + } + } + } + } + + if (imageIsEmpty) + continue; + + maxIndex = { + std::max(maxIndex[0], minIndex[0]), + std::max(maxIndex[1], minIndex[1]), + std::max(maxIndex[2], minIndex[2]) + }; + + for (index[2] = dim[2] - 1; index[2] >= 0; --index[2]) + { + for (index[1] = dim[1] - 1; index[1] >= 0; --index[1]) + { + for (index[0] = dim[0] - 1; index[0] >= 0; --index[0]) + { + if (background != pixelReader->GetPixelByIndex(index)) + { + maxIndex = { + std::max(maxIndex[0], index[0]), + std::max(maxIndex[1], index[1]), + std::max(maxIndex[2], index[2]) + }; + break; + } + } + } + } + + if (!imageIsEmpty) + labelSetImageIsEmpty = false; + } + } + + if (labelSetImageIsEmpty) + { + MITK_WARN << "Autocrop was skipped: Image \"" << dataNode->GetName() << "\" is empty."; + return; + } + + // We are ready to construct a cropped LabelSetImage. Fasten your seat belts, please! + + const itk::Index<3> croppedDim = { + 1 + maxIndex[0] - minIndex[0], + 1 + maxIndex[1] - minIndex[1], + 1 + maxIndex[2] - minIndex[2] + }; + + const auto numPixels = croppedDim[0] * croppedDim[1] * croppedDim[2]; + + mitk::BaseGeometry::BoundsArrayType croppedBounds; + croppedBounds[0] = 0; + croppedBounds[1] = croppedDim[0]; + croppedBounds[2] = 0; + croppedBounds[3] = croppedDim[1]; + croppedBounds[4] = 0; + croppedBounds[5] = croppedDim[2]; + + // Clone and adapt the original TimeGeometry to the cropped region + + auto croppedTimeGeometry = labelSetImage->GetTimeGeometry()->Clone(); + + for (std::remove_const_t timeStep = 0; timeStep < numTimeSteps; ++timeStep) + { + auto geometry = croppedTimeGeometry->GetGeometryForTimeStep(timeStep); + + mitk::Point3D croppedOrigin; + geometry->IndexToWorld(minIndex, croppedOrigin); + geometry->SetOrigin(croppedOrigin); + + geometry->SetBounds(croppedBounds); + } + + auto croppedLabelSetImage = mitk::LabelSetImage::New(); + croppedLabelSetImage->Initialize(mitk::MakeScalarPixelType(), *croppedTimeGeometry); + + // Create cropped image volumes for all time steps in all layers + + for (std::remove_const_t layer = 0; layer < numLayers; ++layer) + { + labelSetImage->SetActiveLayer(layer); + croppedLabelSetImage->AddLayer(); + + for (std::remove_const_t timeStep = 0; timeStep < numTimeSteps; ++timeStep) + { + auto* croppedVolume = new PixelType[numPixels]; + + const mitk::Image* image = nullptr; + + if (numTimeSteps > 1) + { + timeSelector->SetTimeNr(timeStep); + timeSelector->Update(); + image = timeSelector->GetOutput(); + } + else + { + image = labelSetImage; + } + + std::unique_ptr pixelReader; + + try + { + pixelReader = std::make_unique(image); + } + catch (const mitk::Exception&) + { + MITK_ERROR << "Autocrop was aborted: Image read access to \"" << dataNode->GetName() << "\" was denied."; + return; + } + + itk::Index<3> croppedIndex; + + for (croppedIndex[2] = 0; croppedIndex[2] < croppedDim[2]; ++croppedIndex[2]) + { + for (croppedIndex[1] = 0; croppedIndex[1] < croppedDim[1]; ++croppedIndex[1]) + { + // The inner loop may be replaced by a single memcpy, also using an ImageReadAccessor + // instead of an ImagePixelReadAccessor. I wasn't able to correctly calculate the + // source address, though. You like challenges? - Go ahead! :-) + for (croppedIndex[0] = 0; croppedIndex[0] < croppedDim[0]; ++croppedIndex[0]) + { + index[0] = croppedIndex[0] + minIndex[0]; + index[1] = croppedIndex[1] + minIndex[1]; + index[2] = croppedIndex[2] + minIndex[2]; + const auto& pixel = pixelReader->GetPixelByIndex(index); + + croppedVolume[croppedIndex[2] * croppedDim[1] * croppedDim[0] + croppedIndex[1] * croppedDim[0] + croppedIndex[0]] = pixel; + } + } + } + + croppedLabelSetImage->SetVolume(croppedVolume, timeStep); + + // Make sure the labels still have their original properties like names and colors + croppedLabelSetImage->AddLabelSetToLayer(layer, labelSetImage->GetLabelSet(layer)); + } + } + + // Restore the originally active layer + croppedLabelSetImage->SetActiveLayer(activeLayer); + + // Override the original LabelSetImage with the cropped LabelSetImage + dataNode->SetData(croppedLabelSetImage); + + // If we cropped a single LabelSetImage, reinit the views to give a visible feedback to the user + if (1 == selectedNodes.size()) + mitk::RenderingManager::GetInstance()->InitializeViews(croppedTimeGeometry, mitk::RenderingManager::REQUEST_UPDATE_ALL, true); + } +} + +void QmitkAutocropLabelSetImageAction::SetSmoothed(bool) +{ +} + +void QmitkAutocropLabelSetImageAction::SetDecimated(bool) +{ +} + +void QmitkAutocropLabelSetImageAction::SetDataStorage(mitk::DataStorage*) +{ +} + +void QmitkAutocropLabelSetImageAction::SetFunctionality(berry::QtViewPart*) +{ +} diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.h new file mode 100644 index 0000000000..e882b2903a --- /dev/null +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.h @@ -0,0 +1,35 @@ +/*============================================================================ + +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 QmitkAutocropLabelSetImageAction_h +#define QmitkAutocropLabelSetImageAction_h + +#include +#include + +class MITK_QT_SEGMENTATION QmitkAutocropLabelSetImageAction : public QObject, public mitk::IContextMenuAction +{ + Q_OBJECT + Q_INTERFACES(mitk::IContextMenuAction) + +public: + QmitkAutocropLabelSetImageAction(); + ~QmitkAutocropLabelSetImageAction() override; + + void Run(const QList& selectedNodes) override; + void SetDataStorage(mitk::DataStorage* dataStorage) override; + void SetSmoothed(bool smoothed) override; + void SetDecimated(bool decimated) override; + void SetFunctionality(berry::QtViewPart* view) override; +}; + +#endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp index c21a7055b0..173eff2449 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/mitkPluginActivator.cpp @@ -1,63 +1,65 @@ /*============================================================================ 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 "mitkPluginActivator.h" #include "QmitkSegmentationView.h" #include "QmitkThresholdAction.h" #include "QmitkCreatePolygonModelAction.h" #include "QmitkAutocropAction.h" +#include "QmitkAutocropLabelSetImageAction.h" #include "QmitkSegmentationPreferencePage.h" #include "QmitkDeformableClippingPlaneView.h" #include "SegmentationUtilities/QmitkSegmentationUtilitiesView.h" using namespace mitk; ctkPluginContext* PluginActivator::m_context = nullptr; PluginActivator* PluginActivator::m_Instance = nullptr; PluginActivator::PluginActivator() { m_Instance = this; } PluginActivator::~PluginActivator() { m_Instance = nullptr; } void PluginActivator::start(ctkPluginContext *context) { BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationView, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkThresholdAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkCreatePolygonModelAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkAutocropAction, context) + BERRY_REGISTER_EXTENSION_CLASS(QmitkAutocropLabelSetImageAction, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationPreferencePage, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkDeformableClippingPlaneView, context) BERRY_REGISTER_EXTENSION_CLASS(QmitkSegmentationUtilitiesView, context) this->m_context = context; } void PluginActivator::stop(ctkPluginContext *) { this->m_context = nullptr; } PluginActivator* PluginActivator::getDefault() { return m_Instance; } ctkPluginContext*PluginActivator::getContext() { return m_context; }