diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp index e53d577736..730183f65e 100755 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp @@ -1,292 +1,292 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include mitk::ContourModelUtils::ContourModelUtils() { } mitk::ContourModelUtils::~ContourModelUtils() { } mitk::ContourModel::Pointer mitk::ContourModelUtils::ProjectContourTo2DSlice( const Image *slice, const ContourModel *contourIn3D) { if (nullptr == slice || nullptr == contourIn3D) return nullptr; auto projectedContour = ContourModel::New(); projectedContour->Initialize(*contourIn3D); auto sliceGeometry = slice->GetGeometry(); const auto numberOfTimesteps = static_cast(contourIn3D->GetTimeSteps()); for (std::remove_const_t t = 0; t < numberOfTimesteps; ++t) { auto iter = contourIn3D->Begin(t); auto end = contourIn3D->End(t); while (iter != end) { const auto ¤tPointIn3D = (*iter)->Coordinates; Point3D projectedPointIn2D; projectedPointIn2D.Fill(0.0); sliceGeometry->WorldToIndex(currentPointIn3D, projectedPointIn2D); projectedContour->AddVertex(projectedPointIn2D, t); ++iter; } } return projectedContour; } mitk::ContourModel::Pointer mitk::ContourModelUtils::BackProjectContourFrom2DSlice( const BaseGeometry *sliceGeometry, const ContourModel *contourIn2D) { if (nullptr == sliceGeometry || nullptr == contourIn2D) return nullptr; auto worldContour = ContourModel::New(); worldContour->Initialize(*contourIn2D); const auto numberOfTimesteps = static_cast(contourIn2D->GetTimeSteps()); for (std::remove_const_t t = 0; t < numberOfTimesteps; ++t) { auto iter = contourIn2D->Begin(t); auto end = contourIn2D->End(t); while (iter != end) { const auto ¤tPointIn2D = (*iter)->Coordinates; Point3D worldPointIn3D; worldPointIn3D.Fill(0.0); sliceGeometry->IndexToWorld(currentPointIn2D, worldPointIn3D); worldContour->AddVertex(worldPointIn3D, t); ++iter; } } return worldContour; } void mitk::ContourModelUtils::FillContourInSlice2( const ContourModel* projectedContour, Image* sliceImage, int paintingPixelValue) { FillContourInSlice2(projectedContour, 0, sliceImage, paintingPixelValue); } void mitk::ContourModelUtils::FillContourInSlice2( const ContourModel* projectedContour, TimeStepType contourTimeStep, Image* sliceImage, int paintingPixelValue) { if (nullptr == projectedContour) { mitkThrow() << "Cannot fill contour in slice. Passed contour is invalid"; } if (nullptr == sliceImage) { mitkThrow() << "Cannot fill contour in slice. Passed slice is invalid"; } auto contourModelFilter = mitk::ContourModelToSurfaceFilter::New(); contourModelFilter->SetInput(projectedContour); contourModelFilter->Update(); auto surface = mitk::Surface::New(); surface = contourModelFilter->GetOutput(); if (nullptr == surface->GetVtkPolyData(contourTimeStep)) { MITK_WARN << "Could not create surface from contour model."; return; } auto surface2D = vtkSmartPointer::New(); surface2D->SetPoints(surface->GetVtkPolyData(contourTimeStep)->GetPoints()); surface2D->SetLines(surface->GetVtkPolyData(contourTimeStep)->GetLines()); auto polyDataToImageStencil = vtkSmartPointer::New(); // Set a minimal tolerance, so that clipped pixels will be added to contour as well. polyDataToImageStencil->SetTolerance(mitk::eps); polyDataToImageStencil->SetInputData(surface2D); polyDataToImageStencil->Update(); auto imageStencil = vtkSmartPointer::New(); imageStencil->SetInputData(sliceImage->GetVtkImageData()); imageStencil->SetStencilConnection(polyDataToImageStencil->GetOutputPort()); imageStencil->ReverseStencilOn(); imageStencil->SetBackgroundValue(paintingPixelValue); imageStencil->Update(); vtkSmartPointer filledImage = imageStencil->GetOutput(); sliceImage->SetVolume(filledImage->GetScalarPointer()); } void mitk::ContourModelUtils::FillContourInSlice( const ContourModel *projectedContour, Image *sliceImage, const Image* workingImage, int paintingPixelValue) { FillContourInSlice(projectedContour, 0, sliceImage, workingImage, paintingPixelValue); } void mitk::ContourModelUtils::FillContourInSlice( const ContourModel *projectedContour, TimeStepType contourTimeStep, Image *sliceImage, const Image* workingImage, int paintingPixelValue) { if (nullptr == projectedContour) { mitkThrow() << "Cannot fill contour in slice. Passed contour is invalid"; } if (nullptr == sliceImage) { mitkThrow() << "Cannot fill contour in slice. Passed slice is invalid"; } auto contourModelFilter = mitk::ContourModelToSurfaceFilter::New(); contourModelFilter->SetInput(projectedContour); contourModelFilter->Update(); auto surface = mitk::Surface::New(); surface = contourModelFilter->GetOutput(); if (nullptr == surface->GetVtkPolyData(contourTimeStep)) { MITK_WARN << "Could not create surface from contour model."; return; } auto surface2D = vtkSmartPointer::New(); surface2D->SetPoints(surface->GetVtkPolyData(contourTimeStep)->GetPoints()); surface2D->SetLines(surface->GetVtkPolyData(contourTimeStep)->GetLines()); auto image = vtkSmartPointer::New(); image->DeepCopy(sliceImage->GetVtkImageData()); const double FOREGROUND_VALUE = 255.0; const double BACKGROUND_VALUE = 0.0; const vtkIdType count = image->GetNumberOfPoints(); for (std::remove_const_t i = 0; i < count; ++i) image->GetPointData()->GetScalars()->SetTuple1(i, FOREGROUND_VALUE); auto polyDataToImageStencil = vtkSmartPointer::New(); // Set a minimal tolerance, so that clipped pixels will be added to contour as well. polyDataToImageStencil->SetTolerance(mitk::eps); polyDataToImageStencil->SetInputData(surface2D); polyDataToImageStencil->Update(); auto imageStencil = vtkSmartPointer::New(); imageStencil->SetInputData(image); imageStencil->SetStencilConnection(polyDataToImageStencil->GetOutputPort()); imageStencil->ReverseStencilOff(); imageStencil->SetBackgroundValue(BACKGROUND_VALUE); imageStencil->Update(); vtkSmartPointer filledImage = imageStencil->GetOutput(); vtkSmartPointer resultImage = sliceImage->GetVtkImageData(); FillSliceInSlice(filledImage, resultImage, workingImage, paintingPixelValue); sliceImage->SetVolume(resultImage->GetScalarPointer()); } void mitk::ContourModelUtils::FillSliceInSlice( vtkSmartPointer filledImage, vtkSmartPointer resultImage, const Image* image, int paintingPixelValue, double fillForegroundThreshold) { auto labelImage = dynamic_cast(image); const auto numberOfPoints = filledImage->GetNumberOfPoints(); if (nullptr == labelImage) { for (std::remove_const_t i = 0; i < numberOfPoints; ++i) { if (fillForegroundThreshold <= filledImage->GetPointData()->GetScalars()->GetTuple1(i)) resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } else { - if (paintingPixelValue != LabelSetImage::UnlabeledValue) + if (paintingPixelValue != LabelSetImage::UNLABELED_VALUE) { for (std::remove_const_t i = 0; i < numberOfPoints; ++i) { const auto filledValue = filledImage->GetPointData()->GetScalars()->GetTuple1(i); if (fillForegroundThreshold <= filledValue) { const auto existingValue = resultImage->GetPointData()->GetScalars()->GetTuple1(i); if (!labelImage->IsLabelLocked(existingValue)) resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } } else { - const auto activePixelValue = labelImage->GetActiveLabel(labelImage->GetActiveLayer())->GetValue(); + const auto activePixelValue = labelImage->GetActiveLabel()->GetValue(); for (std::remove_const_t i = 0; i < numberOfPoints; ++i) { if (fillForegroundThreshold <= filledImage->GetPointData()->GetScalars()->GetTuple1(i)) { if (resultImage->GetPointData()->GetScalars()->GetTuple1(i) == activePixelValue) resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } } } } mitk::ContourModel::Pointer mitk::ContourModelUtils::MoveZerothContourTimeStep(const ContourModel *contour, TimeStepType t) { if (nullptr == contour) return nullptr; auto resultContour = ContourModel::New(); resultContour->Expand(t + 1); std::for_each(contour->Begin(), contour->End(), [&resultContour, t](ContourElement::VertexType *vertex) { resultContour->AddVertex(*vertex, t); }); return resultContour; } int mitk::ContourModelUtils::GetActivePixelValue(const Image* workingImage) { auto labelSetImage = dynamic_cast(workingImage); int activePixelValue = 1; if (nullptr != labelSetImage) { - activePixelValue = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue(); + activePixelValue = labelSetImage->GetActiveLabel()->GetValue(); } return activePixelValue; } diff --git a/Modules/Core/files.cmake b/Modules/Core/files.cmake index 1aa29e2075..e9f3daabdf 100644 --- a/Modules/Core/files.cmake +++ b/Modules/Core/files.cmake @@ -1,328 +1,329 @@ file(GLOB_RECURSE H_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_CURRENT_SOURCE_DIR}/include/*") set(CPP_FILES mitkCoreActivator.cpp mitkCoreObjectFactoryBase.cpp mitkCoreObjectFactory.cpp mitkCoreServices.cpp mitkException.cpp Algorithms/mitkBaseDataSource.cpp Algorithms/mitkClippedSurfaceBoundsCalculator.cpp Algorithms/mitkCompareImageDataFilter.cpp Algorithms/mitkCompositePixelValueToString.cpp Algorithms/mitkConvert2Dto3DImageFilter.cpp Algorithms/mitkDataNodeSource.cpp Algorithms/mitkExtractSliceFilter.cpp Algorithms/mitkExtractSliceFilter2.cpp Algorithms/mitkHistogramGenerator.cpp Algorithms/mitkImageChannelSelector.cpp Algorithms/mitkImageSliceSelector.cpp Algorithms/mitkImageSource.cpp Algorithms/mitkImageTimeSelector.cpp Algorithms/mitkImageToImageFilter.cpp Algorithms/mitkImageToSurfaceFilter.cpp Algorithms/mitkMultiComponentImageDataComparisonFilter.cpp Algorithms/mitkPlaneGeometryDataToSurfaceFilter.cpp Algorithms/mitkPointSetSource.cpp Algorithms/mitkPointSetToPointSetFilter.cpp Algorithms/mitkRGBToRGBACastImageFilter.cpp Algorithms/mitkSubImageSelector.cpp Algorithms/mitkSurfaceSource.cpp Algorithms/mitkSurfaceToImageFilter.cpp Algorithms/mitkSurfaceToSurfaceFilter.cpp Algorithms/mitkUIDGenerator.cpp Algorithms/mitkVolumeCalculator.cpp Algorithms/mitkTemporalJoinImagesFilter.cpp Controllers/mitkBaseController.cpp Controllers/mitkCallbackFromGUIThread.cpp Controllers/mitkCameraController.cpp Controllers/mitkCameraRotationController.cpp Controllers/mitkCrosshairManager.cpp Controllers/mitkLimitedLinearUndo.cpp Controllers/mitkOperationEvent.cpp Controllers/mitkPlanePositionManager.cpp Controllers/mitkProgressBar.cpp Controllers/mitkRenderingManager.cpp Controllers/mitkSliceNavigationController.cpp Controllers/mitkSliceNavigationHelper.cpp Controllers/mitkStatusBar.cpp Controllers/mitkStepper.cpp Controllers/mitkTestManager.cpp Controllers/mitkTimeNavigationController.cpp Controllers/mitkUndoController.cpp Controllers/mitkVerboseLimitedLinearUndo.cpp Controllers/mitkVtkLayerController.cpp DataManagement/mitkAnatomicalStructureColorPresets.cpp DataManagement/mitkArbitraryTimeGeometry.cpp DataManagement/mitkAbstractTransformGeometry.cpp DataManagement/mitkAnnotationProperty.cpp DataManagement/mitkApplicationCursor.cpp DataManagement/mitkApplyTransformMatrixOperation.cpp DataManagement/mitkBaseData.cpp DataManagement/mitkBaseGeometry.cpp DataManagement/mitkBaseProperty.cpp DataManagement/mitkChannelDescriptor.cpp DataManagement/mitkClippingProperty.cpp DataManagement/mitkColorProperty.cpp DataManagement/mitkCrosshairData.cpp DataManagement/mitkDataNode.cpp DataManagement/mitkDataStorage.cpp DataManagement/mitkEnumerationProperty.cpp DataManagement/mitkFloatPropertyExtension.cpp DataManagement/mitkGeometry3D.cpp DataManagement/mitkGeometryData.cpp DataManagement/mitkGeometryTransformHolder.cpp DataManagement/mitkGroupTagProperty.cpp DataManagement/mitkGenericIDRelationRule.cpp DataManagement/mitkIdentifiable.cpp DataManagement/mitkImageAccessorBase.cpp DataManagement/mitkImageCaster.cpp DataManagement/mitkImageCastPart1.cpp DataManagement/mitkImageCastPart2.cpp DataManagement/mitkImageCastPart3.cpp DataManagement/mitkImageCastPart4.cpp DataManagement/mitkImage.cpp DataManagement/mitkImageDataItem.cpp DataManagement/mitkImageDescriptor.cpp DataManagement/mitkImageReadAccessor.cpp DataManagement/mitkImageStatisticsHolder.cpp DataManagement/mitkImageVtkAccessor.cpp DataManagement/mitkImageVtkReadAccessor.cpp DataManagement/mitkImageVtkWriteAccessor.cpp DataManagement/mitkImageWriteAccessor.cpp DataManagement/mitkIntPropertyExtension.cpp DataManagement/mitkIPersistenceService.cpp DataManagement/mitkIPropertyAliases.cpp DataManagement/mitkIPropertyDescriptions.cpp DataManagement/mitkIPropertyDeserialization.cpp DataManagement/mitkIPropertyExtensions.cpp DataManagement/mitkIPropertyFilters.cpp DataManagement/mitkIPropertyOwner.cpp DataManagement/mitkIPropertyPersistence.cpp DataManagement/mitkIPropertyProvider.cpp + DataManagement/mitkITKEventObserverGuard.cpp DataManagement/mitkLandmarkProjectorBasedCurvedGeometry.cpp DataManagement/mitkLandmarkProjector.cpp DataManagement/mitkLevelWindow.cpp DataManagement/mitkLevelWindowManager.cpp DataManagement/mitkLevelWindowPreset.cpp DataManagement/mitkLevelWindowProperty.cpp DataManagement/mitkLookupTable.cpp DataManagement/mitkLookupTableProperty.cpp DataManagement/mitkLookupTables.cpp DataManagement/mitkMaterial.cpp DataManagement/mitkMemoryUtilities.cpp DataManagement/mitkModalityProperty.cpp DataManagement/mitkModifiedLock.cpp DataManagement/mitkNodePredicateAnd.cpp DataManagement/mitkNodePredicateBase.cpp DataManagement/mitkNodePredicateCompositeBase.cpp DataManagement/mitkNodePredicateData.cpp DataManagement/mitkNodePredicateDataType.cpp DataManagement/mitkNodePredicateDataUID.cpp DataManagement/mitkNodePredicateDimension.cpp DataManagement/mitkNodePredicateFunction.cpp DataManagement/mitkNodePredicateGeometry.cpp DataManagement/mitkNodePredicateNot.cpp DataManagement/mitkNodePredicateOr.cpp DataManagement/mitkNodePredicateProperty.cpp DataManagement/mitkNodePredicateDataProperty.cpp DataManagement/mitkNodePredicateSubGeometry.cpp DataManagement/mitkNumericConstants.cpp DataManagement/mitkPlaneGeometry.cpp DataManagement/mitkPlaneGeometryData.cpp DataManagement/mitkPlaneOperation.cpp DataManagement/mitkPlaneOrientationProperty.cpp DataManagement/mitkPointOperation.cpp DataManagement/mitkPointSet.cpp DataManagement/mitkPointSetShapeProperty.cpp DataManagement/mitkProperties.cpp DataManagement/mitkPropertyAliases.cpp DataManagement/mitkPropertyDescriptions.cpp DataManagement/mitkPropertyDeserialization.cpp DataManagement/mitkPropertyExtension.cpp DataManagement/mitkPropertyExtensions.cpp DataManagement/mitkPropertyFilter.cpp DataManagement/mitkPropertyFilters.cpp DataManagement/mitkPropertyKeyPath.cpp DataManagement/mitkPropertyList.cpp DataManagement/mitkPropertyListReplacedObserver.cpp DataManagement/mitkPropertyNameHelper.cpp DataManagement/mitkPropertyObserver.cpp DataManagement/mitkPropertyPersistence.cpp DataManagement/mitkPropertyPersistenceInfo.cpp DataManagement/mitkPropertyRelationRuleBase.cpp DataManagement/mitkProportionalTimeGeometry.cpp DataManagement/mitkRenderingModeProperty.cpp DataManagement/mitkResliceMethodProperty.cpp DataManagement/mitkRestorePlanePositionOperation.cpp DataManagement/mitkRotationOperation.cpp DataManagement/mitkScaleOperation.cpp DataManagement/mitkSlicedData.cpp DataManagement/mitkSlicedGeometry3D.cpp DataManagement/mitkSmartPointerProperty.cpp DataManagement/mitkStandaloneDataStorage.cpp DataManagement/mitkStringProperty.cpp DataManagement/mitkSurface.cpp DataManagement/mitkSurfaceOperation.cpp DataManagement/mitkSourceImageRelationRule.cpp DataManagement/mitkThinPlateSplineCurvedGeometry.cpp DataManagement/mitkTimeGeometry.cpp DataManagement/mitkTransferFunction.cpp DataManagement/mitkTransferFunctionInitializer.cpp DataManagement/mitkTransferFunctionProperty.cpp DataManagement/mitkTemporoSpatialStringProperty.cpp DataManagement/mitkUIDManipulator.cpp DataManagement/mitkVectorProperty.cpp DataManagement/mitkVtkInterpolationProperty.cpp DataManagement/mitkVtkRepresentationProperty.cpp DataManagement/mitkVtkResliceInterpolationProperty.cpp DataManagement/mitkVtkScalarModeProperty.cpp DataManagement/mitkWeakPointerProperty.cpp DataManagement/mitkIPropertyRelations.cpp DataManagement/mitkPropertyRelations.cpp Interactions/mitkAction.cpp Interactions/mitkBindDispatcherInteractor.cpp Interactions/mitkDataInteractor.cpp Interactions/mitkDispatcher.cpp Interactions/mitkDisplayActionEventBroadcast.cpp Interactions/mitkDisplayActionEventFunctions.cpp Interactions/mitkDisplayActionEventHandler.cpp Interactions/mitkDisplayActionEventHandlerDesynchronized.cpp Interactions/mitkDisplayActionEventHandlerStd.cpp Interactions/mitkDisplayActionEventHandlerSynchronized.cpp Interactions/mitkDisplayCoordinateOperation.cpp Interactions/mitkEventConfig.cpp Interactions/mitkEventFactory.cpp Interactions/mitkEventRecorder.cpp Interactions/mitkEventStateMachine.cpp Interactions/mitkInteractionEventConst.cpp Interactions/mitkInteractionEvent.cpp Interactions/mitkInteractionEventHandler.cpp Interactions/mitkInteractionEventObserver.cpp Interactions/mitkInteractionKeyEvent.cpp Interactions/mitkInteractionPositionEvent.cpp Interactions/mitkInteractionSchemeSwitcher.cpp Interactions/mitkInternalEvent.cpp Interactions/mitkMouseDoubleClickEvent.cpp Interactions/mitkMouseMoveEvent.cpp Interactions/mitkMousePressEvent.cpp Interactions/mitkMouseReleaseEvent.cpp Interactions/mitkMouseWheelEvent.cpp Interactions/mitkPointSetDataInteractor.cpp Interactions/mitkSinglePointDataInteractor.cpp Interactions/mitkStateMachineAction.cpp Interactions/mitkStateMachineCondition.cpp Interactions/mitkStateMachineContainer.cpp Interactions/mitkStateMachineState.cpp Interactions/mitkStateMachineTransition.cpp Interactions/mitkVtkEventAdapter.cpp Interactions/mitkVtkInteractorStyle.cxx Interactions/mitkXML2EventParser.cpp IO/mitkAbstractFileIO.cpp IO/mitkAbstractFileReader.cpp IO/mitkAbstractFileWriter.cpp IO/mitkCustomMimeType.cpp IO/mitkFileReader.cpp IO/mitkFileReaderRegistry.cpp IO/mitkFileReaderSelector.cpp IO/mitkFileReaderWriterBase.cpp IO/mitkFileWriter.cpp IO/mitkFileWriterRegistry.cpp IO/mitkFileWriterSelector.cpp IO/mitkGeometry3DToXML.cpp IO/mitkIFileIO.cpp IO/mitkIFileReader.cpp IO/mitkIFileWriter.cpp IO/mitkGeometryDataReaderService.cpp IO/mitkGeometryDataWriterService.cpp IO/mitkImageVtkLegacyIO.cpp IO/mitkImageVtkXmlIO.cpp IO/mitkIMimeTypeProvider.cpp IO/mitkIOConstants.cpp IO/mitkIOMimeTypes.cpp IO/mitkIOUtil.cpp IO/mitkItkImageIO.cpp IO/mitkItkLoggingAdapter.cpp IO/mitkLegacyFileReaderService.cpp IO/mitkLegacyFileWriterService.cpp IO/mitkLocaleSwitch.cpp IO/mitkLogBackend.cpp IO/mitkMimeType.cpp IO/mitkMimeTypeProvider.cpp IO/mitkOperation.cpp IO/mitkPixelType.cpp IO/mitkPointSetReaderService.cpp IO/mitkPointSetWriterService.cpp IO/mitkProportionalTimeGeometryToXML.cpp IO/mitkRawImageFileReader.cpp IO/mitkStandardFileLocations.cpp IO/mitkSurfaceStlIO.cpp IO/mitkSurfaceVtkIO.cpp IO/mitkSurfaceVtkLegacyIO.cpp IO/mitkSurfaceVtkXmlIO.cpp IO/mitkUtf8Util.cpp IO/mitkVtkLoggingAdapter.cpp IO/mitkPreferenceListReaderOptionsFunctor.cpp IO/mitkIOMetaInformationPropertyConstants.cpp IO/mitkIPreferences.cpp IO/mitkPreferences.cpp IO/mitkIPreferencesService.cpp IO/mitkPreferencesService.cpp IO/mitkIPreferencesStorage.cpp IO/mitkXMLPreferencesStorage.cpp Rendering/mitkAbstractAnnotationRenderer.cpp Rendering/mitkAnnotationUtils.cpp Rendering/mitkBaseRenderer.cpp Rendering/mitkBaseRendererHelper.cpp Rendering/mitkCrosshairVtkMapper2D.cpp Rendering/mitkGradientBackground.cpp Rendering/mitkImageVtkMapper2D.cpp Rendering/mitkMapper.cpp Rendering/mitkAnnotation.cpp Rendering/mitkPlaneGeometryDataMapper2D.cpp Rendering/mitkPlaneGeometryDataVtkMapper3D.cpp Rendering/mitkPointSetVtkMapper2D.cpp Rendering/mitkPointSetVtkMapper3D.cpp Rendering/mitkRenderWindowBase.cpp Rendering/mitkRenderWindow.cpp Rendering/mitkRenderWindowFrame.cpp Rendering/mitkSurfaceVtkMapper2D.cpp Rendering/mitkSurfaceVtkMapper3D.cpp Rendering/mitkVideoRecorder.cpp Rendering/mitkVtkEventProvider.cpp Rendering/mitkVtkMapper.cpp Rendering/mitkVtkPropRenderer.cpp Rendering/mitkVtkWidgetRendering.cpp Rendering/vtkMitkLevelWindowFilter.cpp Rendering/vtkMitkRectangleProp.cpp Rendering/vtkMitkRenderProp.cpp Rendering/vtkMitkThickSlicesFilter.cpp Rendering/vtkNeverTranslucentTexture.cpp ) set(RESOURCE_FILES Interactions/globalConfig.xml Interactions/DisplayInteraction.xml Interactions/DisplayConfigMITKBase.xml Interactions/DisplayConfigPACSBase.xml Interactions/DisplayConfigCrosshair.xml Interactions/DisplayConfigRotation.xml Interactions/DisplayConfigActivateCoupling.xml Interactions/DisplayConfigSwivel.xml Interactions/DisplayConfigPACSPan.xml Interactions/DisplayConfigPACSScroll.xml Interactions/DisplayConfigPACSZoom.xml Interactions/DisplayConfigPACSLevelWindow.xml Interactions/DisplayConfigBlockLMB.xml Interactions/PointSet.xml Interactions/PointSetConfig.xml mitkLevelWindowPresets.xml mitkAnatomicalStructureColorPresets.xml ) diff --git a/Modules/Core/include/mitkITKEventObserverGuard.h b/Modules/Core/include/mitkITKEventObserverGuard.h new file mode 100644 index 0000000000..c2a509286d --- /dev/null +++ b/Modules/Core/include/mitkITKEventObserverGuard.h @@ -0,0 +1,93 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#ifndef mitkITKEventObserverGuard_h +#define mitkITKEventObserverGuard_h + +#include +#include +#include "MitkCoreExports.h" + +namespace itk +{ + class Object; + class Command; + class EventObject; +} + +namespace mitk +{ + /** + \brief Convenience class that helps to manage the lifetime of itk event observers. + + This helper class can be used to ensure itk event observers are removed form + a sender object at the end of a certain scope. + This class behaves similar to a std::unique_ptr but for event observers. + Therefore the observer will be removed from the sender when one of the following + conditions are met: + - the guard is destroyed + - the guard is resetted (by Reset() or operator = ) + + Sample usage: + \code + { + auto &objRef = *o.GetPointer(); + auto guard = ITKEventObserverGuard(o, itk::AnyEvent(), [&objRef](const itk::EventObject &event) + { std::cout << "Object: " << objRef.GetNameOfClass() << " Event: " << event << std::endl; }); + //some code + } + //now the guard is destroyed + \endcode + This will add an Observer to o executing the lambda for any event as long as the guard exists. + @remark If the sender is already destroyed at the moment, when the guard wants to + remove the observer, the removal will be skipped. + */ + + class MITKCORE_EXPORT ITKEventObserverGuard + { + public: + ITKEventObserverGuard(); + ITKEventObserverGuard(const itk::Object* sender, unsigned long observerTag); + ITKEventObserverGuard(const itk::Object* sender, const itk::EventObject& event, itk::Command* command); + ITKEventObserverGuard(const itk::Object* sender, const itk::EventObject& event, std::function function); + ITKEventObserverGuard(ITKEventObserverGuard&&); + ITKEventObserverGuard& operator=(ITKEventObserverGuard&&); + + ~ITKEventObserverGuard(); + + /** Resets the guard by removing the currently guarded observer. After the reset the guard is uninitialized. + *@remark resetting an uninitialized guard has no effect.*/ + void Reset(); + + /** Resets the guard by first removing the currently guarded observer. Then the passed observer tag for the + * passed sender will be guarded.*/ + void Reset(const itk::Object* sender, unsigned long observerTag); + + /** Resets the guard by first removing the currently guarded observer. Then a observer will be added for + * the passed sender with the passed event and command. The new observer is now guarded.*/ + void Reset(const itk::Object* sender, const itk::EventObject& event, itk::Command* command); + + /** Resets the guard by first removing the currently guarded observer. Then a observer will be added for + * the passed sender with the passed event and lambda function. The new observer is now guarded.*/ + void Reset(const itk::Object* sender, const itk::EventObject& event, std::function function); + + bool IsInitialized() const; + + private: + struct Impl; + std::unique_ptr m_ITKEventObserverGuardImpl; + }; + + +} + +#endif diff --git a/Modules/Core/include/mitkMessage.h b/Modules/Core/include/mitkMessage.h index e8242c00eb..0dce64013f 100644 --- a/Modules/Core/include/mitkMessage.h +++ b/Modules/Core/include/mitkMessage.h @@ -1,697 +1,697 @@ /*============================================================================ 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 mitkMessage_h #define mitkMessage_h #include #include #include /** * Adds a Message<> variable and methods to add/remove message delegates to/from * this variable. */ #define mitkNewMessageMacro(msgHandleObject) \ private: \ ::mitk::Message<> m_##msgHandleObject##Message; \ \ public: \ inline void Add##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate<> &delegate) \ { \ m_##msgHandleObject##Message += delegate; \ } \ inline void Remove##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate<> &delegate) \ { \ m_##msgHandleObject##Message -= delegate; \ } #define mitkNewMessageWithReturnMacro(msgHandleObject, returnType) \ private: \ ::mitk::Message m_##msgHandleObject##Message; \ \ public: \ inline void Add##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate &delegate) \ { \ m_##msgHandleObject##Message += delegate; \ } \ inline void Remove##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate &delegate) \ { \ m_##msgHandleObject##Message -= delegate; \ } #define mitkNewMessage1Macro(msgHandleObject, type1) \ private: \ ::mitk::Message1 m_##msgHandleObject##Message; \ \ public: \ - void Add##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate1 &delegate) \ + void Add##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate1 &delegate) const \ { \ m_##msgHandleObject##Message += delegate; \ } \ - void Remove##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate1 &delegate) \ + void Remove##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate1 &delegate) const \ { \ m_##msgHandleObject##Message -= delegate; \ } #define mitkNewMessage2Macro(msgHandleObject, type1, type2) \ private: \ ::mitk::Message2 m_##msgHandleObject##Message; \ \ public: \ void Add##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate2 &delegate) \ { \ m_##msgHandleObject##Message += delegate; \ } \ void Remove##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate2 &delegate) \ { \ m_##msgHandleObject##Message -= delegate; \ } namespace mitk { template class MessageAbstractDelegate { public: virtual ~MessageAbstractDelegate() {} virtual A Execute() const = 0; virtual bool operator==(const MessageAbstractDelegate *cmd) const = 0; virtual MessageAbstractDelegate *Clone() const = 0; }; template class MessageAbstractDelegate1 { public: virtual ~MessageAbstractDelegate1() {} virtual A Execute(T t) const = 0; virtual bool operator==(const MessageAbstractDelegate1 *cmd) const = 0; virtual MessageAbstractDelegate1 *Clone() const = 0; }; template class MessageAbstractDelegate2 { public: virtual ~MessageAbstractDelegate2() {} virtual A Execute(T t, U u) const = 0; virtual bool operator==(const MessageAbstractDelegate2 *cmd) const = 0; virtual MessageAbstractDelegate2 *Clone() const = 0; }; template class MessageAbstractDelegate3 { public: virtual ~MessageAbstractDelegate3() {} virtual A Execute(T t, U u, V v) const = 0; virtual bool operator==(const MessageAbstractDelegate3 *cmd) const = 0; virtual MessageAbstractDelegate3 *Clone() const = 0; }; template class MessageAbstractDelegate4 { public: virtual ~MessageAbstractDelegate4() {} virtual A Execute(T t, U u, V v, W w) const = 0; virtual bool operator==(const MessageAbstractDelegate4 *cmd) const = 0; virtual MessageAbstractDelegate4 *Clone() const = 0; }; /** * This class essentially wraps a function pointer with signature * A(R::*function)(). A is the return type of your callback function * and R the type of the class implementing the function. * * Use this class to add a callback function to * messages without parameters. */ template class MessageDelegate : public MessageAbstractDelegate { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate(R *object, A (R::*memberFunctionPointer)()) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } ~MessageDelegate() override {} // override function "Call" A Execute() const override { return (m_Object->*m_MemberFunctionPointer)(); // execute member function } bool operator==(const MessageAbstractDelegate *c) const override { const MessageDelegate *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate *Clone() const override { return new MessageDelegate(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(); // pointer to member function }; /** * This class essentially wraps a function pointer with signature * A(R::*function)(T). A is the return type of your callback function, * R the type of the class implementing the function and T the type * of the argument. * * Use this class to add a callback function to * messages with one parameter. * * If you need more parameters, use MessageDelegate2 etc. */ template class MessageDelegate1 : public MessageAbstractDelegate1 { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate1(R *object, A (R::*memberFunctionPointer)(T)) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } ~MessageDelegate1() override {} // override function "Call" A Execute(T t) const override { return (m_Object->*m_MemberFunctionPointer)(t); // execute member function } bool operator==(const MessageAbstractDelegate1 *c) const override { const MessageDelegate1 *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate1 *Clone() const override { return new MessageDelegate1(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(T); // pointer to member function }; template class MessageDelegate2 : public MessageAbstractDelegate2 { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate2(R *object, A (R::*memberFunctionPointer)(T, U)) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } ~MessageDelegate2() override {} // override function "Call" A Execute(T t, U u) const override { return (m_Object->*m_MemberFunctionPointer)(t, u); // execute member function } bool operator==(const MessageAbstractDelegate2 *c) const override { const MessageDelegate2 *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate2 *Clone() const override { return new MessageDelegate2(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(T, U); // pointer to member function }; template class MessageDelegate3 : public MessageAbstractDelegate3 { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate3(R *object, A (R::*memberFunctionPointer)(T, U, V)) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } ~MessageDelegate3() override {} // override function "Call" A Execute(T t, U u, V v) const override { return (m_Object->*m_MemberFunctionPointer)(t, u, v); // execute member function } bool operator==(const MessageAbstractDelegate3 *c) const override { const MessageDelegate3 *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate3 *Clone() const override { return new MessageDelegate3(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(T, U, V); // pointer to member function }; template class MessageDelegate4 : public MessageAbstractDelegate4 { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate4(R *object, A (R::*memberFunctionPointer)(T, U, V, W)) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } virtual ~MessageDelegate4() {} // override function "Call" virtual A Execute(T t, U u, V v, W w) const { return (m_Object->*m_MemberFunctionPointer)(t, u, v, w); // execute member function } bool operator==(const MessageAbstractDelegate4 *c) const { const MessageDelegate4 *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate4 *Clone() const { return new MessageDelegate4(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(T, U, V, W); // pointer to member function }; template class MessageBase { public: typedef std::vector ListenerList; virtual ~MessageBase() { for (auto iter = m_Listeners.begin(); iter != m_Listeners.end(); ++iter) { delete *iter; } } MessageBase() {} MessageBase(const MessageBase &o) { for (typename ListenerList::iterator iter = o.m_Listeners.begin(); iter != o.m_Listeners.end(); ++iter) { m_Listeners.push_back((*iter)->Clone()); } } MessageBase &operator=(const MessageBase &o) { MessageBase tmp(o); std::swap(tmp.m_Listeners, this->m_Listeners); return *this; } void AddListener(const AbstractDelegate &delegate) const { AbstractDelegate *msgCmd = delegate.Clone(); m_Mutex.lock(); for (auto iter = m_Listeners.begin(); iter != m_Listeners.end(); ++iter) { if ((*iter)->operator==(msgCmd)) { delete msgCmd; m_Mutex.unlock(); return; } } m_Listeners.push_back(msgCmd); m_Mutex.unlock(); } void operator+=(const AbstractDelegate &delegate) const { this->AddListener(delegate); } void RemoveListener(const AbstractDelegate &delegate) const { m_Mutex.lock(); for (auto iter = m_Listeners.begin(); iter != m_Listeners.end(); ++iter) { if ((*iter)->operator==(&delegate)) { delete *iter; m_Listeners.erase(iter); m_Mutex.unlock(); return; } } m_Mutex.unlock(); } void operator-=(const AbstractDelegate &delegate) const { this->RemoveListener(delegate); } const ListenerList &GetListeners() const { return m_Listeners; } bool HasListeners() const { return !m_Listeners.empty(); } bool IsEmpty() const { return m_Listeners.empty(); } protected: /** * \brief List of listeners. * * This is declared mutable for a reason: Imagine an object that sends out notifications and * someone gets a const Database object, because he/she should not write to the * database. He/she should anyway be able to register for notifications about changes in the database * -- this is why AddListener and RemoveListener are declared const. m_Listeners must be * mutable so that AddListener and RemoveListener can modify it regardless of the object's constness. */ mutable ListenerList m_Listeners; mutable std::mutex m_Mutex; }; /** * \brief Event/message/notification class. * * \sa mitk::BinaryThresholdTool * \sa QmitkBinaryThresholdToolGUI * * This totally ITK, Qt, VTK, whatever toolkit independent class * allows one class to send out messages and another class to * receive these message. This class is templated over the * return type (A) of the callback functions. * There are variations of this class * (Message1, Message2, etc.) for sending * one, two or more parameters along with the messages. * * This is an implementation of the Observer pattern. * * \li There is no guarantee about the order of which observer is notified first. At the moment the observers which * register first will be notified first. * \li Notifications are synchronous, by direct method calls. There is no support for asynchronous messages. * * To conveniently add methods for registering/unregistering observers * to Message variables of your class, you can use the mitkNewMessageMacro * macros. */ template class Message : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; void Send() const { ListenerList listeners; { this->m_Mutex.lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.unlock(); } for (auto iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(); } } void operator()() const { this->Send(); } }; // message with 1 parameter and return type template class Message1 : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; void Send(T t) const { ListenerList listeners; { this->m_Mutex.lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.unlock(); } for (auto iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(t); } } void operator() (T t) const { this->Send(t); } }; // message with 2 parameters and return type template class Message2 : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; void Send(T t, U u) const { ListenerList listeners; { this->m_Mutex.lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.unlock(); } for (auto iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(t, u); } } void operator()(T t, U u) const { this->Send(t, u); } }; // message with 3 parameters and return type template class Message3 : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; void Send(T t, U u, V v) const { ListenerList listeners; { this->m_Mutex.lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.unlock(); } for (typename ListenerList::iterator iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(t, u, v); } } void operator()(T t, U u, V v) const { this->Send(t, u, v); } }; // message with 4 parameters and return type template class Message4 : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; void Send(T t, U u, V v, W w) const { ListenerList listeners; { this->m_Mutex.lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.unlock(); } for (typename ListenerList::iterator iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(t, u, v, w); } } void operator()(T t, U u, V v, W w) const { this->Send(t, u, v, w); } }; /* Here is an example how to use the macros and templates: * * // An object to be sent around * class Law * { * private: * std::string m_Description; * * public: * * Law(const std::string law) : m_Description(law) * { } * * std::string GetDescription() const * { * return m_Description; * } * }; * * // The NewtonMachine will issue specific events * class NewtonMachine * { * mitkNewMessageMacro(AnalysisStarted); * mitkNewMessage1Macro(AnalysisStopped, bool); * mitkNewMessage1Macro(LawDiscovered, const Law&); * * public: * * void StartAnalysis() * { * // send the "started" signal * m_AnalysisStartedMessage(); * * // we found a new law of nature by creating one :-) * Law massLaw("F=ma"); * m_LawDiscoveredMessage(massLaw); * } * * void StopAnalysis() * { * // send the "stop" message with false, indicating * // that no error occurred * m_AnalysisStoppedMessage(false); * } * }; * * class Observer * { * private: * * NewtonMachine* m_Machine; * * public: * * Observer(NewtonMachine* machine) : m_Machine(machine) * { * // Add "observers", i.e. function pointers to the machine * m_Machine->AddAnalysisStartedListener( * ::mitk::MessageDelegate(this, &Observer::MachineStarted)); * m_Machine->AddAnalysisStoppedListener( * ::mitk::MessageDelegate1(this, &Observer::MachineStopped)); * m_Machine->AddLawDiscoveredListener( * ::mitk::MessageDelegate1(this, &Observer::LawDiscovered)); * } * * ~Observer() * { * // Always remove your observers when finished * m_Machine->RemoveAnalysisStartedListener( * ::mitk::MessagDelegate(this, &Observer::MachineStarted)); * m_Machine->RemoveAnalysisStoppedListener( * ::mitk::MessageDelegate1(this, &Observer::MachineStopped)); * m_Machine->RemoveLawDiscoveredListener( * ::mitk::MessageDelegate1(this, &Observer::LawDiscovered)); * } * * void MachineStarted() * { * std::cout << "Observed machine has started" << std::endl; * } * * void MachineStopped(bool error) * { * std::cout << "Observed machine stopped " << (error ? "with an error" : "") << std::endl; * } * * void LawDiscovered(const Law& law) * { * std::cout << "New law of nature discovered: " << law.GetDescription() << std::endl; * } * }; * * NewtonMachine newtonMachine; * Observer observer(&newtonMachine); * * // This will send two events to registered observers * newtonMachine.StartAnalysis(); * // This will send one event to registered observers * newtonMachine.StopAnalysis(); * * Another example of how to use these message classes can be * found in the directory Testing, file mitkMessageTest.cpp * */ } // namespace #endif diff --git a/Modules/Core/src/DataManagement/mitkITKEventObserverGuard.cpp b/Modules/Core/src/DataManagement/mitkITKEventObserverGuard.cpp new file mode 100644 index 0000000000..01afcff13a --- /dev/null +++ b/Modules/Core/src/DataManagement/mitkITKEventObserverGuard.cpp @@ -0,0 +1,107 @@ +/*============================================================================ + +The Medical Imaging Interaction Toolkit (MITK) + +Copyright (c) German Cancer Research Center (DKFZ) +All rights reserved. + +Use of this source code is governed by a 3-clause BSD license that can be +found in the LICENSE file. + +============================================================================*/ + +#include "mitkITKEventObserverGuard.h" + +#include +#include +#include + +namespace mitk +{ + struct ITKEventObserverGuard::Impl + { + explicit Impl(const itk::Object* sender, unsigned long observerTag); + + ~Impl(); + + private: + mitk::WeakPointer m_Sender; + unsigned long m_ObserverTag; + }; + + ITKEventObserverGuard::Impl::Impl(const itk::Object* sender, unsigned long observerTag) : m_Sender(const_cast(sender)), m_ObserverTag(observerTag) + { + //we cast const sender to non const in order to be able to remove observers but + //also support constness in code that uses the guard. + } + + ITKEventObserverGuard::Impl::~Impl() + { + auto sender = m_Sender.Lock(); + + if (sender.IsNotNull()) + { + sender->RemoveObserver(m_ObserverTag); + } + } + + ITKEventObserverGuard::ITKEventObserverGuard() : m_ITKEventObserverGuardImpl(nullptr) {} + + ITKEventObserverGuard::ITKEventObserverGuard(const itk::Object* sender, unsigned long observerTag) : m_ITKEventObserverGuardImpl(new Impl(sender, observerTag)) {} + + ITKEventObserverGuard::ITKEventObserverGuard(const itk::Object* sender, const itk::EventObject& event, itk::Command* command) + { + auto tag = sender->AddObserver(event, command); + m_ITKEventObserverGuardImpl = std::make_unique(sender, tag); + } + + ITKEventObserverGuard::ITKEventObserverGuard(const itk::Object* sender, const itk::EventObject& event, std::function function) + { + auto tag = sender->AddObserver(event, function); + m_ITKEventObserverGuardImpl = std::make_unique(sender, tag); + } + + ITKEventObserverGuard::ITKEventObserverGuard(ITKEventObserverGuard&& other): m_ITKEventObserverGuardImpl(std::move(other.m_ITKEventObserverGuardImpl)) + { + } + + ITKEventObserverGuard::~ITKEventObserverGuard() { } + + ITKEventObserverGuard& ITKEventObserverGuard::operator=(ITKEventObserverGuard&& other) + { + if (&other != this) + { + m_ITKEventObserverGuardImpl = std::move(other.m_ITKEventObserverGuardImpl); + } + + return *this; + } + + void ITKEventObserverGuard::Reset() + { + m_ITKEventObserverGuardImpl.reset(); + } + + void ITKEventObserverGuard::Reset(const itk::Object* sender, unsigned long observerTag) + { + m_ITKEventObserverGuardImpl.reset(new Impl(sender, observerTag)); + } + + void ITKEventObserverGuard::Reset(const itk::Object* sender, const itk::EventObject& event, itk::Command* command) + { + auto tag = sender->AddObserver(event, command); + m_ITKEventObserverGuardImpl.reset(new Impl(sender, tag)); + } + + void ITKEventObserverGuard::Reset(const itk::Object* sender, const itk::EventObject& event, std::function function) + { + auto tag = sender->AddObserver(event, function); + m_ITKEventObserverGuardImpl.reset(new Impl(sender, tag)); + } + + bool ITKEventObserverGuard::IsInitialized() const + { + return nullptr != this->m_ITKEventObserverGuardImpl; + } + +} diff --git a/Modules/Core/test/files.cmake b/Modules/Core/test/files.cmake index 5e7a4c8a5f..b4b926c612 100644 --- a/Modules/Core/test/files.cmake +++ b/Modules/Core/test/files.cmake @@ -1,198 +1,199 @@ # tests with no extra command line parameter set(MODULE_TESTS # IMPORTANT: If you plan to deactivate / comment out a test please write a bug number to the commented out line of code. # # Example: #mitkMyTest #this test is commented out because of bug 12345 # # It is important that the bug is open and that the test will be activated again before the bug is closed. This assures that # no test is forgotten after it was commented out. If there is no bug for your current problem, please add a new one and # mark it as critical. ################## DISABLED TESTS ################################################# #mitkAbstractTransformGeometryTest.cpp #seems as tested class mitkExternAbstractTransformGeometry doesn't exist any more #mitkStateMachineContainerTest.cpp #rewrite test, indirect since no longer exported Bug 14529 #mitkRegistrationBaseTest.cpp #tested class mitkRegistrationBase doesn't exist any more #mitkSegmentationInterpolationTest.cpp #file doesn't exist! #mitkPipelineSmartPointerCorrectnessTest.cpp #file doesn't exist! #mitkITKThreadingTest.cpp #test outdated because itk::Semaphore was removed from ITK #mitkAbstractTransformPlaneGeometryTest.cpp #mitkVtkAbstractTransformPlaneGeometry doesn't exist any more #mitkTestUtilSharedLibrary.cpp #Linker problem with this test... #mitkTextOverlay2DSymbolsRenderingTest.cpp #Implementation of the tested feature is not finished yet. Ask Christoph or see bug 15104 for details. ################# RUNNING TESTS ################################################### mitkAccessByItkTest.cpp mitkCoreObjectFactoryTest.cpp mitkDataNodeTest.cpp mitkMaterialTest.cpp mitkActionTest.cpp mitkDispatcherTest.cpp mitkEnumerationPropertyTest.cpp mitkFileReaderRegistryTest.cpp #mitkFileWriterRegistryTest.cpp mitkFloatToStringTest.cpp mitkGenericPropertyTest.cpp mitkGeometry3DTest.cpp mitkGeometry3DEqualTest.cpp mitkGeometryDataIOTest.cpp mitkGeometryDataToSurfaceFilterTest.cpp mitkImageCastTest.cpp mitkImageDataItemTest.cpp mitkImageGeneratorTest.cpp mitkIOUtilTest.cpp + mitkITKEventObserverGuardTest.cpp mitkBaseDataTest.cpp mitkImportItkImageTest.cpp mitkGrabItkImageMemoryTest.cpp mitkInstantiateAccessFunctionTest.cpp mitkLevelWindowTest.cpp mitkMessageTest.cpp mitkPixelTypeTest.cpp mitkPlaneGeometryTest.cpp mitkPointSetTest.cpp mitkPointSetEqualTest.cpp mitkPointSetFileIOTest.cpp mitkPointSetOnEmptyTest.cpp mitkPointSetLocaleTest.cpp mitkPointSetWriterTest.cpp mitkPointSetPointOperationsTest.cpp mitkProgressBarTest.cpp mitkPropertyTest.cpp mitkPropertyListTest.cpp mitkPropertyPersistenceTest.cpp mitkPropertyPersistenceInfoTest.cpp mitkPropertyRelationRuleBaseTest.cpp mitkPropertyRelationsTest.cpp mitkSlicedGeometry3DTest.cpp mitkSliceNavigationControllerTest.cpp mitkSurfaceTest.cpp mitkSurfaceEqualTest.cpp mitkSurfaceToSurfaceFilterTest.cpp mitkTimeGeometryTest.cpp mitkProportionalTimeGeometryTest.cpp mitkUndoControllerTest.cpp mitkVtkWidgetRenderingTest.cpp mitkVerboseLimitedLinearUndoTest.cpp mitkWeakPointerTest.cpp mitkTransferFunctionTest.cpp mitkStepperTest.cpp mitkRenderingManagerTest.cpp mitkCompositePixelValueToStringTest.cpp vtkMitkThickSlicesFilterTest.cpp mitkNodePredicateDataPropertyTest.cpp mitkNodePredicateFunctionTest.cpp mitkVectorTest.cpp mitkClippedSurfaceBoundsCalculatorTest.cpp mitkExceptionTest.cpp mitkExtractSliceFilterTest.cpp mitkLogTest.cpp mitkImageDimensionConverterTest.cpp mitkLoggingAdapterTest.cpp mitkUIDGeneratorTest.cpp mitkPlanePositionManagerTest.cpp mitkAffineTransformBaseTest.cpp mitkPropertyAliasesTest.cpp mitkPropertyDescriptionsTest.cpp mitkPropertyExtensionsTest.cpp mitkPropertyFiltersTest.cpp mitkPropertyKeyPathTest.cpp mitkTinyXMLTest.cpp mitkRawImageFileReaderTest.cpp mitkInteractionEventTest.cpp mitkLookupTableTest.cpp mitkSTLFileReaderTest.cpp mitkPointTypeConversionTest.cpp mitkVectorTypeConversionTest.cpp mitkMatrixTypeConversionTest.cpp mitkArrayTypeConversionTest.cpp mitkSurfaceToImageFilterTest.cpp mitkBaseGeometryTest.cpp mitkImageToSurfaceFilterTest.cpp mitkEqualTest.cpp mitkLineTest.cpp mitkArbitraryTimeGeometryTest.cpp mitkItkImageIOTest.cpp mitkLevelWindowManagerTest.cpp mitkVectorPropertyTest.cpp mitkTemporoSpatialStringPropertyTest.cpp mitkPropertyNameHelperTest.cpp mitkNodePredicateGeometryTest.cpp mitkNodePredicateSubGeometryTest.cpp mitkPreferenceListReaderOptionsFunctorTest.cpp mitkGenericIDRelationRuleTest.cpp mitkSourceImageRelationRuleTest.cpp mitkTemporalJoinImagesFilterTest.cpp mitkPreferencesTest.cpp ) set(MODULE_RENDERING_TESTS mitkPointSetDataInteractorTest.cpp mitkSurfaceVtkMapper2DTest.cpp mitkSurfaceVtkMapper2D3DTest.cpp ) # test with image filename as an extra command line parameter set(MODULE_IMAGE_TESTS mitkImageTimeSelectorTest.cpp #only runs on images mitkImageAccessorTest.cpp #only runs on images ) set(MODULE_SURFACE_TESTS mitkSurfaceVtkWriterTest.cpp #only runs on surfaces ) # list of images for which the tests are run set(MODULE_TESTIMAGE US4DCyl.nrrd Pic3D.nrrd Pic2DplusT.nrrd BallBinary30x30x30.nrrd Png2D-bw.png ) set(MODULE_TESTSURFACE binary.stl ball.stl ) set(MODULE_CUSTOM_TESTS mitkDataStorageTest.cpp mitkDataNodeTest.cpp mitkEventConfigTest.cpp mitkPointSetLocaleTest.cpp mitkImageTest.cpp mitkImageVtkMapper2DTest.cpp mitkImageVtkMapper2DLevelWindowTest.cpp mitkImageVtkMapper2DOpacityTest.cpp mitkImageVtkMapper2DResliceInterpolationPropertyTest.cpp mitkImageVtkMapper2DColorTest.cpp mitkImageVtkMapper2DSwivelTest.cpp mitkImageVtkMapper2DTransferFunctionTest.cpp mitkImageVtkMapper2DOpacityTransferFunctionTest.cpp mitkImageVtkMapper2DLookupTableTest.cpp mitkSurfaceVtkMapper3DTest.cpp mitkVolumeCalculatorTest.cpp mitkLevelWindowManagerTest.cpp mitkPointSetVtkMapper2DTest.cpp mitkPointSetVtkMapper2DImageTest.cpp mitkPointSetVtkMapper2DGlyphTypeTest.cpp mitkPointSetVtkMapper2DTransformedPointsTest.cpp mitkVTKRenderWindowSizeTest.cpp mitkMultiComponentImageDataComparisonFilterTest.cpp mitkImageToItkTest.cpp mitkImageSliceSelectorTest.cpp mitkPointSetReaderTest.cpp mitkImageEqualTest.cpp mitkRotatedSlice4DTest.cpp mitkPlaneGeometryDataMapper2DTest.cpp ) # Currently not working on windows because of a rendering timing issue # see bug 18083 for details if(NOT WIN32) set(MODULE_CUSTOM_TESTS ${MODULE_CUSTOM_TESTS} mitkSurfaceDepthSortingTest.cpp) endif() set(RESOURCE_FILES Interactions/AddAndRemovePoints.xml Interactions/globalConfig.xml Interactions/StatemachineTest.xml Interactions/StatemachineConfigTest.xml ) diff --git a/Modules/Core/test/mitkITKEventObserverGuardTest.cpp b/Modules/Core/test/mitkITKEventObserverGuardTest.cpp new file mode 100644 index 0000000000..a46aad6a75 --- /dev/null +++ b/Modules/Core/test/mitkITKEventObserverGuardTest.cpp @@ -0,0 +1,258 @@ +/*============================================================================ + +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 "mitkITKEventObserverGuard.h" + +#include +#include + +#include +#include + +// Create a command to observe events +class DummyCommand : public itk::Command +{ +public: + using Self = DummyCommand; + using Superclass = itk::Command; + using Pointer = itk::SmartPointer; + using ConstPointer = itk::SmartPointer; + + itkNewMacro(DummyCommand); + + void Execute(itk::Object*, const itk::EventObject&) override + { + ++ExecutionCount; + } + + void Execute(const itk::Object*, const itk::EventObject&) override + { + ++ExecutionCount; + } + + int ExecutionCount; + +protected: + DummyCommand() : ExecutionCount(0) {} +}; + +class mitkITKEventObserverGuardTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkITKEventObserverGuardTestSuite); + MITK_TEST(TestConstructor); + MITK_TEST(TestConstructor2); + MITK_TEST(TestConstructor3); + MITK_TEST(TestConstructor4); + MITK_TEST(TestReset); + MITK_TEST(TestReset2); + MITK_TEST(TestOperateAssign); + CPPUNIT_TEST_SUITE_END(); + + itk::Object::Pointer m_TestObject; + DummyCommand::Pointer m_DummyCommand; + DummyCommand::Pointer m_DummyCommand2; + +public: + void setUp() override + { + m_TestObject = itk::Object::New(); + m_DummyCommand = DummyCommand::New(); + m_DummyCommand2 = DummyCommand::New(); + } + + void tearDown() override + { + m_TestObject = nullptr; + m_DummyCommand = nullptr; + m_DummyCommand2 = nullptr; + } + // This test is supposed to verify inheritance behaviour, this test will fail if the behaviour changes in the future + + void TestConstructor() + { + mitk::ITKEventObserverGuard guard; + + CPPUNIT_ASSERT(!guard.IsInitialized()); + } + + void TestConstructor2() + { + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(0, m_DummyCommand->ExecutionCount); + + { + auto tag = m_TestObject->AddObserver(itk::ModifiedEvent(), m_DummyCommand); + mitk::ITKEventObserverGuard guard(m_TestObject, tag); + + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + m_TestObject->InvokeEvent(itk::ProgressEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + } + + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + } + + void TestConstructor3() + { + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(0, m_DummyCommand->ExecutionCount); + + { + mitk::ITKEventObserverGuard guard(m_TestObject, itk::ModifiedEvent(), m_DummyCommand); + + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + m_TestObject->InvokeEvent(itk::ProgressEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + } + + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + } + + void TestConstructor4() + { + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(0, m_DummyCommand->ExecutionCount); + + { + mitk::ITKEventObserverGuard guard(m_TestObject, itk::ModifiedEvent(), [this](const itk::EventObject&) {++(this->m_DummyCommand->ExecutionCount); }); + + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + m_TestObject->InvokeEvent(itk::ProgressEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + } + + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + } + + void TestReset() + { + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(0, m_DummyCommand->ExecutionCount); + + mitk::ITKEventObserverGuard guard(m_TestObject, itk::ModifiedEvent(), m_DummyCommand); + + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + guard.Reset(); + CPPUNIT_ASSERT(!guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + guard.Reset(); + CPPUNIT_ASSERT(!guard.IsInitialized()); + } + + void TestReset2() + { + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(0, m_DummyCommand->ExecutionCount); + + mitk::ITKEventObserverGuard guard(m_TestObject, itk::ModifiedEvent(), m_DummyCommand); + + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + auto tag = m_TestObject->AddObserver(itk::ProgressEvent(), m_DummyCommand2); + guard.Reset(m_TestObject, tag); + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + m_TestObject->InvokeEvent(itk::ProgressEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand2->ExecutionCount); + } + + void TestReset3() + { + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(0, m_DummyCommand->ExecutionCount); + + mitk::ITKEventObserverGuard guard(m_TestObject, itk::ModifiedEvent(), m_DummyCommand); + + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + guard.Reset(m_TestObject, itk::ProgressEvent(), m_DummyCommand2); + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + m_TestObject->InvokeEvent(itk::ProgressEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand2->ExecutionCount); + } + + void TestReset4() + { + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(0, m_DummyCommand->ExecutionCount); + + mitk::ITKEventObserverGuard guard(m_TestObject, itk::ModifiedEvent(), m_DummyCommand); + + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + guard.Reset(m_TestObject, itk::ProgressEvent(), [this](const itk::EventObject&) {++(this->m_DummyCommand2->ExecutionCount); }); + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + m_TestObject->InvokeEvent(itk::ProgressEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand2->ExecutionCount); + } + + void TestOperateAssign() + { + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(0, m_DummyCommand->ExecutionCount); + + mitk::ITKEventObserverGuard guard(m_TestObject, itk::ModifiedEvent(), m_DummyCommand); + + CPPUNIT_ASSERT(guard.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + auto guard2 = mitk::ITKEventObserverGuard(m_TestObject, itk::ProgressEvent(), [this](const itk::EventObject&) {++(this->m_DummyCommand2->ExecutionCount); }); + CPPUNIT_ASSERT(guard.IsInitialized()); + CPPUNIT_ASSERT(guard2.IsInitialized()); + m_TestObject->InvokeEvent(itk::ProgressEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand2->ExecutionCount); + + guard = std::move(guard2); + + CPPUNIT_ASSERT(guard.IsInitialized()); + CPPUNIT_ASSERT(!guard2.IsInitialized()); + m_TestObject->InvokeEvent(itk::ModifiedEvent()); + CPPUNIT_ASSERT_EQUAL(1, m_DummyCommand->ExecutionCount); + + m_TestObject->InvokeEvent(itk::ProgressEvent()); + CPPUNIT_ASSERT_EQUAL(2, m_DummyCommand2->ExecutionCount); + } +}; + +MITK_TEST_SUITE_REGISTRATION(mitkITKEventObserverGuard) diff --git a/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp b/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp index 39a9e5f197..58035fabbf 100644 --- a/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp +++ b/Modules/MatchPointRegistration/src/Helper/mitkImageMappingHelper.cpp @@ -1,482 +1,482 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include "mapRegistration.h" #include "mitkImageMappingHelper.h" #include "mitkRegistrationHelper.h" template typename ::itk::InterpolateImageFunction< TImage >::Pointer generateInterpolator(mitk::ImageMappingInterpolator::Type interpolatorType) { typedef ::itk::InterpolateImageFunction< TImage > BaseInterpolatorType; typename BaseInterpolatorType::Pointer result; switch (interpolatorType) { case mitk::ImageMappingInterpolator::NearestNeighbor: { result = ::itk::NearestNeighborInterpolateImageFunction::New(); break; } case mitk::ImageMappingInterpolator::BSpline_3: { typename ::itk::BSplineInterpolateImageFunction::Pointer spInterpolator = ::itk::BSplineInterpolateImageFunction::New(); spInterpolator->SetSplineOrder(3); result = spInterpolator; break; } case mitk::ImageMappingInterpolator::WSinc_Hamming: { result = ::itk::WindowedSincInterpolateImageFunction::New(); break; } case mitk::ImageMappingInterpolator::WSinc_Welch: { result = ::itk::WindowedSincInterpolateImageFunction >::New(); break; } default: { result = ::itk::LinearInterpolateImageFunction::New(); break; } } return result; }; template void doMITKMap(const ::itk::Image* input, mitk::ImageMappingHelper::ResultImageType::Pointer& result, const mitk::ImageMappingHelper::RegistrationType*& registration, bool throwOnOutOfInputAreaError, const double& paddingValue, const mitk::ImageMappingHelper::ResultImageGeometryType*& resultGeometry, bool throwOnMappingError, const double& errorValue, mitk::ImageMappingInterpolator::Type interpolatorType) { typedef ::map::core::Registration ConcreteRegistrationType; typedef ::map::core::ImageMappingTask, ::itk::Image > MappingTaskType; typename MappingTaskType::Pointer spTask = MappingTaskType::New(); typedef typename MappingTaskType::ResultImageDescriptorType ResultImageDescriptorType; typename ResultImageDescriptorType::Pointer resultDescriptor; //check if image and result geometry fits the passed registration ///////////////////////////////////////////////////////////////// if (registration->getMovingDimensions()!=VImageDimension) { map::core::OStringStream str; str << "Dimension of MITK image ("<getMovingDimensions()<<")."; throw mitk::AccessByItkException(str.str()); } if (registration->getTargetDimensions()!=VImageDimension) { map::core::OStringStream str; str << "Dimension of MITK image ("<getTargetDimensions()<<")."; throw mitk::AccessByItkException(str.str()); } const ConcreteRegistrationType* castedReg = dynamic_cast(registration); if (registration->getTargetDimensions()==2 && resultGeometry) { mitk::ImageMappingHelper::ResultImageGeometryType::BoundsArrayType bounds = resultGeometry->GetBounds(); if (bounds[4]!=0 || bounds[5]!=0) { //array "bounds" is constructed as [min Dim1, max Dim1, min Dim2, max Dim2, min Dim3, max Dim3] //therfore [4] and [5] must be 0 map::core::OStringStream str; str << "Dimension of defined result geometry does not equal the target dimension of the registration object ("<getTargetDimensions()<<")."; throw mitk::AccessByItkException(str.str()); } } //check/create resultDescriptor ///////////////////////// if (resultGeometry) { resultDescriptor = ResultImageDescriptorType::New(); typename ResultImageDescriptorType::PointType origin; typename ResultImageDescriptorType::SizeType size; typename ResultImageDescriptorType::SpacingType fieldSpacing; typename ResultImageDescriptorType::DirectionType matrix; mitk::ImageMappingHelper::ResultImageGeometryType::BoundsArrayType geoBounds = resultGeometry->GetBounds(); mitk::Vector3D geoSpacing = resultGeometry->GetSpacing(); mitk::Point3D geoOrigin = resultGeometry->GetOrigin(); mitk::AffineTransform3D::MatrixType geoMatrix = resultGeometry->GetIndexToWorldTransform()->GetMatrix(); for (unsigned int i = 0; i(geoOrigin[i]); fieldSpacing[i] = static_cast(geoSpacing[i]); size[i] = static_cast(geoBounds[(2*i)+1]-geoBounds[2*i])*fieldSpacing[i]; } //Matrix extraction matrix.SetIdentity(); unsigned int i; unsigned int j; /// \warning 2D MITK images could have a 3D rotation, since they have a 3x3 geometry matrix. /// If it is only a rotation around the transversal plane normal, it can be express with a 2x2 matrix. /// In this case, the ITK image conservs this information and is identical to the MITK image! /// If the MITK image contains any other rotation, the ITK image will have no rotation at all. /// Spacing is of course conserved in both cases. // the following loop devides by spacing now to normalize columns. // counterpart of InitializeByItk in mitkImage.h line 372 of revision 15092. // Check if information is lost if ( VImageDimension == 2) { if ( ( geoMatrix[0][2] != 0) || ( geoMatrix[1][2] != 0) || ( geoMatrix[2][0] != 0) || ( geoMatrix[2][1] != 0) || (( geoMatrix[2][2] != 1) && ( geoMatrix[2][2] != -1) )) { // The 2D MITK image contains 3D rotation information. // This cannot be expressed in a 2D ITK image, so the ITK image will have no rotation } else { // The 2D MITK image can be converted to an 2D ITK image without information loss! for ( i=0; i < 2; ++i) { for( j=0; j < 2; ++j ) { matrix[i][j] = geoMatrix[i][j]/fieldSpacing[j]; } } } } else if (VImageDimension == 3) { // Normal 3D image. Conversion possible without problem! for ( i=0; i < 3; ++i) { for( j=0; j < 3; ++j ) { matrix[i][j] = geoMatrix[i][j]/fieldSpacing[j]; } } } else { assert(0); throw mitk::AccessByItkException("Usage of resultGeometry for 2D images is not yet implemented."); /**@TODO Implement extraction of 2D-Rotation-Matrix out of 3D-Rotation-Matrix * to cover this case as well. * matrix = extract2DRotationMatrix(resultGeometry)*/ } resultDescriptor->setOrigin(origin); resultDescriptor->setSize(size); resultDescriptor->setSpacing(fieldSpacing); resultDescriptor->setDirection(matrix); } //do the mapping ///////////////////////// typedef ::itk::InterpolateImageFunction< ::itk::Image > BaseInterpolatorType; typename BaseInterpolatorType::Pointer interpolator = generateInterpolator< ::itk::Image >(interpolatorType); assert(interpolator.IsNotNull()); spTask->setImageInterpolator(interpolator); spTask->setInputImage(input); spTask->setRegistration(castedReg); spTask->setResultImageDescriptor(resultDescriptor); spTask->setThrowOnMappingError(throwOnMappingError); spTask->setErrorValue(errorValue); spTask->setThrowOnPaddingError(throwOnOutOfInputAreaError); spTask->setPaddingValue(paddingValue); spTask->execute(); mitk::CastToMitkImage<>(spTask->getResultImage(),result); } /**Helper function to ensure the mapping of all time steps of an image.*/ void doMapTimesteps(const mitk::ImageMappingHelper::InputImageType* input, mitk::Image* result, const mitk::ImageMappingHelper::RegistrationType* registration, bool throwOnOutOfInputAreaError,double paddingValue, const mitk::ImageMappingHelper::ResultImageGeometryType* resultGeometry, bool throwOnMappingError, double errorValue, mitk::ImageMappingInterpolator::Type interpolatorType) { for (unsigned int i = 0; iGetTimeSteps(); ++i) { mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(input); imageTimeSelector->SetTimeNr(i); imageTimeSelector->UpdateLargestPossibleRegion(); mitk::ImageMappingHelper::InputImageType::Pointer timeStepInput = imageTimeSelector->GetOutput(); mitk::ImageMappingHelper::ResultImageType::Pointer timeStepResult; AccessByItk_n(timeStepInput, doMITKMap, (timeStepResult, registration, throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue, interpolatorType)); mitk::ImageReadAccessor readAccess(timeStepResult); result->SetVolume(readAccess.GetData(), i); } } mitk::TimeGeometry::Pointer CreateResultTimeGeometry(const mitk::ImageMappingHelper::InputImageType* input, const mitk::ImageMappingHelper::ResultImageGeometryType* resultGeometry) { mitk::TimeGeometry::ConstPointer timeGeometry = input->GetTimeGeometry(); mitk::TimeGeometry::Pointer mappedTimeGeometry = timeGeometry->Clone(); for (unsigned int i = 0; i < input->GetTimeSteps(); ++i) { mitk::ImageMappingHelper::ResultImageGeometryType::Pointer mappedGeometry = resultGeometry->Clone(); mappedTimeGeometry->SetTimeStepGeometry(mappedGeometry, i); } return mappedTimeGeometry; } mitk::ImageMappingHelper::ResultImageType::Pointer mitk::ImageMappingHelper::map(const InputImageType* input, const RegistrationType* registration, bool throwOnOutOfInputAreaError, const double& paddingValue, const ResultImageGeometryType* resultGeometry, bool throwOnMappingError, const double& errorValue, mitk::ImageMappingInterpolator::Type interpolatorType) { if (!registration) { mitkThrow() << "Cannot map image. Passed registration wrapper pointer is nullptr."; } if (!input) { mitkThrow() << "Cannot map image. Passed image pointer is nullptr."; } ResultImageType::Pointer result; auto inputLabelSetImage = dynamic_cast(input); if (nullptr == inputLabelSetImage) { if (input->GetTimeSteps() == 1) { //map the image and done AccessByItk_n(input, doMITKMap, (result, registration, throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue, interpolatorType)); } else { //map every time step and compose auto mappedTimeGeometry = CreateResultTimeGeometry(input, resultGeometry); result = mitk::Image::New(); result->Initialize(input->GetPixelType(), *mappedTimeGeometry, 1, input->GetTimeSteps()); doMapTimesteps(input, result, registration, throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue, interpolatorType); } } else { auto resultLabelSetImage = LabelSetImage::New(); auto mappedTimeGeometry = CreateResultTimeGeometry(input, resultGeometry); auto resultTemplate = mitk::Image::New(); resultTemplate->Initialize(input->GetPixelType(), *mappedTimeGeometry, 1, input->GetTimeSteps()); resultLabelSetImage->Initialize(resultTemplate); auto cloneInput = inputLabelSetImage->Clone(); //We need to clone the LabelSetImage due to its illposed design. It is state full //and we have to iterate through all layers as active layers to ensure the content //was really stored (directly working with the layer images does not work with the //active layer). The clone wastes rescources but is the easiest and safest way to //ensure 1) correct mapping 2) avoid race conditions with other parts of the //application because we would change the state of the input. //This whole code block should be reworked as soon as T28525 is done. for (unsigned int layerID = 0; layerID < inputLabelSetImage->GetNumberOfLayers(); ++layerID) { if (resultLabelSetImage->GetNumberOfLayers() <= layerID) { resultLabelSetImage->AddLayer(); } - resultLabelSetImage->AddLabelSetToLayer(layerID, inputLabelSetImage->GetLabelSet(layerID)->Clone()); + resultLabelSetImage->ReplaceGroupLabels(layerID, inputLabelSetImage->GetConstLabelsByValue(inputLabelSetImage->GetLabelValuesByGroup(layerID))); cloneInput->SetActiveLayer(layerID); resultLabelSetImage->SetActiveLayer(layerID); doMapTimesteps(cloneInput, resultLabelSetImage, registration, throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue, mitk::ImageMappingInterpolator::Linear); } resultLabelSetImage->SetActiveLayer(inputLabelSetImage->GetActiveLayer()); - resultLabelSetImage->GetLabelSet(inputLabelSetImage->GetActiveLayer())->SetActiveLabel(inputLabelSetImage->GetActiveLabel(inputLabelSetImage->GetActiveLayer())->GetValue()); + resultLabelSetImage->SetActiveLabel(inputLabelSetImage->GetActiveLabel()->GetValue()); result = resultLabelSetImage; } return result; } mitk::ImageMappingHelper::ResultImageType::Pointer mitk::ImageMappingHelper::map(const InputImageType* input, const MITKRegistrationType* registration, bool throwOnOutOfInputAreaError, const double& paddingValue, const ResultImageGeometryType* resultGeometry, bool throwOnMappingError, const double& errorValue, mitk::ImageMappingInterpolator::Type) { if (!registration) { mitkThrow() << "Cannot map image. Passed registration wrapper pointer is nullptr."; } if (!registration->GetRegistration()) { mitkThrow() << "Cannot map image. Passed registration wrapper containes no registration."; } if (!input) { mitkThrow() << "Cannot map image. Passed image pointer is nullptr."; } ResultImageType::Pointer result = map(input, registration->GetRegistration(), throwOnOutOfInputAreaError, paddingValue, resultGeometry, throwOnMappingError, errorValue); return result; } mitk::ImageMappingHelper::ResultImageGeometryType::Pointer mitk::ImageMappingHelper::GenerateSuperSampledGeometry(const ResultImageGeometryType* inputGeometry, double xScaling, double yScaling, double zScaling) { auto resultGeometry = inputGeometry->Clone(); //change the pixel count and spacing of the geometry mitk::BaseGeometry::BoundsArrayType geoBounds = inputGeometry->GetBounds(); auto oldSpacing = inputGeometry->GetSpacing(); mitk::Vector3D geoSpacing; geoSpacing[0] = oldSpacing[0] / xScaling; geoSpacing[1] = oldSpacing[1] / yScaling; geoSpacing[2] = oldSpacing[2] / zScaling; geoBounds[1] = geoBounds[1] * xScaling; geoBounds[3] = geoBounds[3] * yScaling; geoBounds[5] = geoBounds[5] * zScaling; resultGeometry->SetBounds(geoBounds); resultGeometry->SetSpacing(geoSpacing); auto oldOrigin = inputGeometry->GetOrigin(); //if we change the spacing we must also correct the origin to ensure //that the voxel matrix still covers the same space. This is due the fact //that the origin is not in the corner of the voxel matrix, but in the center // of the voxel that is in the corner. mitk::Point3D newOrigin; for (mitk::Point3D::SizeType i = 0; i < 3; ++i) { newOrigin[i] = 0.5 * (geoSpacing[i] - oldSpacing[i]) + oldOrigin[i]; } return resultGeometry; } mitk::ImageMappingHelper::ResultImageType::Pointer mitk::ImageMappingHelper:: refineGeometry(const InputImageType * input, const RegistrationType * registration, bool throwOnError) { mitk::ImageMappingHelper::ResultImageType::Pointer result = nullptr; if (!registration) { mitkThrow() << "Cannot refine image geometry. Passed registration pointer is nullptr."; } if (!input) { mitkThrow() << "Cannot refine image geometry. Passed image pointer is nullptr."; } mitk::MITKRegistrationHelper::Affine3DTransformType::Pointer spTransform = mitk::MITKRegistrationHelper::getAffineMatrix(registration, false); if (spTransform.IsNull() && throwOnError) { mitkThrow() << "Cannot refine image geometry. Registration does not contain a suitable direct mapping kernel (3D affine transformation or compatible required)."; } if (spTransform.IsNotNull()) { //copy input image result = input->Clone(); //refine geometries for (unsigned int i = 0; i < result->GetTimeSteps(); ++i) { //refine every time step result->GetGeometry(i)->Compose(spTransform); } result->GetTimeGeometry()->Update(); } return result; } mitk::ImageMappingHelper::ResultImageType::Pointer mitk::ImageMappingHelper:: refineGeometry(const InputImageType* input, const MITKRegistrationType* registration, bool throwOnError) { if (!registration) { mitkThrow() << "Cannot refine image geometry. Passed registration wrapper pointer is nullptr."; } if (!registration->GetRegistration()) { mitkThrow() << "Cannot refine image geometry. Passed registration wrapper containes no registration."; } if (!input) { mitkThrow() << "Cannot refine image geometry. Passed image pointer is nullptr."; } ResultImageType::Pointer result = refineGeometry(input, registration->GetRegistration(), throwOnError); return result; } bool mitk::ImageMappingHelper:: canRefineGeometry(const RegistrationType* registration) { bool result = true; if (!registration) { mitkThrow() << "Cannot check refine capability of registration. Passed registration pointer is nullptr."; } //if the helper does not return null, we can refine the geometry. result = mitk::MITKRegistrationHelper::getAffineMatrix(registration,false).IsNotNull(); return result; } bool mitk::ImageMappingHelper:: canRefineGeometry(const MITKRegistrationType* registration) { if (!registration) { mitkThrow() << "Cannot check refine capability of registration. Passed registration wrapper pointer is nullptr."; } if (!registration->GetRegistration()) { mitkThrow() << "Cannot check refine capability of registration. Passed registration wrapper containes no registration."; } return canRefineGeometry(registration->GetRegistration()); } diff --git a/Modules/Multilabel/Testing/files.cmake b/Modules/Multilabel/Testing/files.cmake index 0a03695261..632cad3e3a 100644 --- a/Modules/Multilabel/Testing/files.cmake +++ b/Modules/Multilabel/Testing/files.cmake @@ -1,9 +1,8 @@ set(MODULE_TESTS mitkLabelTest.cpp - mitkLabelSetTest.cpp mitkLabelSetImageTest.cpp mitkLegacyLabelSetImageIOTest.cpp mitkLabelSetImageSurfaceStampFilterTest.cpp mitkTransferLabelTest.cpp ) diff --git a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp index 700825fdaa..32fc677470 100644 --- a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp +++ b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp @@ -1,514 +1,663 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include +namespace CppUnit +{ + namespace StringHelper + { + template<> inline std::string toString(const mitk::LabelSetImage::LabelValueVectorType& lvs) + { + std::ostringstream stream; + stream << "["; + for (mitk::LabelSetImage::LabelValueVectorType::const_iterator iter = lvs.begin(); iter!=lvs.end(); ++iter) + { + stream << *iter; + if (iter + 1 != lvs.end()) stream << ", "; + + } + stream << "]"; + return stream.str(); + } + + template<> inline std::string toString(const std::vector& strings) + { + std::ostringstream stream; + stream << "["; + for (std::vector::const_iterator iter = strings.begin(); iter != strings.end(); ++iter) + { + stream << *iter; + if (iter + 1 != strings.end()) stream << ", "; + + } + stream << "]"; + return stream.str(); + } + + } +} + class mitkLabelSetImageTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkLabelSetImageTestSuite); MITK_TEST(TestInitialize); + MITK_TEST(TestClone); MITK_TEST(TestAddLayer); MITK_TEST(TestGetActiveLabelSet); MITK_TEST(TestGetActiveLabel); MITK_TEST(TestInitializeByLabeledImage); - MITK_TEST(TestGetLabelSet); MITK_TEST(TestGetLabel); + MITK_TEST(TestGetLabelValues); + MITK_TEST(TestGetLabelClassNames); MITK_TEST(TestSetUnlabeledLabelLock); MITK_TEST(TestGetTotalNumberOfLabels); + MITK_TEST(TestGetNumberOfLabels); MITK_TEST(TestExistsLabel); - MITK_TEST(TestExistsLabelSet); + MITK_TEST(TestExistsGroup); MITK_TEST(TestSetActiveLayer); MITK_TEST(TestRemoveLayer); MITK_TEST(TestRemoveLabels); MITK_TEST(TestEraseLabels); MITK_TEST(TestMergeLabels); MITK_TEST(TestCreateLabelMask); CPPUNIT_TEST_SUITE_END(); private: mitk::LabelSetImage::Pointer m_LabelSetImage; + int m_LabelAddedEventCount; + int m_LabelModifiedEventCount; + int m_LabelRemovedEventCount; + int m_LabelsChangedEventCount; + int m_GroupAddedEventCount; + int m_GroupModifiedEventCount; + int m_GroupRemovedEventCount; public: void setUp() override { // Create a new labelset image m_LabelSetImage = mitk::LabelSetImage::New(); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 96, 128, 52 }; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); m_LabelSetImage->Initialize(regularImage); + + this->ResetEvents(); + m_LabelSetImage->AddObserver(mitk::LabelAddedEvent(), [this](const itk::EventObject&) { ++(this->m_LabelAddedEventCount); }); + m_LabelSetImage->AddObserver(mitk::LabelModifiedEvent(), [this](const itk::EventObject&) { ++(this->m_LabelModifiedEventCount); }); + m_LabelSetImage->AddObserver(mitk::LabelRemovedEvent(), [this](const itk::EventObject&) { ++(this->m_LabelRemovedEventCount); }); + m_LabelSetImage->AddObserver(mitk::LabelsChangedEvent(), [this](const itk::EventObject&) { ++(this->m_LabelsChangedEventCount); }); + m_LabelSetImage->AddObserver(mitk::GroupAddedEvent(), [this](const itk::EventObject&) { ++(this->m_GroupAddedEventCount); }); + m_LabelSetImage->AddObserver(mitk::GroupModifiedEvent(), [this](const itk::EventObject&) { ++(this->m_GroupModifiedEventCount); }); + m_LabelSetImage->AddObserver(mitk::GroupRemovedEvent(), [this](const itk::EventObject&) { ++(this->m_GroupRemovedEventCount); }); } void tearDown() override { // Delete LabelSetImage m_LabelSetImage = nullptr; } + void ResetEvents() + { + m_LabelAddedEventCount = 0; + m_LabelModifiedEventCount = 0; + m_LabelRemovedEventCount = 0; + m_LabelsChangedEventCount = 0; + m_GroupAddedEventCount = 0; + m_GroupModifiedEventCount = 0; + m_GroupRemovedEventCount = 0; + } + + bool CheckEvents(int lAdd, int lMod, int lRem, int lsC, int gAdd, int gMod, int gRem) + { + return m_GroupAddedEventCount == gAdd && m_GroupModifiedEventCount == gMod && m_GroupRemovedEventCount == gRem + && m_LabelAddedEventCount == lAdd && m_LabelModifiedEventCount == lMod && m_LabelRemovedEventCount == lRem + && m_LabelsChangedEventCount == lsC; + } + + void InitializeTestSegmentation() + { + mitk::Label::Pointer label1 = mitk::Label::New(1, "Label1"); + mitk::Label::Pointer label2 = mitk::Label::New(20, "Label2"); + mitk::Label::Pointer label22 = mitk::Label::New(22, "Label2"); + mitk::Label::Pointer label3 = mitk::Label::New(30, "Label3"); + + m_LabelSetImage->AddLabel(label1, 0); + m_LabelSetImage->AddLayer({ label2, label22, label3 }); + m_LabelSetImage->AddLayer(); + this->ResetEvents(); + } + void TestInitialize() { // LabelSet image should always has the pixel type mitk::Label::PixelType CPPUNIT_ASSERT_MESSAGE("LabelSetImage has wrong pixel type", m_LabelSetImage->GetPixelType() == mitk::MakeScalarPixelType()); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 96, 128, 52 }; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); mitk::BaseGeometry::Pointer regularImageGeo = regularImage->GetGeometry(); mitk::BaseGeometry::Pointer labelImageGeo = m_LabelSetImage->GetGeometry(); MITK_ASSERT_EQUAL(labelImageGeo, regularImageGeo, "LabelSetImage has wrong geometry"); // By default one layer should be added CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - number of layers is not one", m_LabelSetImage->GetNumberOfLayers() == 1); CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == 0); CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - no active label should be selected", m_LabelSetImage->GetActiveLabel() == nullptr); } + void TestClone() + { + mitk::Label::Pointer label1 = mitk::Label::New(); + label1->SetName("Label1"); + label1->SetValue(1); + + mitk::Label::Pointer label2 = mitk::Label::New(); + label2->SetName("Label2"); + label2->SetValue(200); + + mitk::Label::Pointer label3 = mitk::Label::New(); + label2->SetName("Label3"); + label2->SetValue(300); + + m_LabelSetImage->AddLabel(label1, 0); + m_LabelSetImage->AddLayer({ label2, label3 }); + + auto clone = m_LabelSetImage->Clone(); + MITK_ASSERT_EQUAL(m_LabelSetImage, clone, "LabelSetImage clone is not equal."); + } + void TestAddLayer() { CPPUNIT_ASSERT_MESSAGE("Number of layers is not zero", m_LabelSetImage->GetNumberOfLayers() == 1); m_LabelSetImage->AddLayer(); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - number of layers is not one", m_LabelSetImage->GetNumberOfLayers() == 2); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active layer has wrong ID", - m_LabelSetImage->GetActiveLayer() == 1); + m_LabelSetImage->GetActiveLayer() == 0); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - no active label should be selected", m_LabelSetImage->GetActiveLabel() == nullptr); - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); + CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0,0,0,0,1,0,0)); + mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); - newlayer->AddLabel(label1); - newlayer->AddLabel(label2); - newlayer->SetActiveLabel(200); - unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); + const auto layerID = m_LabelSetImage->AddLayer({ label1, label2 }); + m_LabelSetImage->SetActiveLabel(200); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - number of layers is not two", m_LabelSetImage->GetNumberOfLayers() == 3); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == layerID); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", - m_LabelSetImage->GetActiveLabel(layerID)->GetValue() == 200); + m_LabelSetImage->GetActiveLabel()->GetValue() == 200); + + CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 2, 0, 0)); } void TestGetActiveLabelSet() { - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); - newlayer->AddLabel(label1); - newlayer->AddLabel(label2); - newlayer->SetActiveLabel(200); - - unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); + mitk::LabelSetImage::ConstLabelVectorType refLayer = { label1, label2 }; + unsigned int layerID = m_LabelSetImage->AddLayer(refLayer); + m_LabelSetImage->SetActiveLabel(200); - mitk::LabelSet::Pointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); + auto activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong layer ID was returned", layerID == 1); - CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *activeLayer, 0.00001, true)); + CPPUNIT_ASSERT_MESSAGE("Wrong layer ID was returned", layerID == m_LabelSetImage->GetActiveLayer()); - mitk::LabelSet::ConstPointer constActiveLayer = const_cast(m_LabelSetImage.GetPointer())->GetActiveLabelSet(); - CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *constActiveLayer, 0.00001, true)); + CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(refLayer, activeLayer, 0.00001, true)); } void TestGetActiveLabel() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); mitk::Label::PixelType value1 = 1; label1->SetValue(value1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); mitk::Label::PixelType value2 = 200; label2->SetValue(value2); - m_LabelSetImage->GetActiveLabelSet()->AddLabel(label1); - m_LabelSetImage->GetActiveLabelSet()->AddLabel(label2); - m_LabelSetImage->GetActiveLabelSet()->SetActiveLabel(1); + m_LabelSetImage->AddLabel(label1,0); + m_LabelSetImage->AddLabel(label2,0); + m_LabelSetImage->SetActiveLabel(1); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel()->GetValue() == value1); - m_LabelSetImage->GetActiveLabelSet()->SetActiveLabel(value2); + m_LabelSetImage->SetActiveLabel(value2); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel()->GetValue() == value2); CPPUNIT_ASSERT_MESSAGE("Active Label was not correctly retreived with const getter", const_cast(m_LabelSetImage.GetPointer())->GetActiveLabel()->GetValue() == value2); } void TestInitializeByLabeledImage() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage->InitializeByLabeledImage(image); - CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 5", m_LabelSetImage->GetNumberOfLabels() == 5); - } - - void TestGetLabelSet() - { - // Test get non existing lset - mitk::LabelSet::ConstPointer lset = m_LabelSetImage->GetLabelSet(10000); - CPPUNIT_ASSERT_MESSAGE("Non existing labelset is not nullptr", lset.IsNull()); - - lset = m_LabelSetImage->GetLabelSet(0); - CPPUNIT_ASSERT_MESSAGE("Existing labelset is nullptr", lset.IsNotNull()); + CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 5", m_LabelSetImage->GetNumberOfLabels(0) == 5); } void TestGetLabel() { - mitk::Label::Pointer label1 = mitk::Label::New(); - label1->SetName("Label1"); - mitk::Label::PixelType value1 = 1; - label1->SetValue(value1); - - mitk::Label::Pointer label2 = mitk::Label::New(); - label2->SetName("Label2"); - mitk::Label::PixelType value2 = 200; - label2->SetValue(value2); + mitk::Label::Pointer label1 = mitk::Label::New(1, "Label1"); + mitk::Label::Pointer label2 = mitk::Label::New(20,"Label2"); - m_LabelSetImage->GetActiveLabelSet()->AddLabel(label1); + m_LabelSetImage->AddLabel(label1,0); m_LabelSetImage->AddLayer(); - m_LabelSetImage->GetLabelSet(1)->AddLabel(label2); + m_LabelSetImage->AddLabel(label2,1); + this->ResetEvents(); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for active layer", mitk::Equal(*m_LabelSetImage->GetLabel(1), *label1, 0.0001, true)); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for layer 1", - mitk::Equal(*m_LabelSetImage->GetLabel(200, 1), *label2, 0.0001, true)); + mitk::Equal(*m_LabelSetImage->GetLabel(20), *label2, 0.0001, true)); // Try to get a non existing label - mitk::Label *label3 = m_LabelSetImage->GetLabel(1000); - CPPUNIT_ASSERT_MESSAGE("Non existing label should be nullptr", label3 == nullptr); + mitk::Label *unkownLabel = m_LabelSetImage->GetLabel(1000); + CPPUNIT_ASSERT_MESSAGE("Non existing label should be nullptr", unkownLabel == nullptr); + CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); + } + + void TestGetLabelValues() + { + InitializeTestSegmentation(); + + auto labels = m_LabelSetImage->GetLabelValuesByGroup(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for group 0", + mitk::LabelSetImage::LabelValueVectorType({ 1 }), labels); + + labels = m_LabelSetImage->GetLabelValuesByGroup(1); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for group 1", + mitk::LabelSetImage::LabelValueVectorType({ 20, 22, 30 }), labels); + + labels = m_LabelSetImage->GetLabelValuesByGroup(2); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for group 2", + mitk::LabelSetImage::LabelValueVectorType(), labels); + + CPPUNIT_ASSERT_THROW(m_LabelSetImage->GetLabelValuesByGroup(3), mitk::Exception); + + labels = m_LabelSetImage->GetLabelValuesByName(0, "Label1"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for \"Label2\" in group 0", + mitk::LabelSetImage::LabelValueVectorType({ 1 }), labels); + + labels = m_LabelSetImage->GetLabelValuesByName(1, "Label2"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for \"Label2\" in group 1", + mitk::LabelSetImage::LabelValueVectorType({ 20, 22 }), labels); + + labels = m_LabelSetImage->GetLabelValuesByName(1, "Label3"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for \"Label3\" in group 1", + mitk::LabelSetImage::LabelValueVectorType({ 30 }), labels); - // Try to get a label from a non existing layer - label3 = m_LabelSetImage->GetLabel(200, 1000); - CPPUNIT_ASSERT_MESSAGE("Label from non existing layer should be nullptr", label3 == nullptr); + labels = m_LabelSetImage->GetLabelValuesByName(2, "Label1"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for group 2", + mitk::LabelSetImage::LabelValueVectorType(), labels); + + labels = m_LabelSetImage->GetLabelValuesByName(0, "unkown"); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for unkown name", + mitk::LabelSetImage::LabelValueVectorType(), labels); + + CPPUNIT_ASSERT_THROW(m_LabelSetImage->GetLabelValuesByName(3,"invalid"), mitk::Exception); + + labels = m_LabelSetImage->GetAllLabelValues(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong label values retrieved for unkown name", + mitk::LabelSetImage::LabelValueVectorType({1,20,22,30}), labels); + + CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); + } + + + void TestGetLabelClassNames() + { + InitializeTestSegmentation(); + + auto names = m_LabelSetImage->GetLabelClassNames(); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong names retrieved", + std::vector({ "Label1", "Label2", "Label3"}), names); + names = m_LabelSetImage->GetLabelClassNamesByGroup(0); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong names retrieved for group 0", + std::vector({ "Label1"}), names); + names = m_LabelSetImage->GetLabelClassNamesByGroup(1); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong names retrieved for group 1", + std::vector({"Label2", "Label3" }), names); + names = m_LabelSetImage->GetLabelClassNamesByGroup(2); + CPPUNIT_ASSERT_EQUAL_MESSAGE("Wrong names retrieved for group 2", + std::vector(), names); + + CPPUNIT_ASSERT_THROW(m_LabelSetImage->GetLabelValuesByGroup(3), mitk::Exception); + + CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); } void TestSetUnlabeledLabelLock() { auto locked = m_LabelSetImage->GetUnlabeledLabelLock(); CPPUNIT_ASSERT_MESSAGE("Wrong UnlabeledLabelLock default state", locked == false); m_LabelSetImage->SetUnlabeledLabelLock(true); locked = m_LabelSetImage->GetUnlabeledLabelLock(); CPPUNIT_ASSERT_MESSAGE("Wrong UnlabeledLabelLock state", locked == true); } void TestGetTotalNumberOfLabels() { - mitk::Label::Pointer label1 = mitk::Label::New(); - label1->SetName("Label1"); - mitk::Label::PixelType value1 = 1; - label1->SetValue(value1); - - mitk::Label::Pointer label2 = mitk::Label::New(); - label2->SetName("Label2"); - mitk::Label::PixelType value2 = 200; - label2->SetValue(value2); - - m_LabelSetImage->GetActiveLabelSet()->AddLabel(label1); - m_LabelSetImage->AddLayer(); - m_LabelSetImage->GetLabelSet(1)->AddLabel(label2); + this->InitializeTestSegmentation(); CPPUNIT_ASSERT_MESSAGE( "Wrong total number of labels", - m_LabelSetImage->GetTotalNumberOfLabels() == 2); + m_LabelSetImage->GetTotalNumberOfLabels() == 4); + CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); + } + + void TestGetNumberOfLabels() + { + this->InitializeTestSegmentation(); + CPPUNIT_ASSERT_MESSAGE( + "Wrong number of labels in group 0", + m_LabelSetImage->GetNumberOfLabels(0) == 1); + CPPUNIT_ASSERT_MESSAGE( + "Wrong number of labels in group 1", + m_LabelSetImage->GetNumberOfLabels(1) == 3); + CPPUNIT_ASSERT_MESSAGE( + "Wrong number of labels in group 2", + m_LabelSetImage->GetNumberOfLabels(2) == 0); + + CPPUNIT_ASSERT_THROW(m_LabelSetImage->GetNumberOfLabels(3), mitk::Exception); + + CPPUNIT_ASSERT_MESSAGE("Event count incorrect", CheckEvents(0, 0, 0, 0, 0, 0, 0)); } void TestExistsLabel() { mitk::Label::Pointer label = mitk::Label::New(); label->SetName("Label2"); mitk::Label::PixelType value = 200; label->SetValue(value); m_LabelSetImage->AddLayer(); - m_LabelSetImage->GetLabelSet(1)->AddLabel(label); + m_LabelSetImage->AddLabel(label,1); m_LabelSetImage->SetActiveLayer(0); CPPUNIT_ASSERT_MESSAGE("Existing label was not found", m_LabelSetImage->ExistLabel(value) == true); CPPUNIT_ASSERT_MESSAGE("Non existing label was found", m_LabelSetImage->ExistLabel(10000) == false); } - void TestExistsLabelSet() + void TestExistsGroup() { - // Cache active layer - mitk::LabelSet::ConstPointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); - - // Add new layer - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); - newlayer->AddLabel(label1); - newlayer->AddLabel(label2); - newlayer->SetActiveLabel(200); + m_LabelSetImage->AddLayer({label1, label2}); - m_LabelSetImage->AddLayer(newlayer); - - CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == true); - CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == true); - CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(20) == false); + CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(0) == true); + CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(1) == true); + CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(20) == false); } void TestSetActiveLayer() { // Cache active layer - mitk::LabelSet::ConstPointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); + auto refActiveLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); // Add new layer - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); - newlayer->AddLabel(label1); - newlayer->AddLabel(label2); - newlayer->SetActiveLabel(200); - + mitk::LabelSetImage::ConstLabelVectorType newlayer = { label1, label2 }; unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); // Set initial layer as active layer m_LabelSetImage->SetActiveLayer(0); + auto activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", - mitk::Equal(*activeLayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); + mitk::Equal(refActiveLayer, activeLayer, 0.00001, true)); // Set previously added layer as active layer m_LabelSetImage->SetActiveLayer(layerID); + activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", - mitk::Equal(*newlayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); + mitk::Equal(newlayer, activeLayer, 0.00001, true)); // Set a non existing layer as active layer - nothing should change m_LabelSetImage->SetActiveLayer(10000); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", - mitk::Equal(*newlayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); + mitk::Equal(newlayer, activeLayer, 0.00001, true)); } void TestRemoveLayer() { // Cache active layer - mitk::LabelSet::ConstPointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); + auto refActiveLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); // Add new layers m_LabelSetImage->AddLayer(); - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); - newlayer->AddLabel(label1); - newlayer->AddLabel(label2); - newlayer->SetActiveLabel(200); - - m_LabelSetImage->AddLayer(newlayer); + mitk::LabelSetImage::ConstLabelVectorType newlayer = { label1, label2 }; + unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); + m_LabelSetImage->SetActiveLayer(layerID); + auto activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", - mitk::Equal(*newlayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); + mitk::Equal(newlayer, activeLayer, 0.00001, true)); - m_LabelSetImage->RemoveLayer(); + m_LabelSetImage->RemoveGroup(1); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 2); - CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(2) == false); - CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == true); - CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == true); + CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(2) == false); + CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(1) == true); + CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(0) == true); - m_LabelSetImage->RemoveLayer(); + m_LabelSetImage->RemoveGroup(1); + activeLayer = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(m_LabelSetImage->GetActiveLayer())); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 1); - CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == false); - CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == true); + CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(1) == false); + CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(0) == true); CPPUNIT_ASSERT_MESSAGE("Wrong active layer", - mitk::Equal(*activeLayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); + mitk::Equal(refActiveLayer, activeLayer, 0.00001, true)); - m_LabelSetImage->RemoveLayer(); + m_LabelSetImage->RemoveGroup(0); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 0); - CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == false); - CPPUNIT_ASSERT_MESSAGE("Active layers is not nullptr although all layer have been removed", - m_LabelSetImage->GetActiveLabelSet() == nullptr); + CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistGroup(0) == false); + CPPUNIT_ASSERT_THROW_MESSAGE("GetActiveLayers does not fail although all layer have been removed", + m_LabelSetImage->GetActiveLayer(), mitk::Exception); } void TestRemoveLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); - CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 5); + CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels(0) == 5); // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exist after initialization", m_LabelSetImage->ExistLabel(3) == true); m_LabelSetImage->RemoveLabel(1); std::vector labelsToBeRemoved; labelsToBeRemoved.push_back(3); labelsToBeRemoved.push_back(7); m_LabelSetImage->RemoveLabels(labelsToBeRemoved); CPPUNIT_ASSERT_MESSAGE("Wrong number of labels after some have been removed", - m_LabelSetImage->GetNumberOfLabels() == 2); + m_LabelSetImage->GetNumberOfLabels(0) == 2); // Values within the image are 0, 1, 3, 5, 6, 7 - New Min / Max value should be 5 / 6 // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Labels with value 1 and 3 were not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 5); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); } void TestEraseLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); - CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 5); + CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels(0) == 5); // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exist after initialization", m_LabelSetImage->ExistLabel(3) == true); m_LabelSetImage->EraseLabel(1); std::vector labelsToBeErased; labelsToBeErased.push_back(3); labelsToBeErased.push_back(7); m_LabelSetImage->EraseLabels(labelsToBeErased); CPPUNIT_ASSERT_MESSAGE("Wrong number of labels since none have been removed", - m_LabelSetImage->GetNumberOfLabels() == 5); + m_LabelSetImage->GetNumberOfLabels(0) == 5); // Values within the image are 0, 1, 3, 5, 6, 7 - New Min / Max value should be 5 / 6 // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Labels with value 1 and 3 were not erased from the image", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 5); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not erased from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); } void TestMergeLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); - CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 5); + CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels(0) == 5); // 2ndMin because of unlabeled pixels = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 6 does not exist after initialization", m_LabelSetImage->ExistLabel(6) == true); // Merge label 7 with label 6. Result should be that label 7 is not present anymore. m_LabelSetImage->MergeLabel(6, 7); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); // Count all pixels with value 6 = 507 // Count all pixels with value 7 = 823 // Check if merged label has 507 + 823 = 1330 pixels CPPUNIT_ASSERT_MESSAGE("Labels were not correctly merged", m_LabelSetImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 1330); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exist after initialization", m_LabelSetImage->ExistLabel(3) == true); CPPUNIT_ASSERT_MESSAGE("Label with ID 5 does not exist after initialization", m_LabelSetImage->ExistLabel(5) == true); // Merge labels 5 and 6 with 3. Result should be that labels 5 and 6 are not present anymore. std::vector vectorOfSourcePixelValues{ 5, 6 }; m_LabelSetImage->MergeLabels(3, vectorOfSourcePixelValues); // Values within the image are 0, 1, 3, 5, 6, 7 - New Max value should be 3 CPPUNIT_ASSERT_MESSAGE("Labels with value 5 and 6 were not removed from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 3); // Count all pixels with value 3 = 1893 // Count all pixels with value 5 = 2143 // Count all pixels with value 6 = 1330 // Check if merged label has 1893 + 2143 + 1330 = 5366 pixels CPPUNIT_ASSERT_MESSAGE("Labels were not correctly merged", m_LabelSetImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 5366); } void TestCreateLabelMask() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); auto labelMask = m_LabelSetImage->CreateLabelMask(6); mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); cropFilter->SetInput(labelMask); cropFilter->SetBackgroundValue(0); cropFilter->SetMarginFactor(1.15); cropFilter->Update(); auto maskImage = cropFilter->GetOutput(); // Count all pixels with value 6 = 507 CPPUNIT_ASSERT_MESSAGE("Label mask not correctly created", maskImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 507); } }; MITK_TEST_SUITE_REGISTRATION(mitkLabelSetImage) diff --git a/Modules/Multilabel/Testing/mitkLabelSetTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetTest.cpp deleted file mode 100644 index 01adf5f941..0000000000 --- a/Modules/Multilabel/Testing/mitkLabelSetTest.cpp +++ /dev/null @@ -1,170 +0,0 @@ -/*============================================================================ - -The Medical Imaging Interaction Toolkit (MITK) - -Copyright (c) German Cancer Research Center (DKFZ) -All rights reserved. - -Use of this source code is governed by a 3-clause BSD license that can be -found in the LICENSE file. - -============================================================================*/ - -#include -#include -#include -#include -#include - -class mitkLabelSetTestSuite : public mitk::TestFixture -{ - CPPUNIT_TEST_SUITE(mitkLabelSetTestSuite); - MITK_TEST(TestSetLayer); - MITK_TEST(TestSetActiveLabel); - MITK_TEST(TestRemoveLabel); - MITK_TEST(TestAddLabel); - MITK_TEST(TestRenameLabel); - MITK_TEST(TestSetAllLabelsVisible); - MITK_TEST(TestSetAllLabelsLocked); - MITK_TEST(TestRemoveAllLabels); - CPPUNIT_TEST_SUITE_END(); - -private: - mitk::LabelSet::Pointer m_LabelSet; - mitk::LabelSet::PixelType m_InitialNumberOfLabels; - - void AddLabels(mitk::LabelSet::PixelType numOfLabels) - { - mitk::Label::Pointer label; - const std::string namePrefix = "Label_"; - const mitk::Color gray(0.5f); - - for (mitk::Label::PixelType i = 0; i < numOfLabels; ++i) - { - label = mitk::Label::New(); - label->SetName(namePrefix + std::to_string(i)); - label->SetValue(i); - label->SetVisible((i % 2 == 0)); - label->SetLayer(i % 3); - label->SetColor(gray); - - m_LabelSet->AddLabel(label); - } - } - -public: - void setUp() override - { - m_InitialNumberOfLabels = 6; - m_LabelSet = mitk::LabelSet::New(); - - this->AddLabels(m_InitialNumberOfLabels); - m_LabelSet->SetLayer(0); - m_LabelSet->SetActiveLabel(0); - } - - void tearDown() override - { - m_LabelSet = nullptr; - } - - void TestSetLayer() - { - CPPUNIT_ASSERT_MESSAGE("Wrong initial layer", m_LabelSet->GetLayer() == 0); - - m_LabelSet->SetLayer(1); - CPPUNIT_ASSERT_MESSAGE("Wrong layer", m_LabelSet->GetLayer() == 1); - } - - void TestSetActiveLabel() - { - CPPUNIT_ASSERT_MESSAGE("Wrong initial active label", m_LabelSet->GetActiveLabel() == nullptr); - - m_LabelSet->SetActiveLabel(1); - CPPUNIT_ASSERT_MESSAGE("Wrong active label", m_LabelSet->GetActiveLabel()->GetValue() == 1); - } - - void TestRemoveLabel() - { - CPPUNIT_ASSERT_MESSAGE("Wrong initial number of label", m_LabelSet->GetNumberOfLabels() == m_InitialNumberOfLabels); - - // Remove a label that is not the active label - m_LabelSet->SetActiveLabel(2); - m_LabelSet->RemoveLabel(1); - - mitk::LabelSet::PixelType numLabels = m_InitialNumberOfLabels - 1; - - CPPUNIT_ASSERT_MESSAGE("Label was not removed", m_LabelSet->ExistLabel(1) == false); - CPPUNIT_ASSERT_MESSAGE("Wrong number of label", m_LabelSet->GetNumberOfLabels() == numLabels); - CPPUNIT_ASSERT_MESSAGE("Wrong active label", m_LabelSet->GetActiveLabel()->GetValue() == 2); - - // Remove active label - now the succeeding label should be active - m_LabelSet->RemoveLabel(2); - CPPUNIT_ASSERT_MESSAGE("Wrong active label", m_LabelSet->GetActiveLabel()->GetValue() == 3); - CPPUNIT_ASSERT_MESSAGE("Label was not removed", m_LabelSet->ExistLabel(2) == false); - CPPUNIT_ASSERT_MESSAGE("Wrong initial number of label", m_LabelSet->GetNumberOfLabels() == --numLabels); - } - - void TestAddLabel() - { - auto newLabel = mitk::Label::New(); - newLabel->SetValue(1); - m_LabelSet->AddLabel(newLabel); - - // Since label with value 1 already exists the new label will get the value m_InitialNumberOfValues - CPPUNIT_ASSERT_MESSAGE("Wrong label value", m_LabelSet->GetActiveLabel()->GetValue() == m_InitialNumberOfLabels+1); - CPPUNIT_ASSERT_MESSAGE("Wrong number of label", m_LabelSet->GetNumberOfLabels() == static_castGetNumberOfLabels())>(m_InitialNumberOfLabels + 1)); - } - - void TestRenameLabel() - { - const mitk::Color white(1.0f); - const std::string name = "MyAwesomeLabel"; - - m_LabelSet->RenameLabel(1, name, white); - - const auto* label = m_LabelSet->GetLabel(1); - CPPUNIT_ASSERT_MESSAGE("Wrong label name", label->GetName() == name ); - - const auto& color = label->GetColor(); - CPPUNIT_ASSERT_MESSAGE("Wrong color", color == white); - } - - void TestSetAllLabelsVisible() - { - const auto numLabels = static_cast(m_LabelSet->GetNumberOfLabels()); - - m_LabelSet->SetAllLabelsVisible(true); - - for (mitk::LabelSet::PixelType i = 1; i < numLabels; ++i) - CPPUNIT_ASSERT_MESSAGE("Label not visible", m_LabelSet->GetLabel(i)->GetVisible() == true); - - m_LabelSet->SetAllLabelsVisible(false); - - for (mitk::LabelSet::PixelType i = 1; i < numLabels; ++i) - CPPUNIT_ASSERT_MESSAGE("Label visible", m_LabelSet->GetLabel(i)->GetVisible() == false); - } - - void TestSetAllLabelsLocked() - { - const auto numLabels = static_cast(m_LabelSet->GetNumberOfLabels()); - - m_LabelSet->SetAllLabelsLocked(true); - - for (mitk::LabelSet::PixelType i = 1; i < numLabels; ++i) - CPPUNIT_ASSERT_MESSAGE("Label not locked", m_LabelSet->GetLabel(i)->GetLocked() == true); - - m_LabelSet->SetAllLabelsLocked(false); - - for (mitk::LabelSet::PixelType i = 1; i < numLabels; ++i) - CPPUNIT_ASSERT_MESSAGE("Label locked", m_LabelSet->GetLabel(i)->GetLocked() == false); - } - - void TestRemoveAllLabels() - { - m_LabelSet->RemoveAllLabels(); - CPPUNIT_ASSERT_MESSAGE("Not all labels were removed", m_LabelSet->GetNumberOfLabels() == 0); - } -}; - -MITK_TEST_SUITE_REGISTRATION(mitkLabelSet) diff --git a/Modules/Multilabel/Testing/mitkLegacyLabelSetImageIOTest.cpp b/Modules/Multilabel/Testing/mitkLegacyLabelSetImageIOTest.cpp index 80a7eb7fba..3df9b707b3 100644 --- a/Modules/Multilabel/Testing/mitkLegacyLabelSetImageIOTest.cpp +++ b/Modules/Multilabel/Testing/mitkLegacyLabelSetImageIOTest.cpp @@ -1,142 +1,145 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include std::string pathToImage; class mitkLegacyLabelSetImageIOTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkLegacyLabelSetImageIOTestSuite); MITK_TEST(TestRead3DLabelSetImage_Default); MITK_TEST(TestRead3DLabelSetImage_Adapt); MITK_TEST(TestRead3DLabelSetImage_Split); MITK_TEST(TestRead3DplusTLabelSetImage); CPPUNIT_TEST_SUITE_END(); private: - mitk::LabelSet::Pointer m_labelSet1; - mitk::LabelSet::Pointer m_labelSet2; - mitk::LabelSet::Pointer m_labelSet2_adapted; + mitk::LabelSetImage::ConstLabelVectorType m_labelSet1; + mitk::LabelSetImage::ConstLabelVectorType m_labelSet2; + mitk::LabelSetImage::ConstLabelVectorType m_labelSet2_adapted; public: mitk::Label::Pointer GenerateLabel(mitk::Label::PixelType value, const std::string& name, float r, float g, float b) const { auto label = mitk::Label::New(value, name); mitk::Color color; color.SetRed(r); color.SetGreen(g); color.SetBlue(b); label->SetColor(color); return label; } void setUp() override { - m_labelSet1 = mitk::LabelSet::New(); auto label = GenerateLabel(1, "Label 1", 0.745098054f, 0.f, 0.196078435f); - m_labelSet1->AddLabel(label,false); - label = GenerateLabel(2, "Label 2", 0.952941179, 0.764705896, 0); - m_labelSet1->AddLabel(label, false); + auto label2 = GenerateLabel(2, "Label 2", 0.952941179, 0.764705896, 0); + m_labelSet1 = { label, label2 }; - m_labelSet2 = mitk::LabelSet::New(); label = GenerateLabel(1, "Label 3", 0.552941203, 0.713725507, 0); - m_labelSet2->AddLabel(label, false); - label = GenerateLabel(2, "Label 4", 0.631372571, 0.792156875, 0.945098042); - m_labelSet2->AddLabel(label, false); - label = GenerateLabel(3, "Label 5", 0.639215708, 0.250980407, 0.725490212); - m_labelSet2->AddLabel(label, false); + label2 = GenerateLabel(2, "Label 4", 0.631372571, 0.792156875, 0.945098042); + auto label3 = GenerateLabel(3, "Label 5", 0.639215708, 0.250980407, 0.725490212); + m_labelSet2 = { label, label2, label3 }; - m_labelSet2_adapted = mitk::LabelSet::New(); label = GenerateLabel(3, "Label 3", 0.552941203, 0.713725507, 0); - m_labelSet2_adapted->AddLabel(label, false); - label = GenerateLabel(4, "Label 4", 0.631372571, 0.792156875, 0.945098042); - m_labelSet2_adapted->AddLabel(label, false); - label = GenerateLabel(5, "Label 5", 0.639215708, 0.250980407, 0.725490212); - m_labelSet2_adapted->AddLabel(label, false); - m_labelSet2_adapted->SetLayer(1); + label2 = GenerateLabel(4, "Label 4", 0.631372571, 0.792156875, 0.945098042); + label3 = GenerateLabel(5, "Label 5", 0.639215708, 0.250980407, 0.725490212); + m_labelSet2_adapted = { label, label2, label3 }; } void tearDown() override { - m_labelSet1 = nullptr; - m_labelSet2 = nullptr; - m_labelSet2_adapted = nullptr; + m_labelSet1.clear(); + m_labelSet2.clear(); + m_labelSet2_adapted.clear(); } void TestRead3DLabelSetImage_Default() { auto testImages = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LegacyLabelSetTestImage3D.nrrd")); CPPUNIT_ASSERT_MESSAGE("Error reading label set image", testImages.size()==1); auto lsimage1 = dynamic_cast(testImages[0].GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of layers is not correct", lsimage1->GetNumberOfLayers() == 2); - CPPUNIT_ASSERT_MESSAGE("Error layer 0 is not equal", mitk::Equal(*m_labelSet1, *(lsimage1->GetLabelSet(0)), mitk::eps, true)); - CPPUNIT_ASSERT_MESSAGE("Error layer 1 is not equal", mitk::Equal(*m_labelSet2_adapted, *(lsimage1->GetLabelSet(1)), mitk::eps, true)); + + auto loadedLabels = lsimage1->GetConstLabelsByValue(lsimage1->GetLabelValuesByGroup(0)); + CPPUNIT_ASSERT_MESSAGE("Error layer 0 is not equal", mitk::Equal(m_labelSet1, loadedLabels, mitk::eps, true)); + + loadedLabels = lsimage1->GetConstLabelsByValue(lsimage1->GetLabelValuesByGroup(1)); + CPPUNIT_ASSERT_MESSAGE("Error layer 1 is not equal", mitk::Equal(m_labelSet2_adapted, loadedLabels, mitk::eps, true)); CPPUNIT_ASSERT_MESSAGE("Error, read image has different UID", "c236532b-f95a-4f22-a4c6-7abe4e41ad10"== lsimage1->GetUID()); } void TestRead3DLabelSetImage_Adapt() { mitk::IFileReader::Options options = { {"Multi layer handling", us::Any(std::string("Adapt label values"))} }; auto testImages = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LegacyLabelSetTestImage3D.nrrd"), options); CPPUNIT_ASSERT_MESSAGE("Error reading label set image", testImages.size() == 1); auto lsimage1 = dynamic_cast(testImages[0].GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of layers is not correct", lsimage1->GetNumberOfLayers() == 2); - CPPUNIT_ASSERT_MESSAGE("Error layer 0 is not equal", mitk::Equal(*m_labelSet1, *(lsimage1->GetLabelSet(0)), mitk::eps, true)); - CPPUNIT_ASSERT_MESSAGE("Error layer 1 is not equal", mitk::Equal(*m_labelSet2_adapted, *(lsimage1->GetLabelSet(1)), mitk::eps, true)); + + auto loadedLabels = lsimage1->GetConstLabelsByValue(lsimage1->GetLabelValuesByGroup(0)); + CPPUNIT_ASSERT_MESSAGE("Error layer 0 is not equal", mitk::Equal(m_labelSet1, loadedLabels, mitk::eps, true)); + + loadedLabels = lsimage1->GetConstLabelsByValue(lsimage1->GetLabelValuesByGroup(1)); + CPPUNIT_ASSERT_MESSAGE("Error layer 1 is not equal", mitk::Equal(m_labelSet2_adapted, loadedLabels, mitk::eps, true)); CPPUNIT_ASSERT_MESSAGE("Error, read image has different UID", "c236532b-f95a-4f22-a4c6-7abe4e41ad10" == lsimage1->GetUID()); } void TestRead3DLabelSetImage_Split() { mitk::IFileReader::Options options = { {"Multi layer handling", us::Any(std::string("Split layers"))} }; auto testImages = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LegacyLabelSetTestImage3D.nrrd"), options); CPPUNIT_ASSERT_MESSAGE("Error reading label set image", testImages.size() == 2); auto lsimage1 = dynamic_cast(testImages[0].GetPointer()); auto lsimage2 = dynamic_cast(testImages[1].GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of layers in image 1 isnot correct", lsimage1->GetNumberOfLayers() == 1); CPPUNIT_ASSERT_MESSAGE("Number of layers in image 2 is not correct", lsimage2->GetNumberOfLayers() == 1); - CPPUNIT_ASSERT_MESSAGE("Error layer 0 is not equal", mitk::Equal(*m_labelSet1, *(lsimage1->GetLabelSet(0)), mitk::eps, true)); - CPPUNIT_ASSERT_MESSAGE("Error layer 1 is not equal", mitk::Equal(*m_labelSet2, *(lsimage2->GetLabelSet(0)), mitk::eps, true)); + + auto loadedLabels = lsimage1->GetConstLabelsByValue(lsimage1->GetLabelValuesByGroup(0)); + CPPUNIT_ASSERT_MESSAGE("Error layer 0 is not equal", mitk::Equal(m_labelSet1, loadedLabels, mitk::eps, true)); + + loadedLabels = lsimage2->GetConstLabelsByValue(lsimage2->GetLabelValuesByGroup(0)); + CPPUNIT_ASSERT_MESSAGE("Error layer 1 is not equal", mitk::Equal(m_labelSet2, loadedLabels, mitk::eps, true)); CPPUNIT_ASSERT_MESSAGE("Error, read image has same UID", "c236532b-f95a-4f22-a4c6-7abe4e41ad10" != lsimage1->GetUID()); } void TestRead3DplusTLabelSetImage() { } }; MITK_TEST_SUITE_REGISTRATION(mitkLegacyLabelSetImageIO) diff --git a/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp b/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp index d7a1a54de9..ad1fcb89d7 100644 --- a/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp +++ b/Modules/Multilabel/autoload/DICOMSegIO/mitkDICOMSegmentationIO.cpp @@ -1,699 +1,694 @@ /*============================================================================ 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 __mitkDICOMSegmentationIO__cpp #define __mitkDICOMSegmentationIO__cpp #include "mitkDICOMSegmentationIO.h" #include "mitkDICOMSegIOMimeTypes.h" #include "mitkDICOMSegmentationConstants.h" #include #include #include #include #include #include #include #include // itk #include // dcmqi #include // us #include #include namespace mitk { DICOMSegmentationIO::DICOMSegmentationIO() : AbstractFileIO(LabelSetImage::GetStaticNameOfClass(), mitk::MitkDICOMSEGIOMimeTypes::DICOMSEG_MIMETYPE_NAME(), "DICOM Segmentation") { AbstractFileWriter::SetRanking(10); AbstractFileReader::SetRanking(10); this->RegisterService(); } std::vector DICOMSegmentationIO::GetDICOMTagsOfInterest() { std::vector result; result.emplace_back(DICOMSegmentationConstants::SEGMENT_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_MEANING_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENTED_PROPERTY_CATEGORY_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENTED_PROPERTY_TYPE_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENTED_PROPERTY_MODIFIER_SEQUENCE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()); result.emplace_back(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()); return result; } IFileIO::ConfidenceLevel DICOMSegmentationIO::GetWriterConfidenceLevel() const { if (AbstractFileIO::GetWriterConfidenceLevel() == Unsupported) return Unsupported; // Check if the input file is a segmentation const LabelSetImage *input = dynamic_cast(this->GetInput()); if (input) { if ((input->GetDimension() != 3)) { MITK_INFO << "DICOM segmentation writer is tested only with 3D images, sorry."; return Unsupported; } // Check if input file has dicom information for the referenced image (original DICOM image, e.g. CT) Still necessary, see write() mitk::StringLookupTableProperty::Pointer dicomFilesProp = dynamic_cast(input->GetProperty("referenceFiles").GetPointer()); if (dicomFilesProp.IsNotNull()) return Supported; } return Unsupported; } void DICOMSegmentationIO::Write() { ValidateOutputLocation(); mitk::LocaleSwitch localeSwitch("C"); LocalFile localFile(this); const std::string path = localFile.GetFileName(); auto input = dynamic_cast(this->GetInput()); if (input == nullptr) mitkThrow() << "Cannot write non-image data"; // Get DICOM information from referenced image vector> dcmDatasetsSourceImage; std::unique_ptr readFileFormat(new DcmFileFormat()); try { // TODO: Generate dcmdataset witk DICOM tags from property list; ATM the source are the filepaths from the // property list mitk::StringLookupTableProperty::Pointer filesProp = dynamic_cast(input->GetProperty("referenceFiles").GetPointer()); if (filesProp.IsNull()) { mitkThrow() << "No property with dicom file path."; return; } StringLookupTable filesLut = filesProp->GetValue(); const StringLookupTable::LookupTableType &lookUpTableMap = filesLut.GetLookupTable(); for (const auto &it : lookUpTableMap) { const char *fileName = (it.second).c_str(); if (readFileFormat->loadFile(fileName, EXS_Unknown).good()) { std::unique_ptr readDCMDataset(readFileFormat->getAndRemoveDataset()); dcmDatasetsSourceImage.push_back(std::move(readDCMDataset)); } } } catch (const std::exception &e) { MITK_ERROR << "An error occurred while getting the dicom informations: " << e.what() << endl; return; } // Iterate over all layers. For each a dcm file will be generated for (unsigned int layer = 0; layer < input->GetNumberOfLayers(); ++layer) { vector segmentations; try { - // Hack: Remove the const attribute to switch between the layer images. Normally you could get the different - // layer images by input->GetLayerImage(layer) - mitk::LabelSetImage *mitkLayerImage = const_cast(input); - mitkLayerImage->SetActiveLayer(layer); + auto mitkLayerImage = input->GetGroupImage(layer); // Cast mitk layer image to itk ImageToItk::Pointer imageToItkFilter = ImageToItk::New(); imageToItkFilter->SetInput(mitkLayerImage); // Cast from original itk type to dcmqi input itk image type typedef itk::CastImageFilter castItkImageFilterType; castItkImageFilterType::Pointer castFilter = castItkImageFilterType::New(); castFilter->SetInput(imageToItkFilter->GetOutput()); castFilter->Update(); itkInternalImageType::Pointer itkLabelImage = castFilter->GetOutput(); itkLabelImage->DisconnectPipeline(); // Iterate over all labels. For each label a segmentation image will be created - const LabelSet *labelSet = input->GetLabelSet(layer); + auto labelSet = input->GetConstLabelsByValue(input->GetLabelValuesByGroup(layer)); - for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + for (const auto& label : labelSet) { // Thresold over the image with the given label value itk::ThresholdImageFilter::Pointer thresholdFilter = itk::ThresholdImageFilter::New(); thresholdFilter->SetInput(itkLabelImage); - thresholdFilter->ThresholdOutside(labelIter->first, labelIter->first); + thresholdFilter->ThresholdOutside(label->GetValue(), label->GetValue()); thresholdFilter->SetOutsideValue(0); thresholdFilter->Update(); itkInternalImageType::Pointer segmentImage = thresholdFilter->GetOutput(); segmentImage->DisconnectPipeline(); segmentations.push_back(segmentImage); } } catch (const itk::ExceptionObject &e) { MITK_ERROR << e.GetDescription() << endl; return; } // Create segmentation meta information const std::string tmpMetaInfoFile = this->CreateMetaDataJsonFile(layer); MITK_INFO << "Writing image: " << path << std::endl; try { //TODO is there a better way? Interface expects a vector of raw pointer. vector rawVecDataset; for (const auto& dcmDataSet : dcmDatasetsSourceImage) rawVecDataset.push_back(dcmDataSet.get()); // Convert itk segmentation images to dicom image std::unique_ptr converter = std::make_unique(); std::unique_ptr result(converter->itkimage2dcmSegmentation(rawVecDataset, segmentations, tmpMetaInfoFile, false)); // Write dicom file DcmFileFormat dcmFileFormat(result.get()); std::string filePath = path.substr(0, path.find_last_of(".")); // If there is more than one layer, we have to write more than 1 dicom file if (input->GetNumberOfLayers() != 1) filePath = filePath + std::to_string(layer) + ".dcm"; else filePath = filePath + ".dcm"; dcmFileFormat.saveFile(filePath.c_str(), EXS_LittleEndianExplicit); } catch (const std::exception &e) { MITK_ERROR << "An error occurred during writing the DICOM Seg: " << e.what() << endl; return; } } // Write a dcm file for the next layer } IFileIO::ConfidenceLevel DICOMSegmentationIO::GetReaderConfidenceLevel() const { if (AbstractFileIO::GetReaderConfidenceLevel() == Unsupported) return Unsupported; const std::string fileName = this->GetLocalFileName(); DcmFileFormat dcmFileFormat; OFCondition status = dcmFileFormat.loadFile(fileName.c_str()); if (status.bad()) return Unsupported; OFString modality; if (dcmFileFormat.getDataset()->findAndGetOFString(DCM_Modality, modality).good()) { if (modality.compare("SEG") == 0) return Supported; else return Unsupported; } return Unsupported; } std::vector DICOMSegmentationIO::DoRead() { mitk::LocaleSwitch localeSwitch("C"); LabelSetImage::Pointer labelSetImage; std::vector result; const std::string path = this->GetLocalFileName(); MITK_INFO << "loading " << path << std::endl; if (path.empty()) mitkThrow() << "Empty filename in mitk::ItkImageIO "; try { // Get the dcm data set from file path DcmFileFormat dcmFileFormat; OFCondition status = dcmFileFormat.loadFile(path.c_str()); if (status.bad()) mitkThrow() << "Can't read the input file!"; DcmDataset *dataSet = dcmFileFormat.getDataset(); if (dataSet == nullptr) mitkThrow() << "Can't read data from input file!"; //=============================== dcmqi part ==================================== // Read the DICOM SEG images (segItkImages) and DICOM tags (metaInfo) std::unique_ptr converter = std::make_unique(); pair, string> dcmqiOutput = converter->dcmSegmentation2itkimage(dataSet); map segItkImages = dcmqiOutput.first; dcmqi::JSONSegmentationMetaInformationHandler metaInfo(dcmqiOutput.second.c_str()); metaInfo.read(); MITK_INFO << "Input " << metaInfo.getJSONOutputAsString(); //=============================================================================== // Get the label information from segment attributes for each itk image vector>::const_iterator segmentIter = metaInfo.segmentsAttributesMappingList.begin(); // For each itk image add a layer to the LabelSetImage output for (auto &element : segItkImages) { // Get the labeled image and cast it to mitkImage typedef itk::CastImageFilter castItkImageFilterType; castItkImageFilterType::Pointer castFilter = castItkImageFilterType::New(); castFilter->SetInput(element.second); castFilter->Update(); Image::Pointer layerImage; CastToMitkImage(castFilter->GetOutput(), layerImage); // Get pixel value of the label itkInternalImageType::ValueType segValue = 1; typedef itk::ImageRegionIterator IteratorType; // Iterate over the image to find the pixel value of the label IteratorType iter(element.second, element.second->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { itkInputImageType::PixelType value = iter.Get(); - if (value != LabelSetImage::UnlabeledValue) + if (value != LabelSetImage::UNLABELED_VALUE) { segValue = value; break; } ++iter; } // Get Segment information map map segmentMap = (*segmentIter); map::const_iterator segmentMapIter = (*segmentIter).begin(); dcmqi::SegmentAttributes *segmentAttribute = (*segmentMapIter).second; OFString labelName; if (segmentAttribute->getSegmentedPropertyTypeCodeSequence() != nullptr) { segmentAttribute->getSegmentedPropertyTypeCodeSequence()->getCodeMeaning(labelName); if (segmentAttribute->getSegmentedPropertyTypeModifierCodeSequence() != nullptr) { OFString modifier; segmentAttribute->getSegmentedPropertyTypeModifierCodeSequence()->getCodeMeaning(modifier); labelName.append(" (").append(modifier).append(")"); } } else { labelName = std::to_string(segmentAttribute->getLabelID()).c_str(); if (labelName.empty()) labelName = "Unnamed"; } float tmp[3] = { 0.0, 0.0, 0.0 }; if (segmentAttribute->getRecommendedDisplayRGBValue() != nullptr) { tmp[0] = segmentAttribute->getRecommendedDisplayRGBValue()[0] / 255.0; tmp[1] = segmentAttribute->getRecommendedDisplayRGBValue()[1] / 255.0; tmp[2] = segmentAttribute->getRecommendedDisplayRGBValue()[2] / 255.0; } Label *newLabel = nullptr; // If labelSetImage do not exists (first image) if (labelSetImage.IsNull()) { // Initialize the labelSetImage with the read image labelSetImage = LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(layerImage); // Already a label was generated, so set the information to this - newLabel = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer()); + newLabel = labelSetImage->GetActiveLabel(); newLabel->SetName(labelName.c_str()); newLabel->SetColor(Color(tmp)); newLabel->SetValue(segValue); } else { // Add a new layer to the labelSetImage. Background label is set automatically labelSetImage->AddLayer(layerImage); // Add new label newLabel = new Label; newLabel->SetName(labelName.c_str()); newLabel->SetColor(Color(tmp)); newLabel->SetValue(segValue); - labelSetImage->GetLabelSet(labelSetImage->GetActiveLayer())->AddLabel(newLabel); + labelSetImage->AddLabel(newLabel, labelSetImage->GetActiveLayer()); } // Add some more label properties this->SetLabelProperties(newLabel, segmentAttribute); ++segmentIter; } - labelSetImage->GetLabelSet()->SetAllLabelsVisible(true); + labelSetImage->SetAllLabelsVisible(true); // Add some general DICOM Segmentation properties mitk::IDICOMTagsOfInterest *toiSrv = DICOMIOHelper::GetTagsOfInterestService(); auto tagsOfInterest = toiSrv->GetTagsOfInterest(); DICOMTagPathList tagsOfInterestList; for (const auto &tag : tagsOfInterest) { tagsOfInterestList.push_back(tag.first); } mitk::DICOMDCMTKTagScanner::Pointer scanner = mitk::DICOMDCMTKTagScanner::New(); scanner->SetInputFiles({ GetInputLocation() }); scanner->AddTagPaths(tagsOfInterestList); scanner->Scan(); mitk::DICOMDatasetAccessingImageFrameList frames = scanner->GetFrameInfoList(); if (frames.empty()) { MITK_ERROR << "Error reading the DICOM Seg file" << std::endl; return result; } auto findings = DICOMIOHelper::ExtractPathsOfInterest(tagsOfInterestList, frames); DICOMIOHelper::SetProperties(labelSetImage, findings); // Set active layer to the first layer of the labelset image if (labelSetImage->GetNumberOfLayers() > 1 && labelSetImage->GetActiveLayer() != 0) labelSetImage->SetActiveLayer(0); } catch (const std::exception &e) { MITK_ERROR << "An error occurred while reading the DICOM Seg file: " << e.what(); return result; } catch (...) { MITK_ERROR << "An error occurred in dcmqi while reading the DICOM Seg file"; return result; } result.push_back(labelSetImage.GetPointer()); return result; } const std::string mitk::DICOMSegmentationIO::CreateMetaDataJsonFile(int layer) { const mitk::LabelSetImage *image = dynamic_cast(this->GetInput()); const std::string output; dcmqi::JSONSegmentationMetaInformationHandler handler; // 1. Metadata attributes that will be listed in the resulting DICOM SEG object std::string contentCreatorName; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0070, 0x0084).c_str(), contentCreatorName)) contentCreatorName = "MITK"; handler.setContentCreatorName(contentCreatorName); std::string clinicalTrailSeriesId; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0071).c_str(), clinicalTrailSeriesId)) clinicalTrailSeriesId = "Session 1"; handler.setClinicalTrialSeriesID(clinicalTrailSeriesId); std::string clinicalTrialTimePointID; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0050).c_str(), clinicalTrialTimePointID)) clinicalTrialTimePointID = "0"; handler.setClinicalTrialTimePointID(clinicalTrialTimePointID); std::string clinicalTrialCoordinatingCenterName = ""; if (!image->GetPropertyList()->GetStringProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0060).c_str(), clinicalTrialCoordinatingCenterName)) clinicalTrialCoordinatingCenterName = "Unknown"; handler.setClinicalTrialCoordinatingCenterName(clinicalTrialCoordinatingCenterName); std::string seriesDescription; if (!image->GetPropertyList()->GetStringProperty("name", seriesDescription)) seriesDescription = "MITK Segmentation"; handler.setSeriesDescription(seriesDescription); handler.setSeriesNumber("0" + std::to_string(layer)); handler.setInstanceNumber("1"); handler.setBodyPartExamined(""); - const LabelSet *labelSet = image->GetLabelSet(layer); + auto labelSet = image->GetConstLabelsByValue(image->GetLabelValuesByGroup(layer)); - for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + for (const auto& label : labelSet) { - const Label *label = labelIter->second; - if (label != nullptr) { TemporoSpatialStringProperty *segmentNumberProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()).c_str())); TemporoSpatialStringProperty *segmentLabelProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()).c_str())); TemporoSpatialStringProperty *algorithmTypeProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()).c_str())); TemporoSpatialStringProperty *segmentCategoryCodeValueProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()).c_str())); TemporoSpatialStringProperty *segmentCategoryCodeSchemeProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()).c_str())); TemporoSpatialStringProperty *segmentCategoryCodeMeaningProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()).c_str())); TemporoSpatialStringProperty *segmentTypeCodeValueProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()).c_str())); TemporoSpatialStringProperty *segmentTypeCodeSchemeProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()).c_str())); TemporoSpatialStringProperty *segmentTypeCodeMeaningProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()).c_str())); TemporoSpatialStringProperty *segmentModifierCodeValueProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()).c_str())); TemporoSpatialStringProperty *segmentModifierCodeSchemeProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()).c_str())); TemporoSpatialStringProperty *segmentModifierCodeMeaningProp = dynamic_cast(label->GetProperty( mitk::DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()).c_str())); dcmqi::SegmentAttributes *segmentAttribute = nullptr; if (segmentNumberProp->GetValue() == "") { MITK_ERROR << "Something went wrong with the label ID."; } else { int labelId = std::stoi(segmentNumberProp->GetValue()); segmentAttribute = handler.createAndGetNewSegment(labelId); } if (segmentAttribute != nullptr) { segmentAttribute->setSegmentLabel(segmentLabelProp->GetValueAsString()); segmentAttribute->setSegmentDescription(segmentLabelProp->GetValueAsString()); segmentAttribute->setSegmentAlgorithmType(algorithmTypeProp->GetValueAsString()); segmentAttribute->setSegmentAlgorithmName("MITK Segmentation"); if (segmentCategoryCodeValueProp != nullptr && segmentCategoryCodeSchemeProp != nullptr && segmentCategoryCodeMeaningProp != nullptr) segmentAttribute->setSegmentedPropertyCategoryCodeSequence( segmentCategoryCodeValueProp->GetValueAsString(), segmentCategoryCodeSchemeProp->GetValueAsString(), segmentCategoryCodeMeaningProp->GetValueAsString()); else // some default values segmentAttribute->setSegmentedPropertyCategoryCodeSequence( "M-01000", "SRT", "Morphologically Altered Structure"); if (segmentTypeCodeValueProp != nullptr && segmentTypeCodeSchemeProp != nullptr && segmentTypeCodeMeaningProp != nullptr) { segmentAttribute->setSegmentedPropertyTypeCodeSequence(segmentTypeCodeValueProp->GetValueAsString(), segmentTypeCodeSchemeProp->GetValueAsString(), segmentTypeCodeMeaningProp->GetValueAsString()); handler.setBodyPartExamined(segmentTypeCodeMeaningProp->GetValueAsString()); } else { // some default values segmentAttribute->setSegmentedPropertyTypeCodeSequence("M-03000", "SRT", "Mass"); handler.setBodyPartExamined("Mass"); } if (segmentModifierCodeValueProp != nullptr && segmentModifierCodeSchemeProp != nullptr && segmentModifierCodeMeaningProp != nullptr) segmentAttribute->setSegmentedPropertyTypeModifierCodeSequence( segmentModifierCodeValueProp->GetValueAsString(), segmentModifierCodeSchemeProp->GetValueAsString(), segmentModifierCodeMeaningProp->GetValueAsString()); Color color = label->GetColor(); segmentAttribute->setRecommendedDisplayRGBValue(color[0] * 255, color[1] * 255, color[2] * 255); } } } return handler.getJSONOutputAsString(); } void mitk::DICOMSegmentationIO::SetLabelProperties(mitk::Label *label, dcmqi::SegmentAttributes *segmentAttribute) { // Segment Number:Identification number of the segment.The value of Segment Number(0062, 0004) shall be unique // within the Segmentation instance in which it is created label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()).c_str(), TemporoSpatialStringProperty::New(std::to_string(label->GetValue()))); // Segment Label: User-defined label identifying this segment. label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()).c_str(), TemporoSpatialStringProperty::New(label->GetName())); // Segment Algorithm Type: Type of algorithm used to generate the segment. if (!segmentAttribute->getSegmentAlgorithmType().empty()) label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()).c_str(), TemporoSpatialStringProperty::New(segmentAttribute->getSegmentAlgorithmType())); // Add Segmented Property Category Code Sequence tags auto categoryCodeSequence = segmentAttribute->getSegmentedPropertyCategoryCodeSequence(); if (categoryCodeSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value categoryCodeSequence->getCodeValue(codeValue); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator categoryCodeSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning categoryCodeSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } // Add Segmented Property Type Code Sequence tags auto typeCodeSequence = segmentAttribute->getSegmentedPropertyTypeCodeSequence(); if (typeCodeSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value typeCodeSequence->getCodeValue(codeValue); label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator typeCodeSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning typeCodeSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } // Add Segmented Property Type Modifier Code Sequence tags auto modifierCodeSequence = segmentAttribute->getSegmentedPropertyTypeModifierCodeSequence(); if (modifierCodeSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value modifierCodeSequence->getCodeValue(codeValue); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator modifierCodeSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning modifierCodeSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } // Add Atomic RegionSequence tags auto atomicRegionSequence = segmentAttribute->getAnatomicRegionSequence(); if (atomicRegionSequence != nullptr) { OFString codeValue; // (0008,0100) Code Value atomicRegionSequence->getCodeValue(codeValue); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(codeValue.c_str())); OFString codeScheme; // (0008,0102) Coding Scheme Designator atomicRegionSequence->getCodingSchemeDesignator(codeScheme); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(codeScheme.c_str())); OFString codeMeaning; // (0008,0104) Code Meaning atomicRegionSequence->getCodeMeaning(codeMeaning); label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::ANATOMIC_REGION_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(codeMeaning.c_str())); } } DICOMSegmentationIO *DICOMSegmentationIO::IOClone() const { return new DICOMSegmentationIO(*this); } } // namespace #endif //__mitkDICOMSegmentationIO__cpp diff --git a/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp index 342097f104..4ca16f7ed5 100644 --- a/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkLegacyLabelSetImageIO.cpp @@ -1,272 +1,273 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef __mitkLabelSetImageWriter__cpp #define __mitkLabelSetImageWriter__cpp #include "mitkLegacyLabelSetImageIO.h" #include "mitkBasePropertySerializer.h" #include "mitkMultilabelIOMimeTypes.h" #include "mitkImageAccessByItk.h" #include "mitkMultiLabelIOHelper.h" #include "mitkLabelSetImageConverter.h" #include #include #include #include #include #include // itk #include "itkImageFileReader.h" #include "itkImageFileWriter.h" #include "itkMetaDataDictionary.h" #include "itkMetaDataObject.h" #include "itkNrrdImageIO.h" #include namespace mitk { const constexpr char* const OPTION_NAME_MULTI_LAYER = "Multi layer handling"; const constexpr char* const OPTION_NAME_MULTI_LAYER_ADAPT = "Adapt label values"; const constexpr char* const OPTION_NAME_MULTI_LAYER_SPLIT = "Split layers"; LegacyLabelSetImageIO::LegacyLabelSetImageIO() : AbstractFileReader(MitkMultilabelIOMimeTypes::LEGACYLABELSET_MIMETYPE(), "MITK LabelSetImage (legacy)") { this->InitializeDefaultMetaDataKeys(); AbstractFileReader::SetRanking(10); IFileIO::Options options; std::vector multiLayerStrategy; multiLayerStrategy.push_back(OPTION_NAME_MULTI_LAYER_ADAPT); multiLayerStrategy.push_back(OPTION_NAME_MULTI_LAYER_SPLIT); options[OPTION_NAME_MULTI_LAYER] = multiLayerStrategy; this->SetDefaultOptions(options); this->RegisterService(); } IFileIO::ConfidenceLevel LegacyLabelSetImageIO::GetConfidenceLevel() const { if (AbstractFileReader::GetConfidenceLevel() == Unsupported) return Unsupported; const std::string fileName = this->GetLocalFileName(); itk::NrrdImageIO::Pointer io = itk::NrrdImageIO::New(); io->SetFileName(fileName); io->ReadImageInformation(); itk::MetaDataDictionary imgMetaDataDictionary = io->GetMetaDataDictionary(); std::string value(""); itk::ExposeMetaData(imgMetaDataDictionary, "modality", value); if (value.compare("org.mitk.image.multilabel") == 0) { return Supported; } else return Unsupported; } - std::vector ExtractLabelSetsFromMetaData(const itk::MetaDataDictionary& dictionary) + std::vector ExtractLabelSetsFromMetaData(const itk::MetaDataDictionary& dictionary) { - std::vector result; + std::vector result; // get labels and add them as properties to the image char keybuffer[256]; unsigned int numberOfLayers = MultiLabelIOHelper::GetIntByKey(dictionary, "layers"); std::string _xmlStr; mitk::Label::Pointer label; for (unsigned int layerIdx = 0; layerIdx < numberOfLayers; layerIdx++) { sprintf(keybuffer, "layer_%03u", layerIdx); int numberOfLabels = MultiLabelIOHelper::GetIntByKey(dictionary, keybuffer); - mitk::LabelSet::Pointer labelSet = mitk::LabelSet::New(); + mitk::LabelSetImage::LabelVectorType labelSet; for (int labelIdx = 0; labelIdx < numberOfLabels; labelIdx++) { tinyxml2::XMLDocument doc; sprintf(keybuffer, "label_%03u_%05d", layerIdx, labelIdx); _xmlStr = MultiLabelIOHelper::GetStringByKey(dictionary, keybuffer); doc.Parse(_xmlStr.c_str(), _xmlStr.size()); auto* labelElem = doc.FirstChildElement("Label"); if (labelElem == nullptr) mitkThrow() << "Error parsing NRRD header for mitk::LabelSetImage IO"; label = mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(labelElem); - if (label->GetValue() != mitk::LabelSetImage::UnlabeledValue) + if (label->GetValue() != mitk::LabelSetImage::UNLABELED_VALUE) { - labelSet->AddLabel(label); - labelSet->SetLayer(layerIdx); + labelSet.push_back(label); } else { MITK_INFO << "Multi label image contains a label specification for unlabeled pixels. This legacy information is ignored."; } } result.push_back(labelSet); } return result; } std::vector LegacyLabelSetImageIO::DoRead() { itk::NrrdImageIO::Pointer nrrdImageIO = itk::NrrdImageIO::New(); std::vector result; auto rawimage = ItkImageIO::LoadRawMitkImageFromImageIO(nrrdImageIO, this->GetLocalFileName()); const itk::MetaDataDictionary& dictionary = nrrdImageIO->GetMetaDataDictionary(); std::vector groupImages = { rawimage }; if (rawimage->GetChannelDescriptor().GetPixelType().GetPixelType() == itk::IOPixelEnum::VECTOR) { groupImages = SplitVectorImage(rawimage); } auto labelsets = ExtractLabelSetsFromMetaData(dictionary); if (labelsets.size() != groupImages.size()) { mitkThrow() << "Loaded data is in an invalid state. Number of extracted layer images and labels sets does not match. Found layer images: " << groupImages.size() << "; found labelsets: " << labelsets.size(); } auto props = ItkImageIO::ExtractMetaDataAsPropertyList(nrrdImageIO->GetMetaDataDictionary(), this->GetMimeType()->GetName(), this->m_DefaultMetaDataKeys); const Options userOptions = this->GetOptions(); const auto multiLayerStrategy = userOptions.find(OPTION_NAME_MULTI_LAYER)->second.ToString(); if (multiLayerStrategy == OPTION_NAME_MULTI_LAYER_SPLIT) { //just split layers in different multi label images auto labelSetIterator = labelsets.begin(); for (auto image : groupImages) { auto output = ConvertImageToLabelSetImage(image); - output->AddLabelSetToLayer(0, *labelSetIterator); - output->GetLabelSet(0)->SetLayer(0); + output->ReplaceGroupLabels(0, *labelSetIterator); //meta data handling for (auto& [name, prop] : *(props->GetMap())) { output->SetProperty(name, prop->Clone()); //need to clone to avoid that all outputs pointing to the same prop instances. } // Handle UID //Remark if we split the legacy label set into distinct layer images, the outputs should have new IDs. So we don't get the old one. result.push_back(output.GetPointer()); labelSetIterator++; } } else { //Avoid label id collision. - LabelSetImage::LabelValueType maxValue = LabelSetImage::UnlabeledValue; + LabelSetImage::LabelValueType maxValue = LabelSetImage::UNLABELED_VALUE; auto imageIterator = groupImages.begin(); - std::vector adaptedLabelSets; + std::vector adaptedLabelSets; for (auto labelset : labelsets) { - const auto setValues = labelset->GetUsedLabelValues(); + const auto setValues = LabelSetImage::ExtractLabelValuesFromLabelVector(labelset); //generate mapping table; std::vector > labelMapping; for (auto vIter = setValues.crbegin(); vIter != setValues.crend(); vIter++) { //have to use reverse loop because TransferLabelContent (used to adapt content in the same image; see below) //would potentially corrupt otherwise the content due to "value collision between old values still present //and already adapted values. By going from highest value to lowest, we avoid that. - if (LabelSetImage::UnlabeledValue != *vIter) - labelMapping.push_back({ *vIter, *vIter + maxValue }); + if (LabelSetImage::UNLABELED_VALUE != *vIter) + labelMapping.push_back({*vIter, *vIter + maxValue}); } - - if (LabelSetImage::UnlabeledValue != maxValue) + if (LabelSetImage::UNLABELED_VALUE != maxValue) { //adapt labelset - auto mappedLabelSet = GenerateLabelSetWithMappedValues(labelset, labelMapping); + auto mappedLabelSet = GenerateLabelSetWithMappedValues(LabelSetImage::ConvertLabelVectorConst(labelset), labelMapping); adaptedLabelSets.emplace_back(mappedLabelSet); //adapt image (it is an inplace operation. the image instance stays the same. - TransferLabelContent(*imageIterator, *imageIterator, mappedLabelSet, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, + TransferLabelContent(*imageIterator, *imageIterator, LabelSetImage::ConvertLabelVectorConst(mappedLabelSet), LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, false, labelMapping, MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); } else { adaptedLabelSets.emplace_back(labelset); } - const auto setMaxValue = *(std::max_element(setValues.begin(), setValues.end())); - maxValue += setMaxValue; + const auto maxFinding = std::max_element(setValues.begin(), setValues.end()); + if (maxFinding != setValues.end()) + { + const auto setMaxValue = *(maxFinding); + maxValue += setMaxValue; + } imageIterator++; } auto output = ConvertImageVectorToLabelSetImage(groupImages, rawimage->GetTimeGeometry()); LabelSetImage::GroupIndexType id = 0; for (auto labelset : adaptedLabelSets) { - output->AddLabelSetToLayer(id, labelset); + output->ReplaceGroupLabels(id, labelset); id++; } //meta data handling for (auto& [name, prop] : *(props->GetMap())) { output->SetProperty(name, prop->Clone()); //need to clone to avoid that all outputs pointing to the same prop instances. } // Handle UID if (dictionary.HasKey(PROPERTY_KEY_UID)) { itk::MetaDataObject::ConstPointer uidData = dynamic_cast*>(dictionary.Get(PROPERTY_KEY_UID)); if (uidData.IsNotNull()) { mitk::UIDManipulator uidManipulator(output); uidManipulator.SetUID(uidData->GetMetaDataObjectValue()); } } result.push_back(output.GetPointer()); } MITK_INFO << "...finished!"; return result; } LegacyLabelSetImageIO *LegacyLabelSetImageIO::Clone() const { return new LegacyLabelSetImageIO(*this); } void LegacyLabelSetImageIO::InitializeDefaultMetaDataKeys() { this->m_DefaultMetaDataKeys.push_back("NRRD.space"); this->m_DefaultMetaDataKeys.push_back("NRRD.kinds"); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TYPE); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS); this->m_DefaultMetaDataKeys.push_back("ITK.InputFilterName"); this->m_DefaultMetaDataKeys.push_back("label."); this->m_DefaultMetaDataKeys.push_back("layer."); this->m_DefaultMetaDataKeys.push_back("layers"); this->m_DefaultMetaDataKeys.push_back("modality"); this->m_DefaultMetaDataKeys.push_back("org.mitk.label."); this->m_DefaultMetaDataKeys.push_back("MITK.IO."); } } // namespace #endif //__mitkLabelSetImageWriter__cpp diff --git a/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp index b9e9fba8ea..10d48fb82e 100644 --- a/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp +++ b/Modules/Multilabel/autoload/IO/mitkMultiLabelSegmentationIO.cpp @@ -1,224 +1,224 @@ /*============================================================================ 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 "mitkMultiLabelSegmentationIO.h" #include "mitkBasePropertySerializer.h" #include "mitkIOMimeTypes.h" #include "mitkImageAccessByItk.h" #include "mitkMultiLabelIOHelper.h" #include "mitkLabelSetImageConverter.h" #include #include #include #include #include #include // itk #include "itkImageFileReader.h" #include "itkImageFileWriter.h" #include "itkMetaDataDictionary.h" #include "itkMetaDataObject.h" #include "itkNrrdImageIO.h" #include namespace mitk { const constexpr char* const MULTILABEL_SEGMENTATION_MODALITY_KEY = "modality"; const constexpr char* const MULTILABEL_SEGMENTATION_MODALITY_VALUE = "org.mitk.multilabel.segmentation"; const constexpr char* const MULTILABEL_SEGMENTATION_VERSION_KEY = "org.mitk.multilabel.segmentation.version"; const constexpr int MULTILABEL_SEGMENTATION_VERSION_VALUE = 1; const constexpr char* const MULTILABEL_SEGMENTATION_LABELS_INFO_KEY = "org.mitk.multilabel.segmentation.labelgroups"; const constexpr char* const MULTILABEL_SEGMENTATION_UNLABELEDLABEL_LOCK_KEY = "org.mitk.multilabel.segmentation.unlabeledlabellock"; MultiLabelSegmentationIO::MultiLabelSegmentationIO() : AbstractFileIO(LabelSetImage::GetStaticNameOfClass(), IOMimeTypes::NRRD_MIMETYPE(), "MITK Multilabel Segmentation") { this->InitializeDefaultMetaDataKeys(); AbstractFileWriter::SetRanking(10); AbstractFileReader::SetRanking(10); this->RegisterService(); } IFileIO::ConfidenceLevel MultiLabelSegmentationIO::GetWriterConfidenceLevel() const { if (AbstractFileIO::GetWriterConfidenceLevel() == Unsupported) return Unsupported; const auto *input = static_cast(this->GetInput()); if (input) return Supported; else return Unsupported; } void MultiLabelSegmentationIO::Write() { ValidateOutputLocation(); auto input = dynamic_cast(this->GetInput()); mitk::LocaleSwitch localeSwitch("C"); mitk::Image::Pointer inputVector = mitk::ConvertLabelSetImageToImage(input); // image write if (inputVector.IsNull()) { mitkThrow() << "Cannot write non-image data"; } itk::NrrdImageIO::Pointer nrrdImageIo = itk::NrrdImageIO::New(); ItkImageIO::PreparImageIOToWriteImage(nrrdImageIo, inputVector); LocalFile localFile(this); const std::string path = localFile.GetFileName(); MITK_INFO << "Writing image: " << path << std::endl; try { itk::EncapsulateMetaData( nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_MODALITY_KEY), std::string(MULTILABEL_SEGMENTATION_MODALITY_VALUE)); //nrrd does only support string meta information. So we have to convert before. itk::EncapsulateMetaData( nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_VERSION_KEY), std::to_string(MULTILABEL_SEGMENTATION_VERSION_VALUE)); auto json = MultiLabelIOHelper::SerializeMultLabelGroupsToJSON(input); itk::EncapsulateMetaData( nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_LABELS_INFO_KEY), json.dump()); // end label set specific meta data //nrrd does only support string meta information. So we have to convert before. itk::EncapsulateMetaData( nrrdImageIo->GetMetaDataDictionary(), std::string(MULTILABEL_SEGMENTATION_UNLABELEDLABEL_LOCK_KEY), std::to_string(input->GetUnlabeledLabelLock())); // Handle properties ItkImageIO::SavePropertyListAsMetaData(nrrdImageIo->GetMetaDataDictionary(), input->GetPropertyList(), this->GetMimeType()->GetName()); // Handle UID itk::EncapsulateMetaData(nrrdImageIo->GetMetaDataDictionary(), PROPERTY_KEY_UID, input->GetUID()); // use compression if available nrrdImageIo->UseCompressionOn(); nrrdImageIo->SetFileName(path); ImageReadAccessor imageAccess(inputVector); nrrdImageIo->Write(imageAccess.GetData()); } catch (const std::exception &e) { mitkThrow() << e.what(); } } IFileIO::ConfidenceLevel MultiLabelSegmentationIO::GetReaderConfidenceLevel() const { if (AbstractFileIO::GetReaderConfidenceLevel() == Unsupported) return Unsupported; const std::string fileName = this->GetLocalFileName(); itk::NrrdImageIO::Pointer io = itk::NrrdImageIO::New(); io->SetFileName(fileName); io->ReadImageInformation(); itk::MetaDataDictionary imgMetaDataDictionary = io->GetMetaDataDictionary(); std::string value(""); itk::ExposeMetaData(imgMetaDataDictionary, "modality", value); if (value.compare(MULTILABEL_SEGMENTATION_MODALITY_VALUE) == 0) { return Supported; } else return Unsupported; } std::vector MultiLabelSegmentationIO::DoRead() { itk::NrrdImageIO::Pointer nrrdImageIO = itk::NrrdImageIO::New(); std::vector result; auto rawimage = ItkImageIO::LoadRawMitkImageFromImageIO(nrrdImageIO, this->GetLocalFileName()); const itk::MetaDataDictionary& dictionary = nrrdImageIO->GetMetaDataDictionary(); //check version auto version = MultiLabelIOHelper::GetIntByKey(dictionary, MULTILABEL_SEGMENTATION_VERSION_KEY); if (version > MULTILABEL_SEGMENTATION_VERSION_VALUE) { mitkThrow() << "Data to read has unsupported version. Software is to old to ensure correct reading. Please use a compatible version of MITK or store data in another format. Version of data: " << version << "; Supported versions up to: "< labelsets = MultiLabelIOHelper::DeserializeMultiLabelGroupsFromJSON(jlabelsets); + auto labelsets = MultiLabelIOHelper::DeserializeMultiLabelGroupsFromJSON(jlabelsets); if (labelsets.size() != output->GetNumberOfLayers()) { mitkThrow() << "Loaded data is in an invalid state. Number of extracted layer images and labels sets does not match. Found layer images: " << output->GetNumberOfLayers() << "; found labelsets: " << labelsets.size(); } LabelSetImage::GroupIndexType id = 0; for (auto labelset : labelsets) { - output->AddLabelSetToLayer(id, labelset); + output->ReplaceGroupLabels(id, labelset); id++; } bool unlabeledLock = MultiLabelIOHelper::GetIntByKey(dictionary, MULTILABEL_SEGMENTATION_UNLABELEDLABEL_LOCK_KEY) != 0; output->SetUnlabeledLabelLock(unlabeledLock); //meta data handling auto props = ItkImageIO::ExtractMetaDataAsPropertyList(nrrdImageIO->GetMetaDataDictionary(), this->GetMimeType()->GetName(), this->m_DefaultMetaDataKeys); for (auto& [name, prop] : *(props->GetMap())) { output->SetProperty(name, prop->Clone()); //need to clone to avoid that all outputs pointing to the same prop instances. } // Handle UID if (dictionary.HasKey(PROPERTY_KEY_UID)) { itk::MetaDataObject::ConstPointer uidData = dynamic_cast*>(dictionary.Get(PROPERTY_KEY_UID)); if (uidData.IsNotNull()) { mitk::UIDManipulator uidManipulator(output); uidManipulator.SetUID(uidData->GetMetaDataObjectValue()); } } result.push_back(output.GetPointer()); MITK_INFO << "...finished!"; return result; } MultiLabelSegmentationIO *MultiLabelSegmentationIO::IOClone() const { return new MultiLabelSegmentationIO(*this); } void MultiLabelSegmentationIO::InitializeDefaultMetaDataKeys() { this->m_DefaultMetaDataKeys.push_back("NRRD.space"); this->m_DefaultMetaDataKeys.push_back("NRRD.kinds"); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TYPE); this->m_DefaultMetaDataKeys.push_back(PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS); this->m_DefaultMetaDataKeys.push_back("ITK.InputFilterName"); this->m_DefaultMetaDataKeys.push_back("org.mitk.multilabel."); this->m_DefaultMetaDataKeys.push_back("MITK.IO."); this->m_DefaultMetaDataKeys.push_back(MULTILABEL_SEGMENTATION_MODALITY_KEY); } } // namespace diff --git a/Modules/Multilabel/files.cmake b/Modules/Multilabel/files.cmake index 8723b71a3b..950a2a736f 100644 --- a/Modules/Multilabel/files.cmake +++ b/Modules/Multilabel/files.cmake @@ -1,21 +1,21 @@ set(CPP_FILES mitkLabel.cpp - mitkLabelSet.cpp mitkLabelSetImage.cpp mitkLabelSetImageConverter.cpp mitkLabelSetImageSource.cpp mitkLabelSetImageHelper.cpp mitkLabelSetImageSurfaceStampFilter.cpp mitkLabelSetImageToSurfaceFilter.cpp mitkLabelSetImageToSurfaceThreadedFilter.cpp mitkLabelSetImageVtkMapper2D.cpp mitkMultilabelObjectFactory.cpp mitkMultiLabelIOHelper.cpp + mitkMultiLabelEvents.cpp mitkDICOMSegmentationPropertyHelper.cpp mitkDICOMSegmentationConstants.cpp mitkSegmentationTaskList.cpp ) set(RESOURCE_FILES ) diff --git a/Modules/Multilabel/mitkDICOMSegmentationPropertyHelper.cpp b/Modules/Multilabel/mitkDICOMSegmentationPropertyHelper.cpp index 4bfae098b1..1a27fac954 100644 --- a/Modules/Multilabel/mitkDICOMSegmentationPropertyHelper.cpp +++ b/Modules/Multilabel/mitkDICOMSegmentationPropertyHelper.cpp @@ -1,171 +1,164 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include "mitkDICOMSegmentationPropertyHelper.h" #include #include namespace mitk { void DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(LabelSetImage* dicomSegImage) { PropertyList::Pointer propertyList = dicomSegImage->GetPropertyList(); // Add DICOM Tag (0008, 0060) Modality "SEG" propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0008, 0x0060).c_str(), TemporoSpatialStringProperty::New("SEG")); // Add DICOM Tag (0008,103E) Series Description propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0008, 0x103E).c_str(), TemporoSpatialStringProperty::New("MITK Segmentation")); // Add DICOM Tag (0070,0084) Content Creator Name propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0070, 0x0084).c_str(), TemporoSpatialStringProperty::New("MITK")); // Add DICOM Tag (0012, 0071) Clinical Trial Series ID propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0071).c_str(), TemporoSpatialStringProperty::New("Session 1")); // Add DICOM Tag (0012,0050) Clinical Trial Time Point ID propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0050).c_str(), TemporoSpatialStringProperty::New("0")); // Add DICOM Tag (0012, 0060) Clinical Trial Coordinating Center Name propertyList->SetProperty(GeneratePropertyNameForDICOMTag(0x0012, 0x0060).c_str(), TemporoSpatialStringProperty::New("Unknown")); // Set DICOM properties for each label // Iterate over all layers - for (unsigned int layer = 0; layer < dicomSegImage->GetNumberOfLayers(); ++layer) + auto labels = dicomSegImage->GetLabels(); + for (auto label : labels) { - // Iterate over all labels - const LabelSet *labelSet = dicomSegImage->GetLabelSet(layer); - auto labelIter = labelSet->IteratorConstBegin(); - - for (; labelIter != labelSet->IteratorConstEnd(); ++labelIter) - { - Label::Pointer label = labelIter->second; - SetDICOMSegmentProperties(label); - } + SetDICOMSegmentProperties(label); } } void DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(Label *label) { PropertyList::Pointer propertyList = PropertyList::New(); AnatomicalStructureColorPresets::Category category; AnatomicalStructureColorPresets::Type type; auto presets = vtkSmartPointer::New(); presets->LoadPreset(); for (const auto &preset : presets->GetCategoryPresets()) { auto presetOrganName = preset.first; if (label->GetName().compare(presetOrganName) == 0) { category = preset.second; break; } } for (const auto &preset : presets->GetTypePresets()) { auto presetOrganName = preset.first; if (label->GetName().compare(presetOrganName) == 0) { type = preset.second; break; } } //------------------------------------------------------------ // Add Segment Sequence tags (0062, 0002) // Segment Number:Identification number of the segment.The value of Segment Number(0062, 0004) shall be unique // within the Segmentation instance in which it is created label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_NUMBER_PATH()).c_str(), TemporoSpatialStringProperty::New(std::to_string(label->GetValue()))); // Segment Label: User-defined label identifying this segment. label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_LABEL_PATH()).c_str(), TemporoSpatialStringProperty::New(label->GetName())); // Segment Algorithm Type: Type of algorithm used to generate the segment. AUTOMATIC SEMIAUTOMATIC MANUAL label->SetProperty(DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_ALGORITHM_TYPE_PATH()).c_str(), TemporoSpatialStringProperty::New("SEMIAUTOMATIC")); //------------------------------------------------------------ // Add Segmented Property Category Code Sequence tags (0062, 0003): Sequence defining the general category of this // segment. // (0008,0100) Code Value if (!category.codeValue.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(category.codeValue)); // (0008,0102) Coding Scheme Designator if (!category.codeScheme.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(category.codeScheme)); // (0008,0104) Code Meaning if (!category.codeName.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_CATEGORY_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(category.codeName)); //------------------------------------------------------------ // Add Segmented Property Type Code Sequence (0062, 000F): Sequence defining the specific property type of this // segment. // (0008,0100) Code Value if (!type.codeValue.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(type.codeValue)); // (0008,0102) Coding Scheme Designator if (!type.codeScheme.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(type.codeScheme)); // (0008,0104) Code Meaning if (!type.codeName.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_TYPE_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(type.codeName)); //------------------------------------------------------------ // Add Segmented Property Type Modifier Code Sequence (0062,0011): Sequence defining the modifier of the property // type of this segment. // (0008,0100) Code Value if (!type.modifier.codeValue.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_VALUE_PATH()).c_str(), TemporoSpatialStringProperty::New(type.modifier.codeValue)); // (0008,0102) Coding Scheme Designator if (!type.modifier.codeScheme.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_SCHEME_PATH()).c_str(), TemporoSpatialStringProperty::New(type.modifier.codeScheme)); // (0008,0104) Code Meaning if (!type.modifier.codeName.empty()) label->SetProperty( DICOMTagPathToPropertyName(DICOMSegmentationConstants::SEGMENT_MODIFIER_CODE_MEANING_PATH()).c_str(), TemporoSpatialStringProperty::New(type.modifier.codeName)); } } diff --git a/Modules/Multilabel/mitkLabel.h b/Modules/Multilabel/mitkLabel.h index 968452be5d..3a3b963f67 100644 --- a/Modules/Multilabel/mitkLabel.h +++ b/Modules/Multilabel/mitkLabel.h @@ -1,107 +1,113 @@ /*============================================================================ 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 mitkLabel_h #define mitkLabel_h #include "MitkMultilabelExports.h" #include #include #include #include namespace mitk { //## //##Documentation //## @brief A data structure describing a label. //## @ingroup Data //## class MITKMULTILABEL_EXPORT Label : public PropertyList { public: mitkClassMacro(Label, mitk::PropertyList); typedef unsigned short PixelType; itkNewMacro(Self); mitkNewMacro2Param(Self, PixelType, const std::string&); /// The maximum value a label can get: Since the value is of type unsigned short MAX_LABEL_VALUE = 65535 static const PixelType MAX_LABEL_VALUE; + //** Value indicating pixels that are not labeled at all.*/ + const static PixelType UNLABELED_VALUE = 0; + void SetLocked(bool locked); bool GetLocked() const; void SetVisible(bool visible); bool GetVisible() const; void SetOpacity(float opacity); float GetOpacity() const; void SetName(const std::string &name); std::string GetName() const; void SetCenterOfMassIndex(const mitk::Point3D ¢er); mitk::Point3D GetCenterOfMassIndex() const; void SetCenterOfMassCoordinates(const mitk::Point3D ¢er); mitk::Point3D GetCenterOfMassCoordinates() const; void SetColor(const mitk::Color &); const mitk::Color &GetColor() const; void SetValue(PixelType pixelValue); PixelType GetValue() const; void SetLayer(unsigned int layer); unsigned int GetLayer() const; void SetProperty(const std::string &propertyKey, BaseProperty *property, const std::string &contextName = "", bool fallBackOnDefaultContext = false) override; using itk::Object::Modified; void Modified() { Superclass::Modified(); } Label(); Label(PixelType value, const std::string& name); ~Label() override; protected: void PrintSelf(std::ostream &os, itk::Indent indent) const override; Label(const Label &other); private: itk::LightObject::Pointer InternalClone() const override; }; + using LabelVector = std::vector; + using ConstLabelVector = std::vector; + /** * @brief Equal A function comparing two labels for beeing equal in data * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - Lebel equality via Equal-PropetyList * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolarence for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::Label &leftHandSide, const mitk::Label &rightHandSide, ScalarType eps, bool verbose); } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkLabelSet.cpp b/Modules/Multilabel/mitkLabelSet.cpp deleted file mode 100644 index 990790fe81..0000000000 --- a/Modules/Multilabel/mitkLabelSet.cpp +++ /dev/null @@ -1,384 +0,0 @@ -/*============================================================================ - -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 "mitkLabelSet.h" -#include "mitkDICOMSegmentationPropertyHelper.h" - -#include - -mitk::LabelSet::LabelSet() : m_ActiveLabelValue(0), m_Layer(0) -{ - m_LookupTable = mitk::LookupTable::New(); - m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); - m_ReservedLabelValuesFunctor = nullptr; -} - -mitk::LabelSet::~LabelSet() -{ - m_LabelContainer.clear(); -} - -mitk::LabelSet::LabelSet(const LabelSet &other) - : itk::Object(), - m_LookupTable(other.GetLookupTable()->Clone()), - m_ActiveLabelValue(other.m_ActiveLabelValue), - m_Layer(other.GetLayer()) -{ - // clone Labels - auto otherIt = other.IteratorConstBegin(); - for (; otherIt != other.IteratorConstEnd(); ++otherIt) - { - m_LabelContainer[otherIt->first] = otherIt->second->Clone(); - - auto command = itk::MemberCommand::New(); - command->SetCallbackFunction(this, &LabelSet::OnLabelModified); - m_LabelContainer[otherIt->first]->AddObserver(itk::ModifiedEvent(), command); - } - m_ReservedLabelValuesFunctor = other.m_ReservedLabelValuesFunctor; -} - -std::vector mitk::LabelSet::GetUsedLabelValues() const -{ - std::vector result = { 0 }; - - if (m_ReservedLabelValuesFunctor != nullptr) - { - result = m_ReservedLabelValuesFunctor(); - } - else - { - for (auto [value, label] : this->m_LabelContainer) - { - result.emplace_back(value); - } - } - return result; -} - -void mitk::LabelSet::OnLabelModified(const Object* sender, const itk::EventObject&) -{ - auto label = dynamic_cast(sender); - if (nullptr == label) - mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; - - ModifyLabelEvent.Send(label->GetValue()); - Superclass::Modified(); -} - -mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstEnd() const -{ - return m_LabelContainer.end(); -} - -mitk::LabelSet::LabelContainerConstIteratorType mitk::LabelSet::IteratorConstBegin() const -{ - return m_LabelContainer.begin(); -} - -mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorEnd() -{ - return m_LabelContainer.end(); -} - -mitk::LabelSet::LabelContainerIteratorType mitk::LabelSet::IteratorBegin() -{ - return m_LabelContainer.begin(); -} - -unsigned int mitk::LabelSet::GetNumberOfLabels() const -{ - return m_LabelContainer.size(); -} - -void mitk::LabelSet::SetLayer(unsigned int layer) -{ - m_Layer = layer; - Modified(); -} - -void mitk::LabelSet::SetActiveLabel(PixelType pixelValue) -{ - m_ActiveLabelValue = pixelValue; - ActiveLabelEvent.Send(pixelValue); - Modified(); -} - -bool mitk::LabelSet::ExistLabel(PixelType pixelValue) -{ - return m_LabelContainer.count(pixelValue) > 0 ? true : false; -} - -mitk::Label* mitk::LabelSet::AddLabel(mitk::Label *label, bool addAsClone) -{ - unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; - if (m_LabelContainer.size() >= max_size) - return nullptr; - - mitk::Label::Pointer newLabel = addAsClone ? label->Clone() : Label::Pointer(label); - - // TODO use layer of label parameter - newLabel->SetLayer(m_Layer); - - auto pixelValue = newLabel->GetValue(); - auto usedValues = this->GetUsedLabelValues(); - auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); - - if (!usedValues.empty() && usedValues.end() != finding) - { - pixelValue = usedValues.back()+1; - newLabel->SetValue(pixelValue); - } - - // new map entry - m_LabelContainer[pixelValue] = newLabel; - UpdateLookupTable(pixelValue); - - // add DICOM information of the label - DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); - - auto command = itk::MemberCommand::New(); - command->SetCallbackFunction(this, &LabelSet::OnLabelModified); - newLabel->AddObserver(itk::ModifiedEvent(), command); - - AddLabelEvent.Send(newLabel->GetValue()); - SetActiveLabel(newLabel->GetValue()); - Modified(); - - return newLabel; -} - -mitk::Label* mitk::LabelSet::AddLabel(const std::string &name, const mitk::Color &color) -{ - mitk::Label::Pointer newLabel = mitk::Label::New(); - newLabel->SetName(name); - newLabel->SetColor(color); - return AddLabel(newLabel); -} - -void mitk::LabelSet::RenameLabel(PixelType pixelValue, const std::string &name, const mitk::Color &color) -{ - mitk::Label *label = GetLabel(pixelValue); - label->SetName(name); - label->SetColor(color); - - // change DICOM information of the label - DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); -} - -void mitk::LabelSet::SetLookupTable(mitk::LookupTable *lut) -{ - m_LookupTable = lut; - Modified(); -} - -void mitk::LabelSet::PrintSelf(std::ostream & /*os*/, itk::Indent /*indent*/) const -{ -} - -void mitk::LabelSet::RemoveLabel(PixelType pixelValue) -{ - if (LabelSetImage::UnlabeledValue == pixelValue) - return; - - auto it = m_LabelContainer.rbegin(); - PixelType nextActivePixelValue = it->first; - - for (; it != m_LabelContainer.rend(); ++it) - { - if (it->first == pixelValue) - { - it->second->RemoveAllObservers(); - m_LabelContainer.erase(pixelValue); - break; - } - nextActivePixelValue = it->first; - } - - if (m_ActiveLabelValue == pixelValue) - { - if (ExistLabel(nextActivePixelValue)) - { - this->SetActiveLabel(nextActivePixelValue); - } - else if (!m_LabelContainer.empty()) - { - this->SetActiveLabel(m_LabelContainer.rbegin()->first); - } - else - { - this->SetActiveLabel(0); - } - } - - RemoveLabelEvent.Send(pixelValue); - - Modified(); -} - -void mitk::LabelSet::RemoveAllLabels() -{ - auto _it = IteratorBegin(); - for (; _it != IteratorConstEnd();) - { - auto labelValue = _it->first; - m_LabelContainer.erase(_it++); - RemoveLabelEvent.Send(labelValue); - } - AllLabelsModifiedEvent.Send(); -} - -void mitk::LabelSet::SetNextActiveLabel() -{ - auto it = m_LabelContainer.find(m_ActiveLabelValue); - - if (it != m_LabelContainer.end()) - ++it; - - if (it == m_LabelContainer.end()) - { - it = m_LabelContainer.begin(); - if (m_LabelContainer.size() > 1) - ++it; // ...skip background label! - } - - SetActiveLabel(it->first); -} - -void mitk::LabelSet::SetAllLabelsLocked(bool value) -{ - auto _end = m_LabelContainer.end(); - auto _it = m_LabelContainer.begin(); - for (; _it != _end; ++_it) - _it->second->SetLocked(value); - AllLabelsModifiedEvent.Send(); - Modified(); -} - -void mitk::LabelSet::SetAllLabelsVisible(bool value) -{ - auto _end = m_LabelContainer.end(); - auto _it = m_LabelContainer.begin(); - for (; _it != _end; ++_it) - { - _it->second->SetVisible(value); - UpdateLookupTable(_it->first); - } - AllLabelsModifiedEvent.Send(); - Modified(); -} - -void mitk::LabelSet::UpdateLookupTable(PixelType pixelValue) -{ - const mitk::Color &color = GetLabel(pixelValue)->GetColor(); - - double rgba[4]; - m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); - rgba[0] = color.GetRed(); - rgba[1] = color.GetGreen(); - rgba[2] = color.GetBlue(); - if (GetLabel(pixelValue)->GetVisible()) - rgba[3] = GetLabel(pixelValue)->GetOpacity(); - else - rgba[3] = 0.0; - m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); -} - -mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) -{ - if (m_LabelContainer.find(pixelValue) == m_LabelContainer.end()) - return nullptr; - return m_LabelContainer[pixelValue]; -} - -const mitk::Label *mitk::LabelSet::GetLabel(PixelType pixelValue) const -{ - auto it = m_LabelContainer.find(pixelValue); - if (it == m_LabelContainer.end()) - return nullptr; - return it->second.GetPointer(); -} - -bool mitk::Equal(const mitk::LabelSet &leftHandSide, const mitk::LabelSet &rightHandSide, ScalarType eps, bool verbose) -{ - bool returnValue = true; - // LabelSetmembers - - MITK_INFO(verbose) << "--- LabelSet Equal ---"; - - // m_LookupTable; - const mitk::LookupTable *lhsLUT = leftHandSide.GetLookupTable(); - const mitk::LookupTable *rhsLUT = rightHandSide.GetLookupTable(); - - returnValue = *lhsLUT == *rhsLUT; - if (!returnValue) - { - MITK_INFO(verbose) << "Lookup tabels not equal."; - return returnValue; - ; - } - - // m_ActiveLabel; - if (leftHandSide.GetActiveLabel() != rightHandSide.GetActiveLabel()) - { - returnValue = mitk::Equal(*leftHandSide.GetActiveLabel(), *rightHandSide.GetActiveLabel(), eps, verbose); - if (!returnValue) - { - MITK_INFO(verbose) << "Active label not equal."; - return returnValue; - ; - } - } - - // m_Layer; - returnValue = leftHandSide.GetLayer() == rightHandSide.GetLayer(); - if (!returnValue) - { - MITK_INFO(verbose) << "Layer index not equal."; - return returnValue; - ; - } - - // container size; - returnValue = leftHandSide.GetNumberOfLabels() == rightHandSide.GetNumberOfLabels(); - if (!returnValue) - { - MITK_INFO(verbose) << "Number of labels not equal."; - return returnValue; - ; - } - - // Label container (map) - - // m_LabelContainer; - auto lhsit = leftHandSide.IteratorConstBegin(); - auto rhsit = rightHandSide.IteratorConstBegin(); - for (; lhsit != leftHandSide.IteratorConstEnd(); ++lhsit, ++rhsit) - { - returnValue = rhsit->first == lhsit->first; - if (!returnValue) - { - MITK_INFO(verbose) << "Label in label container not equal."; - return returnValue; - ; - } - - returnValue = mitk::Equal(*(rhsit->second), *(lhsit->second), eps, verbose); - if (!returnValue) - { - MITK_INFO(verbose) << "Label in label container not equal."; - return returnValue; - ; - } - } - - return returnValue; -} diff --git a/Modules/Multilabel/mitkLabelSet.h b/Modules/Multilabel/mitkLabelSet.h deleted file mode 100644 index 14a95e93a4..0000000000 --- a/Modules/Multilabel/mitkLabelSet.h +++ /dev/null @@ -1,263 +0,0 @@ -/*============================================================================ - -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 mitkLabelSet_h -#define mitkLabelSet_h - -#include "MitkMultilabelExports.h" -#include -#include - -#include -#include -#include - -#include - -namespace mitk -{ - // - // Documentation - // @brief LabelSet containing the labels corresponding to a segmentation session. - // @ingroup Data - // - - class MITKMULTILABEL_EXPORT LabelSet : public itk::Object - { - public: - mitkClassMacroItkParent(LabelSet, itk::Object); - itkNewMacro(Self); - - typedef mitk::Label::PixelType PixelType; - - using LabelValueType = mitk::Label::PixelType; - typedef std::map LabelContainerType; - typedef LabelContainerType::const_iterator LabelContainerConstIteratorType; - typedef LabelContainerType::iterator LabelContainerIteratorType; - - /** - * \brief AddLabelEvent is emitted whenever a new label has been added to the LabelSet. - * - * The registered method will be called with the label value of the added label. - * Observers should register to this event by calling myLabelSet->AddLabelEvent.AddListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been added to the LabelSet. - * Observers should unregister by calling myLabelSet->AddLabelEvent.RemoveListener(myObject, MyObject::MyMethod). - * - * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for - * a Message1 object which is thread safe - */ - Message1 AddLabelEvent; - - /** - * \brief RemoveLabelEvent is emitted whenever a new label has been removed from the LabelSet. - * - * The registered method will be called with the label value of the removed label. - * Observers should register to this event by calling myLabelSet->RemoveLabelEvent.AddListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been removed from the LabelSet. - * Observers should unregister by calling myLabelSet->RemoveLabelEvent.RemoveListener(myObject, MyObject::MyMethod). - * - * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for - * a Message object which is thread safe - */ - Message1 RemoveLabelEvent; - - /** - * \brief ModifyLabelEvent is emitted whenever a label has been modified from the LabelSet. - * - * The registered method will be called with the label value of the modified label. - * Observers should register to this event by calling myLabelSet->ModifyLabelEvent.AddListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been removed from the LabelSet. - * Observers should unregister by calling myLabelSet->ModifyLabelEvent.RemoveListener(myObject, MyObject::MyMethod). - * - * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for - * a Message object which is thread safe - */ - Message1 ModifyLabelEvent; - - /** - * \brief ActiveLabelEvent is emitted whenever a label has been set as active in the LabelSet. - */ - Message1 ActiveLabelEvent; - - /** - * \brief AllLabelsModifiedEvent is emitted whenever a new label has been removed from the LabelSet. - * - * Observers should register to this event by calling myLabelSet->AllLabelsModifiedEvent.AddListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been removed from the LabelSet. - * Observers should unregister by calling myLabelSet->AllLabelsModifiedEvent.RemoveListener(myObject, - * MyObject::MyMethod). - * - * member variable is not needed to be locked in multi-threaded scenarios since the LabelSetEvent is a typedef for - * a Message object which is thread safe - */ - Message<> AllLabelsModifiedEvent; - - /** \brief Returns a const iterator poiting to the begining of the container. - */ - LabelContainerConstIteratorType IteratorConstBegin() const; - - /** \brief Returns a const iterator pointing to the end of the container. - */ - LabelContainerConstIteratorType IteratorConstEnd() const; - - /** \brief Returns a iterator poiting to the begining of the container. - */ - LabelContainerIteratorType IteratorBegin(); - - /** \brief Returns a iterator pointing to the end of the container. - */ - LabelContainerIteratorType IteratorEnd(); - - /** \brief - * Recall itk::Object::Modified event from a label and send a ModifyLabelEvent - */ - void OnLabelModified(const Object*, const itk::EventObject&); - - /** \brief - */ - void SetLayer(unsigned int); - - /** \brief - */ - void SetActiveLabel(PixelType); - - /** \brief - */ - void RemoveLabel(PixelType); - - /** \brief - */ - bool ExistLabel(PixelType); - - /** \brief Adds a label to the label set. - * @remark If the pixel value of the label is already used in the label set, the label - * will get a new none conflicting value assigned. - * @param label Instance of an label that should be added or used as template - * @param addAsClone flag that control if the passed instance should be added or - * a clone of the instance. - * @return Instance of the label as it was added to the label set. - */ - mitk::Label* AddLabel(mitk::Label *label, bool addAsClone = true); - - /** \brief - */ - mitk::Label* AddLabel(const std::string &name, const Color &color); - - /** \brief - */ - void RenameLabel(PixelType, const std::string &, const Color &); - - /** \brief - */ - unsigned int GetNumberOfLabels() const; - - /** \brief - */ - void SetAllLabelsVisible(bool); - - /** \brief - */ - void SetAllLabelsLocked(bool); - - /** \brief - */ - void RemoveAllLabels(); - - void SetNextActiveLabel(); - - /** \brief - */ - Label *GetActiveLabel() { return GetLabel(m_ActiveLabelValue); } - /** \brief - */ - const Label *GetActiveLabel() const { return GetLabel(m_ActiveLabelValue); } - /** \brief - */ - Label *GetLabel(PixelType pixelValue); - - /** \brief - */ - const Label *GetLabel(PixelType pixelValue) const; - - itkGetMacro(Layer, int); - - itkGetConstMacro(Layer, int); - - itkGetModifiableObjectMacro(LookupTable, mitk::LookupTable); - - /** \brief - */ - void SetLookupTable(LookupTable *lut); - - /** \brief - */ - void UpdateLookupTable(PixelType pixelValue); - - using ReservedLabelValuesFunctor = std::function()>; - ReservedLabelValuesFunctor m_ReservedLabelValuesFunctor; - - std::vector GetUsedLabelValues() const; - - protected: - LabelSet(); - LabelSet(const LabelSet &); - - mitkCloneMacro(Self); - - ~LabelSet() override; - - void PrintSelf(std::ostream &os, itk::Indent indent) const override; - - LabelContainerType m_LabelContainer; - - LookupTable::Pointer m_LookupTable; - - PixelType m_ActiveLabelValue; - - unsigned int m_Layer; - }; - - /** - * @brief Equal A function comparing two label sets for beeing equal in data - * - * @ingroup MITKTestingAPI - * - * Following aspects are tested for equality: - * - LabelSetmembers - * - Label container (map) - * - * @param rightHandSide An image to be compared - * @param leftHandSide An image to be compared - * @param eps Tolarence for comparison. You can use mitk::eps in most cases. - * @param verbose Flag indicating if the user wants detailed console output or not. - * @return true, if all subsequent comparisons are true, false otherwise - */ - MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSet &leftHandSide, - const mitk::LabelSet &rightHandSide, - ScalarType eps, - bool verbose); - - /** - * Method takes a label set and generates a new label set with the same labels but updated labels values according to - * the passed labelMapping. - * @pre sourceLabelSet is valid - */ - MITKMULTILABEL_EXPORT LabelSet::Pointer GenerateLabelSetWithMappedValues(const LabelSet* sourceLabelSet, - std::vector > labelMapping = { {1,1} }); - -} // namespace mitk - -#endif diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 02b28d5cb8..a8f0632437 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1564 +1,1630 @@ /*============================================================================ 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 "mitkLabelSetImage.h" -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkImagePixelReadAccessor.h" -#include "mitkImagePixelWriteAccessor.h" -#include "mitkInteractionConst.h" -#include "mitkLookupTableProperty.h" -#include "mitkPadImageFilter.h" -#include "mitkRenderingManager.h" -#include "mitkDICOMSegmentationPropertyHelper.h" -#include "mitkDICOMQIPropertyHelper.h" - -#include -#include -#include - -#include -#include -#include -#include -//#include +#include +#include +#include +#include +#include +#include +#include #include - #include -template -void SetToZero(itk::Image *source) -{ - source->FillBuffer(0); -} - -template -void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) +namespace mitk { - mitk::ImagePixelReadAccessor readAccessor(layerImage); - mitk::ImagePixelWriteAccessor writeAccessor(mask); - - std::size_t numberOfPixels = 1; - for (int dim = 0; dim < static_cast(VImageDimension); ++dim) - numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); - - auto src = readAccessor.GetData(); - auto dest = writeAccessor.GetData(); + template + void ClearBufferProcessing(ImageType* itkImage) + { + itkImage->FillBuffer(0); + } - for (std::size_t i = 0; i < numberOfPixels; ++i) + void ClearImageBuffer(mitk::Image* image) { - if (index == *(src + i)) - *(dest + i) = 1; + if (image->GetDimension() == 4) + { //remark: this extra branch was added, because LabelSetImage instances can be + //dynamic (4D), but AccessByItk by support only supports 2D and 3D. + //The option to change the CMake default dimensions for AccessByItk was + //dropped (for details see discussion in T28756) + AccessFixedDimensionByItk(image, ClearBufferProcessing, 4); + } + else + { + AccessByItk(image, ClearBufferProcessing); + } } } -const mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::UnlabeledValue = 0; +const mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::UNLABELED_VALUE = 0; mitk::LabelSetImage::LabelSetImage() - : mitk::Image(), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) + : mitk::Image(), m_ActiveLabelValue(0), m_UnlabeledLabelLock(false), m_ActiveLayer(0), m_activeLayerInvalid(false) { + m_LookupTable = mitk::LookupTable::New(); + m_LookupTable->SetType(mitk::LookupTable::MULTILABEL); + // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), + m_ActiveLabelValue(other.m_ActiveLabelValue), + m_LookupTable(other.m_LookupTable->Clone()), m_UnlabeledLabelLock(other.m_UnlabeledLabelLock), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false) { - for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) + GroupIndexType i = 0; + for (auto groupImage : other.m_LayerContainer) { - // Clone LabelSet data - mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); - - this->RegisterLabelSet(lsClone); - - m_LabelSetContainer.push_back(lsClone); - - // clone layer Image data - mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); - m_LayerContainer.push_back(liClone); + this->AddLayer(groupImage->Clone(), other.GetConstLabelsByValue(other.GetLabelValuesByGroup(i))); + i++; } - - this->ReinitMaps(); + m_Groups = other.m_Groups; // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } -void mitk::LabelSetImage::OnLabelSetModified() -{ - Superclass::Modified(); -} - void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero - if (4 == this->GetDimension()) - { - AccessFixedDimensionByItk(this, SetToZero, 4); - } - else - { - AccessByItk(this, SetToZero); - } + ClearImageBuffer(this); // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack if (this->GetNumberOfLayers() == 0) { AddLayer(); } } mitk::LabelSetImage::~LabelSetImage() { - for (auto ls : m_LabelSetContainer) + for (auto [value, label] : m_LabelMap) { - this->ReleaseLabelSet(ls); + this->ReleaseLabel(label); } - m_LabelSetContainer.clear(); -} - -mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) -{ - return m_LayerContainer[layer]; -} - -const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const -{ - return m_LayerContainer[layer]; + m_LabelMap.clear(); } unsigned int mitk::LabelSetImage::GetActiveLayer() const { + if (m_LayerContainer.size() == 0) mitkThrow() << "Cannot return active layer index. No layer is available."; + return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { - return m_LabelSetContainer.size(); -} - -void mitk::LabelSetImage::RegisterLabelSet(mitk::LabelSet* ls) -{ - // add modified event listener to LabelSet (listen to LabelSet changes) - itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); - command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); - ls->AddObserver(itk::ModifiedEvent(), command); - - ls->AddLabelEvent.AddListener(mitk::MessageDelegate1( - this, &LabelSetImage::OnLabelAdded)); - ls->ModifyLabelEvent.AddListener(mitk::MessageDelegate1( - this, &LabelSetImage::OnLabelModified)); - ls->RemoveLabelEvent.AddListener(mitk::MessageDelegate1( - this, &LabelSetImage::OnLabelRemoved)); - - ls->m_ReservedLabelValuesFunctor = [this]() {return this->GetUsedLabelValues(); }; + return m_LayerContainer.size(); } -void mitk::LabelSetImage::ReleaseLabelSet(mitk::LabelSet* ls) +void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) { - ls->RemoveAllObservers(); - - ls->AddLabelEvent.RemoveListener(mitk::MessageDelegate1( - this, &LabelSetImage::OnLabelAdded)); - ls->ModifyLabelEvent.RemoveListener(mitk::MessageDelegate1( - this, &LabelSetImage::OnLabelModified)); - ls->RemoveLabelEvent.RemoveListener(mitk::MessageDelegate1( - this, &LabelSetImage::OnLabelRemoved)); + if (!this->ExistGroup(indexToDelete)) mitkThrow() << "Cannot remove group. Group does not exist. Invalid group index: "<m_ReservedLabelValuesFunctor = nullptr; -} - -void mitk::LabelSetImage::RemoveLayer() -{ - int layerToDelete = GetActiveLayer(); - // remove all observers from active label set - GetLabelSet(layerToDelete)->RemoveAllObservers(); + const auto activeIndex = GetActiveLayer(); - // set the active layer to one below, if exists. - if (layerToDelete != 0) + auto newActiveIndex = activeIndex; + auto newActiveIndexBeforeDeletion = activeIndex; + //determine new active group index (afte the group will be removed); + if (indexToDelete < activeIndex) + { //lower the index because position in m_LayerContainer etc has changed + newActiveIndex = activeIndex-1; + } + else if (indexToDelete == activeIndex) { - SetActiveLayer(layerToDelete - 1); + if (this->GetNumberOfLayers() == 1) + { //last layer is about to be deleted + newActiveIndex = 0; + } + else + { + //we have to add/subtract one more because we have not removed the layer yet, thus the group count is to 1 high. + newActiveIndex = indexToDelete+1 < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 2; + newActiveIndexBeforeDeletion = indexToDelete + 1 < GetNumberOfLayers() ? indexToDelete+1 : indexToDelete -1; + } } - else + + if (activeIndex == indexToDelete) { - // we are deleting layer zero, it should not be copied back into the vector + // we are deleting the active layer, it should not be copied back into the vector m_activeLayerInvalid = true; + //copy the image content of the upcoming new active layer; + SetActiveLayer(newActiveIndexBeforeDeletion); } - // remove labelset and image data - m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); - m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); + auto relevantLabels = m_GroupToLabelMap[indexToDelete]; + + // remove labels of group + for (auto labelValue : relevantLabels) + { + auto label = m_LabelMap[labelValue]; + this->ReleaseLabel(label); + m_LabelToGroupMap.erase(labelValue); + m_LabelMap.erase(labelValue); + this->InvokeEvent(LabelRemovedEvent(labelValue)); + } + // remove the group entries in the maps and the image. + m_Groups.erase(m_Groups.begin() + indexToDelete); + m_GroupToLabelMap.erase(m_GroupToLabelMap.begin() + indexToDelete); + m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); - if (layerToDelete == 0) + //update old indexes in m_GroupToLabelMap to new layer indexes + for (auto& element : m_LabelToGroupMap) { - this->SetActiveLayer(layerToDelete); + if (element.second > indexToDelete) element.second = element.second -1; } - this->OnGroupRemoved(layerToDelete); + //correct active layer index + m_ActiveLayer = newActiveIndex; + + this->InvokeEvent(LabelsChangedEvent(relevantLabels)); + this->InvokeEvent(GroupRemovedEvent(indexToDelete)); this->Modified(); } -void mitk::LabelSetImage::RemoveGroup(GroupIndexType indexToDelete) +mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const LabelVectorType& labels) { - const auto activeIndex = GetActiveLayer(); - - // remove all observers from active label set - GetLabelSet(indexToDelete)->RemoveAllObservers(); + LabelValueVectorType result; - // set the active layer to one below, if exists. - if (activeIndex>indexToDelete) + for (auto label : labels) { - SetActiveLayer(activeIndex - 1); + result.emplace_back(label->GetValue()); } - else if (activeIndex==indexToDelete) + return result; +} + +mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels) +{ + LabelValueVectorType result; + + for (auto label : labels) { - // we are deleting layer zero, it should not be copied back into the vector - m_activeLayerInvalid = true; + result.emplace_back(label->GetValue()); } + return result; +} - // remove labelset and image data - m_LabelSetContainer.erase(m_LabelSetContainer.begin() + indexToDelete); - m_LayerContainer.erase(m_LayerContainer.begin() + indexToDelete); +mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::ConvertLabelVectorConst(const LabelVectorType& labels) +{ + ConstLabelVectorType result(labels.begin(), labels.end()); + return result; +}; - if (indexToDelete == activeIndex) - { //enforces the new active layer to be set and copied - auto newActiveIndex = indexToDelete < GetNumberOfLayers() ? indexToDelete : GetNumberOfLayers() - 1; - this->SetActiveLayer(newActiveIndex); - } +const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetAllLabelValues() const +{ + LabelValueVectorType result; - this->OnGroupRemoved(indexToDelete); - this->Modified(); + for (auto [value, label] : m_LabelMap) + { + result.emplace_back(value); + } + return result; } mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetUsedLabelValues() const { - LabelValueVectorType result = { UnlabeledValue }; + LabelValueVectorType result = { UNLABELED_VALUE }; for (auto [value, label] : m_LabelMap) { result.emplace_back(value); } + return result; } -unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) +mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(ConstLabelVector labels) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); - if (newImage->GetDimension() < 4) - { - AccessByItk(newImage, SetToZero); - } - else - { - AccessFixedDimensionByItk(newImage, SetToZero, 4); - } + ClearImageBuffer(newImage); - return this->AddLayer(newImage, labelSet); + return this->AddLayer(newImage, labels); } -unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) +mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, ConstLabelVector labels) { - unsigned int newLabelSetId = m_LayerContainer.size(); - - // Add labelset to layer - mitk::LabelSet::Pointer ls; - if (labelSet.IsNotNull()) - { - ls = labelSet; - } - else - { - ls = mitk::LabelSet::New(); - ls->SetActiveLabel(UnlabeledValue); - } - - ls->SetLayer(newLabelSetId); + GroupIndexType newGroupID = m_Groups.size(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); - // push a new labelset for the new layer - m_LabelSetContainer.push_back(ls); + m_Groups.push_back(""); + m_GroupToLabelMap.push_back({}); - RegisterLabelSet(ls); - this->ReinitMaps(); + for (auto label : labels) + { + if (m_LabelMap.end() != m_LabelMap.find(label->GetValue())) + { + mitkThrow() << "Cannot add layer. Labels that should be added with layer use at least one label value that is already in use. Conflicted label value: " << label->GetValue(); + } + + auto labelClone = label->Clone(); + + DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(labelClone); + this->AddLabelToMap(labelClone->GetValue(), labelClone, newGroupID); + this->RegisterLabel(labelClone); + } - SetActiveLayer(newLabelSetId); this->Modified(); - this->OnGroupAdded(newLabelSetId); + this->InvokeEvent(GroupAddedEvent(newGroupID)); - return newLabelSetId; + return newGroupID; } -void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet) +void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& labelSet) { - if (m_LayerContainer.size() <= layerIdx) + if (m_LayerContainer.size() <= groupID) { - mitkThrow() << "Trying to add labelSet to non-existing layer."; + mitkThrow() << "Trying to replace labels of non-existing group. Invalid group id: "<Clone(); - - this->RegisterLabelSet(clonedLabelSet); - - std::vector addedGroups; - - if (layerIdx < m_LabelSetContainer.size()) + //remove old group labels + auto oldLabels = this->m_GroupToLabelMap[groupID]; + for (auto labelID : oldLabels) { - if (m_LabelSetContainer[layerIdx].IsNotNull()) - { - this->ReleaseLabelSet(m_LabelSetContainer[layerIdx]); - } + this->RemoveLabelFromMap(labelID); + this->InvokeEvent(LabelRemovedEvent(labelID)); - m_LabelSetContainer[layerIdx] = clonedLabelSet; } - else + this->InvokeEvent(LabelsChangedEvent(oldLabels)); + this->InvokeEvent(GroupModifiedEvent(groupID)); + + //add new labels to group + for (auto label : labelSet) { - while (layerIdx >= m_LabelSetContainer.size()) - { - mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); - defaultLabelSet->SetActiveLabel(UnlabeledValue); - defaultLabelSet->SetLayer(m_LabelSetContainer.size()); - this->RegisterLabelSet(defaultLabelSet); - this->ReinitMaps(); - m_LabelSetContainer.push_back(defaultLabelSet); - addedGroups.emplace_back(m_LabelSetContainer.size() - 1); - } - m_LabelSetContainer.push_back(clonedLabelSet); - addedGroups.emplace_back(m_LabelSetContainer.size() - 1); + this->AddLabel(label->Clone(), groupID, true, false); } +} - this->ReinitMaps(); +void mitk::LabelSetImage::ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& labelSet) +{ + return ReplaceGroupLabels(groupID, ConvertLabelVectorConst(labelSet)); +} - for (auto groupID : addedGroups) - { - this->m_GroupAddedMessage.Send(groupID); - } +mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) +{ + if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; + + return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID]; +} + +const mitk::Image* mitk::LabelSetImage::GetGroupImage(GroupIndexType groupID) const +{ + if (!this->ExistGroup(groupID)) mitkThrow() << "Error, cannot return group image. Group ID is invalid. Invalid ID: " << groupID; + + return groupID == this->GetActiveLayer() ? this : m_LayerContainer[groupID].GetPointer(); } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } - m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter + m_ActiveLayer = layer; AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } - m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter + m_ActiveLayer = layer; AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } +void mitk::LabelSetImage::SetActiveLabel(LabelValueType label) +{ + m_ActiveLabelValue = label; + + if (label != UNLABELED_VALUE) + { + auto groupID = this->GetGroupIndexOfLabel(label); + if (groupID!=this->GetActiveLayer()) this->SetActiveLayer(groupID); + } + Modified(); +} + void mitk::LabelSetImage::ClearBuffer() { try { - if (this->GetDimension() == 4) - { //remark: this extra branch was added, because LabelSetImage instances can be - //dynamic (4D), but AccessByItk by support only supports 2D and 3D. - //The option to change the CMake default dimensions for AccessByItk was - //dropped (for details see discussion in T28756) - AccessFixedDimensionByItk(this, ClearBufferProcessing,4); - } - else - { - AccessByItk(this, ClearBufferProcessing); - } + ClearImageBuffer(this); this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } -bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const -{ - bool exist = false; - for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) - exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); - return exist; -} - -bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const -{ - bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); - return exist; -} - -bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const -{ - return layer < m_LabelSetContainer.size(); -} - -void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) +void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } - GetLabelSet(layer)->SetActiveLabel(pixelValue); - this->m_LabelModifiedMessage.Send(sourcePixelValue); - this->m_LabelModifiedMessage.Send(pixelValue); - this->m_LabelsChangedMessage.Send({ sourcePixelValue, pixelValue }); + this->SetActiveLabel(pixelValue); + this->InvokeEvent(LabelModifiedEvent(sourcePixelValue)); + this->InvokeEvent(LabelModifiedEvent(pixelValue)); + this->InvokeEvent(LabelsChangedEvent({ sourcePixelValue, pixelValue })); Modified(); } -void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer) +void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); - this->m_LabelModifiedMessage.Send(vectorOfSourcePixelValues[idx]); + this->InvokeEvent(LabelModifiedEvent(vectorOfSourcePixelValues[idx])); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } - GetLabelSet(layer)->SetActiveLabel(pixelValue); - this->m_LabelModifiedMessage.Send(pixelValue); + this->SetActiveLabel(pixelValue); + this->InvokeEvent(LabelModifiedEvent(pixelValue)); auto modifiedValues = vectorOfSourcePixelValues; modifiedValues.push_back(pixelValue); - this->m_LabelsChangedMessage.Send(modifiedValues); + this->InvokeEvent(LabelsChangedEvent(modifiedValues)); Modified(); } void mitk::LabelSetImage::RemoveLabel(LabelValueType pixelValue) { + if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) return; + auto groupID = this->GetGroupIndexOfLabel(pixelValue); //first erase the pixel content (also triggers a LabelModified event) this->EraseLabel(pixelValue); + this->RemoveLabelFromMap(pixelValue); + + + if (m_ActiveLabelValue == pixelValue) + { + this->SetActiveLabel(0); + } + + this->InvokeEvent(LabelRemovedEvent(pixelValue)); + this->InvokeEvent(LabelsChangedEvent({ pixelValue })); + this->InvokeEvent(GroupModifiedEvent(groupID)); +} + +void mitk::LabelSetImage::RemoveLabelFromMap(LabelValueType pixelValue) +{ + if (m_LabelMap.find(pixelValue) == m_LabelMap.end()) mitkThrow()<<"Invalid state of instance. RemoveLabelFromMap was called for unknown label id. invalid label id: "<GetGroupIndexOfLabel(pixelValue); + + this->ReleaseLabel(m_LabelMap[pixelValue]); //now remove the label entry itself - this->GetLabelSet(groupID)->RemoveLabel(pixelValue); - // in the interim version triggered by label set events: this->m_LabelRemovedMessage.Send(pixelValue); - this->m_LabelsChangedMessage.Send({ pixelValue }); - this->m_GroupModifiedMessage.Send(groupID); + m_LabelMap.erase(pixelValue); + m_LabelToGroupMap.erase(pixelValue); + auto labelsInGroup = m_GroupToLabelMap[groupID]; + labelsInGroup.erase(std::remove(labelsInGroup.begin(), labelsInGroup.end(), pixelValue), labelsInGroup.end()); + m_GroupToLabelMap[groupID] = labelsInGroup; } -void mitk::LabelSetImage::RemoveLabels(const std::vector& VectorOfLabelPixelValues) +void mitk::LabelSetImage::RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues) { - for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) + for (const auto labelValue : vectorOfLabelPixelValues) { - this->RemoveLabel(VectorOfLabelPixelValues[idx]); - this->m_LabelsChangedMessage.Send({ VectorOfLabelPixelValues[idx] }); + this->RemoveLabel(labelValue); } + this->InvokeEvent(LabelsChangedEvent(vectorOfLabelPixelValues)); } -void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) +void mitk::LabelSetImage::EraseLabel(LabelValueType pixelValue) { try { auto groupID = this->GetGroupIndexOfLabel(pixelValue); - mitk::Image* groupImage = this->GetActiveLayer() != groupID - ? this->GetLayerImage(groupID) - : this; + mitk::Image* groupImage = this->GetGroupImage(groupID); if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(groupImage, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(groupImage, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } - this->m_LabelModifiedMessage.Send(pixelValue); - this->m_LabelsChangedMessage.Send({ pixelValue }); + this->InvokeEvent(LabelModifiedEvent(pixelValue)); + this->InvokeEvent(LabelsChangedEvent({ pixelValue })); Modified(); } -void mitk::LabelSetImage::EraseLabels(const std::vector& VectorOfLabelPixelValues) +void mitk::LabelSetImage::EraseLabels(const LabelValueVectorType& labelValues) { - for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) + for (auto labelValue : labelValues) { - this->EraseLabel(VectorOfLabelPixelValues[idx]); + this->EraseLabel(labelValue); } } -mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) +mitk::LabelSetImage::LabelValueType mitk::LabelSetImage::GetUnusedLabelValue() const { - if (m_LabelSetContainer.size() <= layer) - return nullptr; - else - return m_LabelSetContainer[layer]->GetActiveLabel(); + auto usedValues = this->GetUsedLabelValues(); + return usedValues.back() + 1; } -const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const +mitk::Label* mitk::LabelSetImage::AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone, bool correctLabelValue) { - if (m_LabelSetContainer.size() <= layer) + unsigned int max_size = mitk::Label::MAX_LABEL_VALUE + 1; + if (m_LayerContainer.size() >= max_size) return nullptr; - else - return m_LabelSetContainer[layer]->GetActiveLabel(); -} -mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const -{ - if (m_LabelSetContainer.size() <= layer) - return nullptr; - else - return m_LabelSetContainer[layer]->GetLabel(pixelValue); -} + mitk::Label::Pointer newLabel = addAsClone ? label->Clone() : Label::Pointer(label); -mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) -{ - if (m_LabelSetContainer.size() <= layer) - return nullptr; - else - return m_LabelSetContainer[layer].GetPointer(); + auto pixelValue = newLabel->GetValue(); + auto usedValues = this->GetUsedLabelValues(); + auto finding = std::find(usedValues.begin(), usedValues.end(), pixelValue); + + if (!usedValues.empty() && usedValues.end() != finding) + { + if (correctLabelValue) + { + pixelValue = this->GetUnusedLabelValue(); + newLabel->SetValue(pixelValue); + } + else + { + mitkThrow() << "Cannot add label due to conflicting label value that already exists in the MultiLabelSegmentation. Conflicting label value: " << pixelValue; + } + } + + // add DICOM information of the label + DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(newLabel); + + this->AddLabelToMap(pixelValue, newLabel, groupID); + this->RegisterLabel(newLabel); + + this->InvokeEvent(LabelAddedEvent(newLabel->GetValue())); + m_ActiveLabelValue = newLabel->GetValue(); + this->Modified(); + + return newLabel; } -const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const +mitk::Label* mitk::LabelSetImage::AddLabel(const std::string& name, const mitk::Color& color, GroupIndexType groupID) { - if (m_LabelSetContainer.size() <= layer) - return nullptr; - else - return m_LabelSetContainer[layer].GetPointer(); + mitk::Label::Pointer newLabel = mitk::Label::New(); + newLabel->SetName(name); + newLabel->SetColor(color); + return AddLabel(newLabel,groupID,false); } -mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() +void mitk::LabelSetImage::RenameLabel(LabelValueType pixelValue, const std::string& name, const mitk::Color& color) { - if (m_LabelSetContainer.size() == 0) - return nullptr; - else - return m_LabelSetContainer[GetActiveLayer()].GetPointer(); + mitk::Label* label = GetLabel(pixelValue); + if (nullptr == label) mitkThrow() << "Cannot rename label.Unknown label value provided. Unknown label value:" << pixelValue; + + label->SetName(name); + label->SetColor(color); + + this->UpdateLookupTable(pixelValue); + // change DICOM information of the label + DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } -const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const +mitk::Label *mitk::LabelSetImage::GetActiveLabel() { - if (m_LabelSetContainer.size() == 0) - return nullptr; - else - return m_LabelSetContainer[GetActiveLayer()].GetPointer(); + if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; + + auto finding = m_LabelMap.find(m_ActiveLabelValue); + return finding == m_LabelMap.end() ? nullptr : finding->second; } -void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) +const mitk::Label* mitk::LabelSetImage::GetActiveLabel() const { - this->UpdateCenterOfMass(pixelValue, this->GetGroupIndexOfLabel(pixelValue)); + if (m_ActiveLabelValue == UNLABELED_VALUE) return nullptr; + + auto finding = m_LabelMap.find(m_ActiveLabelValue); + return finding == m_LabelMap.end() ? nullptr : finding->second; } -void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) +void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue) { if (4 == this->GetDimension()) { - AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); + AccessFixedDimensionByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, 4, pixelValue); } else { - AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); + AccessByItk_1(this->GetGroupImage(this->GetGroupIndexOfLabel(pixelValue)), CalculateCenterOfMassProcessing, pixelValue); } } +void mitk::LabelSetImage::SetLookupTable(mitk::LookupTable* lut) +{ + m_LookupTable = lut; + this->Modified(); +} + +void mitk::LabelSetImage::UpdateLookupTable(PixelType pixelValue) +{ + const mitk::Color& color = this->GetLabel(pixelValue)->GetColor(); + + double rgba[4]; + m_LookupTable->GetTableValue(static_cast(pixelValue), rgba); + rgba[0] = color.GetRed(); + rgba[1] = color.GetGreen(); + rgba[2] = color.GetBlue(); + if (GetLabel(pixelValue)->GetVisible()) + rgba[3] = GetLabel(pixelValue)->GetOpacity(); + else + rgba[3] = 0.0; + m_LookupTable->SetTableValue(static_cast(pixelValue), rgba); +} + unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { - return m_LabelSetContainer[layer]->GetNumberOfLabels(); + if (layer >= m_Groups.size()) mitkThrow() << "Cannot get number of labels in group. Group is unknown. Invalid index:" << layer; + return m_GroupToLabelMap[layer].size(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { - unsigned int totalLabels(0); - auto layerIter = m_LabelSetContainer.begin(); - for (; layerIter != m_LabelSetContainer.end(); ++layerIter) - totalLabels += (*layerIter)->GetNumberOfLabels(); - return totalLabels; + return m_LabelMap.size(); } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } -mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) +mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index) { - auto previousActiveLayer = this->GetActiveLayer(); - auto mask = mitk::Image::New(); - - try - { - // mask->Initialize(this) does not work here if this label set image has a single slice, - // since the mask would be automatically flattened to a 2-d image, whereas we expect the - // original dimension of this label set image. Hence, initialize the mask more explicitly: - mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); - mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); + if (!this->ExistLabel(index)) mitkThrow() << "Error, cannot return label mask. Label ID is invalid. Invalid ID: " << index; - auto byteSize = sizeof(LabelSetImage::PixelType); - for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) - byteSize *= mask->GetDimension(dim); + auto mask = mitk::Image::New(); - { - ImageWriteAccessor accessor(mask); - memset(accessor.GetData(), 0, byteSize); - } + // mask->Initialize(this) does not work here if this label set image has a single slice, + // since the mask would be automatically flattened to a 2-d image, whereas we expect the + // original dimension of this label set image. Hence, initialize the mask more explicitly: + mask->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions()); + mask->SetTimeGeometry(this->GetTimeGeometry()->Clone()); - if (!useActiveLayer) - this->SetActiveLayer(layer); + ClearImageBuffer(mask); - if (4 == this->GetDimension()) - { - ::CreateLabelMaskProcessing<4>(this, mask, index); - } - else if (3 == this->GetDimension()) - { - ::CreateLabelMaskProcessing(this, mask, index); - } - else - { - mitkThrow(); - } - } - catch (...) - { - if (!useActiveLayer) - this->SetActiveLayer(previousActiveLayer); + const auto groupID = this->GetGroupIndexOfLabel(index); - mitkThrow() << "Could not create a mask out of the selected label."; - } + auto destinationLabel = this->GetLabel(index)->Clone(); + destinationLabel->SetValue(1); - if (!useActiveLayer) - this->SetActiveLayer(previousActiveLayer); + TransferLabelContent(this->GetGroupImage(groupID), mask.GetPointer(), + {destinationLabel}, + LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, false, + { { index, destinationLabel->GetValue()}}, MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle::IgnoreLocks); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } + catch (Exception& e) + { + mitkReThrow(e) << "Could not initialize by provided labeled image."; + } catch (...) { - mitkThrow() << "Could not intialize by provided labeled image."; + mitkThrow() << "Could not initialize by provided labeled image due to unknown error."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { - auto sourceValue = static_cast(sourceIter.Get()); + const auto originalSourceValue = sourceIter.Get(); + const auto sourceValue = static_cast(originalSourceValue); + + if (originalSourceValue > mitk::Label::MAX_LABEL_VALUE) + { + mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains a pixel value that exceeds the label value range. Invalid pixel value:" << originalSourceValue; + } + targetIter.Set(sourceValue); - if (LabelSetImage::UnlabeledValue!=sourceValue && !this->ExistLabel(sourceValue)) + if (LabelSetImage::UNLABELED_VALUE!=sourceValue && !this->ExistLabel(sourceValue)) { + if (this->GetTotalNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE) + { + mitkThrow() << "Cannot initialize MultiLabelSegmentation by image. Image contains to many labels."; + } + std::stringstream name; name << "object-" << sourceValue; double rgba[4]; - m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); + this->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); - this->GetLabelSet()->AddLabel(label); - - if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || - sourceValue >= mitk::Label::MAX_LABEL_VALUE) - this->AddLayer(); + this->AddLabel(label,0,false); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); - int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); + const auto activeLabel = this->GetActiveLabel()->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); - if ((sourceValue != UnlabeledValue) && + if ((sourceValue != UNLABELED_VALUE) && (forceOverwrite || !this->IsLabelLocked(targetValue))) // skip unlabeled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template -void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) +void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, LabelValueType pixelValue) { if (ImageType::GetImageDimension() != 3) { return; } auto labelGeometryFilter = itk::LabelGeometryImageFilter::New(); labelGeometryFilter->SetInput(itkImage); labelGeometryFilter->Update(); auto centroid = labelGeometryFilter->GetCentroid(pixelValue); mitk::Point3D pos; pos[0] = centroid[0]; pos[1] = centroid[1]; pos[2] = centroid[2]; - GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); - this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? - GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); -} - -template -void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) -{ - itkImage->FillBuffer(0); + this->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); + this->GetSlicedGeometry()->IndexToWorld(pos, pos); + this->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } - -void mitk::LabelSetImage::OnLabelAdded(LabelValueType labelValue) -{ - Label* label = nullptr; - - unsigned int layerID = 0; - for (; layerID < this->GetNumberOfLayers(); ++layerID) - { - label = this->GetLabel(labelValue, layerID); - if (nullptr != label) break; - } - - if (!label) mitkThrow() << "Wrong internal state. OnLabelAdded was triggered, but label cannot be found. Invalid label: " << labelValue; - - AddLabelToMap(labelValue, label, layerID); - - this->m_LabelAddedMessage.Send(labelValue); -} - void mitk::LabelSetImage::AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID) { if (m_LabelMap.find(labelValue)!=m_LabelMap.end()) mitkThrow() << "Segmentation is in an invalid state: Label value collision. A label was added with a LabelValue already in use. LabelValue: " << labelValue; + if (!this->ExistGroup(groupID)) + mitkThrow() << "Cannot add label. Defined group is unknown. Invalid group index: " << groupID; + m_LabelMap[labelValue] = label; m_LabelToGroupMap[labelValue] = groupID; - auto groupFinding = m_GroupToLabelMap.find(groupID); - if (groupFinding == m_GroupToLabelMap.end()) - { - m_GroupToLabelMap[groupID] = { labelValue }; - } - else + auto groupFinding = std::find(m_GroupToLabelMap[groupID].begin(), m_GroupToLabelMap[groupID].end(), labelValue); + if (groupFinding == m_GroupToLabelMap[groupID].end()) { m_GroupToLabelMap[groupID].push_back(labelValue); } } -void mitk::LabelSetImage::OnLabelModified(LabelValueType labelValue) +void mitk::LabelSetImage::RegisterLabel(mitk::Label* label) { - this->m_LabelModifiedMessage.Send(labelValue); -} + UpdateLookupTable(label->GetValue()); -void mitk::LabelSetImage::OnLabelRemoved(LabelValueType labelValue) -{ - m_LabelMap.erase(labelValue); - auto finding = m_LabelToGroupMap.find(labelValue); - if (finding != m_LabelToGroupMap.end()) - { - auto labelsInGroup = m_GroupToLabelMap[finding->second]; - auto labelFinding = std::find(labelsInGroup.begin(), labelsInGroup.end(),finding->second); - if (labelFinding != labelsInGroup.end()) - { - labelsInGroup.erase(labelFinding); - } - m_LabelToGroupMap.erase(labelValue); - } - - this->m_LabelRemovedMessage.Send(labelValue); + auto command = itk::MemberCommand::New(); + command->SetCallbackFunction(this, &LabelSetImage::OnLabelModified); + label->AddObserver(itk::ModifiedEvent(), command); } -void mitk::LabelSetImage::OnGroupAdded(GroupIndexType groupIndex) +void mitk::LabelSetImage::ReleaseLabel(Label* label) { - this->m_GroupToLabelMap.insert(std::make_pair(groupIndex, LabelValueVectorType())); - - this->m_GroupAddedMessage.Send(groupIndex); + if (nullptr == label) mitkThrow() << "Invalid call of ReleaseLabel with a nullptr."; + label->RemoveAllObservers(); } -void mitk::LabelSetImage::OnGroupModified(GroupIndexType groupIndex) +void mitk::LabelSetImage::ApplyToLabels(const LabelValueVectorType& values, std::function&& lambda) { - this->m_GroupModifiedMessage.Send(groupIndex); + auto labels = this->GetLabelsByValue(values); + std::for_each(labels.begin(), labels.end(), lambda); + this->InvokeEvent(LabelsChangedEvent(values)); } -void mitk::LabelSetImage::OnGroupRemoved(GroupIndexType groupIndex) +void mitk::LabelSetImage::VisitLabels(const LabelValueVectorType& values, std::function&& lambda) const { - this->ReinitMaps(); - this->m_GroupRemovedMessage.Send(groupIndex); + auto labels = this->GetConstLabelsByValue(values); + std::for_each(labels.begin(), labels.end(), lambda); } -// future implementation for T28524 -//bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const -//{ -// auto finding = m_LabelToGroupMap.find(value); -// if (m_LabelToGroupMap.end() != finding) -// { -// return finding->second == groupIndex; -// } -// return false; -//} -// -//bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const -//{ -// return index < m_LabelSetContainer.size(); -//} -bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const +void mitk::LabelSetImage::OnLabelModified(const Object* sender, const itk::EventObject&) { - return index < m_LabelSetContainer.size(); + auto label = dynamic_cast(sender); + if (nullptr == label) + mitkThrow() << "LabelSet is in wrong state. LabelModified event is not send by a label instance."; + + Superclass::Modified(); + this->InvokeEvent(LabelModifiedEvent(label->GetValue())); } -bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value) const +bool mitk::LabelSetImage::ExistLabel(LabelValueType value) const { - GroupIndexType dummy; - return this->IsLabelInGroup(value, dummy); + auto finding = m_LabelMap.find(value); + return m_LabelMap.end() != finding; } -bool mitk::LabelSetImage::IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const +bool mitk::LabelSetImage::ExistLabel(LabelValueType value, GroupIndexType groupIndex) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() != finding) { - groupIndex = finding->second; - return true; + return finding->second == groupIndex; } return false; } +bool mitk::LabelSetImage::ExistGroup(GroupIndexType index) const +{ + return index < m_LayerContainer.size(); +} + mitk::LabelSetImage::GroupIndexType mitk::LabelSetImage::GetGroupIndexOfLabel(LabelValueType value) const { auto finding = m_LabelToGroupMap.find(value); if (m_LabelToGroupMap.end() == finding) { mitkThrow()<< "Cannot deduce group index. Passed label value does not exist. Value: "<< value; } return finding->second; } const mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) const { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; mitk::Label* mitk::LabelSetImage::GetLabel(LabelValueType value) { auto finding = m_LabelMap.find(value); if (m_LabelMap.end() != finding) { return finding->second; } return nullptr; }; bool mitk::LabelSetImage::IsLabelLocked(LabelValueType value) const { - if (value == UnlabeledValue) + if (value == UNLABELED_VALUE) { return m_UnlabeledLabelLock; } const auto label = this->GetLabel(value); return label->GetLocked(); } const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabels() const { ConstLabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabels() { LabelVectorType result; for (auto [value, label] : m_LabelMap) { result.emplace_back(label); } return result; } -const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) const +const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) { - if (!this->ExistGroup(index)) - mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; + LabelVectorType result; + for (const auto& labelValue : labelValues) + { + auto* label = this->GetLabel(labelValue); - mitk::LabelSetImage::ConstLabelVectorType result; - const auto labelValues = m_GroupToLabelMap.find(index)->second; + if (label != nullptr) + { + result.emplace_back(label); + } + else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; + } + return result; +} +const mitk::LabelSetImage::ConstLabelVectorType mitk::LabelSetImage::GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing) const +{ + ConstLabelVectorType result; for (const auto& labelValue : labelValues) { const auto* label = this->GetLabel(labelValue); if (label != nullptr) + { result.emplace_back(label); + } + else if (!ignoreMissing) mitkThrow() << "Error cannot get labels by Value. At least one passed value is unknown. Unknown value: " << labelValue; } - return result; } -const mitk::LabelSetImage::LabelVectorType mitk::LabelSetImage::GetLabelsInGroup(GroupIndexType index) +const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByGroup(GroupIndexType index) const { if (!this->ExistGroup(index)) mitkThrow() << "Cannot get labels of an invalid group. Invalid group index: " << index; - mitk::LabelSetImage::LabelVectorType result; - const auto labelValues = m_GroupToLabelMap[index]; + return m_GroupToLabelMap[index]; +} - for (const auto& labelValue : labelValues) - { - auto* label = this->GetLabel(labelValue); +const mitk::LabelSetImage::LabelValueVectorType mitk::LabelSetImage::GetLabelValuesByName(GroupIndexType index, std::string_view name) const +{ + LabelValueVectorType result; - if (label != nullptr) - result.emplace_back(label); - } + auto searchName = [&result, name](const Label* l) { if(l->GetName() == name) result.push_back(l->GetValue()); }; + + this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); return result; } -void mitk::LabelSetImage::ReinitMaps() +std::vector mitk::LabelSetImage::GetLabelClassNames() const { - this->m_LabelMap.clear(); - this->m_LabelToGroupMap.clear(); - this->m_GroupToLabelMap.clear(); + std::set names; + auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; + this->VisitLabels(this->GetAllLabelValues(), searchName); - for (GroupIndexType layerID = 0; layerID < this->GetNumberOfLayers(); ++layerID) - { - auto labelSet = this->GetLabelSet(layerID); + return std::vector(names.begin(), names.end()); +} - if (labelSet->GetNumberOfLabels() != 0) - { - for (auto iter = labelSet->IteratorBegin(); iter != labelSet->IteratorEnd(); ++iter) - { - if (iter->first != UnlabeledValue) - this->AddLabelToMap(iter->first, iter->second, layerID); - } - } - else - { - m_GroupToLabelMap[layerID] = {}; - } - } +std::vector mitk::LabelSetImage::GetLabelClassNamesByGroup(GroupIndexType index) const +{ + std::set names; + auto searchName = [&names](const Label* l) { names.emplace(l->GetName()); }; + this->VisitLabels(this->GetLabelValuesByGroup(index), searchName); + + return std::vector(names.begin(), names.end()); +} + +void mitk::LabelSetImage::SetAllLabelsVisible(bool visible) +{ + auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; + + this->ApplyToLabels(this->GetAllLabelValues(), setVisibility); +} + +void mitk::LabelSetImage::SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible) +{ + auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; + + this->ApplyToLabels(this->GetLabelValuesByGroup(group), setVisibility); +} + +void mitk::LabelSetImage::SetAllLabelsVisibleByName(GroupIndexType group, std::string_view name, bool visible) +{ + auto setVisibility = [visible](Label* l) { l->SetVisible(visible); }; + + this->ApplyToLabels(this->GetLabelValuesByName(group, name), setVisibility); +} + +void mitk::LabelSetImage::SetAllLabelsLocked(bool locked) +{ + auto setLock = [locked](Label* l) { l->SetLocked(locked); }; + + this->ApplyToLabels(this->GetAllLabelValues(), setLock); +} + +void mitk::LabelSetImage::SetAllLabelsLockedByGroup(GroupIndexType group, bool locked) +{ + auto setLock = [locked](Label* l) { l->SetLocked(locked); }; + + this->ApplyToLabels(this->GetLabelValuesByGroup(group), setLock); +} + +void mitk::LabelSetImage::SetAllLabelsLockedByName(GroupIndexType group, std::string_view name, bool locked) +{ + auto setLock = [locked](Label* l) { l->SetLocked(locked); }; + + this->ApplyToLabels(this->GetLabelValuesByName(group, name), setLock); } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; + // m_LookupTable; + const mitk::LookupTable* lhsLUT = leftHandSide.GetLookupTable(); + const mitk::LookupTable* rhsLUT = rightHandSide.GetLookupTable(); + + returnValue = *lhsLUT == *rhsLUT; + if (!returnValue) + { + MITK_INFO(verbose) << "Lookup tables not equal."; + return returnValue; + ; + } + // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } + if (leftHandSide.GetTotalNumberOfLabels() != rightHandSide.GetTotalNumberOfLabels()) + { + MITK_INFO(verbose) << "Number of labels are not equal."; + return false; + } + for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = - mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); + mitk::Equal(*leftHandSide.GetGroupImage(layerIndex), *rightHandSide.GetGroupImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } - // layer labelset data - returnValue = - mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); - if (!returnValue) + // label data + auto leftLabelsInGroup = leftHandSide.GetLabelValuesByGroup(layerIndex); + auto rightLabelsInGroup = rightHandSide.GetLabelValuesByGroup(layerIndex); + + if (leftLabelsInGroup.size()!=rightLabelsInGroup.size()) { - MITK_INFO(verbose) << "Layer labelset data not equal."; + MITK_INFO(verbose) << "Number of layer labels is not equal. Invalid layer:" <; +ConstLabelMapType ConvertLabelVectorToMap(const mitk::ConstLabelVector& labelV) +{ + ConstLabelMapType result; + for (auto label : labelV) + { + const auto value = label->GetValue(); + auto finding = result.find(value); + if (finding != result.end()) mitkThrow() << "Operation failed. Cannot convert label vector into label map, because at least one label value is not unique. Violating label value: " << value; + result.insert(std::make_pair(value, label)); + } + + return result; +} + + /** Functor class that implements the label transfer and is used in conjunction with the itk::BinaryFunctorImageFilter. * For details regarding the usage of the filter and the functor patterns, please see info of itk::BinaryFunctorImageFilter. */ template class LabelTransferFunctor { public: LabelTransferFunctor() {}; - LabelTransferFunctor(const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, + LabelTransferFunctor(const ConstLabelMapType& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) : - m_DestinationLabelSet(destinationLabelSet), m_SourceBackground(sourceBackground), + m_DestinationLabels(destinationLabels), m_SourceBackground(sourceBackground), m_DestinationBackground(destinationBackground), m_DestinationBackgroundLocked(destinationBackgroundLocked), m_SourceLabel(sourceLabel), m_NewDestinationLabel(newDestinationLabel), m_MergeStyle(mergeStyle), m_OverwriteStyle(overwriteStyle) { }; ~LabelTransferFunctor() {}; bool operator!=(const LabelTransferFunctor& other)const { return !(*this == other); } bool operator==(const LabelTransferFunctor& other) const { return this->m_SourceBackground == other.m_SourceBackground && this->m_DestinationBackground == other.m_DestinationBackground && this->m_DestinationBackgroundLocked == other.m_DestinationBackgroundLocked && this->m_SourceLabel == other.m_SourceLabel && this->m_NewDestinationLabel == other.m_NewDestinationLabel && this->m_MergeStyle == other.m_MergeStyle && this->m_OverwriteStyle == other.m_OverwriteStyle && - this->m_DestinationLabelSet == other.m_DestinationLabelSet; + this->m_DestinationLabels == other.m_DestinationLabels; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { - this->m_DestinationLabelSet = other.m_DestinationLabelSet; + this->m_DestinationLabels = other.m_DestinationLabels; this->m_SourceBackground = other.m_SourceBackground; this->m_DestinationBackground = other.m_DestinationBackground; this->m_DestinationBackgroundLocked = other.m_DestinationBackgroundLocked; this->m_SourceLabel = other.m_SourceLabel; this->m_NewDestinationLabel = other.m_NewDestinationLabel; this->m_MergeStyle = other.m_MergeStyle; this->m_OverwriteStyle = other.m_OverwriteStyle; return *this; } inline TOutputpixel operator()(const TDestinationPixel& existingDestinationValue, const TSourcePixel& existingSourceValue) { if (existingSourceValue == this->m_SourceLabel) { if (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle) { return this->m_NewDestinationLabel; } else { if (existingDestinationValue == m_DestinationBackground) { if (!m_DestinationBackgroundLocked) { return this->m_NewDestinationLabel; } } else { - auto label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); - if (nullptr == label || !label->GetLocked()) + auto labelFinding = this->m_DestinationLabels.find(existingDestinationValue); + if (labelFinding==this->m_DestinationLabels.end() || !labelFinding->second->GetLocked()) { return this->m_NewDestinationLabel; } } } } else if (mitk::MultiLabelSegmentation::MergeStyle::Replace == this->m_MergeStyle && existingSourceValue == this->m_SourceBackground && existingDestinationValue == this->m_NewDestinationLabel && (mitk::MultiLabelSegmentation::OverwriteStyle::IgnoreLocks == this->m_OverwriteStyle || !this->m_DestinationBackgroundLocked)) { return this->m_DestinationBackground; } return existingDestinationValue; } private: - const mitk::LabelSet* m_DestinationLabelSet = nullptr; + ConstLabelMapType m_DestinationLabels; mitk::Label::PixelType m_SourceBackground = 0; mitk::Label::PixelType m_DestinationBackground = 0; bool m_DestinationBackgroundLocked = false; mitk::Label::PixelType m_SourceLabel = 1; mitk::Label::PixelType m_NewDestinationLabel = 1; mitk::MultiLabelSegmentation::MergeStyle m_MergeStyle = mitk::MultiLabelSegmentation::MergeStyle::Replace; mitk::MultiLabelSegmentation::OverwriteStyle m_OverwriteStyle = mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks; }; /**Helper function used by TransferLabelContentAtTimeStep to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentAtTimeStepHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, - const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, + const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, mitk::Label::PixelType sourceLabel, mitk::Label::PixelType newDestinationLabel, mitk::MultiLabelSegmentation::MergeStyle mergeStyle, mitk::MultiLabelSegmentation::OverwriteStyle overwriteStyle) { typedef itk::Image ContentImageType; typename ContentImageType::Pointer itkDestinationImage; mitk::CastToItkImage(destinationImage, itkDestinationImage); auto sourceRegion = itkSourceImage->GetLargestPossibleRegion(); auto relevantRegion = itkDestinationImage->GetLargestPossibleRegion(); bool overlapping = relevantRegion.Crop(sourceRegion); if (!overlapping) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage and destinationImage seem to have no overlapping image region."; } typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; - LabelTransferFunctorType transferFunctor(destinationLabelSet, sourceBackground, destinationBackground, + LabelTransferFunctorType transferFunctor(ConvertLabelVectorToMap(destinationLabels), sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStyle); auto transferFilter = FilterType::New(); transferFilter->SetFunctor(transferFunctor); transferFilter->InPlaceOn(); transferFilter->SetInput1(itkDestinationImage); transferFilter->SetInput2(itkSourceImage); transferFilter->GetOutput()->SetRequestedRegion(relevantRegion); transferFilter->Update(); } void mitk::TransferLabelContentAtTimeStep( - const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, - mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, + const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, const TimeStepType timeStep, mitk::Label::PixelType sourceBackground, + mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage must not be null."; } - if (nullptr == destinationLabelSet) - { - mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationLabelSet must not be null"; - } if (sourceImage == destinationImage && labelMapping.size() > 1) { MITK_DEBUG << "Warning. Using TransferLabelContentAtTimeStep or TransferLabelContent with equal source and destination and more then on label to transfer, can lead to wrong results. Please see documentation and verify that the usage is OK."; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; destinationImage does not have the requested time step: " << timeStep; } + auto destLabelMap = ConvertLabelVectorToMap(destinationLabels); for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { - if (LabelSetImage::UnlabeledValue!=newDestinationLabel && nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) + if (LabelSetImage::UNLABELED_VALUE!=newDestinationLabel && destLabelMap.end() == destLabelMap.find(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } - AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); - destinationLabelSet->ModifyLabelEvent.Send(newDestinationLabel); + AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentAtTimeStepHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabels, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } void mitk::TransferLabelContent( - const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, - mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, + const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabels, mitk::Label::PixelType sourceBackground, + mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; mismatch between images in number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { - TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, i, sourceBackground, + TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, i, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye); } } void mitk::TransferLabelContentAtTimeStep( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, const TimeStepType timeStep, - std::vector > labelMapping, + LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep; sourceImage must not be null."; } - const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); + auto destinationLabels = destinationImage->GetConstLabelsByValue(destinationImage->GetLabelValuesByGroup(destinationImage->GetActiveLayer())); for (const auto& mappingElement : labelMapping) { - if (LabelSetImage::UnlabeledValue != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) + if (LabelSetImage::UNLABELED_VALUE != mappingElement.first && !sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContentAtTimeStep. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } - TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabelSet, timeStep, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, destinationImage->GetUnlabeledLabelLock(), + TransferLabelContentAtTimeStep(sourceImage, destinationImage, destinationLabels, timeStep, LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, destinationImage->GetUnlabeledLabelLock(), labelMapping, mergeStyle, overwriteStlye); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, - std::vector > labelMapping, + LabelValueMappingVector labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } if (nullptr == destinationImage) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage must not be null."; } const auto sourceTimeStepCount = sourceImage->GetTimeGeometry()->CountTimeSteps(); if (sourceTimeStepCount != destinationImage->GetTimeGeometry()->CountTimeSteps()) { mitkThrow() << "Invalid call of TransferLabelContent; images have no equal number of time steps."; } for (mitk::TimeStepType i = 0; i < sourceTimeStepCount; ++i) { TransferLabelContentAtTimeStep(sourceImage, destinationImage, i, labelMapping, mergeStyle, overwriteStlye); } } diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index 709bb63434..dda7565751 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,672 +1,672 @@ /*============================================================================ 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 mitkLabelSetImage_h #define mitkLabelSetImage_h #include -#include +#include +#include +#include +#include #include namespace mitk { - //##Documentation - //## @brief LabelSetImage class for handling labels and layers in a segmentation session. - //## - //## Handles operations for adding, removing, erasing and editing labels and layers. - //## @ingroup Data - + /** @brief LabelSetImage class for handling labels and layers in a segmentation session. + * + * Events that are potentially send by the class in regard to groups or labels: + * - LabelAddedEvent is emitted whenever a new label has been added. + * - LabelModifiedEvent is emitted whenever a label has been modified. + * - LabelRemovedEvent is emitted whenever a label has been removed. + * - LabelsChangedEvent is emitted when labels are changed (added, removed, modified). In difference to the other label events LabelsChanged is send only *one time* after the modification of the + * MultiLableImage instance is finished. So e.g. even if 4 labels are changed by a merge operation, this event will + * only be sent once (compared to LabelRemoved or LabelModified). + * - GroupAddedEvent is emitted whenever a new group has been added. + * - GroupModifiedEvent is emitted whenever a group has been modified. + * - GroupRemovedEvent is emitted whenever a label has been removed. + * + * @ingroup Data + */ class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: - mitkClassMacro(LabelSetImage, Image); - itkNewMacro(Self); - - typedef mitk::Label::PixelType PixelType; - /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // FUTURE MultiLabelSegmentation: // Section that already contains declarations used in the new class. // So this part of the interface will stay after refactoring towards // the new MultiLabelSegmentation class (see T28524). This section was introduced // because some of the planned features are already urgently needed. /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// + mitkClassMacro(LabelSetImage, Image); + itkNewMacro(Self); + + typedef mitk::Label::PixelType PixelType; using GroupIndexType = std::size_t; using LabelValueType = mitk::Label::PixelType; - const static LabelValueType UnlabeledValue; - using ConstLabelVectorType = std::vector; - using LabelVectorType = std::vector; + using ConstLabelVectorType = ConstLabelVector; + using LabelVectorType = LabelVector; using LabelValueVectorType = std::vector; + const static LabelValueType UNLABELED_VALUE; + + /** \brief Adds a label instance to a group of the multi label image. + * @remark By default, if the pixel value of the label is already used in the image, the label + * will get a new none conflicting value assigned. This can be controlled by correctLabelValue. + * @param label Instance of an label that should be added or used as template + * @param groupID The id of the group the label should be added to. + * @param addAsClone Flag that controls, if the passed instance should be added (false; the image will then take ownership, + * be aware that e.g. event observers will be added) + * a clone of the instance (true). + * @param correctLabelValue Flag that controls, if the value of the passed label should be correct, if this value is already used in + * the multi label image. True: Conflicting values will be corrected, be assigning a none conflicting value. False: If the value is conflicting + * an exception will be thrown. + * @return Instance of the label as it was added to the label set. + * @pre label must point to a valid instance. + * @pre If correctLabelValue==false, label value must be non conflicting. + * @pre groupID must indicate an existing group. + */ + mitk::Label* AddLabel(mitk::Label* label, GroupIndexType groupID, bool addAsClone = true, bool correctLabelValue = true); + + /** \brief Adds a new label to a group of the image by providing name and color. + * @param name (Class) name of the label instance that should be added. + * @param color Color of the new label sinstance. + * @param groupID The id of the group the label should be added to. + * @return Instance of the label as it was added to the label set. + * @pre groupID must indicate an existing group. + */ + mitk::Label* AddLabel(const std::string& name, const Color& color, GroupIndexType groupID); + + /** \brief allows to adapt name and color of a certain label + * @param labelValue Value of the label that should be changed + * @param name New name for the label + * @param color New color for the label + * @pre Indicated label value must exist. + */ + void RenameLabel(LabelValueType labelValue, const std::string& name, const Color& color); + /** * @brief Removes the label with the given value. - * The label is removed from the labelset and - * the pixel with the value of the label are set to UnlabeledValue. - * @param labelValue the pixel value of the label to be removed + * The label is removed from the labelset and + * the pixel with the value of the label are set to UNLABELED_VALUE. + * @param labelValue the pixel value of the label to be removed. If the label is unknown, + * the method will return without doing anything. */ void RemoveLabel(LabelValueType labelValue); /** * @brief Removes labels from the mitk::MultiLabelSegmentation. + * The label is removed from the labelset and + * the pixel with the value of the label are set to UNLABELED_VALUE. * If a label value does not exist, it will be ignored. * @param vectorOfLabelPixelValues a list of labels to be removed */ void RemoveLabels(const LabelValueVectorType& vectorOfLabelPixelValues); + /** + * @brief Erases the label with the given value from the labelset image. + * The label itself will not be erased from the respective mitk::LabelSet. In order to + * remove the label itself use mitk::LabelSetImage::RemoveLabels() + * @param labelValue the pixel value of the label that will be erased from the labelset image + * @pre labelValue must exist. + */ + void EraseLabel(LabelValueType labelValue); + + /** + * @brief Erases a list of labels with the given values from the labelset image. + * @param labelValues the list of pixel values of the labels + * that will be erased from the labelset image + * @pre label values must exist + */ + void EraseLabels(const LabelValueVectorType& labelValues); + /** * @brief Removes a whole group including all its labels. - * @remark with removing a group all groups with greater index will be reindexed to - * close the gap. Hence externaly stored spatial group indices may become invalid. + * @remark with removing a group all groups with greater index will be re-indexed to + * close the gap. Hence externally stored spatial group indices may become invalid. * @param group Group index of the spatial group that should be removed. If the spatial group does not exist, an * exception will be raised. * @pre group index must be valid. */ void RemoveGroup(GroupIndexType group); - //future declaration (T28524) currently conflicted with old declaration - ///** - // * \brief Returns true if the value exists in the MultiLabelSegmentation instance*/ - //bool ExistLabel(LabelValueType value) const; + /** \brief Returns true if the value exists in the MultiLabelSegmentation instance*/ + bool ExistLabel(LabelValueType value) const; - ///** - // * @brief Checks if a label belongs in a certain spatial group - // * @param value the label value - // * @param groupIndex Indexp of the spacial group which should be checked for the label - // * @return true if the label exists otherwise false - // */ - //bool ExistLabel(LabelValueType value, GroupIndexType groupIndex) const; + /** + * @brief Checks if a label belongs in a certain spatial group + * @param value the label value + * @param groupIndex Index of the spacial group which should be checked for the label + * @return true if the label exists otherwise false + */ + bool ExistLabel(LabelValueType value, GroupIndexType groupIndex) const; /** * @brief Returns true if the spatial group exists in the MultiLabelSegmentation instance. * * @param index Group index of the group that should be checked for existance. */ bool ExistGroup(GroupIndexType index) const; - bool IsLabelInGroup(LabelValueType value) const; - bool IsLabelInGroup(LabelValueType value, GroupIndexType& groupIndex) const; - /** Returns the group id of the based label value. * @pre label value must exists. */ GroupIndexType GetGroupIndexOfLabel(LabelValueType value) const; /** * @brief Returns the mitk::Label with the given value. * @param value the pixel value of the label - * @return the mitk::Label if available otherwise nullptr + * @return the label instance if defined in the segmentation, otherwise nullptr. */ const mitk::Label* GetLabel(LabelValueType value) const; mitk::Label* GetLabel(LabelValueType value); - /** Returns the lock state of the label (including UnlabeledLabel value). - @pre Requested label does exist.*/ - bool IsLabelLocked(LabelValueType value) const; - - /** Returns a vector with all labels currently defined in the MultiLabelSegmentation + /** Returns a vector with pointers to all labels currently defined in the MultiLabelSegmentation instance.*/ const ConstLabelVectorType GetLabels() const; const LabelVectorType GetLabels(); + /** Returns a vector of all label values currently defined in the MultiLabelSegmentation + instance.*/ + const LabelValueVectorType GetAllLabelValues() const; + + /** @brief Returns a vector with pointers to all labels in the MultiLabelSegmentation indicated + * by the passed label value vector. + * @param labelValues Vector of values of labels that should be returned. + * @ignoreMissing If true(Default) unknown labels Will be skipped in the result. If false, + * an exception will be raised, if a label is requested. + instance.*/ + const LabelVectorType GetLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing = true); + + /** @brief Returns a vector with const pointers to all labels in the MultiLabelSegmentation indicated + * by the passed label value vector. + * For details see GetLabelsByValue(); + */ + const ConstLabelVectorType GetConstLabelsByValue(const LabelValueVectorType& labelValues, bool ignoreMissing = false) const; + + /** Helper function that can be used to extract a vector of label values of a vector of label instance pointers. + @overload.*/ + static LabelValueVectorType ExtractLabelValuesFromLabelVector(const ConstLabelVectorType& labels); + /** Helper function that can be used to extract a vector of label values are vector of label instances.*/ + static LabelValueVectorType ExtractLabelValuesFromLabelVector(const LabelVectorType& labels); + + /** Helper function that converts a given vector of label instance pointers into a vector of const pointers.*/ + static ConstLabelVectorType ConvertLabelVectorConst(const LabelVectorType& labels); + + /** + * @brief Returns a vector of all label values located on the specified group. + * @param index the index of the group for which the vector of labels should be retrieved. + * If an invalid index is passed an exception will be raised. + * @return the respective vector of label values. + * @pre group index must exist. + */ + const LabelValueVectorType GetLabelValuesByGroup(GroupIndexType index) const; + /** - * @brief Returns a vector of all labels located on the specified group. + * @brief Returns a vector of all label values located on the specified group having a certain name. * @param index the index of the group for which the vector of labels should be retrieved. * If an invalid index is passed an exception will be raised. - * @return the respective vector of labels. + * @param name Name of the label instances one is looking for. + * @return the respective vector of label values. * @pre group index must exist. */ - const ConstLabelVectorType GetLabelsInGroup(GroupIndexType index) const; - const LabelVectorType GetLabelsInGroup(GroupIndexType index); + const LabelValueVectorType GetLabelValuesByName(GroupIndexType index, std::string_view name) const; + + /** + * Returns a vector with (class) names of all label instances used in the segmentation (over all groups) + */ + std::vector GetLabelClassNames() const; + + /** + * Returns a vector with (class) names of all label instances present in a certain group. + * @param index ID of the group, for which the label class names should be returned + * @pre Indicated group must exist. */ + std::vector GetLabelClassNamesByGroup(GroupIndexType index) const; + + /** Helper that returns an unused label value, that could be used e.g. if one wants to define a label externally + * before adding it. + * @return A label value currently not in use. + * @remark is no unused label value can be provided an exception will be thrown.*/ + LabelValueType GetUnusedLabelValue() const; itkGetConstMacro(UnlabeledLabelLock, bool); itkSetMacro(UnlabeledLabelLock, bool); itkBooleanMacro(UnlabeledLabelLock); - //////////////////////////////////////////////////////////////////// - //Message slots that allow to react to changes in an instance - - using LabelEventType = Message1; - using LabelsEventType = Message1; - using GroupEventType = Message1; + /** Set the visibility of all label instances accordingly to the passed state. + */ + void SetAllLabelsVisible(bool visible); - /** - * \brief LabelAdded is emitted whenever a new label has been added. - * - * Observers should register to this event by calling this->AddLabelAddedListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. - * Observers should unregister by calling this->RemoveLabelAddedListener(myObject, MyObject::MyMethod). - * The registered method will be called with the label value of the added label. - * @remark the usage of the message object is thread safe. + /** Set the visibility of all label instances in a group accordingly to the passed state. + * @pre The specified group must exist. */ - mitkNewMessage1Macro(LabelAdded, LabelValueType); + void SetAllLabelsVisibleByGroup(GroupIndexType group, bool visible); - /** - * \brief LabelModified is emitted whenever a label has been modified. - * - * A label is modified if either its pixel content was changed, its spatial group or the label instance - * information. - * If you just want to get notified at the end of a MultiLabelSegmentation instance manipulation in the - * case that at least one label was modified (e.g. to avoid getting a signal for each label - * individually), use LabelsChanged instead. - * Observers should register to this event by calling this->AddLabelModifiedListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. - * Observers should unregister by calling this->RemoveLabelModifiedListener(myObject, MyObject::MyMethod). - * The registered method will be called with the label value of the modified label. - * @remark the usage of the message object is thread safe. + /** Set the visibility of all label instances In a group with a given class name + * accordingly to the passed state. + * @pre The specified group must exist. */ - mitkNewMessage1Macro(LabelModified, LabelValueType); + void SetAllLabelsVisibleByName(GroupIndexType group, std::string_view name, bool visible); - /** - * \brief LabelRemoved is emitted whenever a label has been removed. - * - * Observers should register to this event by calling this->AddLabelRemovedListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. - * Observers should unregister by calling this->RemoveLabelRemovedListener(myObject, MyObject::MyMethod). - * The registered method will be called with the label value of the removed label.* - * @remark the usage of the message object is thread safe. + /** Returns the lock state of the label (including UnlabeledLabel value). + @pre Requested label does exist.*/ + bool IsLabelLocked(LabelValueType value) const; + + /** Set the lock state of all label instances accordingly to the passed state. */ - mitkNewMessage1Macro(LabelRemoved, LabelValueType); + void SetAllLabelsLocked(bool locked); - /** - * \brief LabelsChanged is emitted when labels are changed (added, removed, modified). - * - * In difference to the other label events LabelsChanged is send only *one time* after the modification of the - * MultiLableImage instance is finished. So e.g. even if 4 labels are changed by a merge operation, this event will - * only be sent once (compared to LabelRemoved or LabelModified). - * Observers should register to this event by calling myMultiLabelSegmentation->AddLabelsChangedListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been removed from the MultiLabelSegmentation. - * Observers should unregister by calling myMultiLabelSegmentation->RemoveLabelsChangedListener(myObject, - * MyObject::MyMethod). - * The registered method will be called with the vector of label values of the modified labels.* - * @remark the usage of the message object is thread safe. + /** Set the lock state of all label instances in a group accordingly to the passed state. + * @pre The specified group must exist. */ - mitkNewMessage1Macro(LabelsChanged, LabelValueVectorType); + void SetAllLabelsLockedByGroup(GroupIndexType group, bool locked); - /** - * \brief GroupAdded is emitted whenever a new group has been added. - * - * Observers should register to this event by calling this->AddGroupAddedListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new group has been added to the MultiLabelSegmentation. - * Observers should unregister by calling this->RemoveGroupAddedListener(myObject, MyObject::MyMethod). - * The registered method will be called with the group index of the added group. - * @remark the usage of the message object is thread safe. + /** Set the lock state of all label instances In a group with a given class name + * accordingly to the passed state. + * @pre The specified group must exist. */ - mitkNewMessage1Macro(GroupAdded, GroupIndexType); + void SetAllLabelsLockedByName(GroupIndexType group, std::string_view name, bool locked); /** - * \brief GroupModified is emitted whenever a group has been modified. + * \brief Replaces the labels of a group with a given vector of labels. * - * A group is modified if the set of labels associated with it are changed or the group's meta data. - * Observers should register to this event by calling this->AddGroupModifiedListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. - * Observers should unregister by calling this->RemoveGroupModifiedListener(myObject, MyObject::MyMethod). - * The registered method will be called with the group index of the added group. - * @remark the usage of the message object is thread safe. + * @remark The passed label instances will be cloned before added to ensure clear ownership + * of the new labels. + * @remark The pixel content of the old labels will not be removed. + * @param groupID The index of the group that should have its labels replaced + * @param newLabels The vector of new labels + * @pre Group that should be replaced must exist. + * @pre new label values must not be used in other groups. */ - mitkNewMessage1Macro(GroupModified, GroupIndexType); + void ReplaceGroupLabels(const GroupIndexType groupID, const ConstLabelVectorType& newLabels); - /** - * \brief GroupRemoved is emitted whenever a label has been removed. - * - * Observers should register to this event by calling this->AddGroupRemovedListener(myObject, - * MyObject::MyMethod). - * After registering, myObject->MyMethod() will be called every time a new label has been added to the MultiLabelSegmentation. - * Observers should unregister by calling this->RemoveGroupRemovedListener(myObject, MyObject::MyMethod). - * The registered method will be called with the group index of the removed group.* - * @remark the usage of the message object is thread safe. + /**@overload for none-const label vectors. */ + void ReplaceGroupLabels(const GroupIndexType groupID, const LabelVectorType& newLabels); + + /** Returns the pointer to the image that contains the labeling of the indicate group. + *@pre groupID must reference an existing group.*/ + mitk::Image* GetGroupImage(GroupIndexType groupID); + + /** Returns the pointer to the image that contains the labeling of the indicate group. + *@pre groupID must reference an existing group.*/ + const mitk::Image* GetGroupImage(GroupIndexType groupID) const; + + itkGetModifiableObjectMacro(LookupTable, mitk::LookupTable); + void SetLookupTable(LookupTable* lut); + /** Updates the lookup table for a label indicated by the passed label value using the color of the label. + * @pre labelValue must exist. */ - mitkNewMessage1Macro(GroupRemoved, GroupIndexType); + void UpdateLookupTable(PixelType pixelValue); protected: - void OnLabelAdded(LabelValueType labelValue); - void AddLabelToMap(LabelValueType labelValue, mitk::Label* label, GroupIndexType groupID); - void OnLabelModified(LabelValueType labelValue); - void OnLabelRemoved(LabelValueType labelValue); - void OnGroupAdded(GroupIndexType groupIndex); - void OnGroupModified(GroupIndexType groupIndex); - void OnGroupRemoved(GroupIndexType groupIndex); + void OnLabelModified(const Object* sender, const itk::EventObject&); - /** Reeinitalizes the internal maps based on the current layer/label content - * of the instance. */ - void ReinitMaps(); + /** Helper to ensure that the maps are correctly populated for a new label instance.*/ + void AddLabelToMap(LabelValueType labelValue, Label* label, GroupIndexType groupID); + void RemoveLabelFromMap(LabelValueType labelValue); + /** Helper to ensure label events are correctly connected and lookup table is updated for a new label instance.*/ + void RegisterLabel(Label* label); + /** Helper to ensure label events are unregistered.*/ + void ReleaseLabel(Label* label); + /** Helper class used internally to apply lambda functions to the labels specified by the passed label value vector. + */ + void ApplyToLabels(const LabelValueVectorType& values, std::function&& lambda); + /** Helper class used internally to for visiting the labels specified by the passed label value vector + * with the lambda function. + */ + void VisitLabels(const LabelValueVectorType& values, std::function&& lambda) const; + + LabelValueType m_ActiveLabelValue; + + private: using LabelMapType = std::map; + /** Dictionary that holds all known labels (label value is the key).*/ LabelMapType m_LabelMap; + using GroupNameVectorType = std::vector; + /** Vector storing the names of all groups. If a group has no user name defined, string is empty.*/ + GroupNameVectorType m_Groups; + /**This type is internally used to track which label is currently * associated with which layer.*/ - using GroupToLabelMapType = std::map; + using GroupToLabelMapType = std::vector; + /* Dictionary that maps between group id (key) and label values in the group (vector of label value).*/ GroupToLabelMapType m_GroupToLabelMap; using LabelToGroupMapType = std::map; + /* Dictionary that maps between label value (key) and group id (value)*/ LabelToGroupMapType m_LabelToGroupMap; - private: + LookupTable::Pointer m_LookupTable; + /** Indicates if the MultiLabelSegmentation allows to overwrite unlabeled pixels in normal pixel manipulation operations (e.g. TransferLabelConent).*/ bool m_UnlabeledLabelLock; public: - /** - * \brief */ - void UpdateCenterOfMass(PixelType pixelValue); /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// // END FUTURE MultiLabelSegmentation /////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////// + /** + * \brief */ + void UpdateCenterOfMass(PixelType pixelValue); /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; void Initialize(const mitk::Image *image) override; /** - * \brief */ + * \brief removes all pixel content form the active layer.*/ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one - * @param layer the layer in which the merge should be performed */ - void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer = 0); + void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one - * @param layer the layer in which the merge should be performed - */ - void MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues, unsigned int layer = 0); - - /** - * \brief */ - void UpdateCenterOfMass(PixelType pixelValue, unsigned int layer); - - /** - * @brief Erases the label with the given value from the labelset image. - * The label itself will not be erased from the respective mitk::LabelSet. In order to - * remove the label itself use mitk::LabelSetImage::RemoveLabels() - * @param pixelValue the pixel value of the label that will be erased from the labelset image - */ - void EraseLabel(PixelType pixelValue); - - /** - * @brief Erases a list of labels with the given values from the labelset image. - * @param VectorOfLabelPixelValues the list of pixel values of the labels - * that will be erased from the labelset image - */ - void EraseLabels(const std::vector &VectorOfLabelPixelValues); - - /** - * \brief Returns true if the value exists in one of the labelsets*/ - //[[deprecated("Will be changed with T28524")]] - DEPRECATED(bool ExistLabel(PixelType pixelValue) const); - - /** - * @brief Checks if a label exists in a certain layer - * @param pixelValue the label value - * @param layer the layer in which should be searched for the label - * @return true if the label exists otherwise false - */ - //[[deprecated("Will be changed with T28524")]] - DEPRECATED(bool ExistLabel(PixelType pixelValue, unsigned int layer) const); - - /** - * \brief Returns true if the labelset exists*/ - //[[deprecated("Will be removed with T28524")]] - DEPRECATED(bool ExistLabelSet(unsigned int layer) const); - - /** - * @brief Returns the active label of a specific layer - * @param layer the layer ID for which the active label should be returned - * @return the active label of the specified layer - */ - //[[deprecated("Will be removed with T28524")]] - DEPRECATED(mitk::Label *GetActiveLabel(unsigned int layer = 0)); - //[[deprecated("Will be removed with T28524")]] - DEPRECATED(const mitk::Label* GetActiveLabel(unsigned int layer = 0) const); - - /** - * @brief Returns the mitk::Label with the given pixelValue and for the given layer - * @param pixelValue the pixel value of the label - * @param layer the layer in which the labels should be located - * @return the mitk::Label if available otherwise nullptr - */ - mitk::Label *GetLabel(PixelType pixelValue, unsigned int layer) const; - - /** - * @brief Returns the currently active mitk::LabelSet - * @return the mitk::LabelSet of the active layer or nullptr if non is present */ - //[[deprecated ("Will be removed with T28524")]] - DEPRECATED(mitk::LabelSet *GetActiveLabelSet()); - //[[deprecated("Will be removed with T28524")]] - DEPRECATED(const mitk::LabelSet* GetActiveLabelSet() const); - - /** - * @brief Gets the mitk::LabelSet for the given layer - * @param layer the layer for which the mitk::LabelSet should be retrieved - * @return the respective mitk::LabelSet or nullptr if non exists for the given layer - */ - mitk::LabelSet *GetLabelSet(unsigned int layer = 0); - const mitk::LabelSet *GetLabelSet(unsigned int layer = 0) const; + void MergeLabels(PixelType pixelValue, const std::vector& vectorOfSourcePixelValues); /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer + * @pre at least on group must exist. */ unsigned int GetActiveLayer() const; + /** \brief +*/ + Label* GetActiveLabel(); + /** \brief + */ + const Label* GetActiveLabel() const; + /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ - unsigned int GetNumberOfLabels(unsigned int layer = 0) const; + unsigned int GetNumberOfLabels(unsigned int layer) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; - // This function will need to be ported to an external class - // it requires knowledge of pixeltype and dimension and includes - // too much algorithm to be sensibly part of a data class - ///** - // * \brief */ - // void SurfaceStamp(mitk::Surface* surface, bool forceOverwrite); - /** * \brief */ - mitk::Image::Pointer CreateLabelMask(PixelType index, bool useActiveLayer = true, unsigned int layer = 0); + mitk::Image::Pointer CreateLabelMask(PixelType index); /** - * @brief Initialize a new mitk::LabelSetImage by an given image. + * @brief Initialize a new mitk::LabelSetImage by a given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE - * a new layer will be created + * an exception will be raised. * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); /** * \brief */ void MaskStamp(mitk::Image *mask, bool forceOverwrite); /** * \brief */ void SetActiveLayer(unsigned int layer); + void SetActiveLabel(LabelValueType label); /** * \brief */ unsigned int GetNumberOfLayers() const; /** * \brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one. * \param labelSet a labelset that will be added to the new layer if provided * \return the layer ID of the new layer */ - unsigned int AddLayer(mitk::LabelSet::Pointer labelSet = nullptr); + GroupIndexType AddLayer(ConstLabelVector labels = {}); /** * \brief Adds a layer based on a provided mitk::Image. * \param layerImage is added to the vector of label images - * \param labelSet a labelset that will be added to the new layer if provided + * \param labels labels that will be cloned and added to the new layer if provided * \return the layer ID of the new layer */ - unsigned int AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet = nullptr); - - /** - * \brief Add a cloned LabelSet to an existing layer - * - * Remark: The passed LabelSet instance will be cloned before added to ensure clear ownership - * of the new LabelSet addition. - * - * This will replace an existing labelSet if one exists. Throws an exceptions if you are trying - * to add a labelSet to a non-existing layer. - * - * If there are no labelSets for layers with an id less than layerIdx default ones will be added - * for them. - * - * \param layerIdx The index of the layer the LabelSet should be added to - * \param labelSet The LabelSet that should be added - */ - void AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet* labelSet); - - /** - * @brief Removes the active layer and the respective mitk::LabelSet and image information. - * The new active layer is the one below, if exists - */ - void RemoveLayer(); - - /** - * \brief */ - mitk::Image *GetLayerImage(unsigned int layer); - - const mitk::Image *GetLayerImage(unsigned int layer) const; - - void OnLabelSetModified(); + GroupIndexType AddLayer(mitk::Image::Pointer layerImage, ConstLabelVector labels = {}); protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template - void CalculateCenterOfMassProcessing(ImageType *input, PixelType index, unsigned int layer); - - template - void ClearBufferProcessing(ImageType *input); + void CalculateCenterOfMassProcessing(ImageType *input, LabelValueType index); template void EraseLabelProcessing(ImageType *input, PixelType index); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); - /** helper needed for ensuring unique values in all layers until the refactoring is done. - returns a sorted list of all labels.*/ + /** helper needed for ensuring unique values. + returns a sorted list of all labels (including the value for Unlabeled pixels..*/ LabelValueVectorType GetUsedLabelValues() const; - //helper function that ensures - void RegisterLabelSet(mitk::LabelSet* ls); - void ReleaseLabelSet(mitk::LabelSet* ls); - - std::vector m_LabelSetContainer; std::vector m_LayerContainer; int m_ActiveLayer; - bool m_activeLayerInvalid; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); + /** + * @brief Equal A function comparing two vectors of labels for being equal in data + * + * @ingroup MITKTestingAPI + * + * Following aspects are tested for equality: + * - Labels in vector + * + * @param rightHandSide An vector of labels to be compared + * @param leftHandSide An vector of labels to be compared + * @param eps Tolerance for comparison. You can use mitk::eps in most cases. + * @param verbose Flag indicating if the user wants detailed console output or not. + * @return true, if all subsequent comparisons are true, false otherwise + */ + MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage::ConstLabelVectorType& leftHandSide, + const mitk::LabelSetImage::ConstLabelVectorType& rightHandSide, + ScalarType eps, + bool verbose); - /** temporery namespace that is used until the new class MultiLabelSegmentation is + + /** temporary namespace that is used until the new class MultiLabelSegmentation is introduced. It allows to already introduce/use some upcoming definitions, while refactoring code.*/ namespace MultiLabelSegmentation { enum class MergeStyle { Replace, //The old label content of a label value will be replaced by its new label content. //Therefore pixels that are labeled might become unlabeled again. //(This means that a lock of the value is also ignored). Merge //The union of old and new label content will be generated. }; enum class OverwriteStyle { RegardLocks, //Locked labels in the same spatial group will not be overwritten/changed. IgnoreLocks //Label locks in the same spatial group will be ignored, so these labels might be changed. }; } + using LabelValueMappingVector = std::vector < std::pair >; + /**Helper function that transfers pixels of the specified source label from source image to the destination image by using - a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. + a specified destination label for a specific time step. Function processes the whole image volume of the specified time step. @remark in its current implementation the function only transfers contents of the active layer of the passed LabelSetImages. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save if sourceImage and destinationImage are the same instance and more than one label is transferred, because the changes are made in-place for performance reasons in multiple passes. If a mapped value A equals an "old value" that occurs later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then later again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the LabelSetImage which active layer should be used as source for the transfer. @param destinationImage Pointer to the LabelSetImage which active layer should be used as destination for the transfer. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. - If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that + If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transferring), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. @pre sourceImage and destinationImage must be valid @pre sourceImage and destinationImage must contain the indicated timeStep @pre sourceImage must contain all indicated sourceLabels in its active layer. @pre destinationImage must contain all indicated destinationLabels in its active layer.*/ MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, - const TimeStepType timeStep, std::vector > labelMapping = { {1,1} }, + const TimeStepType timeStep, LabelValueMappingVector labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep for LabelSetImages. @sa TransferLabelContentAtTimeStep*/ - MITKMULTILABEL_EXPORT void TransferLabelContent(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping = { {1,1} }, + MITKMULTILABEL_EXPORT void TransferLabelContent(const LabelSetImage* sourceImage, LabelSetImage* destinationImage, LabelValueMappingVector labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using - a specified destination label for a specific timestep. Function processes the whole image volume of the specified time step. + a specified destination label for a specific time step. Function processes the whole image volume of the specified time step. @remark the function assumes that it is only called with source and destination image of same geometry. @remark CAUTION: The function is not save, if sourceImage and destinationImage are the same instance and you transfer more then one - label, because the changes are made inplace for performance reasons but not in one pass. If a mapped value A equals a "old value" + label, because the changes are made in-place for performance reasons but not in one pass. If a mapped value A equals a "old value" that is later in the mapping, one ends up with a wrong transfer, as a pixel would be first mapped to A and then latter again, because it is also an "old" value in the mapping table. @param sourceImage Pointer to the image that should be used as source for the transfer. @param destinationImage Pointer to the image that should be used as destination for the transfer. - @param destinationLabelSet Pointer to the label set specifying labels and lock states in the destination image. Unkown pixel + @param destinationLabelVector Reference to the vector of labels (incl. lock states) in the destination image. Unknown pixel values in the destinationImage will be assumed to be unlocked. @param sourceBackground Value indicating the background in the source image. @param destinationBackground Value indicating the background in the destination image. @param destinationBackgroundLocked Value indicating the lock state of the background in the destination image. @param labelMapping Map that encodes the mappings of all label pixel transfers that should be done. First element is the label in the source image. The second element is the label that transferred pixels should become in the destination image. The order in which the labels will be transfered is the same order of elements in the labelMapping. - If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transfering), keep in mind that + If you use a heterogeneous label mapping (e.g. (1,2); so changing the label while transferring), keep in mind that for the MergeStyle and OverwriteStyle only the destination label (second element) is relevant (e.g. what should be altered with MergeStyle Replace). @param mergeStyle indicates how the transfer should be done (merge or replace). For more details see documentation of MultiLabelSegmentation::MergeStyle. @param overwriteStlye indicates if label locks in the destination image should be regarded or not. For more details see documentation of MultiLabelSegmentation::OverwriteStyle. @param timeStep indicate the time step that should be transferred. - @pre sourceImage, destinationImage and destinationLabelSet must be valid + @pre sourceImage, destinationImage and destinationLabelVector must be valid @pre sourceImage and destinationImage must contain the indicated timeStep - @pre destinationLabelSet must contain all indicated destinationLabels for mapping.*/ - MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, - const TimeStepType timeStep, mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, - mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, + @pre destinationLabelVector must contain all indicated destinationLabels for mapping.*/ + MITKMULTILABEL_EXPORT void TransferLabelContentAtTimeStep(const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabelVector, + const TimeStepType timeStep, mitk::Label::PixelType sourceBackground = LabelSetImage::UNLABELED_VALUE, + mitk::Label::PixelType destinationBackground = LabelSetImage::UNLABELED_VALUE, bool destinationBackgroundLocked = false, - std::vector > labelMapping = { {1,1} }, + LabelValueMappingVector labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); /**Helper function that transfers pixels of the specified source label from source image to the destination image by using a specified destination label. Function processes the whole image volume for all time steps. For more details please see TransferLabelContentAtTimeStep. @sa TransferLabelContentAtTimeStep*/ - MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, - mitk::Label::PixelType sourceBackground = LabelSetImage::UnlabeledValue, - mitk::Label::PixelType destinationBackground = LabelSetImage::UnlabeledValue, + MITKMULTILABEL_EXPORT void TransferLabelContent(const Image* sourceImage, Image* destinationImage, const mitk::ConstLabelVector& destinationLabelVector, + mitk::Label::PixelType sourceBackground = LabelSetImage::UNLABELED_VALUE, + mitk::Label::PixelType destinationBackground = LabelSetImage::UNLABELED_VALUE, bool destinationBackgroundLocked = false, - std::vector > labelMapping = { {1,1} }, + LabelValueMappingVector labelMapping = { {1,1} }, MultiLabelSegmentation::MergeStyle mergeStyle = MultiLabelSegmentation::MergeStyle::Replace, MultiLabelSegmentation::OverwriteStyle overwriteStlye = MultiLabelSegmentation::OverwriteStyle::RegardLocks); } // namespace mitk #endif diff --git a/Modules/Multilabel/mitkLabelSetImageConverter.cpp b/Modules/Multilabel/mitkLabelSetImageConverter.cpp index b994182c38..c571726cea 100644 --- a/Modules/Multilabel/mitkLabelSetImageConverter.cpp +++ b/Modules/Multilabel/mitkLabelSetImageConverter.cpp @@ -1,198 +1,194 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include template static void ConvertLabelSetImageToImage(const itk::Image *, mitk::LabelSetImage::ConstPointer labelSetImage, mitk::Image::Pointer &image) { typedef itk::Image ImageType; typedef itk::ComposeImageFilter ComposeFilterType; typedef itk::ImageDuplicator DuplicatorType; auto numberOfLayers = labelSetImage->GetNumberOfLayers(); if (numberOfLayers > 1) { auto vectorImageComposer = ComposeFilterType::New(); - auto activeLayer = labelSetImage->GetActiveLayer(); for (decltype(numberOfLayers) layer = 0; layer < numberOfLayers; ++layer) { auto layerImage = mitk::ImageToItkImage( - layer != activeLayer ? labelSetImage->GetLayerImage(layer) : labelSetImage); + labelSetImage->GetGroupImage(layer)); vectorImageComposer->SetInput(layer, layerImage); } vectorImageComposer->Update(); // mitk::GrabItkImageMemory does not support 4D, this will handle 4D correctly // and create a memory managed copy image = mitk::ImportItkImage(vectorImageComposer->GetOutput())->Clone(); } else { auto layerImage = mitk::ImageToItkImage(labelSetImage); auto duplicator = DuplicatorType::New(); duplicator->SetInputImage(layerImage); duplicator->Update(); // mitk::GrabItkImageMemory does not support 4D, this will handle 4D correctly // and create a memory managed copy image = mitk::ImportItkImage(duplicator->GetOutput())->Clone(); } } mitk::Image::Pointer mitk::ConvertLabelSetImageToImage(LabelSetImage::ConstPointer labelSetImage) { Image::Pointer image; if (labelSetImage->GetNumberOfLayers() > 0) { if (labelSetImage->GetDimension() == 4) { AccessFixedDimensionByItk_n(labelSetImage, ::ConvertLabelSetImageToImage, 4, (labelSetImage, image)); } else { - AccessByItk_2(labelSetImage->GetLayerImage(0), ::ConvertLabelSetImageToImage, labelSetImage, image); + AccessByItk_2(labelSetImage->GetGroupImage(0), ::ConvertLabelSetImageToImage, labelSetImage, image); } image->SetTimeGeometry(labelSetImage->GetTimeGeometry()->Clone()); } return image; } template static void SplitVectorImage(const itk::VectorImage* image, std::vector& result) { typedef itk::VectorImage VectorImageType; typedef itk::Image ImageType; typedef itk::VectorIndexSelectionCastImageFilter VectorIndexSelectorType; auto numberOfLayers = image->GetVectorLength(); for (decltype(numberOfLayers) layer = 0; layer < numberOfLayers; ++layer) { auto layerSelector = VectorIndexSelectorType::New(); layerSelector->SetInput(image); layerSelector->SetIndex(layer); layerSelector->Update(); mitk::Image::Pointer layerImage = mitk::GrabItkImageMemoryChannel(layerSelector->GetOutput(), nullptr, nullptr, false); result.push_back(layerImage); } } std::vector mitk::SplitVectorImage(const Image* vecImage) { if (nullptr == vecImage) { mitkThrow() << "Invalid usage; nullptr passed to SplitVectorImage."; } if (vecImage->GetChannelDescriptor().GetPixelType().GetPixelType() != itk::IOPixelEnum::VECTOR) { mitkThrow() << "Invalid usage of SplitVectorImage; passed image is not a vector image. Present pixel type: "<< vecImage->GetChannelDescriptor().GetPixelType().GetPixelTypeAsString(); } std::vector result; if (4 == vecImage->GetDimension()) { AccessVectorFixedDimensionByItk_n(vecImage, ::SplitVectorImage, 4, (result)); } else { AccessVectorPixelTypeByItk_n(vecImage, ::SplitVectorImage, (result)); } for (auto image : result) { image->SetTimeGeometry(vecImage->GetTimeGeometry()->Clone()); } return result; } mitk::LabelSetImage::Pointer mitk::ConvertImageToLabelSetImage(Image::Pointer image) { std::vector groupImages; if (image.IsNotNull()) { if (image->GetChannelDescriptor().GetPixelType().GetPixelType() == itk::IOPixelEnum::VECTOR) { groupImages = SplitVectorImage(image); } else { groupImages.push_back(image); } } auto labelSetImage = ConvertImageVectorToLabelSetImage(groupImages, image->GetTimeGeometry()); return labelSetImage; } mitk::LabelSetImage::Pointer mitk::ConvertImageVectorToLabelSetImage(const std::vector& images, const mitk::TimeGeometry* timeGeometry) { LabelSetImage::Pointer labelSetImage = mitk::LabelSetImage::New(); for (auto& groupImage : images) { if (groupImage== images.front()) { labelSetImage->InitializeByLabeledImage(groupImage); } else { labelSetImage->AddLayer(groupImage); } } labelSetImage->SetTimeGeometry(timeGeometry->Clone()); return labelSetImage; } -mitk::LabelSet::Pointer mitk::GenerateLabelSetWithMappedValues(const LabelSet* sourceLabelset, std::vector > labelMapping) +mitk::LabelSetImage::LabelVectorType mitk::GenerateLabelSetWithMappedValues(const LabelSetImage::ConstLabelVectorType& sourceLabelset, LabelValueMappingVector labelMapping) { - if (nullptr == sourceLabelset) - { - mitkThrow() << "Invalid usage; nullptr passed as labelset to GenerateLabelSetWithMappedValues."; - } - - auto result = LabelSet::New(); + LabelSetImage::LabelVectorType result; - for (auto [sourceLabelID, destLabelID] : labelMapping) + for (auto oldLabel : sourceLabelset) { - auto clonedLabel = sourceLabelset->GetLabel(sourceLabelID)->Clone(); - clonedLabel->SetValue(destLabelID); - result->AddLabel(clonedLabel, false); + auto finding = std::find_if(labelMapping.begin(), labelMapping.end(), [oldLabel](const std::pair& mapping) {return oldLabel->GetValue() == mapping.first; }); + if (finding != labelMapping.end()) + { + auto clonedLabel = oldLabel->Clone(); + clonedLabel->SetValue(finding->second); + result.push_back(clonedLabel); + } } - result->SetLayer(sourceLabelset->GetLayer()); - return result; } diff --git a/Modules/Multilabel/mitkLabelSetImageConverter.h b/Modules/Multilabel/mitkLabelSetImageConverter.h index 1e1b577d79..09121dd627 100644 --- a/Modules/Multilabel/mitkLabelSetImageConverter.h +++ b/Modules/Multilabel/mitkLabelSetImageConverter.h @@ -1,40 +1,40 @@ /*============================================================================ 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 mitkLabelSetImageConverter_h #define mitkLabelSetImageConverter_h #include namespace mitk { /** * \brief Convert mitk::LabelSetImage to mitk::Image (itk::VectorImage) */ MITKMULTILABEL_EXPORT Image::Pointer ConvertLabelSetImageToImage(LabelSetImage::ConstPointer labelSetImage); /** * \brief Convert mitk::Image to mitk::LabelSetImage, templating and differentation between itk::Image and * itk::VectorImage is internal */ MITKMULTILABEL_EXPORT LabelSetImage::Pointer ConvertImageToLabelSetImage(Image::Pointer image); MITKMULTILABEL_EXPORT LabelSetImage::Pointer ConvertImageVectorToLabelSetImage(const std::vector& images, const TimeGeometry* timeGeometry); MITKMULTILABEL_EXPORT std::vector SplitVectorImage(const Image* vecImage); - /** Function takes a label set and transfers all labels indicated in the label mapping (first element of pair) into a result label set. In the result label set - the cloned labels will have the label value indicated by the mapping (second element of pair). + /** Function takes a vector of labels and transfers all labels as clones with adapted label values to the result vector. + The values will be adapted according to the provided mapping (key is the old value, value the new). @remark: Only labels will be transfered, nothing else. So things like message observers or m_ReservedLabelValuesFunctor must be copied explicitly.*/ - MITKMULTILABEL_EXPORT LabelSet::Pointer GenerateLabelSetWithMappedValues(const LabelSet* sourceLabelset, std::vector > labelMapping); + MITKMULTILABEL_EXPORT LabelSetImage::LabelVectorType GenerateLabelSetWithMappedValues(const LabelSetImage::ConstLabelVectorType&, LabelValueMappingVector labelMapping); } #endif diff --git a/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp b/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp index d0388bf099..4bcf1430d9 100644 --- a/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp +++ b/Modules/Multilabel/mitkLabelSetImageSurfaceStampFilter.cpp @@ -1,108 +1,108 @@ /*============================================================================ 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 "mitkLabelSetImageSurfaceStampFilter.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include #include #include #include mitk::LabelSetImageSurfaceStampFilter::LabelSetImageSurfaceStampFilter() : m_ForceOverwrite(false) { this->SetNumberOfIndexedInputs(1); this->SetNumberOfRequiredInputs(1); } mitk::LabelSetImageSurfaceStampFilter::~LabelSetImageSurfaceStampFilter() { } void mitk::LabelSetImageSurfaceStampFilter::GenerateData() { // GenerateOutputInformation(); this->SetNthOutput(0, this->GetInput(0)); mitk::Image::Pointer inputImage = this->GetInput(0); if (m_Surface.IsNull()) { MITK_ERROR << "Input surface is nullptr."; return; } mitk::SurfaceToImageFilter::Pointer surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->MakeOutputBinaryOn(); surfaceToImageFilter->SetInput(m_Surface); surfaceToImageFilter->SetImage(inputImage); surfaceToImageFilter->Update(); mitk::Image::Pointer resultImage = surfaceToImageFilter->GetOutput(); AccessByItk_1(inputImage, ItkImageProcessing, resultImage); inputImage->DisconnectPipeline(); } template void mitk::LabelSetImageSurfaceStampFilter::ItkImageProcessing(itk::Image *itkImage, mitk::Image::Pointer resultImage) { typedef itk::Image ImageType; mitk::LabelSetImage::Pointer LabelSetInputImage = dynamic_cast(GetInput()); try { typename ImageType::Pointer itkResultImage = ImageType::New(); mitk::CastToItkImage(resultImage, itkResultImage); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkResultImage, itkResultImage->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); - int activeLabel = (LabelSetInputImage->GetActiveLabel(LabelSetInputImage->GetActiveLayer()))->GetValue(); + int activeLabel = LabelSetInputImage->GetActiveLabel()->GetValue(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); auto targetValue = static_cast(targetIter.Get()); - if ((sourceValue != LabelSetImage::UnlabeledValue) && + if ((sourceValue != LabelSetImage::UNLABELED_VALUE) && (m_ForceOverwrite || !LabelSetInputImage->GetLabel(targetValue)->GetLocked())) // skip unlabled pixels and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImageSurfaceStampFilter::GenerateOutputInformation() { mitk::Image::Pointer inputImage = (mitk::Image *)this->GetInput(); mitk::Image::Pointer output = this->GetOutput(); itkDebugMacro(<< "GenerateOutputInformation()"); if (inputImage.IsNull()) return; } diff --git a/Modules/Multilabel/mitkLabelSetImageToSurfaceThreadedFilter.cpp b/Modules/Multilabel/mitkLabelSetImageToSurfaceThreadedFilter.cpp index e70aa8ecd7..ae1caca213 100644 --- a/Modules/Multilabel/mitkLabelSetImageToSurfaceThreadedFilter.cpp +++ b/Modules/Multilabel/mitkLabelSetImageToSurfaceThreadedFilter.cpp @@ -1,130 +1,130 @@ /*============================================================================ 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 "mitkLabelSetImageToSurfaceThreadedFilter.h" #include "mitkLabelSetImage.h" #include "mitkLabelSetImageToSurfaceFilter.h" namespace mitk { LabelSetImageToSurfaceThreadedFilter::LabelSetImageToSurfaceThreadedFilter() : m_RequestedLabel(1), m_Result(nullptr) { } LabelSetImageToSurfaceThreadedFilter::~LabelSetImageToSurfaceThreadedFilter() {} void LabelSetImageToSurfaceThreadedFilter::Initialize(const NonBlockingAlgorithm *other) { Superclass::Initialize(other); } bool LabelSetImageToSurfaceThreadedFilter::ReadyToRun() { Image::Pointer image; GetPointerParameter("Input", image); return image.IsNotNull() && GetGroupNode(); } bool LabelSetImageToSurfaceThreadedFilter::ThreadedUpdateFunction() { LabelSetImage::Pointer image; this->GetPointerParameter("Input", image); // ProcessObserver::Pointer obsv; /* try { this->GetPointerParameter("Observer", obsv); } catch (std::invalid_argument&) { // MITK_WARN << "None observer provided."; } */ bool useSmoothing(false); try { this->GetParameter("Smooth", useSmoothing); } catch (std::invalid_argument &) { MITK_WARN << "\"Smooth\" parameter was not set: will use the default value (" << useSmoothing << ")."; } try { this->GetParameter("RequestedLabel", m_RequestedLabel); } catch (std::invalid_argument &) { MITK_WARN << "\"RequestedLabel\" parameter was not set: will use the default value (" << m_RequestedLabel << ")."; } mitk::LabelSetImageToSurfaceFilter::Pointer filter = mitk::LabelSetImageToSurfaceFilter::New(); filter->SetInput(image); // filter->SetObserver(obsv); filter->SetGenerateAllLabels(false); filter->SetRequestedLabel(m_RequestedLabel); filter->SetUseSmoothing(useSmoothing); try { filter->Update(); } catch (itk::ExceptionObject &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); return false; } catch (std::exception &e) { MITK_ERROR << "Exception caught: " << e.what(); return false; } catch (...) { MITK_ERROR << "Unknown exception caught"; return false; } m_Result = filter->GetOutput(); if (m_Result.IsNull() || !m_Result->GetVtkPolyData()) return false; m_Result->DisconnectPipeline(); return true; } void LabelSetImageToSurfaceThreadedFilter::ThreadedUpdateSuccessful() { LabelSetImage::Pointer image; this->GetPointerParameter("Input", image); std::string name = this->GetGroupNode()->GetName(); name.append("-surf"); mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(m_Result); node->SetName(name); - mitk::Color color = image->GetLabel(m_RequestedLabel, image->GetActiveLayer())->GetColor(); + mitk::Color color = image->GetLabel(m_RequestedLabel)->GetColor(); node->SetColor(color); this->InsertBelowGroupNode(node); Superclass::ThreadedUpdateSuccessful(); } } // namespace diff --git a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp index 9436894b13..936e561891 100644 --- a/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp +++ b/Modules/Multilabel/mitkLabelSetImageVtkMapper2D.cpp @@ -1,649 +1,643 @@ /*============================================================================ 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 "mitkLabelSetImageVtkMapper2D.h" // MITK #include #include #include #include #include #include #include #include #include #include #include #include #include // MITK Rendering #include "vtkMitkLevelWindowFilter.h" #include "vtkMitkThickSlicesFilter.h" #include "vtkNeverTranslucentTexture.h" // VTK #include #include #include #include #include #include #include #include #include #include #include #include //#include // ITK #include #include mitk::LabelSetImageVtkMapper2D::LabelSetImageVtkMapper2D() { } mitk::LabelSetImageVtkMapper2D::~LabelSetImageVtkMapper2D() { } vtkProp *mitk::LabelSetImageVtkMapper2D::GetVtkProp(mitk::BaseRenderer *renderer) { // return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actors; } mitk::LabelSetImageVtkMapper2D::LocalStorage *mitk::LabelSetImageVtkMapper2D::GetLocalStorage( mitk::BaseRenderer *renderer) { return m_LSH.GetLocalStorage(renderer); } void mitk::LabelSetImageVtkMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::DataNode *node = this->GetDataNode(); auto *image = dynamic_cast(node->GetData()); assert(image && image->IsInitialized()); // check if there is a valid worldGeometry const PlaneGeometry *worldGeometry = renderer->GetCurrentWorldPlaneGeometry(); if ((worldGeometry == nullptr) || (!worldGeometry->IsValid()) || (!worldGeometry->HasReferenceGeometry())) return; image->Update(); int numberOfLayers = image->GetNumberOfLayers(); int activeLayer = image->GetActiveLayer(); float opacity = 1.0f; node->GetOpacity(opacity, renderer, "opacity"); if (numberOfLayers != localStorage->m_NumberOfLayers) { localStorage->m_NumberOfLayers = numberOfLayers; localStorage->m_ReslicedImageVector.clear(); localStorage->m_ReslicerVector.clear(); localStorage->m_LayerTextureVector.clear(); localStorage->m_LevelWindowFilterVector.clear(); localStorage->m_LayerMapperVector.clear(); localStorage->m_LayerActorVector.clear(); localStorage->m_Actors = vtkSmartPointer::New(); for (int lidx = 0; lidx < numberOfLayers; ++lidx) { localStorage->m_ReslicedImageVector.push_back(vtkSmartPointer::New()); localStorage->m_ReslicerVector.push_back(mitk::ExtractSliceFilter::New()); localStorage->m_LayerTextureVector.push_back(vtkSmartPointer::New()); localStorage->m_LevelWindowFilterVector.push_back(vtkSmartPointer::New()); localStorage->m_LayerMapperVector.push_back(vtkSmartPointer::New()); localStorage->m_LayerActorVector.push_back(vtkSmartPointer::New()); // do not repeat the texture (the image) localStorage->m_LayerTextureVector[lidx]->RepeatOff(); // set corresponding mappers for the actors localStorage->m_LayerActorVector[lidx]->SetMapper(localStorage->m_LayerMapperVector[lidx]); localStorage->m_Actors->AddPart(localStorage->m_LayerActorVector[lidx]); } localStorage->m_Actors->AddPart(localStorage->m_OutlineShadowActor); localStorage->m_Actors->AddPart(localStorage->m_OutlineActor); } // early out if there is no intersection of the current rendering geometry // and the geometry of the image that is to be rendered. if (!RenderingGeometryIntersectsImage(worldGeometry, image->GetSlicedGeometry())) { // set image to nullptr, to clear the texture in 3D, because // the latest image is used there if the plane is out of the geometry // see bug-13275 for (int lidx = 0; lidx < numberOfLayers; ++lidx) { localStorage->m_ReslicedImageVector[lidx] = nullptr; localStorage->m_LayerMapperVector[lidx]->SetInputData(localStorage->m_EmptyPolyData); localStorage->m_OutlineActor->SetVisibility(false); localStorage->m_OutlineShadowActor->SetVisibility(false); } return; } for (int lidx = 0; lidx < numberOfLayers; ++lidx) { - mitk::Image *layerImage = nullptr; - - // set main input for ExtractSliceFilter - if (lidx == activeLayer) - layerImage = image; - else - layerImage = image->GetLayerImage(lidx); + const auto layerImage = image->GetGroupImage(lidx); localStorage->m_ReslicerVector[lidx]->SetInput(layerImage); localStorage->m_ReslicerVector[lidx]->SetWorldGeometry(worldGeometry); localStorage->m_ReslicerVector[lidx]->SetTimeStep(this->GetTimestep()); // set the transformation of the image to adapt reslice axis localStorage->m_ReslicerVector[lidx]->SetResliceTransformByGeometry( layerImage->GetTimeGeometry()->GetGeometryForTimeStep(this->GetTimestep())); // is the geometry of the slice based on the image image or the worldgeometry? bool inPlaneResampleExtentByGeometry = false; node->GetBoolProperty("in plane resample extent by geometry", inPlaneResampleExtentByGeometry, renderer); localStorage->m_ReslicerVector[lidx]->SetInPlaneResampleExtentByGeometry(inPlaneResampleExtentByGeometry); localStorage->m_ReslicerVector[lidx]->SetInterpolationMode(ExtractSliceFilter::RESLICE_NEAREST); localStorage->m_ReslicerVector[lidx]->SetVtkOutputRequest(true); // this is needed when thick mode was enabled before. These variables have to be reset to default values localStorage->m_ReslicerVector[lidx]->SetOutputDimensionality(2); localStorage->m_ReslicerVector[lidx]->SetOutputSpacingZDirection(1.0); localStorage->m_ReslicerVector[lidx]->SetOutputExtentZDirection(0, 0); // Bounds information for reslicing (only required if reference geometry is present) // this used for generating a vtkPLaneSource with the right size double sliceBounds[6]; sliceBounds[0] = 0.0; sliceBounds[1] = 0.0; sliceBounds[2] = 0.0; sliceBounds[3] = 0.0; sliceBounds[4] = 0.0; sliceBounds[5] = 0.0; localStorage->m_ReslicerVector[lidx]->GetClippedPlaneBounds(sliceBounds); // setup the textured plane this->GeneratePlane(renderer, sliceBounds); // get the spacing of the slice localStorage->m_mmPerPixel = localStorage->m_ReslicerVector[lidx]->GetOutputSpacing(); localStorage->m_ReslicerVector[lidx]->Modified(); // start the pipeline with updating the largest possible, needed if the geometry of the image has changed localStorage->m_ReslicerVector[lidx]->UpdateLargestPossibleRegion(); localStorage->m_ReslicedImageVector[lidx] = localStorage->m_ReslicerVector[lidx]->GetVtkOutput(); const auto *planeGeometry = dynamic_cast(worldGeometry); double textureClippingBounds[6]; for (auto &textureClippingBound : textureClippingBounds) { textureClippingBound = 0.0; } // Calculate the actual bounds of the transformed plane clipped by the // dataset bounding box; this is required for drawing the texture at the // correct position during 3D mapping. mitk::PlaneClipping::CalculateClippedPlaneBounds(layerImage->GetGeometry(), planeGeometry, textureClippingBounds); textureClippingBounds[0] = static_cast(textureClippingBounds[0] / localStorage->m_mmPerPixel[0] + 0.5); textureClippingBounds[1] = static_cast(textureClippingBounds[1] / localStorage->m_mmPerPixel[0] + 0.5); textureClippingBounds[2] = static_cast(textureClippingBounds[2] / localStorage->m_mmPerPixel[1] + 0.5); textureClippingBounds[3] = static_cast(textureClippingBounds[3] / localStorage->m_mmPerPixel[1] + 0.5); // clipping bounds for cutting the imageLayer localStorage->m_LevelWindowFilterVector[lidx]->SetClippingBounds(textureClippingBounds); localStorage->m_LevelWindowFilterVector[lidx]->SetLookupTable( - image->GetLabelSet(lidx)->GetLookupTable()->GetVtkLookupTable()); + image->GetLookupTable()->GetVtkLookupTable()); // do not use a VTK lookup table (we do that ourselves in m_LevelWindowFilter) localStorage->m_LayerTextureVector[lidx]->SetColorModeToDirectScalars(); // connect the imageLayer with the levelwindow filter localStorage->m_LevelWindowFilterVector[lidx]->SetInputData(localStorage->m_ReslicedImageVector[lidx]); // connect the texture with the output of the levelwindow filter // check for texture interpolation property bool textureInterpolation = false; node->GetBoolProperty("texture interpolation", textureInterpolation, renderer); // set the interpolation modus according to the property localStorage->m_LayerTextureVector[lidx]->SetInterpolate(textureInterpolation); localStorage->m_LayerTextureVector[lidx]->SetInputConnection( localStorage->m_LevelWindowFilterVector[lidx]->GetOutputPort()); this->TransformActor(renderer); // set the plane as input for the mapper localStorage->m_LayerMapperVector[lidx]->SetInputConnection(localStorage->m_Plane->GetOutputPort()); // set the texture for the actor localStorage->m_LayerActorVector[lidx]->SetTexture(localStorage->m_LayerTextureVector[lidx]); localStorage->m_LayerActorVector[lidx]->GetProperty()->SetOpacity(opacity); } - mitk::Label* activeLabel = image->GetActiveLabel(activeLayer); + mitk::Label* activeLabel = image->GetActiveLabel(); if (nullptr != activeLabel) { bool contourActive = false; node->GetBoolProperty("labelset.contour.active", contourActive, renderer); if (contourActive && activeLabel->GetVisible()) //contour rendering { //generate contours/outlines localStorage->m_OutlinePolyData = this->CreateOutlinePolyData(renderer, localStorage->m_ReslicedImageVector[activeLayer], activeLabel->GetValue()); localStorage->m_OutlineActor->SetVisibility(true); localStorage->m_OutlineShadowActor->SetVisibility(true); const mitk::Color& color = activeLabel->GetColor(); localStorage->m_OutlineActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); localStorage->m_OutlineShadowActor->GetProperty()->SetColor(0, 0, 0); float contourWidth(2.0); node->GetFloatProperty("labelset.contour.width", contourWidth, renderer); localStorage->m_OutlineActor->GetProperty()->SetLineWidth(contourWidth); localStorage->m_OutlineShadowActor->GetProperty()->SetLineWidth(contourWidth * 1.5); localStorage->m_OutlineActor->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineShadowActor->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineMapper->SetInputData(localStorage->m_OutlinePolyData); return; } } localStorage->m_OutlineActor->SetVisibility(false); localStorage->m_OutlineShadowActor->SetVisibility(false); } bool mitk::LabelSetImageVtkMapper2D::RenderingGeometryIntersectsImage(const PlaneGeometry *renderingGeometry, SlicedGeometry3D *imageGeometry) { // if either one of the two geometries is nullptr we return true // for safety reasons if (renderingGeometry == nullptr || imageGeometry == nullptr) return true; // get the distance for the first cornerpoint ScalarType initialDistance = renderingGeometry->SignedDistance(imageGeometry->GetCornerPoint(0)); for (int i = 1; i < 8; i++) { mitk::Point3D cornerPoint = imageGeometry->GetCornerPoint(i); // get the distance to the other cornerpoints ScalarType distance = renderingGeometry->SignedDistance(cornerPoint); // if it has not the same signing as the distance of the first point if (initialDistance * distance < 0) { // we have an intersection and return true return true; } } // all distances have the same sign, no intersection and we return false return false; } vtkSmartPointer mitk::LabelSetImageVtkMapper2D::CreateOutlinePolyData(mitk::BaseRenderer *renderer, vtkImageData *image, int pixelValue) { LocalStorage *localStorage = this->GetLocalStorage(renderer); // get the min and max index values of each direction int *extent = image->GetExtent(); int xMin = extent[0]; int xMax = extent[1]; int yMin = extent[2]; int yMax = extent[3]; int *dims = image->GetDimensions(); // dimensions of the image int line = dims[0]; // how many pixels per line? int x = xMin; // pixel index x int y = yMin; // pixel index y // get the depth for each contour float depth = this->CalculateLayerDepth(renderer); vtkSmartPointer points = vtkSmartPointer::New(); // the points to draw vtkSmartPointer lines = vtkSmartPointer::New(); // the lines to connect the points // We take the pointer to the first pixel of the image auto *currentPixel = static_cast(image->GetScalarPointer()); while (y <= yMax) { // if the current pixel value is set to something if ((currentPixel) && (*currentPixel == pixelValue)) { // check in which direction a line is necessary // a line is added if the neighbor of the current pixel has the value 0 // and if the pixel is located at the edge of the image // if vvvvv not the first line vvvvv if (y > yMin && *(currentPixel - line) != pixelValue) { // x direction - bottom edge of the pixel // add the 2 points vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); // add the line between both points lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the last line vvvvv if (y < yMax && *(currentPixel + line) != pixelValue) { // x direction - top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the first pixel vvvvv if ((x > xMin || y > yMin) && *(currentPixel - 1) != pixelValue) { // y direction - left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv not the last pixel vvvvv if ((y < yMax || (x < xMax)) && *(currentPixel + 1) != pixelValue) { // y direction - right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } /* now consider pixels at the edge of the image */ // if vvvvv left edge of image vvvvv if (x == xMin) { // draw left edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv right edge of image vvvvv if (x == xMax) { // draw right edge of the pixel vtkIdType p1 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv bottom edge of image vvvvv if (y == yMin) { // draw bottom edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint((x + 1) * localStorage->m_mmPerPixel[0], y * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // if vvvvv top edge of image vvvvv if (y == yMax) { // draw top edge of the pixel vtkIdType p1 = points->InsertNextPoint(x * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); vtkIdType p2 = points->InsertNextPoint( (x + 1) * localStorage->m_mmPerPixel[0], (y + 1) * localStorage->m_mmPerPixel[1], depth); lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } } // end if currentpixel is set x++; if (x > xMax) { // reached end of line x = xMin; y++; } // Increase the pointer-position to the next pixel. // This is safe, as the while-loop and the x-reset logic above makes // sure we do not exceed the bounds of the image currentPixel++; } // end of while // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); return polyData; } void mitk::LabelSetImageVtkMapper2D::ApplyColor(mitk::BaseRenderer *renderer, const mitk::Color &color) { LocalStorage *localStorage = this->GetLocalStorage(renderer); localStorage->m_OutlineActor->GetProperty()->SetColor(color.GetRed(), color.GetGreen(), color.GetBlue()); localStorage->m_OutlineShadowActor->GetProperty()->SetColor(0, 0, 0); } void mitk::LabelSetImageVtkMapper2D::ApplyOpacity(mitk::BaseRenderer *renderer, int layer) { LocalStorage *localStorage = this->GetLocalStorage(renderer); float opacity = 1.0f; this->GetDataNode()->GetOpacity(opacity, renderer, "opacity"); localStorage->m_LayerActorVector[layer]->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineActor->GetProperty()->SetOpacity(opacity); localStorage->m_OutlineShadowActor->GetProperty()->SetOpacity(opacity); } void mitk::LabelSetImageVtkMapper2D::ApplyLookuptable(mitk::BaseRenderer *renderer, int layer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); auto *input = dynamic_cast(this->GetDataNode()->GetData()); localStorage->m_LevelWindowFilterVector[layer]->SetLookupTable( - input->GetLabelSet(layer)->GetLookupTable()->GetVtkLookupTable()); + input->GetLookupTable()->GetVtkLookupTable()); } void mitk::LabelSetImageVtkMapper2D::Update(mitk::BaseRenderer *renderer) { bool visible = true; const DataNode *node = this->GetDataNode(); node->GetVisibility(visible, renderer, "visible"); if (!visible) return; auto *image = dynamic_cast(node->GetData()); if (image == nullptr || image->IsInitialized() == false) return; // Calculate time step of the image data for the specified renderer (integer value) this->CalculateTimeStep(renderer); // Check if time step is valid const TimeGeometry *dataTimeGeometry = image->GetTimeGeometry(); if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || (!dataTimeGeometry->IsValidTimeStep(this->GetTimestep()))) { return; } image->UpdateOutputInformation(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // check if something important has changed and we need to re-render if ((localStorage->m_LastDataUpdateTime < image->GetMTime()) || (localStorage->m_LastDataUpdateTime < image->GetPipelineMTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) || (localStorage->m_LastDataUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime())) { this->GenerateDataForRenderer(renderer); localStorage->m_LastDataUpdateTime.Modified(); } else if ((localStorage->m_LastPropertyUpdateTime < node->GetPropertyList()->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < node->GetPropertyList(renderer)->GetMTime()) || (localStorage->m_LastPropertyUpdateTime < image->GetPropertyList()->GetMTime())) { this->GenerateDataForRenderer(renderer); localStorage->m_LastPropertyUpdateTime.Modified(); } } // set the two points defining the textured plane according to the dimension and spacing void mitk::LabelSetImageVtkMapper2D::GeneratePlane(mitk::BaseRenderer *renderer, double planeBounds[6]) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); float depth = this->CalculateLayerDepth(renderer); // Set the origin to (xMin; yMin; depth) of the plane. This is necessary for obtaining the correct // plane size in crosshair rotation and swivel mode. localStorage->m_Plane->SetOrigin(planeBounds[0], planeBounds[2], depth); // These two points define the axes of the plane in combination with the origin. // Point 1 is the x-axis and point 2 the y-axis. // Each plane is transformed according to the view (axial, coronal and sagittal) afterwards. localStorage->m_Plane->SetPoint1(planeBounds[1], planeBounds[2], depth); // P1: (xMax, yMin, depth) localStorage->m_Plane->SetPoint2(planeBounds[0], planeBounds[3], depth); // P2: (xMin, yMax, depth) } float mitk::LabelSetImageVtkMapper2D::CalculateLayerDepth(mitk::BaseRenderer *renderer) { // get the clipping range to check how deep into z direction we can render images double maxRange = renderer->GetVtkRenderer()->GetActiveCamera()->GetClippingRange()[1]; // Due to a VTK bug, we cannot use the whole clipping range. /100 is empirically determined float depth = -maxRange * 0.01; // divide by 100 int layer = 0; GetDataNode()->GetIntProperty("layer", layer, renderer); // add the layer property for each image to render images with a higher layer on top of the others depth += layer * 10; //*10: keep some room for each image (e.g. for ODFs in between) if (depth > 0.0f) { depth = 0.0f; MITK_WARN << "Layer value exceeds clipping range. Set to minimum instead."; } return depth; } void mitk::LabelSetImageVtkMapper2D::TransformActor(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // get the transformation matrix of the reslicer in order to render the slice as axial, coronal or sagittal vtkSmartPointer trans = vtkSmartPointer::New(); vtkSmartPointer matrix = localStorage->m_ReslicerVector[0]->GetResliceAxes(); // same for all layers trans->SetMatrix(matrix); for (int lidx = 0; lidx < localStorage->m_NumberOfLayers; ++lidx) { // transform the plane/contour (the actual actor) to the corresponding view (axial, coronal or sagittal) localStorage->m_LayerActorVector[lidx]->SetUserTransform(trans); // transform the origin to center based coordinates, because MITK is center based. localStorage->m_LayerActorVector[lidx]->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); } // same for outline actor localStorage->m_OutlineActor->SetUserTransform(trans); localStorage->m_OutlineActor->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); // same for outline shadow actor localStorage->m_OutlineShadowActor->SetUserTransform(trans); localStorage->m_OutlineShadowActor->SetPosition( -0.5 * localStorage->m_mmPerPixel[0], -0.5 * localStorage->m_mmPerPixel[1], 0.0); } void mitk::LabelSetImageVtkMapper2D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { // add/replace the following properties node->SetProperty("opacity", FloatProperty::New(1.0f), renderer); node->SetProperty("binary", BoolProperty::New(false), renderer); mitk::RenderingModeProperty::Pointer renderingModeProperty = mitk::RenderingModeProperty::New(RenderingModeProperty::LOOKUPTABLE_LEVELWINDOW_COLOR); node->SetProperty("Image Rendering.Mode", renderingModeProperty, renderer); mitk::LevelWindow levelwindow(32767.5, 65535); mitk::LevelWindowProperty::Pointer levWinProp = mitk::LevelWindowProperty::New(levelwindow); levWinProp->SetLevelWindow(levelwindow); node->SetProperty("levelwindow", levWinProp, renderer); node->SetProperty("labelset.contour.active", BoolProperty::New(true), renderer); node->SetProperty("labelset.contour.width", FloatProperty::New(2.0), renderer); Superclass::SetDefaultProperties(node, renderer, overwrite); } mitk::LabelSetImageVtkMapper2D::LocalStorage::~LocalStorage() { } mitk::LabelSetImageVtkMapper2D::LocalStorage::LocalStorage() { // Do as much actions as possible in here to avoid double executions. m_Plane = vtkSmartPointer::New(); m_Actors = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_EmptyPolyData = vtkSmartPointer::New(); m_OutlineActor = vtkSmartPointer::New(); m_OutlineMapper = vtkSmartPointer::New(); m_OutlineShadowActor = vtkSmartPointer::New(); m_NumberOfLayers = 0; m_mmPerPixel = nullptr; m_OutlineActor->SetMapper(m_OutlineMapper); m_OutlineShadowActor->SetMapper(m_OutlineMapper); m_OutlineActor->SetVisibility(false); m_OutlineShadowActor->SetVisibility(false); } diff --git a/Modules/Multilabel/mitkMultiLabelEvents.cpp b/Modules/Multilabel/mitkMultiLabelEvents.cpp new file mode 100644 index 0000000000..02b47c96f1 --- /dev/null +++ b/Modules/Multilabel/mitkMultiLabelEvents.cpp @@ -0,0 +1,105 @@ +/*============================================================================ + +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 "mitkMultiLabelEvents.h" + +namespace mitk +{ + AnyLabelEvent::AnyLabelEvent(Label::PixelType labelValue) : m_LabelValue(labelValue) {} + + AnyLabelEvent::AnyLabelEvent(const AnyLabelEvent& s) : itk::ModifiedEvent(s), m_LabelValue(s.m_LabelValue) {} + + AnyLabelEvent::~AnyLabelEvent() {} + + const char* AnyLabelEvent::GetEventName() const { return "AnyLabelEvent"; } + + bool AnyLabelEvent::CheckEvent(const itk::EventObject* e) const + { + auto castedE = dynamic_cast(e); + + return (nullptr != castedE) && ((castedE->m_LabelValue == m_LabelValue) || (ANY_LABEL == castedE->m_LabelValue) || (ANY_LABEL == m_LabelValue)); + } + + itk::EventObject* AnyLabelEvent::MakeObject() const { return new AnyLabelEvent(); } + + void AnyLabelEvent::SetLabelValue(Label::PixelType labelValue) + { + m_LabelValue = labelValue; + } + + Label::PixelType AnyLabelEvent::GetLabelValue() const + { + return m_LabelValue; + } + + mitkMultiLabelEventMacroDefinition(LabelAddedEvent, AnyLabelEvent, Label::PixelType); + mitkMultiLabelEventMacroDefinition(LabelModifiedEvent, AnyLabelEvent, Label::PixelType); + mitkMultiLabelEventMacroDefinition(LabelRemovedEvent, AnyLabelEvent, Label::PixelType); + + LabelsChangedEvent::LabelsChangedEvent(std::vector labelValues) : m_LabelValues(labelValues) {} + + LabelsChangedEvent::LabelsChangedEvent(const LabelsChangedEvent& s) : itk::ModifiedEvent(s), m_LabelValues(s.m_LabelValues) {} + + LabelsChangedEvent::~LabelsChangedEvent() {} + + const char* LabelsChangedEvent::GetEventName() const { return "LabelsChangedEvent"; } + + bool LabelsChangedEvent::CheckEvent(const itk::EventObject* e) const + { + auto castedE = dynamic_cast(e); + + return (nullptr != castedE) && ((castedE->m_LabelValues == m_LabelValues) || (castedE->m_LabelValues.empty()) || (m_LabelValues.empty())); + } + + itk::EventObject* LabelsChangedEvent::MakeObject() const { return new LabelsChangedEvent(); } + + void LabelsChangedEvent::SetLabelValues(std::vector labelValues) + { + m_LabelValues = labelValues; + } + + std::vector LabelsChangedEvent::GetLabelValues() const + { + return m_LabelValues; + } + + AnyGroupEvent::AnyGroupEvent(GroupIndexType groupID) : m_GroupID(groupID) {} + + AnyGroupEvent::AnyGroupEvent(const AnyGroupEvent& s) : itk::ModifiedEvent(s), m_GroupID(s.m_GroupID) {} + + AnyGroupEvent::~AnyGroupEvent() {} + + const char* AnyGroupEvent::GetEventName() const { return "AnyGroupEvent"; } + + bool AnyGroupEvent::CheckEvent(const itk::EventObject* e) const + { + auto castedE = dynamic_cast(e); + + return (nullptr != castedE) && ((castedE->m_GroupID == m_GroupID) || (ANY_GROUP == castedE->m_GroupID) || (ANY_GROUP == m_GroupID)); + } + + itk::EventObject* AnyGroupEvent::MakeObject() const { return new AnyGroupEvent(); } + + void AnyGroupEvent::SetGroupID(AnyGroupEvent::GroupIndexType groupID) + { + m_GroupID = groupID; + } + + AnyGroupEvent::GroupIndexType AnyGroupEvent::GetGroupID() const + { + return m_GroupID; + } + + mitkMultiLabelEventMacroDefinition(GroupAddedEvent, AnyGroupEvent, AnyGroupEvent::GroupIndexType); + mitkMultiLabelEventMacroDefinition(GroupModifiedEvent, AnyGroupEvent, AnyGroupEvent::GroupIndexType); + mitkMultiLabelEventMacroDefinition(GroupRemovedEvent, AnyGroupEvent, AnyGroupEvent::GroupIndexType); +} \ No newline at end of file diff --git a/Modules/Multilabel/mitkMultiLabelEvents.h b/Modules/Multilabel/mitkMultiLabelEvents.h new file mode 100644 index 0000000000..1ad2e95b15 --- /dev/null +++ b/Modules/Multilabel/mitkMultiLabelEvents.h @@ -0,0 +1,197 @@ +/*============================================================================ + +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 mitkMultiLabelEvents_h +#define mitkMultiLabelEvents_h + +#include +#include + +#include + +namespace mitk +{ +#define mitkMultiLabelEventMacroDeclaration(classname, super, IDType) \ + /** \class classname */ \ + class MITKMULTILABEL_EXPORT classname : public super \ + { \ + public: \ + using Self = classname; \ + using Superclass = super; \ + classname() = default; \ + classname(IDType value); \ + classname(const Self & s); \ + virtual ~classname() override; \ + virtual const char * \ + GetEventName() const override; \ + virtual bool \ + CheckEvent(const itk::EventObject * e) const override; \ + virtual itk::EventObject * \ + MakeObject() const override; \ + \ + private: \ + void \ + operator=(const Self &); \ + }; \ + static_assert(true, "Compile time eliminated. Used to require a semi-colon at end of macro.") + +#define mitkMultiLabelEventMacroDefinition(classname, super, IDType) \ + classname::classname(const classname & s) \ + : super(s){}; \ + classname::classname(IDType value): super(value) {} \ + classname::~classname() {} \ + const char * classname::GetEventName() const { return #classname; } \ + bool classname::CheckEvent(const itk::EventObject * e) const \ + { \ + if (!super::CheckEvent(e)) return false; \ + return (dynamic_cast(e) != nullptr); \ + } \ + itk::EventObject * classname::MakeObject() const { return new classname; } \ + static_assert(true, "Compile time eliminated. Used to require a semi-colon at end of macro.") + + /** Base event class for all events that are about a label in a MultiLabel class. + * + * It has a member that indicates the label id the event is refering to. + * Use the ANY_LABEL value if you want to define an rvent (e.g. for adding an observer) + * that reacts to every label and not just to a special one. + */ + class MITKMULTILABEL_EXPORT AnyLabelEvent : public itk::ModifiedEvent + { + public: + using Self = AnyLabelEvent; + using Superclass = itk::ModifiedEvent; + const static mitk::Label::PixelType ANY_LABEL = std::numeric_limits::max(); + + AnyLabelEvent() = default; + AnyLabelEvent(Label::PixelType labelValue); + AnyLabelEvent(const Self & s); + ~AnyLabelEvent() override; + const char * GetEventName() const override; + bool CheckEvent(const itk::EventObject * e) const override; + itk::EventObject * MakeObject() const override; + + void SetLabelValue(Label::PixelType labelValue); + Label::PixelType GetLabelValue() const; + private: + void operator=(const Self &); + Label::PixelType m_LabelValue = std::numeric_limits::max(); + }; + + /** Event class that is used to indicated if a label is added in a MultiLabel class. + * + * It has a member that indicates the label id the event is refering to. + * Use the ANY_LABEL value if you want to define an rvent (e.g. for adding an observer) + * that reacts to every label and not just to a special one. + */ + mitkMultiLabelEventMacroDeclaration(LabelAddedEvent, AnyLabelEvent, Label::PixelType); + + /** Event class that is used to indicated if a label is modified in a MultiLabel class. + * + * It has a member that indicates the label id the event is refering to. + * Use the ANY_LABEL value if you want to define an rvent (e.g. for adding an observer) + * that reacts to every label and not just to a special one. + */ + mitkMultiLabelEventMacroDeclaration(LabelModifiedEvent, AnyLabelEvent, Label::PixelType); + + /** Event class that is used to indicated if a label is removed in a MultiLabel class. + * + * It has a member that indicates the label id the event is refering to. + * Use the ANY_LABEL value if you want to define an rvent (e.g. for adding an observer) + * that reacts to every label and not just to a special one. + */ + mitkMultiLabelEventMacroDeclaration(LabelRemovedEvent, AnyLabelEvent, Label::PixelType); + + /** Event class that is used to indicated if a set of labels is changed in a MultiLabel class. + * + * In difference to the other label events LabelsChangedEvent is send only *one time* after + * the modification of the MultiLableImage instance is finished. So e.g. even if 4 labels are + * changed by a merge operation, this event will only be sent once (compared to LabelRemoved + * or LabelModified). + * It has a member that indicates the label ids the event is refering to. + */ + class MITKMULTILABEL_EXPORT LabelsChangedEvent : public itk::ModifiedEvent + { + public: + using Self = LabelsChangedEvent; + using Superclass = itk::ModifiedEvent; + + LabelsChangedEvent() = default; + LabelsChangedEvent(std::vector labelValues); + LabelsChangedEvent(const Self& s); + ~LabelsChangedEvent() override; + const char* GetEventName() const override; + bool CheckEvent(const itk::EventObject* e) const override; + itk::EventObject* MakeObject() const override; + + void SetLabelValues(std::vector labelValues); + std::vector GetLabelValues() const; + private: + void operator=(const Self&); + std::vector m_LabelValues; + }; + + /** Base event class for all events that are about a group in a MultiLabel class. + * + * It has a member that indicates the group id the event is refering to. + * Use the ANY_GROUP value if you want to define an event (e.g. for adding an observer) + * that reacts to every group and not just to a special one. + */ + class MITKMULTILABEL_EXPORT AnyGroupEvent : public itk::ModifiedEvent + { + public: + using GroupIndexType = std::size_t; + using Self = AnyGroupEvent; + using Superclass = itk::ModifiedEvent; + const static GroupIndexType ANY_GROUP = std::numeric_limits::max(); + + AnyGroupEvent() = default; + AnyGroupEvent(GroupIndexType groupID); + AnyGroupEvent(const Self& s); + ~AnyGroupEvent() override; + const char* GetEventName() const override; + bool CheckEvent(const itk::EventObject* e) const override; + itk::EventObject* MakeObject() const override; + + void SetGroupID(GroupIndexType groupID); + GroupIndexType GetGroupID() const; + private: + void operator=(const Self&); + GroupIndexType m_GroupID = std::numeric_limits::max(); + }; + + /** Event class that is used to indicated if a group is added in a MultiLabel class. + * + * It has a member that indicates the group id the event is refering to. + * Use the ANY_GROUP value if you want to define an rvent (e.g. for adding an observer) + * that reacts to every group and not just to a special one. + */ + mitkMultiLabelEventMacroDeclaration(GroupAddedEvent, AnyGroupEvent, AnyGroupEvent::GroupIndexType); + + /** Event class that is used to indicated if a group is modified in a MultiLabel class. + * + * It has a member that indicates the group id the event is refering to. + * Use the ANY_GROUP value if you want to define an rvent (e.g. for adding an observer) + * that reacts to every group and not just to a special one. + */ + mitkMultiLabelEventMacroDeclaration(GroupModifiedEvent, AnyGroupEvent, AnyGroupEvent::GroupIndexType); + + /** Event class that is used to indicated if a group is removed in a MultiLabel class. + * + * It has a member that indicates the group id the event is refering to. + * Use the ANY_GROUP value if you want to define an rvent (e.g. for adding an observer) + * that reacts to every group and not just to a special one. + */ + mitkMultiLabelEventMacroDeclaration(GroupRemovedEvent, AnyGroupEvent, AnyGroupEvent::GroupIndexType); + +} + +#endif diff --git a/Modules/Multilabel/mitkMultiLabelIOHelper.cpp b/Modules/Multilabel/mitkMultiLabelIOHelper.cpp index 80cf783467..9303c04431 100644 --- a/Modules/Multilabel/mitkMultiLabelIOHelper.cpp +++ b/Modules/Multilabel/mitkMultiLabelIOHelper.cpp @@ -1,442 +1,440 @@ /*============================================================================ 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 "mitkMultiLabelIOHelper.h" #include "mitkLabelSetImage.h" #include #include "itkMetaDataDictionary.h" #include "itkMetaDataObject.h" #include namespace { std::string EnsureExtension(const std::string& filename) { const std::string extension = ".lsetp"; if (filename.size() < extension.size() || std::string::npos == filename.find(extension, filename.size() - extension.size())) return filename + extension; return filename; } } bool mitk::MultiLabelIOHelper::SaveLabelSetImagePreset(const std::string &presetFilename, const mitk::LabelSetImage *inputImage) { const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; xmlDocument.InsertEndChild(xmlDocument.NewDeclaration()); auto *rootElement = xmlDocument.NewElement("LabelSetImagePreset"); rootElement->SetAttribute("layers", inputImage->GetNumberOfLayers()); xmlDocument.InsertEndChild(rootElement); for (unsigned int layerIndex = 0; layerIndex < inputImage->GetNumberOfLayers(); layerIndex++) { auto *layerElement = xmlDocument.NewElement("Layer"); layerElement->SetAttribute("index", layerIndex); layerElement->SetAttribute("labels", inputImage->GetNumberOfLabels(layerIndex)); rootElement->InsertEndChild(layerElement); - for (unsigned int labelIndex = 0; labelIndex < inputImage->GetNumberOfLabels(layerIndex); labelIndex++) - layerElement->InsertEndChild(MultiLabelIOHelper::GetLabelAsXMLElement(xmlDocument, inputImage->GetLabel(labelIndex, layerIndex))); + auto labelsInGroup = inputImage->GetConstLabelsByValue(inputImage->GetLabelValuesByGroup(layerIndex)); + + for (const auto& label : labelsInGroup) + layerElement->InsertEndChild(MultiLabelIOHelper::GetLabelAsXMLElement(xmlDocument, label)); } return tinyxml2::XML_SUCCESS == xmlDocument.SaveFile(filename.c_str()); } bool mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(const std::string &presetFilename, mitk::LabelSetImage *inputImage) { if (nullptr == inputImage) return false; const auto filename = EnsureExtension(presetFilename); tinyxml2::XMLDocument xmlDocument; if (tinyxml2::XML_SUCCESS != xmlDocument.LoadFile(filename.c_str())) { MITK_WARN << "Label set preset file \"" << filename << "\" does not exist or cannot be opened"; return false; } auto *rootElement = xmlDocument.FirstChildElement("LabelSetImagePreset"); if (nullptr == rootElement) { MITK_WARN << "Not a valid Label set preset"; return false; } auto activeLayerBackup = inputImage->GetActiveLayer(); int numberOfLayers = 0; rootElement->QueryIntAttribute("layers", &numberOfLayers); auto* layerElement = rootElement->FirstChildElement("Layer"); if (nullptr == layerElement) { MITK_WARN << "Label set preset does not contain any layers"; return false; } for (int layerIndex = 0; layerIndex < numberOfLayers; layerIndex++) { int numberOfLabels = 0; layerElement->QueryIntAttribute("labels", &numberOfLabels); - if (nullptr == inputImage->GetLabelSet(layerIndex)) - { - inputImage->AddLayer(); - } - else + if (!inputImage->ExistGroup(layerIndex)) { - inputImage->SetActiveLayer(layerIndex); + while (!inputImage->ExistGroup(layerIndex)) + { + inputImage->AddLayer(); + } } auto *labelElement = layerElement->FirstChildElement("Label"); if (nullptr == labelElement) continue; for (int labelIndex = 0; labelIndex < numberOfLabels; labelIndex++) { auto label = mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(labelElement); const auto labelValue = label->GetValue(); - if (LabelSetImage::UnlabeledValue != labelValue) + if (LabelSetImage::UNLABELED_VALUE != labelValue) { - auto* labelSet = inputImage->GetLabelSet(layerIndex); - auto* alreadyExistingLabel = labelSet->GetLabel(labelValue); - - if (nullptr != alreadyExistingLabel) + if (inputImage->ExistLabel(labelValue)) { // Override existing label with label from preset + auto alreadyExistingLabel = inputImage->GetLabel(labelValue); alreadyExistingLabel->ConcatenatePropertyList(label); - labelSet->UpdateLookupTable(labelValue); + inputImage->UpdateLookupTable(labelValue); } else { - labelSet->AddLabel(label); + inputImage->AddLabel(label, layerIndex, false); } } labelElement = labelElement->NextSiblingElement("Label"); if (nullptr == labelElement) continue; } layerElement = layerElement->NextSiblingElement("Layer"); if (nullptr == layerElement) continue; } inputImage->SetActiveLayer(activeLayerBackup); return true; } -tinyxml2::XMLElement *mitk::MultiLabelIOHelper::GetLabelAsXMLElement(tinyxml2::XMLDocument &doc, Label *label) +tinyxml2::XMLElement *mitk::MultiLabelIOHelper::GetLabelAsXMLElement(tinyxml2::XMLDocument &doc, const Label *label) { auto *labelElem = doc.NewElement("Label"); if (nullptr != label) { // add XML contents const PropertyList::PropertyMap* propmap = label->GetMap(); for (auto iter = propmap->begin(); iter != propmap->end(); ++iter) { std::string key = iter->first; const BaseProperty* property = iter->second; auto* element = PropertyToXMLElement(doc, key, property); if (element) labelElem->InsertEndChild(element); } } return labelElem; } mitk::Label::Pointer mitk::MultiLabelIOHelper::LoadLabelFromXMLDocument(const tinyxml2::XMLElement *labelElem) { // reread auto *propElem = labelElem->FirstChildElement("property"); std::string name; mitk::BaseProperty::Pointer prop; mitk::Label::Pointer label = mitk::Label::New(); while (propElem) { MultiLabelIOHelper::PropertyFromXMLElement(name, prop, propElem); label->SetProperty(name, prop); propElem = propElem->NextSiblingElement("property"); } return label.GetPointer(); } tinyxml2::XMLElement *mitk::MultiLabelIOHelper::PropertyToXMLElement(tinyxml2::XMLDocument &doc, const std::string &key, const BaseProperty *property) { auto *keyelement = doc.NewElement("property"); keyelement->SetAttribute("key", key.c_str()); keyelement->SetAttribute("type", property->GetNameOfClass()); // construct name of serializer class std::string serializername(property->GetNameOfClass()); serializername += "Serializer"; std::list allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << property->GetNameOfClass() << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple serializers found for " << property->GetNameOfClass() << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { serializer->SetProperty(property); try { auto *valueelement = serializer->Serialize(doc); if (valueelement) keyelement->InsertEndChild(valueelement); } catch (std::exception &e) { MITK_ERROR << "Serializer " << serializer->GetNameOfClass() << " failed: " << e.what(); } break; } } return keyelement; } bool mitk::MultiLabelIOHelper::PropertyFromXMLElement(std::string &key, mitk::BaseProperty::Pointer &prop, const tinyxml2::XMLElement *elem) { const char* typeC = elem->Attribute("type"); std::string type = nullptr != typeC ? typeC : ""; const char* keyC = elem->Attribute("key"); key = nullptr != keyC ? keyC : ""; // construct name of serializer class std::string serializername(type); serializername += "Serializer"; std::list allSerializers = itk::ObjectFactoryBase::CreateAllInstance(serializername.c_str()); if (allSerializers.size() < 1) MITK_ERROR << "No serializer found for " << type << ". Skipping object"; if (allSerializers.size() > 1) MITK_WARN << "Multiple deserializers found for " << type << "Using arbitrarily the first one."; for (auto iter = allSerializers.begin(); iter != allSerializers.end(); ++iter) { if (auto *serializer = dynamic_cast(iter->GetPointer())) { try { prop = serializer->Deserialize(elem->FirstChildElement()); } catch (std::exception &e) { MITK_ERROR << "Deserializer " << serializer->GetNameOfClass() << " failed: " << e.what(); return false; } break; } } if (prop.IsNull()) return false; return true; } int mitk::MultiLabelIOHelper::GetIntByKey(const itk::MetaDataDictionary& dic, const std::string& str) { std::vector imgMetaKeys = dic.GetKeys(); std::vector::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData(dic, *itKey, metaString); if (itKey->find(str.c_str()) != std::string::npos) { return atoi(metaString.c_str()); } } return 0; } std::string mitk::MultiLabelIOHelper::GetStringByKey(const itk::MetaDataDictionary& dic, const std::string& str) { std::vector imgMetaKeys = dic.GetKeys(); std::vector::const_iterator itKey = imgMetaKeys.begin(); std::string metaString(""); for (; itKey != imgMetaKeys.end(); itKey++) { itk::ExposeMetaData(dic, *itKey, metaString); if (itKey->find(str.c_str()) != std::string::npos) { return metaString; } } return metaString; } nlohmann::json mitk::MultiLabelIOHelper::SerializeMultLabelGroupsToJSON(const mitk::LabelSetImage* inputImage) { if (nullptr == inputImage) { mitkThrow() << "Invalid call of SerializeMultLabelGroupsToJSON. Passed image pointer is null."; } nlohmann::json result; for (LabelSetImage::GroupIndexType i = 0; i < inputImage->GetNumberOfLayers(); i++) { nlohmann::json jgroup; nlohmann::json jlabels; - for (const auto& label : inputImage->GetLabelsInGroup(i)) + for (const auto& label : inputImage->GetConstLabelsByValue(inputImage->GetLabelValuesByGroup(i))) { jlabels.emplace_back(SerializeLabelToJSON(label)); } jgroup["labels"] = jlabels; result.emplace_back(jgroup); } return result; }; -std::vector mitk::MultiLabelIOHelper::DeserializeMultiLabelGroupsFromJSON(const nlohmann::json& listOfLabelSets) +std::vector mitk::MultiLabelIOHelper::DeserializeMultiLabelGroupsFromJSON(const nlohmann::json& listOfLabelSets) { - std::vector result; + std::vector result; for (const auto& jlabelset : listOfLabelSets) { - LabelSet::Pointer labelSet = LabelSet::New(); + LabelVector labelSet; if (jlabelset.find("labels") != jlabelset.end()) { auto jlabels = jlabelset["labels"]; for (const auto& jlabel : jlabels) { - auto label = DeserializeLabelFromJSON(jlabel); - labelSet->AddLabel(label, false); + labelSet.push_back(DeserializeLabelFromJSON(jlabel)); } } result.emplace_back(labelSet); } return result; } nlohmann::json mitk::MultiLabelIOHelper::SerializeLabelToJSON(const Label* label) { if (nullptr == label) { mitkThrow() << "Invalid call of GetLabelAsJSON. Passed label pointer is null."; } nlohmann::json j; j["name"] = label->GetName(); j["value"] = label->GetValue(); nlohmann::json jcolor; jcolor["type"] = "ColorProperty"; jcolor["value"] = {label->GetColor().GetRed(), label->GetColor().GetGreen(), label->GetColor().GetBlue() }; j["color"] = jcolor; j["locked"] = label->GetLocked(); j["opacity"] = label->GetOpacity(); j["visible"] = label->GetVisible(); return j; }; template bool GetValueFromJson(const nlohmann::json& labelJson, const std::string& key, TValueType& value) { if (labelJson.find(key) != labelJson.end()) { try { value = labelJson[key].get(); return true; } catch (...) { MITK_ERROR << "Unable to read label information from json. Value has wrong type. Failed key: " << key << "; invalid value: " << labelJson[key].dump(); throw; } } return false; } mitk::Label::Pointer mitk::MultiLabelIOHelper::DeserializeLabelFromJSON(const nlohmann::json& labelJson) { Label::Pointer resultLabel = Label::New(); std::string name = "Unkown label name"; GetValueFromJson(labelJson, "name", name); resultLabel->SetName(name); Label::PixelType value = 1; GetValueFromJson(labelJson, "value", value); resultLabel->SetValue(value); if (labelJson.find("color") != labelJson.end()) { auto jcolor = labelJson["color"]["value"]; Color color; color.SetRed(jcolor[0].get()); color.SetGreen(jcolor[1].get()); color.SetBlue(jcolor[2].get()); resultLabel->SetColor(color); } bool locked = false; if (GetValueFromJson(labelJson, "locked", locked)) resultLabel->SetLocked(locked); float opacity = 1.; if (GetValueFromJson(labelJson, "opacity", opacity)) resultLabel->SetOpacity(opacity); bool visible = true; if (GetValueFromJson(labelJson, "visible", visible)) resultLabel->SetVisible(visible); return resultLabel; } diff --git a/Modules/Multilabel/mitkMultiLabelIOHelper.h b/Modules/Multilabel/mitkMultiLabelIOHelper.h index a30e22787e..0345aa2fcb 100644 --- a/Modules/Multilabel/mitkMultiLabelIOHelper.h +++ b/Modules/Multilabel/mitkMultiLabelIOHelper.h @@ -1,127 +1,127 @@ /*============================================================================ 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 mitkMultiLabelIOHelper_h #define mitkMultiLabelIOHelper_h -#include +#include #include #include #include namespace tinyxml2 { class XMLDocument; class XMLElement; } namespace itk { class MetaDataDictionary; } namespace mitk { class LabelSetImage; const constexpr char* const PROPERTY_NAME_TIMEGEOMETRY_TYPE = "org.mitk.timegeometry.type"; const constexpr char* const PROPERTY_NAME_TIMEGEOMETRY_TIMEPOINTS = "org.mitk.timegeometry.timepoints"; const constexpr char* const PROPERTY_KEY_TIMEGEOMETRY_TYPE = "org_mitk_timegeometry_type"; const constexpr char* const PROPERTY_KEY_TIMEGEOMETRY_TIMEPOINTS = "org_mitk_timegeometry_timepoints"; const constexpr char* const PROPERTY_KEY_UID = "org_mitk_uid"; /** * @brief The MultiLabelIOHelper is a static helper class that supports serialization of mitk::LabelSetImage * * This class provides static functions for converting mitk::Label into XML and also allows the serialization * of mitk::LabelSet as presets */ class MITKMULTILABEL_EXPORT MultiLabelIOHelper { public: /** * @brief Saves the mitk::LabelSet configuration of inputImage to presetFilename. * The preset is stored as "*.lsetp" * @param presetFilename the filename including the filesystem path * @param inputImage the input image from which the preset should be generated * @return true if the serialization was successful and false otherwise */ static bool SaveLabelSetImagePreset(const std::string &presetFilename, const mitk::LabelSetImage *inputImage); /** * @brief Loads an existing preset for a mitk::LabelSetImage from presetFilename and applies it to inputImage * @param presetFilename the filename of the preset including the filesystem path * @param inputImage the image to which the loaded preset will be applied * @return true if the deserilization was successful and false otherwise */ static bool LoadLabelSetImagePreset(const std::string &presetFilename, mitk::LabelSetImage *inputImage); /** * @brief Creates a mitk::Label from an XML element * @param labelElem the xml element from which a mitk::Label will be created * @return the created mitk::Label */ static itk::SmartPointer LoadLabelFromXMLDocument(const tinyxml2::XMLElement *labelElem); /** * @brief Creates an XML element from a mitk::Label * @param doc * @param label the mitk::Label from which the xml element will be created * @return the created XML element */ - static tinyxml2::XMLElement *GetLabelAsXMLElement(tinyxml2::XMLDocument &doc, Label *label); + static tinyxml2::XMLElement *GetLabelAsXMLElement(tinyxml2::XMLDocument &doc, const Label *label); /** * @brief Since a mitk::Label is basically a mitk::PropertyList this function coverts the label's properties into * XML * @param doc * @param key the property's key which will be used in the XML element * @param property the mitk::BaseProperty that should be converted * @return the created XML element */ static tinyxml2::XMLElement *PropertyToXMLElement(tinyxml2::XMLDocument& doc, const std::string &key, const BaseProperty *property); /** * @brief Since a mitk::Label is basically a mitk::PropertyList this function coverts a XML element into a property * @param key the property's key * @param prop the mitk::BaseProperty that will be created * @param elem the XML elem from which the property will be created * @return true if the conversion was successful and false otherwise */ static bool PropertyFromXMLElement(std::string &key, itk::SmartPointer &prop, const tinyxml2::XMLElement *elem); /** Helper that extracts the value of a key in a meta dictionary as int. * If the key does not exist 0 is returned.*/ static int GetIntByKey(const itk::MetaDataDictionary& dic, const std::string& key); /** Helper that extracts the value of a key in a meta dictionary as string. * If the key does not exist an empty string is returned.*/ static std::string GetStringByKey(const itk::MetaDataDictionary& dic, const std::string& key); static nlohmann::json SerializeMultLabelGroupsToJSON(const mitk::LabelSetImage* inputImage); - static std::vector DeserializeMultiLabelGroupsFromJSON(const nlohmann::json& listOfLabelSets); + static std::vector DeserializeMultiLabelGroupsFromJSON(const nlohmann::json& listOfLabelSets); static nlohmann::json SerializeLabelToJSON(const Label* label); static mitk::Label::Pointer DeserializeLabelFromJSON(const nlohmann::json& labelJson); private: MultiLabelIOHelper(); }; } #endif diff --git a/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp index 86ccf33984..046c4aecb7 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp +++ b/Modules/Segmentation/Algorithms/mitkDiffImageApplier.cpp @@ -1,376 +1,376 @@ /*============================================================================ 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 "mitkDiffImageApplier.h" #include "mitkApplyDiffImageOperation.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include "mitkRenderingManager.h" #include "mitkSegmentationInterpolationController.h" #include #include #include #include mitk::DiffImageApplier::DiffImageApplier() : m_Image(nullptr), m_SliceDifferenceImage(nullptr), m_SliceIndex(0), m_SliceDimension(0), m_TimeStep(0), m_Dimension0(0), m_Dimension1(0), m_DestinationLabel(std::numeric_limits::max()), m_Factor(1.0) { } mitk::DiffImageApplier::~DiffImageApplier() { } void mitk::DiffImageApplier::SetDestinationLabel(mitk::Label::PixelType label) { m_DestinationLabel = label; } void mitk::DiffImageApplier::ExecuteOperation(Operation *operation) { auto *imageOperation = dynamic_cast(operation); if (imageOperation // we actually have the kind of operation that we can handle && imageOperation->IsImageStillValid()) // AND the image is not yet deleted { m_Image = imageOperation->GetImage(); Image::Pointer image3D = m_Image; // will be changed later in case of 3D+t m_SliceDifferenceImage = imageOperation->GetDiffImage(); m_TimeStep = imageOperation->GetTimeStep(); m_Factor = imageOperation->GetFactor(); if (m_SliceDifferenceImage->GetDimension() == 2) { m_SliceIndex = imageOperation->GetSliceIndex(); m_SliceDimension = imageOperation->GetSliceDimension(); switch (m_SliceDimension) { default: case 2: m_Dimension0 = 0; m_Dimension1 = 1; break; case 1: m_Dimension0 = 0; m_Dimension1 = 2; break; case 0: m_Dimension0 = 1; m_Dimension1 = 2; break; } if (m_SliceDifferenceImage->GetDimension() != 2 || (m_Image->GetDimension() < 3 || m_Image->GetDimension() > 4) || m_SliceDifferenceImage->GetDimension(0) != m_Image->GetDimension(m_Dimension0) || m_SliceDifferenceImage->GetDimension(1) != m_Image->GetDimension(m_Dimension1) || m_SliceIndex >= m_Image->GetDimension(m_SliceDimension)) { itkExceptionMacro( "Slice and image dimensions differ or slice index is too large. Sorry, cannot work like this."); return; } if (m_Image->GetDimension() == 4) { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(m_Image); timeSelector->SetTimeNr(m_TimeStep); timeSelector->UpdateLargestPossibleRegion(); image3D = timeSelector->GetOutput(); } AccessFixedDimensionByItk(image3D, ItkImageSwitch2DDiff, 3); if (m_Factor == 1 || m_Factor == -1) { if (m_Factor == -1) { // multiply diff pixels by factor and then send this diff slice AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 2); } // just send the diff to SegmentationInterpolationController SegmentationInterpolationController *interpolator = SegmentationInterpolationController::InterpolatorForImage(m_Image); if (interpolator) { interpolator->BlockModified(true); interpolator->SetChangedSlice(m_SliceDifferenceImage, m_SliceDimension, m_SliceIndex, m_TimeStep); } m_Image->Modified(); if (interpolator) { interpolator->BlockModified(false); } if (m_Factor == -1) // return to normal values { AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 2); } } else // no trivial case, too lazy to do something else { m_Image->Modified(); // check if interpolation is called. prefer to send diff directly } RenderingManager::GetInstance()->RequestUpdateAll(); } else if (m_SliceDifferenceImage->GetDimension() == 3) { // ... if (m_SliceDifferenceImage->GetDimension(0) != m_Image->GetDimension(0) || m_SliceDifferenceImage->GetDimension(1) != m_Image->GetDimension(1) || m_SliceDifferenceImage->GetDimension(2) != m_Image->GetDimension(2) || m_TimeStep >= m_Image->GetDimension(3)) { itkExceptionMacro("Diff image size differs from original image size. Sorry, cannot work like this."); return; } if (m_Image->GetDimension() == 4) { ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); timeSelector->SetInput(m_Image); timeSelector->SetTimeNr(m_TimeStep); timeSelector->UpdateLargestPossibleRegion(); image3D = timeSelector->GetOutput(); } auto labelSetImage = dynamic_cast(m_Image.GetPointer()); // this will do a long long if/else to find out both pixel types TransferLabelContentAtTimeStep( m_SliceDifferenceImage, labelSetImage, - labelSetImage->GetActiveLabelSet(), + labelSetImage->GetConstLabelsByValue(labelSetImage->GetLabelValuesByGroup(labelSetImage->GetActiveLayer())), m_TimeStep, 0, 0, false, {{1, m_DestinationLabel}}, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); if (m_Factor == 1 || m_Factor == -1) { if (m_Factor == -1) { // multiply diff pixels by factor and then send this diff slice AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 3); } // just send the diff to SegmentationInterpolationController SegmentationInterpolationController *interpolator = SegmentationInterpolationController::InterpolatorForImage(m_Image); if (interpolator) { interpolator->BlockModified(true); interpolator->SetChangedVolume(m_SliceDifferenceImage, m_TimeStep); } if (interpolator) { interpolator->BlockModified(false); } if (m_Factor == -1) // return to normal values { AccessFixedDimensionByItk(m_SliceDifferenceImage, ItkInvertPixelValues, 3); } } else // no trivial case, too lazy to do something else { m_Image->Modified(); // check if interpolation is called. prefer to send diff directly } RenderingManager::GetInstance()->RequestUpdateAll(); } else { itkExceptionMacro("Diff image must be 2D or 3D. Sorry, cannot work like this."); return; } } m_Image = nullptr; m_SliceDifferenceImage = nullptr; } mitk::DiffImageApplier *mitk::DiffImageApplier::GetInstanceForUndo() { static DiffImageApplier::Pointer s_Instance = DiffImageApplier::New(); return s_Instance; } // basically copied from mitk/Core/Algorithms/mitkImageAccessByItk.h #define myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, pixeltype, dimension, itkimage2) \ if (typeId == MapPixelComponentType::value) \ \ { \ typedef itk::Image ImageType; \ typedef mitk::ImageToItk ImageToItkType; \ itk::SmartPointer imagetoitk = ImageToItkType::New(); \ const mitk::Image *constImage = mitkImage; \ mitk::Image *nonConstImage = const_cast(constImage); \ nonConstImage->Update(); \ imagetoitk->SetInput(nonConstImage); \ imagetoitk->Update(); \ itkImageTypeFunction(imagetoitk->GetOutput(), itkimage2); \ \ } #define myMITKDiffImageApplierFilterAccessAllTypesByItk(mitkImage, itkImageTypeFunction, dimension, itkimage2) \ \ { \ myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, double, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk( \ mitkImage, \ itkImageTypeFunction, \ float, \ dimension, \ itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, int, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, \ itkImageTypeFunction, \ unsigned int, \ dimension, \ itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, short, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, itkImageTypeFunction, unsigned short, dimension, itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, \ itkImageTypeFunction, \ char, \ dimension, \ itkimage2) else myMITKDiffImageApplierFilterAccessByItk(mitkImage, \ itkImageTypeFunction, \ unsigned char, \ dimension, \ itkimage2) \ \ } template void mitk::DiffImageApplier::ItkImageSwitch2DDiff(itk::Image *itkImage) { const auto typeId = m_SliceDifferenceImage->GetPixelType().GetComponentType(); myMITKDiffImageApplierFilterAccessAllTypesByItk(m_SliceDifferenceImage, ItkImageProcessing2DDiff, 2, itkImage); } template void mitk::DiffImageApplier::ItkImageSwitch3DDiff(itk::Image *itkImage) { const auto typeId = m_SliceDifferenceImage->GetPixelType().GetComponentType(); myMITKDiffImageApplierFilterAccessAllTypesByItk(m_SliceDifferenceImage, ItkImageProcessing3DDiff, 3, itkImage); } template void mitk::DiffImageApplier::ItkImageProcessing2DDiff(itk::Image *diffImage, itk::Image *outputImage) { typedef itk::Image DiffImageType; typedef itk::Image VolumeImageType; typedef itk::ImageSliceIteratorWithIndex OutputSliceIteratorType; typedef itk::ImageRegionConstIterator DiffSliceIteratorType; typename VolumeImageType::RegionType sliceInVolumeRegion; sliceInVolumeRegion = outputImage->GetLargestPossibleRegion(); sliceInVolumeRegion.SetSize(m_SliceDimension, 1); // just one slice sliceInVolumeRegion.SetIndex(m_SliceDimension, m_SliceIndex); // exactly this slice, please OutputSliceIteratorType outputIterator(outputImage, sliceInVolumeRegion); outputIterator.SetFirstDirection(m_Dimension0); outputIterator.SetSecondDirection(m_Dimension1); DiffSliceIteratorType diffIterator(diffImage, diffImage->GetLargestPossibleRegion()); // iterate over output slice (and over input slice simultaneously) outputIterator.GoToBegin(); diffIterator.GoToBegin(); while (!outputIterator.IsAtEnd()) { while (!outputIterator.IsAtEndOfSlice()) { while (!outputIterator.IsAtEndOfLine()) { TPixel2 newValue = outputIterator.Get() + (TPixel2)((double)diffIterator.Get() * m_Factor); outputIterator.Set(newValue); ++outputIterator; ++diffIterator; } outputIterator.NextLine(); } outputIterator.NextSlice(); } } template void mitk::DiffImageApplier::ItkImageProcessing3DDiff(itk::Image *diffImage, itk::Image *outputImage) { typedef itk::Image DiffImageType; typedef itk::Image VolumeImageType; typedef itk::ImageRegionIterator OutputSliceIteratorType; typedef itk::ImageRegionConstIterator DiffSliceIteratorType; OutputSliceIteratorType outputIterator(outputImage, outputImage->GetLargestPossibleRegion()); DiffSliceIteratorType diffIterator(diffImage, diffImage->GetLargestPossibleRegion()); // iterate over output slice (and over input slice simultaneously) outputIterator.GoToBegin(); diffIterator.GoToBegin(); while (!outputIterator.IsAtEnd()) { TPixel2 newValue = outputIterator.Get() + (TPixel2)((double)diffIterator.Get() * m_Factor); outputIterator.Set(newValue); ++outputIterator; ++diffIterator; } } #ifdef _MSC_VER # pragma warning(push) # pragma warning(disable:4146) // unary minus operator applied to unsigned type, result still unsigned #endif template void mitk::DiffImageApplier::ItkInvertPixelValues(itk::Image *itkImage) { typedef itk::ImageRegionIterator> IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { iter.Set(-(iter.Get())); ++iter; } } #ifdef _MSC_VER # pragma warning(pop) #endif diff --git a/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp b/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp index fc8ce4fbdf..dda2126a15 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp +++ b/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp @@ -1,89 +1,78 @@ /*============================================================================ 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 "mitkDiffSliceOperationApplier.h" #include "mitkDiffSliceOperation.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include #include // VTK #include mitk::DiffSliceOperationApplier::DiffSliceOperationApplier() { } mitk::DiffSliceOperationApplier::~DiffSliceOperationApplier() { } void mitk::DiffSliceOperationApplier::ExecuteOperation(Operation *operation) { auto *imageOperation = dynamic_cast(operation); // as we only support DiffSliceOperation return if operation is not type of DiffSliceOperation if (!imageOperation) return; // check if the operation is valid if (imageOperation->IsValid()) { // the actual overwrite filter (vtk) vtkSmartPointer reslice = vtkSmartPointer::New(); mitk::Image::Pointer slice = imageOperation->GetSlice(); // Set the slice as 'input' reslice->SetInputSlice(slice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); // a wrapper for vtkImageOverwrite mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(imageOperation->GetImage()); extractor->SetTimeStep(imageOperation->GetTimeStep()); extractor->SetWorldGeometry(dynamic_cast(imageOperation->GetWorldGeometry())); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(imageOperation->GetImage()->GetGeometry(imageOperation->GetTimeStep())); extractor->Modified(); extractor->Update(); // make sure the modification is rendered RenderingManager::GetInstance()->RequestUpdateAll(); imageOperation->GetImage()->Modified(); - mitk::ExtractSliceFilter::Pointer extractor2 = mitk::ExtractSliceFilter::New(); - extractor2->SetInput(imageOperation->GetImage()); - extractor2->SetTimeStep(imageOperation->GetTimeStep()); - extractor2->SetWorldGeometry(dynamic_cast(imageOperation->GetWorldGeometry())); - extractor2->SetResliceTransformByGeometry(imageOperation->GetImage()->GetGeometry(imageOperation->GetTimeStep())); - extractor2->Modified(); - extractor2->Update(); - - // TODO Move this code to SurfaceInterpolationController! - mitk::Image::Pointer slice2 = extractor2->GetOutput(); - mitk::PlaneGeometry::ConstPointer plane = dynamic_cast(imageOperation->GetWorldGeometry()); - slice2->DisconnectPipeline(); - mitk::SegTool2D::UpdateSurfaceInterpolation(slice2, imageOperation->GetImage(), plane, true); + PlaneGeometry::ConstPointer plane = dynamic_cast(imageOperation->GetWorldGeometry()); + SegTool2D::UpdateAllSurfaceInterpolations(dynamic_cast(imageOperation->GetImage()), imageOperation->GetTimeStep(), plane, true); } } mitk::DiffSliceOperationApplier *mitk::DiffSliceOperationApplier::GetInstance() { static auto *s_Instance = new DiffSliceOperationApplier(); return s_Instance; } diff --git a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp index f64e713f13..64efffee41 100644 --- a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp +++ b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp @@ -1,333 +1,325 @@ /*============================================================================ 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 "mitkShowSegmentationAsSurface.h" #include "mitkManualSegmentationToSurfaceFilter.h" #include "mitkVtkRepresentationProperty.h" #include #include #include namespace mitk { ShowSegmentationAsSurface::ShowSegmentationAsSurface() : m_UIDGeneratorSurfaces("Surface_"), m_IsLabelSetImage(false) { } ShowSegmentationAsSurface::~ShowSegmentationAsSurface() { } void ShowSegmentationAsSurface::Initialize(const NonBlockingAlgorithm *other) { Superclass::Initialize(other); bool syncVisibility(false); if (other) { other->GetParameter("Sync visibility", syncVisibility); } SetParameter("Sync visibility", syncVisibility); SetParameter("Median kernel size", 3u); SetParameter("Apply median", true); SetParameter("Smooth", true); SetParameter("Gaussian SD", 1.5); SetParameter("Decimate mesh", true); SetParameter("Decimation rate", 0.8); SetParameter("Wireframe", false); m_SurfaceNodes.clear(); } bool ShowSegmentationAsSurface::ReadyToRun() { try { Image::Pointer image; GetPointerParameter("Input", image); return image.IsNotNull() && GetGroupNode(); } catch (std::invalid_argument &) { return false; } } bool ShowSegmentationAsSurface::ThreadedUpdateFunction() { Image::Pointer image; GetPointerParameter("Input", image); bool smooth(true); GetParameter("Smooth", smooth); bool applyMedian(true); GetParameter("Apply median", applyMedian); bool decimateMesh(true); GetParameter("Decimate mesh", decimateMesh); unsigned int medianKernelSize(3); GetParameter("Median kernel size", medianKernelSize); double gaussianSD(1.5); GetParameter("Gaussian SD", gaussianSD); double reductionRate(0.8); GetParameter("Decimation rate", reductionRate); MITK_INFO << "Creating polygon model with smoothing " << smooth << " gaussianSD " << gaussianSD << " median " << applyMedian << " median kernel " << medianKernelSize << " mesh reduction " << decimateMesh << " reductionRate " << reductionRate; auto labelSetImage = dynamic_cast(image.GetPointer()); if (nullptr != labelSetImage) { - auto numberOfLayers = labelSetImage->GetNumberOfLayers(); + const auto labels = labelSetImage->GetLabels(); - for (decltype(numberOfLayers) layerIndex = 0; layerIndex < numberOfLayers; ++layerIndex) + for (auto label : labels) { - auto labelSet = labelSetImage->GetLabelSet(layerIndex); + auto labelImage = labelSetImage->CreateLabelMask(label->GetValue()); - for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) - { - if (0 == labelIter->first) - continue; // Do not process background label - - auto labelImage = labelSetImage->CreateLabelMask(labelIter->first, false, layerIndex); - - if (labelImage.IsNull()) - continue; + if (labelImage.IsNull()) + continue; - auto labelSurface = this->ConvertBinaryImageToSurface(labelImage); + auto labelSurface = this->ConvertBinaryImageToSurface(labelImage); - if (labelSurface.IsNull()) - continue; + if (labelSurface.IsNull()) + continue; - auto* polyData = labelSurface->GetVtkPolyData(); + auto* polyData = labelSurface->GetVtkPolyData(); - if (smooth && (polyData->GetNumberOfPoints() < 1 || polyData->GetNumberOfCells() < 1)) - { - MITK_WARN << "Label \"" << labelIter->second->GetName() << "\" didn't produce any smoothed surface data (try again without smoothing)."; - continue; - } + if (smooth && (polyData->GetNumberOfPoints() < 1 || polyData->GetNumberOfCells() < 1)) + { + MITK_WARN << "Label \"" << label->GetName() << "\" didn't produce any smoothed surface data (try again without smoothing)."; + continue; + } - auto node = DataNode::New(); - node->SetData(labelSurface); - node->SetColor(labelIter->second->GetColor()); - node->SetName(labelIter->second->GetName()); + auto node = DataNode::New(); + node->SetData(labelSurface); + node->SetColor(label->GetColor()); + node->SetName(label->GetName()); - m_SurfaceNodes.push_back(node); - } + m_SurfaceNodes.push_back(node); } } else { auto surface = this->ConvertBinaryImageToSurface(image); if (surface.IsNotNull()) { auto* polyData = surface->GetVtkPolyData(); if (smooth && (polyData->GetNumberOfPoints() < 1 || polyData->GetNumberOfCells() < 1)) { MITK_WARN << "Could not produce smoothed surface data (try again without smoothing)."; } else { auto node = DataNode::New(); node->SetData(surface); m_SurfaceNodes.push_back(node); } } } m_IsLabelSetImage = nullptr != labelSetImage; return true; } void ShowSegmentationAsSurface::ThreadedUpdateSuccessful() { for (const auto &node : m_SurfaceNodes) { bool wireframe = false; GetParameter("Wireframe", wireframe); if (wireframe) { auto representation = dynamic_cast(node->GetProperty("material.representation")); if (nullptr != representation) representation->SetRepresentationToWireframe(); } node->SetProperty("opacity", FloatProperty::New(0.3f)); node->SetProperty("line width", FloatProperty::New(1.0f)); node->SetProperty("scalar visibility", BoolProperty::New(false)); auto name = node->GetName(); auto groupNode = this->GetGroupNode(); if (!m_IsLabelSetImage) { if ((name.empty() || DataNode::NO_NAME_VALUE() == name) && nullptr != groupNode) name = groupNode->GetName(); if (name.empty()) name = "Surface"; } bool smooth = true; GetParameter("Smooth", smooth); if (smooth) name.append(" (smoothed)"); node->SetName(name); if (!m_IsLabelSetImage) { auto colorProp = groupNode->GetProperty("color"); if (nullptr != colorProp) { node->ReplaceProperty("color", colorProp->Clone()); } else { node->SetProperty("color", ColorProperty::New(1.0, 1.0, 0.0)); } } bool showResult = true; GetParameter("Show result", showResult); bool syncVisibility = false; GetParameter("Sync visibility", syncVisibility); auto visibleProp = groupNode->GetProperty("visible"); if (nullptr != visibleProp && syncVisibility) { node->ReplaceProperty("visible", visibleProp->Clone()); } else { node->SetProperty("visible", BoolProperty::New(showResult)); } if (!m_IsLabelSetImage) { Image::Pointer image; GetPointerParameter("Input", image); if (image.IsNotNull()) { auto organTypeProp = image->GetProperty("organ type"); if (nullptr != organTypeProp) node->GetData()->SetProperty("organ type", organTypeProp); } } this->InsertBelowGroupNode(node); } Superclass::ThreadedUpdateSuccessful(); } Surface::Pointer ShowSegmentationAsSurface::ConvertBinaryImageToSurface(Image::Pointer binaryImage) { bool smooth = true; GetParameter("Smooth", smooth); bool applyMedian = true; GetParameter("Apply median", applyMedian); bool decimateMesh = true; GetParameter("Decimate mesh", decimateMesh); unsigned int medianKernelSize = 3; GetParameter("Median kernel size", medianKernelSize); double gaussianSD = 1.5; GetParameter("Gaussian SD", gaussianSD); double reductionRate = 0.8; GetParameter("Decimation rate", reductionRate); auto filter = ManualSegmentationToSurfaceFilter::New(); filter->SetInput(binaryImage); filter->SetThreshold(0.5); filter->SetUseGaussianImageSmooth(smooth); filter->SetSmooth(smooth); filter->SetMedianFilter3D(applyMedian); if (smooth) { filter->InterpolationOn(); filter->SetGaussianStandardDeviation(gaussianSD); } if (applyMedian) filter->SetMedianKernelSize(medianKernelSize, medianKernelSize, medianKernelSize); // Fix to avoid VTK warnings (see T5390) if (binaryImage->GetDimension() > 3) decimateMesh = false; if (decimateMesh) { filter->SetDecimate(ImageToSurfaceFilter::QuadricDecimation); filter->SetTargetReduction(reductionRate); } else { filter->SetDecimate(ImageToSurfaceFilter::NoDecimation); } filter->UpdateLargestPossibleRegion(); auto surface = filter->GetOutput(); auto polyData = surface->GetVtkPolyData(); if (nullptr == polyData) throw std::logic_error("Could not create polygon model"); polyData->SetVerts(nullptr); polyData->SetLines(nullptr); if (smooth || applyMedian || decimateMesh) { auto normals = vtkSmartPointer::New(); normals->AutoOrientNormalsOn(); normals->FlipNormalsOff(); normals->SetInputData(polyData); normals->Update(); surface->SetVtkPolyData(normals->GetOutput()); } else { surface->SetVtkPolyData(polyData); } return surface; } } diff --git a/Modules/Segmentation/Controllers/mitkSliceBasedInterpolationController.cpp b/Modules/Segmentation/Controllers/mitkSliceBasedInterpolationController.cpp deleted file mode 100644 index 68bfe9b4c9..0000000000 --- a/Modules/Segmentation/Controllers/mitkSliceBasedInterpolationController.cpp +++ /dev/null @@ -1,442 +0,0 @@ -/*============================================================================ - -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 "mitkSliceBasedInterpolationController.h" - -#include "mitkExtractSliceFilter.h" -#include "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkImageReadAccessor.h" -#include "mitkImageTimeSelector.h" - -#include "mitkShapeBasedInterpolationAlgorithm.h" - -#include -#include -#include - -mitk::SliceBasedInterpolationController::InterpolatorMapType - mitk::SliceBasedInterpolationController::s_InterpolatorForImage; // static member initialization - -mitk::SliceBasedInterpolationController *mitk::SliceBasedInterpolationController::InterpolatorForImage( - const Image *image) -{ - auto iter = s_InterpolatorForImage.find(image); - if (iter != s_InterpolatorForImage.end()) - { - return iter->second; - } - else - { - return nullptr; - } -} - -mitk::SliceBasedInterpolationController::SliceBasedInterpolationController() - : m_WorkingImage(nullptr), m_ReferenceImage(nullptr) -{ -} - -mitk::SliceBasedInterpolationController::~SliceBasedInterpolationController() -{ - // remove this from the list of interpolators - for (auto iter = s_InterpolatorForImage.begin(); iter != s_InterpolatorForImage.end(); - ++iter) - { - if (iter->second == this) - { - s_InterpolatorForImage.erase(iter); - break; - } - } -} - -void mitk::SliceBasedInterpolationController::ResetLabelCount() -{ - m_LabelCountInSlice.clear(); - int numberOfLabels = m_WorkingImage->GetNumberOfLabels(); - m_LabelCountInSlice.resize(m_WorkingImage->GetTimeSteps()); - - for (unsigned int timeStep = 0; timeStep < m_WorkingImage->GetTimeSteps(); ++timeStep) - { - m_LabelCountInSlice[timeStep].resize(3); - for (unsigned int dim = 0; dim < 3; ++dim) - { - m_LabelCountInSlice[timeStep][dim].clear(); - m_LabelCountInSlice[timeStep][dim].resize(m_WorkingImage->GetDimension(dim)); - for (unsigned int slice = 0; slice < m_WorkingImage->GetDimension(dim); ++slice) - { - m_LabelCountInSlice[timeStep][dim][slice].clear(); - m_LabelCountInSlice[timeStep][dim][slice].resize(numberOfLabels); - m_LabelCountInSlice[timeStep][dim][slice].assign(numberOfLabels, 0); - } - } - } -} - -void mitk::SliceBasedInterpolationController::SetWorkingImage(LabelSetImage *newImage) -{ - if (m_WorkingImage != newImage) - { - // delete the current working image from the list of interpolators - auto iter = s_InterpolatorForImage.find(m_WorkingImage); - if (iter != s_InterpolatorForImage.end()) - { - s_InterpolatorForImage.erase(iter); - } - - m_WorkingImage = newImage; - - s_InterpolatorForImage.insert(std::make_pair(m_WorkingImage, this)); - } - - this->ResetLabelCount(); - - AccessFixedDimensionByItk_1(m_WorkingImage, ScanImageITKProcessing, 3, 0); - - // for all timesteps, scan whole image: TODO: enable this again for 3D+time - /* - for (unsigned int timeStep = 0; timeStep < m_WorkingImage->GetTimeSteps(); ++timeStep) - { - ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput( m_WorkingImage ); - timeSelector->SetTimeNr( timeStep ); - timeSelector->UpdateLargestPossibleRegion(); - Image::Pointer segmentation3D = timeSelector->GetOutput(); - this->SetChangedVolume( segmentation3D.GetPointer(), timeStep ); - } - */ - // this->Modified(); -} - -void mitk::SliceBasedInterpolationController::SetReferenceImage(Image *newImage) -{ - if (!newImage) - return; - - m_ReferenceImage = newImage; - - // ensure the reference image has the same dimensionality and extents as the segmentation image - if (m_WorkingImage.IsNull() || m_ReferenceImage->GetDimension() != m_WorkingImage->GetDimension() || - m_ReferenceImage->GetPixelType().GetNumberOfComponents() != 1 || - m_WorkingImage->GetPixelType().GetNumberOfComponents() != 1) - { - MITK_WARN << "Segmentation image has different image characteristics than reference image." << std::endl; - m_ReferenceImage = nullptr; - return; - } - - for (unsigned int dim = 0; dim < m_WorkingImage->GetDimension(); ++dim) - { - if (m_ReferenceImage->GetDimension(dim) != m_WorkingImage->GetDimension(dim)) - { - MITK_WARN << "original patient image does not match segmentation (different extent in dimension " << dim - << "), ignoring patient image" << std::endl; - m_ReferenceImage = nullptr; - return; - } - } -} - -void mitk::SliceBasedInterpolationController::SetChangedSlice(const Image *slice, - unsigned int sliceDimension, - unsigned int sliceIndex, - unsigned int timeStep) -{ - if (!slice) - return; - if (slice->GetDimension() != 2) - return; - if (sliceDimension > 2) - return; - if (m_WorkingImage.IsNull()) - return; - - // check if the number of labels has changed - auto numberOfLabels = m_WorkingImage->GetNumberOfLabels(); - if (m_LabelCountInSlice[0][0][0].size() != numberOfLabels) - return; - - unsigned int dim0(0); - unsigned int dim1(1); - - // determine the other two dimensions - switch (sliceDimension) - { - default: - case 2: - dim0 = 0; - dim1 = 1; - break; - case 1: - dim0 = 0; - dim1 = 2; - break; - case 0: - dim0 = 1; - dim1 = 2; - break; - } - - AccessFixedDimensionByItk_1( - slice, ScanSliceITKProcessing, 2, SetChangedSliceOptions(sliceDimension, sliceIndex, dim0, dim1, timeStep)); - - // this->Modified(); -} - -template -void mitk::SliceBasedInterpolationController::ScanSliceITKProcessing(const itk::Image *input, - const SetChangedSliceOptions &options) -{ - unsigned int timeStep = options.timeStep; - unsigned int sliceDimension = options.sliceDimension; - unsigned int sliceIndex = options.sliceIndex; - - if (sliceDimension > 2) - return; - if (sliceIndex >= m_LabelCountInSlice[timeStep][sliceDimension].size()) - return; - - unsigned int dim0(options.dim0); - unsigned int dim1(options.dim1); - - std::vector numberOfPixels; // number of pixels in the current slice that are equal to the active label - unsigned int numberOfLabels = m_WorkingImage->GetNumberOfLabels(); - numberOfPixels.resize(numberOfLabels); - - typedef itk::Image ImageType; - typedef itk::ImageRegionConstIteratorWithIndex IteratorType; - - IteratorType iter(input, input->GetLargestPossibleRegion()); - iter.GoToBegin(); - - typename IteratorType::IndexType index; - - while (!iter.IsAtEnd()) - { - index = iter.GetIndex(); - auto value = static_cast(iter.Get()); - ++m_LabelCountInSlice[timeStep][dim0][index[0]][value]; - ++m_LabelCountInSlice[timeStep][dim1][index[1]][value]; - ++numberOfPixels[value]; - ++iter; - } - - for (unsigned int label = 0; label < numberOfLabels; ++label) - { - m_LabelCountInSlice[timeStep][sliceDimension][sliceIndex][label] = numberOfPixels[label]; - } -} - -template -void mitk::SliceBasedInterpolationController::ScanImageITKProcessing(itk::Image *input, - unsigned int timeStep) -{ - typedef itk::ImageSliceConstIteratorWithIndex> IteratorType; - - IteratorType iter(input, input->GetLargestPossibleRegion()); - iter.SetFirstDirection(0); - iter.SetSecondDirection(1); - - typename IteratorType::IndexType index; - unsigned int x = 0; - unsigned int y = 0; - unsigned int z = 0; - - std::vector numberOfPixels; // number of pixels per slice that are equal to the active label - unsigned int numberOfLabels = m_WorkingImage->GetNumberOfLabels(); - numberOfPixels.resize(numberOfLabels); - - iter.GoToBegin(); - while (!iter.IsAtEnd()) - { - while (!iter.IsAtEndOfSlice()) - { - while (!iter.IsAtEndOfLine()) - { - index = iter.GetIndex(); - x = index[0]; - y = index[1]; - z = index[2]; - - int value = static_cast(iter.Get()); - ++m_LabelCountInSlice[timeStep][0][x][value]; - ++m_LabelCountInSlice[timeStep][1][y][value]; - ++numberOfPixels[value]; - - ++iter; - } - iter.NextLine(); - } - - for (unsigned int label = 0; label < numberOfLabels; ++label) - m_LabelCountInSlice[timeStep][2][z][label] += numberOfPixels[label]; - - // clear label counter - numberOfPixels.assign(numberOfLabels, 0); - iter.NextSlice(); - } -} - -mitk::Image::Pointer mitk::SliceBasedInterpolationController::Interpolate(unsigned int sliceDimension, - unsigned int sliceIndex, - const mitk::PlaneGeometry *currentPlane, - unsigned int timeStep) -{ - if (m_WorkingImage.IsNull()) - return nullptr; - if (!currentPlane) - return nullptr; - if (timeStep >= m_LabelCountInSlice.size()) - return nullptr; - if (sliceDimension > 2) - return nullptr; - unsigned int upperLimit = m_LabelCountInSlice[timeStep][sliceDimension].size(); - if (sliceIndex >= upperLimit - 1) - return nullptr; // can't interpolate first and last slice - if (sliceIndex < 1) - return nullptr; - - int pixelValue = m_WorkingImage->GetActiveLabel()->GetValue(); - - // slice contains a segmentation, won't interpolate anything then - if (m_LabelCountInSlice[timeStep][sliceDimension][sliceIndex][pixelValue] > 0) - return nullptr; - - unsigned int lowerBound(0); - unsigned int upperBound(0); - bool bounds(false); - - for (lowerBound = sliceIndex - 1; /*lowerBound >= 0*/; --lowerBound) - { - if (m_LabelCountInSlice[timeStep][sliceDimension][lowerBound][pixelValue] > 0) - { - bounds = true; - break; - } - - if (lowerBound == 0) - break; - } - - if (!bounds) - return nullptr; - - bounds = false; - for (upperBound = sliceIndex + 1; upperBound < upperLimit; ++upperBound) - { - if (m_LabelCountInSlice[timeStep][sliceDimension][upperBound][pixelValue] > 0) - { - bounds = true; - break; - } - } - - if (!bounds) - return nullptr; - - // ok, we have found two neighboring slices with the active label - // (and we made sure that the current slice does NOT contain the active label - // Setting up the ExtractSliceFilter - mitk::ExtractSliceFilter::Pointer extractor = ExtractSliceFilter::New(); - extractor->SetInput(m_WorkingImage); - extractor->SetTimeStep(timeStep); - extractor->SetResliceTransformByGeometry(m_WorkingImage->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); - extractor->SetVtkOutputRequest(false); - - // Reslicing the current plane - extractor->SetWorldGeometry(currentPlane); - extractor->Modified(); - - try - { - extractor->Update(); - } - catch (const std::exception &e) - { - MITK_ERROR << "Error in 2D interpolation: " << e.what(); - return nullptr; - } - - mitk::Image::Pointer resultImage = extractor->GetOutput(); - resultImage->DisconnectPipeline(); - - // Creating PlaneGeometry for lower slice - mitk::PlaneGeometry::Pointer reslicePlane = currentPlane->Clone(); - - // Transforming the current origin so that it matches the lower slice - mitk::Point3D origin = currentPlane->GetOrigin(); - m_WorkingImage->GetSlicedGeometry()->WorldToIndex(origin, origin); - origin[sliceDimension] = lowerBound; - m_WorkingImage->GetSlicedGeometry()->IndexToWorld(origin, origin); - reslicePlane->SetOrigin(origin); - - // Extract the lower slice - extractor->SetWorldGeometry(reslicePlane); - extractor->Modified(); - - try - { - extractor->Update(); - } - catch (const std::exception &e) - { - MITK_ERROR << "Error in 2D interpolation: " << e.what(); - return nullptr; - } - - mitk::Image::Pointer lowerMITKSlice = extractor->GetOutput(); - lowerMITKSlice->DisconnectPipeline(); - - // Transforming the current origin so that it matches the upper slice - m_WorkingImage->GetSlicedGeometry()->WorldToIndex(origin, origin); - origin[sliceDimension] = upperBound; - m_WorkingImage->GetSlicedGeometry()->IndexToWorld(origin, origin); - reslicePlane->SetOrigin(origin); - - // Extract the upper slice - extractor->SetWorldGeometry(reslicePlane); - extractor->Modified(); - - try - { - extractor->Update(); - } - catch (const std::exception &e) - { - MITK_ERROR << "Error in 2D interpolation: " << e.what(); - return nullptr; - } - - mitk::Image::Pointer upperMITKSlice = extractor->GetOutput(); - upperMITKSlice->DisconnectPipeline(); - - if (lowerMITKSlice.IsNull() || upperMITKSlice.IsNull()) - return nullptr; - - // interpolation algorithm gets some inputs - // two segmentations (guaranteed to be of the same data type, but no special data type guaranteed) - // orientation (sliceDimension) of the segmentations - // position of the two slices (sliceIndices) - // one volume image (original patient image) - // - // interpolation algorithm can use e.g. itk::ImageSliceConstIteratorWithIndex to - // inspect the original patient image at appropriate positions - - mitk::SegmentationInterpolationAlgorithm::Pointer algorithm = - mitk::ShapeBasedInterpolationAlgorithm::New().GetPointer(); - - algorithm->Interpolate( - lowerMITKSlice.GetPointer(), lowerBound, upperMITKSlice.GetPointer(), upperBound, sliceIndex, 0, resultImage); - - return resultImage; -} diff --git a/Modules/Segmentation/Controllers/mitkSliceBasedInterpolationController.h b/Modules/Segmentation/Controllers/mitkSliceBasedInterpolationController.h deleted file mode 100644 index f0fab35c3e..0000000000 --- a/Modules/Segmentation/Controllers/mitkSliceBasedInterpolationController.h +++ /dev/null @@ -1,190 +0,0 @@ -/*============================================================================ - -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 mitkSliceBasedInterpolationController_h -#define mitkSliceBasedInterpolationController_h - -#include "mitkLabelSetImage.h" -#include - -#include -#include - -#include -#include - -namespace mitk -{ - class Image; - - /** - \brief Generates interpolations of 2D slices. - - \sa QmitkSlicesInterpolator - \sa QmitkInteractiveSegmentation - - \ingroup ToolManagerEtAl - - This class keeps track of the contents of a 3D segmentation image. - \attention mitk::SliceBasedInterpolationController assumes that the image contains pixel values of 0 and 1. - - After you set the segmentation image using SetSegmentationVolume(), the whole image is scanned for pixels other than - 0. - SliceBasedInterpolationController registers as an observer to the segmentation image, and repeats the scan whenvever - the - image is modified. - - You can prevent this (time consuming) scan if you do the changes slice-wise and send difference images to - SliceBasedInterpolationController. - For this purpose SetChangedSlice() should be used. mitk::OverwriteImageFilter already does this every time it - changes a - slice of an image. There is a static method InterpolatorForImage(), which can be used to find out if there already - is an interpolator - instance for a specified image. OverwriteImageFilter uses this to get to know its interpolator. - - SliceBasedInterpolationController needs to maintain some information about the image slices (in every dimension). - This information is stored internally in m_SegmentationCountInSlice, which is basically three std::vectors (one for - each dimension). - Each item describes one image dimension, each vector item holds the count of pixels in "its" slice. This is perhaps - better to understand - from the following picture (where red items just mean to symbolize "there is some segmentation" - in reality there - is an integer count). - - $Author$ - */ - class MITKSEGMENTATION_EXPORT SliceBasedInterpolationController : public itk::Object - { - public: - mitkClassMacroItkParent(SliceBasedInterpolationController, itk::Object); - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); - - /** - \brief Find interpolator for a given image. - \return nullptr if there is no interpolator yet. - - This method is useful if several "clients" modify the same image and want to access the interpolations. - Then they can share the same object. - */ - static SliceBasedInterpolationController *InterpolatorForImage(const Image *); - - /** - \brief Initialize with a whole volume. - - Will scan the volume for segmentation pixels (values other than 0) and fill some internal data structures. - You don't have to call this method every time something changes, but only - when several slices at once change. - - When you change a single slice, call SetChangedSlice() instead. - */ - void SetWorkingImage(LabelSetImage *image); - - /** - \brief Set a reference image (original patient image) - optional. - - If this image is set (must exactly match the dimensions of the segmentation), - the interpolation algorithm may consider image content to improve the interpolated - (estimated) segmentation. - */ - void SetReferenceImage(Image *image); - - /** - \brief Update after changing a single slice in the working image. - - \param image is a 2D image with the difference image of the slice determined by sliceDimension and sliceIndex. - The difference is (pixel value in the new slice minus pixel value in the old slice). - - \param sliceDimension Number of the dimension which is constant for all pixels of the meant slice. - - \param sliceIndex Which slice to take, in the direction specified by sliceDimension. Count starts from 0. - - \param timeStep Which time step is changed - */ - void SetChangedSlice(const Image *image, - unsigned int sliceDimension, - unsigned int sliceIndex, - unsigned int timeStep); - - /** - \brief Generates an interpolated image for the given slice. - - \param sliceDimension Number of the dimension which is constant for all pixels of the meant slice. - - \param sliceIndex Which slice to take, in the direction specified by sliceDimension. Count starts from 0. - - \param currentPlane - - \param timeStep Which time step to use - */ - Image::Pointer Interpolate(unsigned int sliceDimension, - unsigned int sliceIndex, - const mitk::PlaneGeometry *currentPlane, - unsigned int timeStep); - - /** - \brief Initializes the internal container with the number of voxels per label. - */ - void ResetLabelCount(); - - protected: - /** - \brief Protected class of mitk::SliceBasedInterpolationController. Don't use (you shouldn't be able to do so)! - */ - class MITKSEGMENTATION_EXPORT SetChangedSliceOptions - { - public: - SetChangedSliceOptions(unsigned int sd, unsigned int si, unsigned int d0, unsigned int d1, unsigned int t) - : sliceDimension(sd), sliceIndex(si), dim0(d0), dim1(d1), timeStep(t) - { - } - - unsigned int sliceDimension; - unsigned int sliceIndex; - unsigned int dim0; - unsigned int dim1; - unsigned int timeStep; - // void* pixelData; - }; - - typedef std::vector LabelCounterVectorType; - typedef std::vector LabelCounterSliceVectorType; - typedef std::vector> LabelCounterSliceTimeVectorType; - typedef std::map InterpolatorMapType; - - SliceBasedInterpolationController(); // purposely hidden - ~SliceBasedInterpolationController() override; - - /// internal scan of a single slice - template - void ScanSliceITKProcessing(const itk::Image *, const SetChangedSliceOptions &options); - - /// internal scan of the whole image - template - void ScanImageITKProcessing(itk::Image *, unsigned int timeStep); - - /** - An array that of flags. One for each dimension of the image. A flag is set, when a slice in a certain dimension - has at least one pixel that is not 0 (which would mean that it has to be considered by the interpolation - algorithm). - E.g. flags for axial slices are stored in m_SegmentationCountInSlice[0][index]. - Enhanced with time steps it is now m_SegmentationCountInSlice[timeStep][0][index] - */ - LabelCounterSliceTimeVectorType m_LabelCountInSlice; - - static InterpolatorMapType s_InterpolatorForImage; - - LabelSetImage::Pointer m_WorkingImage; - Image::Pointer m_ReferenceImage; - }; -} // namespace - -#endif diff --git a/Modules/Segmentation/Interactions/mitkCloseRegionTool.cpp b/Modules/Segmentation/Interactions/mitkCloseRegionTool.cpp index 73c00146ee..4e6abe5178 100644 --- a/Modules/Segmentation/Interactions/mitkCloseRegionTool.cpp +++ b/Modules/Segmentation/Interactions/mitkCloseRegionTool.cpp @@ -1,119 +1,119 @@ /*============================================================================ 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 "mitkCloseRegionTool.h" // us #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, CloseRegionTool, "Close tool"); } const char **mitk::CloseRegionTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::CloseRegionTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Close.svg"); return resource; } us::ModuleResource mitk::CloseRegionTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Close_Cursor.svg"); return resource; } const char *mitk::CloseRegionTool::GetName() const { return "Close"; } template void DoITKRegionClosing(const itk::Image* oldSegImage, mitk::Image::Pointer& filledRegionImage, itk::Index seedIndex, mitk::Label::PixelType& seedLabel) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; using FillHoleFilter = itk::BinaryFillholeImageFilter; seedLabel = oldSegImage->GetPixel(seedIndex); typename OutputImageType::Pointer itkResultImage; filledRegionImage = nullptr; try { auto regionGrower = RegionGrowingFilterType::New(); regionGrower->SetInput(oldSegImage); regionGrower->SetReplaceValue(1); regionGrower->AddSeed(seedIndex); regionGrower->SetLower(seedLabel); regionGrower->SetUpper(seedLabel); auto filler = FillHoleFilter::New(); filler->SetInput(regionGrower->GetOutput()); filler->SetForegroundValue(1); filler->Update(); itkResultImage = filler->GetOutput(); } catch (const itk::ExceptionObject&) { return; // can't work } catch (...) { return; } mitk::CastToMitkImage(itkResultImage, filledRegionImage); } mitk::Image::Pointer mitk::CloseRegionTool::GenerateFillImage(const Image* workingSlice, Point3D seedPoint, mitk::Label::PixelType& seedLabelValue) const { itk::Index<2> seedIndex; workingSlice->GetGeometry()->WorldToIndex(seedPoint, seedIndex); Image::Pointer fillImage; AccessFixedDimensionByItk_n(workingSlice, DoITKRegionClosing, 2, (fillImage, seedIndex, seedLabelValue)); - if (seedLabelValue == LabelSetImage::UnlabeledValue) + if (seedLabelValue == LabelSetImage::UNLABELED_VALUE) { return nullptr; } return fillImage; } void mitk::CloseRegionTool::PrepareFilling(const Image* /*workingSlice*/, Point3D /*seedPoint*/) { m_FillLabelValue = m_SeedLabelValue; m_MergeStyle = MultiLabelSegmentation::MergeStyle::Merge; }; diff --git a/Modules/Segmentation/Interactions/mitkEraseRegionTool.cpp b/Modules/Segmentation/Interactions/mitkEraseRegionTool.cpp index 65fa667b0c..2ce18ddc49 100644 --- a/Modules/Segmentation/Interactions/mitkEraseRegionTool.cpp +++ b/Modules/Segmentation/Interactions/mitkEraseRegionTool.cpp @@ -1,87 +1,87 @@ /*============================================================================ 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 "mitkEraseRegionTool.h" #include "mitkEraseRegionTool.xpm" #include #include #include // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, EraseRegionTool, "Erase tool"); } const char **mitk::EraseRegionTool::GetXPM() const { return mitkEraseRegionTool_xpm; } us::ModuleResource mitk::EraseRegionTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Erase.svg"); return resource; } us::ModuleResource mitk::EraseRegionTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Erase_Cursor.svg"); return resource; } const char *mitk::EraseRegionTool::GetName() const { return "Erase"; } template void DoFillImage(itk::Image* image) { image->FillBuffer(1); }; mitk::Image::Pointer mitk::EraseRegionTool::GenerateFillImage(const Image* workingSlice, Point3D seedPoint, mitk::Label::PixelType& seedLabelValue) const { itk::Index<2> seedIndex; workingSlice->GetGeometry()->WorldToIndex(seedPoint, seedIndex); using AccessorType = ImagePixelReadAccessor; AccessorType accessor(workingSlice); seedLabelValue = accessor.GetPixelByIndex(seedIndex); Image::Pointer fillImage; - if ( seedLabelValue == LabelSetImage::UnlabeledValue) + if ( seedLabelValue == LabelSetImage::UNLABELED_VALUE) { //clicked on background remove everything which is not locked. fillImage = workingSlice->Clone(); AccessByItk(fillImage, DoFillImage); } else { fillImage = Superclass::GenerateFillImage(workingSlice, seedPoint, seedLabelValue); } return fillImage; } void mitk::EraseRegionTool::PrepareFilling(const Image* /*workingSlice*/, Point3D /*seedPoint*/) { - m_FillLabelValue = LabelSetImage::UnlabeledValue; + m_FillLabelValue = LabelSetImage::UNLABELED_VALUE; }; diff --git a/Modules/Segmentation/Interactions/mitkFillRegionBaseTool.cpp b/Modules/Segmentation/Interactions/mitkFillRegionBaseTool.cpp index c539a758b6..2bf51465fc 100644 --- a/Modules/Segmentation/Interactions/mitkFillRegionBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFillRegionBaseTool.cpp @@ -1,147 +1,146 @@ /*============================================================================ 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 "mitkFillRegionBaseTool.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkRenderingManager.h" #include mitk::FillRegionBaseTool::FillRegionBaseTool() : SegTool2D("MouseReleaseOnly") { } mitk::FillRegionBaseTool::~FillRegionBaseTool() { } void mitk::FillRegionBaseTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("Release", OnClick); } template void DoITKRegionGrowing(const itk::Image* oldSegImage, mitk::Image::Pointer& filledRegionImage, itk::Index seedIndex, mitk::Label::PixelType& seedLabel ) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; seedLabel = oldSegImage->GetPixel(seedIndex); typename OutputImageType::Pointer itkResultImage; filledRegionImage = nullptr; try { typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); regionGrower->SetInput(oldSegImage); regionGrower->SetReplaceValue(1); regionGrower->AddSeed(seedIndex); regionGrower->SetLower(seedLabel); regionGrower->SetUpper(seedLabel); regionGrower->Update(); itkResultImage = regionGrower->GetOutput(); } catch (const itk::ExceptionObject&) { return; // can't work } catch (...) { return; } mitk::CastToMitkImage(itkResultImage, filledRegionImage); } void mitk::FillRegionBaseTool::OnClick(StateMachineAction*, InteractionEvent* interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; auto labelSetImage = dynamic_cast(this->GetWorkingData()); if (nullptr == labelSetImage) { return; } if (!IsPositionEventInsideImageRegion(positionEvent, labelSetImage)) { return; } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); auto workingSlice = this->GetAffectedWorkingSlice(positionEvent); auto click = positionEvent->GetPositionInWorld(); m_SeedLabelValue = 0; auto fillImage = this->GenerateFillImage(workingSlice, click, m_SeedLabelValue); if (fillImage.IsNull()) { return; //nothing to fill; } - if (labelSetImage->IsLabelLocked(m_SeedLabelValue) && m_SeedLabelValue!=labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue()) + if (labelSetImage->IsLabelLocked(m_SeedLabelValue) && m_SeedLabelValue!=labelSetImage->GetActiveLabel()->GetValue()) { ErrorMessage.Send("Label of selected region is locked. Tool operation has no effect."); return; } this->PrepareFilling(workingSlice, click); //as fill region tools should always allow to manipulate active label //(that is what the user expects/knows when using tools so far: //the active label can always be changed even if locked) //we realize that by cloning the relevant label set and changing the lock state //this fillLabelSet is used for the transfer. - auto fillLabelSet = labelSetImage->GetActiveLabelSet()->Clone(); - auto activeLabelClone = fillLabelSet->GetLabel(labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue()); + auto activeLabelClone = labelSetImage->GetActiveLabel()->Clone(); if (nullptr != activeLabelClone) { activeLabelClone->SetLocked(false); } - TransferLabelContentAtTimeStep(fillImage, workingSlice, fillLabelSet, 0, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, false, { {1, m_FillLabelValue} }, m_MergeStyle); + TransferLabelContentAtTimeStep(fillImage, workingSlice, { activeLabelClone }, 0, LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, false, { {1, m_FillLabelValue} }, m_MergeStyle); this->WriteBackSegmentationResult(positionEvent, workingSlice); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } mitk::Image::Pointer mitk::FillRegionBaseTool::GenerateFillImage(const Image* workingSlice, Point3D seedPoint, mitk::Label::PixelType& seedLabelValue) const { itk::Index<2> seedIndex; workingSlice->GetGeometry()->WorldToIndex(seedPoint, seedIndex); Image::Pointer fillImage; AccessFixedDimensionByItk_n(workingSlice, DoITKRegionGrowing, 2, (fillImage, seedIndex, seedLabelValue)); return fillImage; } diff --git a/Modules/Segmentation/Interactions/mitkFillRegionTool.cpp b/Modules/Segmentation/Interactions/mitkFillRegionTool.cpp index 296e8f613c..bde58bce1a 100644 --- a/Modules/Segmentation/Interactions/mitkFillRegionTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFillRegionTool.cpp @@ -1,59 +1,59 @@ /*============================================================================ 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 "mitkFillRegionTool.h" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, FillRegionTool, "Fill tool"); } const char **mitk::FillRegionTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::FillRegionTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Fill.svg"); return resource; } us::ModuleResource mitk::FillRegionTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Fill_Cursor.svg"); return resource; } const char *mitk::FillRegionTool::GetName() const { return "Fill"; } void mitk::FillRegionTool::PrepareFilling(const Image* /*workingSlice*/, Point3D /*seedPoint*/) { auto labelSetImage = dynamic_cast(this->GetWorkingData()); if (nullptr == labelSetImage) mitkThrow() << "Invalid state of FillRegionTool. Working image is not of correct type."; - m_FillLabelValue = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue(); + m_FillLabelValue = labelSetImage->GetActiveLabel()->GetValue(); m_MergeStyle = MultiLabelSegmentation::MergeStyle::Merge; }; diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index 688412dd2e..d07c107ce8 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp @@ -1,114 +1,110 @@ /*============================================================================ 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. ============================================================================*/ // MITK #include "mitkOtsuTool3D.h" #include "mitkOtsuSegmentationFilter.h" #include #include // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } mitk::OtsuTool3D::OtsuTool3D() : SegWithPreviewTool() { this->ResetsToEmptyPreviewOn(); this->UseSpecialPreviewColorOff(); } void mitk::OtsuTool3D::Activated() { Superclass::Activated(); m_NumberOfBins = 128; m_NumberOfRegions = 2; m_UseValley = false; this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } const char **mitk::OtsuTool3D::GetXPM() const { return nullptr; } us::ModuleResource mitk::OtsuTool3D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Otsu.svg"); return resource; } const char* mitk::OtsuTool3D::GetName() const { return "Otsu"; } void mitk::OtsuTool3D::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType timeStep) { int numberOfThresholds = m_NumberOfRegions - 1; mitk::OtsuSegmentationFilter::Pointer otsuFilter = mitk::OtsuSegmentationFilter::New(); otsuFilter->SetNumberOfThresholds(numberOfThresholds); otsuFilter->SetValleyEmphasis(m_UseValley); otsuFilter->SetNumberOfBins(m_NumberOfBins); otsuFilter->SetInput(inputAtTimeStep); otsuFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); try { otsuFilter->Update(); } catch (...) { mitkThrow() << "itkOtsuFilter error (image dimension must be in {2, 3} and image must not be RGB)"; } auto otsuResultImage = otsuFilter->GetOutput(); mitk::ImageReadAccessor newMitkImgAcc(otsuResultImage); previewImage->SetVolume(newMitkImgAcc.GetData(), timeStep); } void mitk::OtsuTool3D::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); - auto labelset = preview->GetLabelSet(preview->GetActiveLayer()); - for (LabelSetImage::GroupIndexType i = 0; iGetNumberOfLayers(); ++i) - { - preview->GetLabelSet(i)->RemoveAllLabels(); - } + preview->RemoveLabels(preview->GetAllLabelValues()); for (unsigned int i = 0; i < m_NumberOfRegions; ++i) { - auto label = LabelSetImageHelper::CreateNewLabel(preview, "Otsu"); + auto label = LabelSetImageHelper::CreateNewLabel(preview, "Otsu " + std::to_string(i)); label->SetValue(i + 1); - labelset->AddLabel(label, false); + preview->AddLabel(label, preview->GetActiveLayer(), false, false); } } unsigned int mitk::OtsuTool3D::GetMaxNumberOfBins() const { const auto min = this->GetReferenceData()->GetStatistics()->GetScalarValueMin(); const auto max = this->GetReferenceData()->GetStatistics()->GetScalarValueMaxNoRecompute(); return static_cast(max - min) + 1; } diff --git a/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp b/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp index 288818e7d7..1dd6ab7418 100644 --- a/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp @@ -1,612 +1,616 @@ /*============================================================================ 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 "mitkPaintbrushTool.h" #include "mitkAbstractTransformGeometry.h" #include "mitkBaseRenderer.h" #include "mitkToolManager.h" #include "mitkContourModelUtils.h" #include "mitkLevelWindowProperty.h" #include "mitkImageWriteAccessor.h" int mitk::PaintbrushTool::m_Size = 1; mitk::PaintbrushTool::PaintbrushTool(bool startWithFillMode) : FeedbackContourTool("PressMoveReleaseWithCTRLInversionAllMouseMoves"), m_FillMode(startWithFillMode), m_LastContourSize(0) // other than initial mitk::PaintbrushTool::m_Size (around l. 28) { m_MasterContour = ContourModel::New(); m_MasterContour->Initialize(); m_CurrentPlane = nullptr; } mitk::PaintbrushTool::~PaintbrushTool() { } void mitk::PaintbrushTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnPrimaryButtonPressedMoved); CONNECT_FUNCTION("MouseMove", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); CONNECT_FUNCTION("InvertLogic", OnInvertLogic); } void mitk::PaintbrushTool::Activated() { Superclass::Activated(); SizeChanged.Send(m_Size); this->GetToolManager()->WorkingDataChanged += mitk::MessageDelegate(this, &mitk::PaintbrushTool::OnToolManagerWorkingDataModified); m_PaintingNode = DataNode::New(); m_PaintingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, m_InternalFillValue))); m_PaintingNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_PaintingNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_PaintingNode->SetProperty("name", mitk::StringProperty::New("Paintbrush_Node")); m_PaintingNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PaintingNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_PaintingNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); auto allRenderWindows = BaseRenderer::GetAll3DRenderWindows(); for (auto mapit = allRenderWindows.begin(); mapit != allRenderWindows.end(); ++mapit) { m_PaintingNode->SetVisibility(false, mapit->second); } this->UpdateFeedbackColor(); FeedbackContourTool::SetFeedbackContourVisible(true); this->GetToolManager()->GetDataStorage()->Add(m_PaintingNode); } void mitk::PaintbrushTool::Deactivated() { FeedbackContourTool::SetFeedbackContourVisible(false); if (this->GetToolManager()->GetDataStorage()->Exists(m_PaintingNode)) this->GetToolManager()->GetDataStorage()->Remove(m_PaintingNode); m_WorkingSlice = nullptr; m_PaintingSlice = nullptr; m_CurrentPlane = nullptr; m_PaintingNode = nullptr; this->GetToolManager()->WorkingDataChanged -= mitk::MessageDelegate(this, &mitk::PaintbrushTool::OnToolManagerWorkingDataModified); Superclass::Deactivated(); } void mitk::PaintbrushTool::SetSize(int value) { m_Size = value; } mitk::Point2D mitk::PaintbrushTool::upperLeft(mitk::Point2D p) { p[0] -= 0.5; p[1] += 0.5; return p; } void mitk::PaintbrushTool::UpdateContour(const InteractionPositionEvent *positionEvent) { // MITK_INFO<<"Update..."; // examine stateEvent and create a contour that matches the pixel mask that we are going to draw // mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); // const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return; // Get Spacing of current Slice // mitk::Vector3D vSpacing = m_WorkingSlice->GetSlicedGeometry()->GetPlaneGeometry(0)->GetSpacing(); // // Draw a contour in Square according to selected brush size // int radius = (m_Size) / 2; float fradius = static_cast(m_Size) / 2.0f; ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New(); // estimate center point of the brush ( relative to the pixel the mouse points on ) // -- left upper corner for even sizes, // -- midpoint for uneven sizes mitk::Point2D centerCorrection; centerCorrection.Fill(0); // even --> correction of [+0.5, +0.5] bool evenSize = ((m_Size % 2) == 0); if (evenSize) { centerCorrection[0] += 0.5; centerCorrection[1] += 0.5; } // we will compute the control points for the upper left quarter part of a circle contour std::vector quarterCycleUpperRight; std::vector quarterCycleLowerRight; std::vector quarterCycleLowerLeft; std::vector quarterCycleUpperLeft; mitk::Point2D curPoint; bool curPointIsInside = true; curPoint[0] = 0; curPoint[1] = radius; quarterCycleUpperRight.push_back(upperLeft(curPoint)); // to estimate if a pixel is inside the circle, we need to compare against the 'outer radius' // i.e. the distance from the midpoint [0,0] to the border of the pixel [0,radius] // const float outer_radius = static_cast(radius) + 0.5; while (curPoint[1] > 0) { // Move right until pixel is outside circle float curPointX_squared = 0.0f; float curPointY_squared = (curPoint[1] - centerCorrection[1]) * (curPoint[1] - centerCorrection[1]); while (curPointIsInside) { // increment posX and chec curPoint[0]++; curPointX_squared = (curPoint[0] - centerCorrection[0]) * (curPoint[0] - centerCorrection[0]); const float len = sqrt(curPointX_squared + curPointY_squared); if (len > fradius) { // found first Pixel in this horizontal line, that is outside the circle curPointIsInside = false; } } quarterCycleUpperRight.push_back(upperLeft(curPoint)); // Move down until pixel is inside circle while (!curPointIsInside) { // increment posX and chec curPoint[1]--; curPointY_squared = (curPoint[1] - centerCorrection[1]) * (curPoint[1] - centerCorrection[1]); const float len = sqrt(curPointX_squared + curPointY_squared); if (len <= fradius) { // found first Pixel in this horizontal line, that is outside the circle curPointIsInside = true; quarterCycleUpperRight.push_back(upperLeft(curPoint)); } // Quarter cycle is full, when curPoint y position is 0 if (curPoint[1] <= 0) break; } } // QuarterCycle is full! Now copy quarter cycle to other quarters. if (!evenSize) { std::vector::const_iterator it = quarterCycleUpperRight.begin(); while (it != quarterCycleUpperRight.end()) { mitk::Point2D p; p = *it; // the contour points in the lower right corner have same position but with negative y values p[1] *= -1; quarterCycleLowerRight.push_back(p); // the contour points in the lower left corner have same position // but with both x,y negative p[0] *= -1; quarterCycleLowerLeft.push_back(p); // the contour points in the upper left corner have same position // but with x negative p[1] *= -1; quarterCycleUpperLeft.push_back(p); it++; } } else { std::vector::const_iterator it = quarterCycleUpperRight.begin(); while (it != quarterCycleUpperRight.end()) { mitk::Point2D p, q; p = *it; q = p; // the contour points in the lower right corner have same position but with negative y values q[1] *= -1; // correct for moved offset if size even = the midpoint is not the midpoint of the current pixel // but its upper rigt corner q[1] += 1; quarterCycleLowerRight.push_back(q); q = p; // the contour points in the lower left corner have same position // but with both x,y negative q[1] = -1.0f * q[1] + 1; q[0] = -1.0f * q[0] + 1; quarterCycleLowerLeft.push_back(q); // the contour points in the upper left corner have same position // but with x negative q = p; q[0] *= -1; q[0] += 1; quarterCycleUpperLeft.push_back(q); it++; } } // fill contour with poins in right ordering, starting with the upperRight block mitk::Point3D tempPoint; for (unsigned int i = 0; i < quarterCycleUpperRight.size(); i++) { tempPoint[0] = quarterCycleUpperRight[i][0]; tempPoint[1] = quarterCycleUpperRight[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } // the lower right has to be parsed in reverse order for (int i = quarterCycleLowerRight.size() - 1; i >= 0; i--) { tempPoint[0] = quarterCycleLowerRight[i][0]; tempPoint[1] = quarterCycleLowerRight[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } for (unsigned int i = 0; i < quarterCycleLowerLeft.size(); i++) { tempPoint[0] = quarterCycleLowerLeft[i][0]; tempPoint[1] = quarterCycleLowerLeft[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } // the upper left also has to be parsed in reverse order for (int i = quarterCycleUpperLeft.size() - 1; i >= 0; i--) { tempPoint[0] = quarterCycleUpperLeft[i][0]; tempPoint[1] = quarterCycleUpperLeft[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } m_MasterContour = contourInImageIndexCoordinates; } void mitk::PaintbrushTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { if (m_WorkingSlice.IsNull()) return; auto* positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; this->ResetWorkingSlice(positionEvent); m_WorkingSlice->GetGeometry()->WorldToIndex(positionEvent->GetPositionInWorld(), m_LastPosition); this->m_PaintingNode->SetVisibility(true); m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_MasterContour->SetClosed(true); this->MouseMoved(interactionEvent, true); } void mitk::PaintbrushTool::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { MouseMoved(interactionEvent, false); } void mitk::PaintbrushTool::OnPrimaryButtonPressedMoved(StateMachineAction *, InteractionEvent *interactionEvent) { MouseMoved(interactionEvent, true); } /** Insert the point to the feedback contour,finish to build the contour and at the same time the painting function */ void mitk::PaintbrushTool::MouseMoved(mitk::InteractionEvent *interactionEvent, bool leftMouseButtonPressed) { auto *positionEvent = dynamic_cast(interactionEvent); bool newSlice = CheckIfCurrentSliceHasChanged(positionEvent); if (newSlice) { this->ResetWorkingSlice(positionEvent); } if (m_LastContourSize != m_Size) { UpdateContour(positionEvent); m_LastContourSize = m_Size; } Point3D worldCoordinates = positionEvent->GetPositionInWorld(); Point3D indexCoordinates; m_WorkingSlice->GetGeometry()->WorldToIndex(worldCoordinates, indexCoordinates); // round to nearest voxel center (abort if this hasn't changed) if (m_Size % 2 == 0) // even { indexCoordinates[0] = std::round(indexCoordinates[0]); indexCoordinates[1] = std::round(indexCoordinates[1]); } else // odd { indexCoordinates[0] = std::round(indexCoordinates[0]); indexCoordinates[1] = std::round(indexCoordinates[1]); } static Point3D lastPos; // uninitialized: if somebody finds out how this can be initialized in a one-liner, tell me if (fabs(indexCoordinates[0] - lastPos[0]) > mitk::eps || fabs(indexCoordinates[1] - lastPos[1]) > mitk::eps || fabs(indexCoordinates[2] - lastPos[2]) > mitk::eps || leftMouseButtonPressed) { lastPos = indexCoordinates; } else { return; } auto contour = ContourModel::New(); contour->SetClosed(true); auto it = m_MasterContour->Begin(); auto end = m_MasterContour->End(); while (it != end) { auto point = (*it)->Coordinates; point[0] += indexCoordinates[0]; point[1] += indexCoordinates[1]; contour->AddVertex(point); ++it; } if (leftMouseButtonPressed) { ContourModelUtils::FillContourInSlice2(contour, m_PaintingSlice, m_InternalFillValue); const double dist = indexCoordinates.EuclideanDistanceTo(m_LastPosition); const double radius = static_cast(m_Size) / 2.0; // if points are >= radius away draw rectangle to fill empty holes // in between the 2 points if (dist > radius) { const mitk::Point3D ¤tPos = indexCoordinates; mitk::Point3D direction; mitk::Point3D vertex; mitk::Point3D normal; direction[0] = indexCoordinates[0] - m_LastPosition[0]; direction[1] = indexCoordinates[1] - m_LastPosition[1]; direction[2] = indexCoordinates[2] - m_LastPosition[2]; direction[0] = direction.GetVnlVector().normalize()[0]; direction[1] = direction.GetVnlVector().normalize()[1]; direction[2] = direction.GetVnlVector().normalize()[2]; // 90 degrees rotation of direction normal[0] = -1.0 * direction[1]; normal[1] = direction[0]; auto gapContour = ContourModel::New(); // upper left corner vertex[0] = m_LastPosition[0] + (normal[0] * radius); vertex[1] = m_LastPosition[1] + (normal[1] * radius); gapContour->AddVertex(vertex); // upper right corner vertex[0] = currentPos[0] + (normal[0] * radius); vertex[1] = currentPos[1] + (normal[1] * radius); gapContour->AddVertex(vertex); // lower right corner vertex[0] = currentPos[0] - (normal[0] * radius); vertex[1] = currentPos[1] - (normal[1] * radius); gapContour->AddVertex(vertex); // lower left corner vertex[0] = m_LastPosition[0] - (normal[0] * radius); vertex[1] = m_LastPosition[1] - (normal[1] * radius); gapContour->AddVertex(vertex); ContourModelUtils::FillContourInSlice2(gapContour, m_PaintingSlice, m_InternalFillValue); } } else { // switched from different renderwindow // no activate hover highlighting. Otherwise undo / redo wont work this->m_PaintingNode->SetVisibility(false); } m_LastPosition = indexCoordinates; // visualize contour ContourModel::Pointer tmp = FeedbackContourTool::BackProjectContourFrom2DSlice(m_WorkingSlice->GetGeometry(), contour); this->UpdateCurrentFeedbackContour(tmp); if (newSlice) { RenderingManager::GetInstance()->RequestUpdateAll(); } else { assert(positionEvent->GetSender()->GetRenderWindow()); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } void mitk::PaintbrushTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { // When mouse is released write segmentationresult back into image auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; DataNode* workingNode(this->GetToolManager()->GetWorkingData(0)); auto workingImage = dynamic_cast(workingNode->GetData()); Label::PixelType activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); if (!m_FillMode) { - activePixelValue = LabelSetImage::UnlabeledValue; + activePixelValue = LabelSetImage::UNLABELED_VALUE; } //as paintbrush tools should always allow to manipulate active label //(that is what the user expects/knows when using tools so far: //the active label can always be changed even if locked) - //we realize that by cloning the relevant label set and changing the lock state + //we realize that by cloning the relevant label and changing the lock state //this fillLabelSet is used for the transfer. - auto fillLabelSet = workingImage->GetActiveLabelSet()->Clone(); - auto activeLabelClone = fillLabelSet->GetLabel(workingImage->GetActiveLabel(workingImage->GetActiveLayer())->GetValue()); + auto destinationLabels = workingImage->GetConstLabelsByValue(workingImage->GetLabelValuesByGroup(workingImage->GetActiveLayer())); + auto activeLabelClone = workingImage->GetActiveLabel()->Clone(); if (nullptr != activeLabelClone) { activeLabelClone->SetLocked(false); + auto activeIter = std::find(destinationLabels.begin(), destinationLabels.end(), workingImage->GetActiveLabel()); + if (activeIter == destinationLabels.end()) mitkThrow() << "Application is in an invalid state. Active label is not contained in the labelset, but its group was requested."; + *activeIter = activeLabelClone; } - TransferLabelContentAtTimeStep(m_PaintingSlice, m_WorkingSlice, fillLabelSet, 0, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, false, { {m_InternalFillValue, activePixelValue} }, mitk::MultiLabelSegmentation::MergeStyle::Merge); + + TransferLabelContentAtTimeStep(m_PaintingSlice, m_WorkingSlice, destinationLabels, 0, LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, false, { {m_InternalFillValue, activePixelValue} }, mitk::MultiLabelSegmentation::MergeStyle::Merge); this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice->Clone()); // deactivate visibility of helper node m_PaintingNode->SetVisibility(false); m_PaintingNode->SetData(nullptr); m_PaintingSlice = nullptr; m_WorkingSlice = nullptr; RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PaintbrushTool::UpdateFeedbackColor() { mitk::Color currentColor; if (m_FillMode) { FeedbackContourTool::SetFeedbackContourColorDefault(); currentColor.Set(0.0, 1.0, 0.); } else { FeedbackContourTool::SetFeedbackContourColor(1.0, 0.0, 0.0); currentColor.Set(1.0, 0.0, 0.); } if (m_PaintingNode.IsNotNull()) { m_PaintingNode->SetProperty("color", mitk::ColorProperty::New(currentColor[0], currentColor[1], currentColor[2])); } } /** Called when the CTRL key is pressed. */ void mitk::PaintbrushTool::OnInvertLogic(StateMachineAction *, InteractionEvent *) { m_FillMode = !m_FillMode; UpdateFeedbackColor(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } bool mitk::PaintbrushTool::CheckIfCurrentSliceHasChanged(const InteractionPositionEvent *event) { const PlaneGeometry* planeGeometry((event->GetSender()->GetCurrentWorldPlaneGeometry())); const auto* abstractTransformGeometry( dynamic_cast(event->GetSender()->GetCurrentWorldPlaneGeometry())); if (nullptr == planeGeometry || nullptr != abstractTransformGeometry) { return false; } bool newPlane = false; if (m_CurrentPlane.IsNull() || m_WorkingSlice.IsNull() //or not the same slice || !mitk::MatrixEqualElementWise(planeGeometry->GetIndexToWorldTransform()->GetMatrix(), m_CurrentPlane->GetIndexToWorldTransform()->GetMatrix()) || !mitk::Equal(planeGeometry->GetIndexToWorldTransform()->GetOffset(), m_CurrentPlane->GetIndexToWorldTransform()->GetOffset())) { m_CurrentPlane = planeGeometry; newPlane = true; } return newPlane; } void mitk::PaintbrushTool::ResetWorkingSlice(const InteractionPositionEvent* event) { const PlaneGeometry* planeGeometry((event->GetSender()->GetCurrentWorldPlaneGeometry())); const auto* abstractTransformGeometry( dynamic_cast(event->GetSender()->GetCurrentWorldPlaneGeometry())); if (nullptr == planeGeometry || nullptr != abstractTransformGeometry) { return; } m_WorkingSlice = nullptr; m_PaintingSlice = nullptr; m_PaintingNode->SetData(nullptr); DataNode* workingNode = this->GetToolManager()->GetWorkingData(0); if (nullptr == workingNode) { return; } Image::Pointer image = dynamic_cast(workingNode->GetData()); if (nullptr == image) { return; } m_WorkingSlice = SegTool2D::GetAffectedImageSliceAs2DImage(event, image)->Clone(); m_PaintingSlice = Image::New(); m_PaintingSlice->Initialize(m_WorkingSlice); unsigned int byteSize = m_PaintingSlice->GetPixelType().GetSize(); for (unsigned int dim = 0; dim < m_PaintingSlice->GetDimension(); ++dim) { byteSize *= m_PaintingSlice->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(m_PaintingSlice.GetPointer(), m_PaintingSlice->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); m_PaintingNode->SetData(m_PaintingSlice); } void mitk::PaintbrushTool::OnToolManagerWorkingDataModified() { // Here we simply set the current working slice to null. The next time the mouse is moved // within a renderwindow a new slice will be extracted from the new working data m_WorkingSlice = nullptr; m_PaintingSlice = nullptr; } diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.cpp b/Modules/Segmentation/Interactions/mitkPickingTool.cpp index 6478b66ed6..f544ad8b7b 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPickingTool.cpp @@ -1,259 +1,259 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkPickingTool.h" #include "mitkProperties.h" #include "mitkToolManager.h" #include "mitkInteractionPositionEvent.h" // us #include #include #include #include #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageTimeSelector.h" #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, PickingTool, "PickingTool"); } mitk::PickingTool::PickingTool() : SegWithPreviewTool(false, "PressMoveReleaseAndPointSetting") { this->ResetsToEmptyPreviewOn(); } mitk::PickingTool::~PickingTool() { } const char **mitk::PickingTool::GetXPM() const { return nullptr; } const char *mitk::PickingTool::GetName() const { return "Picking"; } us::ModuleResource mitk::PickingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Picking.svg"); return resource; } void mitk::PickingTool::Activated() { Superclass::Activated(); m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); m_PointSetNode = mitk::DataNode::New(); m_PointSetNode->SetData(m_PointSet); m_PointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); m_PointSetNode->SetBoolProperty("helper object", true); m_PointSetNode->SetColor(0.0, 1.0, 0.0); m_PointSetNode->SetVisibility(true); this->GetDataStorage()->Add(m_PointSetNode, this->GetToolManager()->GetWorkingData(0)); } void mitk::PickingTool::Deactivated() { this->ClearSeeds(); // remove from data storage and disable interaction GetDataStorage()->Remove(m_PointSetNode); m_PointSetNode = nullptr; m_PointSet = nullptr; Superclass::Deactivated(); } void mitk::PickingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::PickingTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { m_PointSet->InsertPoint(m_PointSet->GetSize(), positionEvent->GetPositionInWorld()); this->UpdatePreview(); } } } void mitk::PickingTool::OnDelete(StateMachineAction*, InteractionEvent* /*interactionEvent*/) { if (!this->IsUpdating() && m_PointSet.IsNotNull()) { // delete last seed point if (this->m_PointSet->GetSize() > 0) { m_PointSet->RemovePointAtEnd(0); this->UpdatePreview(); } } } void mitk::PickingTool::ClearPicks() { this->ClearSeeds(); this->UpdatePreview(); } bool mitk::PickingTool::HasPicks() const { return this->m_PointSet.IsNotNull() && this->m_PointSet->GetSize()>0; } void mitk::PickingTool::ClearSeeds() { if (this->m_PointSet.IsNotNull()) { // renew pointset this->m_PointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_PointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); this->m_PointSetNode->SetData(this->m_PointSet); } } template void DoITKRegionGrowing(const itk::Image* oldSegImage, mitk::Image* segmentation, const mitk::PointSet* seedPoints, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry, const mitk::Label::PixelType outputValue, const mitk::Label::PixelType backgroundValue, bool& emptyTimeStep) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef typename InputImageType::IndexType IndexType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; using IndexMapType = std::map < mitk::Label::PixelType, std::vector >; IndexMapType indexMap; // convert world coordinates to image indices for (auto pos = seedPoints->Begin(); pos != seedPoints->End(); ++pos) { IndexType seedIndex; inputGeometry->WorldToIndex(pos->Value(), seedIndex); const auto selectedLabel = oldSegImage->GetPixel(seedIndex); if (selectedLabel != backgroundValue) { indexMap[selectedLabel].push_back(seedIndex); } } typename OutputImageType::Pointer itkResultImage; try { bool first = true; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); regionGrower->SetInput(oldSegImage); regionGrower->SetReplaceValue(outputValue); for (const auto& [label, indeces] : indexMap) { // perform region growing in desired segmented region regionGrower->ClearSeeds(); for (const auto& index : indeces) { regionGrower->AddSeed(index); } regionGrower->SetLower(label); regionGrower->SetUpper(label); regionGrower->Update(); if (first) { itkResultImage = regionGrower->GetOutput(); } else { typename itk::OrImageFilter::Pointer orFilter = itk::OrImageFilter::New(); orFilter->SetInput1(regionGrower->GetOutput()); orFilter->SetInput2(itkResultImage); orFilter->Update(); itkResultImage = orFilter->GetOutput(); } first = false; itkResultImage->DisconnectPipeline(); } } catch (const itk::ExceptionObject&) { return; // can't work } catch (...) { return; } if (itkResultImage.IsNotNull()) { segmentation->SetVolume((void*)(itkResultImage->GetPixelContainer()->GetBufferPointer()),timeStep); } emptyTimeStep = itkResultImage.IsNull(); } void mitk::PickingTool::DoUpdatePreview(const Image* /*inputAtTimeStep*/, const Image* oldSegAtTimeStep, LabelSetImage* previewImage, TimeStepType timeStep) { if (nullptr != oldSegAtTimeStep && nullptr != previewImage && m_PointSet.IsNotNull()) { bool emptyTimeStep = true; if (this->HasPicks()) { const auto activeValue = this->GetActiveLabelValueOfPreview(); this->SetSelectedLabels({activeValue}); - AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), activeValue, mitk::LabelSetImage::UnlabeledValue, emptyTimeStep)); + AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), activeValue, mitk::LabelSetImage::UNLABELED_VALUE, emptyTimeStep)); } if (emptyTimeStep) { this->ResetPreviewContentAtTimeStep(timeStep); } } } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index 3cab141cdc..d0063c907d 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,798 +1,795 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegTool2D.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkPlaneGeometry.h" #include +#include "mitkImageAccessByItk.h" // Include of the new ImageExtractor #include "mitkMorphologicalOperations.h" #include "mitkPlanarCircle.h" #include "usGetModuleContext.h" // Includes for 3DSurfaceInterpolation #include "mitkImageTimeSelector.h" #include "mitkImageToContourFilter.h" #include "mitkSurfaceInterpolationController.h" // includes for resling and overwriting #include #include #include #include #include "mitkOperationEvent.h" #include "mitkUndoController.h" #include #include "mitkAbstractTransformGeometry.h" #include "mitkLabelSetImage.h" #include "mitkContourModelUtils.h" // #include #include #include #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) bool mitk::SegTool2D::m_SurfaceInterpolationEnabled = true; mitk::SegTool2D::SliceInformation::SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep) : slice(aSlice), plane(aPlane), timestep(aTimestep) { } mitk::SegTool2D::SegTool2D(const char *type, const us::Module *interactorModule) : Tool(type, interactorModule), m_Contourmarkername("Position") { Tool::m_EventConfig = "DisplayConfigBlockLMB.xml"; } mitk::SegTool2D::~SegTool2D() { } bool mitk::SegTool2D::FilterEvents(InteractionEvent *interactionEvent, DataNode *) { const auto *positionEvent = dynamic_cast(interactionEvent); bool isValidEvent = (positionEvent && // Only events of type mitk::InteractionPositionEvent interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard2D // Only events from the 2D renderwindows ); return isValidEvent; } bool mitk::SegTool2D::DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice) { assert(image); assert(plane); // compare normal of plane to the three axis vectors of the image Vector3D normal = plane->GetNormal(); Vector3D imageNormal0 = image->GetSlicedGeometry()->GetAxisVector(0); Vector3D imageNormal1 = image->GetSlicedGeometry()->GetAxisVector(1); Vector3D imageNormal2 = image->GetSlicedGeometry()->GetAxisVector(2); normal.Normalize(); imageNormal0.Normalize(); imageNormal1.Normalize(); imageNormal2.Normalize(); imageNormal0.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal0.GetVnlVector())); imageNormal1.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal1.GetVnlVector())); imageNormal2.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal2.GetVnlVector())); double eps(0.00001); // axial if (imageNormal2.GetNorm() <= eps) { affectedDimension = 2; } // sagittal else if (imageNormal1.GetNorm() <= eps) { affectedDimension = 1; } // coronal else if (imageNormal0.GetNorm() <= eps) { affectedDimension = 0; } else { affectedDimension = -1; // no idea return false; } // determine slice number in image BaseGeometry *imageGeometry = image->GetGeometry(0); Point3D testPoint = imageGeometry->GetCenter(); Point3D projectedPoint; plane->Project(testPoint, projectedPoint); Point3D indexPoint; imageGeometry->WorldToIndex(projectedPoint, indexPoint); affectedSlice = ROUND(indexPoint[affectedDimension]); MITK_DEBUG << "indexPoint " << indexPoint << " affectedDimension " << affectedDimension << " affectedSlice " << affectedSlice; // check if this index is still within the image if (affectedSlice < 0 || affectedSlice >= static_cast(image->GetDimension(affectedDimension))) return false; return true; } -void mitk::SegTool2D::UpdateSurfaceInterpolation(const Image *slice, - const Image *workingImage, +void mitk::SegTool2D::UpdateAllSurfaceInterpolations(const LabelSetImage *workingImage, + TimeStepType timeStep, const PlaneGeometry *plane, bool detectIntersection) { - std::vector slices = { SliceInformation(slice, plane, 0)}; - Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection, 0, 0); + if (nullptr == workingImage) mitkThrow() << "Cannot update surface interpolation. Invalid working image passed."; + if (nullptr == plane) mitkThrow() << "Cannot update surface interpolation. Invalid plane passed."; + + auto affectedLabels = mitk::SurfaceInterpolationController::GetInstance()->GetAffectedLabels(workingImage, timeStep, plane); + for (auto affectedLabel : affectedLabels) + { + auto groupID = workingImage->GetGroupIndexOfLabel(affectedLabel); + auto slice = GetAffectedImageSliceAs2DImage(plane, workingImage->GetGroupImage(groupID), timeStep); + std::vector slices = { SliceInformation(slice, plane, timeStep) }; + Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection, affectedLabel, true); + } + + if(!affectedLabels.empty()) mitk::SurfaceInterpolationController::GetInstance()->Modified(); } -void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo) +void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo, LabelSetImage::LabelValueType labelValue) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; - contourInfo.ContourNormal = sliceInfo.plane->GetNormal(); - contourInfo.ContourPoint = sliceInfo.plane->GetOrigin(); - mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); + contourInfo.LabelValue = labelValue; + contourInfo.TimeStep = sliceInfo.timestep; + contourInfo.Plane = sliceInfo.plane; + + mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo, true); +} + +template +void ClearBufferProcessing(ImageType* itkImage) +{ + itkImage->FillBuffer(0); } void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection, - unsigned int activeLayerID, - mitk::Label::PixelType activeLabelValue) + mitk::Label::PixelType activeLabelValue, bool silent) { if (!m_SurfaceInterpolationEnabled) return; //Remark: the ImageTimeSelector is just needed to extract a timestep/channel of //the image in order to get the image dimension (time dimension and channel dimension //stripped away). Therfore it is OK to always use time step 0 and channel 0 mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(workingImage); timeSelector->SetTimeNr(0); timeSelector->SetChannelNr(0); timeSelector->Update(); const auto dimRefImg = timeSelector->GetOutput()->GetDimension(); if (dimRefImg != 3) return; std::vector contourList; contourList.reserve(sliceInfos.size()); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); std::vector relevantSlices = sliceInfos; if (detectIntersection) { relevantSlices.clear(); for (const auto& sliceInfo : sliceInfos) { // Test whether there is something to extract or whether the slice just contains intersections of others - mitk::Image::Pointer slice2 = sliceInfo.slice->Clone(); - mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); + //Remark we cannot just errode the clone of sliceInfo.slice, because Erode currently only + //works on pixel value 1. But we need to erode active label. Therefore we use TransferLabelContent + //as workarround. + //If MorphologicalOperations::Erode is supports user defined pixel values, the workarround + //can be removed. + //Workarround starts + mitk::Image::Pointer slice2 = Image::New(); + slice2->Initialize(sliceInfo.slice); + AccessByItk(slice2, ClearBufferProcessing); + LabelSetImage::LabelValueType erodeValue = 1; + auto label = Label::New(erodeValue, ""); + TransferLabelContent(sliceInfo.slice, slice2, { label }, LabelSetImage::UNLABELED_VALUE, LabelSetImage::UNLABELED_VALUE, false, { {activeLabelValue, erodeValue} }); + //Workarround ends + + mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); contourExtractor->SetInput(slice2); + contourExtractor->SetContourValue(erodeValue); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { - Self::RemoveContourFromInterpolator(sliceInfo); + Self::RemoveContourFromInterpolator(sliceInfo, activeLabelValue); } else { relevantSlices.push_back(sliceInfo); } } } - if (relevantSlices.empty()) - return; - - std::vector contourPlanes; + SurfaceInterpolationController::CPIVector cpis; for (const auto& sliceInfo : relevantSlices) { contourExtractor->SetInput(sliceInfo.slice); contourExtractor->SetContourValue(activeLabelValue); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { - Self::RemoveContourFromInterpolator(sliceInfo); + Self::RemoveContourFromInterpolator(sliceInfo, activeLabelValue); } else { - vtkSmartPointer intArray = vtkSmartPointer::New(); - intArray->InsertNextValue(activeLabelValue); - intArray->InsertNextValue(activeLayerID); - contour->GetVtkPolyData()->GetFieldData()->AddArray(intArray); - contour->DisconnectPipeline(); - contourList.push_back(contour); - contourPlanes.push_back(sliceInfo.plane); + cpis.emplace_back(contour, sliceInfo.plane->Clone(), activeLabelValue, sliceInfo.timestep); } } - mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList, contourPlanes); + //this call is relevant even if cpis is empty to ensure SurfaceInterpolationController::Modified is triggered if silent==false; + mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(cpis, false, silent); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const InteractionPositionEvent *positionEvent, const Image *image, unsigned int component /*= 0*/) { if (!positionEvent) { return nullptr; } assert(positionEvent->GetSender()); // sure, right? const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); // get the timestep of the visible part (time-wise) of the image return GetAffectedImageSliceAs2DImage(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry(), image, timeStep, component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; return SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint), component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const PlaneGeometry *planeGeometry, const Image *image, TimeStepType timeStep, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); // use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); // additionally extract the given component // default is 0; the extractor checks for multi-component images extractor->SetComponent(component); extractor->Modified(); extractor->Update(); Image::Pointer slice = extractor->GetOutput(); return slice; } mitk::Image::Pointer mitk::SegTool2D::GetAffectedWorkingSlice(const InteractionPositionEvent *positionEvent) const { const auto workingNode = this->GetWorkingDataNode(); if (!workingNode) { return nullptr; } const auto *workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { return nullptr; } return GetAffectedImageSliceAs2DImage(positionEvent, workingImage); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const InteractionPositionEvent *positionEvent) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto *referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage); } } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto* referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep); } } void mitk::SegTool2D::Activated() { Superclass::Activated(); this->GetToolManager()->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); m_LastTimePointTriggered = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); } void mitk::SegTool2D::Deactivated() { this->GetToolManager()->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); Superclass::Deactivated(); } void mitk::SegTool2D::OnTimePointChangedInternal() { if (m_IsTimePointChangeAware && nullptr != this->GetWorkingDataNode()) { const TimePointType timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (timePoint != m_LastTimePointTriggered) { m_LastTimePointTriggered = timePoint; this->OnTimePointChanged(); } } } void mitk::SegTool2D::OnTimePointChanged() { //default implementation does nothing } mitk::DataNode* mitk::SegTool2D::GetWorkingDataNode() const { if (nullptr != this->GetToolManager()) { return this->GetToolManager()->GetWorkingData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetWorkingData() const { auto node = this->GetWorkingDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } mitk::DataNode* mitk::SegTool2D::GetReferenceDataNode() const { if (nullptr != this->GetToolManager()) { return this->GetToolManager()->GetReferenceData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetReferenceData() const { auto node = this->GetReferenceDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } void mitk::SegTool2D::WriteBackSegmentationResult(const InteractionPositionEvent *positionEvent, const Image * segmentationResult) { if (!positionEvent) return; const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); const auto *abstractTransformGeometry( dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (planeGeometry && segmentationResult && !abstractTransformGeometry) { const auto workingNode = this->GetWorkingDataNode(); auto *image = dynamic_cast(workingNode->GetData()); const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); this->WriteBackSegmentationResult(planeGeometry, segmentationResult, timeStep); } } void mitk::SegTool2D::WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); Self::WriteBackSegmentationResults(workingNode, { sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image * segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; if(m_LastEventSender == nullptr) { return; } unsigned int currentSlicePosition = m_LastEventSender->GetSliceNavigationController()->GetStepper()->GetPos(); SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); sliceInfo.slicePosition = currentSlicePosition; WriteBackSegmentationResults({ sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResults(const std::vector &sliceList, bool writeSliceToVolume) { if (sliceList.empty()) { return; } if (nullptr == m_LastEventSender) { MITK_WARN << "Cannot write tool results. Tool seems to be in an invalid state, as no interaction event was recieved but is expected."; return; } const auto workingNode = this->GetWorkingDataNode(); // the first geometry is needed otherwise restoring the position is not working const auto* plane3 = dynamic_cast(dynamic_cast( m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) ->GetPlaneGeometry(0)); const unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetStepper()->GetPos(); mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); /* A cleaner solution would be to add a contour marker for each slice info. It currently does not work as the contour markers expect that the plane is always the plane of slice 0. Had not the time to do it properly no. Should be solved by T28146*/ this->AddContourmarker(plane3, slicePosition); } void mitk::SegTool2D::WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume) { if (sliceList.empty()) { return; } if (nullptr == workingNode) { mitkThrow() << "Cannot write slice to working node. Working node is invalid."; } auto image = dynamic_cast(workingNode->GetData()); mitk::Label::PixelType activeLabelValue = 0; - unsigned int activeLayerID = 0; try{ auto labelSetImage = dynamic_cast(workingNode->GetData()); - activeLayerID = labelSetImage->GetActiveLayer(); - activeLabelValue = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); + activeLabelValue = labelSetImage->GetActiveLabel()->GetValue(); } catch(...) { mitkThrow() << "Working node does not contain labelSetImage."; } if (nullptr == image) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } for (const auto& sliceInfo : sliceList) { if (writeSliceToVolume && nullptr != sliceInfo.plane && sliceInfo.slice.IsNotNull()) { SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } - SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false, activeLayerID, activeLabelValue); + SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false, activeLabelValue); // also mark its node as modified (T27308). Can be removed if T27307 // is properly solved if (workingNode != nullptr) workingNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo) { SliceInformation sliceInfo(slice, planeGeometry, timeStep); WriteSliceToVolume(workingImage, sliceInfo, allowUndo); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo) { if (nullptr == workingImage) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } DiffSliceOperation* undoOperation = nullptr; if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // Create undo operation by caching the not yet modified slices mitk::Image::Pointer originalSlice = GetAffectedImageSliceAs2DImage(sliceInfo.plane, workingImage, sliceInfo.timestep); undoOperation = new DiffSliceOperation(workingImage, originalSlice, dynamic_cast(originalSlice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); /*============= END undo/redo feature block ========================*/ } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set the slice as 'input' // casting const away is needed and OK as long the OverwriteMode of // mitkVTKImageOverwrite is true. // Reason: because then the input slice is not touched but // used to overwrite the input of the ExtractSliceFilter. auto noneConstSlice = const_cast(sliceInfo.slice.GetPointer()); reslice->SetInputSlice(noneConstSlice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(workingImage); extractor->SetTimeStep(sliceInfo.timestep); extractor->SetWorldGeometry(sliceInfo.plane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(workingImage->GetGeometry(sliceInfo.timestep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so workingImage->Modified(); workingImage->GetVtkImageData()->Modified(); if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // specify the redo operation with the edited slice auto* doOperation = new DiffSliceOperation(workingImage, extractor->GetOutput(), dynamic_cast(sliceInfo.slice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); // create an operation event for the undo stack OperationEvent* undoStackItem = new OperationEvent(DiffSliceOperationApplier::GetInstance(), doOperation, undoOperation, "Segmentation"); // add it to the undo controller UndoStackItem::IncCurrObjectEventId(); UndoStackItem::IncCurrGroupEventId(); UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); /*============= END undo/redo feature block ========================*/ } } void mitk::SegTool2D::SetShowMarkerNodes(bool status) { m_ShowMarkerNodes = status; } void mitk::SegTool2D::SetEnable3DInterpolation(bool enabled) { m_SurfaceInterpolationEnabled = enabled; } int mitk::SegTool2D::AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex) { if (planeGeometry == nullptr) return -1; us::ServiceReference serviceRef = us::GetModuleContext()->GetServiceReference(); PlanePositionManagerService *service = us::GetModuleContext()->GetService(serviceRef); unsigned int size = service->GetNumberOfPlanePositions(); unsigned int id = service->AddNewPlanePosition(planeGeometry, sliceIndex); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); contourMarker->SetPlaneGeometry(planeGeometry->Clone()); contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); contourMarker->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); std::stringstream markerStream; auto workingNode = this->GetWorkingDataNode(); markerStream << m_Contourmarkername; markerStream << " "; markerStream << id + 1; DataNode::Pointer rotatedContourNode = DataNode::New(); rotatedContourNode->SetData(contourMarker); rotatedContourNode->SetProperty("name", StringProperty::New(markerStream.str())); rotatedContourNode->SetProperty("isContourMarker", BoolProperty::New(true)); rotatedContourNode->SetBoolProperty("PlanarFigureInitializedWindow", true, m_LastEventSender); rotatedContourNode->SetProperty("includeInBoundingBox", BoolProperty::New(false)); rotatedContourNode->SetProperty("helper object", mitk::BoolProperty::New(!m_ShowMarkerNodes)); rotatedContourNode->SetProperty("planarfigure.drawcontrolpoints", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawname", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawoutline", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawshadow", BoolProperty::New(false)); if (planeGeometry) { if (id == size) { this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } else { mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer markers = this->GetToolManager()->GetDataStorage()->GetDerivations(workingNode, isMarker); for (auto iter = markers->begin(); iter != markers->end(); ++iter) { std::string nodeName = (*iter)->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int markerId = atof(nodeName.substr(t + 1).c_str()) - 1; if (id == markerId) { return id; } } this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } } return id; } void mitk::SegTool2D::InteractiveSegmentationBugMessage(const std::string &message) const { MITK_ERROR << "********************************************************************************" << std::endl << " " << message << std::endl << "********************************************************************************" << std::endl << " " << std::endl << " If your image is rotated or the 2D views don't really contain the patient image, try to press the " "button next to the image selection. " << std::endl << " " << std::endl << " Please file a BUG REPORT: " << std::endl << " https://phabricator.mitk.org/" << std::endl << " Contain the following information:" << std::endl << " - What image were you working on?" << std::endl << " - Which region of the image?" << std::endl << " - Which tool did you use?" << std::endl << " - What did you do?" << std::endl << " - What happened (not)? What did you expect?" << std::endl; } -void mitk::SegTool2D::WritePreviewOnWorkingImage( - Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue) -{ - if (nullptr == targetSlice) - { - mitkThrow() << "Cannot write preview on working image. Target slice does not point to a valid instance."; - } - - if (nullptr == sourceSlice) - { - mitkThrow() << "Cannot write preview on working image. Source slice does not point to a valid instance."; - } - - if (nullptr == workingImage) - { - mitkThrow() << "Cannot write preview on working image. Working image does not point to a valid instance."; - } - - auto constVtkSource = sourceSlice->GetVtkImageData(); - /*Need to const cast because Vtk interface does not support const correctly. - (or I am not experienced enough to use it correctly)*/ - auto nonConstVtkSource = const_cast(constVtkSource); - - ContourModelUtils::FillSliceInSlice(nonConstVtkSource, targetSlice->GetVtkImageData(), workingImage, paintingPixelValue, 1.0); -} - bool mitk::SegTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent* positionEvent, const mitk::BaseData* data) { bool isPositionEventInsideImageRegion = nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); if (!isPositionEventInsideImageRegion) MITK_WARN("EditableContourTool") << "PositionEvent is outside ImageRegion!"; return isPositionEventInsideImageRegion; } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.h b/Modules/Segmentation/Interactions/mitkSegTool2D.h index 99d6fdf91a..a8ba18eb0b 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,303 +1,290 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegTool2D_h #define mitkSegTool2D_h #include #include #include #include #include #include #include #include #include namespace mitk { class BaseRenderer; /** \brief Abstract base class for segmentation tools. \sa Tool \ingroup Interaction \ingroup ToolManagerEtAl Implements 2D segmentation specific helper methods, that might be of use to all kind of 2D segmentation tools. At the moment these are: - Determination of the slice where the user paints upon (DetermineAffectedImageSlice) - Projection of a 3D contour onto a 2D plane/slice SegTool2D tries to structure the interaction a bit. If you pass "PressMoveRelease" as the interaction type of your derived tool, you might implement the methods OnMousePressed, OnMouseMoved, and OnMouseReleased. Yes, your guess about when they are called is correct. \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT SegTool2D : public Tool { public: mitkClassMacro(SegTool2D, Tool); /** \brief Calculates for a given Image and PlaneGeometry, which slice of the image (in index corrdinates) is meant by the plane. \return false, if no slice direction seems right (e.g. rotated planes) \param image \param plane \param affectedDimension The image dimension, which is constant for all points in the plane, e.g. Axial --> 2 \param affectedSlice The index of the image slice */ static bool DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice); /** - * @brief Updates the surface interpolation by extracting the contour form the given slice. + * @brief Updates the surface interpolations by extracting the contour form the given slice for all labels + * that have a surface contour information stored for the given plane at the given timestep. * @param slice the slice from which the contour should be extracted * @param workingImage the segmentation image + * @param timeStep the time step for wich the surface interpolation information should be updated. * @param plane the plane in which the slice lies * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ - static void UpdateSurfaceInterpolation(const Image *slice, - const Image *workingImage, + static void UpdateAllSurfaceInterpolations(const LabelSetImage* workingImage, + TimeStepType timeStep, const PlaneGeometry *plane, bool detectIntersection); /** * \brief Extract the slice of an image that the user just scribbles on. The given component denotes the vector component of an vector image. * * \param positionEvent Event that specifies the plane that should be used to slice * \param image Image that should be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const InteractionPositionEvent* positionEvent, const Image* image, unsigned int component = 0); /** * \brief Extract the slice of an image cut by given plane. The given component denotes the vector component of a vector image. * * \param planeGeometry Geometry defining the slice that should be cut out. * \param image Image that should be sliced * \param timeStep TimeStep of the image that shold be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const PlaneGeometry* planeGeometry, const Image* image, TimeStepType timeStep, unsigned int component = 0); static Image::Pointer GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component = 0); /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * Calls static WriteBackSegmentationResults*/ static void WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep); /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * For more details see protected WriteSliceToVolume version.*/ static void WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo); void SetShowMarkerNodes(bool); /** * \brief Enables or disables the 3D interpolation after writing back the 2D segmentation result, and defaults to * true. */ void SetEnable3DInterpolation(bool); void Activated() override; void Deactivated() override; itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); protected: SegTool2D(); // purposely hidden SegTool2D(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~SegTool2D() override; /** * @brief returns the segmentation node that should be modified by the tool. */ DataNode* GetWorkingDataNode() const; Image* GetWorkingData() const; DataNode* GetReferenceDataNode() const; Image* GetReferenceData() const; /** * This function can be reimplemented by derived classes to react on changes of the current * time point. Default implementation does nothing.*/ virtual void OnTimePointChanged(); struct SliceInformation { mitk::Image::ConstPointer slice; const mitk::PlaneGeometry *plane = nullptr; mitk::TimeStepType timestep = 0; unsigned int slicePosition; SliceInformation() = default; SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep); }; /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param sliceInfos vector of slice information instances from which the contours should be extracted * @param workingImage the segmentation image * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the - * @param activeLayerID The layer ID of the active label. * @param activeLabelValue The label value of the active label. + * @param silent Indicates if the modification event of the SurfaceInterpolationController should be triggered. * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection, - unsigned int activeLayerID, - mitk::Label::PixelType activeLabelValue); + mitk::Label::PixelType activeLabelValue, bool silent = false); /** * \brief Filters events that cannot be handled by 2D segmentation tools * * Currently an event is discarded if it was not sent by a 2D renderwindow and if it is * not of type InteractionPositionEvent */ bool FilterEvents(InteractionEvent *interactionEvent, DataNode *dataNode) override; /** \brief Extract the slice of the currently selected working image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no working image is selected. */ Image::Pointer GetAffectedWorkingSlice(const InteractionPositionEvent *) const; /** \brief Extract the slice of the currently selected reference image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no reference image is selected. */ Image::Pointer GetAffectedReferenceSlice(const InteractionPositionEvent *) const; /** Overload version that gets the reference slice passed on the passed plane geometry and timestep.*/ Image::Pointer GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const; /** Convenience version that can be called for a given event (which is used to deduce timepoint and plane) and a slice image. * Calls non static WriteBackSegmentationResults*/ void WriteBackSegmentationResult(const InteractionPositionEvent *, const Image* segmentationResult); /** Convenience version that can be called for a given planeGeometry, slice image and time step. * Calls non static WriteBackSegmentationResults*/ void WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image* segmentationResult, TimeStepType timeStep); /** Overloaded version that calls the static version and also adds the contour markers. * @remark If the sliceList is empty, this function does nothing.*/ void WriteBackSegmentationResults(const std::vector &sliceList, bool writeSliceToVolume = true); /** \brief Writes all provided source slices into the data of the passed workingNode. * The function does the following: 1) for every passed slice write it to workingNode (and generate and undo/redo step); * 2) update the surface interpolation and 3) mark the node as modified. * @param workingNode Pointer to the node that contains the working image. * @param sliceList Vector of all slices that should be written into the workingNode. If the list is * empty, the function call does nothing. * @param writeSliceToVolume If set to false the write operation (WriteSliceToVolume will be skipped) * and only the surface interpolation will be updated. * @pre workingNode must point to a valid instance and contain an image instance as data.*/ static void WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume = true); - /** Writes the provided source slice into the target slice with the given pixel value. - * If passed workingImage is a LabelSetImage the label set rules will be applied when - * writing all non zero source pixels into the target slice (e.g. locked lables will not be touched) - * with the given paintingPixelValue. - * @param targetSlice Pointer to the slice that should be filled with the content of the sourceSlice. - * @param sourceSlice Pointer to the slice that is the source/preview every pixel will be (tried to be) transfered . - * @param workingImage Will be used to check if LabeSetImageRules have to be applied and the label set state. - * @param paintingPixelValue Value that will be used to paint onto target slice. - * @pre targetSlice must point to a valid instance. - * @pre sourceSlice must point to a valid instance. - * @pre workingImage must point to a valid instance.*/ - static void WritePreviewOnWorkingImage( - Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue); - /** Writes a provided slice into the passed working image. The content of working image that is covered * by the slice will be completly overwritten. If asked for it also generates the needed * undo/redo steps. * @param workingImage Pointer to the image that is the target of the write operation. * @param sliceInfo SliceInfo instance that containes the slice image, the defining plane geometry and time step. * @param allowUndo Indicates if undo/redo operations should be registered for the write operation * performed by this call. true: undo/redo will be generated; false: no undo/redo will be generated, so * this operation cannot be revoked by the user. * @pre workingImage must point to a valid instance.*/ static void WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo); /** \brief Adds a new node called Contourmarker to the datastorage which holds a mitk::PlanarFigure. By selecting this node the slicestack will be reoriented according to the passed PlanarFigure's Geometry */ int AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex); void InteractiveSegmentationBugMessage(const std::string &message) const; /** Helper function to check if a position events points to a point inside the boundingbox of a passed data instance.*/ static bool IsPositionEventInsideImageRegion(InteractionPositionEvent* positionEvent, const BaseData* data); BaseRenderer *m_LastEventSender = nullptr; unsigned int m_LastEventSlice = 0; itkGetMacro(LastTimePointTriggered, TimePointType); private: /** Internal method that gets triggered as soon as the tool manager indicates a * time point change. If the time point has changed since last time and tool * is set to be time point change aware, OnTimePointChanged() will be called.*/ void OnTimePointChangedInternal(); - static void RemoveContourFromInterpolator(const SliceInformation& sliceInfo); + static void RemoveContourFromInterpolator(const SliceInformation& sliceInfo, LabelSetImage::LabelValueType labelValue); // The prefix of the contourmarkername. Suffix is a consecutive number const std::string m_Contourmarkername; bool m_ShowMarkerNodes = false; static bool m_SurfaceInterpolationEnabled; bool m_IsTimePointChangeAware = true; TimePointType m_LastTimePointTriggered = 0.; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp index 77ffb417cd..3e7aa8d51c 100644 --- a/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSegWithPreviewTool.cpp @@ -1,842 +1,840 @@ /*============================================================================ 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 "mitkSegWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" #include "mitkSegTool2D.h" mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews): Tool("dummy"), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::SegWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = ToolCommand::New(); } mitk::SegWithPreviewTool::~SegWithPreviewTool() { } void mitk::SegWithPreviewTool::SetMergeStyle(MultiLabelSegmentation::MergeStyle mergeStyle) { m_MergeStyle = mergeStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetOverwriteStyle(MultiLabelSegmentation::OverwriteStyle overwriteStyle) { m_OverwriteStyle = overwriteStyle; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferScope(LabelTransferScope labelTransferScope) { m_LabelTransferScope = labelTransferScope; this->Modified(); } void mitk::SegWithPreviewTool::SetLabelTransferMode(LabelTransferMode labelTransferMode) { m_LabelTransferMode = labelTransferMode; this->Modified(); } void mitk::SegWithPreviewTool::SetSelectedLabels(const SelectedLabelVectorType& labelsToTransfer) { m_SelectedLabels = labelsToTransfer; this->Modified(); } bool mitk::SegWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return false; auto* referenceImage = dynamic_cast(referenceData); if (referenceImage == nullptr) return false; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* workingImage = dynamic_cast(workingData); if (workingImage == nullptr) return false; // If the working image is a normal image and not a label set image // it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == workingImage->GetPixelType(); } void mitk::SegWithPreviewTool::Activated() { Superclass::Activated(); this->GetToolManager()->RoiDataChanged += MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged += MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = this->GetToolManager()->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; m_LastTimePointOfUpdate = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { this->GetToolManager()->ActivateTool(-1); } } void mitk::SegWithPreviewTool::Deactivated() { this->GetToolManager()->RoiDataChanged -= MessageDelegate(this, &SegWithPreviewTool::OnRoiDataChanged); this->GetToolManager()->SelectedTimePointChanged -= MessageDelegate(this, &SegWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::SegWithPreviewTool::ConfirmSegmentation() { bool labelChanged = this->EnsureUpToDateUserDefinedActiveLabel(); if ((m_LazyDynamicPreviews && m_CreateAllTimeSteps) || labelChanged) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { this->GetToolManager()->ActivateTool(-1); } this->ConfirmCleanUp(); } void mitk::SegWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } const mitk::LabelSetImage* mitk::SegWithPreviewTool::GetPreviewSegmentation() const { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::SegWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::SegWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::SegWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } template void ClearBufferProcessing(ImageType* itkImage) { itkImage->FillBuffer(0); } void mitk::SegWithPreviewTool::ResetPreviewContentAtTimeStep(unsigned int timeStep) { auto previewImage = GetImageByTimeStep(this->GetPreviewSegmentation(), timeStep); if (nullptr != previewImage) { AccessByItk(previewImage, ClearBufferProcessing); } } void mitk::SegWithPreviewTool::ResetPreviewContent() { auto previewImage = this->GetPreviewSegmentation(); if (nullptr != previewImage) { auto castedPreviewImage = dynamic_cast(previewImage); if (nullptr == castedPreviewImage) mitkThrow() << "Application is on wrong state / invalid tool implementation. Preview image should always be of type LabelSetImage now."; castedPreviewImage->ClearBuffer(); } } void mitk::SegWithPreviewTool::ResetPreviewNode() { if (m_IsUpdating) { mitkThrow() << "Used tool is implemented incorrectly. ResetPreviewNode is called while preview update is ongoing. Check implementation!"; } itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { LabelSetImage::ConstPointer workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (this->GetResetsToEmptyPreview()) { newPreviewImage->ClearBuffer(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); - auto* activeLabelSet = newPreviewImage->GetActiveLabelSet(); - if (nullptr == activeLabelSet) + if (newPreviewImage->GetNumberOfLayers() == 0) { newPreviewImage->AddLayer(); - activeLabelSet = newPreviewImage->GetActiveLabelSet(); + newPreviewImage->SetActiveLayer(0); } - auto* activeLabel = activeLabelSet->GetActiveLabel(); + auto* activeLabel = newPreviewImage->GetActiveLabel(); if (nullptr == activeLabel) { - activeLabel = activeLabelSet->AddLabel("toolresult", previewColor); - activeLabelSet = newPreviewImage->GetActiveLabelSet(); - activeLabelSet->UpdateLookupTable(activeLabel->GetValue()); + activeLabel = newPreviewImage->AddLabel("toolresult", previewColor, newPreviewImage->GetActiveLayer()); + newPreviewImage->UpdateLookupTable(activeLabel->GetValue()); } else if (m_UseSpecialPreviewColor) { // Let's paint the feedback node green... activeLabel->SetColor(previewColor); - activeLabelSet->UpdateLookupTable(activeLabel->GetValue()); + newPreviewImage->UpdateLookupTable(activeLabel->GetValue()); } activeLabel->SetVisible(true); } else { Image::ConstPointer workingImageBin = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { Image::Pointer newPreviewImage; if (this->GetResetsToEmptyPreview()) { newPreviewImage = Image::New(); newPreviewImage->Initialize(workingImageBin); } else { auto newPreviewImage = workingImageBin->Clone(); } if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = this->GetToolManager()->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } mitk::SegWithPreviewTool::LabelMappingType mitk::SegWithPreviewTool::GetLabelMapping() const { LabelSetImage::LabelValueType offset = 0; if (LabelTransferMode::AddLabel == m_LabelTransferMode && LabelTransferScope::ActiveLabel!=m_LabelTransferScope) { //If we are not just working on active label and transfer mode is add, we need to compute an offset for adding the //preview labels instat of just mapping them to existing segmentation labels. const auto segmentation = this->GetTargetSegmentation(); if (nullptr == segmentation) mitkThrow() << "Invalid state of SegWithPreviewTool. Cannot GetLabelMapping if no target segmentation is set."; auto labels = segmentation->GetLabels(); auto maxLabelIter = std::max_element(std::begin(labels), std::end(labels), [](const Label::Pointer& a, const Label::Pointer& b) { return a->GetValue() < b->GetValue(); }); if (maxLabelIter != labels.end()) { offset = maxLabelIter->GetPointer()->GetValue(); } } LabelMappingType labelMapping = {}; switch (this->m_LabelTransferScope) { case LabelTransferScope::SelectedLabels: { for (auto label : this->m_SelectedLabels) { labelMapping.push_back({label, label + offset}); } } break; case LabelTransferScope::AllLabels: { - const auto labelSet = this->GetPreviewSegmentation()->GetActiveLabelSet(); - for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + const auto labelValues = this->GetPreviewSegmentation()->GetLabelValuesByGroup(this->GetPreviewSegmentation()->GetActiveLayer()); + for (auto labelValue : labelValues) { - labelMapping.push_back({labelIter->second->GetValue(), labelIter->second->GetValue() + offset}); + labelMapping.push_back({ labelValue, labelValue + offset}); } } break; default: { if (m_SelectedLabels.empty()) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but no label is indicated as selected label. Check " "implementation of derived tool class."; if (m_SelectedLabels.size() > 1) mitkThrow() << "Failed to generate label transfer mapping. Tool is in an invalid state, as " "LabelTransferScope==ActiveLabel but more then one selected label is indicated." "Should be only one. Check implementation of derived tool class."; labelMapping.push_back({m_SelectedLabels.front(), this->GetUserDefinedActiveLabel()}); } break; } return labelMapping; } void mitk::SegWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep, const LabelMappingType& labelMapping) { try { Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_COORDINATE_PRECISION, NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_DIRECTION_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } if (nullptr != this->GetWorkingPlaneGeometry()) { auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); auto resultSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), destinationImage, timeStep)->Clone(); auto destLSImage = dynamic_cast(destinationImage); //We need to transfer explictly to a copy of the current working image to ensure that labelMapping is done and things //like merge style, overwrite style and locks are regarded. TransferLabelContentAtTimeStep(sourceSlice, resultSlice, - destLSImage->GetActiveLabelSet(), + destLSImage->GetConstLabelsByValue(destLSImage->GetLabelValuesByGroup(destLSImage->GetActiveLayer())), timeStep, 0, 0, destLSImage->GetUnlabeledLabelLock(), labelMapping, m_MergeStyle, m_OverwriteStyle); //We use WriteBackSegmentationResult to ensure undo/redo is supported also by derived tools of this class. SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, resultSlice, timeStep); } else { //take care of the full segmentation volume auto sourceLSImage = dynamic_cast(sourceImage); auto destLSImage = dynamic_cast(destinationImage); TransferLabelContentAtTimeStep(sourceLSImage, destLSImage, timeStep, labelMapping, m_MergeStyle, m_OverwriteStyle); } } catch (mitk::Exception& e) { Tool::ErrorMessage(e.GetDescription()); mitkReThrow(e); } } void mitk::SegWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working referenceImage (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot confirm/transfer segmentation. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } auto labelMapping = this->GetLabelMapping(); this->PreparePreviewToResultTransfer(labelMapping); if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep, labelMapping); } // since we are maybe working on a smaller referenceImage, pad it to the size of the original referenceImage if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { PadImageFilter::Pointer padFilter = PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::SegWithPreviewTool::OnRoiDataChanged() { DataNode::ConstPointer node = this->GetToolManager()->GetRoiData(0); if (node.IsNotNull()) { MaskAndCutRoiImageFilter::Pointer roiFilter = MaskAndCutRoiImageFilter::New(); Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); DataNode::Pointer tmpNode = DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::SegWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic referenceImage this->UpdatePreview(); } } } bool mitk::SegWithPreviewTool::EnsureUpToDateUserDefinedActiveLabel() { bool labelChanged = true; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); if (const auto& labelSetImage = dynamic_cast(workingImage)) { // this is a fix for T28131 / T28986, which should be refactored if T28524 is being worked on - auto newLabel = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer())->GetValue(); + auto newLabel = labelSetImage->GetActiveLabel()->GetValue(); labelChanged = newLabel != m_UserDefinedActiveLabel; m_UserDefinedActiveLabel = newLabel; } else { m_UserDefinedActiveLabel = 1; labelChanged = false; } return labelChanged; } void mitk::SegWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; const auto workingImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); this->EnsureUpToDateUserDefinedActiveLabel(); this->CurrentlyBusy.Send(true); m_IsUpdating = true; this->UpdatePrepare(); const TimePointType timePoint = RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); if (nullptr != this->GetWorkingPlaneGeometry()) { //only extract a specific slice defined by the working plane as feedback referenceImage. feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, previewTimePoint); } else { //work on the whole feedback referenceImage feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); currentSegImage = this->GetImageByTimePoint(workingImage, previewTimePoint); } this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } } else { Image::ConstPointer feedBackImage; Image::ConstPointer currentSegImage; if (nullptr != this->GetWorkingPlaneGeometry()) { feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); currentSegImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), workingImage, timePoint); } else { feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); currentSegImage = this->GetImageByTimePoint(workingImage, timePoint); } auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); this->DoUpdatePreview(feedBackImage, currentSegImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); m_IsUpdating = false; CurrentlyBusy.Send(false); } bool mitk::SegWithPreviewTool::IsUpdating() const { return m_IsUpdating; } void mitk::SegWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::ConfirmCleanUp() { // default implementation does nothing // reimplement in derived classes for special behavior } void mitk::SegWithPreviewTool::TransferLabelInformation(const LabelMappingType& labelMapping, const mitk::LabelSetImage* source, mitk::LabelSetImage* target) { for (const auto& [sourceLabel, targetLabel] : labelMapping) { - if (LabelSetImage::UnlabeledValue != sourceLabel && - LabelSetImage::UnlabeledValue != targetLabel && + if (LabelSetImage::UNLABELED_VALUE != sourceLabel && + LabelSetImage::UNLABELED_VALUE != targetLabel && !target->ExistLabel(targetLabel, target->GetActiveLayer())) { if (!source->ExistLabel(sourceLabel, source->GetActiveLayer())) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Preview seems invalid as label is missing. Missing label: " << sourceLabel; } auto clonedLabel = source->GetLabel(sourceLabel)->Clone(); clonedLabel->SetValue(targetLabel); - target->GetActiveLabelSet()->AddLabel(clonedLabel); + target->AddLabel(clonedLabel,target->GetActiveLayer(), false, false); } } } void mitk::SegWithPreviewTool::PreparePreviewToResultTransfer(const LabelMappingType& labelMapping) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); if (nullptr == resultSegmentation) { mitkThrow() << "Cannot prepare segmentation for preview transfer. Tool is in invalid state as segmentation is not existing or of right type"; } auto preview = this->GetPreviewSegmentation(); TransferLabelInformation(labelMapping, preview, resultSegmentation); } } mitk::TimePointType mitk::SegWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } mitk::LabelSetImage::LabelValueType mitk::SegWithPreviewTool::GetActiveLabelValueOfPreview() const { const auto previewImage = this->GetPreviewSegmentation(); - const auto activeLabel = previewImage->GetActiveLabel(previewImage->GetActiveLayer()); + const auto activeLabel = previewImage->GetActiveLabel(); if (nullptr == activeLabel) mitkThrow() << this->GetNameOfClass() <<" is in an invalid state, as " "preview has no active label indicated. Check " "implementation of the class."; return activeLabel->GetValue(); } const char* mitk::SegWithPreviewTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimeStep(const mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::Pointer mitk::SegWithPreviewTool::GetImageByTimeStep(mitk::Image* image, TimeStepType timestep) { return SelectImageByTimeStep(image, timestep); } mitk::Image::ConstPointer mitk::SegWithPreviewTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { return SelectImageByTimePoint(image, timePoint); } void mitk::SegWithPreviewTool::EnsureTargetSegmentationNodeInDataStorage() const { auto targetNode = this->GetTargetSegmentationNode(); auto dataStorage = this->GetToolManager()->GetDataStorage(); if (!dataStorage->Exists(targetNode)) { dataStorage->Add(targetNode, this->GetToolManager()->GetReferenceData(0)); } } std::string mitk::SegWithPreviewTool::GetCurrentSegmentationName() { auto workingData = this->GetToolManager()->GetWorkingData(0); return nullptr != workingData ? workingData->GetName() : ""; } mitk::DataNode* mitk::SegWithPreviewTool::GetTargetSegmentationNode() const { return this->GetToolManager()->GetWorkingData(0); } mitk::LabelSetImage* mitk::SegWithPreviewTool::GetTargetSegmentation() const { auto node = this->GetTargetSegmentationNode(); if (nullptr == node) return nullptr; return dynamic_cast(node->GetData()); } void mitk::SegWithPreviewTool::TransferLabelSetImageContent(const LabelSetImage* source, LabelSetImage* target, TimeStepType timeStep) { mitk::ImageReadAccessor newMitkImgAcc(source); LabelMappingType labelMapping; - const auto labelSet = source->GetActiveLabelSet(); - for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) + const auto labelValues = source->GetLabelValuesByGroup(source->GetActiveLayer()); + for (const auto& labelValue : labelValues) { - labelMapping.push_back({ labelIter->second->GetValue(),labelIter->second->GetValue() }); + labelMapping.push_back({ labelValue,labelValue }); } TransferLabelInformation(labelMapping, source, target); target->SetVolume(newMitkImgAcc.GetData(), timeStep); } diff --git a/Modules/Segmentation/Interactions/mitkTool.cpp b/Modules/Segmentation/Interactions/mitkTool.cpp index 6ad70a18e1..81ce0c816a 100644 --- a/Modules/Segmentation/Interactions/mitkTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTool.cpp @@ -1,338 +1,339 @@ /*============================================================================ 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 "mitkTool.h" #include "mitkDisplayActionEventBroadcast.h" #include "mitkImageReadAccessor.h" #include "mitkImageWriteAccessor.h" #include "mitkLevelWindowProperty.h" #include "mitkLookupTableProperty.h" #include "mitkProperties.h" #include "mitkVtkResliceInterpolationProperty.h" #include #include // us #include #include // itk #include namespace mitk { itkEventMacroDefinition(ToolEvent, itk::ModifiedEvent); } mitk::Tool::Tool(const char *type, const us::Module *interactorModule) : m_EventConfig(""), m_ToolManager(nullptr), m_PredicateImages(NodePredicateDataType::New("Image")), // for reference images m_PredicateDim3(NodePredicateDimension::New(3, 1)), m_PredicateDim4(NodePredicateDimension::New(4, 1)), m_PredicateDimension(mitk::NodePredicateOr::New(m_PredicateDim3, m_PredicateDim4)), m_PredicateImage3D(NodePredicateAnd::New(m_PredicateImages, m_PredicateDimension)), m_PredicateBinary(NodePredicateProperty::New("binary", BoolProperty::New(true))), m_PredicateNotBinary(NodePredicateNot::New(m_PredicateBinary)), m_PredicateSegmentation(NodePredicateProperty::New("segmentation", BoolProperty::New(true))), m_PredicateNotSegmentation(NodePredicateNot::New(m_PredicateSegmentation)), m_PredicateHelper(NodePredicateProperty::New("helper object", BoolProperty::New(true))), m_PredicateNotHelper(NodePredicateNot::New(m_PredicateHelper)), m_PredicateImageColorful(NodePredicateAnd::New(m_PredicateNotBinary, m_PredicateNotSegmentation)), m_PredicateImageColorfulNotHelper(NodePredicateAnd::New(m_PredicateImageColorful, m_PredicateNotHelper)), m_PredicateReference(NodePredicateAnd::New(m_PredicateImage3D, m_PredicateImageColorfulNotHelper)), m_IsSegmentationPredicate( NodePredicateAnd::New(NodePredicateOr::New(m_PredicateBinary, m_PredicateSegmentation), m_PredicateNotHelper)), m_InteractorType(type), m_DisplayInteractionConfigs(), m_InteractorModule(interactorModule) { } mitk::Tool::~Tool() { } bool mitk::Tool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { if (referenceData == nullptr) return false; return true; } void mitk::Tool::InitializeStateMachine() { if (m_InteractorType.empty()) return; try { auto isThisModule = nullptr == m_InteractorModule; auto module = isThisModule ? us::GetModuleContext()->GetModule() : m_InteractorModule; LoadStateMachine(m_InteractorType + ".xml", module); SetEventConfig(isThisModule ? "SegmentationToolsConfig.xml" : m_InteractorType + "Config.xml", module); } catch (const std::exception &e) { MITK_ERROR << "Could not load statemachine pattern " << m_InteractorType << ".xml with exception: " << e.what(); } } void mitk::Tool::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled) { this->HandleEvent(interactionEvent, nullptr); } } void mitk::Tool::ConnectActionsAndFunctions() { } bool mitk::Tool::FilterEvents(InteractionEvent *, DataNode *) { return true; } const char *mitk::Tool::GetGroup() const { return "default"; } void mitk::Tool::SetToolManager(ToolManager *manager) { m_ToolManager = manager; } mitk::ToolManager* mitk::Tool::GetToolManager() const { return m_ToolManager; } mitk::DataStorage* mitk::Tool::GetDataStorage() const { if (nullptr != m_ToolManager) { return m_ToolManager->GetDataStorage(); } return nullptr; } void mitk::Tool::Activated() { // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts // with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction // will still be enabled m_DisplayInteractionConfigs.clear(); auto eventObservers = us::GetModuleContext()->GetServiceReferences(); for (const auto& eventObserver : eventObservers) { auto displayActionEventBroadcast = dynamic_cast( us::GetModuleContext()->GetService(eventObserver)); if (nullptr != displayActionEventBroadcast) { // remember the original configuration m_DisplayInteractionConfigs.insert(std::make_pair(eventObserver, displayActionEventBroadcast->GetEventConfig())); // here the alternative configuration is loaded displayActionEventBroadcast->AddEventConfig(m_EventConfig.c_str()); } } } void mitk::Tool::Deactivated() { // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (const auto& displayInteractionConfig : m_DisplayInteractionConfigs) { if (displayInteractionConfig.first) { auto displayActionEventBroadcast = static_cast( us::GetModuleContext()->GetService(displayInteractionConfig.first)); if (nullptr != displayActionEventBroadcast) { // here the regular configuration is loaded again displayActionEventBroadcast->SetEventConfig(displayInteractionConfig.second); } } } m_DisplayInteractionConfigs.clear(); } itk::Object::Pointer mitk::Tool::GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix) { itk::Object::Pointer object; std::string classname = this->GetNameOfClass(); std::string guiClassname = toolkitPrefix + classname + toolkitPostfix; std::list allGUIs = itk::ObjectFactoryBase::CreateAllInstance(guiClassname.c_str()); for (auto iter = allGUIs.begin(); iter != allGUIs.end(); ++iter) { if (object.IsNull()) { object = dynamic_cast(iter->GetPointer()); } else { MITK_ERROR << "There is more than one GUI for " << classname << " (several factories claim ability to produce a " << guiClassname << " ) " << std::endl; return nullptr; // people should see and fix this error } } return object; } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetReferenceDataPreference() const { return m_PredicateReference.GetPointer(); } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetWorkingDataPreference() const { return m_IsSegmentationPredicate.GetPointer(); } mitk::DataNode::Pointer mitk::Tool::CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) const { // we NEED a reference image for size etc. if (!original) return nullptr; // actually create a new empty segmentation PixelType pixelType(mitk::MakeScalarPixelType()); LabelSetImage::Pointer segmentation = LabelSetImage::New(); if (original->GetDimension() == 2) { const unsigned int dimensions[] = {original->GetDimension(0), original->GetDimension(1), 1}; segmentation->Initialize(pixelType, 3, dimensions); segmentation->AddLayer(); + segmentation->SetActiveLayer(0); } else { segmentation->Initialize(original); } mitk::Label::Pointer label = mitk::Label::New(); label->SetName(organName); label->SetColor(color); label->SetValue(1); - segmentation->GetActiveLabelSet()->AddLabel(label); - segmentation->GetActiveLabelSet()->SetActiveLabel(1); + segmentation->AddLabel(label,segmentation->GetActiveLayer()); + segmentation->SetActiveLabel(label->GetValue()); unsigned int byteSize = sizeof(mitk::Label::PixelType); if (segmentation->GetDimension() < 4) { for (unsigned int dim = 0; dim < segmentation->GetDimension(); ++dim) { byteSize *= segmentation->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); } else { // if we have a time-resolved image we need to set memory to 0 for each time step for (unsigned int dim = 0; dim < 3; ++dim) { byteSize *= segmentation->GetDimension(dim); } for (unsigned int volumeNumber = 0; volumeNumber < segmentation->GetDimension(3); volumeNumber++) { mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(volumeNumber)); memset(writeAccess.GetData(), 0, byteSize); } } if (original->GetTimeGeometry()) { TimeGeometry::Pointer originalGeometry = original->GetTimeGeometry()->Clone(); segmentation->SetTimeGeometry(originalGeometry); } else { Tool::ErrorMessage("Original image does not have a 'Time sliced geometry'! Cannot create a segmentation."); return nullptr; } return CreateSegmentationNode(segmentation, organName, color); } mitk::DataNode::Pointer mitk::Tool::CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) const { if (!image) return nullptr; // decorate the datatreenode with some properties DataNode::Pointer segmentationNode = DataNode::New(); segmentationNode->SetData(image); // name segmentationNode->SetProperty("name", StringProperty::New(organName)); // visualization properties segmentationNode->SetProperty("binary", BoolProperty::New(true)); segmentationNode->SetProperty("color", ColorProperty::New(color)); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::MULTILABEL); mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(); lutProp->SetLookupTable(lut); segmentationNode->SetProperty("LookupTable", lutProp); segmentationNode->SetProperty("texture interpolation", BoolProperty::New(false)); segmentationNode->SetProperty("layer", IntProperty::New(10)); segmentationNode->SetProperty("levelwindow", LevelWindowProperty::New(LevelWindow(0.5, 1))); segmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); segmentationNode->SetProperty("segmentation", BoolProperty::New(true)); segmentationNode->SetProperty("reslice interpolation", VtkResliceInterpolationProperty::New()); // otherwise -> segmentation appears in 2 // slices sometimes (only visual effect, not // different data) // For MITK-3M3 release, the volume of all segmentations should be shown segmentationNode->SetProperty("showVolume", BoolProperty::New(true)); return segmentationNode; } us::ModuleResource mitk::Tool::GetIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } us::ModuleResource mitk::Tool::GetCursorIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } diff --git a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp index c3ac4dffd4..edab65b958 100644 --- a/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTotalSegmentatorTool.cpp @@ -1,354 +1,348 @@ /*============================================================================ 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. ============================================================================*/ // MITK #include "mitkTotalSegmentatorTool.h" #include #include #include #include #include #include // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, TotalSegmentatorTool, "Total Segmentator"); } mitk::TotalSegmentatorTool::~TotalSegmentatorTool() { std::filesystem::remove_all(this->GetMitkTempDir()); } mitk::TotalSegmentatorTool::TotalSegmentatorTool() : SegWithPreviewTool(true) // prevents auto-compute across all timesteps { this->IsTimePointChangeAwareOff(); } void mitk::TotalSegmentatorTool::Activated() { Superclass::Activated(); this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } const char **mitk::TotalSegmentatorTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::TotalSegmentatorTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } const char *mitk::TotalSegmentatorTool::GetName() const { return "TotalSegmentator"; } void mitk::TotalSegmentatorTool::onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) { std::string testCOUT; std::string testCERR; const auto *pEvent = dynamic_cast(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } void mitk::TotalSegmentatorTool::DoUpdatePreview(const Image *inputAtTimeStep, const Image * /*oldSegAtTimeStep*/, LabelSetImage *previewImage, TimeStepType timeStep) { if (this->m_MitkTempDir.empty()) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } ProcessExecutor::Pointer spExec = ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&onPythonProcessEvent); spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); m_ProgressCommand->SetProgress(5); std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; inDir = IOUtil::CreateTemporaryDirectory("totalseg-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, TEMPLATE_FILENAME, inDir + IOUtil::GetDirectorySeparator()); tmpStream.close(); std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); std::string fileName = inputImagePath.substr(found + 1); std::string token = fileName.substr(0, fileName.find("_")); outDir = IOUtil::CreateTemporaryDirectory("totalseg-out-XXXXXX", this->GetMitkTempDir()); LabelSetImage::Pointer outputBuffer; m_ProgressCommand->SetProgress(20); IOUtil::Save(inputAtTimeStep, inputImagePath); m_ProgressCommand->SetProgress(50); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { outputImagePath = outDir; this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, !isSubTask, !isSubTask, this->GetGpuId(), this->GetSubTask()); // Construct Label Id map std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); // Agglomerate individual mask files into one multi-label image. std::for_each(files.begin(), files.end(), [&](std::string &fileName) { fileName = (outDir + IOUtil::GetDirectorySeparator() + fileName); }); outputBuffer = AgglomerateLabelFiles(files, inputAtTimeStep->GetDimensions(), inputAtTimeStep->GetGeometry()); } else { this->run_totalsegmentator( spExec, inputImagePath, outputImagePath, this->GetFast(), !isSubTask, this->GetGpuId(), DEFAULT_TOTAL_TASK); Image::Pointer outputImage = IOUtil::Load(outputImagePath); outputBuffer = mitk::LabelSetImage::New(); outputBuffer->InitializeByLabeledImage(outputImage); outputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } m_ProgressCommand->SetProgress(180); mitk::ImageReadAccessor newMitkImgAcc(outputBuffer.GetPointer()); this->MapLabelsToSegmentation(outputBuffer, previewImage, m_LabelMapTotal); previewImage->SetVolume(newMitkImgAcc.GetData(), timeStep); } void mitk::TotalSegmentatorTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); - for (LabelSetImage::GroupIndexType i = 0; i < preview->GetNumberOfLayers(); ++i) - { - preview->GetLabelSet(i)->RemoveAllLabels(); - } + preview->RemoveLabels(preview->GetAllLabelValues()); if (m_LabelMapTotal.empty()) { this->ParseLabelMapTotalDefault(); } const bool isSubTask = (this->GetSubTask() != DEFAULT_TOTAL_TASK); if (isSubTask) { std::vector files = SUBTASKS_MAP.at(this->GetSubTask()); m_LabelMapTotal.clear(); mitk::Label::PixelType labelId = 1; for (auto const &file : files) { std::string labelName = file.substr(0, file.find('.')); m_LabelMapTotal[labelId] = labelName; labelId++; } } } mitk::LabelSetImage::Pointer mitk::TotalSegmentatorTool::AgglomerateLabelFiles(std::vector &filePaths, const unsigned int *dimensions, mitk::BaseGeometry *geometry) { Label::PixelType labelId = 1; auto aggloLabelImage = mitk::LabelSetImage::New(); auto initImage = mitk::Image::New(); initImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); aggloLabelImage->Initialize(initImage); aggloLabelImage->SetGeometry(geometry); - mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); - newlayer->SetLayer(0); - aggloLabelImage->AddLayer(newlayer); + const auto layerIndex = aggloLabelImage->AddLayer(); + aggloLabelImage->SetActiveLayer(layerIndex); for (auto const &outputImagePath : filePaths) { double rgba[4]; - aggloLabelImage->GetActiveLabelSet()->GetLookupTable()->GetTableValue(labelId, rgba); + aggloLabelImage->GetLookupTable()->GetTableValue(labelId, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName("object-" + std::to_string(labelId)); label->SetValue(labelId); label->SetColor(color); label->SetOpacity(rgba[3]); - aggloLabelImage->GetActiveLabelSet()->AddLabel(label); + aggloLabelImage->AddLabel(label, layerIndex, false, false); Image::Pointer outputImage = IOUtil::Load(outputImagePath); auto source = mitk::LabelSetImage::New(); source->InitializeByLabeledImage(outputImage); source->SetGeometry(geometry); - auto labelSet = aggloLabelImage->GetActiveLabelSet(); - mitk::TransferLabelContent(source, aggloLabelImage, labelSet, 0, 0, false, {{1, labelId}}); + mitk::TransferLabelContent(source, aggloLabelImage, aggloLabelImage->GetConstLabelsByValue(aggloLabelImage->GetLabelValuesByGroup(layerIndex)), 0, 0, false, {{1, labelId}}); labelId++; } return aggloLabelImage; } void mitk::TotalSegmentatorTool::run_totalsegmentator(ProcessExecutor* spExec, const std::string &inputImagePath, const std::string &outputImagePath, bool isFast, bool isMultiLabel, unsigned int gpuId, const std::string &subTask) { ProcessExecutor::ArgumentListType args; std::string command = "TotalSegmentator"; #ifdef _WIN32 command += ".exe"; #endif args.clear(); args.push_back("-i"); args.push_back(inputImagePath); args.push_back("-o"); args.push_back(outputImagePath); if (subTask != DEFAULT_TOTAL_TASK) { args.push_back("-ta"); args.push_back(subTask); } if (isMultiLabel) { args.push_back("--ml"); } if (isFast) { args.push_back("--fast"); } try { std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(gpuId); itksys::SystemTools::PutEnv(cudaEnv.c_str()); std::stringstream logStream; for (const auto &arg : args) logStream << arg << " "; logStream << this->GetPythonPath(); MITK_INFO << logStream.str(); spExec->Execute(this->GetPythonPath(), command, args); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } } void mitk::TotalSegmentatorTool::ParseLabelMapTotalDefault() { if (!this->GetLabelMapPath().empty()) { std::regex sanitizer(R"([^A-Za-z0-9_])"); std::fstream newfile; newfile.open(this->GetLabelMapPath(), ios::in); std::stringstream buffer; if (newfile.is_open()) { int line = 0; std::string temp; while (std::getline(newfile, temp)) { if (line > 111 && line < 229) { buffer << temp; } ++line; } } std::string key, val; while (std::getline(std::getline(buffer, key, ':'), val, ',')) { std::string sanitized = std::regex_replace(val, sanitizer, ""); m_LabelMapTotal[std::stoi(key)] = sanitized; } } } void mitk::TotalSegmentatorTool::MapLabelsToSegmentation(const mitk::LabelSetImage* source, mitk::LabelSetImage* dest, std::map &labelMap) { - auto labelset = dest->GetLabelSet(); auto lookupTable = mitk::LookupTable::New(); lookupTable->SetType(mitk::LookupTable::LookupTableType::MULTILABEL); for (auto const &[key, val] : labelMap) { if (source->ExistLabel(key, source->GetActiveLayer())) { Label::Pointer label = Label::New(key, val); std::array lookupTableColor; lookupTable->GetColor(key, lookupTableColor.data()); Color color; color.SetRed(lookupTableColor[0]); color.SetGreen(lookupTableColor[1]); color.SetBlue(lookupTableColor[2]); label->SetColor(color); - labelset->AddLabel(label, false); + dest->AddLabel(label, 0,false); } } } std::string mitk::TotalSegmentatorTool::GetLabelMapPath() { std::string pythonFileName; std::filesystem::path pathToLabelMap(this->GetPythonPath()); pathToLabelMap = pathToLabelMap.parent_path(); #ifdef _WIN32 pythonFileName = pathToLabelMap.string() + "/Lib/site-packages/totalsegmentator/map_to_binary.py"; #else pathToLabelMap.append("lib"); for (auto const &dir_entry : std::filesystem::directory_iterator{pathToLabelMap}) { if (dir_entry.is_directory()) { auto dirName = dir_entry.path().filename().string(); if (dirName.rfind("python", 0) == 0) { pathToLabelMap.append(dir_entry.path().filename().string()); break; } } } pythonFileName = pathToLabelMap.string() + "/site-packages/totalsegmentator/map_to_binary.py"; #endif return pythonFileName; } diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp index d2e923b8e0..6af435b28e 100644 --- a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp @@ -1,323 +1,322 @@ /*============================================================================ 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 "mitknnUnetTool.h" #include "mitkIOUtil.h" #include "mitkProcessExecutor.h" #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, nnUNetTool, "nnUNet tool"); } mitk::nnUNetTool::~nnUNetTool() { std::filesystem::remove_all(this->GetMitkTempDir()); } void mitk::nnUNetTool::Activated() { Superclass::Activated(); this->SetLabelTransferScope(LabelTransferScope::AllLabels); this->SetLabelTransferMode(LabelTransferMode::AddLabel); } void mitk::nnUNetTool::RenderOutputBuffer() { if (m_OutputBuffer != nullptr) { try { if (nullptr != this->GetPreviewSegmentationNode()) { auto previewImage = this->GetPreviewSegmentation(); previewImage->InitializeByLabeledImage(m_OutputBuffer); } } catch (const mitk::Exception &e) { MITK_INFO << e.GetDescription(); } } } void mitk::nnUNetTool::SetOutputBuffer(LabelSetImage::Pointer segmentation) { m_OutputBuffer = segmentation; } mitk::LabelSetImage::Pointer mitk::nnUNetTool::GetOutputBuffer() { return m_OutputBuffer; } void mitk::nnUNetTool::ClearOutputBuffer() { m_OutputBuffer = nullptr; } us::ModuleResource mitk::nnUNetTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("AI.svg"); return resource; } const char **mitk::nnUNetTool::GetXPM() const { return nullptr; } const char *mitk::nnUNetTool::GetName() const { return "nnUNet"; } mitk::DataStorage *mitk::nnUNetTool::GetDataStorage() { return this->GetToolManager()->GetDataStorage(); } mitk::DataNode *mitk::nnUNetTool::GetRefNode() { return this->GetToolManager()->GetReferenceData(0); } void mitk::nnUNetTool::UpdatePrepare() { Superclass::UpdatePrepare(); auto preview = this->GetPreviewSegmentation(); - auto labelset = preview->GetLabelSet(preview->GetActiveLayer()); - labelset->RemoveAllLabels(); + preview->RemoveLabels(preview->GetLabelValuesByGroup(preview->GetActiveLayer())); } namespace { void onPythonProcessEvent(itk::Object * /*pCaller*/, const itk::EventObject &e, void *) { std::string testCOUT; std::string testCERR; const auto *pEvent = dynamic_cast(&e); if (pEvent) { testCOUT = testCOUT + pEvent->GetOutput(); MITK_INFO << testCOUT; } const auto *pErrEvent = dynamic_cast(&e); if (pErrEvent) { testCERR = testCERR + pErrEvent->GetOutput(); MITK_ERROR << testCERR; } } } // namespace void mitk::nnUNetTool::DoUpdatePreview(const Image* inputAtTimeStep, const Image* /*oldSegAtTimeStep*/, LabelSetImage* previewImage, TimeStepType /*timeStep*/) { if (this->GetMitkTempDir().empty()) { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-nnunet-XXXXXX")); } std::string inDir, outDir, inputImagePath, outputImagePath, scriptPath; ProcessExecutor::Pointer spExec = ProcessExecutor::New(); itk::CStyleCommand::Pointer spCommand = itk::CStyleCommand::New(); spCommand->SetCallback(&onPythonProcessEvent); spExec->AddObserver(ExternalProcessOutputEvent(), spCommand); ProcessExecutor::ArgumentListType args; inDir = IOUtil::CreateTemporaryDirectory("nnunet-in-XXXXXX", this->GetMitkTempDir()); std::ofstream tmpStream; inputImagePath = IOUtil::CreateTemporaryFile(tmpStream, m_TEMPLATE_FILENAME, inDir + IOUtil::GetDirectorySeparator()); tmpStream.close(); std::size_t found = inputImagePath.find_last_of(IOUtil::GetDirectorySeparator()); std::string fileName = inputImagePath.substr(found + 1); std::string token = fileName.substr(0, fileName.find("_")); if (this->GetNoPip()) { scriptPath = this->GetnnUNetDirectory() + IOUtil::GetDirectorySeparator() + "nnunet" + IOUtil::GetDirectorySeparator() + "inference" + IOUtil::GetDirectorySeparator() + "predict_simple.py"; } try { if (this->GetMultiModal()) { const std::string fileFormat(".nii.gz"); const std::string fileNamePart("_000_000"); std::string outModalFile; size_t len = inDir.length() + 1 + token.length() + fileNamePart.length() + 1 + fileFormat.length(); outModalFile.reserve(len); // The 1(s) indicates a directory separator char and an underscore. for (size_t i = 0; i < m_OtherModalPaths.size(); ++i) { mitk::Image::ConstPointer modalImage = m_OtherModalPaths[i]; outModalFile.append(inDir); outModalFile.push_back(IOUtil::GetDirectorySeparator()); outModalFile.append(token); outModalFile.append(fileNamePart); outModalFile.append(std::to_string(i)); outModalFile.append(fileFormat); IOUtil::Save(modalImage.GetPointer(), outModalFile); outModalFile.clear(); } } else { IOUtil::Save(inputAtTimeStep, inputImagePath); } } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } // Code calls external process std::string command = "nnUNet_predict"; if (this->GetNoPip()) { #ifdef _WIN32 command = "python"; #else command = "python3"; #endif } for (ModelParams &modelparam : m_ParamQ) { outDir = IOUtil::CreateTemporaryDirectory("nnunet-out-XXXXXX", this->GetMitkTempDir()); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; modelparam.outputDir = outDir; args.clear(); if (this->GetNoPip()) { args.push_back(scriptPath); } args.push_back("-i"); args.push_back(inDir); args.push_back("-o"); args.push_back(outDir); args.push_back("-t"); args.push_back(modelparam.task); if (modelparam.model.find("cascade") != std::string::npos) { args.push_back("-ctr"); } else { args.push_back("-tr"); } args.push_back(modelparam.trainer); args.push_back("-m"); args.push_back(modelparam.model); args.push_back("-p"); args.push_back(modelparam.planId); if (!modelparam.folds.empty()) { args.push_back("-f"); for (auto fold : modelparam.folds) { args.push_back(fold); } } args.push_back("--num_threads_nifti_save"); args.push_back("1"); // fixing to 1 if (!this->GetMirror()) { args.push_back("--disable_tta"); } if (!this->GetMixedPrecision()) { args.push_back("--disable_mixed_precision"); } if (this->GetEnsemble()) { args.push_back("--save_npz"); } try { std::string resultsFolderEnv = "RESULTS_FOLDER=" + this->GetModelDirectory(); itksys::SystemTools::PutEnv(resultsFolderEnv.c_str()); std::string cudaEnv = "CUDA_VISIBLE_DEVICES=" + std::to_string(this->GetGpuId()); itksys::SystemTools::PutEnv(cudaEnv.c_str()); spExec->Execute(this->GetPythonPath(), command, args); } catch (const mitk::Exception &e) { /* Can't throw mitk exception to the caller. Refer: T28691 */ MITK_ERROR << e.GetDescription(); return; } } if (this->GetEnsemble() && !this->GetPostProcessingJsonDirectory().empty()) { args.clear(); command = "nnUNet_ensemble"; outDir = IOUtil::CreateTemporaryDirectory("nnunet-ensemble-out-XXXXXX", this->GetMitkTempDir()); outputImagePath = outDir + IOUtil::GetDirectorySeparator() + token + "_000.nii.gz"; args.push_back("-f"); for (ModelParams &modelparam : m_ParamQ) { args.push_back(modelparam.outputDir); } args.push_back("-o"); args.push_back(outDir); if (!this->GetPostProcessingJsonDirectory().empty()) { args.push_back("-pp"); args.push_back(this->GetPostProcessingJsonDirectory()); } spExec->Execute(this->GetPythonPath(), command, args); } try { Image::Pointer outputImage = IOUtil::Load(outputImagePath); previewImage->InitializeByLabeledImage(outputImage); previewImage->SetGeometry(inputAtTimeStep->GetGeometry()); m_InputBuffer = inputAtTimeStep; m_OutputBuffer = mitk::LabelSetImage::New(); m_OutputBuffer->InitializeByLabeledImage(outputImage); m_OutputBuffer->SetGeometry(inputAtTimeStep->GetGeometry()); } catch (const mitk::Exception &e) { MITK_ERROR << e.GetDescription(); return; } } diff --git a/Modules/Segmentation/cmdapps/ContoursToImage.cpp b/Modules/Segmentation/cmdapps/ContoursToImage.cpp index d6f865c9fa..8d63fca2b1 100644 --- a/Modules/Segmentation/cmdapps/ContoursToImage.cpp +++ b/Modules/Segmentation/cmdapps/ContoursToImage.cpp @@ -1,312 +1,312 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include #include #include #include #include #include enum class OutputFormat { Binary, Label, Multilabel }; void InitializeCommandLineParser(mitkCommandLineParser& parser) { parser.setTitle("Contour to Image Converter"); parser.setCategory("Segmentation"); parser.setDescription("Converts contours (i. e. RTSTRUCT or MITK Contour Model Set) to binary image masks or (multi-)label segmentations."); parser.setContributor("German Cancer Research Center (DKFZ)"); parser.setArgumentPrefix("--", "-"); parser.addArgument("input", "i", mitkCommandLineParser::File, "Input file:", "Input contour(s)", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("reference", "r", mitkCommandLineParser::Image, "Reference image:", "Input reference image", us::Any(), false, false, false, mitkCommandLineParser::Input); parser.addArgument("output", "o", mitkCommandLineParser::Image, "Output file:", "Output image", us::Any(), false, false, false, mitkCommandLineParser::Output); parser.addArgument("format", "f", mitkCommandLineParser::String, "Output format:", "Output format (binary, label, or multilabel)", std::string("binary")); } std::string GetSafeName(const mitk::IPropertyProvider* propertyProvider) { std::string name; if (propertyProvider != nullptr) { name = propertyProvider->GetConstProperty("name")->GetValueAsString(); if (!name.empty()) { boost::trim(name); boost::replace_all(name, "/", "_"); boost::replace_all(name, "\\", "_"); // If you read this, feel free to handle invalid filename characters here. :) } } return name; } void CreateParentDirectories(const std::filesystem::path& path) { if (path.has_parent_path()) { auto parentPath = path.parent_path(); if (!std::filesystem::exists(parentPath)) std::filesystem::create_directories(parentPath); } } bool SetLabelName(const mitk::IPropertyProvider* propertyProvider, mitk::Label* label) { if (propertyProvider != nullptr) { if (auto property = propertyProvider->GetConstProperty("name"); property.IsNotNull()) { if (auto nameProperty = dynamic_cast(property.GetPointer()); nameProperty != nullptr) { if (auto name = nameProperty->GetValueAsString(); !name.empty()) { label->SetName(name); return true; } } } } return false; } bool SetLabelColor(const mitk::IPropertyProvider* propertyProvider, mitk::Label* label) { if (propertyProvider != nullptr) { if (auto property = propertyProvider->GetConstProperty("color"); property.IsNotNull()) { if (auto colorProperty = dynamic_cast(property.GetPointer()); colorProperty != nullptr) { label->SetColor(colorProperty->GetColor()); return true; } } } return false; } void CopyImageToActiveLayerImage(const mitk::Image* image, mitk::LabelSetImage* labelSetImage) { mitk::ImageReadAccessor readAccessor(image); mitk::ImageWriteAccessor writeAccessor(labelSetImage); auto size = sizeof(mitk::Label::PixelType); for (size_t dim = 0; dim < image->GetDimension(); ++dim) size *= image->GetDimension(dim); memcpy(writeAccessor.GetData(), readAccessor.GetData(), size); } OutputFormat ParseOutputFormat(const mitk::IFileIO::Options& args) { auto it = args.find("format"); if (it != args.end()) { auto format = us::any_cast(it->second); if (format == "multilabel") return OutputFormat::Multilabel; if (format == "label") return OutputFormat::Label; if (format != "binary") mitkThrow() << "Unknown output format \"" << format << "\" (must be \"binary\", \"label\" or \"multilabel\")."; } return OutputFormat::Binary; } std::vector FilterValidInputs(const std::vector& inputs) { std::vector validInputs; for (auto input : inputs) { if (input.IsNull()) { MITK_WARN << "Skipping null input."; continue; } auto* validInput = dynamic_cast(input.GetPointer()); if (validInput == nullptr) { MITK_WARN << "Skipping input of type \"" << input->GetNameOfClass() << "\"."; continue; } validInputs.push_back(validInput); } return validInputs; } int main(int argc, char* argv[]) { int returnValue = EXIT_SUCCESS; mitkCommandLineParser parser; InitializeCommandLineParser(parser); auto args = parser.parseArguments(argc, argv); if (args.empty()) return EXIT_FAILURE; try { auto inputFilename = us::any_cast(args["input"]); auto referenceFilename = us::any_cast(args["reference"]); auto outputFilename = us::any_cast(args["output"]); auto format = ParseOutputFormat(args); auto referenceImage = mitk::IOUtil::Load(referenceFilename); auto inputs = FilterValidInputs(mitk::IOUtil::Load(inputFilename)); MITK_INFO << "Found " << inputs.size() << " input contour set(s)"; std::filesystem::path outputPath(outputFilename); CreateParentDirectories(outputPath); mitk::LabelSetImage::Pointer labelSetImage; // Only used for "multilabel" output unsigned int nonameCounter = 0; // Helper variable to generate placeholder names for nameless contour sets for (auto input : inputs) { // If the input file contains multiple contour sets but the output format is not set to "multilabel", // we create separate output files for each contour set. In this case the specified output filename // is used only as a base filename and the names of the individual contour sets are appended accordingly. if (inputs.size() > 1 && format != OutputFormat::Multilabel) { outputPath = outputFilename; auto name = GetSafeName(input); if (name.empty()) name = "nameless_" + std::to_string(nonameCounter++); outputPath.replace_filename(outputPath.stem().string() + '_' + name + outputPath.extension().string()); } // Do the actual conversion from a contour set to an image with a background pixel value of 0. // - For "binary" output, use pixel value 1 and unsigned char as pixel type. // - For "label" output, use pixel value 1 and our label pixel type. // - For "multilabel" output, use the next available label value instead. const auto labelValue = labelSetImage.IsNotNull() - ? static_cast(labelSetImage->GetTotalNumberOfLabels() + 1) + ? labelSetImage->GetUnusedLabelValue() : 1; auto filter = mitk::ContourModelSetToImageFilter::New(); filter->SetMakeOutputLabelPixelType(format != OutputFormat::Binary); filter->SetPaintingPixelValue(labelValue); filter->SetImage(referenceImage); filter->SetInput(input); filter->Update(); mitk::Image::Pointer image = filter->GetOutput(); filter = nullptr; if (image.IsNull()) { MITK_ERROR << "Contour set to image conversion failed without exception. Continue with next contour set... "; returnValue = EXIT_FAILURE; continue; } if (format == OutputFormat::Binary) { mitk::IOUtil::Save(image, outputPath.string()); } else { if (labelSetImage.IsNull()) { labelSetImage = mitk::LabelSetImage::New(); labelSetImage->Initialize(image); CopyImageToActiveLayerImage(image, labelSetImage); } else { labelSetImage->AddLayer(image); } auto label = mitk::LabelSetImageHelper::CreateNewLabel(labelSetImage); label->SetValue(labelValue); SetLabelName(input, label); SetLabelColor(input, label); if (format == OutputFormat::Multilabel) MITK_INFO << "Creating label: " << label->GetName() << " [" << labelValue << ']'; - labelSetImage->GetActiveLabelSet()->AddLabel(label, false); + labelSetImage->AddLabel(label, labelSetImage->GetActiveLayer(), false, false); if (format == OutputFormat::Label) { mitk::IOUtil::Save(labelSetImage, outputPath.string()); labelSetImage = nullptr; } } } // In case of the "multilabel" output format, eventually save the single output file. // For all other output formats, the output file(s) have been saved already while iterating // over the inputs. if (labelSetImage.IsNotNull()) mitk::IOUtil::Save(labelSetImage, outputPath.string()); } catch (const mitk::Exception& e) { MITK_ERROR << e.GetDescription(); return EXIT_FAILURE; } catch (const std::exception& e) { MITK_ERROR << e.what(); return EXIT_FAILURE; } catch (...) { return EXIT_FAILURE; } return returnValue; } diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 5a57291852..60ecdb6613 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,120 +1,118 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkGrowCutSegmentationFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp - #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkSegmentationHelper.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkSegWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCloseRegionTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkEditableContourTool.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkLassoTool.cpp Interactions/mitkContourTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionBaseTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkGrowCutTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkPickingTool.cpp Interactions/mitknnUnetTool.cpp Interactions/mitkProcessExecutor.cpp Interactions/mitkSegmentAnythingProcessExecutor.cpp Interactions/mitkTotalSegmentatorTool.cpp Interactions/mitkSegmentAnythingTool.cpp Interactions/mitkSegmentAnythingPythonService.cpp Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML - Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add.svg Add_Cursor.svg AI.svg AI_Cursor.svg Close.svg Close_Cursor.svg Erase.svg Erase_Cursor.svg Fill.svg Fill_Cursor.svg LiveWire.svg LiveWire_Cursor.svg Lasso.svg GrowCut.svg Lasso_Cursor.svg Otsu.svg Paint.svg Paint_Cursor.svg Picking.svg RegionGrowing.svg RegionGrowing_Cursor.svg Subtract.svg Subtract_Cursor.svg Threshold.svg ULThreshold.svg Wipe.svg Wipe_Cursor.svg Interactions/dummy.xml Interactions/EditableContourTool.xml Interactions/PickingTool.xml Interactions/MouseReleaseOnly.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationConfig.xml Interactions/SegmentationInteraction.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp index 0f4565a621..1e21abc95f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelInspector.cpp @@ -1,1139 +1,1123 @@ /*============================================================================ 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 // mitk #include #include #include // Qmitk #include #include #include #include // Qt #include #include #include #include #include QmitkMultiLabelInspector::QmitkMultiLabelInspector(QWidget* parent/* = nullptr*/) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelInspector) { m_Controls->setupUi(this); m_Model = new QmitkMultiLabelTreeModel(this); m_Controls->view->setModel(m_Model); m_ColorItemDelegate = new QmitkLabelColorItemDelegate(this); auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); m_VisibilityItemDelegate = new QmitkLabelToggleItemDelegate(visibleIcon, invisibleIcon, this); auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); m_LockItemDelegate = new QmitkLabelToggleItemDelegate(lockIcon, unlockIcon, this); auto* view = this->m_Controls->view; view->setItemDelegateForColumn(1, m_LockItemDelegate); view->setItemDelegateForColumn(2, m_ColorItemDelegate); view->setItemDelegateForColumn(3, m_VisibilityItemDelegate); auto* header = view->header(); header->setSectionResizeMode(0,QHeaderView::Stretch); header->setSectionResizeMode(1, QHeaderView::ResizeToContents); header->setSectionResizeMode(2, QHeaderView::ResizeToContents); header->setSectionResizeMode(3, QHeaderView::ResizeToContents); view->setContextMenuPolicy(Qt::CustomContextMenu); connect(m_Model, &QAbstractItemModel::modelReset, this, &QmitkMultiLabelInspector::OnModelReset); connect(view->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)), SLOT(OnChangeModelSelection(const QItemSelection&, const QItemSelection&))); connect(view, &QAbstractItemView::customContextMenuRequested, this, &QmitkMultiLabelInspector::OnContextMenuRequested); connect(view, &QAbstractItemView::doubleClicked, this, &QmitkMultiLabelInspector::OnItemDoubleClicked); } QmitkMultiLabelInspector::~QmitkMultiLabelInspector() { delete m_Controls; } void QmitkMultiLabelInspector::Initialize() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; m_Model->SetSegmentation(m_Segmentation); m_Controls->view->expandAll(); m_LastValidSelectedLabels = {}; //in singel selection mode, if at least one label exist select the first label of the mode. if (m_Segmentation.IsNotNull() && !this->GetMultiSelectionMode() && m_Segmentation->GetTotalNumberOfLabels() > 0) { auto firstIndex = m_Model->FirstLabelInstanceIndex(QModelIndex()); auto labelVariant = firstIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (labelVariant.isValid()) { this->SetSelectedLabel(labelVariant.value()); m_Controls->view->selectionModel()->setCurrentIndex(firstIndex, QItemSelectionModel::NoUpdate); } } } void QmitkMultiLabelInspector::SetMultiSelectionMode(bool multiMode) { m_Controls->view->setSelectionMode(multiMode ? QAbstractItemView::SelectionMode::MultiSelection : QAbstractItemView::SelectionMode::SingleSelection); } bool QmitkMultiLabelInspector::GetMultiSelectionMode() const { return QAbstractItemView::SelectionMode::MultiSelection == m_Controls->view->selectionMode(); } void QmitkMultiLabelInspector::SetAllowVisibilityModification(bool visibilityMod) { m_AllowVisibilityModification = visibilityMod; this->m_Model->SetAllowVisibilityModification(visibilityMod); } void QmitkMultiLabelInspector::SetAllowLabelModification(bool labelMod) { m_AllowLabelModification = labelMod; } bool QmitkMultiLabelInspector::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelInspector::SetAllowLockModification(bool lockMod) { m_AllowLockModification = lockMod; this->m_Model->SetAllowLockModification(lockMod); } bool QmitkMultiLabelInspector::GetAllowLockModification() const { return m_AllowLockModification; } bool QmitkMultiLabelInspector::GetAllowLabelModification() const { return m_AllowLabelModification; } void QmitkMultiLabelInspector::SetDefaultLabelNaming(bool defaultLabelNaming) { m_DefaultLabelNaming = defaultLabelNaming; } void QmitkMultiLabelInspector::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != m_Segmentation) { m_Segmentation = segmentation; this->Initialize(); } } bool QmitkMultiLabelInspector::GetModelManipulationOngoing() const { return m_ModelManipulationOngoing; } void QmitkMultiLabelInspector::OnModelReset() { m_LastValidSelectedLabels = {}; m_ModelManipulationOngoing = false; } bool EqualLabelSelections(const QmitkMultiLabelInspector::LabelValueVectorType& selection1, const QmitkMultiLabelInspector::LabelValueVectorType& selection2) { if (selection1.size() == selection2.size()) { // lambda to compare node pointer inside both lists return std::is_permutation(selection1.begin(), selection1.end(), selection2.begin()); } return false; } void QmitkMultiLabelInspector::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { if (EqualLabelSelections(this->GetSelectedLabels(), selectedLabels)) { return; } this->UpdateSelectionModel(selectedLabels); m_LastValidSelectedLabels = selectedLabels; } void QmitkMultiLabelInspector::UpdateSelectionModel(const LabelValueVectorType& selectedLabels) { // create new selection by retrieving the corresponding indices of the labels QItemSelection newCurrentSelection; for (const auto& labelID : selectedLabels) { QModelIndexList matched = m_Model->match(m_Model->index(0, 0), QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole, QVariant(labelID), 1, Qt::MatchRecursive); if (!matched.empty()) { newCurrentSelection.select(matched.front(), matched.front()); } } m_Controls->view->selectionModel()->select(newCurrentSelection, QItemSelectionModel::ClearAndSelect|QItemSelectionModel::Current); } void QmitkMultiLabelInspector::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->SetSelectedLabels({ selectedLabel }); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabelsFromSelectionModel() const { LabelValueVectorType result; QModelIndexList selectedIndexes = m_Controls->view->selectionModel()->selectedIndexes(); for (const auto& index : std::as_const(selectedIndexes)) { QVariant qvariantDataNode = m_Model->data(index, QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (qvariantDataNode.canConvert()) { result.push_back(qvariantDataNode.value()); } } return result; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetSelectedLabels() const { return m_LastValidSelectedLabels; } mitk::Label* QmitkMultiLabelInspector::GetFirstSelectedLabelObject() const { if (m_LastValidSelectedLabels.empty() || m_Segmentation.IsNull()) return nullptr; return m_Segmentation->GetLabel(m_LastValidSelectedLabels.front()); } void QmitkMultiLabelInspector::OnChangeModelSelection(const QItemSelection& /*selected*/, const QItemSelection& /*deselected*/) { if (!m_ModelManipulationOngoing) { auto internalSelection = GetSelectedLabelsFromSelectionModel(); if (internalSelection.empty()) { //empty selections are not allowed by UI interactions, there should always be at least on label selected. //but selections are e.g. also cleared if the model is updated (e.g. due to addition of labels) UpdateSelectionModel(m_LastValidSelectedLabels); } else { m_LastValidSelectedLabels = internalSelection; emit CurrentSelectionChanged(GetSelectedLabels()); } } } void QmitkMultiLabelInspector::WaitCursorOn() const { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelInspector::WaitCursorOff() const { this->RestoreOverrideCursor(); } void QmitkMultiLabelInspector::RestoreOverrideCursor() const { QApplication::restoreOverrideCursor(); } mitk::Label* QmitkMultiLabelInspector::GetCurrentLabel() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); mitk::Label::Pointer currentIndexLabel = nullptr; if (labelVariant.isValid()) { auto uncastedLabel = labelVariant.value(); currentIndexLabel = static_cast(uncastedLabel); } return currentIndexLabel; } QmitkMultiLabelInspector::IndexLevelType QmitkMultiLabelInspector::GetCurrentLevelType() const { auto currentIndex = this->m_Controls->view->currentIndex(); auto labelInstanceVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceDataRole); auto labelVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelDataRole); if (labelInstanceVariant.isValid() ) { return IndexLevelType::LabelInstance; } else if (labelVariant.isValid()) { return IndexLevelType::LabelClass; } return IndexLevelType::Group; } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetCurrentlyAffactedLabelInstances() const { auto currentIndex = m_Controls->view->currentIndex(); return m_Model->GetLabelsInSubTree(currentIndex); } QmitkMultiLabelInspector::LabelValueVectorType QmitkMultiLabelInspector::GetLabelInstancesOfSelectedFirstLabel() const { if (m_Segmentation.IsNull()) return {}; if (this->GetSelectedLabels().empty()) return {}; const auto index = m_Model->indexOfLabel(this->GetSelectedLabels().front()); return m_Model->GetLabelInstancesOfSameLabelClass(index); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstanceInternal(mitk::Label* templateLabel) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabelInstance."; if (nullptr == templateLabel) mitkThrow() << "QmitkMultiLabelInspector is in an invalid state. AddNewLabelInstanceInternal was called with a non existing label as template"; auto groupID = m_Segmentation->GetGroupIndexOfLabel(templateLabel->GetValue()); - auto group = m_Segmentation->GetLabelSet(groupID); m_ModelManipulationOngoing = true; - auto newLabel = group->AddLabel(templateLabel, true); + auto newLabel = m_Segmentation->AddLabel(templateLabel, groupID, true); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabel->GetValue(); } emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInstance() { auto currentLabel = this->GetFirstSelectedLabelObject(); if (nullptr == currentLabel) return nullptr; return this->AddNewLabelInstanceInternal(currentLabel); } mitk::Label* QmitkMultiLabelInspector::AddNewLabelInternal(const mitk::LabelSetImage::GroupIndexType& containingGroup) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(m_Segmentation); if (!m_DefaultLabelNaming) emit LabelRenameRequested(newLabel, false); - auto group = m_Segmentation->GetLabelSet(containingGroup); - m_ModelManipulationOngoing = true; - group->AddLabel(newLabel, false); + m_Segmentation->AddLabel(newLabel, containingGroup, false); m_ModelManipulationOngoing = false; this->SetSelectedLabel(newLabel->GetValue()); auto index = m_Model->indexOfLabel(newLabel->GetValue()); if (!index.isValid()) mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the " "model after adding it to the segmentation. Label value: " << newLabel->GetValue(); m_Controls->view->expand(index.parent()); emit ModelUpdated(); return newLabel; } mitk::Label* QmitkMultiLabelInspector::AddNewLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } auto currentLabel = this->GetFirstSelectedLabelObject(); mitk::LabelSetImage::GroupIndexType groupID = nullptr != currentLabel ? m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()) : 0; return AddNewLabelInternal(groupID); } void QmitkMultiLabelInspector::DeleteLabelInstance() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInstance."; if (m_Segmentation.IsNull()) return; auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; auto index = m_Model->indexOfLabel(label->GetValue()); auto instanceName = index.data(Qt::DisplayRole); auto question = "Do you really want to delete label instance \"" + instanceName.toString() + "\"?"; auto answer = QMessageBox::question(this, QString("Delete label instances"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) this->DeleteLabelInternal({ label->GetValue() }); } void QmitkMultiLabelInspector::DeleteLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabel."; if (m_Segmentation.IsNull()) return; const auto label = this->GetFirstSelectedLabelObject(); if (nullptr == label) return; const auto relevantLabels = this->GetLabelInstancesOfSelectedFirstLabel(); if (relevantLabels.empty()) return; auto question = "Do you really want to delete label \"" + QString::fromStdString(label->GetName()); question = relevantLabels.size()==1 ? question + "\"?" : question + "\" with all "+QString::number(relevantLabels.size()) +" instances?"; auto answer = QMessageBox::question(this, QString("Delete label"), question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answer == QMessageBox::Yes) this->DeleteLabelInternal(relevantLabels); } void QmitkMultiLabelInspector::DeleteLabelInternal(const LabelValueVectorType& labelValues) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of DeleteLabelInternal."; if (m_Segmentation.IsNull()) { return; } QVariant nextLabelVariant; this->WaitCursorOn(); m_ModelManipulationOngoing = true; for (auto labelValue : labelValues) { if (labelValue == labelValues.back()) { auto currentIndex = m_Model->indexOfLabel(labelValue); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); nextLabelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); } m_Segmentation->RemoveLabel(labelValue); } m_ModelManipulationOngoing = false; this->WaitCursorOff(); if (nextLabelVariant.isValid()) { auto newLabelValue = nextLabelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::Label* QmitkMultiLabelInspector::AddNewGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of AddNewLabel."; if (m_Segmentation.IsNull()) { return nullptr; } mitk::LabelSetImage::GroupIndexType groupID = 0; mitk::Label* newLabel = nullptr; m_ModelManipulationOngoing = true; try { this->WaitCursorOn(); groupID = m_Segmentation->AddLayer(); + m_Segmentation->SetActiveLayer(groupID); this->WaitCursorOff(); newLabel = this->AddNewLabelInternal(groupID); } catch (mitk::Exception& e) { this->WaitCursorOff(); m_ModelManipulationOngoing = false; MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Add group", "Could not add a new group. See error log for details."); } m_ModelManipulationOngoing = false; emit ModelUpdated(); return newLabel; } void QmitkMultiLabelInspector::RemoveGroupInternal(const mitk::LabelSetImage::GroupIndexType& groupID) { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) return; auto currentIndex = m_Model->indexOfGroup(groupID); auto nextIndex = m_Model->ClosestLabelInstanceIndex(currentIndex); auto labelVariant = nextIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); try { this->WaitCursorOn(); m_ModelManipulationOngoing = true; m_Segmentation->RemoveGroup(groupID); m_ModelManipulationOngoing = false; this->WaitCursorOff(); } catch (mitk::Exception& e) { m_ModelManipulationOngoing = false; this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Delete group", "Could not delete the currently active group. See error log for details."); return; } if (labelVariant.isValid()) { auto newLabelValue = labelVariant.value(); this->SetSelectedLabel(newLabelValue); auto index = m_Model->indexOfLabel(newLabelValue); //we have to get index again, because it could have changed due to remove operation. if (index.isValid()) { m_Controls->view->expand(index.parent()); } else { mitkThrow() << "Segmentation or QmitkMultiLabelTreeModel is in an invalid state. Label is not present in the model after adding it to the segmentation. Label value: " << newLabelValue; } } else { this->SetSelectedLabels({}); } emit ModelUpdated(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkMultiLabelInspector::RemoveGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; if (m_Segmentation->GetNumberOfLayers() < 2) { QMessageBox::information(this, "Delete group", "Cannot delete last remaining group. A segmentation must contain at least a single group."); return; } const auto* selectedLabel = this->GetFirstSelectedLabelObject(); if (selectedLabel == nullptr) return; const auto group = m_Segmentation->GetGroupIndexOfLabel(selectedLabel->GetValue()); auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(group); auto answer = QMessageBox::question(this, QStringLiteral("Delete group %1").arg(group), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(group); } void QmitkMultiLabelInspector::OnDeleteGroup() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) return; auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); auto question = QStringLiteral("Do you really want to delete group %1 including all of its labels?").arg(groupID); auto answer = QMessageBox::question(this, QString("Delete group %1").arg(groupID), question, QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes); if (answer != QMessageBox::Yes) return; this->RemoveGroupInternal(groupID); } }; void QmitkMultiLabelInspector::OnContextMenuRequested(const QPoint& /*pos*/) { if (m_Segmentation.IsNull() || !this->isEnabled()) return; const auto indexLevel = this->GetCurrentLevelType(); if (IndexLevelType::Group == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg")), "&Add label", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabel); menu->addAction(addInstanceAction); if (m_Segmentation->GetNumberOfLayers() > 1) { QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg")), "Delete group", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteGroup); menu->addAction(removeAction); } } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock group", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock group", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show group", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide group", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else if (IndexLevelType::LabelClass == indexLevel) { QMenu* menu = new QMenu(this); if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "&Delete label", this); QObject::connect(removeAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnDeleteAffectedLabel); menu->addAction(removeAction); } if (m_AllowLockModification) { menu->addSeparator(); QAction* lockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")), "Lock label instances", this); QObject::connect(lockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnLockAffectedLabels); menu->addAction(lockAllAction); QAction* unlockAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")), "Unlock label instances", this); QObject::connect(unlockAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnUnlockAffectedLabels); menu->addAction(unlockAllAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Show label instances", this); QObject::connect(viewAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsVisible); menu->addAction(viewAllAction); QAction* hideAllAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")), "Hide label instances", this); QObject::connect(hideAllAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible); menu->addAction(hideAllAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr!=opacityAction) menu->addAction(opacityAction); } menu->popup(QCursor::pos()); } else { auto selectedLabelValues = this->GetSelectedLabels(); if (selectedLabelValues.empty()) return; QMenu* menu = new QMenu(this); if (this->GetMultiSelectionMode() && selectedLabelValues.size() > 1) { QAction* mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); menu->addAction(mergeAction); QAction* removeLabelsAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete selected labels", this); QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnDeleteLabels(bool))); menu->addAction(removeLabelsAction); QAction* clearLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear selected labels", this); QObject::connect(clearLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabels(bool))); menu->addAction(clearLabelsAction); } else { if (m_AllowLabelModification) { QAction* addInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg")), "&Add label instance", this); QObject::connect(addInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::OnAddLabelInstance); menu->addAction(addInstanceAction); const auto selectedLabelIndex = m_Model->indexOfLabel(selectedLabelValues.front()); if (m_Model->GetLabelInstancesOfSameLabelClass(selectedLabelIndex).size() > 1) // Only labels that actually appear as instance (having additional instances) { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label instance", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); QAction* removeInstanceAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg")), "&Delete label instance", this); QObject::connect(removeInstanceAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabelInstance); menu->addAction(removeInstanceAction); } else { QAction* renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "&Rename label", this); QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); menu->addAction(renameAction); } QAction* removeLabelAction = new QAction(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg")), "Delete &label", this); QObject::connect(removeLabelAction, &QAction::triggered, this, &QmitkMultiLabelInspector::DeleteLabel); menu->addAction(removeLabelAction); QAction* clearAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "&Clear content", this); QObject::connect(clearAction, SIGNAL(triggered(bool)), this, SLOT(OnClearLabel(bool))); menu->addAction(clearAction); } if (m_AllowVisibilityModification) { menu->addSeparator(); QAction* viewOnlyAction = new QAction(QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")), "Hide everything in group but this", this); QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); menu->addAction(viewOnlyAction); menu->addSeparator(); auto opacityAction = this->CreateOpacityAction(); if (nullptr != opacityAction) menu->addAction(opacityAction); } } menu->popup(QCursor::pos()); } } QWidgetAction* QmitkMultiLabelInspector::CreateOpacityAction() { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); std::vector relevantLabels; if (!relevantLabelValues.empty()) { - //we assume here that all affacted label belong to one group. - auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); - auto group = m_Segmentation->GetLabelSet(groupID); - for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; relevantLabels.emplace_back(label); } auto* opacitySlider = new QSlider; opacitySlider->setMinimum(0); opacitySlider->setMaximum(100); opacitySlider->setOrientation(Qt::Horizontal); auto opacity = relevantLabels.front()->GetOpacity(); opacitySlider->setValue(static_cast(opacity * 100)); auto segmentation = m_Segmentation; - QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, relevantLabels, group](const int value) + QObject::connect(opacitySlider, &QSlider::valueChanged, this, [segmentation, relevantLabels](const int value) { auto opacity = static_cast(value) / 100.0f; for (auto label : relevantLabels) { label->SetOpacity(opacity); - group->UpdateLookupTable(label->GetValue()); + segmentation->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } ); QLabel* opacityLabel = new QLabel("Opacity: "); QVBoxLayout* opacityWidgetLayout = new QVBoxLayout; opacityWidgetLayout->setContentsMargins(4, 4, 4, 4); opacityWidgetLayout->addWidget(opacityLabel); opacityWidgetLayout->addWidget(opacitySlider); QWidget* opacityWidget = new QWidget; opacityWidget->setLayout(opacityWidgetLayout); QWidgetAction* opacityAction = new QWidgetAction(this); opacityAction->setDefaultWidget(opacityWidget); return opacityAction; } return nullptr; } void QmitkMultiLabelInspector::OnClearLabels(bool /*value*/) { QString question = "Do you really want to clear the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Clear selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabels(this->GetSelectedLabels()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnDeleteAffectedLabel() { if (!m_AllowLabelModification) mitkThrow() << "QmitkMultiLabelInspector is configured incorrectly. Set AllowLabelModification to true to allow the usage of RemoveLabel."; if (m_Segmentation.IsNull()) { return; } auto affectedLabels = GetCurrentlyAffactedLabelInstances(); auto currentLabel = m_Segmentation->GetLabel(affectedLabels.front()); QString question = "Do you really want to delete all instances of label \"" + QString::fromStdString(currentLabel->GetName()) + "\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Delete label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->DeleteLabelInternal(affectedLabels); } } void QmitkMultiLabelInspector::OnDeleteLabels(bool /*value*/) { QString question = "Do you really want to remove the selected labels?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->RemoveLabels(this->GetSelectedLabels()); this->WaitCursorOff(); } } void QmitkMultiLabelInspector::OnMergeLabels(bool /*value*/) { auto currentLabel = GetCurrentLabel(); QString question = "Do you really want to merge selected labels into \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question( this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); - m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels(), m_Segmentation->GetActiveLayer()); + m_Segmentation->MergeLabels(currentLabel->GetValue(), this->GetSelectedLabels()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnAddLabel() { auto currentIndex = this->m_Controls->view->currentIndex(); auto groupIDVariant = currentIndex.data(QmitkMultiLabelTreeModel::ItemModelRole::GroupIDRole); if (groupIDVariant.isValid()) { auto groupID = groupIDVariant.value(); this->AddNewLabelInternal(groupID); } } void QmitkMultiLabelInspector::OnAddLabelInstance() { auto currentLabel = this->GetCurrentLabel(); if (nullptr == currentLabel) return; this->AddNewLabelInstanceInternal(currentLabel); } void QmitkMultiLabelInspector::OnClearLabel(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); QString question = "Do you really want to clear the contents of label \"" + QString::fromStdString(currentLabel->GetName())+"\"?"; QMessageBox::StandardButton answerButton = QMessageBox::question(this, "Clear label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); if (answerButton == QMessageBox::Yes) { this->WaitCursorOn(); m_Segmentation->EraseLabel(currentLabel->GetValue()); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnRenameLabel(bool /*value*/) { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); auto currentLabel = this->GetCurrentLabel(); emit LabelRenameRequested(currentLabel, true); - //we assume here that all affacted label belong to one group. - auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); - auto group = m_Segmentation->GetLabelSet(groupID); - for (auto value : relevantLabelValues) { if (value != currentLabel->GetValue()) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetName(currentLabel->GetName()); label->SetColor(currentLabel->GetColor()); - group->UpdateLookupTable(label->GetValue()); + m_Segmentation->UpdateLookupTable(label->GetValue()); mitk::DICOMSegmentationPropertyHelper::SetDICOMSegmentProperties(label); } } emit ModelUpdated(); } void QmitkMultiLabelInspector::SetLockOfAffectedLabels(bool locked) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetLocked(locked); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnUnlockAffectedLabels() { this->SetLockOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnLockAffectedLabels() { this->SetLockOfAffectedLabels(true); } void QmitkMultiLabelInspector::SetVisibilityOfAffectedLabels(bool visible) const { auto relevantLabelValues = this->GetCurrentlyAffactedLabelInstances(); if (!relevantLabelValues.empty()) { - //we assume here that all affacted label belong to one group. - auto groupID = m_Segmentation->GetGroupIndexOfLabel(relevantLabelValues.front()); - auto group = m_Segmentation->GetLabelSet(groupID); - for (auto value : relevantLabelValues) { auto label = this->m_Segmentation->GetLabel(value); if (nullptr == label) mitkThrow() << "Invalid state. Internal model returned a label value that does not exist in segmentation. Invalid value:" << value; label->SetVisible(visible); - group->UpdateLookupTable(label->GetValue()); + m_Segmentation->UpdateLookupTable(label->GetValue()); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelInspector::OnSetAffectedLabelsVisible() { this->SetVisibilityOfAffectedLabels(true); } void QmitkMultiLabelInspector::OnSetAffectedLabelsInvisible() { this->SetVisibilityOfAffectedLabels(false); } void QmitkMultiLabelInspector::OnSetOnlyActiveLabelVisible(bool /*value*/) { auto currentLabel = GetFirstSelectedLabelObject(); const auto labelID = currentLabel->GetValue(); - auto groupID = m_Segmentation->GetGroupIndexOfLabel(currentLabel->GetValue()); - auto group = m_Segmentation->GetLabelSet(groupID); - group->SetAllLabelsVisible(false); + m_Segmentation->SetAllLabelsVisible(false); currentLabel->SetVisible(true); - group->UpdateLookupTable(labelID); + m_Segmentation->UpdateLookupTable(labelID); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::OnItemDoubleClicked(const QModelIndex& index) { if (!index.isValid()) return; if (index.column() > 0) return; auto labelVariant = index.data(QmitkMultiLabelTreeModel::ItemModelRole::LabelInstanceValueRole); if (!labelVariant.isValid()) return; const auto labelID = labelVariant.value(); if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) { this->OnRenameLabel(false); return; } this->PrepareGoToLabel(labelID); } void QmitkMultiLabelInspector::PrepareGoToLabel(mitk::Label::PixelType labelID) const { this->WaitCursorOn(); m_Segmentation->UpdateCenterOfMass(labelID); const auto currentLabel = m_Segmentation->GetLabel(labelID); const mitk::Point3D& pos = currentLabel->GetCenterOfMassCoordinates(); this->WaitCursorOff(); if (pos.GetVnlVector().max_value() > 0.0) { emit GoToLabel(currentLabel->GetValue(), pos); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp index 2030759c82..45efcfa8ea 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.cpp @@ -1,525 +1,536 @@ /*============================================================================ 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 // mitk #include #include #include #include #include #include #include #include #include // Qmitk #include #include // Qt #include #include #include #include #include #include #include #include #include #include #include // itk #include #include QmitkMultiLabelManager::QmitkMultiLabelManager(QWidget *parent) : QWidget(parent), m_Controls(new Ui::QmitkMultiLabelManagerControls), m_Completer(nullptr), m_ProcessingManualSelection(false) { m_Controls->setupUi(this); m_Controls->labelSearchBox->setAlwaysShowClearIcon(true); m_Controls->labelSearchBox->setShowSearchIcon(true); QStringList completionList; completionList << ""; m_Completer = new QCompleter(completionList, this); m_Completer->setCaseSensitivity(Qt::CaseInsensitive); m_Controls->labelSearchBox->setCompleter(m_Completer); m_Controls->labelInspector->SetAllowLabelModification(true); connect(m_Controls->labelSearchBox, SIGNAL(returnPressed()), this, SLOT(OnSearchLabel())); QStringListModel *completeModel = static_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); // See T29549 m_Controls->labelSearchBox->hide(); m_Controls->btnSavePreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); m_Controls->btnLoadPreset->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); m_Controls->btnAddLabel->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add.svg"))); m_Controls->btnAddInstance->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_add_instance.svg"))); m_Controls->btnAddGroup->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_add.svg"))); m_Controls->btnRemoveLabel->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete.svg"))); m_Controls->btnRemoveInstance->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_label_delete_instance.svg"))); m_Controls->btnRemoveGroup->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_group_delete.svg"))); connect(m_Controls->btnAddLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); connect(m_Controls->btnRemoveLabel, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::DeleteLabel); connect(m_Controls->btnAddInstance, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabelInstance); connect(m_Controls->btnRemoveInstance, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::DeleteLabelInstance); connect(m_Controls->btnAddGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewGroup); connect(m_Controls->btnRemoveGroup, &QToolButton::clicked, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::RemoveGroup); connect(m_Controls->btnSavePreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnSavePreset); connect(m_Controls->btnLoadPreset, &QToolButton::clicked, this, &QmitkMultiLabelManager::OnLoadPreset); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::GoToLabel, this, &QmitkMultiLabelManager::OnGoToLabel); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::LabelRenameRequested, this, &QmitkMultiLabelManager::OnLabelRenameRequested); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::CurrentSelectionChanged, this, &QmitkMultiLabelManager::OnSelectedLabelChanged); connect(this->m_Controls->labelInspector, &QmitkMultiLabelInspector::ModelUpdated, this, &QmitkMultiLabelManager::OnModelUpdated); auto* renameLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_R), this); connect(renameLabelShortcut, &QShortcut::activated, this, &QmitkMultiLabelManager::OnRenameLabelShortcutActivated); auto* newLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_A), this); connect(newLabelShortcut, &QShortcut::activated, this->m_Controls->labelInspector, &QmitkMultiLabelInspector::AddNewLabel); this->UpdateControls(); } QmitkMultiLabelManager::~QmitkMultiLabelManager() { this->SetMultiLabelSegmentation(nullptr); delete m_Controls; } QmitkMultiLabelManager::LabelValueVectorType QmitkMultiLabelManager::GetSelectedLabels() const { return m_Controls->labelInspector->GetSelectedLabels(); } void QmitkMultiLabelManager::OnRenameLabelShortcutActivated() { auto selectedLabels = this->GetSelectedLabels(); for (auto labelValue : selectedLabels) { auto currentLabel = this->m_Segmentation->GetLabel(labelValue); emit LabelRenameRequested(currentLabel, true); } } void QmitkMultiLabelManager::OnSelectedLabelChanged(LabelValueVectorType labels) { this->UpdateControls(); if (labels.empty() || labels.size() > 1) return; emit CurrentSelectionChanged(labels); } QStringList &QmitkMultiLabelManager::GetLabelStringList() { return m_LabelStringList; } void QmitkMultiLabelManager::SetDefaultLabelNaming(bool defaultLabelNaming) { this->m_Controls->labelInspector->SetDefaultLabelNaming(defaultLabelNaming); } void QmitkMultiLabelManager::setEnabled(bool enabled) { QWidget::setEnabled(enabled); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabels(const LabelValueVectorType& selectedLabels) { this->m_Controls->labelInspector->SetSelectedLabels(selectedLabels); UpdateControls(); } void QmitkMultiLabelManager::SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel) { this->m_Controls->labelInspector->SetSelectedLabel(selectedLabel); UpdateControls(); } void QmitkMultiLabelManager::SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation) { if (segmentation != this->m_Segmentation.GetPointer()) { this->RemoveSegmentationObserver(); m_Segmentation = segmentation; this->AddSegmentationObserver(); this->m_Controls->labelInspector->SetMultiLabelSegmentation(segmentation); UpdateControls(); } } void QmitkMultiLabelManager::SetDataStorage(mitk::DataStorage *storage) { m_DataStorage = storage; } void QmitkMultiLabelManager::OnSearchLabel() { //std::string text = m_Controls->labelSearchBox->text().toStdString(); //int pixelValue = -1; //int row = -1; //for (int i = 0; i < m_Controls->m_LabelSetTableWidget->rowCount(); ++i) //{ // if (m_Controls->m_LabelSetTableWidget->item(i, 0)->text().toStdString().compare(text) == 0) // { // pixelValue = m_Controls->m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt(); // row = i; // break; // } //} //if (pixelValue == -1) //{ // return; //} //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //QTableWidgetItem *nameItem = m_Controls->m_LabelSetTableWidget->item(row, NAME_COL); //if (!nameItem) //{ // return; //} //m_Controls->m_LabelSetTableWidget->clearSelection(); //m_Controls->m_LabelSetTableWidget->selectRow(row); //m_Controls->m_LabelSetTableWidget->scrollToItem(nameItem); //GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); //this->WaitCursorOn(); //mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); //m_ToolManager->WorkingDataChanged(); //if (pos.GetVnlVector().max_value() > 0.0) //{ // emit goToLabel(pos); //} //else //{ // GetWorkingImage()->UpdateCenterOfMass(pixelValue, GetWorkingImage()->GetActiveLayer()); // mitk::Point3D pos = // GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); // emit goToLabel(pos); //} //this->WaitCursorOff(); } void QmitkMultiLabelManager::UpdateControls() { bool hasWorkingData = m_Segmentation.IsNotNull(); auto labels = this->m_Controls->labelInspector->GetSelectedLabels(); bool hasMultipleInstances = this->m_Controls->labelInspector->GetLabelInstancesOfSelectedFirstLabel().size() > 1; m_Controls->labelSearchBox->setEnabled(hasWorkingData); m_Controls->btnAddGroup->setEnabled(hasWorkingData); m_Controls->btnAddInstance->setEnabled(hasWorkingData && labels.size()==1); m_Controls->btnAddLabel->setEnabled(hasWorkingData); m_Controls->btnLoadPreset->setEnabled(hasWorkingData); m_Controls->btnRemoveGroup->setEnabled(hasWorkingData && !labels.empty() && m_Segmentation->GetNumberOfLayers()>1); m_Controls->btnRemoveLabel->setEnabled(hasWorkingData && !labels.empty()); m_Controls->btnRemoveInstance->setEnabled(hasWorkingData && !labels.empty() && hasMultipleInstances); m_Controls->btnSavePreset->setEnabled(hasWorkingData); if (!hasWorkingData) return; QStringListModel *completeModel = dynamic_cast(m_Completer->model()); completeModel->setStringList(GetLabelStringList()); } void QmitkMultiLabelManager::OnCreateCroppedMask(bool) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); mitk::Image::Pointer maskImage; auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); try { this->WaitCursorOn(); mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); cropFilter->SetInput(this->m_Segmentation->CreateLabelMask(pixelValue)); cropFilter->SetBackgroundValue(0); cropFilter->SetMarginFactor(1.15); cropFilter->Update(); maskImage = cropFilter->GetOutput(); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, this->m_SegmentationNode); } void QmitkMultiLabelManager::OnCreateMask(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::Image::Pointer maskImage; try { this->WaitCursorOn(); maskImage = m_Segmentation->CreateLabelMask(pixelValue); this->WaitCursorOff(); } catch (mitk::Exception &e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } if (maskImage.IsNull()) { QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); return; } mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); std::string name = currentLabel->GetName(); name += "-mask"; maskNode->SetName(name); maskNode->SetData(maskImage); maskNode->SetBoolProperty("binary", true); maskNode->SetBoolProperty("outline binary", true); maskNode->SetBoolProperty("outline binary shadow", true); maskNode->SetFloatProperty("outline width", 2.0); maskNode->SetColor(currentLabel->GetColor()); maskNode->SetOpacity(1.0); m_DataStorage->Add(maskNode, m_SegmentationNode); } void QmitkMultiLabelManager::OnCreateSmoothedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = m_SegmentationNode; surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", m_Segmentation); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", true); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkMultiLabelManager::OnCreateDetailedSurface(bool /*triggered*/) { mitk::ToolManagerProvider::GetInstance()->GetToolManager()->ActivateTool(-1); auto currentLabel = this->m_Segmentation->GetLabel(this->GetSelectedLabels().front()); auto pixelValue = currentLabel->GetValue(); mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); itk::SimpleMemberCommand::Pointer successCommand = itk::SimpleMemberCommand::New(); successCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); itk::SimpleMemberCommand::Pointer errorCommand = itk::SimpleMemberCommand::New(); errorCommand->SetCallbackFunction(this, &QmitkMultiLabelManager::OnThreadedCalculationDone); surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); mitk::DataNode::Pointer groupNode = m_SegmentationNode; surfaceFilter->SetPointerParameter("Group node", groupNode); surfaceFilter->SetPointerParameter("Input", m_Segmentation); surfaceFilter->SetParameter("RequestedLabel", pixelValue); surfaceFilter->SetParameter("Smooth", false); surfaceFilter->SetDataStorage(*m_DataStorage); mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); try { surfaceFilter->StartAlgorithm(); } catch (mitk::Exception &e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::information(this, "Create Surface", "Could not create a surface mesh out of the selected label. See error log for details.\n"); } } void QmitkMultiLabelManager::OnSavePreset() { QmitkSaveMultiLabelPreset(m_Segmentation); } void QmitkMultiLabelManager::OnLoadPreset() { QmitkLoadMultiLabelPreset({ m_Segmentation }); } void QmitkMultiLabelManager::OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) const { emit GoToLabel(label, position); } void QmitkMultiLabelManager::OnLabelRenameRequested(mitk::Label* label, bool rename) const { emit LabelRenameRequested(label, rename); } void QmitkMultiLabelManager::WaitCursorOn() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); } void QmitkMultiLabelManager::WaitCursorOff() { this->RestoreOverrideCursor(); } void QmitkMultiLabelManager::RestoreOverrideCursor() { QApplication::restoreOverrideCursor(); } void QmitkMultiLabelManager::OnThreadedCalculationDone() { mitk::StatusBar::GetInstance()->Clear(); } void QmitkMultiLabelManager::AddSegmentationObserver() { if (this->m_Segmentation.IsNotNull()) { - this->m_Segmentation->AddLabelAddedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnLabelEvent)); - this->m_Segmentation->AddLabelModifiedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnLabelEvent)); - this->m_Segmentation->AddLabelRemovedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnLabelEvent)); - this->m_Segmentation->AddGroupAddedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnGroupEvent)); - this->m_Segmentation->AddGroupModifiedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnGroupEvent)); - this->m_Segmentation->AddGroupRemovedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnGroupEvent)); + auto& widget = *this; + m_LabelAddedObserver.Reset(m_Segmentation, mitk::LabelAddedEvent(), [&widget](const itk::EventObject& event) + { + auto labelEvent = dynamic_cast(&event); + widget.OnLabelEvent(labelEvent->GetLabelValue()); + }); + m_LabelModifiedObserver.Reset(m_Segmentation, mitk::LabelModifiedEvent(), [&widget](const itk::EventObject& event) + { + auto labelEvent = dynamic_cast(&event); + widget.OnLabelEvent(labelEvent->GetLabelValue()); + }); + m_LabelRemovedObserver.Reset(m_Segmentation, mitk::LabelRemovedEvent(), [&widget](const itk::EventObject& event) + { + auto labelEvent = dynamic_cast(&event); + widget.OnLabelEvent(labelEvent->GetLabelValue()); + }); + + m_GroupAddedObserver.Reset(m_Segmentation, mitk::GroupAddedEvent(), [&widget](const itk::EventObject& event) + { + auto groupEvent = dynamic_cast(&event); + widget.OnGroupEvent(groupEvent->GetGroupID()); + }); + m_GroupModifiedObserver.Reset(m_Segmentation, mitk::GroupModifiedEvent(), [&widget](const itk::EventObject& event) + { + auto groupEvent = dynamic_cast(&event); + widget.OnGroupEvent(groupEvent->GetGroupID()); + }); + m_GroupRemovedObserver.Reset(m_Segmentation, mitk::GroupRemovedEvent(), [&widget](const itk::EventObject& event) + { + auto groupEvent = dynamic_cast(&event); + widget.OnGroupEvent(groupEvent->GetGroupID()); + }); } } void QmitkMultiLabelManager::RemoveSegmentationObserver() { - if (this->m_Segmentation.IsNotNull()) - { - this->m_Segmentation->RemoveLabelAddedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnLabelEvent)); - this->m_Segmentation->RemoveLabelModifiedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnLabelEvent)); - this->m_Segmentation->RemoveLabelRemovedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnLabelEvent)); - this->m_Segmentation->RemoveGroupAddedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnGroupEvent)); - this->m_Segmentation->RemoveGroupModifiedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnGroupEvent)); - this->m_Segmentation->RemoveGroupRemovedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelManager::OnGroupEvent)); - } + m_LabelAddedObserver.Reset(); + m_LabelModifiedObserver.Reset(); + m_LabelRemovedObserver.Reset(); + m_GroupAddedObserver.Reset(); + m_GroupModifiedObserver.Reset(); + m_GroupRemovedObserver.Reset(); } void QmitkMultiLabelManager::OnLabelEvent(mitk::LabelSetImage::LabelValueType /*labelValue*/) { if (!m_Controls->labelInspector->GetModelManipulationOngoing()) this->UpdateControls(); } void QmitkMultiLabelManager::OnGroupEvent(mitk::LabelSetImage::GroupIndexType /*groupIndex*/) { if (!m_Controls->labelInspector->GetModelManipulationOngoing()) this->UpdateControls(); } void QmitkMultiLabelManager::OnModelUpdated() { this->UpdateControls(); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h index 685ce703d1..4eae48e9f2 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelManager.h @@ -1,176 +1,183 @@ /*============================================================================ 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 QmitkMultiLabelManager_h #define QmitkMultiLabelManager_h #include #include #include #include +#include #include class QmitkDataStorageComboBox; class QCompleter; namespace Ui { class QmitkMultiLabelManagerControls; } namespace mitk { class DataStorage; } class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelManager : public QWidget { Q_OBJECT public: explicit QmitkMultiLabelManager(QWidget *parent = nullptr); ~QmitkMultiLabelManager() override; using LabelValueVectorType = mitk::LabelSetImage::LabelValueVectorType; /** * @brief Retrieve the currently selected labels (equals the last CurrentSelectionChanged values). */ LabelValueVectorType GetSelectedLabels() const; Q_SIGNALS: /** * @brief A signal that will be emitted if the selected labels change. * * @param labels A list of label values that are now selected. */ void CurrentSelectionChanged(LabelValueVectorType labels); /** * @brief A signal that will be emitted if the user has requested to "go to" a certain label. * * Going to a label would be e.g. to focus the renderwindows on the centroid of the label. * @param label The label that should be focused. * @param point in World coordinate that should be focused. */ void GoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& point) const; /** @brief Signal that is emitted, if a label should be (re)named and default * label naming is deactivated. * * The instance for which a new name is requested is passed with the signal. * @param label Pointer to the instance that needs a (new) name. * @param rename Indicates if it is a renaming or naming of a new label. */ void LabelRenameRequested(mitk::Label* label, bool rename) const; public Q_SLOTS: /** * @brief Transform a list label values into a model selection and set this as a new selection of the view * * @param selectedLabels A list of data nodes that should be newly selected. */ void SetSelectedLabels(const LabelValueVectorType& selectedLabels); /** * @brief Selects the passed label instance and sets a new selection of the view * * @param selectedLabel Value of the label instance that should be selected. */ void SetSelectedLabel(mitk::LabelSetImage::LabelValueType selectedLabel); /** * @brief Sets the segmentation that will be used /monitored by the widget. * * @param segmentation A pointer to the segmentation to set. */ void SetMultiLabelSegmentation(mitk::LabelSetImage* segmentation); void SetDataStorage(mitk::DataStorage *storage); void UpdateControls(); virtual void setEnabled(bool enabled); QStringList &GetLabelStringList(); void SetDefaultLabelNaming(bool defaultLabelNaming); private Q_SLOTS: // LabelSet dependent void OnRenameLabelShortcutActivated(); // reaction to "returnPressed" signal from ... void OnSearchLabel(); // reaction to the change of labels. If multiple labels are selected, it is ignored. void OnSelectedLabelChanged(LabelValueVectorType labels); // LabelSetImage Dependet void OnCreateDetailedSurface(bool); void OnCreateSmoothedSurface(bool); // reaction to the signal "createMask" from QmitkLabelSetTableWidget void OnCreateMask(bool); // reaction to the signal "createCroppedMask" from QmitkLabelSetTableWidget void OnCreateCroppedMask(bool); void OnSavePreset(); void OnLoadPreset(); void OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D& position) const; void OnLabelRenameRequested(mitk::Label* label, bool rename) const; void OnModelUpdated(); private: enum TableColumns { NAME_COL = 0, LOCKED_COL, COLOR_COL, VISIBLE_COL }; void WaitCursorOn(); void WaitCursorOff(); void RestoreOverrideCursor(); void OnThreadedCalculationDone(); void AddSegmentationObserver(); void RemoveSegmentationObserver(); void OnLabelEvent(mitk::LabelSetImage::LabelValueType labelValue); void OnGroupEvent(mitk::LabelSetImage::GroupIndexType groupIndex); Ui::QmitkMultiLabelManagerControls* m_Controls; QCompleter *m_Completer; QStringList m_OrganColors; QStringList m_LabelStringList; bool m_ProcessingManualSelection; mitk::LabelSetImage::Pointer m_Segmentation; mitk::DataNode::Pointer m_SegmentationNode; mitk::DataStorage* m_DataStorage; + mitk::ITKEventObserverGuard m_LabelAddedObserver; + mitk::ITKEventObserverGuard m_LabelModifiedObserver; + mitk::ITKEventObserverGuard m_LabelRemovedObserver; + mitk::ITKEventObserverGuard m_GroupAddedObserver; + mitk::ITKEventObserverGuard m_GroupModifiedObserver; + mitk::ITKEventObserverGuard m_GroupRemovedObserver; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp index 8486ea1dbe..004cd80513 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp @@ -1,149 +1,149 @@ /*============================================================================ 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 "QmitkMultiLabelSegWithPreviewToolGUIBase.h" #include "mitkSegWithPreviewTool.h" #include #include QmitkMultiLabelSegWithPreviewToolGUIBase::QmitkMultiLabelSegWithPreviewToolGUIBase() : QmitkSegWithPreviewToolGUIBase(false) { auto enableMLSelectedDelegate = [this](bool enabled) { auto tool = this->GetConnectedToolAs(); return nullptr != tool ? (tool->GetLabelTransferScope() == mitk::SegWithPreviewTool::LabelTransferScope::AllLabels || !tool->GetSelectedLabels().empty()) && enabled : false; }; m_EnableConfirmSegBtnFnc = enableMLSelectedDelegate; } void QmitkMultiLabelSegWithPreviewToolGUIBase::InitializeUI(QBoxLayout* mainLayout) { auto radioTransferAll = new QRadioButton("Transfer all labels", this); radioTransferAll->setToolTip("Transfer all preview labels when confirmed."); radioTransferAll->setChecked(true); connect(radioTransferAll, &QAbstractButton::toggled, this, &QmitkMultiLabelSegWithPreviewToolGUIBase::OnRadioTransferAllClicked); mainLayout->addWidget(radioTransferAll); m_RadioTransferAll = radioTransferAll; auto radioTransferSelected = new QRadioButton("Transfer selected labels", this); radioTransferSelected->setToolTip("Transfer the selected preview labels when confirmed."); radioTransferSelected->setChecked(false); mainLayout->addWidget(radioTransferSelected); m_RadioTransferSelected = radioTransferSelected; m_LabelSelectionList = new QmitkSimpleLabelSetListWidget(this); m_LabelSelectionList->setObjectName(QString::fromUtf8("m_LabelSelectionList")); QSizePolicy sizePolicy2(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); sizePolicy2.setHorizontalStretch(0); sizePolicy2.setVerticalStretch(0); sizePolicy2.setHeightForWidth(m_LabelSelectionList->sizePolicy().hasHeightForWidth()); m_LabelSelectionList->setSizePolicy(sizePolicy2); m_LabelSelectionList->setMaximumSize(QSize(10000000, 10000000)); m_LabelSelectionList->setVisible(false); mainLayout->addWidget(m_LabelSelectionList); connect(m_LabelSelectionList, &QmitkSimpleLabelSetListWidget::SelectedLabelsChanged, this, &QmitkMultiLabelSegWithPreviewToolGUIBase::OnLabelSelectionChanged); this->OnRadioTransferAllClicked(true); Superclass::InitializeUI(mainLayout); } void QmitkMultiLabelSegWithPreviewToolGUIBase::OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels) { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { mitk::SegWithPreviewTool::SelectedLabelVectorType labelIDs; for (const auto& label : selectedLabels) { labelIDs.push_back(label->GetValue()); } tool->SetSelectedLabels(labelIDs); this->ActualizePreviewLabelVisibility(); this->EnableWidgets(true); //used to actualize the ConfirmSeg btn via the delegate; } } void QmitkMultiLabelSegWithPreviewToolGUIBase::ActualizePreviewLabelVisibility() { auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { auto preview = tool->GetPreviewSegmentation(); if (nullptr != preview) { - auto labelSet = preview->GetActiveLabelSet(); + auto labels = preview->GetLabelsByValue(preview->GetLabelValuesByGroup(preview->GetActiveLayer())); auto selectedLabels = tool->GetSelectedLabels(); - for (auto labelIter = labelSet->IteratorBegin(); labelIter != labelSet->IteratorEnd(); ++labelIter) + for (auto label : labels) { bool isVisible = tool->GetLabelTransferScope() == mitk::SegWithPreviewTool::LabelTransferScope::AllLabels - || (std::find(selectedLabels.begin(), selectedLabels.end(), labelIter->second->GetValue()) != selectedLabels.end()); - labelIter->second->SetVisible(isVisible); - labelSet->UpdateLookupTable(labelIter->second->GetValue()); + || (std::find(selectedLabels.begin(), selectedLabels.end(), label->GetValue()) != selectedLabels.end()); + label->SetVisible(isVisible); + preview->UpdateLookupTable(label->GetValue()); } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelSegWithPreviewToolGUIBase::OnRadioTransferAllClicked(bool checked) { m_LabelSelectionList->setVisible(!checked); auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { if (checked) { tool->SetLabelTransferScope(mitk::SegWithPreviewTool::LabelTransferScope::AllLabels); } else { tool->SetLabelTransferScope(mitk::SegWithPreviewTool::LabelTransferScope::SelectedLabels); } } this->ActualizePreviewLabelVisibility(); } void QmitkMultiLabelSegWithPreviewToolGUIBase::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); if (nullptr != m_LabelSelectionList) { m_LabelSelectionList->setEnabled(enabled); } if (nullptr != m_RadioTransferAll) { m_RadioTransferAll->setEnabled(enabled); } if (nullptr != m_RadioTransferSelected) { m_RadioTransferSelected->setEnabled(enabled); } } void QmitkMultiLabelSegWithPreviewToolGUIBase::SetLabelSetPreview(const mitk::LabelSetImage* preview) { if (nullptr != m_LabelSelectionList) { m_LabelSelectionList->SetLabelSetImage(preview); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp index bbf9702b83..574f44d36f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.cpp @@ -1,1021 +1,1024 @@ /*============================================================================ 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 "QmitkMultiLabelTreeModel.h" -#include "mitkRenderingManager.h" +#include +#include -#include "QmitkStyleManager.h" +#include class QmitkMultiLabelSegTreeItem { public: enum class ItemType { Group, Label, Instance }; QmitkMultiLabelSegTreeItem() { }; explicit QmitkMultiLabelSegTreeItem(ItemType type, QmitkMultiLabelSegTreeItem* parentItem, mitk::Label* label = nullptr, std::string className = ""): m_parentItem(parentItem), m_ItemType(type), m_Label(label), m_ClassName(className) { }; ~QmitkMultiLabelSegTreeItem() { for (auto item : m_childItems) { delete item; } }; void AppendChild(QmitkMultiLabelSegTreeItem* child) { m_childItems.push_back(child); }; void RemoveChild(std::size_t row) { if (row < m_childItems.size()) { delete m_childItems[row]; m_childItems.erase(m_childItems.begin() + row); } }; int Row() const { if (m_parentItem) { auto finding = std::find(m_parentItem->m_childItems.begin(), m_parentItem->m_childItems.end(), this); if (finding != m_parentItem->m_childItems.end()) { return std::distance(m_parentItem->m_childItems.begin(), finding); } } return 0; }; QmitkMultiLabelSegTreeItem* ParentItem() { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* ParentItem() const { return m_parentItem; }; const QmitkMultiLabelSegTreeItem* NextSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row + 1 < m_parentItem->m_childItems.size()) return m_parentItem->m_childItems[row+1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* PrevSibblingItem() const { if (m_parentItem) { const std::vector::size_type row = this->Row(); if (row > 0) return m_parentItem->m_childItems[row-1]; } return nullptr; }; const QmitkMultiLabelSegTreeItem* RootItem() const { auto item = this; while (item->m_parentItem != nullptr) { item = item->m_parentItem; } return item; }; std::size_t GetGroupID() const { auto root = this->RootItem(); auto item = this; if (root == this) return 0; while (root != item->m_parentItem) { item = item->m_parentItem; } auto iter = std::find(root->m_childItems.begin(), root->m_childItems.end(), item); if (root->m_childItems.end() == iter) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Root does not have an currentItem as child that has root as parent."; return std::distance(root->m_childItems.begin(), iter); } bool HandleAsInstance() const { return (ItemType::Instance == m_ItemType) || ((ItemType::Label == m_ItemType) && (m_childItems.size() == 1)); } mitk::Label* GetLabel() const { if (ItemType::Instance == m_ItemType) { return m_Label; } if (ItemType::Label == m_ItemType) { if (m_childItems.empty()) mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Internal label currentItem has no instance currentItem."; return m_childItems[0]->GetLabel(); } return nullptr; }; mitk::LabelSetImage::LabelValueType GetLabelValue() const { auto label = this->GetLabel(); if (nullptr == label) { mitkThrow() << "Invalid internal state of QmitkMultiLabelTreeModel. Called GetLabelValue on an group currentItem."; } return label->GetValue(); }; /** returns a vector containing all label values of referenced by this item or its child items.*/ std::vector< mitk::LabelSetImage::LabelValueType> GetLabelsInSubTree() const { if (this->m_ItemType == ItemType::Instance) { return { this->GetLabelValue() }; } std::vector< mitk::LabelSetImage::LabelValueType> result; for (const auto child : this->m_childItems) { auto childresult = child->GetLabelsInSubTree(); result.reserve(result.size() + childresult.size()); result.insert(result.end(), childresult.begin(), childresult.end()); } return result; } std::vector m_childItems; QmitkMultiLabelSegTreeItem* m_parentItem = nullptr; ItemType m_ItemType = ItemType::Group; mitk::Label::Pointer m_Label; std::string m_ClassName; }; QModelIndex GetIndexByItem(const QmitkMultiLabelSegTreeItem* start, const QmitkMultiLabelTreeModel* model) { QModelIndex parentIndex = QModelIndex(); if (nullptr != start->m_parentItem) { parentIndex = GetIndexByItem(start->m_parentItem, model); } else { return parentIndex; } return model->index(start->Row(), 0, parentIndex); } QmitkMultiLabelSegTreeItem* GetGroupItem(QmitkMultiLabelTreeModel::GroupIndexType groupIndex, QmitkMultiLabelSegTreeItem* root) { if (nullptr != root && groupIndex < root->m_childItems.size()) { return root->m_childItems[groupIndex]; } return nullptr; } QmitkMultiLabelSegTreeItem* GetInstanceItem(QmitkMultiLabelTreeModel::LabelValueType labelValue, QmitkMultiLabelSegTreeItem* root) { QmitkMultiLabelSegTreeItem* result = nullptr; for (auto item : root->m_childItems) { result = GetInstanceItem(labelValue, item); if (nullptr != result) return result; } if (root->m_ItemType == QmitkMultiLabelSegTreeItem::ItemType::Instance && root->GetLabelValue() == labelValue) { return root; } return nullptr; } const QmitkMultiLabelSegTreeItem* GetFirstInstanceLikeItem(const QmitkMultiLabelSegTreeItem* startItem) { const QmitkMultiLabelSegTreeItem* result = nullptr; if (nullptr != startItem) { if (startItem->HandleAsInstance()) { result = startItem; } else if (!startItem->m_childItems.empty()) { result = GetFirstInstanceLikeItem(startItem->m_childItems.front()); } } return result; } QmitkMultiLabelSegTreeItem* GetLabelItemInGroup(const std::string& labelName, QmitkMultiLabelSegTreeItem* group) { if (nullptr != group) { auto predicate = [labelName](const QmitkMultiLabelSegTreeItem* item) { return labelName == item->m_ClassName; }; auto finding = std::find_if(group->m_childItems.begin(), group->m_childItems.end(), predicate); if (group->m_childItems.end() != finding) { return *finding; } } return nullptr; } QmitkMultiLabelTreeModel::QmitkMultiLabelTreeModel(QObject *parent) : QAbstractItemModel(parent) -, m_Observed(false) { m_RootItem = std::make_unique(); } QmitkMultiLabelTreeModel ::~QmitkMultiLabelTreeModel() { this->SetSegmentation(nullptr); }; int QmitkMultiLabelTreeModel::columnCount(const QModelIndex& /*parent*/) const { return 4; } int QmitkMultiLabelTreeModel::rowCount(const QModelIndex &parent) const { if (parent.column() > 0) return 0; if (m_Segmentation.IsNull()) return 0; QmitkMultiLabelSegTreeItem* parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); if (parentItem->HandleAsInstance()) { return 0; } return parentItem->m_childItems.size(); } QVariant QmitkMultiLabelTreeModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) return QVariant(); auto item = static_cast(index.internalPointer()); if (!item) return QVariant(); if (role == Qt::DisplayRole||role == Qt::EditRole) { if (TableColumns::NAME_COL == index.column()) { switch (item->m_ItemType) { case QmitkMultiLabelSegTreeItem::ItemType::Group: return QVariant(QString("Group %1").arg(item->GetGroupID())); case QmitkMultiLabelSegTreeItem::ItemType::Label: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; QString name = QString::fromStdString(label->GetName()); if (!item->HandleAsInstance()) name = name + QString(" (%1 instances)").arg(item->m_childItems.size()); return QVariant(name); } case QmitkMultiLabelSegTreeItem::ItemType::Instance: { auto label = item->GetLabel(); if (nullptr == label) mitkThrow() << "Invalid internal state. QmitkMultiLabelTreeModel currentItem is refering to a label that does not exist."; return QVariant(QString::fromStdString(label->GetName()) + QString(" [%1]").arg(item->GetLabelValue())); } } } else { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { return QVariant(label->GetLocked()); } else if (TableColumns::COLOR_COL == index.column()) { return QVariant(QColor(label->GetColor().GetRed() * 255, label->GetColor().GetGreen() * 255, label->GetColor().GetBlue() * 255)); } else if (TableColumns::VISIBLE_COL == index.column()) { return QVariant(label->GetVisible()); } } } } else if (role == ItemModelRole::LabelDataRole) { auto label = item->GetLabel(); if (nullptr!=label) return QVariant::fromValue(label); } else if (role == ItemModelRole::LabelValueRole) { auto label = item->GetLabel(); if (nullptr != label) return QVariant(label->GetValue()); } else if (role == ItemModelRole::LabelInstanceDataRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant::fromValue(label); } } else if (role == ItemModelRole::LabelInstanceValueRole) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); return QVariant(label->GetValue()); } } else if (role == ItemModelRole::GroupIDRole) { QVariant v; v.setValue(item->GetGroupID()); return v; } return QVariant(); } mitk::Color QtToMitk(const QColor& color) { mitk::Color mitkColor; mitkColor.SetRed(color.red() / 255.0f); mitkColor.SetGreen(color.green() / 255.0f); mitkColor.SetBlue(color.blue() / 255.0f); return mitkColor; } bool QmitkMultiLabelTreeModel::setData(const QModelIndex& index, const QVariant& value, int role) { if (!index.isValid()) return false; auto item = static_cast(index.internalPointer()); if (!item) return false; if (role == Qt::EditRole) { if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance()) { auto label = item->GetLabel(); if (TableColumns::LOCKED_COL == index.column()) { label->SetLocked(value.toBool()); } else if (TableColumns::COLOR_COL == index.column()) { label->SetColor(QtToMitk(value.value())); } else if (TableColumns::VISIBLE_COL == index.column()) { label->SetVisible(value.toBool()); } - auto groupID = m_Segmentation->GetGroupIndexOfLabel(label->GetValue()); - m_Segmentation->GetLabelSet(groupID)->UpdateLookupTable(label->GetValue()); + m_Segmentation->UpdateLookupTable(label->GetValue()); m_Segmentation->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { } return true; } } return false; } QModelIndex QmitkMultiLabelTreeModel::index(int row, int column, const QModelIndex &parent) const { if (!hasIndex(row, column, parent)) return QModelIndex(); auto parentItem = m_RootItem.get(); if (parent.isValid()) parentItem = static_cast(parent.internalPointer()); QmitkMultiLabelSegTreeItem *childItem = parentItem->m_childItems[row]; if (childItem) return createIndex(row, column, childItem); else return QModelIndex(); } QModelIndex QmitkMultiLabelTreeModel::indexOfLabel(mitk::Label::PixelType labelValue) const { - if (labelValue == mitk::LabelSetImage::UnlabeledValue) return QModelIndex(); + if (labelValue == mitk::LabelSetImage::UNLABELED_VALUE) return QModelIndex(); auto relevantItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == relevantItem) return QModelIndex(); auto labelItem = relevantItem->ParentItem(); if (labelItem->m_childItems.size() == 1) { //was the only instance of the label, therefor return the label item instat. relevantItem = labelItem; } return GetIndexByItem(relevantItem, this); } QModelIndex QmitkMultiLabelTreeModel::indexOfGroup(mitk::LabelSetImage::GroupIndexType groupIndex) const { auto relevantItem = GetGroupItem(groupIndex, this->m_RootItem.get()); if (nullptr == relevantItem) QModelIndex(); return GetIndexByItem(relevantItem, this); } QModelIndex QmitkMultiLabelTreeModel::parent(const QModelIndex &child) const { if (!child.isValid()) return QModelIndex(); QmitkMultiLabelSegTreeItem *childItem = static_cast(child.internalPointer()); QmitkMultiLabelSegTreeItem *parentItem = childItem->ParentItem(); if (parentItem == m_RootItem.get()) return QModelIndex(); return createIndex(parentItem->Row(), 0, parentItem); } QModelIndex QmitkMultiLabelTreeModel::ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const { if (!currentIndex.isValid()) return QModelIndex(); auto currentItem = static_cast(currentIndex.internalPointer()); if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; auto searchItem = currentItem; const auto rootItem = currentItem->RootItem(); while (searchItem != rootItem) { const auto* sibling = searchItem; while (sibling != nullptr) { sibling = sibling->NextSibblingItem(); resultItem = GetFirstInstanceLikeItem(sibling); if (nullptr != resultItem) break; } if (nullptr != resultItem) break; // No next closest label instance on this level -> check for closest before sibling = searchItem; while (sibling != nullptr) { sibling = sibling->PrevSibblingItem(); resultItem = GetFirstInstanceLikeItem(sibling); if (nullptr != resultItem) break; } if (nullptr != resultItem) break; // No closest label instance before current on this level -> moeve one level up searchItem = searchItem->ParentItem(); } if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } QModelIndex QmitkMultiLabelTreeModel::FirstLabelInstanceIndex(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return QModelIndex(); if (currentItem->RootItem() != this->m_RootItem.get()) mitkThrow() << "Invalid call. Passed currentIndex does not seem to be a valid index of this model. It is either outdated or from another model."; const QmitkMultiLabelSegTreeItem* resultItem = nullptr; resultItem = GetFirstInstanceLikeItem(currentItem); if (nullptr == resultItem) return QModelIndex(); return GetIndexByItem(resultItem, this); } ///** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance //or instance node). If current index is at the end, an invalid index is returned.*/ //QModelIndex QmitkMultiLabelTreeModel::PrevLabelInstanceIndex(const QModelIndex& currentIndex) const; std::vector QmitkMultiLabelTreeModel::GetLabelsInSubTree(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (!currentIndex.isValid()) { currentItem = this->m_RootItem.get(); } else { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; return currentItem->GetLabelsInSubTree(); } std::vector QmitkMultiLabelTreeModel::GetLabelInstancesOfSameLabelClass(const QModelIndex& currentIndex) const { const QmitkMultiLabelSegTreeItem* currentItem = nullptr; if (currentIndex.isValid()) { currentItem = static_cast(currentIndex.internalPointer()); } if (!currentItem) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Group == currentItem->m_ItemType) return {}; if (QmitkMultiLabelSegTreeItem::ItemType::Instance == currentItem->m_ItemType) currentItem = currentItem->ParentItem(); return currentItem->GetLabelsInSubTree(); } Qt::ItemFlags QmitkMultiLabelTreeModel::flags(const QModelIndex &index) const { if (!index.isValid()) return Qt::NoItemFlags; if (!index.isValid()) return Qt::NoItemFlags; auto item = static_cast(index.internalPointer()); if (!item) return Qt::NoItemFlags; if (TableColumns::NAME_COL != index.column()) { if (item->HandleAsInstance() && ((TableColumns::VISIBLE_COL == index.column() && m_AllowVisibilityModification) || (TableColumns::COLOR_COL == index.column() && m_AllowVisibilityModification) || //m_AllowVisibilityModification controls visibility and color (TableColumns::LOCKED_COL == index.column() && m_AllowLockModification))) { return Qt::ItemIsEnabled | Qt::ItemIsEditable; } else { return Qt::ItemIsEnabled; } } else { if (item->HandleAsInstance()) { return Qt::ItemIsEnabled | Qt::ItemIsSelectable; } else { return Qt::ItemIsEnabled; } } return Qt::NoItemFlags; } QVariant QmitkMultiLabelTreeModel::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole == role) && (Qt::Horizontal == orientation)) { if (TableColumns::NAME_COL == section) { return "Name"; } else if (TableColumns::LOCKED_COL == section) { return "Locked"; } else if (TableColumns::COLOR_COL == section) { return "Color"; } else if (TableColumns::VISIBLE_COL == section) { return "Visibility"; } } return QVariant(); } const mitk::LabelSetImage* QmitkMultiLabelTreeModel::GetSegmentation() const { return m_Segmentation; } void QmitkMultiLabelTreeModel::SetSegmentation(mitk::LabelSetImage* segmentation) { if (m_Segmentation != segmentation) { - this->RemoveObserver(); this->m_Segmentation = segmentation; this->AddObserver(); this->UpdateInternalTree(); } } /**Helper function that adds a labek into the item tree. Passes back the new created instance iten*/ QmitkMultiLabelSegTreeItem* AddLabelToGroupTree(mitk::Label* label, QmitkMultiLabelSegTreeItem* groupItem, bool& newLabelItemCreated) { if (nullptr == groupItem) return nullptr; if (nullptr == label) return nullptr; newLabelItemCreated = false; std::set labelNames; for (auto labelItem : groupItem->m_childItems) { labelNames.emplace(labelItem->GetLabel()->GetName()); } QmitkMultiLabelSegTreeItem* labelItem = nullptr; auto finding = labelNames.find(label->GetName()); if (finding != labelNames.end()) { //other label with same name exists labelItem = groupItem->m_childItems[std::distance(labelNames.begin(), finding)]; } else { newLabelItemCreated = true; labelItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Label, groupItem, nullptr, label->GetName()); auto predicate = [label](const std::string& name) { return name > label->GetName(); }; auto insertFinding = std::find_if(labelNames.begin(), labelNames.end(), predicate); groupItem->m_childItems.insert(groupItem->m_childItems.begin() + std::distance(labelNames.begin(), insertFinding), labelItem); } auto instanceItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Instance, labelItem, label); auto predicate = [label](const QmitkMultiLabelSegTreeItem* item) { return item->GetLabelValue() > label->GetValue(); }; auto insertFinding = std::find_if(labelItem->m_childItems.begin(), labelItem->m_childItems.end(), predicate); labelItem->m_childItems.insert(labelItem->m_childItems.begin() + std::distance(labelItem->m_childItems.begin(), insertFinding), instanceItem); return instanceItem; } void QmitkMultiLabelTreeModel::GenerateInternalGroupTree(unsigned int groupID, QmitkMultiLabelSegTreeItem* groupItem) { - auto labelSet = m_Segmentation->GetLabelSet(groupID); + auto labels = m_Segmentation->GetLabelsByValue(m_Segmentation->GetLabelValuesByGroup(groupID)); - for (auto lIter = labelSet->IteratorConstBegin(); lIter != labelSet->IteratorConstEnd(); lIter++) + for (auto& label : labels) { - if (lIter->first== mitk::LabelSetImage::UnlabeledValue) continue; + if (label->GetValue()== mitk::LabelSetImage::UNLABELED_VALUE) continue; bool newItemCreated = false; - AddLabelToGroupTree(lIter->second, groupItem, newItemCreated); + AddLabelToGroupTree(label, groupItem, newItemCreated); } } QmitkMultiLabelSegTreeItem* QmitkMultiLabelTreeModel::GenerateInternalTree() { auto rootItem = new QmitkMultiLabelSegTreeItem(); if (m_Segmentation.IsNotNull()) { for (unsigned int groupID = 0; groupID < m_Segmentation->GetNumberOfLayers(); ++groupID) { auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); GenerateInternalGroupTree(groupID, groupItem); } } return rootItem; } void QmitkMultiLabelTreeModel::UpdateInternalTree() { emit beginResetModel(); auto newTree = this->GenerateInternalTree(); this->m_RootItem.reset(newTree); emit endResetModel(); emit modelChanged(); } -void QmitkMultiLabelTreeModel::AddObserver() +void QmitkMultiLabelTreeModel::ITKEventHandler(const itk::EventObject& e) { - if (this->m_Segmentation.IsNotNull()) + if (mitk::LabelAddedEvent().CheckEvent(&e)) { - if (m_Observed) - { - MITK_DEBUG << "Invalid observer state in QmitkMultiLabelTreeModel. There is already a registered observer. Internal logic is not correct. May be an old observer was not removed."; - } - - this->m_Segmentation->AddLabelAddedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnLabelAdded)); - this->m_Segmentation->AddLabelModifiedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnLabelModified)); - this->m_Segmentation->AddLabelRemovedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); - this->m_Segmentation->AddGroupAddedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnGroupAdded)); - this->m_Segmentation->AddGroupModifiedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnGroupModified)); - this->m_Segmentation->AddGroupRemovedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); - m_Observed = true; + auto labelEvent = dynamic_cast(&e); + this->OnLabelAdded(labelEvent->GetLabelValue()); + } + else if (mitk::LabelModifiedEvent().CheckEvent(&e)) + { + auto labelEvent = dynamic_cast(&e); + this->OnLabelModified(labelEvent->GetLabelValue()); + } + else if (mitk::LabelRemovedEvent().CheckEvent(&e)) + { + auto labelEvent = dynamic_cast(&e); + this->OnLabelRemoved(labelEvent->GetLabelValue()); + } + else if (mitk::GroupAddedEvent().CheckEvent(&e)) + { + auto labelEvent = dynamic_cast(&e); + this->OnGroupAdded(labelEvent->GetGroupID()); + } + else if (mitk::GroupModifiedEvent().CheckEvent(&e)) + { + auto labelEvent = dynamic_cast(&e); + this->OnGroupModified(labelEvent->GetGroupID()); + } + else if (mitk::GroupRemovedEvent().CheckEvent(&e)) + { + auto labelEvent = dynamic_cast(&e); + this->OnGroupRemoved(labelEvent->GetGroupID()); } } -void QmitkMultiLabelTreeModel::RemoveObserver() +void QmitkMultiLabelTreeModel::AddObserver() { + m_LabelAddedObserver.Reset(); + m_LabelModifiedObserver.Reset(); + m_LabelRemovedObserver.Reset(); + m_GroupAddedObserver.Reset(); + m_GroupModifiedObserver.Reset(); + m_GroupRemovedObserver.Reset(); + if (this->m_Segmentation.IsNotNull()) { - this->m_Segmentation->RemoveLabelAddedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnLabelAdded)); - this->m_Segmentation->RemoveLabelModifiedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnLabelModified)); - this->m_Segmentation->RemoveLabelRemovedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnLabelRemoved)); - this->m_Segmentation->RemoveGroupAddedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnGroupAdded)); - this->m_Segmentation->RemoveGroupModifiedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnGroupModified)); - this->m_Segmentation->RemoveGroupRemovedListener(mitk::MessageDelegate1( - this, &QmitkMultiLabelTreeModel::OnGroupRemoved)); + auto& model = *this; + + m_LabelAddedObserver.Reset(m_Segmentation, mitk::LabelAddedEvent(), [&model](const itk::EventObject& event){model.ITKEventHandler(event);}); + m_LabelModifiedObserver.Reset(m_Segmentation, mitk::LabelModifiedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); + m_LabelRemovedObserver.Reset(m_Segmentation, mitk::LabelRemovedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); + m_GroupAddedObserver.Reset(m_Segmentation, mitk::GroupAddedEvent(), [&model](const itk::EventObject& event) { + model.ITKEventHandler(event); }); + m_GroupModifiedObserver.Reset(m_Segmentation, mitk::GroupModifiedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); + m_GroupRemovedObserver.Reset(m_Segmentation, mitk::GroupRemovedEvent(), [&model](const itk::EventObject& event) {model.ITKEventHandler(event); }); } - m_Observed = false; } void QmitkMultiLabelTreeModel::OnLabelAdded(LabelValueType labelValue) { - GroupIndexType groupIndex = 0; - if (m_Segmentation->IsLabelInGroup(labelValue, groupIndex)) - { - auto label = m_Segmentation->GetLabel(labelValue); - if (nullptr == label) mitkThrow() << "Invalid internal state. Segmentation signaled the addition of an label that does not exist in the segmentation. Invalid label value:" << labelValue; - if (labelValue == mitk::LabelSetImage::UnlabeledValue) return; + GroupIndexType groupIndex = m_Segmentation->GetGroupIndexOfLabel(labelValue); + auto label = m_Segmentation->GetLabel(labelValue); + if (nullptr == label) mitkThrow() << "Invalid internal state. Segmentation signaled the addition of an label that does not exist in the segmentation. Invalid label value:" << labelValue; + if (labelValue == mitk::LabelSetImage::UNLABELED_VALUE) return; - auto groupItem = GetGroupItem(groupIndex, this->m_RootItem.get()); + auto groupItem = GetGroupItem(groupIndex, this->m_RootItem.get()); - bool newLabelCreated = false; - auto instanceItem = AddLabelToGroupTree(label, groupItem, newLabelCreated); + bool newLabelCreated = false; + auto instanceItem = AddLabelToGroupTree(label, groupItem, newLabelCreated); - if (newLabelCreated) - { - if (groupItem->m_childItems.size() == 1) - { //first label added - auto groupIndex = GetIndexByItem(groupItem, this); - emit dataChanged(groupIndex, groupIndex); - this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); - this->endInsertRows(); - } - else - { //whole new label level added to group item - auto groupIndex = GetIndexByItem(groupItem, this); - this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); - this->endInsertRows(); - } + if (newLabelCreated) + { + if (groupItem->m_childItems.size() == 1) + { //first label added + auto groupIndex = GetIndexByItem(groupItem, this); + emit dataChanged(groupIndex, groupIndex); + this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); + this->endInsertRows(); } else - { - if (instanceItem->ParentItem()->m_childItems.size() < 3) - { //second instance item was added, so label item will now able to colapse - // -> the whole label node has to be updated. - auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); - emit dataChanged(labelIndex, labelIndex); - this->beginInsertRows(labelIndex, 0, instanceItem->ParentItem()->m_childItems.size()-1); - this->endInsertRows(); - } - else - { - // instance item was added to existing label item with multiple instances - //-> just notify the row insertion - auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); - this->beginInsertRows(labelIndex, instanceItem->Row(), instanceItem->Row()); - this->endInsertRows(); - } + { //whole new label level added to group item + auto groupIndex = GetIndexByItem(groupItem, this); + this->beginInsertRows(groupIndex, instanceItem->ParentItem()->Row(), instanceItem->ParentItem()->Row()); + this->endInsertRows(); } } else { - mitkThrow() << "Group less labels are not supported in the current implementation."; + if (instanceItem->ParentItem()->m_childItems.size() < 3) + { //second instance item was added, so label item will now able to colapse + // -> the whole label node has to be updated. + auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); + emit dataChanged(labelIndex, labelIndex); + this->beginInsertRows(labelIndex, 0, instanceItem->ParentItem()->m_childItems.size()-1); + this->endInsertRows(); + } + else + { + // instance item was added to existing label item with multiple instances + //-> just notify the row insertion + auto labelIndex = GetIndexByItem(instanceItem->ParentItem(), this); + this->beginInsertRows(labelIndex, instanceItem->Row(), instanceItem->Row()); + this->endInsertRows(); + } } } void QmitkMultiLabelTreeModel::OnLabelModified(LabelValueType labelValue) { - if (labelValue == mitk::LabelSetImage::UnlabeledValue) return; + if (labelValue == mitk::LabelSetImage::UNLABELED_VALUE) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) { mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelModified signal for a label that is not represented in the model. Invalid label: " << labelValue; } auto labelItem = instanceItem->ParentItem(); if (labelItem->m_ClassName == instanceItem->GetLabel()->GetName()) { //only the state of the label changed, but not its position in the model tree. auto index = GetIndexByItem(labelItem, this); emit dataChanged(index, index); } else { //the name of the label changed and thus its place in the model tree, delete the current item and add a new one this->OnLabelRemoved(labelValue); this->OnLabelAdded(labelValue); } } void QmitkMultiLabelTreeModel::OnLabelRemoved(LabelValueType labelValue) { - if (labelValue == mitk::LabelSetImage::UnlabeledValue) return; + if (labelValue == mitk::LabelSetImage::UNLABELED_VALUE) return; auto instanceItem = GetInstanceItem(labelValue, this->m_RootItem.get()); if (nullptr == instanceItem) mitkThrow() << "Internal invalid state. QmitkMultiLabelTreeModel recieved a LabelRemoved signal for a label that is not represented in the model. Invalid label: " << labelValue; auto labelItem = instanceItem->ParentItem(); if (labelItem->m_childItems.size() > 2) { auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); } else if (labelItem->m_childItems.size() == 2) { //After removal only one label is left -> the whole label node is about to be changed (no instances are shown any more). auto labelIndex = GetIndexByItem(labelItem, this); this->beginRemoveRows(labelIndex, instanceItem->Row(), instanceItem->Row()); labelItem->RemoveChild(instanceItem->Row()); this->endRemoveRows(); emit dataChanged(labelIndex, labelIndex); } else { //was the only instance of the label, therefor also remove the label node from the tree. auto groupItem = labelItem->ParentItem(); auto groupIndex = GetIndexByItem(groupItem, this); this->beginRemoveRows(groupIndex, labelItem->Row(), labelItem->Row()); groupItem->RemoveChild(labelItem->Row()); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::OnGroupAdded(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginInsertRows(QModelIndex(), groupIndex, groupIndex); auto rootItem = m_RootItem.get(); auto groupItem = new QmitkMultiLabelSegTreeItem(QmitkMultiLabelSegTreeItem::ItemType::Group, rootItem); rootItem->AppendChild(groupItem); this->GenerateInternalGroupTree(groupIndex, groupItem); this->endInsertRows(); } } void QmitkMultiLabelTreeModel::OnGroupModified(GroupIndexType /*groupIndex*/) { //currently not needed } void QmitkMultiLabelTreeModel::OnGroupRemoved(GroupIndexType groupIndex) { if (m_ShowGroups) { this->beginRemoveRows(QModelIndex(), groupIndex, groupIndex); auto root = m_RootItem.get(); root->RemoveChild(groupIndex); this->endRemoveRows(); } } void QmitkMultiLabelTreeModel::SetAllowVisibilityModification(bool vmod) { m_AllowVisibilityModification = vmod; } bool QmitkMultiLabelTreeModel::GetAllowVisibilityModification() const { return m_AllowVisibilityModification; } void QmitkMultiLabelTreeModel::SetAllowLockModification(bool lmod) { m_AllowLockModification = lmod; } bool QmitkMultiLabelTreeModel::GetAllowLockModification() const { return m_AllowLockModification; } diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h index 750a62630d..a8b4d671d5 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h +++ b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelTreeModel.h @@ -1,166 +1,168 @@ /*============================================================================ 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 QmitkMultiLabelTreeModel_h #define QmitkMultiLabelTreeModel_h #include "mitkLabelSetImage.h" +#include // qt #include #include "MitkSegmentationUIExports.h" class QmitkMultiLabelSegTreeItem; /*! \class QmitkMultiLabelTreeModel The class is used to represent the information of an MITK MultiLabel segmentation instance (labels, spacial groups...). */ class MITKSEGMENTATIONUI_EXPORT QmitkMultiLabelTreeModel : public QAbstractItemModel { Q_OBJECT public: using LabelValueType = mitk::LabelSetImage::LabelValueType; using GroupIndexType = mitk::LabelSetImage::GroupIndexType; QmitkMultiLabelTreeModel(QObject *parent = nullptr); ~QmitkMultiLabelTreeModel() override; void SetSegmentation(mitk::LabelSetImage* segmentation); const mitk::LabelSetImage* GetSegmentation() const; Qt::ItemFlags flags(const QModelIndex &index) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; bool setData(const QModelIndex& index, const QVariant& value, int role = Qt::EditRole) override; QVariant headerData(int section, Qt::Orientation orientation, int role) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; /** returns the index of a passed label value (always first column). If label value does not exist in segmentation or segmentation is not set an invalid index will be returned.*/ QModelIndex indexOfLabel(mitk::Label::PixelType labelValue) const; QModelIndex indexOfGroup(mitk::LabelSetImage::GroupIndexType groupIndex) const; /** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance or instance node). If current index is at the end, an invalid index is returned.*/ QModelIndex ClosestLabelInstanceIndex(const QModelIndex& currentIndex) const; /** Returns the index to the first child node (or itself) in the tree that behaves like an instance (label node with only one instance or instance node). If current index is at the end, an invalid index is returned. If an invalid index is passed into the methods, the search starts at the root; thus the whole tree is search for the first label instance.*/ QModelIndex FirstLabelInstanceIndex(const QModelIndex& currentIndex) const; ///** Returns the index to the next node in the tree that behaves like an instance (label node with only one instance //or instance node). If current index is at the end, an invalid index is returned.*/ //QModelIndex PrevLabelInstanceIndex(const QModelIndex& currentIndex) const; /** Returns a vector containing all label values of the passed currentIndex or its child items.*/ std::vector GetLabelsInSubTree(const QModelIndex& currentIndex) const; /** Returns a vector containing all label values of all label instances that belong to the same label * class like the passed index. * * If index points to a group or invalid, nothing will be returned. * @pre currentIndex must be valid and point to a label (class or instance). */ std::vector GetLabelInstancesOfSameLabelClass(const QModelIndex& currentIndex) const; enum TableColumns { NAME_COL = 0, LOCKED_COL, COLOR_COL, VISIBLE_COL }; enum ItemModelRole { /**This role returns the label object that is associated with an index. - On group level it always returns an invalid QVariant - On label level (with multiple instances) it returns the first label instance). - On instance level it returns the label instance object.*/ LabelDataRole = 64, /**This role returns only the label value of the label that would be returned by LabelDataRole.*/ LabelValueRole = 65, /**Simelar to LabelDataRole, but only returns a valid QVariant if index points only to a specific instance (so either instance level or label level with only one instance). You can use that role if you want to assure that only one specific label instance is referenced by the index.*/ LabelInstanceDataRole = 66, /**Simelar to LabelValueRole, but like LabelInstanceDataRole only returns a valid QVariant if index points only to a specific instance (so either instance level or label level with only one instance). You can use that role if you want to assure that only one specific label instance is referenced by the index.*/ LabelInstanceValueRole = 67, /**This role returns the group ID the item/index belongs to.*/ GroupIDRole = 68 }; bool GetAllowVisibilityModification() const; bool GetAllowLockModification() const; public Q_SLOTS: void SetAllowVisibilityModification(bool vmod); void SetAllowLockModification(bool lmod); Q_SIGNALS: void dataAvailable(); /** Is emitted whenever the model changes are finished (usually a bit later than dataAvailable()).*/ void modelChanged(); protected: + void ITKEventHandler(const itk::EventObject& e); + void OnLabelAdded(LabelValueType labelValue); void OnLabelModified(LabelValueType labelValue); void OnLabelRemoved(LabelValueType labelValue); void OnGroupAdded(GroupIndexType groupIndex); void OnGroupModified(GroupIndexType groupIndex); void OnGroupRemoved(GroupIndexType groupIndex); private: void AddObserver(); - void RemoveObserver(); void UpdateInternalTree(); void GenerateInternalGroupTree(unsigned int layerID, QmitkMultiLabelSegTreeItem* layerItem); QmitkMultiLabelSegTreeItem* GenerateInternalTree(); - /* builds a hierarchical tree model for the image statistics - 1. Level: Image - --> 2. Level: Mask [if exist] - --> 3. Level: Timestep [if >1 exist] */ - void BuildHierarchicalModel(); - mitk::LabelSetImage::Pointer m_Segmentation; std::mutex m_Mutex; std::unique_ptr m_RootItem; - bool m_Observed; bool m_ShowGroups = true; bool m_ShowVisibility = true; bool m_ShowLock = true; bool m_ShowOther = false; bool m_AllowVisibilityModification = true; bool m_AllowLockModification = true; + + mitk::ITKEventObserverGuard m_LabelAddedObserver; + mitk::ITKEventObserverGuard m_LabelModifiedObserver; + mitk::ITKEventObserverGuard m_LabelRemovedObserver; + mitk::ITKEventObserverGuard m_GroupAddedObserver; + mitk::ITKEventObserverGuard m_GroupModifiedObserver; + mitk::ITKEventObserverGuard m_GroupRemovedObserver; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp index d3e04175c0..25f783db43 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSegmentationTaskListWidget.cpp @@ -1,1012 +1,1012 @@ /*============================================================================ 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 "QmitkSegmentationTaskListWidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { mitk::IPreferences* GetSegmentationPreferences() { return mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); } std::filesystem::path GetInputLocation(const mitk::BaseData* data) { std::string result; if (data != nullptr) data->GetPropertyList()->GetStringProperty("MITK.IO.reader.inputlocation", result); return result; } QString ColorString(const QString& string, const QColor& color, const QColor& backgroundColor = QColor::Invalid) { if (!color.isValid() && !backgroundColor.isValid()) return string; auto result = QStringLiteral("%1").arg(string); return result; } mitk::DataStorage::SetOfObjects::ConstPointer GetSubset(const mitk::DataStorage* dataStorage, const mitk::NodePredicateBase* condition, const mitk::DataNode* removedDataNode) { auto subset = dataStorage->GetSubset(condition); if (nullptr != removedDataNode) { auto actualSubset = mitk::DataStorage::SetOfObjects::New(); for (auto node : *subset) { if (node != removedDataNode) actualSubset->push_back(node); } return actualSubset; } return subset; } } /* This constructor has three objectives: * 1. Do widget initialization that cannot be done in the .ui file * 2. Connect signals and slots * 3. Explicitly trigger a reset to a valid initial widget state */ QmitkSegmentationTaskListWidget::QmitkSegmentationTaskListWidget(QWidget* parent) : QWidget(parent), m_Ui(new Ui::QmitkSegmentationTaskListWidget), m_FileSystemWatcher(new QFileSystemWatcher(this)), m_DataStorage(nullptr), m_UnsavedChanges(false) { m_Ui->setupUi(this); m_Ui->selectionWidget->SetNodePredicate(mitk::TNodePredicateDataType::New()); m_Ui->progressBar->setStyleSheet(QString("QProgressBar::chunk { background-color: %1; }").arg(QmitkStyleManager::GetIconAccentColor())); m_Ui->findButton->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/Qmitk/icon_find.svg"))); m_Ui->storeButton->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); using Self = QmitkSegmentationTaskListWidget; connect(m_Ui->selectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &Self::OnSelectionChanged); connect(m_Ui->previousButton, &QToolButton::clicked, this, &Self::OnPreviousButtonClicked); connect(m_Ui->nextButton, &QToolButton::clicked, this, &Self::OnNextButtonClicked); connect(m_Ui->findButton, &QToolButton::clicked, this, &Self::OnFindButtonClicked); connect(m_Ui->loadButton, &QPushButton::clicked, this, &Self::OnLoadButtonClicked); connect(m_Ui->storeButton, &QPushButton::clicked, this, &Self::OnStoreButtonClicked); connect(m_Ui->acceptButton, &QPushButton::clicked, this, &Self::OnAcceptButtonClicked); connect(m_FileSystemWatcher, &QFileSystemWatcher::directoryChanged, this, &Self::OnResultDirectoryChanged); auto* prevShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_P), this); connect(prevShortcut, &QShortcut::activated, this, &Self::OnPreviousTaskShortcutActivated); auto* prevUndoneShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key::Key_P), this); connect(prevUndoneShortcut, &QShortcut::activated, this, &Self::OnPreviousTaskShortcutActivated); auto* nextShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_N), this); connect(nextShortcut, &QShortcut::activated, this, &Self::OnNextTaskShortcutActivated); auto* nextUndoneShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key::Key_N), this); connect(nextUndoneShortcut, &QShortcut::activated, this, &Self::OnNextTaskShortcutActivated); auto *findTaskShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_F), this); connect(findTaskShortcut, &QShortcut::activated, this, &Self::OnFindTaskShortcutActivated); auto* loadShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_L), this); connect(loadShortcut, &QShortcut::activated, this, &Self::OnLoadTaskShortcutActivated); auto* storeShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_S), parent); connect(storeShortcut, &QShortcut::activated, this, &Self::OnStoreInterimResultShortcutActivated); auto* acceptShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key::Key_A), parent); connect(acceptShortcut, &QShortcut::activated, this, &Self::OnAcceptSegmentationShortcutActivated); this->ResetControls(); this->CheckDataStorage(); } QmitkSegmentationTaskListWidget::~QmitkSegmentationTaskListWidget() { } void QmitkSegmentationTaskListWidget::SetDataStorage(mitk::DataStorage* dataStorage) { m_DataStorage = dataStorage; m_Ui->selectionWidget->SetDataStorage(dataStorage); // Triggers OnSelectionChanged() m_Ui->selectionWidget->SetAutoSelectNewNodes(true); this->CheckDataStorage(); } void QmitkSegmentationTaskListWidget::CheckDataStorage(const mitk::DataNode* removedNode) { QString warning; if (nullptr == m_DataStorage) { warning = QStringLiteral( "

Developer warning

Call SetDataStorage() to fully initialize " "this instance of QmitkSegmentationTaskListWidget.

"); } else { auto isTaskList = mitk::TNodePredicateDataType::New(); auto taskListNodes = GetSubset(m_DataStorage, isTaskList, removedNode); if (taskListNodes->empty()) { warning = QStringLiteral( "

No segmentation task list found

Load a segmentation task list to use " "this plugin.

"); } else if (taskListNodes->Size() > 1) { warning = QStringLiteral( "

More than one segmentation task list found

Unload everything but a " "single segmentation task list to use this plugin.

"); } else { const auto* taskListNode = (*taskListNodes)[0].GetPointer(); auto isTaskListNode = mitk::NodePredicateFunction::New([taskListNode](const mitk::DataNode* node) { return node == taskListNode; }); auto isChildOfTaskListNode = mitk::NodePredicateFunction::New([this, isTaskListNode](const mitk::DataNode* node) { return !m_DataStorage->GetSources(node, isTaskListNode, false)->empty(); }); auto isHelperObject = mitk::NodePredicateProperty::New("helper object"); auto isUndesiredNode = mitk::NodePredicateNot::New(mitk::NodePredicateOr::New( isTaskListNode, isChildOfTaskListNode, isHelperObject)); if (!GetSubset(m_DataStorage, isUndesiredNode, removedNode)->empty()) { warning = QStringLiteral( "

Unrelated data found

Unload everything but a single segmentation task " "list to use this plugin.

"); } } } m_Ui->label->setText("" + warning + ""); m_Ui->label->setVisible(!warning.isEmpty()); m_Ui->widget->setVisible(warning.isEmpty()); } void QmitkSegmentationTaskListWidget::OnUnsavedChangesSaved() { if (m_UnsavedChanges) { m_UnsavedChanges = false; if (this->ActiveTaskIsShown()) this->UpdateDetailsLabel(); } } /* Make sure that the widget transitions into a valid state whenever the * selection changes. */ void QmitkSegmentationTaskListWidget::OnSelectionChanged(const QmitkSingleNodeSelectionWidget::NodeList& nodes) { this->UnloadTasks(); this->ResetControls(); if (!nodes.empty()) { m_TaskListNode = nodes.front(); auto taskList = dynamic_cast(m_TaskListNode->GetData()); if (taskList != nullptr) { this->OnTaskListChanged(taskList); return; } } this->SetTaskList(nullptr); m_TaskListNode = nullptr; } /* Reset all controls to a default state as a common basis for further * adjustments. */ void QmitkSegmentationTaskListWidget::ResetControls() { m_Ui->progressBar->setEnabled(false); m_Ui->progressBar->setFormat(""); m_Ui->progressBar->setValue(0); m_Ui->progressBar->setMaximum(1); m_Ui->previousButton->setEnabled(false); m_Ui->nextButton->setEnabled(false); this->UpdateLoadButton(); this->UpdateDetailsLabel(); this->UpdateStoreAndAcceptButtons(); } /* If the segmentation task changed, reset all member variables to expected * default values and reset the file system watcher. */ void QmitkSegmentationTaskListWidget::SetTaskList(mitk::SegmentationTaskList* taskList) { if (m_TaskList != taskList) { m_TaskList = taskList; if (taskList != nullptr) { this->SetCurrentTaskIndex(0); } else { this->SetCurrentTaskIndex(std::nullopt); } this->ResetFileSystemWatcher(); } } void QmitkSegmentationTaskListWidget::ResetFileSystemWatcher() { auto paths = m_FileSystemWatcher->directories(); if (!paths.empty()) m_FileSystemWatcher->removePaths(paths); if (m_TaskList.IsNotNull()) { for (const auto& task : *m_TaskList) { auto resultPath = m_TaskList->GetAbsolutePath(task.GetResult()).remove_filename(); if (!std::filesystem::exists(resultPath)) { try { std::filesystem::create_directories(resultPath); } catch (const std::filesystem::filesystem_error& e) { MITK_ERROR << e.what(); } } if (std::filesystem::exists(resultPath)) m_FileSystemWatcher->addPath(QString::fromStdString(resultPath.string())); } } } void QmitkSegmentationTaskListWidget::OnResultDirectoryChanged(const QString&) { // TODO: If a segmentation was modified ("Unsaved changes"), saved ("Done"), and then the file is deleted, the status should be "Unsaved changes" instead of "Not done". this->UpdateProgressBar(); this->UpdateDetailsLabel(); } void QmitkSegmentationTaskListWidget::UpdateProgressBar() { int progress = 0; for (size_t i = 0; i < m_TaskList->GetNumberOfTasks(); ++i) { if (m_TaskList->IsDone(i)) ++progress; } m_Ui->progressBar->setValue(progress); } /* Provided that a valid segmentation task list is currently selected and the * widget is in its default state, update all controls accordingly. * TODO: Then, load the first unfinished task, if any. */ void QmitkSegmentationTaskListWidget::OnTaskListChanged(mitk::SegmentationTaskList* taskList) { this->SetTaskList(taskList); const auto numTasks = taskList->GetNumberOfTasks(); m_Ui->progressBar->setMaximum(numTasks); m_Ui->progressBar->setFormat(QStringLiteral("%v/%m Task(s) done")); m_Ui->progressBar->setEnabled(true); this->UpdateProgressBar(); m_Ui->loadButton->setEnabled(true); if (numTasks > 1) m_Ui->nextButton->setEnabled(true); // TODO: This line should be enough but it is happening too early even before // the RenderingManager has any registered render windows, resulting in mismatching // renderer and data geometries. // this->LoadNextUnfinishedTask(); } /* If possible, change the currently displayed task to the previous task. * Enable/disable navigation buttons according to the task's position. */ void QmitkSegmentationTaskListWidget::OnPreviousButtonClicked() { auto current = m_CurrentTaskIndex.value(); // If the shift modifier key is pressed, look for the previous undone task. if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { if (current > 0) { for (decltype(current) i = current; i > 0; --i) { if (!m_TaskList->IsDone(i - 1)) { this->SetCurrentTaskIndex(i - 1); break; } } } } else { if (current != 0) this->SetCurrentTaskIndex(current - 1); } this->UpdateNavigationButtons(); } /* If possible, change the currently displayed task to the next task. * Enable/disable navigation buttons according to the task's position. */ void QmitkSegmentationTaskListWidget::OnNextButtonClicked() { const auto numTasks = m_TaskList->GetNumberOfTasks(); auto current = m_CurrentTaskIndex.value(); // If the shift modifier key is pressed, look for the next undone task. if (QApplication::queryKeyboardModifiers().testFlag(Qt::ShiftModifier)) { for (std::remove_const_t i = current + 1; i < numTasks; ++i) { if (!m_TaskList->IsDone(i)) { this->SetCurrentTaskIndex(i); break; } } } else { if (current < numTasks - 1) this->SetCurrentTaskIndex(current + 1); } this->UpdateNavigationButtons(); } void QmitkSegmentationTaskListWidget::OnFindButtonClicked() { if (m_TaskList.IsNull()) return; QmitkFindSegmentationTaskDialog dialog; dialog.SetTaskList(m_TaskList); if (dialog.exec() != QDialog::Accepted) return; if (!dialog.GetSelectedTask().has_value()) return; this->SetCurrentTaskIndex(dialog.GetSelectedTask()); if (dialog.LoadSelectedTask()) { if (!m_ActiveTaskIndex.has_value() || m_ActiveTaskIndex.value() != dialog.GetSelectedTask().value()) this->OnLoadButtonClicked(); } } void QmitkSegmentationTaskListWidget::UpdateNavigationButtons() { if (m_TaskList.IsNull() || m_TaskList->GetNumberOfTasks() == 0) { m_Ui->previousButton->setEnabled(false); m_Ui->nextButton->setEnabled(false); return; } const auto maxIndex = m_TaskList->GetNumberOfTasks() - 1; const auto current = m_CurrentTaskIndex.value(); m_Ui->previousButton->setEnabled(current != 0); m_Ui->nextButton->setEnabled(current != maxIndex); } /* Update affected controls when the currently displayed task changed. */ void QmitkSegmentationTaskListWidget::OnCurrentTaskChanged() { this->UpdateLoadButton(); this->UpdateNavigationButtons(); this->UpdateDetailsLabel(); this->UpdateStoreAndAcceptButtons(); } /* Update the load button according to the currently displayed task. */ void QmitkSegmentationTaskListWidget::UpdateLoadButton() { auto text = !this->ActiveTaskIsShown() ? QStringLiteral("Load task") : QStringLiteral("Task"); if (m_CurrentTaskIndex.has_value()) { const auto current = m_CurrentTaskIndex.value(); if (m_TaskList.IsNotNull()) { text += QString(" %1/%2").arg(current + 1).arg(m_TaskList->GetNumberOfTasks()); if (m_TaskList->HasName(current)) text += QStringLiteral(":\n") + QString::fromStdString(m_TaskList->GetName(current)); } m_Ui->loadButton->setDisabled(this->ActiveTaskIsShown()); } else { m_Ui->loadButton->setEnabled(false); } m_Ui->loadButton->setText(text); } /* Update the details label according to the currently display task. * The text is composed of the status of the task and a variable number * of text blocks according to the optional values provided by the task. */ void QmitkSegmentationTaskListWidget::UpdateDetailsLabel() { if (!m_CurrentTaskIndex.has_value()) { m_Ui->detailsLabel->clear(); return; } const auto current = m_CurrentTaskIndex.value(); bool isDone = m_TaskList->IsDone(current); auto details = QString("

Status: %1 / ").arg(this->ActiveTaskIsShown() ? ColorString("Active", Qt::white, QColor(Qt::green).darker()) : ColorString("Inactive", Qt::white, QColor(Qt::red).darker())); if (m_UnsavedChanges && this->ActiveTaskIsShown()) { details += QString("%1

").arg(ColorString("Unsaved changes", Qt::white, QColor(Qt::red).darker())); } else { details += QString("%1

").arg(isDone ? ColorString("Done", Qt::white, QColor(Qt::green).darker()) : ColorString("Not done", Qt::white, QColor(Qt::red).darker())); } if (m_TaskList->HasDescription(current)) details += QString("

Description: %1

").arg(QString::fromStdString(m_TaskList->GetDescription(current))); QStringList stringList; if (m_TaskList->HasImage(current)) stringList << QString::fromStdString("Image: " + m_TaskList->GetImage(current).string()); if (m_TaskList->HasSegmentation(current)) stringList << QString::fromStdString("Segmentation: " + m_TaskList->GetSegmentation(current).string()); if (m_TaskList->HasLabelName(current)) stringList << QString::fromStdString("Label name: " + m_TaskList->GetLabelName(current)); if (m_TaskList->HasLabelNameSuggestions(current)) stringList << QString::fromStdString("Label name suggestions: " + m_TaskList->GetLabelNameSuggestions(current).string()); if (m_TaskList->HasPreset(current)) stringList << QString::fromStdString("Label set preset: " + m_TaskList->GetPreset(current).string()); if (m_TaskList->HasDynamic(current)) stringList << QString("Segmentation type: %1").arg(m_TaskList->GetDynamic(current) ? "Dynamic" : "Static"); if (!stringList.empty()) details += QString("

%1

").arg(stringList.join(QStringLiteral("
"))); m_Ui->detailsLabel->setText(details); } void QmitkSegmentationTaskListWidget::UpdateStoreAndAcceptButtons() { auto activeTaskIsShown = this->ActiveTaskIsShown(); m_Ui->storeButton->setVisible(activeTaskIsShown); m_Ui->acceptButton->setEnabled(activeTaskIsShown); } /* Load/activate the currently displayed task. Unload all data nodes from * previously active tasks first, but spare and reuse the image if possible. */ void QmitkSegmentationTaskListWidget::OnLoadButtonClicked() { if (!this->HandleUnsavedChanges() || m_UnsavedChanges) return; m_Ui->loadButton->setEnabled(false); QApplication::setOverrideCursor(Qt::BusyCursor); this->LoadTask(this->GetImageDataNode(m_CurrentTaskIndex.value())); QApplication::restoreOverrideCursor(); } /* If present, return the image data node for the task with the specified * index. Otherwise, return nullptr. */ mitk::DataNode* QmitkSegmentationTaskListWidget::GetImageDataNode(size_t index) const { const auto imagePath = m_TaskList->GetAbsolutePath(m_TaskList->GetImage(index)); auto imageNodes = m_DataStorage->GetDerivations(m_TaskListNode, mitk::NodePredicateFunction::New([imagePath](const mitk::DataNode* node) { return imagePath == GetInputLocation(node->GetData()); })); return !imageNodes->empty() ? imageNodes->front() : nullptr; } /* If present, return the segmentation data node for the task with the * specified index. Otherwise, return nullptr. */ mitk::DataNode* QmitkSegmentationTaskListWidget::GetSegmentationDataNode(size_t index) const { const auto* imageNode = this->GetImageDataNode(index); if (imageNode != nullptr) { auto segmentations = m_DataStorage->GetDerivations(imageNode, mitk::TNodePredicateDataType::New()); if (!segmentations->empty()) return segmentations->front(); } return nullptr; } /* Unload all task data nodes but spare the passed image data node. */ void QmitkSegmentationTaskListWidget::UnloadTasks(const mitk::DataNode* skip) { this->UnsubscribeFromActiveSegmentation(); if (m_TaskListNode.IsNotNull()) { auto imageNodes = m_DataStorage->GetDerivations(m_TaskListNode, mitk::TNodePredicateDataType::New()); for (auto imageNode : *imageNodes) { m_DataStorage->Remove(m_DataStorage->GetDerivations(imageNode, nullptr, false)); if (imageNode != skip) m_DataStorage->Remove(imageNode); } } this->SetActiveTaskIndex(std::nullopt); } void QmitkSegmentationTaskListWidget::LoadNextUnfinishedTask() { const auto current = m_CurrentTaskIndex.value(); const auto numTasks = m_TaskList->GetNumberOfTasks(); for (size_t unboundNext = current; unboundNext < current + numTasks; ++unboundNext) { auto next = unboundNext % numTasks; if (!m_TaskList->IsDone(next)) { this->SetCurrentTaskIndex(next); this->OnLoadButtonClicked(); break; } } } /* Load/activate the currently displayed task. The task must specify * an image. The segmentation is either created from scratch with an optional * name for the first label, possibly based on a label set preset specified by * the task, or loaded as specified by the task. If a result file does * exist, it is chosen as segmentation instead. */ void QmitkSegmentationTaskListWidget::LoadTask(mitk::DataNode::Pointer imageNode) { this->UnloadTasks(imageNode); const auto current = m_CurrentTaskIndex.value(); mitk::Image::Pointer image; mitk::LabelSetImage::Pointer segmentation; try { if (imageNode.IsNull()) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetImage(current)); image = mitk::IOUtil::Load(path.string()); } const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetResult(current)); const auto interimPath = m_TaskList->GetInterimPath(path); if (std::filesystem::exists(path)) { segmentation = mitk::IOUtil::Load(path.string()); } else if (std::filesystem::exists(interimPath)) { segmentation = mitk::IOUtil::Load(interimPath.string()); } else if (m_TaskList->HasSegmentation(current)) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetSegmentation(current)); segmentation = mitk::IOUtil::Load(path.string()); } } catch (const mitk::Exception&) { return; } if (imageNode.IsNull()) { imageNode = mitk::DataNode::New(); imageNode->SetData(image); m_DataStorage->Add(imageNode, m_TaskListNode); mitk::RenderingManager::GetInstance()->InitializeViews(image->GetTimeGeometry()); } else { image = static_cast(imageNode->GetData()); } auto name = "Task " + std::to_string(current + 1); imageNode->SetName(name); if (segmentation.IsNull()) { mitk::Image::ConstPointer templateImage = image; if (templateImage->GetDimension() > 3) { if (m_TaskList->HasDynamic(current)) { if (!m_TaskList->GetDynamic(current)) templateImage = mitk::SegmentationHelper::GetStaticSegmentationTemplate(image); } else { QmitkStaticDynamicSegmentationDialog dialog(this); dialog.SetReferenceImage(templateImage); dialog.exec(); templateImage = dialog.GetSegmentationTemplate(); } } auto segmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(imageNode, templateImage, name); segmentation = static_cast(segmentationNode->GetData()); if (m_TaskList->HasPreset(current)) { const auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetPreset(current)); mitk::MultiLabelIOHelper::LoadLabelSetImagePreset(path.string(), segmentation); } else { auto label = mitk::LabelSetImageHelper::CreateNewLabel(segmentation); if (m_TaskList->HasLabelName(current)) label->SetName(m_TaskList->GetLabelName(current)); - segmentation->GetActiveLabelSet()->AddLabel(label); + segmentation->AddLabel(label, segmentation->GetActiveLayer()); } m_DataStorage->Add(segmentationNode, imageNode); } else { auto segmentationNode = mitk::DataNode::New(); segmentationNode->SetName(name); segmentationNode->SetData(segmentation); m_DataStorage->Add(segmentationNode, imageNode); } // Workaround for T29431. Remove when T26953 is fixed. mitk::DICOMQIPropertyHelper::DeriveDICOMSourceProperties(image, segmentation); auto prefs = GetSegmentationPreferences(); if (prefs != nullptr) { if (m_TaskList->HasLabelNameSuggestions(current)) { auto path = m_TaskList->GetAbsolutePath(m_TaskList->GetLabelNameSuggestions(current)); prefs->PutBool("default label naming", false); prefs->Put("label suggestions", path.string()); prefs->PutBool("replace standard suggestions", true); prefs->PutBool("suggest once", true); } else { prefs->PutBool("default label naming", true); prefs->Put("label suggestions", ""); } } m_UnsavedChanges = false; this->SetActiveTaskIndex(current); this->SubscribeToActiveSegmentation(); this->OnCurrentTaskChanged(); } void QmitkSegmentationTaskListWidget::SubscribeToActiveSegmentation() { if (m_ActiveTaskIndex.has_value()) { auto segmentationNode = this->GetSegmentationDataNode(m_ActiveTaskIndex.value()); if (segmentationNode != nullptr) { auto segmentation = static_cast(segmentationNode->GetData()); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationTaskListWidget::OnSegmentationModified); m_SegmentationModifiedObserverTag = segmentation->AddObserver(itk::ModifiedEvent(), command); } } } void QmitkSegmentationTaskListWidget::UnsubscribeFromActiveSegmentation() { if (m_ActiveTaskIndex.has_value() && m_SegmentationModifiedObserverTag.has_value()) { auto segmentationNode = this->GetSegmentationDataNode(m_ActiveTaskIndex.value()); if (segmentationNode != nullptr) { auto segmentation = static_cast(segmentationNode->GetData()); segmentation->RemoveObserver(m_SegmentationModifiedObserverTag.value()); } m_SegmentationModifiedObserverTag.reset(); } } void QmitkSegmentationTaskListWidget::OnSegmentationModified() { if (!m_UnsavedChanges) { m_UnsavedChanges = true; if (m_ActiveTaskIndex.value() == m_CurrentTaskIndex) this->UpdateDetailsLabel(); } } void QmitkSegmentationTaskListWidget::SetActiveTaskIndex(const std::optional& index) { if (m_ActiveTaskIndex != index) { m_ActiveTaskIndex = index; this->UpdateStoreAndAcceptButtons(); } } void QmitkSegmentationTaskListWidget::SetCurrentTaskIndex(const std::optional& index) { if (m_CurrentTaskIndex != index) { m_CurrentTaskIndex = index; this->OnCurrentTaskChanged(); } } bool QmitkSegmentationTaskListWidget::ActiveTaskIsShown() const { return m_ActiveTaskIndex.has_value() && m_CurrentTaskIndex.has_value() && m_ActiveTaskIndex == m_CurrentTaskIndex; } bool QmitkSegmentationTaskListWidget::HandleUnsavedChanges(const QString& alternativeTitle) { if (m_UnsavedChanges) { const auto active = m_ActiveTaskIndex.value(); const auto current = m_CurrentTaskIndex.value(); QString title; if (alternativeTitle.isEmpty()) { title = QString("Load task %1").arg(current + 1); if (m_TaskList->HasName(current)) title += ": " + QString::fromStdString(m_TaskList->GetName(current)); } else { title = alternativeTitle; } auto text = QString("The currently active task %1 ").arg(active + 1); if (m_TaskList->HasName(active)) text += "(" + QString::fromStdString(m_TaskList->GetName(active)) + ") "; text += "has unsaved changes."; auto reply = QMessageBox::question(this, title, text, QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel); switch (reply) { case QMessageBox::Save: this->SaveActiveTask(!std::filesystem::exists(m_TaskList->GetResult(active))); break; case QMessageBox::Discard: m_UnsavedChanges = false; break; default: return false; } } return true; } void QmitkSegmentationTaskListWidget::SaveActiveTask(bool saveAsIntermediateResult) { if (!m_ActiveTaskIndex.has_value()) return; QApplication::setOverrideCursor(Qt::BusyCursor); try { const auto active = m_ActiveTaskIndex.value(); m_TaskList->SaveTask(active, this->GetSegmentationDataNode(active)->GetData(), saveAsIntermediateResult); this->OnUnsavedChangesSaved(); } catch (const mitk::Exception& e) { MITK_ERROR << e; } QApplication::restoreOverrideCursor(); } bool QmitkSegmentationTaskListWidget::OnPreShutdown() { return this->HandleUnsavedChanges(QStringLiteral("Application shutdown")); } void QmitkSegmentationTaskListWidget::OnPreviousTaskShortcutActivated() { m_Ui->previousButton->click(); } void QmitkSegmentationTaskListWidget::OnNextTaskShortcutActivated() { m_Ui->nextButton->click(); } void QmitkSegmentationTaskListWidget::OnFindTaskShortcutActivated() { m_Ui->findButton->click(); } void QmitkSegmentationTaskListWidget::OnLoadTaskShortcutActivated() { m_Ui->loadButton->click(); } void QmitkSegmentationTaskListWidget::OnStoreInterimResultShortcutActivated() { m_Ui->storeButton->click(); } void QmitkSegmentationTaskListWidget::OnAcceptSegmentationShortcutActivated() { m_Ui->acceptButton->click(); } void QmitkSegmentationTaskListWidget::OnStoreButtonClicked() { this->SaveActiveTask(true); } void QmitkSegmentationTaskListWidget::OnAcceptButtonClicked() { auto* toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); int activeToolId = -1; if (toolManager != nullptr) activeToolId = toolManager->GetActiveToolID(); this->SaveActiveTask(); this->LoadNextUnfinishedTask(); if (toolManager != nullptr) toolManager->ActivateTool(activeToolId); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp index ce0e57f777..bd18ca218b 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.cpp @@ -1,199 +1,164 @@ /*============================================================================ 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 "QmitkSimpleLabelSetListWidget.h" #include "mitkMessage.h" #include QmitkSimpleLabelSetListWidget::QmitkSimpleLabelSetListWidget(QWidget* parent) : QWidget(parent), m_LabelList(nullptr), m_Emmiting(false) { QGridLayout* layout = new QGridLayout(this); this->setContentsMargins(0, 0, 0, 0); m_LabelList = new QListWidget(this); m_LabelList->setSelectionMode(QAbstractItemView::MultiSelection); m_LabelList->setResizeMode(QListView::Adjust); m_LabelList->setAutoScrollMargin(0); layout->addWidget(m_LabelList); connect(m_LabelList, SIGNAL(itemSelectionChanged()), this, SLOT(OnLabelSelectionChanged())); } QmitkSimpleLabelSetListWidget::~QmitkSimpleLabelSetListWidget() { if (m_LabelSetImage.IsNotNull()) { - m_LabelSetImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( - this, &QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection); m_LabelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( - this, &QmitkSimpleLabelSetListWidget::OnEstablishLabelSetConnection); - OnLooseLabelSetConnection(); + this, &QmitkSimpleLabelSetListWidget::OnLayerChanged); } } QmitkSimpleLabelSetListWidget::LabelVectorType QmitkSimpleLabelSetListWidget::SelectedLabels() const { auto selectedItems = m_LabelList->selectedItems(); LabelVectorType result; QList::Iterator it; for (it = selectedItems.begin(); it != selectedItems.end(); ++it) { auto labelValue = (*it)->data(Qt::UserRole).toUInt(); - - - auto activeLayerID = m_LabelSetImage->GetActiveLayer(); - auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); - - result.push_back(labelSet->GetLabel(labelValue)); + result.push_back(m_LabelSetImage->GetLabel(labelValue)); } return result; } const mitk::LabelSetImage* QmitkSimpleLabelSetListWidget::GetLabelSetImage() const { return m_LabelSetImage; } void QmitkSimpleLabelSetListWidget::SetLabelSetImage(const mitk::LabelSetImage* image) { if (image != m_LabelSetImage) { - if (m_LabelSetImage.IsNotNull()) - { - m_LabelSetImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( - this, &QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection); - m_LabelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( - this, &QmitkSimpleLabelSetListWidget::OnLayerChanged); - this->OnLooseLabelSetConnection(); - } + m_LabelAddedObserver.Reset(); + m_LabelModifiedObserver.Reset(); + m_LabelRemovedObserver.Reset(); m_LabelSetImage = image; if (m_LabelSetImage.IsNotNull()) { - m_LabelSetImage->BeforeChangeLayerEvent += mitk::MessageDelegate( - this, &QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection); + auto& widget = *this; + m_LabelAddedObserver.Reset(m_LabelSetImage, mitk::LabelAddedEvent(), [&widget](const itk::EventObject& event) + { + auto labelEvent = dynamic_cast(&event); + widget.OnLabelChanged(labelEvent->GetLabelValue()); + }); + m_LabelModifiedObserver.Reset(m_LabelSetImage, mitk::LabelModifiedEvent(), [&widget](const itk::EventObject& event) + { + auto labelEvent = dynamic_cast(&event); + widget.OnLabelChanged(labelEvent->GetLabelValue()); + }); + m_LabelRemovedObserver.Reset(m_LabelSetImage, mitk::LabelRemovedEvent(), [&widget](const itk::EventObject& event) + { + auto labelEvent = dynamic_cast(&event); + widget.OnLabelChanged(labelEvent->GetLabelValue()); + }); + m_LabelSetImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSimpleLabelSetListWidget::OnLayerChanged); - this->OnLayerChanged(); } } } -void QmitkSimpleLabelSetListWidget::OnLooseLabelSetConnection() -{ - if (m_LabelSetImage.IsNull()) - return; - - auto activeLayerID = m_LabelSetImage->GetActiveLayer(); - auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); - - // Reset LabelSetWidget Events - labelSet->AddLabelEvent -= mitk::MessageDelegate1( - this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); - labelSet->RemoveLabelEvent -= mitk::MessageDelegate1( - this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); - labelSet->ModifyLabelEvent -= mitk::MessageDelegate1( - this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); -} - -void QmitkSimpleLabelSetListWidget::OnEstablishLabelSetConnection() -{ - if (m_LabelSetImage.IsNull()) - return; - - auto activeLayerID = m_LabelSetImage->GetActiveLayer(); - auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); - - // Reset LabelSetWidget Events - labelSet->AddLabelEvent += mitk::MessageDelegate1( - this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); - labelSet->RemoveLabelEvent += mitk::MessageDelegate1( - this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); - labelSet->ModifyLabelEvent += mitk::MessageDelegate1( - this, &QmitkSimpleLabelSetListWidget::OnLabelChanged); -} - void QmitkSimpleLabelSetListWidget::OnLayerChanged() { - this->OnEstablishLabelSetConnection(); if (!this->m_Emmiting) { this->ResetList(); this->m_Emmiting = true; emit ActiveLayerChanged(); emit SelectedLabelsChanged(this->SelectedLabels()); this->m_Emmiting = false; } } -void QmitkSimpleLabelSetListWidget::OnLabelChanged(mitk::LabelSetImage::LabelValueType /*lv*/) +void QmitkSimpleLabelSetListWidget::OnLabelChanged(mitk::LabelSetImage::LabelValueType lv) { - if (!this->m_Emmiting) + if (!this->m_Emmiting && m_LabelSetImage->GetGroupIndexOfLabel(lv)==m_LabelSetImage->GetActiveLayer()) { this->ResetList(); this->m_Emmiting = true; emit ActiveLayerChanged(); emit SelectedLabelsChanged(this->SelectedLabels()); this->m_Emmiting = false; } } void QmitkSimpleLabelSetListWidget::OnLabelSelectionChanged() { if (!this->m_Emmiting) { this->m_Emmiting = true; emit SelectedLabelsChanged(this->SelectedLabels()); this->m_Emmiting = false; } } void QmitkSimpleLabelSetListWidget::ResetList() { m_LabelList->clear(); auto activeLayerID = m_LabelSetImage->GetActiveLayer(); - auto labelSet = m_LabelSetImage->GetLabelSet(activeLayerID); + auto labels = m_LabelSetImage->GetConstLabelsByValue(m_LabelSetImage->GetLabelValuesByGroup(activeLayerID)); - auto iter = labelSet->IteratorConstBegin(); - for (; iter != labelSet->IteratorConstEnd(); ++iter) + for (auto& label : labels) { - auto color = iter->second->GetColor(); + auto color = label->GetColor(); QPixmap pixmap(10, 10); pixmap.fill(QColor(color[0] * 255, color[1] * 255, color[2] * 255)); QIcon icon(pixmap); - QListWidgetItem* item = new QListWidgetItem(icon, QString::fromStdString(iter->second->GetName())); - item->setData(Qt::UserRole, QVariant(iter->second->GetValue())); + QListWidgetItem* item = new QListWidgetItem(icon, QString::fromStdString(label->GetName())); + item->setData(Qt::UserRole, QVariant(label->GetValue())); m_LabelList->addItem(item); } } void QmitkSimpleLabelSetListWidget::SetSelectedLabels(const LabelVectorType& selectedLabels) { for (int i = 0; i < m_LabelList->count(); ++i) { QListWidgetItem* item = m_LabelList->item(i); auto labelValue = item->data(Qt::UserRole).toUInt(); auto finding = std::find_if(selectedLabels.begin(), selectedLabels.end(), [labelValue](const mitk::Label* label) {return label->GetValue() == labelValue; }); item->setSelected(finding != selectedLabels.end()); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h index 7eb5153611..ffbb7184d7 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSimpleLabelSetListWidget.h @@ -1,64 +1,66 @@ /*============================================================================ 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 QmitkSimpleLabelSetListWidget_h #define QmitkSimpleLabelSetListWidget_h #include "mitkLabel.h" #include "mitkLabelSetImage.h" +#include #include #include /** \brief Widget that offers a simple list that displays all labels (color and name) in the active layer of a LabelSetImage. */ class MITKSEGMENTATIONUI_EXPORT QmitkSimpleLabelSetListWidget : public QWidget { Q_OBJECT public: QmitkSimpleLabelSetListWidget(QWidget* parent = nullptr); ~QmitkSimpleLabelSetListWidget() override; using LabelVectorType = std::vector; LabelVectorType SelectedLabels() const; const mitk::LabelSetImage* GetLabelSetImage() const; signals: void SelectedLabelsChanged(const LabelVectorType& selectedLabels); void ActiveLayerChanged(); public slots : void SetLabelSetImage(const mitk::LabelSetImage* image); void SetSelectedLabels(const LabelVectorType& selectedLabels); protected slots: void OnLabelSelectionChanged(); protected: void OnLayerChanged(); void OnLabelChanged(mitk::LabelSetImage::LabelValueType lv); - void OnLooseLabelSetConnection(); - void OnEstablishLabelSetConnection(); - void ResetList(); mitk::LabelSetImage::ConstPointer m_LabelSetImage; QListWidget* m_LabelList; bool m_Emmiting; + + mitk::ITKEventObserverGuard m_LabelAddedObserver; + mitk::ITKEventObserverGuard m_LabelModifiedObserver; + mitk::ITKEventObserverGuard m_LabelRemovedObserver; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp index 9feec73c51..58dc2c7acc 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.cpp @@ -1,2039 +1,1499 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkSlicesInterpolator.h" #include "QmitkRenderWindow.h" #include "QmitkRenderWindowWidget.h" #include "mitkApplyDiffImageOperation.h" #include "mitkColorProperty.h" #include "mitkCoreObjectFactory.h" #include "mitkDiffImageApplier.h" #include "mitkInteractionConst.h" #include "mitkLevelWindowProperty.h" #include "mitkOperationEvent.h" #include "mitkProgressBar.h" #include "mitkProperties.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include "mitkSliceNavigationController.h" #include "mitkSurfaceToImageFilter.h" #include #include "mitkToolManager.h" #include "mitkUndoController.h" #include #include #include #include #include #include #include #include #include #include #include #include // Includes for the merge operation #include "mitkImageToContourFilter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { template itk::SmartPointer GetData(const mitk::DataNode* dataNode) { return nullptr != dataNode ? dynamic_cast(dataNode->GetData()) : nullptr; } } float SURFACE_COLOR_RGB[3] = {0.49f, 1.0f, 0.16f}; const QmitkSlicesInterpolator::ActionToSliceDimensionMapType QmitkSlicesInterpolator::CreateActionToSlicer(const QList& windows) { std::map actionToSliceDimension; for (auto* window : windows) { std::string windowName; auto renderWindowWidget = dynamic_cast(window->parentWidget()); if (renderWindowWidget) { windowName = renderWindowWidget->GetCornerAnnotationText(); } else { windowName = window->GetRenderer()->GetName(); } auto slicer = window->GetSliceNavigationController(); actionToSliceDimension[new QAction(QString::fromStdString(windowName), nullptr)] = slicer; } return actionToSliceDimension; } -// 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 != mitk::LabelSetImage::UnlabeledValue) ) - 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; - auto* currentContourList = surfaceInterpolator->GetContours(timePoint, currentLayerID); - - while (nullptr == currentContourList) - { - surfaceInterpolator->OnAddLayer(); - currentContourList = 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), 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) ); } } +void QmitkSlicesInterpolator::SetActiveLabelValue(mitk::LabelSetImage::LabelValueType labelValue) +{ + bool changedValue = labelValue != this->m_CurrentActiveLabelValue; + + this->m_CurrentActiveLabelValue = labelValue; + + if (changedValue) this->OnActiveLabelChanged(labelValue); +}; + + mitk::DataStorage *QmitkSlicesInterpolator::GetDataStorage() { if (m_DataStorage.IsNotNull()) { return m_DataStorage; } else { return nullptr; } } void QmitkSlicesInterpolator::InitializeWindow(QmitkRenderWindow* window) { auto slicer = window->GetSliceNavigationController(); if (slicer == nullptr) { MITK_WARN << "Tried setting up interpolation for a render window that does not have a slice navigation controller set"; return; } // Has to be initialized m_LastSNC = slicer; itk::MemberCommand::Pointer deleteCommand = itk::MemberCommand::New(); deleteCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceNavigationControllerDeleted); m_ControllerToDeleteObserverTag[slicer] = slicer->AddObserver(itk::DeleteEvent(), deleteCommand); itk::MemberCommand::Pointer sliceChangedCommand = itk::MemberCommand::New(); sliceChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnSliceChanged); m_ControllerToSliceObserverTag[slicer] = slicer->AddObserver(mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), sliceChangedCommand); } void QmitkSlicesInterpolator::Initialize(mitk::ToolManager *toolManager, const QList& windows) { Q_ASSERT(!windows.empty()); if (m_Initialized) { // remove old observers this->Uninitialize(); } m_ToolManager = toolManager; if (m_ToolManager) { // set enabled only if a segmentation is selected mitk::DataNode *node = m_ToolManager->GetWorkingData(0); QWidget::setEnabled(node != nullptr); // react whenever the set of selected segmentation changes m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged += mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); itk::MemberCommand::Pointer timeChangedCommand = itk::MemberCommand::New(); timeChangedCommand->SetCallbackFunction(this, &QmitkSlicesInterpolator::OnTimeChanged); m_ControllerToTimeObserverTag = timeNavigationController->AddObserver(mitk::TimeNavigationController::TimeEvent(0), timeChangedCommand); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); // connect to the slice navigation controller. after each change, call the interpolator for (auto* window : windows) { this->InitializeWindow(window); } m_ActionToSlicerMap = CreateActionToSlicer(windows); } m_Initialized = true; } void QmitkSlicesInterpolator::Uninitialize() { if (m_ToolManager.IsNotNull()) { m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &QmitkSlicesInterpolator::OnToolManagerWorkingDataModified); m_ToolManager->ReferenceDataChanged -= mitk::MessageDelegate( this, &QmitkSlicesInterpolator::OnToolManagerReferenceDataModified); } auto* timeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); timeNavigationController->RemoveObserver(m_ControllerToTimeObserverTag); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->RemoveObserver(m_ControllerToDeleteObserverTag.take(slicer)); slicer->RemoveObserver(m_ControllerToSliceObserverTag.take(slicer)); } - this->ClearSegmentationObservers(); m_ActionToSlicerMap.clear(); m_ToolManager = nullptr; m_Initialized = false; } QmitkSlicesInterpolator::~QmitkSlicesInterpolator() { if (m_Initialized) { // remove old observers this->Uninitialize(); } WaitForFutures(); if (m_DataStorage.IsNotNull()) { m_DataStorage->RemoveNodeEvent.RemoveListener( mitk::MessageDelegate1(this, &QmitkSlicesInterpolator::NodeRemoved) ); - if (m_DataStorage->Exists(m_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(); + m_SurfaceInterpolator->SetCurrentInterpolationSession(nullptr); delete m_Timer; } /** External enableization... */ void QmitkSlicesInterpolator::setEnabled(bool enable) { QWidget::setEnabled(enable); // Set the gui elements of the different interpolation modi enabled if (enable) { if (m_2DInterpolationEnabled) { this->Show2DInterpolationControls(true); m_Interpolator->Activate2DInterpolation(true); } else if (m_3DInterpolationEnabled) { this->Show3DInterpolationControls(true); this->Show3DInterpolationResult(true); } } // Set all gui elements of the interpolation disabled else { this->HideAllInterpolationControls(); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::On2DInterpolationEnabled(bool status) { OnInterpolationActivated(status); m_Interpolator->Activate2DInterpolation(status); } void QmitkSlicesInterpolator::On3DInterpolationEnabled(bool status) { On3DInterpolationActivated(status); } void QmitkSlicesInterpolator::OnInterpolationDisabled(bool status) { if (status) { OnInterpolationActivated(!status); On3DInterpolationActivated(!status); this->Show3DInterpolationResult(false); } } void QmitkSlicesInterpolator::HideAllInterpolationControls() { this->Show2DInterpolationControls(false); this->Show3DInterpolationControls(false); } void QmitkSlicesInterpolator::Show2DInterpolationControls(bool show) { m_BtnApply2D->setVisible(show); m_BtnApplyForAllSlices2D->setVisible(show); } void QmitkSlicesInterpolator::Show3DInterpolationControls(bool show) { m_BtnApply3D->setVisible(show); - // 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); this->Show3DInterpolationResult(false); m_Interpolator->Activate2DInterpolation(true); break; case 2: // 3D m_GroupBoxEnableExclusiveInterpolationMode->setTitle("Interpolation (Enabled)"); this->HideAllInterpolationControls(); this->Show3DInterpolationControls(true); this->OnInterpolationActivated(false); this->On3DInterpolationActivated(true); m_Interpolator->Activate2DInterpolation(false); break; default: MITK_ERROR << "Unknown interpolation method!"; m_CmbInterpolation->setCurrentIndex(0); break; } } void QmitkSlicesInterpolator::OnShowMarkers(bool state) { mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = m_DataStorage->GetSubset(mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { it->Value()->SetProperty("helper object", mitk::BoolProperty::New(!state)); } } void QmitkSlicesInterpolator::OnToolManagerWorkingDataModified() { - this->ClearSegmentationObservers(); - 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) { if (!dynamic_cast(&e)) { return; } const auto* timeNavigationController = dynamic_cast(sender); if (nullptr == timeNavigationController) { return; } + bool timeChanged = m_TimePoint != timeNavigationController->GetSelectedTimePoint(); m_TimePoint = timeNavigationController->GetSelectedTimePoint(); if (m_Watcher.isRunning()) m_Watcher.waitForFinished(); - if (m_TimePoint != m_SurfaceInterpolator->GetCurrentTimePoint()) + if (timeChanged) { - m_SurfaceInterpolator->SetCurrentTimePoint(m_TimePoint); if (m_3DInterpolationEnabled) { - m_3DContourNode->SetData(nullptr); m_InterpolatedSurfaceNode->SetData(nullptr); } m_SurfaceInterpolator->Modified(); } if (nullptr == m_LastSNC) { return; } if (TranslateAndInterpolateChangedSlice(m_LastSNC->GetCreatedWorldGeometry())) { m_LastSNC->GetRenderer()->RequestUpdate(); } } void QmitkSlicesInterpolator::OnSliceChanged(itk::Object *sender, const itk::EventObject &e) { if (!dynamic_cast(&e)) { return; } auto sliceNavigationController = dynamic_cast(sender); if (nullptr == sliceNavigationController) { return; } if(m_2DInterpolationEnabled) { this->On2DInterpolationEnabled(m_2DInterpolationEnabled); } if (TranslateAndInterpolateChangedSlice(e, sliceNavigationController)) { sliceNavigationController->GetRenderer()->RequestUpdate(); } } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const itk::EventObject& e, mitk::SliceNavigationController* sliceNavigationController) { const mitk::SliceNavigationController::GeometrySliceEvent* event = dynamic_cast(&e); mitk::TimeGeometry* timeGeometry = event->GetTimeGeometry(); m_LastSNC = sliceNavigationController; return this->TranslateAndInterpolateChangedSlice(timeGeometry); } bool QmitkSlicesInterpolator::TranslateAndInterpolateChangedSlice(const mitk::TimeGeometry* timeGeometry) { if (!m_2DInterpolationEnabled) { return false; } if (nullptr == timeGeometry) { return false; } if (!timeGeometry->IsValidTimePoint(m_TimePoint)) { return false; } mitk::SlicedGeometry3D* slicedGeometry = dynamic_cast(timeGeometry->GetGeometryForTimePoint(m_TimePoint).GetPointer()); if (nullptr == slicedGeometry) { return false; } mitk::PlaneGeometry* plane = dynamic_cast(slicedGeometry->GetPlaneGeometry(m_LastSNC->GetStepper()->GetPos())); if (nullptr == plane) { return false; } this->Interpolate(plane); return true; } -void QmitkSlicesInterpolator::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) { if (nullptr == m_ToolManager) { return; } mitk::DataNode* node = m_ToolManager->GetWorkingData(0); if (nullptr == node) { return; } m_Segmentation = dynamic_cast(node->GetData()); if (nullptr == m_Segmentation) { return; } if (!m_Segmentation->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot interpolate WorkingImage. Passed time point is not within the time bounds of WorkingImage. " "Time point: " << m_TimePoint; return; } const auto timeStep = m_Segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); int clickedSliceDimension = -1; int clickedSliceIndex = -1; // calculate real slice position, i.e. slice of the image mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, plane, clickedSliceDimension, clickedSliceIndex); mitk::Image::Pointer interpolation = m_Interpolator->Interpolate(clickedSliceDimension, clickedSliceIndex, plane, timeStep); m_FeedbackNode->SetData(interpolation); // maybe just have a variable that stores the active label color. if (m_ToolManager) { auto* workingNode = m_ToolManager->GetWorkingData(0); if (workingNode != nullptr) { - auto* activeLabel = dynamic_cast(workingNode->GetData())->GetActiveLabelSet()->GetActiveLabel(); + auto* activeLabel = dynamic_cast(workingNode->GetData())->GetActiveLabel(); if (nullptr != activeLabel) { auto activeColor = activeLabel->GetColor(); m_FeedbackNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } } } m_LastSliceIndex = clickedSliceIndex; } void QmitkSlicesInterpolator::OnSurfaceInterpolationFinished() { - mitk::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) + if (workingNode && workingNode->GetData()) { - m_BtnApply3D->setEnabled(true); - - // T28261 - // m_BtnSuggestPlane->setEnabled(true); - - m_InterpolatedSurfaceNode->SetData(interpolatedSurface); - - m_3DContourNode->SetData(m_SurfaceInterpolator->GetContoursAsSurface()); + const auto segmentation = dynamic_cast(workingNode->GetData()); - this->Show3DInterpolationResult(true); - - if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) + if (segmentation == nullptr) { - m_DataStorage->Add(m_InterpolatedSurfaceNode); + MITK_ERROR << "Run3DInterpolation triggered with no MultiLabelSegmentation as working data."; + return; } - } - else if (interpolatedSurface.IsNull()) - { - m_BtnApply3D->setEnabled(false); + mitk::Surface::Pointer interpolatedSurface = m_SurfaceInterpolator->GetInterpolationResult(segmentation, m_CurrentActiveLabelValue, segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint)); - // T28261 - // m_BtnSuggestPlane->setEnabled(false); + if (interpolatedSurface.IsNotNull()) + { + m_BtnApply3D->setEnabled(true);; - if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) + m_InterpolatedSurfaceNode->SetData(interpolatedSurface); + this->Show3DInterpolationResult(true); + + if (!m_DataStorage->Exists(m_InterpolatedSurfaceNode)) + { + m_DataStorage->Add(m_InterpolatedSurfaceNode); + } + } + else { - this->Show3DInterpolationResult(false); + m_BtnApply3D->setEnabled(false); + + if (m_DataStorage->Exists(m_InterpolatedSurfaceNode)) + { + this->Show3DInterpolationResult(false); + } } } m_BtnReinit3DInterpolation->setEnabled(true); for (auto* slicer : m_ControllerToSliceObserverTag.keys()) { slicer->GetRenderer()->RequestUpdate(); } - m_SurfaceInterpolator->ReinitializeInterpolation(); } void QmitkSlicesInterpolator::OnAcceptInterpolationClicked() { auto* workingNode = m_ToolManager->GetWorkingData(0); auto* planeGeometry = m_LastSNC->GetCurrentPlaneGeometry(); auto* interpolatedPreview = dynamic_cast(m_FeedbackNode->GetData()); if (nullptr == workingNode || nullptr == interpolatedPreview) return; auto* segmentationImage = dynamic_cast(workingNode->GetData()); if (nullptr == segmentationImage) return; if (!segmentationImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the " "time bounds of segmentation. Time point: " << m_TimePoint; return; } const auto timeStep = segmentationImage->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint); auto interpolatedSlice = mitk::SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, segmentationImage, timeStep)->Clone(); - auto labelSet = segmentationImage->GetActiveLabelSet(); - auto activeValue = labelSet->GetActiveLabel()->GetValue(); + auto activeValue = segmentationImage->GetActiveLabel()->GetValue(); mitk::TransferLabelContentAtTimeStep( interpolatedPreview, interpolatedSlice, - labelSet, + segmentationImage->GetConstLabelsByValue(segmentationImage->GetLabelValuesByGroup(segmentationImage->GetActiveLayer())), timeStep, 0, - mitk::LabelSetImage::UnlabeledValue, + mitk::LabelSetImage::UNLABELED_VALUE, false, - { {0, mitk::LabelSetImage::UnlabeledValue}, {1, activeValue} } + { {0, mitk::LabelSetImage::UNLABELED_VALUE}, {1, activeValue} } ); mitk::SegTool2D::WriteBackSegmentationResult(workingNode, planeGeometry, interpolatedSlice, timeStep); m_FeedbackNode->SetData(nullptr); } void QmitkSlicesInterpolator::AcceptAllInterpolations(mitk::SliceNavigationController *slicer) { /* * What exactly is done here: * 1. We create an empty diff image for the current segmentation * 2. All interpolated slices are written into the diff image * 3. Then the diffimage is applied to the original segmentation */ if (m_Segmentation) { mitk::Image::Pointer segmentation3D = m_Segmentation; unsigned int timeStep = 0; if (4 == m_Segmentation->GetDimension()) { const auto* geometry = m_Segmentation->GetTimeGeometry(); if (!geometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept all interpolations. Time point selected by passed SliceNavigationController is not " "within the time bounds of segmentation. Time point: " << m_TimePoint; return; } mitk::Image::Pointer activeLabelImage; try { auto labelSetImage = dynamic_cast(m_Segmentation); - activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(), true, 0); + activeLabelImage = labelSetImage->CreateLabelMask(labelSetImage->GetActiveLabel()->GetValue()); } catch (const std::exception& e) { MITK_ERROR << e.what() << " | NO LABELSETIMAGE IN WORKING NODE\n"; } m_Interpolator->SetSegmentationVolume(activeLabelImage); timeStep = geometry->TimePointToTimeStep(m_TimePoint); auto timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(m_Segmentation); timeSelector->SetTimeNr(timeStep); timeSelector->Update(); segmentation3D = timeSelector->GetOutput(); } // Create an empty diff image for the undo operation auto diffImage = mitk::Image::New(); diffImage->Initialize(segmentation3D); // Create scope for ImageWriteAccessor so that the accessor is destroyed right after use { mitk::ImageWriteAccessor accessor(diffImage); // Set all pixels to zero auto pixelType = mitk::MakeScalarPixelType(); // For legacy purpose support former pixel type of segmentations (before multilabel) if (itk::IOComponentEnum::UCHAR == m_Segmentation->GetImageDescriptor()->GetChannelDescriptor().GetPixelType().GetComponentType()) pixelType = mitk::MakeScalarPixelType(); memset(accessor.GetData(), 0, pixelType.GetSize() * diffImage->GetDimension(0) * diffImage->GetDimension(1) * diffImage->GetDimension(2)); } // Since we need to shift the plane it must be clone so that the original plane isn't altered auto slicedGeometry = m_Segmentation->GetSlicedGeometry(); auto planeGeometry = slicer->GetCurrentPlaneGeometry()->Clone(); int sliceDimension = -1; int sliceIndex = -1; mitk::SegTool2D::DetermineAffectedImageSlice(m_Segmentation, planeGeometry, sliceDimension, sliceIndex); const auto numSlices = m_Segmentation->GetDimension(sliceDimension); mitk::ProgressBar::GetInstance()->AddStepsToDo(numSlices); std::atomic_uint totalChangedSlices; // Reuse interpolation algorithm instance for each slice to cache boundary calculations auto algorithm = mitk::ShapeBasedInterpolationAlgorithm::New(); // Distribute slice interpolations to multiple threads const auto numThreads = std::min(std::thread::hardware_concurrency(), numSlices); - // 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(); + const mitk::Label::PixelType newDestinationLabel = dynamic_cast(m_Segmentation)->GetActiveLabel()->GetValue(); // Do and Undo Operations if (totalChangedSlices > 0) { // Create do/undo operations auto* doOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); auto* undoOp = new mitk::ApplyDiffImageOperation(mitk::OpTEST, m_Segmentation, diffImage, timeStep); undoOp->SetFactor(-1.0); auto comment = "Confirm all interpolations (" + std::to_string(totalChangedSlices) + ")"; auto* undoStackItem = new mitk::OperationEvent(mitk::DiffImageApplier::GetInstanceForUndo(), doOp, undoOp, comment); mitk::OperationEvent::IncCurrGroupEventId(); mitk::OperationEvent::IncCurrObjectEventId(); mitk::UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); mitk::DiffImageApplier::GetInstanceForUndo()->SetDestinationLabel(newDestinationLabel); // Apply the changes to the original image mitk::DiffImageApplier::GetInstanceForUndo()->ExecuteOperation(doOp); } m_FeedbackNode->SetData(nullptr); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSlicesInterpolator::FinishInterpolation(mitk::SliceNavigationController *slicer) { // this redirect is for calling from outside if (slicer == nullptr) OnAcceptAllInterpolationsClicked(); else AcceptAllInterpolations(slicer); } void QmitkSlicesInterpolator::OnAcceptAllInterpolationsClicked() { QMenu orientationPopup(this); for (auto it = m_ActionToSlicerMap.begin(); it != m_ActionToSlicerMap.end(); ++it) { orientationPopup.addAction(it->first); } connect(&orientationPopup, SIGNAL(triggered(QAction *)), this, SLOT(OnAcceptAllPopupActivated(QAction *))); orientationPopup.exec(QCursor::pos()); } void QmitkSlicesInterpolator::OnAccept3DInterpolationClicked() { auto referenceImage = GetData(m_ToolManager->GetReferenceData(0)); auto* segmentationDataNode = m_ToolManager->GetWorkingData(0); auto labelSetImage = dynamic_cast(segmentationDataNode->GetData()); - auto activeLabelColor = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetColor(); - std::string activeLabelName = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetName(); + auto activeLabelColor = labelSetImage->GetActiveLabel()->GetColor(); + std::string activeLabelName = labelSetImage->GetActiveLabel()->GetName(); auto segmentation = GetData(segmentationDataNode); if (referenceImage.IsNull() || segmentation.IsNull()) return; const auto* segmentationGeometry = segmentation->GetTimeGeometry(); if (!referenceImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint) || !segmentationGeometry->IsValidTimePoint(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Current time point is not within the time bounds of the patient image and segmentation."; return; } auto interpolatedSurface = GetData(m_InterpolatedSurfaceNode); if (interpolatedSurface.IsNull()) return; auto surfaceToImageFilter = mitk::SurfaceToImageFilter::New(); surfaceToImageFilter->SetImage(referenceImage); surfaceToImageFilter->SetMakeOutputBinary(true); surfaceToImageFilter->SetUShortBinaryPixelType(itk::IOComponentEnum::USHORT == segmentation->GetPixelType().GetComponentType()); surfaceToImageFilter->SetInput(interpolatedSurface); surfaceToImageFilter->Update(); mitk::Image::Pointer interpolatedSegmentation = surfaceToImageFilter->GetOutput(); auto timeStep = segmentationGeometry->TimePointToTimeStep(m_TimePoint); - const mitk::Label::PixelType newDestinationLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); + const mitk::Label::PixelType newDestinationLabel = labelSetImage->GetActiveLabel()->GetValue(); TransferLabelContentAtTimeStep( interpolatedSegmentation, labelSetImage, - labelSetImage->GetActiveLabelSet(), + labelSetImage->GetConstLabelsByValue(labelSetImage->GetLabelValuesByGroup(labelSetImage->GetActiveLayer())), timeStep, 0, 0, false, {{1, newDestinationLabel}}, mitk::MultiLabelSegmentation::MergeStyle::Merge, mitk::MultiLabelSegmentation::OverwriteStyle::RegardLocks); this->Show3DInterpolationResult(false); std::string name = segmentationDataNode->GetName() + " 3D-interpolation - " + activeLabelName; mitk::TimeBounds timeBounds; if (1 < interpolatedSurface->GetTimeSteps()) { name += "_t" + std::to_string(timeStep); auto* polyData = vtkPolyData::New(); polyData->DeepCopy(interpolatedSurface->GetVtkPolyData(timeStep)); auto surface = mitk::Surface::New(); surface->SetVtkPolyData(polyData); interpolatedSurface = surface; timeBounds = segmentationGeometry->GetTimeBounds(timeStep); } else { timeBounds = segmentationGeometry->GetTimeBounds(0); } auto* surfaceGeometry = static_cast(interpolatedSurface->GetTimeGeometry()); surfaceGeometry->SetFirstTimePoint(timeBounds[0]); surfaceGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); // Typical file formats for surfaces do not save any time-related information. As a workaround at least for MITK scene files, we have the // possibility to seralize this information as properties. interpolatedSurface->SetProperty("ProportionalTimeGeometry.FirstTimePoint", mitk::FloatProperty::New(surfaceGeometry->GetFirstTimePoint())); interpolatedSurface->SetProperty("ProportionalTimeGeometry.StepDuration", mitk::FloatProperty::New(surfaceGeometry->GetStepDuration())); auto interpolatedSurfaceDataNode = mitk::DataNode::New(); interpolatedSurfaceDataNode->SetData(interpolatedSurface); interpolatedSurfaceDataNode->SetName(name); interpolatedSurfaceDataNode->SetOpacity(0.7f); interpolatedSurfaceDataNode->SetColor(activeLabelColor); m_DataStorage->Add(interpolatedSurfaceDataNode, segmentationDataNode); } void QmitkSlicesInterpolator::OnReinit3DInterpolation() { // Step 1. Load from the isContourPlaneGeometry nodes the contourNodes. mitk::NodePredicateProperty::Pointer pred = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = m_DataStorage->GetDerivations(m_ToolManager->GetWorkingData(0), pred); if (contourNodes->Size() != 0) { - 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(); if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } - // Adding layer, label and timeStep information for the contourNodes. + mitk::SurfaceInterpolationController::CPIVector newCPIs; + // Adding label and timeStep information for the contourNodes. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto contourNode = it->Value(); - auto 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); + auto groupID = labelSetImage->GetGroupIndexOfLabel(labelID); + auto sliceImage = ExtractSliceFromImage(labelSetImage->GetGroupImage(groupID), planeGeometry, timeStep); mitk::ImageToContourFilter::Pointer contourExtractor = mitk::ImageToContourFilter::New(); contourExtractor->SetInput(sliceImage); contourExtractor->SetContourValue(labelID); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) continue; - 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); + newCPIs.emplace_back(contour, planeGeometry->Clone(),labelID,timeStep); } - 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); + m_SurfaceInterpolator->CompleteReinitialization(newCPIs); } catch(const std::exception& e) { MITK_ERROR << "Exception thrown casting toolmanager working data to labelsetImage"; } } } else { m_BtnApply3D->setEnabled(false); QMessageBox errorInfo; errorInfo.setWindowTitle("Reinitialize surface interpolation"); errorInfo.setIcon(QMessageBox::Information); errorInfo.setText("No contours available for the selected segmentation!"); errorInfo.exec(); } } void QmitkSlicesInterpolator::OnAcceptAllPopupActivated(QAction *action) { try { auto iter = m_ActionToSlicerMap.find(action); if (iter != m_ActionToSlicerMap.end()) { mitk::SliceNavigationController *slicer = iter->second; this->AcceptAllInterpolations(slicer); } } catch (...) { /* Showing message box with possible memory error */ QMessageBox errorInfo; errorInfo.setWindowTitle("Interpolation Process"); errorInfo.setIcon(QMessageBox::Critical); errorInfo.setText("An error occurred during interpolation. Possible cause: Not enough memory!"); errorInfo.exec(); - // 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) { auto labelSetImage = dynamic_cast(workingNode->GetData()); if (nullptr == labelSetImage) { MITK_ERROR << "NO LABELSETIMAGE IN WORKING NODE\n"; mitk::RenderingManager::GetInstance()->RequestUpdateAll(); return; } - auto* activeLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel(); - auto* segmentation = dynamic_cast(workingNode->GetData()); + const auto* activeLabel = labelSetImage->GetActiveLabel(); + const auto* segmentation = dynamic_cast(workingNode->GetData()); if (nullptr != activeLabel && nullptr != segmentation) { - auto activeLabelImage = labelSetImage->CreateLabelMask(activeLabel->GetValue(), true, 0); + auto activeLabelImage = labelSetImage->CreateLabelMask(activeLabel->GetValue()); m_Interpolator->SetSegmentationVolume(activeLabelImage); if (referenceNode) { mitk::Image *referenceImage = dynamic_cast(referenceNode->GetData()); m_Interpolator->SetReferenceVolume(referenceImage); // may be nullptr } } } } this->UpdateVisibleSuggestion(); } void QmitkSlicesInterpolator::Run3DInterpolation() { - m_SurfaceInterpolator->Interpolate(); + auto workingNode = m_ToolManager->GetWorkingData(0); + + if (workingNode == nullptr) + { + MITK_ERROR << "Run3DInterpolation triggered with no working data set."; + return; + } + + const auto segmentation = dynamic_cast(workingNode->GetData()); + + if (segmentation == nullptr) + { + MITK_ERROR << "Run3DInterpolation triggered with no MultiLabelSegmentation as working data."; + return; + } + + if (!segmentation->ExistLabel(m_CurrentActiveLabelValue)) + { + MITK_ERROR << "Run3DInterpolation triggered with no valid label selected. Currently selected invalid label: "<Interpolate(segmentation,m_CurrentActiveLabelValue,segmentation->GetTimeGeometry()->TimePointToTimeStep(m_TimePoint)); } void QmitkSlicesInterpolator::StartUpdateInterpolationTimer() { m_Timer->start(500); } void QmitkSlicesInterpolator::StopUpdateInterpolationTimer() { if(m_ToolManager) { - auto* workingNode = m_ToolManager->GetWorkingData(0); - auto activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabelSet()->GetActiveLabel()->GetColor(); + const auto* workingNode = m_ToolManager->GetWorkingData(0); + const auto activeColor = dynamic_cast(workingNode->GetData())->GetActiveLabel()->GetColor(); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); - m_3DContourNode->SetProperty("color", mitk::ColorProperty::New(activeColor)); } m_Timer->stop(); } void QmitkSlicesInterpolator::ChangeSurfaceColor() { float currentColor[3]; m_InterpolatedSurfaceNode->GetColor(currentColor); m_InterpolatedSurfaceNode->SetProperty("color", mitk::ColorProperty::New(SURFACE_COLOR_RGB)); m_InterpolatedSurfaceNode->Update(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(mitk::RenderingManager::REQUEST_UPDATE_3DWINDOWS); } -void QmitkSlicesInterpolator::PrepareInputsFor3DInterpolation() -{ - if (m_DataStorage.IsNotNull() && m_ToolManager && m_3DInterpolationEnabled) - { - auto *workingNode = m_ToolManager->GetWorkingData(0); - if (workingNode != nullptr) - { - 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(); - } - - auto labelSetImage = dynamic_cast(workingNode->GetData()); - auto activeLabel = labelSetImage->GetActiveLabelSet()->GetActiveLabel()->GetValue(); - - m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(activeLabel); - - if (m_Watcher.isRunning()) - m_Watcher.waitForFinished(); - - if (ret == QMessageBox::Yes) - { - // Maybe set the segmentation node here - m_Future = QtConcurrent::run(&QmitkSlicesInterpolator::Run3DInterpolation, this); - 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(); + auto workingNode = m_ToolManager->GetWorkingData(0); - if (m_3DInterpolationEnabled) + if (workingNode == nullptr) { - m_3DContourNode->SetData(nullptr); - m_InterpolatedSurfaceNode->SetData(nullptr); - auto* workingNode = m_ToolManager->GetWorkingData(0); + MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no working data set."; + return; + } - if (workingNode == nullptr) - return; + const auto segmentation = dynamic_cast(workingNode->GetData()); - auto* labelSetImage = dynamic_cast(workingNode->GetData()); - auto* label = labelSetImage->GetActiveLabelSet()->GetActiveLabel(); + if (segmentation == nullptr) + { + MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no MultiLabelSegmentation as working data."; + return; + } - if (label == nullptr) - return; + if (!segmentation->ExistLabel(m_CurrentActiveLabelValue)) + { + MITK_DEBUG << "OnSurfaceInterpolationInfoChanged triggered with no valid label selected. Currently selected invalid label: " << m_CurrentActiveLabelValue; + return; + } - m_SurfaceInterpolator->AddActiveLabelContoursForInterpolation(label->GetValue()); + if (m_Watcher.isRunning()) + m_Watcher.waitForFinished(); + + if (m_3DInterpolationEnabled) + { + m_InterpolatedSurfaceNode->SetData(nullptr); m_Future = QtConcurrent::run(&QmitkSlicesInterpolator::Run3DInterpolation, this); m_Watcher.setFuture(m_Future); } } void QmitkSlicesInterpolator::SetCurrentContourListID() { // New ContourList = hide current interpolation Show3DInterpolationResult(false); if (m_DataStorage.IsNotNull() && m_ToolManager && m_LastSNC) { mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); - 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); - // 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(m_TimePoint)) { MITK_WARN << "Cannot accept interpolation. Time point selected by SliceNavigationController is not within the time bounds of WorkingImage. Time point: " << m_TimePoint; return; } - // Sets up the surface interpolator to accept - time_position = workingNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(m_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()); + auto segmentationImage = dynamic_cast(workingNode->GetData()); m_SurfaceInterpolator->SetCurrentInterpolationSession(segmentationImage); - m_SurfaceInterpolator->SetCurrentTimePoint(m_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_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::MessageDelegate1( - 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::MessageDelegate1( - 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::MessageDelegate1( - 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::MessageDelegate1( - 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(mitk::Label::PixelType /*removedLabelValue*/) -{ - 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) - { - if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(m_TimePoint)) - { - MITK_ERROR << "Invalid time point requested for interpolation pipeline."; - return; - } - auto timeStep = labelSetImage->GetTimeGeometry()->TimePointToTimeStep(m_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) -{ - auto* contours = m_SurfaceInterpolator->GetContours(timeStep, layerID); - - if (nullptr == contours) - return; - - 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()); -} - -void QmitkSlicesInterpolator::ClearSegmentationObservers() -{ - 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(); -} diff --git a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h index 331990c3ed..3960e2888c 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h +++ b/Modules/SegmentationUI/Qmitk/QmitkSlicesInterpolator.h @@ -1,428 +1,344 @@ /*============================================================================ 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 "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 TimeNavigationController; } 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& 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(); + void SetActiveLabelValue(mitk::LabelSetImage::LabelValueType labelValue); /** 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(mitk::Label::PixelType removedLabelValue); - - /** - * @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: typedef std::map ActionToSliceDimensionMapType; const ActionToSliceDimensionMapType CreateActionToSlicer(const QList& windows); ActionToSliceDimensionMapType m_ActionToSlicerMap; 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 sliceNavigationController the SliceNavigationController */ bool TranslateAndInterpolateChangedSlice(const itk::EventObject &e, mitk::SliceNavigationController *sliceNavigationController); bool TranslateAndInterpolateChangedSlice(const mitk::TimeGeometry* timeGeometry); /** 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); /** 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); - void ClearSegmentationObservers(); 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; unsigned int 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; mitk::TimePointType m_TimePoint; 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/Modules/SegmentationUI/test/QmitkMultiLabelTreeModelTest.cpp b/Modules/SegmentationUI/test/QmitkMultiLabelTreeModelTest.cpp index 30ffa512b0..6a4c385cf9 100644 --- a/Modules/SegmentationUI/test/QmitkMultiLabelTreeModelTest.cpp +++ b/Modules/SegmentationUI/test/QmitkMultiLabelTreeModelTest.cpp @@ -1,543 +1,541 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include "QmitkMultiLabelTreeModel.h" #include #include class QmitkMultiLabelTreeModelTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(QmitkMultiLabelTreeModelTestSuite); MITK_TEST(NullTest); MITK_TEST(GetterSetterTest); MITK_TEST(AddingLabelTest); MITK_TEST(AddingLayerTest); MITK_TEST(RemovingLabelTest); MITK_TEST(RemovingLayerTest); MITK_TEST(ModifyLabelNameTest); MITK_TEST(ModifyLabelTest); CPPUNIT_TEST_SUITE_END(); mitk::LabelSetImage::Pointer m_Segmentation; QCoreApplication* m_TestApp; public: mitk::LabelSetImage::Pointer GenerateSegmentation() { // Create a new labelset image auto seg = mitk::LabelSetImage::New(); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 5, 5, 5 }; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); seg->Initialize(regularImage); return seg; } QColor GetQColor(const mitk::Label* label) { return QColor(label->GetColor().GetRed() * 255, label->GetColor().GetGreen() * 255, label->GetColor().GetBlue() * 255); } mitk::Label::Pointer CreateLabel(const std::string& name, mitk::Label::PixelType value) { auto label = mitk::Label::New(); label->SetName(name); label->SetValue(value); label->SetColor(mitk::Color(value / 255.)); return label; } /** Populate a seg with a following setup (in brackets the order of addition). * - Group 1 (1) * - Label A * - Instance 1 (1) * - Instance 5 (2) * - Instance 10 (8) * - Label B * - Instance 4 (3) * - Label D * - Instance 2 (7) * - Group 2 (4) * - Group 3 (5) * - Label B * - Instance 9 (6) */ void PopulateSegmentation(mitk::LabelSetImage* seg) { seg->SetActiveLayer(0); - seg->GetActiveLabelSet()->AddLabel(CreateLabel("A", 1)); - seg->GetActiveLabelSet()->AddLabel(CreateLabel("A", 5)); - seg->GetActiveLabelSet()->AddLabel(CreateLabel("B", 4)); + + seg->AddLabel(CreateLabel("A", 1),0); + seg->AddLabel(CreateLabel("A", 5),0); + seg->AddLabel(CreateLabel("B", 4),0); + seg->AddLayer(); seg->AddLayer(); - seg->SetActiveLayer(2); - seg->GetActiveLabelSet()->AddLabel(CreateLabel("B", 9)); - seg->SetActiveLayer(0); - seg->GetActiveLabelSet()->AddLabel(CreateLabel("D", 2)); - seg->GetActiveLabelSet()->AddLabel(CreateLabel("A", 10)); + + seg->AddLabel(CreateLabel("B", 9),2); + + seg->AddLabel(CreateLabel("D", 2),0); + seg->AddLabel(CreateLabel("A", 10),0); } void setUp() override { m_Segmentation = GenerateSegmentation(); PopulateSegmentation(m_Segmentation); int argc = 0; char** argv = nullptr; m_TestApp = new QCoreApplication(argc, argv); } void tearDown() override { delete m_TestApp; } QModelIndex GetIndex(const QmitkMultiLabelTreeModel& model, const std::vector& rows, int column = 0) const { QModelIndex testIndex; int i = 0; for (auto row : rows) { if (static_cast::size_type>(i) + 1 < rows.size()) { testIndex = model.index(row, 0, testIndex); } else { testIndex = model.index(row, column, testIndex); } i++; } return testIndex; } bool CheckModelItem(const QmitkMultiLabelTreeModel& model, const std::vector& rows, const QVariant& reference, int column, const mitk::Label* /*label = nullptr*/) const { QModelIndex testIndex = GetIndex(model, rows, column); auto value = model.data(testIndex); bool test = value == reference; if (!test) std::cerr << std::endl <<" Model item error. Expected: '" << reference.toString().toStdString() << "'; actual: '" << value.toString().toStdString() <<"'"; return test; } bool CheckModelRow(const QmitkMultiLabelTreeModel& model, const std::vector& rows, const std::vector references) const { int column = 0; bool test = true; for (const auto& ref : references) { test = test & CheckModelItem(model, rows, ref, column, nullptr); column++; } return test; } void CheckModelGroup0Default(const QmitkMultiLabelTreeModel& model) { CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A (3 instances)"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,0 }, { QString("A [1]"), QVariant(true), QVariant(QColor(1,1,1)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,1 }, { QString("A [5]"), QVariant(true), QVariant(QColor(5,5,5)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,2 }, { QString("A [10]"), QVariant(true), QVariant(QColor(10,10,10)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); } void CheckModelGroup1Default(const QmitkMultiLabelTreeModel& model) { CPPUNIT_ASSERT(CheckModelRow(model, { 1 }, { QString("Group 1"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 1 }))); } void CheckModelGroup2Default(const QmitkMultiLabelTreeModel& model) { CPPUNIT_ASSERT(CheckModelRow(model, { 2 }, { QString("Group 2"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(1, model.rowCount(GetIndex(model, { 2 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 2,0 }, { QString("B"), QVariant(true), QVariant(QColor(9,9,9)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 2,0 }))); } void CheckModelDefault(const QmitkMultiLabelTreeModel& model) { CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CheckModelGroup0Default(model); CheckModelGroup1Default(model); CheckModelGroup2Default(model); } void NullTest() { QmitkMultiLabelTreeModel model(nullptr); CPPUNIT_ASSERT(nullptr == model.GetSegmentation()); } void GetterSetterTest() { QmitkMultiLabelTreeModel model(nullptr); model.SetSegmentation(m_Segmentation); CheckModelDefault(model); model.SetSegmentation(nullptr); CPPUNIT_ASSERT(nullptr == model.GetSegmentation()); CPPUNIT_ASSERT(false == model.hasChildren(QModelIndex())); } void AddingLabelTest() { QmitkMultiLabelTreeModel model(nullptr); model.SetSegmentation(m_Segmentation); //Add label instance (not visible) to labelwith multiple instances (at the end) m_Segmentation->SetActiveLayer(0); auto newLabel = CreateLabel("A", 100); newLabel->SetVisible(false); - m_Segmentation->GetActiveLabelSet()->AddLabel(newLabel); + m_Segmentation->AddLabel(newLabel,0); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A (4 instances)"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(4, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,0 }, { QString("A [1]"), QVariant(true), QVariant(QColor(1,1,1)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,1 }, { QString("A [5]"), QVariant(true), QVariant(QColor(5,5,5)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,2 }, { QString("A [10]"), QVariant(true), QVariant(QColor(10,10,10)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,3 }, { QString("A [100]"), QVariant(true), QVariant(QColor(100,100,100)), QVariant(false) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); CheckModelGroup1Default(model); CheckModelGroup2Default(model); //Add label instance (not locked) to label with multiple instances (in between) m_Segmentation->SetActiveLayer(0); newLabel = CreateLabel("A", 7); newLabel->SetLocked(false); - m_Segmentation->GetActiveLabelSet()->AddLabel(newLabel); + m_Segmentation->AddLabel(newLabel,0); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A (5 instances)"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(5, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,0 }, { QString("A [1]"), QVariant(true), QVariant(QColor(1,1,1)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,1 }, { QString("A [5]"), QVariant(true), QVariant(QColor(5,5,5)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,2 }, { QString("A [7]"), QVariant(false), QVariant(QColor(7,7,7)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,3 }, { QString("A [10]"), QVariant(true), QVariant(QColor(10,10,10)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,4 }, { QString("A [100]"), QVariant(true), QVariant(QColor(100,100,100)), QVariant(false) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); CheckModelGroup1Default(model); CheckModelGroup2Default(model); //reset everything m_Segmentation = GenerateSegmentation(); PopulateSegmentation(m_Segmentation); model.SetSegmentation(m_Segmentation); //Add label instance to an empty group m_Segmentation->SetActiveLayer(1); newLabel = CreateLabel("A", 3); - m_Segmentation->GetActiveLabelSet()->AddLabel(newLabel); + m_Segmentation->AddLabel(newLabel,1); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CheckModelGroup0Default(model); CPPUNIT_ASSERT(CheckModelRow(model, { 1 }, { QString("Group 1"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(1, model.rowCount(GetIndex(model, { 1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 1,0 }, { QString("A"), QVariant(true), QVariant(QColor(3,3,3)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 1,0 }))); CheckModelGroup2Default(model); } void AddingLayerTest() { QmitkMultiLabelTreeModel model(nullptr); model.SetSegmentation(m_Segmentation); m_Segmentation->AddLayer(); CPPUNIT_ASSERT_EQUAL(4, model.rowCount(QModelIndex())); CheckModelGroup0Default(model); CheckModelGroup1Default(model); CheckModelGroup2Default(model); CPPUNIT_ASSERT(CheckModelRow(model, { 3 }, { QString("Group 3"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 3 }))); } void RemovingLabelTest() { QmitkMultiLabelTreeModel model(nullptr); model.SetSegmentation(m_Segmentation); //remove label instance from label with multiple instances (middel) m_Segmentation->RemoveLabel(5); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A (2 instances)"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(2, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,0 }, { QString("A [1]"), QVariant(true), QVariant(QColor(1,1,1)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,1 }, { QString("A [10]"), QVariant(true), QVariant(QColor(10,10,10)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); CheckModelGroup1Default(model); CheckModelGroup2Default(model); //remove label instance from label with multiple instances (first) m_Segmentation->RemoveLabel(1); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A"), QVariant(true), QVariant(QColor(10,10,10)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); CheckModelGroup1Default(model); CheckModelGroup2Default(model); //reset everything m_Segmentation = GenerateSegmentation(); PopulateSegmentation(m_Segmentation); model.SetSegmentation(m_Segmentation); //remove label instance from label with multiple instances (at the end) m_Segmentation->RemoveLabel(10); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A (2 instances)"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(2, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,0 }, { QString("A [1]"), QVariant(true), QVariant(QColor(1,1,1)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,1 }, { QString("A [5]"), QVariant(true), QVariant(QColor(5,5,5)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); CheckModelGroup1Default(model); CheckModelGroup2Default(model); //reset everything m_Segmentation = GenerateSegmentation(); PopulateSegmentation(m_Segmentation); model.SetSegmentation(m_Segmentation); //remove label instance from label with only one instance m_Segmentation->RemoveLabel(9); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CheckModelGroup0Default(model); CheckModelGroup1Default(model); CPPUNIT_ASSERT(CheckModelRow(model, { 2 }, { QString("Group 2"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 2 }))); } void RemovingLayerTest() { QmitkMultiLabelTreeModel model(nullptr); model.SetSegmentation(m_Segmentation); //remove group in the middle - m_Segmentation->SetActiveLayer(1); - m_Segmentation->RemoveLayer(); + m_Segmentation->RemoveGroup(1); CPPUNIT_ASSERT_EQUAL(2, model.rowCount(QModelIndex())); CheckModelGroup0Default(model); CPPUNIT_ASSERT(CheckModelRow(model, { 1 }, { QString("Group 1"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(1, model.rowCount(GetIndex(model, { 1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 1,0 }, { QString("B"), QVariant(true), QVariant(QColor(9,9,9)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 1,0 }))); //remove groups in the end - m_Segmentation->SetActiveLayer(1); - m_Segmentation->RemoveLayer(); + m_Segmentation->RemoveGroup(1); CPPUNIT_ASSERT_EQUAL(1, model.rowCount(QModelIndex())); CheckModelGroup0Default(model); //remove all groups - m_Segmentation->SetActiveLayer(0); - m_Segmentation->RemoveLayer(); + m_Segmentation->RemoveGroup(0); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(QModelIndex())); //reset everything m_Segmentation = GenerateSegmentation(); PopulateSegmentation(m_Segmentation); model.SetSegmentation(m_Segmentation); //remove first group - m_Segmentation->SetActiveLayer(0); - m_Segmentation->RemoveLayer(); + m_Segmentation->RemoveGroup(0); CPPUNIT_ASSERT_EQUAL(2, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 1 }, { QString("Group 1"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(1, model.rowCount(GetIndex(model, { 1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 1,0 }, { QString("B"), QVariant(true), QVariant(QColor(9,9,9)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 1,0 }))); } void ModifyLabelNameTest() { QmitkMultiLabelTreeModel model(nullptr); model.SetSegmentation(m_Segmentation); //move from multiple instance to new label in the middle - auto label = m_Segmentation->GetLabel(5,0); + auto label = m_Segmentation->GetLabel(5); label->SetName("C"); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(4, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A (2 instances)"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(2, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,0 }, { QString("A [1]"), QVariant(true), QVariant(QColor(1,1,1)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0,1 }, { QString("A [10]"), QVariant(true), QVariant(QColor(10,10,10)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("C"), QVariant(true), QVariant(QColor(5,5,5)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,3 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,3 }))); CheckModelGroup1Default(model); CheckModelGroup2Default(model); //move from multiple instance to new label at the end - label = m_Segmentation->GetLabel(10, 0); + label = m_Segmentation->GetLabel(10); label->SetName("E"); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(5, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A"), QVariant(true), QVariant(QColor(1,1,1)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("C"), QVariant(true), QVariant(QColor(5,5,5)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,3 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,3 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,4 }, { QString("E"), QVariant(true), QVariant(QColor(10,10,10)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,4 }))); CheckModelGroup1Default(model); CheckModelGroup2Default(model); //move last instance to new label - label = m_Segmentation->GetLabel(10, 0); + label = m_Segmentation->GetLabel(10); label->SetName("F"); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(5, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A"), QVariant(true), QVariant(QColor(1,1,1)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("C"), QVariant(true), QVariant(QColor(5,5,5)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,3 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,3 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,4 }, { QString("F"), QVariant(true), QVariant(QColor(10,10,10)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,4 }))); CheckModelGroup1Default(model); CheckModelGroup2Default(model); //move last instance to an existing label label->SetName("B"); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CPPUNIT_ASSERT(CheckModelRow(model, { 0 }, { QString("Group 0"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(4, model.rowCount(GetIndex(model, { 0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,0 }, { QString("A"), QVariant(true), QVariant(QColor(1,1,1)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1 }, { QString("B (2 instances)"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(2, model.rowCount(GetIndex(model, { 0,1 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1,0 }, { QString("B [4]"), QVariant(true), QVariant(QColor(4,4,4)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,1,1 }, { QString("B [10]"), QVariant(true), QVariant(QColor(10,10,10)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 0,2 }, { QString("C"), QVariant(true), QVariant(QColor(5,5,5)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,2 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 0,3 }, { QString("D"), QVariant(true), QVariant(QColor(2,2,2)), QVariant(true) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 0,3 }))); CheckModelGroup1Default(model); CheckModelGroup2Default(model); } void ModifyLabelTest() { QmitkMultiLabelTreeModel model(nullptr); model.SetSegmentation(m_Segmentation); - auto label = m_Segmentation->GetLabel(9, 2); + auto label = m_Segmentation->GetLabel(9); //check single instance modifications label->SetVisible(false); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CheckModelGroup0Default(model); CheckModelGroup1Default(model); CPPUNIT_ASSERT(CheckModelRow(model, { 2 }, { QString("Group 2"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(1, model.rowCount(GetIndex(model, { 2 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 2,0 }, { QString("B"), QVariant(true), QVariant(QColor(9,9,9)), QVariant(false) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 2,0 }))); label->SetLocked(false); label->SetColor(mitk::Color(22 / 255.)); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CheckModelGroup0Default(model); CheckModelGroup1Default(model); CPPUNIT_ASSERT(CheckModelRow(model, { 2 }, { QString("Group 2"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(1, model.rowCount(GetIndex(model, { 2 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 2,0 }, { QString("B"), QVariant(false), QVariant(QColor(22,22,22)), QVariant(false) })); CPPUNIT_ASSERT_EQUAL(0, model.rowCount(GetIndex(model, { 2,0 }))); //check instance modifications with multi instance label m_Segmentation->SetActiveLayer(2); - m_Segmentation->GetActiveLabelSet()->AddLabel(CreateLabel("B", 33)); + m_Segmentation->AddLabel(CreateLabel("B", 33),2); label->SetVisible(true); CPPUNIT_ASSERT_EQUAL(3, model.rowCount(QModelIndex())); CheckModelGroup0Default(model); CheckModelGroup1Default(model); CPPUNIT_ASSERT(CheckModelRow(model, { 2 }, { QString("Group 2"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(1, model.rowCount(GetIndex(model, { 2 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 2,0 }, { QString("B (2 instances)"), QVariant(), QVariant(), QVariant() })); CPPUNIT_ASSERT_EQUAL(2, model.rowCount(GetIndex(model, { 2,0 }))); CPPUNIT_ASSERT(CheckModelRow(model, { 2,0,0 }, { QString("B [9]"), QVariant(false), QVariant(QColor(22,22,22)), QVariant(true) })); CPPUNIT_ASSERT(CheckModelRow(model, { 2,0,1 }, { QString("B [33]"), QVariant(true), QVariant(QColor(33,33,33)), QVariant(true) })); } }; MITK_TEST_SUITE_REGISTRATION(QmitkMultiLabelTreeModel) diff --git a/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp b/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp index 26418f9785..3c25a2851d 100644 --- a/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp +++ b/Modules/SurfaceInterpolation/Testing/mitkSurfaceInterpolationControllerTest.cpp @@ -1,1097 +1,660 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include +#include "mitkImageAccessByItk.h" #include #include #include #include #include #include #include +template +void ClearBufferProcessing(ImageType* itkImage) +{ + itkImage->FillBuffer(0); +} + class mitkSurfaceInterpolationControllerTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkSurfaceInterpolationControllerTestSuite); MITK_TEST(TestSingleton); MITK_TEST(TestSetCurrentInterpolationSession); - MITK_TEST(TestReplaceInterpolationSession); MITK_TEST(TestRemoveAllInterpolationSessions); MITK_TEST(TestRemoveInterpolationSession); MITK_TEST(TestOnSegmentationDeleted); + MITK_TEST(TestOnLabelRemoved); MITK_TEST(TestSetCurrentInterpolationSession4D); - MITK_TEST(TestReplaceInterpolationSession4D); MITK_TEST(TestRemoveAllInterpolationSessions4D); MITK_TEST(TestRemoveInterpolationSession4D); MITK_TEST(TestOnSegmentationDeleted4D); /// \todo Workaround for memory leak in TestAddNewContour. Bug 18096. vtkDebugLeaks::SetExitError(0); - MITK_TEST(TestAddNewContour); - MITK_TEST(TestRemoveContour); + MITK_TEST(TestAddNewContours); + MITK_TEST(TestRemoveContours); CPPUNIT_TEST_SUITE_END(); private: mitk::SurfaceInterpolationController::Pointer m_Controller; public: mitk::Image::Pointer createImage(unsigned int *dimensions) { mitk::Image::Pointer newImage = mitk::Image::New(); // mitk::LabelSetImage::Pointer newImage = mitk::LabelSetImage::New(); mitk::PixelType p_type = mitk::MakeScalarPixelType(); newImage->Initialize(p_type, 3, dimensions); + AccessFixedDimensionByItk(newImage, ClearBufferProcessing, 3); return newImage; } mitk::LabelSetImage::Pointer createLabelSetImage(unsigned int *dimensions) { mitk::Image::Pointer image = createImage(dimensions); mitk::LabelSetImage::Pointer newImage = mitk::LabelSetImage::New(); newImage->InitializeByLabeledImage(image); return newImage; } mitk::Image::Pointer createImage4D(unsigned int *dimensions) { mitk::Image::Pointer newImage = mitk::Image::New(); mitk::PixelType p_type = mitk::MakeScalarPixelType(); newImage->Initialize(p_type, 4, dimensions); return newImage; } mitk::LabelSetImage::Pointer createLabelSetImage4D(unsigned int *dimensions) { mitk::Image::Pointer image = createImage4D(dimensions); mitk::LabelSetImage::Pointer newImage = mitk::LabelSetImage::New(); newImage->InitializeByLabeledImage(image); return newImage; } void setUp() override { m_Controller = mitk::SurfaceInterpolationController::GetInstance(); m_Controller->RemoveAllInterpolationSessions(); - m_Controller->SetCurrentTimePoint(0); vtkSmartPointer polygonSource = vtkSmartPointer::New(); polygonSource->SetRadius(100); polygonSource->SetNumberOfSides(7); polygonSource->Update(); mitk::Surface::Pointer surface = mitk::Surface::New(); surface->SetVtkPolyData(polygonSource->GetOutput()); } void TestSingleton() { mitk::SurfaceInterpolationController::Pointer controller2 = mitk::SurfaceInterpolationController::GetInstance(); CPPUNIT_ASSERT_MESSAGE("SurfaceInterpolationController pointers are not equal!", m_Controller.GetPointer() == controller2.GetPointer()); } void TestSetCurrentInterpolationSession() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); - MITK_ASSERT_EQUAL( - m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not equal"); - CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); + CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); // Test 2 m_Controller->SetCurrentInterpolationSession(segmentation_2); - MITK_ASSERT_EQUAL( - m_Controller->GetCurrentSegmentation(), segmentation_2->Clone(), "Segmentation images are not equal"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); + m_Controller->GetCurrentSegmentation() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 3 m_Controller->SetCurrentInterpolationSession(segmentation_1); - MITK_ASSERT_EQUAL( - m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not equal"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); + m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 4 m_Controller->SetCurrentInterpolationSession(segmentation_1); - MITK_ASSERT_EQUAL( - m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not equal"); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); + m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // // Test 5 m_Controller->SetCurrentInterpolationSession(nullptr); - CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().IsNull()); + CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation() == nullptr); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } - mitk::PlaneGeometry::Pointer GetPlaneGeometry() + mitk::PlaneGeometry::Pointer CreatePlaneGeometry(mitk::ScalarType zIndex) { mitk::Point3D origin; - mitk::Vector3D right, bottom, normal, spacing; - mitk::ScalarType width, height; + mitk::Vector3D right, bottom, spacing; mitk::ScalarType widthInMM, heightInMM, thicknessInMM; auto planegeometry = mitk::PlaneGeometry::New(); - width = 100; - widthInMM = width; - height = 200; - heightInMM = height; + widthInMM = 100; + heightInMM = 200; thicknessInMM = 1.0; - mitk::FillVector3D(origin, 4.5, 7.3, 11.2); + mitk::FillVector3D(origin, 4.5, 7.3, zIndex*thicknessInMM); mitk::FillVector3D(right, widthInMM, 0, 0); mitk::FillVector3D(bottom, 0, heightInMM, 0); - mitk::FillVector3D(normal, 0, 0, thicknessInMM); mitk::FillVector3D(spacing, 1.0, 1.0, thicknessInMM); planegeometry->InitializeStandardPlane(right, bottom); planegeometry->SetOrigin(origin); planegeometry->SetSpacing(spacing); return planegeometry; } - void TestReplaceInterpolationSession() + mitk::Surface::Pointer CreateContour(int numOfSides) { - // Create segmentation image - unsigned int dimensions1[] = {10, 10, 10}; - mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); - - m_Controller->SetCurrentInterpolationSession(segmentation_1); - - // Create some contours - double center_1[3] = {1.25f, 3.43f, 4.44f}; - double normal_1[3] = {0.25f, 1.76f, 0.93f}; + double center_1[3] = { 1.25f, 3.43f, 4.44f }; + double normal_1[3] = { 0.25f, 1.76f, 0.93f }; vtkSmartPointer p_source = vtkSmartPointer::New(); - p_source->SetNumberOfSides(20); + p_source->SetNumberOfSides(numOfSides*10); p_source->SetCenter(center_1); p_source->SetRadius(4); p_source->SetNormal(normal_1); p_source->Update(); - vtkPolyData *poly_1 = p_source->GetOutput(); + vtkPolyData* poly_1 = p_source->GetOutput(); mitk::Surface::Pointer surf_1 = mitk::Surface::New(); surf_1->SetVtkPolyData(poly_1); - vtkSmartPointer int1Array = vtkSmartPointer::New(); - int1Array->InsertNextValue(1); - int1Array->InsertNextValue(0); - int1Array->InsertNextValue(0); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); - vtkSmartPointer double1Array = vtkSmartPointer::New(); - double1Array->InsertNextValue(center_1[0]); - double1Array->InsertNextValue(center_1[1]); - double1Array->InsertNextValue(center_1[2]); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); - - double center_2[3] = {4.0f, 4.0f, 4.0f}; - double normal_2[3] = {1.0f, 0.0f, 0.0f}; - vtkSmartPointer p_source_2 = vtkSmartPointer::New(); - p_source_2->SetNumberOfSides(80); - p_source_2->SetCenter(center_2); - p_source_2->SetRadius(4); - p_source_2->SetNormal(normal_2); - p_source_2->Update(); - vtkPolyData *poly_2 = p_source_2->GetOutput(); - mitk::Surface::Pointer surf_2 = mitk::Surface::New(); - surf_2->SetVtkPolyData(poly_2); - vtkSmartPointer int2Array = vtkSmartPointer::New(); - int2Array->InsertNextValue(1); - int2Array->InsertNextValue(0); - int2Array->InsertNextValue(0); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); - vtkSmartPointer doubleArray = vtkSmartPointer::New(); - doubleArray->InsertNextValue(center_2[0]); - doubleArray->InsertNextValue(center_2[1]); - doubleArray->InsertNextValue(center_2[2]); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(doubleArray); - - std::vector surfaces; - surfaces.push_back(surf_1); - surfaces.push_back(surf_2); - - const mitk::PlaneGeometry * planeGeometry1 = GetPlaneGeometry(); - const mitk::PlaneGeometry * planeGeometry2 = GetPlaneGeometry(); - - std::vector planeGeometries; - planeGeometries.push_back(planeGeometry1); - planeGeometries.push_back(planeGeometry2); - - // Add contours - m_Controller->AddNewContours(surfaces, planeGeometries, true); - - // Check if all contours are there - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo1; - - mitk::ScalarType n[3]; - vtkPolygon::ComputeNormal(surf_1->GetVtkPolyData()->GetPoints(), n); - contourInfo1.ContourNormal = n; - contourInfo1.ContourPoint = center_1; - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; - - vtkPolygon::ComputeNormal(surf_2->GetVtkPolyData()->GetPoints(), n); - - contourInfo2.ContourNormal = n; - contourInfo2.ContourPoint = center_2; - - - const mitk::Surface *contour_1 = m_Controller->GetContour(contourInfo1); - const mitk::Surface *contour_2 = m_Controller->GetContour(contourInfo2); - - - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); - CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_1->GetVtkPolyData()), 0.000001, true)); - CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_2->GetVtkPolyData()), 0.000001, true)); - - CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", - m_Controller->GetNumberOfInterpolationSessions() == 1); - - mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions1); - bool success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_2); - - CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == true); - CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); - - unsigned int dimensions2[] = {10, 20, 10}; - mitk::Image::Pointer segmentation_3 = createLabelSetImage(dimensions2); - success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_3); - CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == false); - CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", - m_Controller->GetNumberOfInterpolationSessions() == 1); - CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); + return surf_1; } void TestRemoveAllInterpolationSessions() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; auto segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; auto segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); m_Controller->RemoveAllInterpolationSessions(); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestRemoveInterpolationSession() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); unsigned int dimensions2[] = {20, 10, 30}; - mitk::Image::Pointer segmentation_2 = createLabelSetImage(dimensions2); + mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); + m_Controller->GetCurrentSegmentation() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", - m_Controller->GetCurrentSegmentation().IsNotNull()); + m_Controller->GetCurrentSegmentation() != nullptr); m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Current segmentation is not null after session was removed", - m_Controller->GetCurrentSegmentation().IsNull()); + m_Controller->GetCurrentSegmentation() == nullptr); } void TestOnSegmentationDeleted() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10}; auto segmentation_1 = createLabelSetImage(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); segmentation_1 = nullptr; CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } - void TestAddNewContour() + void TestAddNewContours() { // Create segmentation image - unsigned int dimensions1[] = {10, 10, 10}; - mitk::Image::Pointer segmentation_1 = createLabelSetImage(dimensions1); + unsigned int dimensions1[] = {10, 10, 10, 4}; + auto segmentation_1 = createLabelSetImage4D(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); - // Create some contours - double center_1[3] = {1.25f, 3.43f, 4.44f}; - double normal_1[3] = {0.25f, 1.76f, 0.93f}; - vtkSmartPointer p_source = vtkSmartPointer::New(); - p_source->SetNumberOfSides(20); - p_source->SetCenter(center_1); - p_source->SetRadius(4); - p_source->SetNormal(normal_1); - p_source->Update(); - vtkPolyData *poly_1 = p_source->GetOutput(); - mitk::Surface::Pointer surf_1 = mitk::Surface::New(); - surf_1->SetVtkPolyData(poly_1); - vtkSmartPointer int1Array = vtkSmartPointer::New(); - int1Array->InsertNextValue(1); - int1Array->InsertNextValue(0); - int1Array->InsertNextValue(0); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); - vtkSmartPointer double1Array = vtkSmartPointer::New(); - double1Array->InsertNextValue(center_1[0]); - double1Array->InsertNextValue(center_1[1]); - double1Array->InsertNextValue(center_1[2]); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); - - double center_2[3] = {4.0f, 4.0f, 4.0f}; - double normal_2[3] = {1.0f, 0.0f, 0.0f}; - vtkSmartPointer p_source_2 = vtkSmartPointer::New(); - p_source_2->SetNumberOfSides(80); - p_source_2->SetCenter(center_2); - p_source_2->SetRadius(4); - p_source_2->SetNormal(normal_2); - p_source_2->Update(); - vtkPolyData *poly_2 = p_source_2->GetOutput(); - mitk::Surface::Pointer surf_2 = mitk::Surface::New(); - surf_2->SetVtkPolyData(poly_2); - vtkSmartPointer int2Array = vtkSmartPointer::New(); - int2Array->InsertNextValue(1); - int2Array->InsertNextValue(0); - int2Array->InsertNextValue(0); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); - vtkSmartPointer double2Array = vtkSmartPointer::New(); - double2Array->InsertNextValue(center_2[0]); - double2Array->InsertNextValue(center_2[1]); - double2Array->InsertNextValue(center_2[2]); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(double2Array); - - double center_3[3] = {4.0f, 4.0f, 3.0f}; - double normal_3[3] = {0.0f, 0.0f, 1.0f}; - vtkSmartPointer p_source_3 = vtkSmartPointer::New(); - p_source_3->SetNumberOfSides(10); - p_source_3->SetCenter(center_3); - p_source_3->SetRadius(4); - p_source_3->SetNormal(normal_3); - p_source_3->Update(); - vtkPolyData *poly_3 = p_source_3->GetOutput(); - mitk::Surface::Pointer surf_3 = mitk::Surface::New(); - surf_3->SetVtkPolyData(poly_3); - vtkSmartPointer int3Array = vtkSmartPointer::New(); - int3Array->InsertNextValue(1); - int3Array->InsertNextValue(0); - int3Array->InsertNextValue(0); - surf_3->GetVtkPolyData()->GetFieldData()->AddArray(int3Array); - vtkSmartPointer double3Array = vtkSmartPointer::New(); - double3Array->InsertNextValue(center_3[0]); - double3Array->InsertNextValue(center_3[1]); - double3Array->InsertNextValue(center_3[2]); - surf_3->GetVtkPolyData()->GetFieldData()->AddArray(double3Array); - - std::vector surfaces; - surfaces.push_back(surf_1); - surfaces.push_back(surf_2); - surfaces.push_back(surf_3); - - const mitk::PlaneGeometry * planeGeometry1 = GetPlaneGeometry(); - const mitk::PlaneGeometry * planeGeometry2 = GetPlaneGeometry(); - const mitk::PlaneGeometry * planeGeometry3 = GetPlaneGeometry(); - - std::vector planeGeometries; - planeGeometries.push_back(planeGeometry1); - planeGeometries.push_back(planeGeometry2); - planeGeometries.push_back(planeGeometry3); + auto surf_1 = CreateContour(3); + auto surf_2 = CreateContour(3); + auto surf_3 = CreateContour(3); + auto surf_3New = CreateContour(3); - // Add contours - m_Controller->AddNewContours(surfaces, planeGeometries, true); + auto planeGeometry1 = CreatePlaneGeometry(1); + auto planeGeometry2 = CreatePlaneGeometry(2); + auto planeGeometry3 = CreatePlaneGeometry(3); - mitk::ScalarType n[3]; + mitk::SurfaceInterpolationController::CPIVector cpis= { {surf_1, planeGeometry1, 1, 0}, + {surf_2, planeGeometry2, 1, 0}, {surf_3, planeGeometry3, 1, 0}, {surf_3New, planeGeometry3, 1, 0}, + {surf_1, planeGeometry1, 1, 1}, {surf_1, planeGeometry1, 2, 0}, {surf_2, planeGeometry3, 2, 0} + }; + // Add contours + m_Controller->AddNewContours(cpis, true); // Check if all contours are there - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo1; - vtkPolygon::ComputeNormal(surf_1->GetVtkPolyData()->GetPoints(), n); - contourInfo1.ContourNormal = n; - contourInfo1.ContourPoint = center_1; - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; - vtkPolygon::ComputeNormal(surf_2->GetVtkPolyData()->GetPoints(), n); - contourInfo2.ContourNormal = n; - contourInfo2.ContourPoint = center_2; - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo3; - vtkPolygon::ComputeNormal(surf_3->GetVtkPolyData()->GetPoints(), n); - contourInfo3.ContourNormal = n; - contourInfo3.ContourPoint = center_3; - - const mitk::Surface *contour_1 = m_Controller->GetContour(contourInfo1); - const mitk::Surface *contour_2 = m_Controller->GetContour(contourInfo2); - const mitk::Surface *contour_3 = m_Controller->GetContour(contourInfo3); - - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 3); + auto contours = m_Controller->GetContours(1, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 3); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_1->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_2->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_3New->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(1, 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_3->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 2); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 1); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); + contours = m_Controller->GetContours(3, 0); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); // Create another segmentation image - unsigned int dimensions2[] = {20, 20, 20}; - mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage(dimensions2); + unsigned int dimensions2[] = {20, 20, 20, 4}; + mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); m_Controller->SetCurrentInterpolationSession(segmentation_2); - // Create some contours - double center_4[3] = {10.0f, 10.0f, 10.0f}; - double normal_4[3] = {0.0f, 1.0f, 0.0f}; - vtkSmartPointer p_source_4 = vtkSmartPointer::New(); - p_source_4->SetNumberOfSides(8); - p_source_4->SetCenter(center_4); - p_source_4->SetRadius(5); - p_source_4->SetNormal(normal_4); - p_source_4->Update(); - vtkPolyData *poly_4 = p_source_4->GetOutput(); - mitk::Surface::Pointer surf_4 = mitk::Surface::New(); - surf_4->SetVtkPolyData(poly_4); - vtkSmartPointer int4Array = vtkSmartPointer::New(); - int4Array->InsertNextValue(2); - int4Array->InsertNextValue(0); - int4Array->InsertNextValue(0); - surf_4->GetVtkPolyData()->GetFieldData()->AddArray(int4Array); - vtkSmartPointer double4Array = vtkSmartPointer::New(); - double4Array->InsertNextValue(center_4[0]); - double4Array->InsertNextValue(center_4[1]); - double4Array->InsertNextValue(center_4[2]); - surf_4->GetVtkPolyData()->GetFieldData()->AddArray(double4Array); - - double center_5[3] = {3.0f, 10.0f, 10.0f}; - double normal_5[3] = {1.0f, 0.0f, 0.0f}; - vtkSmartPointer p_source_5 = vtkSmartPointer::New(); - p_source_5->SetNumberOfSides(16); - p_source_5->SetCenter(center_5); - p_source_5->SetRadius(8); - p_source_5->SetNormal(normal_5); - p_source_5->Update(); - vtkPolyData *poly_5 = p_source_5->GetOutput(); - mitk::Surface::Pointer surf_5 = mitk::Surface::New(); - surf_5->SetVtkPolyData(poly_5); - vtkSmartPointer int5Array = vtkSmartPointer::New(); - int5Array->InsertNextValue(2); - int5Array->InsertNextValue(0); - int5Array->InsertNextValue(0); - surf_5->GetVtkPolyData()->GetFieldData()->AddArray(int5Array); - vtkSmartPointer double5Array = vtkSmartPointer::New(); - double5Array->InsertNextValue(center_5[0]); - double5Array->InsertNextValue(center_5[1]); - double5Array->InsertNextValue(center_5[2]); - surf_5->GetVtkPolyData()->GetFieldData()->AddArray(double5Array); - - double center_6[3] = {10.0f, 10.0f, 3.0f}; - double normal_6[3] = {0.0f, 0.0f, 1.0f}; - vtkSmartPointer p_source_6 = vtkSmartPointer::New(); - p_source_6->SetNumberOfSides(100); - p_source_6->SetCenter(center_6); - p_source_6->SetRadius(5); - p_source_6->SetNormal(normal_6); - p_source_6->Update(); - vtkPolyData *poly_6 = p_source_6->GetOutput(); - mitk::Surface::Pointer surf_6 = mitk::Surface::New(); - surf_6->SetVtkPolyData(poly_6); - vtkSmartPointer int6Array = vtkSmartPointer::New(); - int6Array->InsertNextValue(2); - int6Array->InsertNextValue(0); - int6Array->InsertNextValue(0); - surf_6->GetVtkPolyData()->GetFieldData()->AddArray(int6Array); - vtkSmartPointer double6Array = vtkSmartPointer::New(); - double6Array->InsertNextValue(center_6[0]); - double6Array->InsertNextValue(center_6[1]); - double6Array->InsertNextValue(center_6[2]); - surf_6->GetVtkPolyData()->GetFieldData()->AddArray(double6Array); - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo4; - vtkPolygon::ComputeNormal(surf_4->GetVtkPolyData()->GetPoints(), n); - contourInfo4.ContourNormal = n; - contourInfo4.ContourPoint = center_4; - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo5; - vtkPolygon::ComputeNormal(surf_5->GetVtkPolyData()->GetPoints(), n); - contourInfo5.ContourNormal = n; - contourInfo5.ContourPoint = center_5; - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo6; - vtkPolygon::ComputeNormal(surf_6->GetVtkPolyData()->GetPoints(), n); - contourInfo6.ContourNormal = n; - contourInfo6.ContourPoint = center_6; - - const mitk::PlaneGeometry * planeGeometry4 = GetPlaneGeometry(); - const mitk::PlaneGeometry * planeGeometry5 = GetPlaneGeometry(); - const mitk::PlaneGeometry * planeGeometry6 = GetPlaneGeometry(); - - std::vector surfaces2; - surfaces2.push_back(surf_4); - surfaces2.push_back(surf_5); - surfaces2.push_back(surf_6); - - std::vector planeGeometries2; - planeGeometries2.push_back(planeGeometry4); - planeGeometries2.push_back(planeGeometry5); - planeGeometries2.push_back(planeGeometry6); - - m_Controller->AddNewContours(surfaces2, planeGeometries2, true); + auto planeGeometry4 = CreatePlaneGeometry(4); + auto planeGeometry5 = CreatePlaneGeometry(5); + auto planeGeometry6 = CreatePlaneGeometry(6); + + auto surf_4 = CreateContour(4); + auto surf_5 = CreateContour(5); + auto surf_6 = CreateContour(6); + + mitk::SurfaceInterpolationController::CPIVector cpis2 = { {surf_1, planeGeometry1, 1, 0}, + {surf_2, planeGeometry2, 1, 0}, {surf_4, planeGeometry4, 1, 0}, + {surf_5, planeGeometry5, 1, 1}, {surf_6, planeGeometry6, 2, 0} + }; + contours = m_Controller->GetContours(1, 0); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); + + m_Controller->AddNewContours(cpis2); // Check if all contours are there - auto contour_4 = m_Controller->GetContour(contourInfo4); - auto contour_5 = m_Controller->GetContour(contourInfo5); - auto contour_6 = m_Controller->GetContour(contourInfo6); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 3); + contours = m_Controller->GetContours(1, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 3); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_4->GetVtkPolyData()), *(contour_4->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_5->GetVtkPolyData()), *(contour_5->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_4->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(1, 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_6->GetVtkPolyData()), *(contour_6->GetVtkPolyData()), 0.000001, true)); - - // Modify some contours - vtkSmartPointer p_source_7 = vtkSmartPointer::New(); - p_source_7->SetNumberOfSides(200); - p_source_7->SetCenter(3.0, 10.0, 10.0); - p_source_7->SetRadius(5); - p_source_7->SetNormal(1, 0, 0); - p_source_7->Update(); - vtkPolyData *poly_7 = p_source_7->GetOutput(); - mitk::Surface::Pointer surf_7 = mitk::Surface::New(); - surf_7->SetVtkPolyData(poly_7); - vtkSmartPointer int7Array = vtkSmartPointer::New(); - int7Array->InsertNextValue(2); - int7Array->InsertNextValue(0); - int7Array->InsertNextValue(0); - surf_7->GetVtkPolyData()->GetFieldData()->AddArray(int7Array); - vtkSmartPointer double7Array = vtkSmartPointer::New(); - double7Array->InsertNextValue(3.0); - double7Array->InsertNextValue(10.0); - double7Array->InsertNextValue(10.0); - surf_7->GetVtkPolyData()->GetFieldData()->AddArray(double7Array); - - - std::vector surfaces3; - surfaces3.push_back(surf_7); - - const mitk::PlaneGeometry * planeGeometry7 = GetPlaneGeometry(); - std::vector planeGeometries3; - planeGeometries3.push_back(planeGeometry7); - - m_Controller->AddNewContours(surfaces3, planeGeometries3, true); - - mitk::ScalarType center_7[3]; - center_7[0] = 3.0; - center_7[1] = 10.0; - center_7[2] = 10.0; - vtkPolygon::ComputeNormal(surf_7->GetVtkPolyData()->GetPoints(), n); - contourInfo5.ContourNormal = n; - contourInfo5.ContourPoint = center_7; - - auto contour_7 = m_Controller->GetContour(contourInfo5); + mitk::Equal(*(surf_5->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_7->GetVtkPolyData()), *(contour_7->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_6->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); - // Change session and test if all contours are available + contours = m_Controller->GetContours(2, 1); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); + contours = m_Controller->GetContours(3, 0); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); + + // Check if all contours of segmentation_1 are still there m_Controller->SetCurrentInterpolationSession(segmentation_1); - auto contour_8 = m_Controller->GetContour(contourInfo1); - auto contour_9 = m_Controller->GetContour(contourInfo2); - auto contour_10 = m_Controller->GetContour(contourInfo3); - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 3); + contours = m_Controller->GetContours(1, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 3); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_8->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_9->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_3New->GetVtkPolyData()), *((*contours)[2].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(1, 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 1); CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_10->GetVtkPolyData()), 0.000001, true)); + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 0); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", contours->size() == 2); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_1->GetVtkPolyData()), *((*contours)[0].Contour->GetVtkPolyData()), 0.000001, true)); + CPPUNIT_ASSERT_MESSAGE("Contours not equal!", + mitk::Equal(*(surf_2->GetVtkPolyData()), *((*contours)[1].Contour->GetVtkPolyData()), 0.000001, true)); + + contours = m_Controller->GetContours(2, 1); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); + contours = m_Controller->GetContours(3, 0); + CPPUNIT_ASSERT_MESSAGE("Invalid CPIs exists!", contours == nullptr); } - void TestRemoveContour() + void TestRemoveContours() { // Create segmentation image - unsigned int dimensions1[] = {12, 12, 12}; - mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage(dimensions1); + unsigned int dimensions1[] = {12, 12, 12, 3}; + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); - // Create some contours - double center_1[3] = {4.0f, 4.0f, 4.0f}; - double normal_1[3] = {0.0f, 1.0f, 0.0f}; - vtkSmartPointer p_source = vtkSmartPointer::New(); - p_source->SetNumberOfSides(20); - p_source->SetCenter(center_1); - p_source->SetRadius(4); - p_source->SetNormal(normal_1); - p_source->Update(); - vtkPolyData *poly_1 = p_source->GetOutput(); - mitk::Surface::Pointer surf_1 = mitk::Surface::New(); - surf_1->SetVtkPolyData(poly_1); - vtkSmartPointer int1Array = vtkSmartPointer::New(); - int1Array->InsertNextValue(1); - int1Array->InsertNextValue(0); - int1Array->InsertNextValue(0); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); - vtkSmartPointer double1Array = vtkSmartPointer::New(); - double1Array->InsertNextValue(center_1[0]); - double1Array->InsertNextValue(center_1[1]); - double1Array->InsertNextValue(center_1[2]); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); - - double center_2[3] = {4.0f, 4.0f, 4.0f}; - double normal_2[3] = {1.0f, 0.0f, 0.0f}; - vtkSmartPointer p_source_2 = vtkSmartPointer::New(); - p_source_2->SetNumberOfSides(80); - p_source_2->SetCenter(center_2); - p_source_2->SetRadius(4); - p_source_2->SetNormal(normal_2); - p_source_2->Update(); - vtkPolyData *poly_2 = p_source_2->GetOutput(); - mitk::Surface::Pointer surf_2 = mitk::Surface::New(); - surf_2->SetVtkPolyData(poly_2); - vtkSmartPointer int2Array = vtkSmartPointer::New(); - int2Array->InsertNextValue(1); - int2Array->InsertNextValue(0); - int2Array->InsertNextValue(0); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); - vtkSmartPointer double2Array = vtkSmartPointer::New(); - double2Array->InsertNextValue(center_2[0]); - double2Array->InsertNextValue(center_2[1]); - double2Array->InsertNextValue(center_2[2]); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(double2Array); - - std::vector surfaces; - surfaces.push_back(surf_1); - surfaces.push_back(surf_2); - - const mitk::PlaneGeometry * planeGeometry1 = GetPlaneGeometry(); - const mitk::PlaneGeometry * planeGeometry2 = GetPlaneGeometry(); - std::vector planeGeometries; - planeGeometries.push_back(planeGeometry1); - planeGeometries.push_back(planeGeometry2); - - m_Controller->AddNewContours(surfaces, planeGeometries, true); - // // Add contours - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo3; - contourInfo3.Contour = surf_1->Clone(); - contourInfo3.ContourNormal = normal_1; - contourInfo3.ContourPoint = center_1; - // Shift the new contour so that it is different - contourInfo3.ContourPoint += mitk::Vector(0.5); - - bool success = m_Controller->RemoveContour(contourInfo3); - CPPUNIT_ASSERT_MESSAGE("Remove failed - contour was unintentionally removed!", - (m_Controller->GetNumberOfContours() == 2) && !success); - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; - contourInfo2.ContourNormal = normal_2; - contourInfo2.ContourPoint = center_2; - contourInfo2.Contour = surf_2; - success = m_Controller->RemoveContour(contourInfo2); - CPPUNIT_ASSERT_MESSAGE("Remove failed - contour was not removed!", - (m_Controller->GetNumberOfContours() == 1) && success); - - // // Let's see if the other contour No. 1 is still there - contourInfo3.ContourPoint -= mitk::Vector(0.5); - const mitk::Surface *remainingContour = m_Controller->GetContour(contourInfo3); - CPPUNIT_ASSERT_MESSAGE( - "Remove failed - contour was accidentally removed!", - (m_Controller->GetNumberOfContours() == 1) && - mitk::Equal(*(surf_1->GetVtkPolyData()), *(remainingContour->GetVtkPolyData()), 0.000001, true) && success); + + auto surf_1 = CreateContour(3); + auto surf_2 = CreateContour(3); + auto surf_3 = CreateContour(3); + + auto planeGeometry1 = CreatePlaneGeometry(1); + auto planeGeometry2 = CreatePlaneGeometry(2); + auto planeGeometry3 = CreatePlaneGeometry(3); + + mitk::SurfaceInterpolationController::CPIVector cpis = { {surf_1, planeGeometry1, 1, 0}, + {surf_2, planeGeometry2, 1, 1}, {surf_3, planeGeometry3, 1, 2}, + {surf_1, planeGeometry1, 2, 0}, {surf_2, planeGeometry3, 2, 0} + }; + m_Controller->AddNewContours(cpis); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + //Remove unkown label + m_Controller->RemoveContours(segmentation_1, 9); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + //Remove unkown time step + m_Controller->RemoveContours(segmentation_1, 1,3); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + + m_Controller->RemoveContours(segmentation_1, 1, 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + m_Controller->RemoveContours(segmentation_1, 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + m_Controller->RemoveContours(segmentation_1, 2); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0) == nullptr); } + void TestOnLabelRemoved() + { + // Create segmentation image + unsigned int dimensions[] = { 20, 10, 30, 4 }; + mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions); + segmentation_1->AddLabel(mitk::Label::New(1, "Label1"), 0); + segmentation_1->AddLabel(mitk::Label::New(2, "Label2"), 0); + mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions); + segmentation_2->AddLabel(mitk::Label::New(1, "Label1"), 0); + segmentation_2->AddLabel(mitk::Label::New(2, "Label2"), 0); + + auto surf_1 = CreateContour(3); + auto surf_2 = CreateContour(3); + auto surf_3 = CreateContour(3); + + auto planeGeometry1 = CreatePlaneGeometry(1); + auto planeGeometry2 = CreatePlaneGeometry(2); + auto planeGeometry3 = CreatePlaneGeometry(3); + + mitk::SurfaceInterpolationController::CPIVector cpis = { {surf_1, planeGeometry1, 1, 0}, + {surf_2, planeGeometry2, 1, 1}, {surf_3, planeGeometry3, 1, 2}, + {surf_1, planeGeometry1, 2, 0}, {surf_2, planeGeometry3, 2, 0} + }; + + m_Controller->SetCurrentInterpolationSession(segmentation_1); + m_Controller->AddNewContours(cpis); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + m_Controller->SetCurrentInterpolationSession(segmentation_2); + m_Controller->AddNewContours(cpis); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + + segmentation_1->RemoveLabel(1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2)->size() == 1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + m_Controller->SetCurrentInterpolationSession(segmentation_1); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 0) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 1) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(1, 2) == nullptr); + CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetContours(2, 0)->size() == 2); + } + + bool AssertImagesEqual4D(mitk::LabelSetImage *img1, mitk::LabelSetImage *img2) { auto selector1 = mitk::ImageTimeSelector::New(); selector1->SetInput(img1); selector1->SetChannelNr(0); auto selector2 = mitk::ImageTimeSelector::New(); selector2->SetInput(img2); selector2->SetChannelNr(0); int numTs1 = img1->GetTimeSteps(); int numTs2 = img2->GetTimeSteps(); if (numTs1 != numTs2) { return false; } /*mitk::ImagePixelWriteAccessor accessor( img1 ); itk::Index<4> ind; ind[0] = 5; ind[1] = 5; ind[2] = 5; ind[3] = 2; accessor.SetPixelByIndex( ind, 7 );*/ for (int ts = 0; ts < numTs1; ++ts) { selector1->SetTimeNr(ts); selector2->SetTimeNr(ts); selector1->Update(); selector2->Update(); mitk::Image::Pointer imgSel1 = selector1->GetOutput(); mitk::Image::Pointer imgSel2 = selector2->GetOutput(); MITK_ASSERT_EQUAL(imgSel1, imgSel2, "Segmentation images are not equal"); } return true; } void TestSetCurrentInterpolationSession4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 5}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); // mitk::Image * segmentationImage_1 = dynamic_cast(segmentation_1.GetPointer()); unsigned int dimensions2[] = {20, 10, 30, 4}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); - auto currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation().GetPointer()); + auto currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation()); AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); + m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); // Test 2 m_Controller->SetCurrentInterpolationSession(segmentation_2); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_2->Clone(), "Segmentation images are not // equal"); - currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation().GetPointer()); + currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation()); // AssertImagesEqual4D(currentSegmentation, segmentation_2->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", currentSegmentation == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 3 m_Controller->SetCurrentInterpolationSession(segmentation_1); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not // equal"); - currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation().GetPointer()); + currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation()); AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); + m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 4 m_Controller->SetCurrentInterpolationSession(segmentation_1); // MITK_ASSERT_EQUAL(m_Controller->GetCurrentSegmentation(), segmentation_1->Clone(), "Segmentation images are not // equal"); - currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation().GetPointer()); + currentSegmentation = dynamic_cast(m_Controller->GetCurrentSegmentation()); AssertImagesEqual4D(currentSegmentation, segmentation_1->Clone()); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_1.GetPointer()); + m_Controller->GetCurrentSegmentation() == segmentation_1.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test 5 m_Controller->SetCurrentInterpolationSession(nullptr); - CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation().IsNull()); + CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", m_Controller->GetCurrentSegmentation()==nullptr); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); } - void TestReplaceInterpolationSession4D() - { - // Create segmentation image - unsigned int dimensions1[] = {10, 10, 10, 5}; - mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); - m_Controller->SetCurrentInterpolationSession(segmentation_1); - - m_Controller->SetCurrentTimePoint(0); - - // Create some contours - double center_1[3] = {1.25f, 3.43f, 4.44f}; - double normal_1[3] = {0.25f, 1.76f, 0.93f}; - vtkSmartPointer p_source = vtkSmartPointer::New(); - p_source->SetNumberOfSides(20); - p_source->SetCenter(center_1); - p_source->SetRadius(4); - p_source->SetNormal(normal_1); - p_source->Update(); - vtkPolyData *poly_1 = p_source->GetOutput(); - mitk::Surface::Pointer surf_1 = mitk::Surface::New(); - surf_1->SetVtkPolyData(poly_1); - vtkSmartPointer int1Array = vtkSmartPointer::New(); - int1Array->InsertNextValue(1); - int1Array->InsertNextValue(0); - int1Array->InsertNextValue(0); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(int1Array); - vtkSmartPointer double1Array = vtkSmartPointer::New(); - double1Array->InsertNextValue(center_1[0]); - double1Array->InsertNextValue(center_1[1]); - double1Array->InsertNextValue(center_1[2]); - surf_1->GetVtkPolyData()->GetFieldData()->AddArray(double1Array); - - double center_2[3] = {4.0f, 4.0f, 4.0f}; - double normal_2[3] = {1.0f, 0.0f, 0.0f}; - vtkSmartPointer p_source_2 = vtkSmartPointer::New(); - p_source_2->SetNumberOfSides(80); - p_source_2->SetCenter(center_2); - p_source_2->SetRadius(4); - p_source_2->SetNormal(normal_2); - p_source_2->Update(); - vtkPolyData *poly_2 = p_source_2->GetOutput(); - mitk::Surface::Pointer surf_2 = mitk::Surface::New(); - surf_2->SetVtkPolyData(poly_2); - vtkSmartPointer int2Array = vtkSmartPointer::New(); - int2Array->InsertNextValue(1); - int2Array->InsertNextValue(0); - int2Array->InsertNextValue(0); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(int2Array); - vtkSmartPointer double2Array = vtkSmartPointer::New(); - double2Array->InsertNextValue(center_2[0]); - double2Array->InsertNextValue(center_2[1]); - double2Array->InsertNextValue(center_2[2]); - surf_2->GetVtkPolyData()->GetFieldData()->AddArray(double2Array); - - - std::vector surfaces; - surfaces.push_back(surf_1); - surfaces.push_back(surf_2); - - const mitk::PlaneGeometry * planeGeometry1 = GetPlaneGeometry(); - const mitk::PlaneGeometry * planeGeometry2 = GetPlaneGeometry(); - std::vector planeGeometries; - planeGeometries.push_back(planeGeometry1); - planeGeometries.push_back(planeGeometry2); - // Add contours - m_Controller->AddNewContours(surfaces, planeGeometries, true); - - - // Add contours for another timestep - m_Controller->SetCurrentTimePoint(2); - - double center_3[3] = {1.3f, 3.5f, 4.6f}; - double normal_3[3] = {0.20f, 1.6f, 0.8f}; - vtkSmartPointer p_source_3 = vtkSmartPointer::New(); - p_source_3->SetNumberOfSides(20); - p_source_3->SetCenter(center_3); - p_source_3->SetRadius(4); - p_source_3->SetNormal(normal_3); - p_source_3->Update(); - vtkPolyData *poly_3 = p_source_3->GetOutput(); - mitk::Surface::Pointer surf_3 = mitk::Surface::New(); - surf_3->SetVtkPolyData(poly_3); - vtkSmartPointer int3Array = vtkSmartPointer::New(); - int3Array->InsertNextValue(1); - int3Array->InsertNextValue(0); - int3Array->InsertNextValue(2); - surf_3->GetVtkPolyData()->GetFieldData()->AddArray(int3Array); - vtkSmartPointer double3Array = vtkSmartPointer::New(); - double3Array->InsertNextValue(center_3[0]); - double3Array->InsertNextValue(center_3[1]); - double3Array->InsertNextValue(center_3[2]); - surf_3->GetVtkPolyData()->GetFieldData()->AddArray(double3Array); - - double center_4[3] = {1.32f, 3.53f, 4.8f}; - double normal_4[3] = {0.22f, 1.5f, 0.85f}; - vtkSmartPointer p_source_4 = vtkSmartPointer::New(); - p_source_4->SetNumberOfSides(20); - p_source_4->SetCenter(center_4); - p_source_4->SetRadius(4); - p_source_4->SetNormal(normal_4); - p_source_4->Update(); - vtkPolyData *poly_4 = p_source_4->GetOutput(); - mitk::Surface::Pointer surf_4 = mitk::Surface::New(); - surf_4->SetVtkPolyData(poly_4); - vtkSmartPointer int4Array = vtkSmartPointer::New(); - int4Array->InsertNextValue(1); - int4Array->InsertNextValue(0); - int4Array->InsertNextValue(2); - surf_4->GetVtkPolyData()->GetFieldData()->AddArray(int4Array); - vtkSmartPointer double4Array = vtkSmartPointer::New(); - double4Array->InsertNextValue(center_4[0]); - double4Array->InsertNextValue(center_4[1]); - double4Array->InsertNextValue(center_4[2]); - surf_4->GetVtkPolyData()->GetFieldData()->AddArray(double4Array); - - - std::vector surfaces2; - surfaces2.push_back(surf_3); - surfaces2.push_back(surf_4); - - const mitk::PlaneGeometry * planeGeometry3 = GetPlaneGeometry(); - const mitk::PlaneGeometry * planeGeometry4 = GetPlaneGeometry(); - std::vector planeGeometries2; - planeGeometries2.push_back(planeGeometry3); - planeGeometries2.push_back(planeGeometry4); - // Add contours - m_Controller->AddNewContours(surfaces2, planeGeometries2, true); - - m_Controller->SetCurrentTimePoint(0); - - // Check if all contours are there - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo1; - contourInfo1.ContourNormal = normal_1; - contourInfo1.ContourPoint = center_1; - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo2; - contourInfo2.ContourNormal = normal_2; - contourInfo2.ContourPoint = center_2; - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo3; - mitk::ScalarType n[3]; - vtkPolygon::ComputeNormal(surf_3->GetVtkPolyData()->GetPoints(), n); - contourInfo3.ContourNormal = n; - contourInfo3.ContourPoint = center_3; - - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo4; - // mitk::ScalarType n[3]; - vtkPolygon::ComputeNormal(surf_4->GetVtkPolyData()->GetPoints(), n); - contourInfo4.ContourNormal = n; - contourInfo4.ContourPoint = center_4; - - const mitk::Surface *contour_1 = m_Controller->GetContour(contourInfo1); - const mitk::Surface *contour_2 = m_Controller->GetContour(contourInfo2); - - CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_1->GetVtkPolyData()), *(contour_1->GetVtkPolyData()), 0.000001, true)); - CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_2->GetVtkPolyData()), *(contour_2->GetVtkPolyData()), 0.000001, true)); - - m_Controller->SetCurrentTimePoint(2); - const mitk::Surface *contour_3 = m_Controller->GetContour(contourInfo3); - const mitk::Surface *contour_4 = m_Controller->GetContour(contourInfo4); - - CPPUNIT_ASSERT_MESSAGE("Wrong number of contours!", m_Controller->GetNumberOfContours() == 2); - CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_3->GetVtkPolyData()), *(contour_3->GetVtkPolyData()), 0.000001, true)); - CPPUNIT_ASSERT_MESSAGE("Contours not equal!", - mitk::Equal(*(surf_4->GetVtkPolyData()), *(contour_4->GetVtkPolyData()), 0.000001, true)); - - mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions1); - bool success = m_Controller->ReplaceInterpolationSession(segmentation_1, segmentation_2); - - CPPUNIT_ASSERT_MESSAGE("Replace session failed!", success == true); - CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", - m_Controller->GetNumberOfInterpolationSessions() == 1); - CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); - } - void TestRemoveAllInterpolationSessions4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 4}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 5}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); m_Controller->RemoveAllInterpolationSessions(); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } void TestRemoveInterpolationSession4D() { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 3}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); unsigned int dimensions2[] = {20, 10, 30, 6}; mitk::LabelSetImage::Pointer segmentation_2 = createLabelSetImage4D(dimensions2); // Test 1 m_Controller->SetCurrentInterpolationSession(segmentation_1); m_Controller->SetCurrentInterpolationSession(segmentation_2); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Segmentation images are not equal", - m_Controller->GetCurrentSegmentation().GetPointer() == segmentation_2.GetPointer()); + m_Controller->GetCurrentSegmentation() == segmentation_2.GetPointer()); CPPUNIT_ASSERT_MESSAGE("Current segmentation is null after another one was removed", - m_Controller->GetCurrentSegmentation().IsNotNull()); + m_Controller->GetCurrentSegmentation()!=nullptr); m_Controller->SetCurrentInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 2", m_Controller->GetNumberOfInterpolationSessions() == 2); // Test current segmentation should not be null if another one was removed m_Controller->RemoveInterpolationSession(segmentation_1); CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 1", m_Controller->GetNumberOfInterpolationSessions() == 1); CPPUNIT_ASSERT_MESSAGE("Current segmentation is not null after session was removed", - m_Controller->GetCurrentSegmentation().IsNull()); + m_Controller->GetCurrentSegmentation()==nullptr); } void TestOnSegmentationDeleted4D() { { // Create image for testing unsigned int dimensions1[] = {10, 10, 10, 7}; mitk::LabelSetImage::Pointer segmentation_1 = createLabelSetImage4D(dimensions1); m_Controller->SetCurrentInterpolationSession(segmentation_1); - m_Controller->SetCurrentTimePoint(3); } CPPUNIT_ASSERT_MESSAGE("Number of interpolation session not 0", m_Controller->GetNumberOfInterpolationSessions() == 0); } }; MITK_TEST_SUITE_REGISTRATION(mitkSurfaceInterpolationController) diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp index f939d7a447..a9011e69f7 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp @@ -1,1406 +1,876 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include +#include + #include #include #include -#include #include #include -#include -#include #include #include +#include #include #include #include -#include -#include -#include - -// Check whether the given contours are coplanar -bool ContoursCoplanar(mitk::SurfaceInterpolationController::ContourPositionInformation leftHandSide, - mitk::SurfaceInterpolationController::ContourPositionInformation rightHandSide) +struct CPICache { - // 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::SurfaceInterpolationController::CPIVector cpis; + itk::TimeStamp cpiTimeStamp; + mitk::Surface::Pointer cachedSurface; +}; -mitk::SurfaceInterpolationController::ContourPositionInformation CreateContourPositionInformation( - mitk::Surface::Pointer contour, const mitk::PlaneGeometry* planeGeometry) -{ - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; - contourInfo.Contour = contour; - mitk::ScalarType n[3]; - vtkPolygon::ComputeNormal(contour->GetVtkPolyData()->GetPoints(), n); - contourInfo.ContourNormal = n; - contourInfo.Pos = -1; - contourInfo.TimeStep = std::numeric_limits::max(); - contourInfo.Plane = const_cast(planeGeometry); - - auto contourIntArray = vtkIntArray::SafeDownCast( contour->GetVtkPolyData()->GetFieldData()->GetAbstractArray(0) ); - - if (contourIntArray->GetSize() < 2) - { - MITK_ERROR << "In CreateContourPositionInformation. The contourIntArray is empty."; - } - contourInfo.LabelValue = contourIntArray->GetValue(0); - contourInfo.LayerValue = contourIntArray->GetValue(1); +typedef std::map CPITimeStepMap; +typedef std::map CPITimeStepLabelMap; - if (contourIntArray->GetSize() >= 3) - { - contourInfo.TimeStep = contourIntArray->GetValue(2); - } +typedef std::map CPITimeStepLabelSegMap; - contourInfo.SliceIndex = 0; +CPITimeStepLabelSegMap cpiMap; +std::shared_mutex cpiMutex; - return contourInfo; -}; +std::map segmentationObserverTags; +std::map labelRemovedObserverTags; mitk::SurfaceInterpolationController::SurfaceInterpolationController() - : m_SelectedSegmentation(nullptr), - m_CurrentTimePoint(0.), - m_ContourIndex(0), - m_ContourPosIndex(0), - m_NumberOfLayersInCurrentSegmentation(0), - m_PreviousActiveLabelValue(0), - m_CurrentActiveLabelValue(0), - m_PreviousLayerIndex(0), - m_CurrentLayerIndex(0) + : m_DistanceImageVolume(50000), + m_SelectedSegmentation(nullptr) { - m_DistanceImageSpacing = 0.0; - m_ReduceFilter = ReduceContourSetFilter::New(); - m_NormalsFilter = ComputeContourSetNormalsFilter::New(); - m_InterpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); - // m_TimeSelector = ImageTimeSelector::New(); - - m_ReduceFilter->SetUseProgressBar(false); - // m_ReduceFilter->SetProgressStepSize(1); - m_NormalsFilter->SetUseProgressBar(true); - m_NormalsFilter->SetProgressStepSize(1); - m_InterpolateSurfaceFilter->SetUseProgressBar(true); - m_InterpolateSurfaceFilter->SetProgressStepSize(7); - - m_Contours = Surface::New(); - - m_PolyData = vtkSmartPointer::New(); - vtkSmartPointer points = vtkSmartPointer::New(); - m_PolyData->SetPoints(points); - - m_NumberOfConnectionsAdded = 0; - - m_InterpolationResult = nullptr; - m_CurrentNumberOfReducedContours = 0; } mitk::SurfaceInterpolationController::~SurfaceInterpolationController() { - // Removing all observers this->RemoveObservers(); } void mitk::SurfaceInterpolationController::RemoveObservers() { // Removing all observers - auto dataIter = m_SegmentationObserverTags.begin(); - for (; dataIter != m_SegmentationObserverTags.end(); ++dataIter) + while (segmentationObserverTags.size()) { - (*dataIter).first->RemoveObserver((*dataIter).second); + this->RemoveObserversInternal(segmentationObserverTags.begin()->first); } - m_SegmentationObserverTags.clear(); } mitk::SurfaceInterpolationController *mitk::SurfaceInterpolationController::GetInstance() { static mitk::SurfaceInterpolationController::Pointer m_Instance; if (m_Instance.IsNull()) { m_Instance = SurfaceInterpolationController::New(); } return m_Instance; } -void mitk::SurfaceInterpolationController::AddNewContour(mitk::Surface::Pointer newContour) +void mitk::SurfaceInterpolationController::AddNewContours(const std::vector& newCPIs, + bool reinitializationAction, bool silent) { - if (newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) + auto selectedSegmentation = m_SelectedSegmentation.Lock(); + if (selectedSegmentation.IsNull()) return; + + for (auto cpi : newCPIs) { - ContourPositionInformation contourInfo = CreateContourPositionInformation(newContour, nullptr); - this->AddToInterpolationPipeline(contourInfo); - this->Modified(); + if (cpi.Contour->GetVtkPolyData()->GetNumberOfPoints() > 0) + { + this->AddToCPIMap(cpi, reinitializationAction); + } } + if (!silent) this->Modified(); } -void mitk::SurfaceInterpolationController::AddNewContours(const std::vector& newContours, - std::vector& contourPlanes, - bool reinitializationAction) + +mitk::DataNode* GetSegmentationImageNodeInternal(mitk::DataStorage* ds, const mitk::LabelSetImage* seg) { - if (nullptr == m_SelectedSegmentation) return; + if (nullptr == ds) return nullptr; + if (nullptr == seg) return nullptr; - if (newContours.size() != contourPlanes.size()) + mitk::DataNode* segmentationNode = nullptr; + mitk::NodePredicateDataUID::Pointer dataUIDPredicate = mitk::NodePredicateDataUID::New(seg->GetUID()); + auto dataNodeObjects = ds->GetSubset(dataUIDPredicate); + + if (dataNodeObjects->Size() != 0) { - MITK_ERROR << "SurfaceInterpolationController::AddNewContours. contourPlanes and newContours are not of the same size."; + for (auto it = dataNodeObjects->Begin(); it != dataNodeObjects->End(); ++it) + { + segmentationNode = it->Value(); + } } + else + { + MITK_ERROR << "Unable to find the labelSetImage with the desired UID."; + } + return segmentationNode; +} + +mitk::DataNode* mitk::SurfaceInterpolationController::GetSegmentationImageNode() const +{ + if (m_DataStorage.IsNull()) return nullptr; + auto selectedSegmentation = m_SelectedSegmentation.Lock(); + if (selectedSegmentation.IsNull()) return nullptr; + return GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation); +} + +mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const +{ + DataStorage::SetOfObjects::Pointer relevantNodes = DataStorage::SetOfObjects::New(); - for (size_t i = 0; i < newContours.size(); ++i) + if (m_DataStorage.IsNotNull()) { - const auto &newContour = newContours[i]; + //remove relevant plane nodes + auto nodes = this->GetPlaneGeometryNodeFromDataStorage(segNode, labelValue); - const mitk::PlaneGeometry * planeGeometry = contourPlanes[i]; - if (newContour->GetVtkPolyData()->GetNumberOfPoints() > 0) + for (auto it = nodes->Begin(); it != nodes->End(); ++it) { - auto contourInfo = CreateContourPositionInformation(newContour, planeGeometry); - if (!reinitializationAction) - { - contourInfo.ContourPoint = this->ComputeInteriorPointOfContour(contourInfo, - dynamic_cast(m_SelectedSegmentation) ); - } - else + auto aTS = static_cast(dynamic_cast(it->Value()->GetProperty("timeStep"))->GetValue()); + bool sameTS = (timeStep == aTS); + + if (sameTS) { - auto vtkPolyData = contourInfo.Contour->GetVtkPolyData(); - auto pointVtkArray = vtkDoubleArray::SafeDownCast(vtkPolyData->GetFieldData()->GetAbstractArray(1)); - mitk::ScalarType *ptArr = new mitk::ScalarType[3]; - for (int i = 0; i < pointVtkArray->GetSize(); ++i) - ptArr[i] = pointVtkArray->GetValue(i); - - mitk::Point3D pt3D; - pt3D.FillPoint(ptArr); - contourInfo.ContourPoint = pt3D; + relevantNodes->push_back(it->Value()); } - - this->AddToInterpolationPipeline(contourInfo, reinitializationAction); } } - this->Modified(); + return relevantNodes; } -mitk::DataNode* mitk::SurfaceInterpolationController::GetSegmentationImageNode() +mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const { - DataNode* segmentationNode = nullptr; - mitk::NodePredicateDataUID::Pointer dataUIDPredicate = mitk::NodePredicateDataUID::New(m_SelectedSegmentation->GetUID()); - auto dataNodeObjects = m_DataStorage->GetSubset(dataUIDPredicate); + auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); + auto isCorrectLabel = NodePredicateProperty::New("labelID", mitk::UShortProperty::New(labelValue)); + auto searchPredicate = NodePredicateAnd::New(isContourPlaneGeometry, isCorrectLabel); - if (dataNodeObjects->Size() != 0) - { - for (auto it = dataNodeObjects->Begin(); it != dataNodeObjects->End(); ++it) - { - segmentationNode = it->Value(); - } - } - else - { - MITK_ERROR << "Unable to find the labelSetImage with the desired UID."; - } - return segmentationNode; + mitk::DataStorage::SetOfObjects::ConstPointer result; + if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, searchPredicate); + return result; } -void mitk::SurfaceInterpolationController::AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) +mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const +{ + auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); + + mitk::DataStorage::SetOfObjects::ConstPointer result; + if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, isContourPlaneGeometry); + return result; +} +void mitk::SurfaceInterpolationController::AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const { + auto selectedSegmentation = m_SelectedSegmentation.Lock(); + if (selectedSegmentation.IsNull()) + { + mitkThrow()<< "Cannot add plane geometries. No valid segmentation selected."; + } + + if (!selectedSegmentation->GetTimeGeometry()->IsValidTimeStep(contourInfo.TimeStep)) + { + MITK_ERROR << "Invalid time point requested in AddPlaneGeometryNodeToDataStorage."; + return; + } + + if (m_DataStorage.IsNull()) + { + MITK_DEBUG << "Cannot add plane geometry nodes. No data storage is set."; + return; + } + auto planeGeometry = contourInfo.Plane; - auto planeGeometryData = mitk::PlanarCircle::New(); - planeGeometryData->SetPlaneGeometry(planeGeometry); - mitk::Point2D p1; - planeGeometry->Map(planeGeometry->GetCenter(), p1); - planeGeometryData->PlaceFigure(p1); - planeGeometryData->SetCurrentControlPoint(p1); - planeGeometryData->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); if (planeGeometry) { auto segmentationNode = this->GetSegmentationImageNode(); - auto isContourPlaneGeometry = mitk::NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); - - mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = - m_DataStorage->GetDerivations(segmentationNode, isContourPlaneGeometry); + mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = this->GetPlaneGeometryNodeFromDataStorage(segmentationNode, contourInfo.LabelValue, contourInfo.TimeStep); - auto contourFound = false; + mitk::DataNode::Pointer contourPlaneGeometryDataNode; // Go through the pre-existing contours and check if the contour position matches them. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { - auto layerID = dynamic_cast(it->Value()->GetProperty("layerID"))->GetValue(); - auto labelID = dynamic_cast(it->Value()->GetProperty("labelID"))->GetValue(); - auto posID = dynamic_cast(it->Value()->GetProperty("position"))->GetValue(); - bool sameLayer = (layerID == contourInfo.LayerValue); - bool sameLabel = (labelID == contourInfo.LabelValue); - bool samePos = (posID == contourInfo.Pos); - - if (samePos & sameLabel & sameLayer) + auto planeData = dynamic_cast(it->Value()->GetData()); + if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; + + bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); + + if (samePlane) { - contourFound = true; - it->Value()->SetData(planeGeometryData); + contourPlaneGeometryDataNode = it->Value(); break; } } - if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) - { - MITK_ERROR << "Invalid time point requested in AddPlaneGeometryNodeToDataStorage."; - return; - } - - const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - // Go through the contourPlaneGeometry Data and add the segmentationNode to it. - if (!contourFound) + if (contourPlaneGeometryDataNode.IsNull()) { - std::string contourName = "contourPlane " + std::to_string(m_ContourIndex); + auto planeGeometryData = mitk::PlanarCircle::New(); + planeGeometryData->SetPlaneGeometry(planeGeometry->Clone()); + mitk::Point2D p1; + planeGeometry->Map(planeGeometry->GetCenter(), p1); + planeGeometryData->PlaceFigure(p1); + planeGeometryData->SetCurrentControlPoint(p1); + planeGeometryData->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); + + std::string contourName = "contourPlane L " + std::to_string(contourInfo.LabelValue) + " T " + std::to_string(contourInfo.TimeStep); - auto contourPlaneGeometryDataNode = mitk::DataNode::New(); + contourPlaneGeometryDataNode = mitk::DataNode::New(); contourPlaneGeometryDataNode->SetData(planeGeometryData); // No need to change properties contourPlaneGeometryDataNode->SetProperty("helper object", mitk::BoolProperty::New(false)); contourPlaneGeometryDataNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetProperty("isContourPlaneGeometry", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetVisibility(false); // Need to change properties contourPlaneGeometryDataNode->SetProperty("name", mitk::StringProperty::New(contourName) ); - contourPlaneGeometryDataNode->SetProperty("layerID", mitk::UIntProperty::New(contourInfo.LayerValue)); contourPlaneGeometryDataNode->SetProperty("labelID", mitk::UShortProperty::New(contourInfo.LabelValue)); - contourPlaneGeometryDataNode->SetProperty("position", mitk::IntProperty::New(contourInfo.Pos)); - contourPlaneGeometryDataNode->SetProperty("timeStep", mitk::IntProperty::New(currentTimeStep)); + contourPlaneGeometryDataNode->SetProperty("timeStep", mitk::IntProperty::New(contourInfo.TimeStep)); - contourPlaneGeometryDataNode->SetProperty("px", mitk::DoubleProperty::New(contourInfo.ContourPoint[0])); - contourPlaneGeometryDataNode->SetProperty("py", mitk::DoubleProperty::New(contourInfo.ContourPoint[1])); - contourPlaneGeometryDataNode->SetProperty("pz", mitk::DoubleProperty::New(contourInfo.ContourPoint[2])); + contourPlaneGeometryDataNode->SetData(planeGeometryData); m_DataStorage->Add(contourPlaneGeometryDataNode, segmentationNode); } } } -void mitk::SurfaceInterpolationController::AddToInterpolationPipeline(ContourPositionInformation& contourInfo, bool reinitializationAction) +void mitk::SurfaceInterpolationController::AddToCPIMap(ContourPositionInformation& contourInfo, bool reinitializationAction) { - if (!m_SelectedSegmentation) + auto selectedSegmentation = m_SelectedSegmentation.Lock(); + if (selectedSegmentation.IsNull()) return; - if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + if (!selectedSegmentation->GetTimeGeometry()->IsValidTimeStep(contourInfo.TimeStep)) { - MITK_ERROR << "Invalid time point requested for interpolation pipeline."; + MITK_ERROR << "Invalid time step requested for interpolation pipeline."; return; } - // Get current time step either from the - auto GetCurrentTimeStep = [=](ContourPositionInformation contourInfo) - { - if (reinitializationAction) - { - return contourInfo.TimeStep; - } - return static_cast(m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint)); - }; - const auto currentTimeStep = GetCurrentTimeStep(contourInfo); - auto GetContourLayerID = [=](ContourPositionInformation contourInfo) + if (contourInfo.Plane == nullptr) { - unsigned int currentLayerID; - if(reinitializationAction) - { - if (contourInfo.LayerValue == std::numeric_limits::max()) - { - MITK_ERROR << "In mitk::SurfaceInterpolationController::AddToInterpolationPipeline. Problem in finding layerID"; - } - currentLayerID = contourInfo.LayerValue; - } - else - { - try - { - currentLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); - } - catch (const std::exception& e) - { - MITK_ERROR << "Unable to cast image to LabelSetImage. " << e.what() << '\n'; - } - } - return currentLayerID; - }; + MITK_ERROR << "contourInfo plane is null."; + return; + } - unsigned int currentLayerID = GetContourLayerID(contourInfo); + if (contourInfo.Contour->GetVtkPolyData()->GetNumberOfPoints() == 0) + { + this->RemoveContour(contourInfo); + MITK_DEBUG << "contourInfo contour is empty."; + return; + } - ContourPositionInformationVec3D ¤tImageContours = m_ListOfContours.at(m_SelectedSegmentation); - ContourPositionInformationVec2D ¤tTimeStepContoursList = currentImageContours.at(currentTimeStep); - ContourPositionInformationList ¤tContourList = currentTimeStepContoursList.at(currentLayerID); + { + std::lock_guard guard(cpiMutex); + const auto& currentTimeStep = contourInfo.TimeStep; + const auto& currentLabelValue = contourInfo.LabelValue; - int replacementIndex = -1; - int pos = -1; - mitk::Surface* newContour = contourInfo.Contour; + auto& currentImageContours = cpiMap[selectedSegmentation]; + auto& currentLabelContours = currentImageContours[currentLabelValue]; + auto& currentCPICache = currentLabelContours[currentTimeStep]; + auto& currentContourList = currentCPICache.cpis; - for (size_t i = 0; i < currentContourList.size(); i++) - { - auto& contourFromList = currentContourList.at(i); - bool contoursAreCoplanar = ContoursCoplanar(contourInfo, contourFromList); - bool contoursHaveSameLabel = contourInfo.LabelValue == contourFromList.LabelValue; + auto finding = std::find_if(currentContourList.begin(), currentContourList.end(), [contourInfo](const ContourPositionInformation& element) {return contourInfo.Plane->IsOnPlane(element.Plane); }); - // Coplanar contours have the same "pos". - if (contoursAreCoplanar) + if (finding != currentContourList.end()) { - pos = contourFromList.Pos; - if (contoursHaveSameLabel) - { - replacementIndex = i; - } + MITK_DEBUG << "CPI already exists. CPI is updated. Label: "<< currentLabelValue << "; Time Step: " << currentTimeStep; + *finding = contourInfo; } - } - // The current contour has the same label and position as the current slice and a replacement is done. - if (replacementIndex != -1) - { - contourInfo.Pos = pos; - m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).at(replacementIndex) = contourInfo; - - if (!reinitializationAction) + else { - this->AddPlaneGeometryNodeToDataStorage(contourInfo); + currentContourList.push_back(contourInfo); } - return; + currentCPICache.cpiTimeStamp.Modified(); } - // Case that there is no contour in the current slice with the current label - if (pos == -1) - pos = m_ContourPosIndex++; - - m_ContourIndex++; - contourInfo.Pos = pos; - m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).push_back(contourInfo); - - if (contourInfo.Plane == nullptr) - { - MITK_ERROR << "contourInfo plane is null."; - } if (!reinitializationAction) { this->AddPlaneGeometryNodeToDataStorage(contourInfo); } - - if (newContour->GetVtkPolyData()->GetNumberOfPoints() == 0) - { - this->RemoveContour(contourInfo); - if (m_ContourIndex > 0) - m_ContourIndex--; - if (m_ContourIndex > 0) - m_ContourIndex--; - } } -bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo) +bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo, bool keepPlaceholderForUndo) { - if (!m_SelectedSegmentation) + auto selectedSegmentation = m_SelectedSegmentation.Lock(); + if (selectedSegmentation.IsNull()) { return false; } - if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + if (!selectedSegmentation->GetTimeGeometry()->IsValidTimeStep(contourInfo.TimeStep)) { return false; } - const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - unsigned int currentLayerID = 0; - try - { - currentLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); - } - catch (const std::exception& e) - { - MITK_ERROR << e.what() << '\n'; - } + bool removedIt = false; - auto it = m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).begin(); - while (it != m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).end()) { - const ContourPositionInformation ¤tContour = (*it); - if (ContoursCoplanar(currentContour, contourInfo)) + std::lock_guard cpiGuard(cpiMutex); + + const auto currentTimeStep = contourInfo.TimeStep; + const auto currentLabel = contourInfo.LabelValue; + auto& cpiCache = cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep); + auto it = cpiCache.cpis.begin(); + + + while (it != cpiCache.cpis.end()) { - m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(currentLayerID).erase(it); - this->ReinitializeInterpolation(); - return true; + const ContourPositionInformation& currentContour = (*it); + if (currentContour.Plane->IsOnPlane(contourInfo.Plane)) + { + if (keepPlaceholderForUndo) + { + it->Contour = nullptr; + } + else + { + cpiCache.cpis.erase(it); + } + cpiCache.cpiTimeStamp.Modified(); + removedIt = true; + + if (m_DataStorage.IsNotNull()) + { + mitk::DataNode::Pointer contourPlaneGeometryDataNode; + + auto contourNodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(m_DataStorage, selectedSegmentation), currentLabel, currentTimeStep); + + // Go through the nodes and check if the contour position matches them. + for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) + { + auto planeData = dynamic_cast(it->Value()->GetData()); + if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; + + bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); + + if (samePlane) + { + m_DataStorage->Remove(it->Value()); + break; + } + } + } + break; + } + ++it; } - ++it; } - return false; + + return removedIt; } -const mitk::Surface *mitk::SurfaceInterpolationController::GetContour(const ContourPositionInformation &contourInfo) +void mitk::SurfaceInterpolationController::AddActiveLabelContoursForInterpolation(ReduceContourSetFilter* reduceFilter, const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { - if (!m_SelectedSegmentation) + const auto& currentImageContours = cpiMap.at(segmentationImage); + + auto finding = currentImageContours.find(labelValue); + if (finding == currentImageContours.end()) { - return nullptr; + MITK_INFO << "Contours for label don't exist. Label value: " << labelValue; + return; } - if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + const auto& currentLabelContoursMap = finding->second; + + auto tsfinding = currentLabelContoursMap.find(timeStep); + if (tsfinding == currentLabelContoursMap.end()) { - return nullptr; + MITK_INFO << "Contours for current time step don't exist."; + return; } - const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - const auto activeLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); - const auto &contourList = m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep).at(activeLayerID); - for (auto ¤tContour : contourList) + const auto& currentContours = tsfinding->second.cpis; + + unsigned int index = 0; + for (const auto& cpi : currentContours) { - if (ContoursCoplanar(contourInfo, currentContour)) + if (!cpi.IsPlaceHolder()) { - return currentContour.Contour; + reduceFilter->SetInput(index, cpi.Contour); + ++index; } } - return nullptr; } -unsigned int mitk::SurfaceInterpolationController::GetNumberOfContours() +bool CPICacheIsOutdated(const mitk::LabelSetImage* segmentationImage, mitk::LabelSetImage::LabelValueType labelValue, mitk::TimeStepType timeStep) { - if (!m_SelectedSegmentation) - { - return -1; - } + const auto& currentImageContours = cpiMap.at(segmentationImage); - if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + auto finding = currentImageContours.find(labelValue); + if (finding == currentImageContours.end()) { - return -1; + return false; } - const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - auto contourDoubleList = m_ListOfContours.at(m_SelectedSegmentation).at(currentTimeStep); - unsigned int numContours = 0; - for (auto& contourList : contourDoubleList) - { + const auto& currentLabelContoursMap = finding->second; - numContours += contourList.size(); + auto tsfinding = currentLabelContoursMap.find(timeStep); + if (tsfinding == currentLabelContoursMap.end()) + { + return false; } - return numContours; + bool result = tsfinding->second.cachedSurface.IsNull() || tsfinding->second.cachedSurface->GetMTime() < tsfinding->second.cpiTimeStamp.GetMTime(); + return result; } -void mitk::SurfaceInterpolationController::AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel) +void SetCPICacheSurface(mitk::Surface* surface, const mitk::LabelSetImage* segmentationImage, mitk::LabelSetImage::LabelValueType labelValue, mitk::TimeStepType timeStep) { - this->ReinitializeInterpolation(); + const auto& currentImageContours = cpiMap.at(segmentationImage); - if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + auto finding = currentImageContours.find(labelValue); + if (finding == currentImageContours.end()) { - MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } - const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - unsigned int currentLayerID = 0; - try - { - currentLayerID = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); - } - catch (const std::exception& e) + const auto& currentLabelContoursMap = finding->second; + + auto tsfinding = currentLabelContoursMap.find(timeStep); + if (tsfinding == currentLabelContoursMap.end()) { - MITK_ERROR << e.what() << '\n'; + return; } - ContourPositionInformationVec3D ¤tImageContours = m_ListOfContours.at(m_SelectedSegmentation); + cpiMap[segmentationImage][labelValue][timeStep].cachedSurface = surface; +} - if (currentImageContours.size() <= currentTimeStep) +void mitk::SurfaceInterpolationController::Interpolate(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) +{ + if (nullptr == segmentationImage) { - MITK_INFO << "Contours for current time step don't exist."; - return; + mitkThrow() << "Cannot interpolate contours. No valid segmentation passed."; } - ContourPositionInformationVec2D ¤tTimeStepContoursList = currentImageContours.at(currentTimeStep); - if (currentTimeStepContoursList.size() <= currentLayerID) + std::lock_guard guard(cpiMutex); + auto it = cpiMap.find(segmentationImage); + if (it == cpiMap.end()) { - MITK_INFO << "Contours for current layer don't exist."; - return; + mitkThrow() << "Cannot interpolate contours. Passed segmentation is not registered at controller."; } - ContourPositionInformationList ¤tContours = currentTimeStepContoursList.at(currentLayerID); - for (size_t i = 0; i < currentContours.size(); ++i) + if (!segmentationImage->ExistLabel(labelValue)) { - if (currentContours.at(i).LabelValue == activeLabel) - { - m_ListOfInterpolationSessions.at(m_SelectedSegmentation).at(currentTimeStep).push_back(currentContours.at(i)); - m_ReduceFilter->SetInput(m_ListOfInterpolationSessions.at(m_SelectedSegmentation).at(currentTimeStep).size()-1, currentContours.at(i).Contour); - } + mitkThrow() << "Cannot interpolate contours. None existent label request. Invalid label:" << labelValue; } -} -void mitk::SurfaceInterpolationController::Interpolate() -{ - if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + if (!segmentationImage->GetTimeGeometry()->IsValidTimeStep(timeStep)) { - MITK_WARN << "No interpolation possible, currently selected timepoint is not in the time bounds of currently selected segmentation. Time point: " << m_CurrentTimePoint; - m_InterpolationResult = nullptr; - return; + mitkThrow() << "Cannot interpolate contours. No valid time step requested. Invalid time step:" << timeStep; } - const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - m_ReduceFilter->Update(); - m_CurrentNumberOfReducedContours = m_ReduceFilter->GetNumberOfOutputs(); - if (m_CurrentNumberOfReducedContours == 1) + if (!CPICacheIsOutdated(segmentationImage, labelValue, timeStep)) return; + + mitk::Surface::Pointer interpolationResult = nullptr; + + auto reduceFilter = ReduceContourSetFilter::New(); + auto normalsFilter = ComputeContourSetNormalsFilter::New(); + auto interpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); + + const auto spacing = segmentationImage->GetGeometry(timeStep)->GetSpacing(); + double minSpacing = 100; + double maxSpacing = 0; + for (int i = 0; i < 3; i++) { - vtkPolyData *tmp = m_ReduceFilter->GetOutput(0)->GetVtkPolyData(); - if (tmp == nullptr) + if (spacing[i] < minSpacing) + { + minSpacing = spacing[i]; + } + if (spacing[i] > maxSpacing) { - m_CurrentNumberOfReducedContours = 0; + maxSpacing = spacing[i]; } } + reduceFilter->SetMinSpacing(minSpacing); + reduceFilter->SetMaxSpacing(maxSpacing); + normalsFilter->SetMaxSpacing(maxSpacing); + interpolateSurfaceFilter->SetDistanceImageVolume(m_DistanceImageVolume); - // We use the timeSelector to get the segmentation image for the current segmentation. + reduceFilter->SetUseProgressBar(false); + normalsFilter->SetUseProgressBar(true); + normalsFilter->SetProgressStepSize(1); + interpolateSurfaceFilter->SetUseProgressBar(true); + interpolateSurfaceFilter->SetProgressStepSize(7); + + // Set reference image for interpolation surface filter + itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(m_SelectedSegmentation); - timeSelector->SetTimeNr(currentTimeStep); + timeSelector->SetInput(segmentationImage); + timeSelector->SetTimeNr(timeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); - mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); - itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); + interpolateSurfaceFilter->SetReferenceImage(itkImage.GetPointer()); - m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); - - for (size_t i = 0; i < m_CurrentNumberOfReducedContours; ++i) + try { - mitk::Surface::Pointer reducedContour = m_ReduceFilter->GetOutput(i); - reducedContour->DisconnectPipeline(); - m_NormalsFilter->SetInput(i, reducedContour); - m_InterpolateSurfaceFilter->SetInput(i, m_NormalsFilter->GetOutput(i)); - } + this->AddActiveLabelContoursForInterpolation(reduceFilter, segmentationImage, labelValue, timeStep); + reduceFilter->Update(); + auto currentNumberOfReducedContours = reduceFilter->GetNumberOfOutputs(); - if (m_CurrentNumberOfReducedContours < 2) - { - // If no interpolation is possible reset the interpolation result - MITK_INFO << "Interpolation impossible: not enough contours."; - m_InterpolationResult = nullptr; - return; - } + if (currentNumberOfReducedContours < 2) + { + // If no interpolation is possible reset the interpolation result + MITK_INFO << "Interpolation impossible: not enough contours."; + } + else + { + normalsFilter->SetSegmentationBinaryImage(refSegImage); - // Setting up progress bar - mitk::ProgressBar::GetInstance()->AddStepsToDo(10); + for (size_t i = 0; i < currentNumberOfReducedContours; ++i) + { + mitk::Surface::Pointer reducedContour = reduceFilter->GetOutput(i); + reducedContour->DisconnectPipeline(); + normalsFilter->SetInput(i, reducedContour); + interpolateSurfaceFilter->SetInput(i, normalsFilter->GetOutput(i)); + } - // create a surface from the distance-image - mitk::ImageToSurfaceFilter::Pointer imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); - imageToSurfaceFilter->SetInput(m_InterpolateSurfaceFilter->GetOutput()); - imageToSurfaceFilter->SetThreshold(0); - imageToSurfaceFilter->SetSmooth(true); - imageToSurfaceFilter->SetSmoothIteration(1); - imageToSurfaceFilter->Update(); + // Setting up progress bar + mitk::ProgressBar::GetInstance()->AddStepsToDo(10); - mitk::Surface::Pointer interpolationResult = mitk::Surface::New(); - interpolationResult->Expand(m_SelectedSegmentation->GetTimeSteps()); + // create a surface from the distance-image + auto imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); + imageToSurfaceFilter->SetInput(interpolateSurfaceFilter->GetOutput()); + imageToSurfaceFilter->SetThreshold(0); + imageToSurfaceFilter->SetSmooth(true); + imageToSurfaceFilter->SetSmoothIteration(1); + imageToSurfaceFilter->Update(); - auto geometry = m_SelectedSegmentation->GetTimeGeometry()->Clone(); - geometry->ReplaceTimeStepGeometries(mitk::Geometry3D::New()); - interpolationResult->SetTimeGeometry(geometry); + interpolationResult = mitk::Surface::New(); + interpolationResult->Expand(segmentationImage->GetTimeSteps()); - interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), currentTimeStep); - m_InterpolationResult = interpolationResult; + auto geometry = segmentationImage->GetTimeGeometry()->Clone(); + geometry->ReplaceTimeStepGeometries(mitk::Geometry3D::New()); + interpolationResult->SetTimeGeometry(geometry); - m_DistanceImageSpacing = m_InterpolateSurfaceFilter->GetDistanceImageSpacing(); + interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), timeStep); + interpolationResult->DisconnectPipeline(); - auto* contoursGeometry = static_cast(m_Contours->GetTimeGeometry()); - auto timeBounds = geometry->GetTimeBounds(currentTimeStep); - contoursGeometry->SetFirstTimePoint(timeBounds[0]); - contoursGeometry->SetStepDuration(timeBounds[1] - timeBounds[0]); + // Last progress step + mitk::ProgressBar::GetInstance()->Progress(20); - // Last progress step - mitk::ProgressBar::GetInstance()->Progress(20); - m_InterpolationResult->DisconnectPipeline(); -} + } + } + catch (const Exception& e) + { + MITK_ERROR << "Interpolation failed: " << e.what(); + interpolationResult = nullptr; + } -mitk::Surface::Pointer mitk::SurfaceInterpolationController::GetInterpolationResult() -{ - return m_InterpolationResult; + SetCPICacheSurface(interpolationResult, segmentationImage, labelValue, timeStep); } -mitk::Surface *mitk::SurfaceInterpolationController::GetContoursAsSurface() +mitk::Surface::Pointer mitk::SurfaceInterpolationController::GetInterpolationResult(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { - return m_Contours; -} + if (nullptr == segmentationImage) + { + mitkThrow() << "Cannot interpolate contours. No valid segmentation passed."; + } + std::shared_lock guard(cpiMutex); -void mitk::SurfaceInterpolationController::SetDataStorage(DataStorage::Pointer ds) -{ - m_DataStorage = ds; -} + if (cpiMap.find(segmentationImage) == cpiMap.end()) + { + mitkThrow() << "Cannot interpolate contours. Passed segmentation is not registered at controller."; + } -void mitk::SurfaceInterpolationController::SetMinSpacing(double minSpacing) -{ - m_ReduceFilter->SetMinSpacing(minSpacing); -} + if (!segmentationImage->ExistLabel(labelValue)) + { + mitkThrow() << "Cannot interpolate contours. None existent label request. Invalid label:" << labelValue; + } -void mitk::SurfaceInterpolationController::SetMaxSpacing(double maxSpacing) -{ - m_ReduceFilter->SetMaxSpacing(maxSpacing); - m_NormalsFilter->SetMaxSpacing(maxSpacing); -} + if (!segmentationImage->GetTimeGeometry()->IsValidTimeStep(timeStep)) + { + mitkThrow() << "Cannot interpolate contours. No valid time step requested. Invalid time step:" << timeStep; + } -void mitk::SurfaceInterpolationController::SetDistanceImageVolume(unsigned int distImgVolume) -{ - m_InterpolateSurfaceFilter->SetDistanceImageVolume(distImgVolume); + const auto& currentImageContours = cpiMap.at(segmentationImage); + + auto finding = currentImageContours.find(labelValue); + if (finding == currentImageContours.end()) + { + return nullptr; + } + + const auto& currentLabelContoursMap = finding->second; + + auto tsfinding = currentLabelContoursMap.find(timeStep); + if (tsfinding == currentLabelContoursMap.end()) + { + return nullptr; + } + + return tsfinding->second.cachedSurface; } -mitk::Image::Pointer mitk::SurfaceInterpolationController::GetCurrentSegmentation() +void mitk::SurfaceInterpolationController::SetDataStorage(DataStorage::Pointer ds) { - return m_SelectedSegmentation; + m_DataStorage = ds; } -mitk::Image *mitk::SurfaceInterpolationController::GetImage() +void mitk::SurfaceInterpolationController::SetDistanceImageVolume(unsigned int distImgVolume) { - return m_InterpolateSurfaceFilter->GetOutput(); + m_DistanceImageVolume = distImgVolume; } -double mitk::SurfaceInterpolationController::EstimatePortionOfNeededMemory() +mitk::LabelSetImage* mitk::SurfaceInterpolationController::GetCurrentSegmentation() { - double numberOfPointsAfterReduction = m_ReduceFilter->GetNumberOfPointsAfterReduction() * 3; - double sizeOfPoints = pow(numberOfPointsAfterReduction, 2) * sizeof(double); - double totalMem = mitk::MemoryUtilities::GetTotalSizeOfPhysicalRam(); - double percentage = sizeOfPoints / totalMem; - return percentage; + return m_SelectedSegmentation.Lock(); } unsigned int mitk::SurfaceInterpolationController::GetNumberOfInterpolationSessions() { - return m_ListOfInterpolationSessions.size(); + return cpiMap.size(); } template void mitk::SurfaceInterpolationController::GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result) { result->Graft(input); } -void mitk::SurfaceInterpolationController::SetCurrentSegmentationInterpolationList(mitk::Image::Pointer segmentation) +void mitk::SurfaceInterpolationController::SetCurrentInterpolationSession(mitk::LabelSetImage* currentSegmentationImage) { - this->SetCurrentInterpolationSession(segmentation); -} + auto selectedSegmentation = m_SelectedSegmentation.Lock(); -void mitk::SurfaceInterpolationController::SetCurrentInterpolationSession(mitk::Image::Pointer currentSegmentationImage) -{ - if (currentSegmentationImage.GetPointer() == m_SelectedSegmentation) + if (currentSegmentationImage == selectedSegmentation) { return; } - if (currentSegmentationImage.IsNull()) - { - m_SelectedSegmentation = nullptr; - return; - } - m_SelectedSegmentation = currentSegmentationImage.GetPointer(); + m_SelectedSegmentation = currentSegmentationImage; + selectedSegmentation = m_SelectedSegmentation.Lock(); - try + if (selectedSegmentation.IsNotNull()) { - auto labelSetImage = dynamic_cast(m_SelectedSegmentation); - auto it = m_ListOfContours.find(currentSegmentationImage.GetPointer()); - // If the session does not exist yet create a new ContourPositionPairList otherwise reinitialize the interpolation - // pipeline - if (it == m_ListOfContours.end()) - { - ContourPositionInformationVec3D newList; - - auto numTimeSteps = labelSetImage->GetTimeGeometry()->CountTimeSteps(); - - for (size_t t = 0; t < numTimeSteps; ++t) - { - auto twoDList = ContourPositionInformationVec2D(); - auto contourList = ContourPositionInformationList(); - twoDList.push_back(contourList); - newList.push_back(twoDList); - } - - m_ListOfContours[m_SelectedSegmentation] = newList; + std::lock_guard guard(cpiMutex); - m_InterpolationResult = nullptr; - m_CurrentNumberOfReducedContours = 0; + auto it = cpiMap.find(selectedSegmentation); + if (it == cpiMap.end()) + { + cpiMap[selectedSegmentation] = CPITimeStepLabelMap(); auto command = itk::MemberCommand::New(); command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); - m_SegmentationObserverTags[m_SelectedSegmentation] = labelSetImage->AddObserver(itk::DeleteEvent(), command); - - m_NumberOfLayersInCurrentSegmentation = labelSetImage->GetNumberOfLayers(); - } - - // auto labelSetImage = dynamic_cast(m_SelectedSegmentation); - auto numLayersInSelectedSegmentation = labelSetImage->GetNumberOfLayers(); - // Maybe this has to change. - for (size_t layerID = 0; layerID < numLayersInSelectedSegmentation; ++layerID) - { - this->AddLabelSetConnection(layerID); + segmentationObserverTags[selectedSegmentation] = selectedSegmentation->AddObserver(itk::DeleteEvent(), command); + auto& controller = *this; + const auto sender = selectedSegmentation.GetPointer(); + labelRemovedObserverTags[selectedSegmentation] = selectedSegmentation->AddObserver(mitk::LabelRemovedEvent(), [&controller, sender](const itk::EventObject& event) + { + controller.OnRemoveLabel(sender, event); + }); } } - catch (const std::exception &e) - { - MITK_ERROR << "Unable to cast image as LabelSetImage"; - } - - auto it2 = m_ListOfInterpolationSessions.find(currentSegmentationImage.GetPointer()); - if (it2 == m_ListOfInterpolationSessions.end()) - { - ContourPositionInformationVec2D newList; - m_ListOfInterpolationSessions[m_SelectedSegmentation] = newList; - m_InterpolationResult = nullptr; - m_CurrentNumberOfReducedContours = 0; - } - - this->ReinitializeInterpolation(); } -bool mitk::SurfaceInterpolationController::ReplaceInterpolationSession(mitk::Image::Pointer oldSession, - mitk::Image::Pointer newSession) +void mitk::SurfaceInterpolationController::RemoveInterpolationSession(const mitk::LabelSetImage* segmentationImage) { - if (oldSession.IsNull() || newSession.IsNull()) - return false; - - if (oldSession.GetPointer() == newSession.GetPointer()) - return false; - - if (!mitk::Equal(*(oldSession->GetGeometry()), *(newSession->GetGeometry()), mitk::eps, false)) - return false; - - auto it = m_ListOfInterpolationSessions.find(oldSession.GetPointer()); - - if (it == m_ListOfInterpolationSessions.end()) - return false; - - if (!newSession->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) + if (nullptr != segmentationImage) { - MITK_WARN << "Interpolation session cannot be replaced. Currently selected timepoint is not in the time bounds of the new session. Time point: " << m_CurrentTimePoint; - return false; - } - - ContourPositionInformationVec2D oldList = (*it).second; - - m_ListOfInterpolationSessions[newSession.GetPointer()] = oldList; - - itk::MemberCommand::Pointer command = - itk::MemberCommand::New(); - - command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); - - m_SegmentationObserverTags[newSession] = newSession->AddObserver(itk::DeleteEvent(), command); - - if (m_SelectedSegmentation == oldSession) - m_SelectedSegmentation = newSession; - - const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(m_SelectedSegmentation); - timeSelector->SetTimeNr(currentTimeStep); - timeSelector->SetChannelNr(0); - timeSelector->Update(); - mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); - - m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); - - this->RemoveInterpolationSession(oldSession); - return true; -} - -void mitk::SurfaceInterpolationController::RemoveSegmentationFromContourList(mitk::Image *segmentation) -{ - this->RemoveInterpolationSession(segmentation); -} - -void mitk::SurfaceInterpolationController::RemoveInterpolationSession(mitk::Image::Pointer segmentationImage) -{ - if (segmentationImage) - { - if (m_SelectedSegmentation == segmentationImage) + auto selectedSegmentation = m_SelectedSegmentation.Lock(); + if (selectedSegmentation == segmentationImage) { - m_NormalsFilter->SetSegmentationBinaryImage(nullptr); - m_SelectedSegmentation = nullptr; + this->SetCurrentInterpolationSession(nullptr); } - m_ListOfInterpolationSessions.erase(segmentationImage); - m_ListOfContours.erase(segmentationImage); - // Remove observer - auto pos = m_SegmentationObserverTags.find(segmentationImage); - if (pos != m_SegmentationObserverTags.end()) { - segmentationImage->RemoveObserver((*pos).second); - m_SegmentationObserverTags.erase(pos); + std::lock_guard guard(cpiMutex); + this->RemoveObserversInternal(segmentationImage); + cpiMap.erase(segmentationImage); + if (m_DataStorage.IsNotNull()) + { + auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, segmentationImage)); + this->m_DataStorage->Remove(nodes); + } } + } } -void mitk::SurfaceInterpolationController::RemoveAllInterpolationSessions() +void mitk::SurfaceInterpolationController::RemoveObserversInternal(const mitk::LabelSetImage* segmentationImage) { - // Removing all observers - auto dataIter = m_SegmentationObserverTags.begin(); - while (dataIter != m_SegmentationObserverTags.end()) + auto pos = segmentationObserverTags.find(const_cast(segmentationImage)); + if (pos != segmentationObserverTags.end()) { - mitk::Image *image = (*dataIter).first; - image->RemoveObserver((*dataIter).second); - ++dataIter; + pos->first->RemoveObserver((*pos).second); + segmentationObserverTags.erase(const_cast(segmentationImage)); } - m_SegmentationObserverTags.clear(); - m_SelectedSegmentation = nullptr; - m_ListOfInterpolationSessions.clear(); - m_ListOfContours.clear(); -} - - -template -std::vector GetPixelValuesPresentInImage(mitk::LabelSetImage* labelSetImage) -{ - mitk::ImagePixelReadAccessor readAccessor(labelSetImage); - std::vector pixelsPresent; - - std::size_t numberOfPixels = 1; - for (int dim = 0; dim < static_cast(VImageDimension); ++dim) - numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); - - auto src = readAccessor.GetData(); - for (std::size_t i = 0; i < numberOfPixels; ++i) + auto pos2 = labelRemovedObserverTags.find(const_cast(segmentationImage)); + if (pos2 != labelRemovedObserverTags.end()) { - mitk::Label::PixelType pixelVal = *(src + i); - if ( (std::find(pixelsPresent.begin(), pixelsPresent.end(), pixelVal) == pixelsPresent.end()) && (pixelVal != 0) ) - { - pixelsPresent.push_back(pixelVal); - } + pos2->first->RemoveObserver((*pos2).second); + labelRemovedObserverTags.erase(const_cast(segmentationImage)); } - return pixelsPresent; } -void mitk::SurfaceInterpolationController::RemoveContours(mitk::Label::PixelType label, - unsigned int timeStep, - unsigned int layerID) +void mitk::SurfaceInterpolationController::RemoveAllInterpolationSessions() { - auto isContourEqualToLabelValue = [label] (ContourPositionInformation& contour) -> bool + while (!cpiMap.empty()) { - return (contour.LabelValue == label); - }; - - ContourPositionInformationVec3D ¤tImageContours = m_ListOfContours.at(m_SelectedSegmentation); - ContourPositionInformationList ¤tContourList = currentImageContours.at(timeStep).at(layerID); - unsigned int numContoursBefore = currentContourList.size(); - auto it = std::remove_if(currentContourList.begin(), currentContourList.end(), isContourEqualToLabelValue); - currentContourList.erase(it, currentContourList.end()); - unsigned int numContoursAfter = currentContourList.size(); - unsigned int numContours = numContoursAfter - numContoursBefore; - m_ContourIndex -= numContours; + this->RemoveInterpolationSession(cpiMap.begin()->first); + } } -void mitk::SurfaceInterpolationController::OnSegmentationDeleted(const itk::Object *caller, - const itk::EventObject & /*event*/) +void mitk::SurfaceInterpolationController::RemoveContours(const LabelSetImage* segmentationImage, + mitk::Label::PixelType label, + TimeStepType timeStep) { - auto *tempImage = dynamic_cast(const_cast(caller)); - if (tempImage) + if (nullptr == segmentationImage) { - if (m_SelectedSegmentation == tempImage) - { - m_NormalsFilter->SetSegmentationBinaryImage(nullptr); - m_SelectedSegmentation = nullptr; - } - m_SegmentationObserverTags.erase(tempImage); - m_ListOfContours.erase(tempImage); - m_ListOfInterpolationSessions.erase(tempImage); + mitkThrow() << "Cannot remove contours. No valid segmentation passed."; } -} - -void mitk::SurfaceInterpolationController::ReinitializeInterpolation() -{ - // If session has changed reset the pipeline - m_ReduceFilter->Reset(); - m_NormalsFilter->Reset(); - m_InterpolateSurfaceFilter->Reset(); - // Empty out the listOfInterpolationSessions - m_ListOfInterpolationSessions[m_SelectedSegmentation].clear(); + std::lock_guard guard(cpiMutex); - itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); + auto segfinding = cpiMap.find(segmentationImage); - if (m_SelectedSegmentation) + if (segfinding != cpiMap.end()) { - if (!m_SelectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) - { - MITK_WARN << "Interpolation cannot be reinitialized. Currently selected timepoint is not in the time bounds of the currently selected segmentation. Time point: " << m_CurrentTimePoint; - return; - } - - const auto currentTimeStep = m_SelectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); - - // Set reference image for interpolation surface filter - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(m_SelectedSegmentation); - timeSelector->SetTimeNr(currentTimeStep); - timeSelector->SetChannelNr(0); - timeSelector->Update(); - mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); - AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); - m_InterpolateSurfaceFilter->SetReferenceImage(itkImage.GetPointer()); - - // Resize listofinterpolationsessions and listofcontours to numTimeSteps - unsigned int numTimeSteps = m_SelectedSegmentation->GetTimeSteps(); - unsigned int size = m_ListOfInterpolationSessions[m_SelectedSegmentation].size(); + auto& cpiLabelMap = cpiMap[segmentationImage]; + auto finding = cpiLabelMap.find(label); - if (size != numTimeSteps) + if (finding != cpiLabelMap.end()) { - m_ListOfInterpolationSessions.at(m_SelectedSegmentation).resize(numTimeSteps); + cpiLabelMap[label].erase(timeStep); } - } -} -void mitk::SurfaceInterpolationController::AddLabelSetConnection(unsigned int layerID) -{ - if (m_SelectedSegmentation != nullptr) - { - try + if (m_DataStorage.IsNotNull()) { - auto workingImage = dynamic_cast(m_SelectedSegmentation); - auto previousLayerID = workingImage->GetActiveLayer(); - workingImage->SetActiveLayer(layerID); - auto activeLabelSet = workingImage->GetLabelSet(layerID); - - if (activeLabelSet == nullptr) - return; - - activeLabelSet->RemoveLabelEvent += mitk::MessageDelegate1( - this, &mitk::SurfaceInterpolationController::OnRemoveLabel); - activeLabelSet->ActiveLabelEvent += mitk::MessageDelegate1( - this, &mitk::SurfaceInterpolationController::OnActiveLabel); - workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( - this, &mitk::SurfaceInterpolationController::OnLayerChanged); - m_NumberOfConnectionsAdded += 1; - workingImage->SetActiveLayer(previousLayerID); - } - catch(const std::exception& e) - { - MITK_ERROR << e.what() << '\n'; - } - } -} + //remove relevant plane nodes + auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, segmentationImage), label, timeStep); -void mitk::SurfaceInterpolationController::AddLabelSetConnection() -{ - if (m_SelectedSegmentation != nullptr) - { - try - { - auto workingImage = dynamic_cast(m_SelectedSegmentation); - auto activeLabelSet = workingImage->GetActiveLabelSet(); - - if (activeLabelSet == nullptr) - return; - - activeLabelSet->RemoveLabelEvent += mitk::MessageDelegate1( - this, &mitk::SurfaceInterpolationController::OnRemoveLabel); - workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1( - this, &mitk::SurfaceInterpolationController::OnActiveLabel); - workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( - this, &mitk::SurfaceInterpolationController::OnLayerChanged); - m_NumberOfConnectionsAdded += 1; - } - catch(const std::exception& e) - { - MITK_ERROR << e.what() << '\n'; + this->m_DataStorage->Remove(nodes); } + this->Modified(); } } -void mitk::SurfaceInterpolationController::RemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID) -{ - labelSetImage->SetActiveLayer(layerID); - labelSetImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate1( - this, &mitk::SurfaceInterpolationController::OnRemoveLabel); - // labelSetImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( - // this, &mitk::SurfaceInterpolationController::OnActiveLabel); - labelSetImage->AfterChangeLayerEvent -= mitk::MessageDelegate( - this, &mitk::SurfaceInterpolationController::OnLayerChanged); - m_NumberOfConnectionsAdded -= 1; -} - -void mitk::SurfaceInterpolationController::RemoveLabelSetConnection() +void mitk::SurfaceInterpolationController::RemoveContours(const LabelSetImage* segmentationImage, + mitk::Label::PixelType label) { - if (m_SelectedSegmentation != nullptr) + if (nullptr == segmentationImage) { - try - { - auto workingImage = dynamic_cast(m_SelectedSegmentation); - workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate1( - this, &mitk::SurfaceInterpolationController::OnRemoveLabel); - workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1( - this, &mitk::SurfaceInterpolationController::OnActiveLabel); - workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( - this, &mitk::SurfaceInterpolationController::OnLayerChanged); - } - catch (const std::exception& e) - { - std::cerr << e.what() << '\n'; - } + mitkThrow() << "Cannot remove contours. No valid segmentation passed."; } -} -void mitk::SurfaceInterpolationController::OnRemoveLabel(mitk::Label::PixelType /*removedLabelValue*/) -{ - if (m_SelectedSegmentation != nullptr) - { - auto numTimeSteps = m_SelectedSegmentation->GetTimeGeometry()->CountTimeSteps(); - try - { - auto labelSetImage = dynamic_cast(m_SelectedSegmentation); - auto currentLayerID = labelSetImage->GetActiveLayer(); + std::lock_guard guard(cpiMutex); - for(unsigned int t = 0; t < numTimeSteps; ++t) - { - this->RemoveContours(m_PreviousActiveLabelValue,t,currentLayerID); - } + auto finding = cpiMap.find(segmentationImage); + if (finding != cpiMap.end()) + { + cpiMap[segmentationImage].erase(label); - } - catch(const std::exception& e) + if (m_DataStorage.IsNotNull()) { - std::cerr << e.what() << '\n'; + //remove relevant plane nodes + auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, segmentationImage), label); + this->m_DataStorage->Remove(nodes); } + this->Modified(); } } -void mitk::SurfaceInterpolationController::OnActiveLabel(mitk::Label::PixelType newActiveLabelValue) -{ - m_PreviousActiveLabelValue = m_CurrentActiveLabelValue; - m_CurrentActiveLabelValue = newActiveLabelValue; -} -unsigned int mitk::SurfaceInterpolationController::GetNumberOfLayersInCurrentSegmentation() const -{ - return m_NumberOfLayersInCurrentSegmentation; -} - -void mitk::SurfaceInterpolationController::SetNumberOfLayersInCurrentSegmentation(unsigned int numLayers) -{ - m_NumberOfLayersInCurrentSegmentation = numLayers; -} - -void mitk::SurfaceInterpolationController::OnAddLayer() +void mitk::SurfaceInterpolationController::OnSegmentationDeleted(const itk::Object *caller, + const itk::EventObject & /*event*/) { - assert(m_SelectedSegmentation != nullptr); - auto& contoursForSegmentation = m_ListOfContours.at(m_SelectedSegmentation); - // Push an information list for each time step. - for(size_t t = 0; t < contoursForSegmentation.size(); ++t) + auto tempImage = dynamic_cast(const_cast(caller)); + if (tempImage) { - contoursForSegmentation.at(t).push_back( ContourPositionInformationList() ); + this->RemoveInterpolationSession(tempImage); } } -void mitk::SurfaceInterpolationController::OnRemoveLayer() +void mitk::SurfaceInterpolationController::OnRemoveLabel(const itk::Object* caller, const itk::EventObject& event) { - assert(m_SelectedSegmentation != nullptr); - auto& contoursForSegmentation = m_ListOfContours.at(m_SelectedSegmentation); - // Erase the layers in each of the time steps. + auto sendingSegmentation = dynamic_cast(caller); - // The previous layer is removed - for (size_t t = 0; t < contoursForSegmentation.size(); ++t) - { - assert(m_PreviousLayerIndex < contoursForSegmentation.at(t).size()); - auto& contoursAtTimeStep = contoursForSegmentation.at(t); - for (size_t c = m_CurrentLayerIndex+1; c < contoursAtTimeStep.size(); ++c) - { - auto& contoursInCurrentLayer = contoursAtTimeStep.at(c); - for (auto& contour : contoursInCurrentLayer) - { - contour.LayerValue = contour.LayerValue - 1; - } - } - } + auto removeEvent = dynamic_cast(&event); - for (size_t t = 0; t < contoursForSegmentation.size(); ++t) + if (nullptr != sendingSegmentation && nullptr != removeEvent) { - assert (m_CurrentLayerIndex < contoursForSegmentation.at(t).size()); - contoursForSegmentation.at(t).erase(contoursForSegmentation.at(t).begin() + m_PreviousLayerIndex); + this->RemoveContours(sendingSegmentation, removeEvent->GetLabelValue()); } - this->Modified(); -} - -void mitk::SurfaceInterpolationController::OnLayerChanged() -{ - auto currentLayer = dynamic_cast(m_SelectedSegmentation)->GetActiveLayer(); - m_PreviousLayerIndex = m_CurrentLayerIndex; - m_CurrentLayerIndex = currentLayer; } -mitk::SurfaceInterpolationController::ContourPositionInformationList* mitk::SurfaceInterpolationController::GetContours(unsigned int timeStep, unsigned int layerID) +mitk::SurfaceInterpolationController::CPIVector* mitk::SurfaceInterpolationController::GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { - if (m_SelectedSegmentation == nullptr) - return nullptr; - - if (timeStep >= m_ListOfContours.at(m_SelectedSegmentation).size()) - return nullptr; + auto selectedSegmentation = m_SelectedSegmentation.Lock(); - if (layerID >= m_ListOfContours.at(m_SelectedSegmentation).at(timeStep).size()) + if (selectedSegmentation == nullptr) return nullptr; - return &m_ListOfContours[m_SelectedSegmentation][timeStep][layerID]; -} - -void mitk::SurfaceInterpolationController::CompleteReinitialization(const std::vector& contourList, - std::vector& contourPlanes) -{ - this->ClearInterpolationSession(); + std::shared_lock guard(cpiMutex); - auto labelSetImage = dynamic_cast(m_SelectedSegmentation); - auto numLayers = labelSetImage->GetNumberOfLayers(); + auto labelFinding = cpiMap[selectedSegmentation].find(labelValue); - // Add layers to the m_ListOfContours - for (size_t layer = 0; layer < numLayers; ++layer) + if (labelFinding != cpiMap[selectedSegmentation].end()) { - this->OnAddLayer(); + auto tsFinding = labelFinding->second.find(timeStep); + + if (tsFinding != labelFinding->second.end()) + { + return &(tsFinding->second.cpis); + } } - // Now the layers should be empty and the new layers can be added. - this->AddNewContours(contourList, contourPlanes, true); + return nullptr; } -void mitk::SurfaceInterpolationController::ClearInterpolationSession() +std::vector mitk::SurfaceInterpolationController::GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const { - if (m_SelectedSegmentation != nullptr) - { - auto it = m_ListOfContours.find(m_SelectedSegmentation); - if (it != m_ListOfContours.end()) - { - auto timeSteps = m_ListOfContours[m_SelectedSegmentation].size(); - try - { - auto labelSetImage = dynamic_cast(m_SelectedSegmentation); - auto labelSetImageTimeSteps = labelSetImage->GetTimeGeometry()->CountTimeSteps(); + std::lock_guard guard(cpiMutex); - if (timeSteps != labelSetImageTimeSteps) - { - MITK_ERROR << "Time steps are not the same."; - } - - for (size_t t = 0; t < timeSteps; ++t) - { - m_ListOfContours[m_SelectedSegmentation][t].clear(); - } + std::vector result; - } - catch(std::bad_cast& e) - { - MITK_ERROR << "Unable to cast m_SelectedSegmentation to labelSetImage in ClearInterpolationSession"; - } - } - } -} + auto finding = cpiMap.find(seg); + if (finding == cpiMap.end()) return result; + const auto& currentImageContours = cpiMap[seg]; -std::vector< mitk::Point3D > mitk::ContourExt::GetBoundingBoxGridPoints( - size_t planeDimension, - double startDim1, - size_t numPointsToSampleDim1, - double deltaDim1, - double startDim2, - size_t numPointsToSampleDim2, - double deltaDim2, - double valuePlaneDim) -{ - std::vector< mitk::Point3D > gridPoints; - for (size_t i = 0; i < numPointsToSampleDim1; ++i) + for (const auto& [label, contours] : currentImageContours) { - for (size_t j = 0; j < numPointsToSampleDim2; ++j) + auto tsFinding = contours.find(timeStep); + if (tsFinding != contours.end()) { - mitk::ScalarType *ptVec = new mitk::ScalarType[3]; - - if (planeDimension == 0) - { - ptVec[0] = valuePlaneDim; - ptVec[1] = startDim1 + deltaDim1 * i; - ptVec[2] = startDim2 + deltaDim2 * j; - } - else if (planeDimension == 1) - { - ptVec[0] = startDim1 + deltaDim1 * i; - ptVec[1] = valuePlaneDim; - ptVec[2] = startDim2 + deltaDim2 * j; + const auto& cpis = contours.at(timeStep).cpis; + auto finding = std::find_if(cpis.begin(), cpis.end(), [plane](const ContourPositionInformation& element) {return plane->IsOnPlane(element.Plane); }); - } - else if (planeDimension == 2) + if (finding != cpis.end()) { - ptVec[0] = startDim1 + deltaDim1 * i; - ptVec[1] = startDim2 + deltaDim2 * j; - ptVec[2] = valuePlaneDim; + result.push_back(label); } - - mitk::Point3D pt3D; - pt3D.FillPoint(ptVec); - gridPoints.push_back(pt3D); } } - - return gridPoints; + return result; } -mitk::Point3D mitk::SurfaceInterpolationController::ComputeInteriorPointOfContour( - const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, - mitk::LabelSetImage * labelSetImage) -{ - if (labelSetImage->GetDimension() == 4) - { - return mitk::ContourExt::ComputeInteriorPointOfContour<4>(contour, labelSetImage, m_CurrentTimePoint); - } - else - { - return mitk::ContourExt::ComputeInteriorPointOfContour<3>(contour, labelSetImage, m_CurrentTimePoint); - } -} -template -mitk::Point3D mitk::ContourExt::ComputeInteriorPointOfContour( - const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, - mitk::LabelSetImage * labelSetImage, - mitk::TimePointType currentTimePoint) +void mitk::SurfaceInterpolationController::CompleteReinitialization(const std::vector& newCPIs) { - mitk::ImagePixelReadAccessor readAccessor(labelSetImage); - - if (!labelSetImage->GetTimeGeometry()->IsValidTimePoint(currentTimePoint)) - { - MITK_ERROR << "Invalid time point requested for interpolation pipeline."; - mitk::Point3D pt; - return pt; - } - - std::vector pixelsPresent; - const auto currentTimeStep = labelSetImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); - - auto polyData = contour.Contour->GetVtkPolyData(); - - polyData->ComputeCellsBounds(); - mitk::ScalarType cellBounds[6]; - polyData->GetCellsBounds(cellBounds); - - size_t numPointsToSample = 10; - mitk::ScalarType StartX = cellBounds[0]; - mitk::ScalarType StartY = cellBounds[2]; - mitk::ScalarType StartZ = cellBounds[4]; - - size_t deltaX = (cellBounds[1] - cellBounds[0]) / numPointsToSample; - size_t deltaY = (cellBounds[3] - cellBounds[2]) / numPointsToSample; - size_t deltaZ = (cellBounds[5] - cellBounds[4]) / numPointsToSample; - - auto planeOrientation = mitk::ContourExt::GetContourOrientation(contour.ContourNormal); - - std::vector points; - if (planeOrientation == 0) - { - points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, - StartY, numPointsToSample, deltaY, - StartZ, numPointsToSample, deltaZ, - StartX); - } - else if (planeOrientation == 1) - { - points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, - StartX, numPointsToSample, deltaX, - StartZ, numPointsToSample, deltaZ, - StartY); - } - else if (planeOrientation == 2) - { - points = mitk::ContourExt::GetBoundingBoxGridPoints(planeOrientation, - StartX, numPointsToSample, deltaX, - StartY, numPointsToSample, deltaY, - StartZ); - } - mitk::Label::PixelType pixelVal; - mitk::Point3D pt3D; - std::vector pixelVals; - for (size_t i = 0; i < points.size(); ++i) - { - pt3D = points[i]; - itk::Index<3> itkIndex; - labelSetImage->GetGeometry()->WorldToIndex(pt3D, itkIndex); - - if (VImageDimension == 4) - { - itk::Index time3DIndex; - for (size_t i = 0; i < itkIndex.size(); ++i) - time3DIndex[i] = itkIndex[i]; - time3DIndex[3] = currentTimeStep; - - pixelVal = readAccessor.GetPixelByIndexSafe(time3DIndex); - } - else if (VImageDimension == 3) - { - itk::Index geomIndex; - for (size_t i=0;iClearInterpolationSession(); - if (pixelVal == contour.LabelValue) - break; - } - return pt3D; + // Now the layers should be empty and the new layers can be added. + this->AddNewContours(newCPIs, true); } -size_t mitk::ContourExt::GetContourOrientation(const mitk::Vector3D& ContourNormal) +void mitk::SurfaceInterpolationController::ClearInterpolationSession() { - double n[3]; - n[0] = ContourNormal[0]; - n[1] = ContourNormal[1]; - n[2] = ContourNormal[2]; - - double XVec[3]; - XVec[0] = 1.0; XVec[1] = 0.0; XVec[2] = 0.0; - double dotX = vtkMath::Dot(n, XVec); + auto selectedSegmentation = m_SelectedSegmentation.Lock(); - double YVec[3]; - YVec[0] = 0.0; YVec[1] = 1.0; YVec[2] = 0.0; - double dotY = vtkMath::Dot(n, YVec); - - double ZVec[3]; - ZVec[0] = 0.0; ZVec[1] = 0.0; ZVec[2] = 1.0; - double dotZ = vtkMath::Dot(n, ZVec); - - size_t planeOrientation = 0; - if (fabs(dotZ) > mitk::eps) - { - planeOrientation = 2; - } - else if (fabs(dotY) > mitk::eps) - { - planeOrientation = 1; - } - else if(fabs(dotX) > mitk::eps) + if (selectedSegmentation != nullptr) { - planeOrientation = 0; + std::lock_guard guard(cpiMutex); + cpiMap[selectedSegmentation].clear(); } - return planeOrientation; } diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h index 3a2ea6eccc..8d147f1956 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h @@ -1,468 +1,260 @@ /*============================================================================ 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 mitkSurfaceInterpolationController_h #define mitkSurfaceInterpolationController_h #include -#include +#include #include #include #include namespace mitk { class ComputeContourSetNormalsFilter; class CreateDistanceImageFromSurfaceFilter; class LabelSetImage; class ReduceContourSetFilter; class MITKSURFACEINTERPOLATION_EXPORT SurfaceInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SurfaceInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - itkGetMacro(DistanceImageSpacing, double); struct MITKSURFACEINTERPOLATION_EXPORT ContourPositionInformation { - int Pos; - unsigned int SliceIndex; - Surface::Pointer Contour; - Vector3D ContourNormal; - Point3D ContourPoint; - mitk::PlaneGeometry* Plane; - mitk::Label::PixelType LabelValue; - unsigned int LayerValue; - size_t TimeStep; + Surface::ConstPointer Contour; + PlaneGeometry::ConstPointer Plane; + Label::PixelType LabelValue; + TimeStepType TimeStep; ContourPositionInformation() - : Pos(-1), - SliceIndex(0), - Plane(nullptr), - LabelValue(std::numeric_limits::max()), - LayerValue(std::numeric_limits::max()), - TimeStep(std::numeric_limits::max()) + : Plane(nullptr), + LabelValue(std::numeric_limits::max()), + TimeStep(std::numeric_limits::max()) { } - }; - - typedef std::vector ContourPositionInformationList; - typedef std::vector ContourPositionInformationVec2D; - - // first index is the current time step. second index is the layerID. third index is the contour index. - typedef std::vector ContourPositionInformationVec3D; - - typedef std::map ContourListMap; - typedef std::map ContourContainer; - static SurfaceInterpolationController *GetInstance(); - - void SetCurrentTimePoint(TimePointType tp) - { - if (m_CurrentTimePoint != tp) + ContourPositionInformation(Surface::ConstPointer contour, + PlaneGeometry::ConstPointer plane, + Label::PixelType labelValue, + TimeStepType timeStep) + : + Contour(contour), + Plane(plane), + LabelValue(labelValue), + TimeStep(timeStep) { - m_CurrentTimePoint = tp; + } - if (m_SelectedSegmentation) - { - this->ReinitializeInterpolation(); - } + bool IsPlaceHolder() const + { + return Contour.IsNull(); } }; - TimePointType GetCurrentTimePoint() const { return m_CurrentTimePoint; }; + typedef std::vector CPIVector; - /** - * @brief Adds a new extracted contour to the list - * @param newContour the contour to be added. If a contour at that position - * already exists the related contour will be updated - */ - void AddNewContour(Surface::Pointer newContour); + static SurfaceInterpolationController *GetInstance(); /** * @brief Adds new extracted contours to the list. If one or more contours at a given position * already exist they will be updated respectively */ - void AddNewContours(const std::vector& newContours, std::vector& contourPlanes, bool reinitializeAction = false); + void AddNewContours(const std::vector& newCPIs, bool reinitializeAction = false, bool silent = false); /** - * @brief Returns the contour for a given plane for the current selected segmenation - * @param contourInfo the contour which should be returned - * @return the contour as an mitk::Surface. If no contour is available at the give position nullptr is returned - */ - const mitk::Surface *GetContour(const ContourPositionInformation& contourInfo); - - /** - * @brief Computes an interior point of the input contour. It's used to detect merge and erase operations. - * - * @param contour Contour for which to compute the contour - * @param labelSetImage LabelSetImage used input to check contour Label. - * @return mitk::Point3D 3D Interior point of the contour returned. - */ - mitk::Point3D ComputeInteriorPointOfContour(const ContourPositionInformation& contour, - mitk::LabelSetImage * labelSetImage); - - /** - * @brief Make the surface interpolator responsive to the segmentation image by subscribing to events from the image. - * - */ - void AddLabelSetConnection(); - - /** - * @brief Make the surface interpolator responsive to the segmentation image by stopping subscription to events from the image. - * - */ - void RemoveLabelSetConnection(); - - void RemoveLabelSetConnection(mitk::LabelSetImage* labelSetImage, unsigned int layerID); - - - /** - * @brief Resets the pipeline for interpolation. The various filters used are reset. - * + * @brief Removes the contour for a given plane for the current selected segmenation + * @param contourInfo the contour which should be removed + * @return true if a contour was found and removed, false if no contour was found */ - void ReinitializeInterpolation(); + bool RemoveContour(ContourPositionInformation contourInfo, bool keepPlaceholderForUndo = false); void RemoveObservers(); - void AddLabelSetConnection(unsigned int layerID); - - void UnsetSelectedImage() - { - m_SelectedSegmentation = nullptr; - } - - /** - * @brief Returns the number of layers in the current segmentation image. - * - */ - unsigned int GetNumberOfLayersInCurrentSegmentation() const; - - /** - * @brief Set the number of layers in the current segmentation image. - * - */ - void SetNumberOfLayersInCurrentSegmentation(unsigned int); - - /** - * @brief Function that does the data management when a layer is removed. - * - */ - void OnRemoveLayer(); - - /** - * @brief Function that does the data management when a layer is added. - * - */ - void OnAddLayer(); - - /** - * @brief Returns the number of available contours for the current selected segmentation - * @return the number of contours - */ - unsigned int GetNumberOfContours(); - /** * @brief Performs the interpolation. * */ - void Interpolate(); + void Interpolate(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); /** * @brief Get the Result of the interpolation operation. * * @return mitk::Surface::Pointer */ - mitk::Surface::Pointer GetInterpolationResult(); + mitk::Surface::Pointer GetInterpolationResult(const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface. * - * @param minSpacing Paramter to set + * @param minSpacing Parameter to set */ void SetMinSpacing(double minSpacing); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface * @param maxSpacing Set the max Spacing for interpolation */ void SetMaxSpacing(double maxSpacing); /** * Sets the volume i.e. the number of pixels that the distance image should have * By evaluation we found out that 50.000 pixel delivers a good result */ void SetDistanceImageVolume(unsigned int distImageVolume); /** * @brief Get the current selected segmentation for which the interpolation is performed * @return the current segmentation image */ - mitk::Image::Pointer GetCurrentSegmentation(); - - Surface *GetContoursAsSurface(); + mitk::LabelSetImage* GetCurrentSegmentation(); void SetDataStorage(DataStorage::Pointer ds); - /** - * Sets the current list of contourpoints which is used for the surface interpolation - * @param segmentation The current selected segmentation - * \deprecatedSince{2014_03} - */ - DEPRECATED(void SetCurrentSegmentationInterpolationList(mitk::Image::Pointer segmentation)); - /** * Sets the current list of contourpoints which is used for the surface interpolation * @param currentSegmentationImage The current selected segmentation */ - void SetCurrentInterpolationSession(mitk::Image::Pointer currentSegmentationImage); - - /** - * Removes the segmentation and all its contours from the list - * @param segmentation The segmentation to be removed - * \deprecatedSince{2014_03} - */ - DEPRECATED(void RemoveSegmentationFromContourList(mitk::Image *segmentation)); + void SetCurrentInterpolationSession(LabelSetImage* currentSegmentationImage); /** * @brief Remove interpolation session * @param segmentationImage the session to be removed */ - void RemoveInterpolationSession(mitk::Image::Pointer segmentationImage); - - /** - * Replaces the current interpolation session with a new one. All contours form the old - * session will be applied to the new session. This only works if the two images have the - * geometry - * @param oldSession the session which should be replaced - * @param newSession the new session which replaces the old one - * @return true it the the replacement was successful, false if not (e.g. the image's geometry differs) - */ - bool ReplaceInterpolationSession(mitk::Image::Pointer oldSession, mitk::Image::Pointer newSession); + void RemoveInterpolationSession(const LabelSetImage* segmentationImage); /** * @brief Removes all sessions */ void RemoveAllInterpolationSessions(); - mitk::Image *GetImage(); - /** * @brief Get the Contours at a certain timeStep and layerID. * * @param timeStep Time Step from which to get the contours. - * @param layerID Layer from which to get the contours. + * @param labelValue label from which to get the contours. * @return std::vector Returns contours. */ - ContourPositionInformationList* GetContours(unsigned int timeStep, unsigned int layerID); + CPIVector* GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); + + std::vector GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const; /** - * @brief Trigerred with the "Reinit Interpolation" action. The contours are used to repopulate the + * @brief Triggered with the "Reinit Interpolation" action. The contours are used to repopulate the * surfaceInterpolator data structures so that interpolation can be performed after reloading data. * * @param contourList List of contours extracted * @param contourPlanes List of planes at which the contours were extracted */ - void CompleteReinitialization(const std::vector& contourList, - std::vector& contourPlanes); + void CompleteReinitialization(const std::vector& newCPIs); /** - * @brief Removes contours of a particular label, at a given time step and layerID. + * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. * * @param label Label of contour to remove. * @param timeStep Time step in which to remove the contours. - * @param layerID Layer in which the contour should be removed. + * @remark if the label or time step does not exist, nothing happens. */ - void RemoveContours(mitk::Label::PixelType label, unsigned int timeStep, unsigned int layerID); + void RemoveContours(const LabelSetImage* segmentationImage, mitk::Label::PixelType label, TimeStepType timeStep); /** - * Estimates the memory which is needed to build up the equationsystem for the interpolation. - * \returns The percentage of the real memory which will be used by the interpolation - */ - double EstimatePortionOfNeededMemory(); - - /** - * Adds Contours from the active Label to the interpolation pipeline + * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. + * + * @param label Label of contour to remove. + * @param timeStep Time step in which to remove the contours. + * @remark if the label or time step does not exist, nothing happens. */ - void AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel); + void RemoveContours(const LabelSetImage* segmentationImage, mitk::Label::PixelType label); unsigned int GetNumberOfInterpolationSessions(); - /** - * @brief Removes the contour for a given plane for the current selected segmenation - * @param contourInfo the contour which should be removed - * @return true if a contour was found and removed, false if no contour was found - */ - bool RemoveContour(ContourPositionInformation contourInfo); - /** * @brief Get the Segmentation Image Node object * * @return DataNode* returns the DataNode containing the segmentation image. */ - mitk::DataNode* GetSegmentationImageNode(); - - + mitk::DataNode* GetSegmentationImageNode() const; protected: SurfaceInterpolationController(); ~SurfaceInterpolationController() override; template void GetImageBase(itk::Image *input, itk::ImageBase<3>::Pointer &result); private: /** * @brief * * @param caller * @param event */ void OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event); /** - * @brief Function that removes contours of a particular label when the "Remove Label" event is trigerred in the labelSetImage. + * @brief Function that removes contours of a particular label when the "Remove Label" event is triggered in the labelSetImage. * */ - void OnRemoveLabel(mitk::Label::PixelType removedLabelValue); + void OnRemoveLabel(const itk::Object* caller, const itk::EventObject& event); /** * @brief When a new contour is added to the pipeline or an existing contour is replaced, * the plane geometry information of that contour is added as a child node to the * current node of the segmentation image. This is useful in the retrieval of contour information * when data is reloaded after saving. * * @param contourInfo contourInfo struct to add to data storage. */ - void AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo); + void AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const; + + DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const; + DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const; + DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const; /** - * @brief Function that toggles active label, when the active label is changed. - * + * Adds Contours from the active Label to the interpolation pipeline */ - void OnActiveLabel(mitk::Label::PixelType); + void AddActiveLabelContoursForInterpolation(ReduceContourSetFilter* reduceFilter, const LabelSetImage* segmentationImage, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); /** * @brief Clears the interpolation data structures. Called from CompleteReinitialization(). * */ void ClearInterpolationSession(); + void RemoveObserversInternal(const mitk::LabelSetImage* segmentationImage); + /** * @brief Add contour to the interpolation pipeline * * @param contourInfo Contour information to be added * @param reinitializationAction If the contour is coming from a reinitialization process or not */ - void AddToInterpolationPipeline(ContourPositionInformation& contourInfo, bool reinitializationAction = false); - - /** - * @brief Function to respond to layer changed - * - */ - void OnLayerChanged(); - - itk::SmartPointer m_ReduceFilter; - itk::SmartPointer m_NormalsFilter; - itk::SmartPointer m_InterpolateSurfaceFilter; - - mitk::Surface::Pointer m_Contours; - - double m_DistanceImageSpacing; - - vtkSmartPointer m_PolyData; + void AddToCPIMap(ContourPositionInformation& contourInfo, bool reinitializationAction = false); + unsigned int m_DistanceImageVolume; mitk::DataStorage::Pointer m_DataStorage; - ContourContainer m_ListOfInterpolationSessions; - ContourListMap m_ListOfContours; - - mitk::Surface::Pointer m_InterpolationResult; - - unsigned int m_CurrentNumberOfReducedContours; - unsigned int m_NumberOfConnectionsAdded; - - mitk::Image *m_SelectedSegmentation; - - std::map m_SegmentationObserverTags; - - mitk::TimePointType m_CurrentTimePoint; - - unsigned int m_ContourIndex; - unsigned int m_ContourPosIndex; - unsigned int m_NumberOfLayersInCurrentSegmentation; - - mitk::Label::PixelType m_PreviousActiveLabelValue; - mitk::Label::PixelType m_CurrentActiveLabelValue; - - unsigned int m_PreviousLayerIndex; - unsigned int m_CurrentLayerIndex; + WeakPointer m_SelectedSegmentation; }; - - namespace ContourExt - { - /** - * @brief Returns the plane the contour belongs to. - * - * @param ContourNormal - * @return size_t - */ - size_t GetContourOrientation(const mitk::Vector3D& ContourNormal); - - /** - * @brief Function used to compute an interior point of the contour. - * Used to react to the merge label and erase label actions. - * - * - * @tparam VImageDimension Dimension of the image - * @param contour Contour for which to compute the interior point - * @param labelSetImage Label Set Image For which to find the contour - * @param currentTimePoint Current Time Point of the Image - * @return mitk::Point3D The returned point in the interior of the contour.s - */ - template - mitk::Point3D ComputeInteriorPointOfContour(const mitk::SurfaceInterpolationController::ContourPositionInformation& contour, - mitk::LabelSetImage * labelSetImage, - mitk::TimePointType currentTimePoint); - /** - * @brief Get a Grid points within the bounding box of the contour at a certain spacing. - * - * @param planeDimension Plane orientation (Sagittal, Coronal, Axial) - * @param startDim1 Starting coordinate along dimension 1 to start the grid point sampling from - * @param numPointsToSampleDim1 Number of points to sample along dimension 1 - * @param deltaDim1 Spacing for dimension 1 at which points should be sampled - * @param startDim2 Starting coordinate along dimension 2 to start the grid point sampling from - * @param numPointsToSampleDim2 Number of points to sample along dimension 2 - * @param deltaDim2 Spacing for dimension 1 at which points should be sampled - * @param valuePlaneDim Slice index of the plane in the volume - * @return std::vector< mitk::Point3D > The computed grid points are returned by the function. - */ - std::vector< mitk::Point3D > GetBoundingBoxGridPoints(size_t planeDimension, - double startDim1, - size_t numPointsToSampleDim1, - double deltaDim1, - double startDim2, - size_t numPointsToSampleDim2, - double deltaDim2, - double valuePlaneDim); - }; - } #endif diff --git a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp index df501800f1..5c8a807591 100644 --- a/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp +++ b/Plugins/org.mitk.gui.qt.imagecropper/src/internal/QmitkImageCropperView.cpp @@ -1,510 +1,510 @@ /*============================================================================ 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 "QmitkImageCropperView.h" #include #include #include #include #include #include #include #include #include #include #include #include #include const std::string QmitkImageCropperView::VIEW_ID = "org.mitk.views.qmitkimagecropper"; QmitkImageCropperView::QmitkImageCropperView(QObject *) : m_ParentWidget(nullptr) , m_BoundingShapeInteractor(nullptr) , m_CropOutsideValue(0) { CreateBoundingShapeInteractor(false); } QmitkImageCropperView::~QmitkImageCropperView() { //disable interactor if (m_BoundingShapeInteractor != nullptr) { m_BoundingShapeInteractor->SetDataNode(nullptr); m_BoundingShapeInteractor->EnableInteraction(false); } } void QmitkImageCropperView::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.imageSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.imageSelectionWidget->SetNodePredicate( mitk::NodePredicateAnd::New(mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls.imageSelectionWidget->SetSelectionIsOptional(true); m_Controls.imageSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.imageSelectionWidget->SetEmptyInfo(QString("Please select an image node")); m_Controls.imageSelectionWidget->SetPopUpTitel(QString("Select image node")); connect(m_Controls.imageSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnImageSelectionChanged); m_Controls.boundingBoxSelectionWidget->SetDataStorage(GetDataStorage()); m_Controls.boundingBoxSelectionWidget->SetNodePredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); m_Controls.boundingBoxSelectionWidget->SetSelectionIsOptional(true); m_Controls.boundingBoxSelectionWidget->SetAutoSelectNewNodes(true); m_Controls.boundingBoxSelectionWidget->SetEmptyInfo(QString("Please select a bounding box")); m_Controls.boundingBoxSelectionWidget->SetPopUpTitel(QString("Select bounding box node")); connect(m_Controls.boundingBoxSelectionWidget, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkImageCropperView::OnBoundingBoxSelectionChanged); connect(m_Controls.buttonCreateNewBoundingBox, SIGNAL(clicked()), this, SLOT(OnCreateNewBoundingBox())); connect(m_Controls.buttonCropping, SIGNAL(clicked()), this, SLOT(OnCropping())); connect(m_Controls.buttonMasking, SIGNAL(clicked()), this, SLOT(OnMasking())); auto lambda = [this]() { m_Controls.groupImageSettings->setVisible(!m_Controls.groupImageSettings->isVisible()); }; connect(m_Controls.buttonAdvancedSettings, &ctkExpandButton::clicked, this, lambda); connect(m_Controls.spinBoxOutsidePixelValue, SIGNAL(valueChanged(int)), this, SLOT(OnSliderValueChanged(int))); SetDefaultGUI(); m_ParentWidget = parent; this->OnImageSelectionChanged(m_Controls.imageSelectionWidget->GetSelectedNodes()); this->OnBoundingBoxSelectionChanged(m_Controls.boundingBoxSelectionWidget->GetSelectedNodes()); } void QmitkImageCropperView::OnImageSelectionChanged(QList) { bool rotationEnabled = false; m_Controls.labelWarningRotation->setVisible(false); auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { SetDefaultGUI(); return; } auto image = dynamic_cast(imageNode->GetData()); if (nullptr != image) { if (image->GetDimension() < 3) { QMessageBox::warning(nullptr, tr("Invalid image selected"), tr("ImageCropper only works with 3 or more dimensions."), QMessageBox::Ok | QMessageBox::NoButton, QMessageBox::NoButton); SetDefaultGUI(); return; } m_ParentWidget->setEnabled(true); m_Controls.buttonCreateNewBoundingBox->setEnabled(true); vtkSmartPointer imageMat = image->GetGeometry()->GetVtkMatrix(); // check whether the image geometry is rotated; if so, no pixel aligned cropping or masking can be performed if ((imageMat->GetElement(1, 0) == 0.0) && (imageMat->GetElement(0, 1) == 0.0) && (imageMat->GetElement(1, 2) == 0.0) && (imageMat->GetElement(2, 1) == 0.0) && (imageMat->GetElement(2, 0) == 0.0) && (imageMat->GetElement(0, 2) == 0.0)) { rotationEnabled = false; m_Controls.labelWarningRotation->setVisible(false); } else { rotationEnabled = true; m_Controls.labelWarningRotation->setStyleSheet(" QLabel { color: rgb(255, 0, 0) }"); m_Controls.labelWarningRotation->setVisible(true); } this->CreateBoundingShapeInteractor(rotationEnabled); if (itk::IOPixelEnum::SCALAR == image->GetPixelType().GetPixelType()) { // Might be changed with the upcoming new image statistics plugin //(recomputation might be very expensive for large images ;) ) auto statistics = image->GetStatistics(); auto minPixelValue = statistics->GetScalarValueMin(); auto maxPixelValue = statistics->GetScalarValueMax(); if (minPixelValue < std::numeric_limits::min()) { minPixelValue = std::numeric_limits::min(); } if (maxPixelValue > std::numeric_limits::max()) { maxPixelValue = std::numeric_limits::max(); } m_Controls.spinBoxOutsidePixelValue->setEnabled(true); m_Controls.spinBoxOutsidePixelValue->setMaximum(static_cast(maxPixelValue)); m_Controls.spinBoxOutsidePixelValue->setMinimum(static_cast(minPixelValue)); m_Controls.spinBoxOutsidePixelValue->setValue(static_cast(minPixelValue)); } else { m_Controls.spinBoxOutsidePixelValue->setEnabled(false); } unsigned int dim = image->GetDimension(); if (dim < 2 || dim > 4) { m_ParentWidget->setEnabled(false); } if (m_Controls.boundingBoxSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnBoundingBoxSelectionChanged(QList) { auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { SetDefaultGUI(); m_BoundingShapeInteractor->EnableInteraction(false); m_BoundingShapeInteractor->SetDataNode(nullptr); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCreateNewBoundingBox->setEnabled(true); } return; } auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != boundingBox) { // node newly selected boundingBoxNode->SetVisibility(true); m_BoundingShapeInteractor->EnableInteraction(true); m_BoundingShapeInteractor->SetDataNode(boundingBoxNode); mitk::RenderingManager::GetInstance()->InitializeViews(); if (m_Controls.imageSelectionWidget->GetSelectedNode().IsNotNull()) { m_Controls.buttonCropping->setEnabled(true); m_Controls.buttonMasking->setEnabled(true); m_Controls.buttonAdvancedSettings->setEnabled(true); m_Controls.groupImageSettings->setEnabled(true); } } } void QmitkImageCropperView::OnCreateNewBoundingBox() { auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { return; } if (nullptr == imageNode->GetData()) { return; } QString name = QString::fromStdString(imageNode->GetName() + " Bounding Box"); auto boundingShape = this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&name](const mitk::DataNode *node) { return 0 == node->GetName().compare(name.toStdString()); })); if (nullptr != boundingShape) { name = this->AdaptBoundingObjectName(name); } // get current timestep to support 3d+t images auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const mitk::TimePointType timePoint = renderWindowPart->GetSelectedTimePoint(); const auto imageGeometry = imageNode->GetData()->GetTimeGeometry()->GetGeometryForTimePoint(timePoint); auto boundingBox = mitk::GeometryData::New(); boundingBox->SetGeometry(static_cast(this->InitializeWithImageGeometry(imageGeometry))); auto boundingBoxNode = mitk::DataNode::New(); boundingBoxNode->SetData(boundingBox); boundingBoxNode->SetProperty("name", mitk::StringProperty::New(name.toStdString())); boundingBoxNode->SetProperty("layer", mitk::IntProperty::New(99)); boundingBoxNode->AddProperty("Bounding Shape.Handle Size Factor", mitk::DoubleProperty::New(0.02)); boundingBoxNode->SetBoolProperty("pickable", true); if (!this->GetDataStorage()->Exists(boundingBoxNode)) { GetDataStorage()->Add(boundingBoxNode, imageNode); } m_Controls.boundingBoxSelectionWidget->SetCurrentSelectedNode(boundingBoxNode); } void QmitkImageCropperView::OnCropping() { this->ProcessImage(false); } void QmitkImageCropperView::OnMasking() { this->ProcessImage(true); } void QmitkImageCropperView::OnSliderValueChanged(int slidervalue) { m_CropOutsideValue = slidervalue; } void QmitkImageCropperView::CreateBoundingShapeInteractor(bool rotationEnabled) { if (m_BoundingShapeInteractor.IsNull()) { m_BoundingShapeInteractor = mitk::BoundingShapeInteractor::New(); m_BoundingShapeInteractor->LoadStateMachine("BoundingShapeInteraction.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); m_BoundingShapeInteractor->SetEventConfig("BoundingShapeMouseConfig.xml", us::ModuleRegistry::GetModule("MitkBoundingShape")); } m_BoundingShapeInteractor->SetRotationEnabled(rotationEnabled); } mitk::Geometry3D::Pointer QmitkImageCropperView::InitializeWithImageGeometry(const mitk::BaseGeometry* geometry) const { // convert a BaseGeometry into a Geometry3D (otherwise IO is not working properly) if (geometry == nullptr) mitkThrow() << "Geometry is not valid."; auto boundingGeometry = mitk::Geometry3D::New(); boundingGeometry->SetBounds(geometry->GetBounds()); boundingGeometry->SetImageGeometry(geometry->GetImageGeometry()); boundingGeometry->SetOrigin(geometry->GetOrigin()); boundingGeometry->SetSpacing(geometry->GetSpacing()); boundingGeometry->SetIndexToWorldTransform(geometry->GetIndexToWorldTransform()->Clone()); boundingGeometry->Modified(); return boundingGeometry; } void QmitkImageCropperView::ProcessImage(bool mask) { auto renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::IRenderWindowPartStrategy::OPEN); const auto timePoint = renderWindowPart->GetSelectedTimePoint(); auto imageNode = m_Controls.imageSelectionWidget->GetSelectedNode(); if (imageNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); return; } auto boundingBoxNode = m_Controls.boundingBoxSelectionWidget->GetSelectedNode(); if (boundingBoxNode.IsNull()) { QMessageBox::information(nullptr, "Warning", "Please load and select a cropping object before starting image processing."); return; } if (!imageNode->GetData()->GetTimeGeometry()->IsValidTimePoint(timePoint)) { QMessageBox::information(nullptr, "Warning", "Please select a time point that is within the time bounds of the selected image."); return; } const auto timeStep = imageNode->GetData()->GetTimeGeometry()->TimePointToTimeStep(timePoint); - auto image = dynamic_cast(imageNode->GetData()); - auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); + const auto image = dynamic_cast(imageNode->GetData()); + const auto boundingBox = dynamic_cast(boundingBoxNode->GetData()); if (nullptr != image && nullptr != boundingBox) { // Check if initial node name is already in box name std::string imagePrefix = ""; if (boundingBoxNode->GetName().find(imageNode->GetName()) != 0) { imagePrefix = imageNode->GetName() + "_"; } QString imageName; if (mask) { imageName = QString::fromStdString(imagePrefix + boundingBoxNode->GetName() + "_masked"); } else { imageName = QString::fromStdString(imagePrefix + boundingBoxNode->GetName() + "_cropped"); } if (m_Controls.checkBoxCropTimeStepOnly->isChecked()) { imageName = imageName + "_T" + QString::number(timeStep); } // image and bounding shape ok, set as input auto croppedImageNode = mitk::DataNode::New(); auto cutter = mitk::BoundingShapeCropper::New(); cutter->SetGeometry(boundingBox); // adjustable in advanced settings cutter->SetUseWholeInputRegion(mask); //either mask (mask=true) or crop (mask=false) cutter->SetOutsideValue(m_CropOutsideValue); cutter->SetUseCropTimeStepOnly(m_Controls.checkBoxCropTimeStepOnly->isChecked()); cutter->SetCurrentTimeStep(timeStep); // TODO: Add support for MultiLayer (right now only Mulitlabel support) - auto labelsetImageInput = dynamic_cast(image); + const auto labelsetImageInput = dynamic_cast(image); if (nullptr != labelsetImageInput) { cutter->SetInput(labelsetImageInput); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok | QMessageBox::NoButton, QMessageBox::NoButton); return; } auto labelSetImage = mitk::LabelSetImage::New(); labelSetImage->InitializeByLabeledImage(cutter->GetOutput()); for (unsigned int i = 0; i < labelsetImageInput->GetNumberOfLayers(); i++) { - labelSetImage->AddLabelSetToLayer(i, labelsetImageInput->GetLabelSet(i)); + labelSetImage->ReplaceGroupLabels(i, labelsetImageInput->GetConstLabelsByValue(labelsetImageInput->GetLabelValuesByGroup(i))); } croppedImageNode->SetData(labelSetImage); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { imageNode->SetData(labelSetImage); imageNode->Modified(); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); } } else { cutter->SetInput(image); // do the actual cutting try { cutter->Update(); } catch (const itk::ExceptionObject& e) { std::string message = std::string("The Cropping filter could not process because of: \n ") + e.GetDescription(); QMessageBox::warning(nullptr, tr("Cropping not possible!"), tr(message.c_str()), QMessageBox::Ok | QMessageBox::NoButton, QMessageBox::NoButton); return; } //add cropping result to the current data storage as child node to the image node if (!m_Controls.checkOverwriteImage->isChecked()) { croppedImageNode->SetData(cutter->GetOutput()); croppedImageNode->SetProperty("name", mitk::StringProperty::New(imageName.toStdString())); croppedImageNode->SetProperty("color", mitk::ColorProperty::New(1.0, 1.0, 1.0)); mitk::LevelWindow levelWindow; imageNode->GetLevelWindow(levelWindow); croppedImageNode->SetLevelWindow(levelWindow); if (!this->GetDataStorage()->Exists(croppedImageNode)) { this->GetDataStorage()->Add(croppedImageNode, imageNode); imageNode->SetVisibility(mask); // Give the user a visual clue that something happened when image was cropped } } else // original image will be overwritten by the result image and the bounding box of the result is adjusted { mitk::LevelWindow levelWindow; imageNode->GetLevelWindow(levelWindow); imageNode->SetData(cutter->GetOutput()); imageNode->SetLevelWindow(levelWindow); // Adjust coordinate system by doing a reinit on auto tempDataStorage = mitk::DataStorage::SetOfObjects::New(); tempDataStorage->InsertElement(0, imageNode); // initialize the views to the bounding geometry auto bounds = this->GetDataStorage()->ComputeBoundingGeometry3D(tempDataStorage); mitk::RenderingManager::GetInstance()->InitializeViews(bounds); } } } else { QMessageBox::information(nullptr, "Warning", "Please load and select an image before starting image processing."); } } void QmitkImageCropperView::SetDefaultGUI() { m_Controls.buttonCreateNewBoundingBox->setEnabled(false); m_Controls.buttonCropping->setEnabled(false); m_Controls.buttonMasking->setEnabled(false); m_Controls.buttonAdvancedSettings->setEnabled(false); m_Controls.groupImageSettings->setEnabled(false); m_Controls.groupImageSettings->setVisible(false); m_Controls.checkOverwriteImage->setChecked(false); m_Controls.checkBoxCropTimeStepOnly->setChecked(false); } QString QmitkImageCropperView::AdaptBoundingObjectName(const QString& name) const { unsigned int counter = 2; QString newName = QString("%1 %2").arg(name).arg(counter); while (nullptr != this->GetDataStorage()->GetNode(mitk::NodePredicateFunction::New([&newName](const mitk::DataNode *node) { return 0 == node->GetName().compare(newName.toStdString()); }))) { newName = QString("%1 %2").arg(name).arg(++counter); } return newName; } diff --git a/Plugins/org.mitk.gui.qt.segmentation/files.cmake b/Plugins/org.mitk.gui.qt.segmentation/files.cmake index 38abb65df5..8d18612b19 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/files.cmake +++ b/Plugins/org.mitk.gui.qt.segmentation/files.cmake @@ -1,81 +1,72 @@ set(SRC_CPP_FILES QmitkSegmentationPreferencePage.cpp QmitkNewSegmentationDialog.cpp - QmitkLabelSetWidget.cpp QmitkSegmentAnythingPreferencePage.cpp ) set(INTERNAL_CPP_FILES mitkPluginActivator.cpp QmitkSegmentationView.cpp QmitkSegmentationUtilitiesView.cpp QmitkSegmentationTaskListView.cpp QmitkAutocropAction.cpp QmitkAutocropLabelSetImageAction.cpp QmitkCreatePolygonModelAction.cpp QmitkLoadMultiLabelPresetAction.cpp QmitkSaveMultiLabelPresetAction.cpp QmitkConvertSurfaceToLabelAction.cpp QmitkConvertMaskToLabelAction.cpp QmitkConvertToMultiLabelSegmentationAction.cpp QmitkCreateMultiLabelSegmentationAction.cpp - Common/QmitkLabelsWidget.cpp - Common/QmitkLayersWidget.cpp ) set(UI_FILES src/QmitkSegmentationPreferencePageControls.ui src/QmitkSegmentAnythingPreferencePage.ui src/QmitkNewSegmentationDialog.ui - src/QmitkLabelSetWidgetControls.ui src/internal/QmitkSegmentationViewControls.ui src/internal/QmitkSegmentationUtilitiesViewControls.ui src/internal/QmitkSegmentationTaskListView.ui - src/internal/Common/QmitkLabelsWidgetControls.ui - src/internal/Common/QmitkLayersWidgetControls.ui ) set(MOC_H_FILES src/QmitkSegmentationPreferencePage.h src/QmitkSegmentAnythingPreferencePage.h src/QmitkNewSegmentationDialog.h - src/QmitkLabelSetWidget.h src/internal/mitkPluginActivator.h src/internal/QmitkSegmentationView.h src/internal/QmitkSegmentationUtilitiesView.h src/internal/QmitkSegmentationTaskListView.h src/internal/QmitkAutocropAction.h src/internal/QmitkAutocropLabelSetImageAction.h src/internal/QmitkCreatePolygonModelAction.h src/internal/QmitkLoadMultiLabelPresetAction.h src/internal/QmitkSaveMultiLabelPresetAction.h src/internal/QmitkConvertSurfaceToLabelAction.h src/internal/QmitkConvertMaskToLabelAction.h src/internal/QmitkConvertToMultiLabelSegmentationAction.h src/internal/QmitkCreateMultiLabelSegmentationAction.h - src/internal/Common/QmitkLabelsWidget.h - src/internal/Common/QmitkLayersWidget.h ) set(CACHED_RESOURCE_FILES resources/segmentation.svg resources/segmentation_utilities.svg resources/SegmentationTaskListIcon.svg plugin.xml ) set(QRC_FILES resources/segmentation.qrc resources/SegmentationUtilities.qrc resources/SegmentationTaskList.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/src/QmitkLabelSetWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.cpp deleted file mode 100644 index 4cd0404652..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.cpp +++ /dev/null @@ -1,1191 +0,0 @@ -/*============================================================================ - -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 "QmitkLabelSetWidget.h" - -// mitk -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// Qmitk -#include -#include -#include - -// Qt -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// itk -#include - -QmitkLabelSetWidget::QmitkLabelSetWidget(QWidget *parent) - : QWidget(parent), m_DataStorage(nullptr), m_Completer(nullptr), m_ToolManager(nullptr), m_ProcessingManualSelection(false) -{ - m_Controls.setupUi(this); - - m_ColorSequenceRainbow.GoToBegin(); - - m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); - - m_Controls.m_LabelSearchBox->setAlwaysShowClearIcon(true); - m_Controls.m_LabelSearchBox->setShowSearchIcon(true); - - QStringList completionList; - completionList << ""; - m_Completer = new QCompleter(completionList, this); - m_Completer->setCaseSensitivity(Qt::CaseInsensitive); - m_Controls.m_LabelSearchBox->setCompleter(m_Completer); - - connect(m_Controls.m_LabelSearchBox, SIGNAL(returnPressed()), this, SLOT(OnSearchLabel())); - - auto* renameLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_R), this); - connect(renameLabelShortcut, &QShortcut::activated, this, &QmitkLabelSetWidget::OnRenameLabelShortcutActivated); - - QStringListModel *completeModel = static_cast(m_Completer->model()); - completeModel->setStringList(GetLabelStringList()); - - m_Controls.m_LabelSearchBox->setEnabled(false); - - m_Controls.m_lblCaption->setText(""); - - InitializeTableWidget(); -} - -QmitkLabelSetWidget::~QmitkLabelSetWidget() {} - -void QmitkLabelSetWidget::OnTableViewContextMenuRequested(const QPoint & /*pos*/) -{ - int pixelValue = GetPixelValueOfSelectedItem(); - - if (-1 == pixelValue) - return; - - QMenu *menu = new QMenu(m_Controls.m_LabelSetTableWidget); - - if (m_Controls.m_LabelSetTableWidget->selectedItems().size() > 1) - { - QAction *mergeAction = new QAction(QIcon(":/Qmitk/MergeLabels.png"), "Merge selection on current label", this); - mergeAction->setEnabled(true); - QObject::connect(mergeAction, SIGNAL(triggered(bool)), this, SLOT(OnMergeLabels(bool))); - menu->addAction(mergeAction); - - QAction *removeLabelsAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Remove selected labels", this); - removeLabelsAction->setEnabled(true); - QObject::connect(removeLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnRemoveLabels(bool))); - menu->addAction(removeLabelsAction); - - QAction *eraseLabelsAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "Erase selected labels", this); - eraseLabelsAction->setEnabled(true); - QObject::connect(eraseLabelsAction, SIGNAL(triggered(bool)), this, SLOT(OnEraseLabels(bool))); - menu->addAction(eraseLabelsAction); - } - else - { - QAction *renameAction = new QAction(QIcon(":/Qmitk/RenameLabel.png"), "Rename...", this); - renameAction->setEnabled(true); - QObject::connect(renameAction, SIGNAL(triggered(bool)), this, SLOT(OnRenameLabel(bool))); - menu->addAction(renameAction); - - QAction *removeAction = new QAction(QIcon(":/Qmitk/RemoveLabel.png"), "Remove...", this); - removeAction->setEnabled(true); - QObject::connect(removeAction, SIGNAL(triggered(bool)), this, SLOT(OnRemoveLabel(bool))); - menu->addAction(removeAction); - - QAction *eraseAction = new QAction(QIcon(":/Qmitk/EraseLabel.png"), "Erase...", this); - eraseAction->setEnabled(true); - QObject::connect(eraseAction, SIGNAL(triggered(bool)), this, SLOT(OnEraseLabel(bool))); - menu->addAction(eraseAction); - - QAction *randomColorAction = new QAction(QIcon(":/Qmitk/RandomColor.png"), "Random color", this); - randomColorAction->setEnabled(true); - QObject::connect(randomColorAction, SIGNAL(triggered(bool)), this, SLOT(OnRandomColor(bool))); - menu->addAction(randomColorAction); - - QAction *viewOnlyAction = new QAction(QIcon(":/Qmitk/visible.png"), "View only", this); - viewOnlyAction->setEnabled(true); - QObject::connect(viewOnlyAction, SIGNAL(triggered(bool)), this, SLOT(OnSetOnlyActiveLabelVisible(bool))); - menu->addAction(viewOnlyAction); - - QAction *viewAllAction = new QAction(QIcon(":/Qmitk/visible.png"), "View all", this); - viewAllAction->setEnabled(true); - QObject::connect(viewAllAction, SIGNAL(triggered(bool)), this, SLOT(OnSetAllLabelsVisible(bool))); - menu->addAction(viewAllAction); - - QAction *hideAllAction = new QAction(QIcon(":/Qmitk/invisible.png"), "Hide all", this); - hideAllAction->setEnabled(true); - QObject::connect(hideAllAction, SIGNAL(triggered(bool)), this, SLOT(OnSetAllLabelsInvisible(bool))); - menu->addAction(hideAllAction); - - QAction *lockAllAction = new QAction(QIcon(":/Qmitk/lock.png"), "Lock all", this); - lockAllAction->setEnabled(true); - QObject::connect(lockAllAction, SIGNAL(triggered(bool)), this, SLOT(OnLockAllLabels(bool))); - menu->addAction(lockAllAction); - - QAction *unlockAllAction = new QAction(QIcon(":/Qmitk/unlock.png"), "Unlock all", this); - unlockAllAction->setEnabled(true); - QObject::connect(unlockAllAction, SIGNAL(triggered(bool)), this, SLOT(OnUnlockAllLabels(bool))); - menu->addAction(unlockAllAction); - - QAction *createSurfaceAction = new QAction(QIcon(":/Qmitk/CreateSurface.png"), "Create surface", this); - createSurfaceAction->setEnabled(true); - createSurfaceAction->setMenu(new QMenu()); - - QAction *tmp1 = createSurfaceAction->menu()->addAction(QString("Detailed")); - QAction *tmp2 = createSurfaceAction->menu()->addAction(QString("Smoothed")); - - QObject::connect(tmp1, SIGNAL(triggered(bool)), this, SLOT(OnCreateDetailedSurface(bool))); - QObject::connect(tmp2, SIGNAL(triggered(bool)), this, SLOT(OnCreateSmoothedSurface(bool))); - - menu->addAction(createSurfaceAction); - - QAction *createMaskAction = new QAction(QIcon(":/Qmitk/CreateMask.png"), "Create mask", this); - createMaskAction->setEnabled(true); - QObject::connect(createMaskAction, SIGNAL(triggered(bool)), this, SLOT(OnCreateMask(bool))); - - menu->addAction(createMaskAction); - - QAction *createCroppedMaskAction = new QAction(QIcon(":/Qmitk/CreateMask.png"), "Create cropped mask", this); - createCroppedMaskAction->setEnabled(true); - QObject::connect(createCroppedMaskAction, SIGNAL(triggered(bool)), this, SLOT(OnCreateCroppedMask(bool))); - - menu->addAction(createCroppedMaskAction); - - QSlider *opacitySlider = new QSlider; - opacitySlider->setMinimum(0); - opacitySlider->setMaximum(100); - opacitySlider->setOrientation(Qt::Horizontal); - QObject::connect(opacitySlider, SIGNAL(valueChanged(int)), this, SLOT(OnOpacityChanged(int))); - - QLabel *_OpacityLabel = new QLabel("Opacity: "); - QVBoxLayout *_OpacityWidgetLayout = new QVBoxLayout; - _OpacityWidgetLayout->setContentsMargins(4, 4, 4, 4); - _OpacityWidgetLayout->addWidget(_OpacityLabel); - _OpacityWidgetLayout->addWidget(opacitySlider); - QWidget *_OpacityWidget = new QWidget; - _OpacityWidget->setLayout(_OpacityWidgetLayout); - - QWidgetAction *OpacityAction = new QWidgetAction(this); - OpacityAction->setDefaultWidget(_OpacityWidget); - // QObject::connect( m_OpacityAction, SIGNAL( changed() ), this, SLOT( OpacityActionChanged() ) ); - auto workingImage = this->GetWorkingImage(); - auto activeLayer = workingImage->GetActiveLayer(); - auto label = workingImage->GetLabel(pixelValue, activeLayer); - - if (nullptr != label) - { - auto opacity = label->GetOpacity(); - opacitySlider->setValue(static_cast(opacity * 100)); - } - - menu->addAction(OpacityAction); - } - menu->popup(QCursor::pos()); -} - -void QmitkLabelSetWidget::OnUnlockAllLabels(bool /*value*/) -{ - GetWorkingImage()->GetActiveLabelSet()->SetAllLabelsLocked(false); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkLabelSetWidget::OnLockAllLabels(bool /*value*/) -{ - GetWorkingImage()->GetActiveLabelSet()->SetAllLabelsLocked(true); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkLabelSetWidget::OnSetAllLabelsVisible(bool /*value*/) -{ - GetWorkingImage()->GetActiveLabelSet()->SetAllLabelsVisible(true); - UpdateAllTableWidgetItems(); -} - -void QmitkLabelSetWidget::OnSetAllLabelsInvisible(bool /*value*/) -{ - GetWorkingImage()->GetActiveLabelSet()->SetAllLabelsVisible(false); - UpdateAllTableWidgetItems(); -} - -void QmitkLabelSetWidget::OnSetOnlyActiveLabelVisible(bool /*value*/) -{ - mitk::LabelSetImage *workingImage = GetWorkingImage(); - int pixelValue = GetPixelValueOfSelectedItem(); - - workingImage->GetActiveLabelSet()->SetAllLabelsVisible(false); - workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->SetVisible(true); - - workingImage->GetActiveLabelSet()->UpdateLookupTable(pixelValue); - - this->WaitCursorOn(); - - const mitk::Point3D &pos = - workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetCenterOfMassCoordinates(); - this->WaitCursorOff(); - if (pos.GetVnlVector().max_value() > 0.0) - { - emit goToLabel(pos); - } - - UpdateAllTableWidgetItems(); -} - -void QmitkLabelSetWidget::OnEraseLabel(bool /*value*/) -{ - int pixelValue = GetPixelValueOfSelectedItem(); - QString question = "Do you really want to erase the contents of label \""; - question.append( - QString::fromStdString(GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetName())); - question.append("\"?"); - - QMessageBox::StandardButton answerButton = - QMessageBox::question(this, "Erase label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); - - if (answerButton == QMessageBox::Yes) - { - this->WaitCursorOn(); - GetWorkingImage()->EraseLabel(pixelValue); - this->WaitCursorOff(); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - } -} - -void QmitkLabelSetWidget::OnRemoveLabel(bool /*value*/) -{ - int pixelValue = GetPixelValueOfSelectedItem(); - QString question = "Do you really want to remove label \""; - question.append( - QString::fromStdString(GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetName())); - question.append("\"?"); - - QMessageBox::StandardButton answerButton = - QMessageBox::question(this, "Remove label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); - - if (answerButton == QMessageBox::Yes) - { - this->WaitCursorOn(); - GetWorkingImage()->RemoveLabel(pixelValue); - this->WaitCursorOff(); - } - - ResetAllTableWidgetItems(); -} - -void QmitkLabelSetWidget::OnRenameLabel(bool /*value*/) -{ - int pixelValue = GetPixelValueOfSelectedItem(); - QmitkNewSegmentationDialog dialog(this, this->GetWorkingImage(), QmitkNewSegmentationDialog::RenameLabel); - dialog.SetColor(GetWorkingImage()->GetActiveLabelSet()->GetLabel(pixelValue)->GetColor()); - dialog.SetName(QString::fromStdString(GetWorkingImage()->GetActiveLabelSet()->GetLabel(pixelValue)->GetName())); - - if (dialog.exec() == QDialog::Rejected) - { - return; - } - QString segmentationName = dialog.GetName(); - if (segmentationName.isEmpty()) - { - segmentationName = "Unnamed"; - } - - GetWorkingImage()->GetActiveLabelSet()->RenameLabel(pixelValue, segmentationName.toStdString(), dialog.GetColor()); - GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); - - UpdateAllTableWidgetItems(); -} - -void QmitkLabelSetWidget::OnRenameLabelShortcutActivated() -{ - if (m_Controls.m_LabelSetTableWidget->selectedItems().size() == 1) - this->OnRenameLabel(true); -} - -void QmitkLabelSetWidget::OnCombineAndCreateMask(bool /*value*/) -{ - m_Controls.m_LabelSetTableWidget->selectedRanges(); - // ...to do... // -} - -void QmitkLabelSetWidget::OnCreateMasks(bool /*value*/) -{ - m_Controls.m_LabelSetTableWidget->selectedRanges(); - // ..to do.. // -} - -void QmitkLabelSetWidget::OnCombineAndCreateSurface(bool /*value*/) -{ - m_Controls.m_LabelSetTableWidget->selectedRanges(); - // ..to do.. // -} - -void QmitkLabelSetWidget::OnEraseLabels(bool /*value*/) -{ - QString question = "Do you really want to erase the selected labels?"; - - QMessageBox::StandardButton answerButton = QMessageBox::question( - this, "Erase selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); - - if (answerButton == QMessageBox::Yes) - { - QList ranges = m_Controls.m_LabelSetTableWidget->selectedRanges(); - if (ranges.isEmpty()) - return; - - std::vector VectorOfLablePixelValues; - foreach (QTableWidgetSelectionRange a, ranges) - for (int i = a.topRow(); i <= a.bottomRow(); i++) - VectorOfLablePixelValues.push_back(m_Controls.m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt()); - - this->WaitCursorOn(); - GetWorkingImage()->EraseLabels(VectorOfLablePixelValues); - this->WaitCursorOff(); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - } -} - -void QmitkLabelSetWidget::OnRemoveLabels(bool /*value*/) -{ - QString question = "Do you really want to remove the selected labels?"; - QMessageBox::StandardButton answerButton = QMessageBox::question( - this, "Remove selected labels", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); - - if (answerButton == QMessageBox::Yes) - { - QList ranges = m_Controls.m_LabelSetTableWidget->selectedRanges(); - if (ranges.isEmpty()) - { - return; - } - - std::vector VectorOfLablePixelValues; - foreach (QTableWidgetSelectionRange a, ranges) - { - for (int i = a.topRow(); i <= a.bottomRow(); ++i) - { - VectorOfLablePixelValues.push_back(m_Controls.m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt()); - } - } - - this->WaitCursorOn(); - GetWorkingImage()->RemoveLabels(VectorOfLablePixelValues); - this->WaitCursorOff(); - } - - ResetAllTableWidgetItems(); -} - -void QmitkLabelSetWidget::OnMergeLabels(bool /*value*/) -{ - int pixelValue = GetPixelValueOfSelectedItem(); - QString question = "Do you really want to merge selected labels into \""; - question.append( - QString::fromStdString(GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetName())); - question.append("\"?"); - - QMessageBox::StandardButton answerButton = QMessageBox::question( - this, "Merge selected label", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); - - if (answerButton == QMessageBox::Yes) - { - QList ranges = m_Controls.m_LabelSetTableWidget->selectedRanges(); - if (ranges.isEmpty()) - { - return; - } - - std::vector vectorOfSourcePixelValues; - foreach (QTableWidgetSelectionRange a, ranges) - { - for (int i = a.topRow(); i <= a.bottomRow(); ++i) - { - vectorOfSourcePixelValues.push_back(m_Controls.m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt()); - } - } - - this->WaitCursorOn(); - GetWorkingImage()->MergeLabels(pixelValue, vectorOfSourcePixelValues, GetWorkingImage()->GetActiveLayer()); - this->WaitCursorOff(); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - } -} - -void QmitkLabelSetWidget::OnLockedButtonClicked() -{ - int row = -1; - for (int i = 0; i < m_Controls.m_LabelSetTableWidget->rowCount(); ++i) - { - if (sender() == m_Controls.m_LabelSetTableWidget->cellWidget(i, LOCKED_COL)) - { - row = i; - } - } - if (row >= 0 && row < m_Controls.m_LabelSetTableWidget->rowCount()) - { - int pixelValue = m_Controls.m_LabelSetTableWidget->item(row, 0)->data(Qt::UserRole).toInt(); - GetWorkingImage() - ->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer()) - ->SetLocked(!GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetLocked()); - } -} - -void QmitkLabelSetWidget::OnVisibleButtonClicked() -{ - int row = -1; - for (int i = 0; i < m_Controls.m_LabelSetTableWidget->rowCount(); ++i) - { - if (sender() == m_Controls.m_LabelSetTableWidget->cellWidget(i, VISIBLE_COL)) - { - row = i; - break; - } - } - - if (row >= 0 && row < m_Controls.m_LabelSetTableWidget->rowCount()) - { - QTableWidgetItem *item = m_Controls.m_LabelSetTableWidget->item(row, 0); - int pixelValue = item->data(Qt::UserRole).toInt(); - GetWorkingImage() - ->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer()) - ->SetVisible(!GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetVisible()); - GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); - } - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkLabelSetWidget::OnColorButtonClicked() -{ - int row = -1; - for (int i = 0; i < m_Controls.m_LabelSetTableWidget->rowCount(); ++i) - { - if (sender() == m_Controls.m_LabelSetTableWidget->cellWidget(i, COLOR_COL)) - { - row = i; - } - } - - if (row >= 0 && row < m_Controls.m_LabelSetTableWidget->rowCount()) - { - int pixelValue = m_Controls.m_LabelSetTableWidget->item(row, 0)->data(Qt::UserRole).toInt(); - const mitk::Color &color = GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetColor(); - QColor initial(color.GetRed() * 255, color.GetGreen() * 255, color.GetBlue() * 255); - QColor qcolor = QColorDialog::getColor(initial, nullptr, QString("Change color")); - if (!qcolor.isValid()) - { - return; - } - - QPushButton *button = static_cast(m_Controls.m_LabelSetTableWidget->cellWidget(row, COLOR_COL)); - if (!button) - { - return; - } - - button->setAutoFillBackground(true); - - QString styleSheet = "background-color:rgb("; - styleSheet.append(QString::number(qcolor.red())); - styleSheet.append(","); - styleSheet.append(QString::number(qcolor.green())); - styleSheet.append(","); - styleSheet.append(QString::number(qcolor.blue())); - styleSheet.append("); border: 0;"); - button->setStyleSheet(styleSheet); - - mitk::Color newColor; - newColor.SetRed(qcolor.red() / 255.0); - newColor.SetGreen(qcolor.green() / 255.0); - newColor.SetBlue(qcolor.blue() / 255.0); - - GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->SetColor(newColor); - - GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); - } -} - -void QmitkLabelSetWidget::OnRandomColor(bool /*value*/) -{ - int pixelValue = GetPixelValueOfSelectedItem(); - GetWorkingImage() - ->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer()) - ->SetColor(m_ColorSequenceRainbow.GetNextColor()); - GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); - UpdateAllTableWidgetItems(); -} - -void QmitkLabelSetWidget::OnActiveLabelChanged(int pixelValue) -{ - mitk::LabelSetImage *workingImage = GetWorkingImage(); - assert(workingImage); - workingImage->GetActiveLabelSet()->SetActiveLabel(pixelValue); - // MITK_INFO << "Active Label set to << " << pixelValue; - - workingImage->Modified(); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkLabelSetWidget::OnItemClicked(QTableWidgetItem *item) -{ - if (!item) - return; - - int pixelValue = item->data(Qt::UserRole).toInt(); - - QList ranges = m_Controls.m_LabelSetTableWidget->selectedRanges(); - if (!ranges.empty() && ranges.back().rowCount() == 1) - { - m_ProcessingManualSelection = true; - OnActiveLabelChanged(pixelValue); - m_ProcessingManualSelection = false; - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - } -} - -void QmitkLabelSetWidget::OnItemDoubleClicked(QTableWidgetItem *item) -{ - if (!item) - return; - - if (QApplication::queryKeyboardModifiers().testFlag(Qt::AltModifier)) - { - this->OnRenameLabelShortcutActivated(); - return; - } - - int pixelValue = item->data(Qt::UserRole).toInt(); - // OnItemClicked(item); <<-- Double click first call OnItemClicked - WaitCursorOn(); - mitk::LabelSetImage *workingImage = GetWorkingImage(); - workingImage->UpdateCenterOfMass(pixelValue, workingImage->GetActiveLayer()); - const mitk::Point3D &pos = - workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetCenterOfMassCoordinates(); - WaitCursorOff(); - if (pos.GetVnlVector().max_value() > 0.0) - { - emit goToLabel(pos); - } - - workingImage->Modified(); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkLabelSetWidget::SelectLabelByPixelValue(mitk::Label::PixelType pixelValue) -{ - if (m_ProcessingManualSelection || !GetWorkingImage()->ExistLabel(pixelValue)) - return; - - for (int row = 0; row < m_Controls.m_LabelSetTableWidget->rowCount(); row++) - { - if (m_Controls.m_LabelSetTableWidget->item(row, 0)->data(Qt::UserRole).toInt() == pixelValue) - { - m_Controls.m_LabelSetTableWidget->clearSelection(); - m_Controls.m_LabelSetTableWidget->selectRow(row); - m_Controls.m_LabelSetTableWidget->scrollToItem(m_Controls.m_LabelSetTableWidget->item(row, 0)); - return; - } - } -} - -void QmitkLabelSetWidget::InsertTableWidgetItem(mitk::Label *label) -{ - const mitk::Color &color = label->GetColor(); - - QString styleSheet = "background-color:rgb("; - styleSheet.append(QString::number(color[0] * 255)); - styleSheet.append(","); - styleSheet.append(QString::number(color[1] * 255)); - styleSheet.append(","); - styleSheet.append(QString::number(color[2] * 255)); - styleSheet.append("); border: 0;"); - - QTableWidget *tableWidget = m_Controls.m_LabelSetTableWidget; - int colWidth = (tableWidget->columnWidth(NAME_COL) < 180) ? 180 : tableWidget->columnWidth(NAME_COL) - 2; - QString text = fontMetrics().elidedText(label->GetName().c_str(), Qt::ElideMiddle, colWidth); - QTableWidgetItem *nameItem = new QTableWidgetItem(text); - nameItem->setTextAlignment(Qt::AlignCenter | Qt::AlignLeft); - // ---!--- - // IMPORTANT: ADD PIXELVALUE TO TABLEWIDGETITEM.DATA - nameItem->setData(Qt::UserRole, QVariant(label->GetValue())); - // ---!--- - - QPushButton *pbColor = new QPushButton(tableWidget); - pbColor->setFixedSize(24, 24); - pbColor->setCheckable(false); - pbColor->setAutoFillBackground(true); - pbColor->setToolTip("Change label color"); - pbColor->setStyleSheet(styleSheet); - - connect(pbColor, SIGNAL(clicked()), this, SLOT(OnColorButtonClicked())); - - QString transparentStyleSheet = QLatin1String("background-color: transparent; border: 0;"); - - QPushButton *pbLocked = new QPushButton(tableWidget); - pbLocked->setFixedSize(24, 24); - QIcon *iconLocked = new QIcon(); - auto lockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/lock.svg")); - auto unlockIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/unlock.svg")); - iconLocked->addPixmap(lockIcon.pixmap(64), QIcon::Normal, QIcon::Off); - iconLocked->addPixmap(unlockIcon.pixmap(64), QIcon::Normal, QIcon::On); - pbLocked->setIcon(*iconLocked); - pbLocked->setIconSize(QSize(24, 24)); - pbLocked->setCheckable(true); - pbLocked->setToolTip("Lock/unlock label"); - pbLocked->setChecked(!label->GetLocked()); - pbLocked->setStyleSheet(transparentStyleSheet); - - connect(pbLocked, SIGNAL(clicked()), this, SLOT(OnLockedButtonClicked())); - - QPushButton *pbVisible = new QPushButton(tableWidget); - pbVisible->setFixedSize(24, 24); - pbVisible->setAutoRepeat(false); - QIcon *iconVisible = new QIcon(); - auto visibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/visible.svg")); - auto invisibleIcon = QmitkStyleManager::ThemeIcon(QLatin1String(":/Qmitk/invisible.svg")); - iconVisible->addPixmap(visibleIcon.pixmap(64), QIcon::Normal, QIcon::Off); - iconVisible->addPixmap(invisibleIcon.pixmap(64), QIcon::Normal, QIcon::On); - pbVisible->setIcon(*iconVisible); - pbVisible->setIconSize(QSize(24, 24)); - pbVisible->setCheckable(true); - pbVisible->setToolTip("Show/hide label"); - pbVisible->setChecked(!label->GetVisible()); - pbVisible->setStyleSheet(transparentStyleSheet); - - connect(pbVisible, SIGNAL(clicked()), this, SLOT(OnVisibleButtonClicked())); - - int row = tableWidget->rowCount(); - tableWidget->insertRow(row); - tableWidget->setRowHeight(row, 24); - tableWidget->setItem(row, 0, nameItem); - tableWidget->setCellWidget(row, 1, pbLocked); - tableWidget->setCellWidget(row, 2, pbColor); - tableWidget->setCellWidget(row, 3, pbVisible); - tableWidget->selectRow(row); - - // m_LabelSetImage->SetActiveLabel(label->GetPixelValue()); - // m_ToolManager->WorkingDataModified.Send(); - // emit activeLabelChanged(label->GetPixelValue()); - - if (row == 0) - { - tableWidget->hideRow(row); // hide exterior label - } -} - -void QmitkLabelSetWidget::UpdateAllTableWidgetItems(mitk::Label::PixelType /*lv*/) -{ - this->UpdateAllTableWidgetItems(); -} - -void QmitkLabelSetWidget::UpdateAllTableWidgetItems() -{ - mitk::LabelSetImage *workingImage = GetWorkingImage(); - if (!workingImage) - return; - - // add all labels - QTableWidget *tableWidget = m_Controls.m_LabelSetTableWidget; - m_LabelStringList.clear(); - for (int i = 0; i < tableWidget->rowCount(); ++i) - { - UpdateTableWidgetItem(tableWidget->item(i, 0)); - m_LabelStringList.append(tableWidget->item(i, 0)->text()); - } - - OnLabelListModified(m_LabelStringList); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkLabelSetWidget::UpdateTableWidgetItem(QTableWidgetItem *item) -{ - mitk::LabelSetImage *workingImage = GetWorkingImage(); - mitk::Label *label = workingImage->GetLabel(item->data(Qt::UserRole).toInt(), workingImage->GetActiveLayer()); - - const mitk::Color &color = label->GetColor(); - - QString styleSheet = "background-color:rgb("; - styleSheet.append(QString::number(color[0] * 255)); - styleSheet.append(","); - styleSheet.append(QString::number(color[1] * 255)); - styleSheet.append(","); - styleSheet.append(QString::number(color[2] * 255)); - styleSheet.append("); border: 0;"); - - QTableWidget *tableWidget = m_Controls.m_LabelSetTableWidget; - int colWidth = (tableWidget->columnWidth(NAME_COL) < 180) ? 180 : tableWidget->columnWidth(NAME_COL) - 2; - QString text = fontMetrics().elidedText(label->GetName().c_str(), Qt::ElideMiddle, colWidth); - item->setText(text); - - QPushButton *pbLocked = dynamic_cast(tableWidget->cellWidget(item->row(), 1)); - pbLocked->setChecked(!label->GetLocked()); - - QPushButton *pbColor = dynamic_cast(tableWidget->cellWidget(item->row(), 2)); - pbColor->setStyleSheet(styleSheet); - - QPushButton *pbVisible = dynamic_cast(tableWidget->cellWidget(item->row(), 3)); - pbVisible->setChecked(!label->GetVisible()); - - if (item->row() == 0) - { - tableWidget->hideRow(item->row()); // hide exterior label - } -} - -void QmitkLabelSetWidget::ResetAllTableWidgetItems(mitk::Label::PixelType /*lv*/) -{ - this->ResetAllTableWidgetItems(); -} - -void QmitkLabelSetWidget::ResetAllTableWidgetItems() -{ - QTableWidget *tableWidget = m_Controls.m_LabelSetTableWidget; - // remove all rows - while (tableWidget->rowCount()) - { - tableWidget->removeRow(0); - } - - mitk::DataNode * workingNode = GetWorkingNode(); - auto workingImage = dynamic_cast(workingNode->GetData()); - if (nullptr == workingImage) - { - return; - } - - // add all labels - m_LabelStringList.clear(); - - mitk::LabelSet::LabelContainerConstIteratorType it = workingImage->GetActiveLabelSet()->IteratorConstBegin(); - mitk::LabelSet::LabelContainerConstIteratorType end = workingImage->GetActiveLabelSet()->IteratorConstEnd(); - - int pixelValue = -1; - while (it != end) - { - InsertTableWidgetItem(it->second); - if (workingImage->GetActiveLabel(workingImage->GetActiveLayer()) == it->second) // get active - pixelValue = it->first; - m_LabelStringList.append(QString(it->second->GetName().c_str())); - it++; - } - - SelectLabelByPixelValue(pixelValue); - - OnLabelListModified(m_LabelStringList); - - std::stringstream captionText; - captionText << "Number of labels: " << workingImage->GetNumberOfLabels(workingImage->GetActiveLayer()) - 1; - m_Controls.m_lblCaption->setText(captionText.str().c_str()); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - emit LabelSetWidgetReset(); -} - -int QmitkLabelSetWidget::GetPixelValueOfSelectedItem() -{ - if (m_Controls.m_LabelSetTableWidget->currentItem()) - { - return m_Controls.m_LabelSetTableWidget->currentItem()->data(Qt::UserRole).toInt(); - } - return -1; -} - -QStringList &QmitkLabelSetWidget::GetLabelStringList() -{ - return m_LabelStringList; -} - -void QmitkLabelSetWidget::InitializeTableWidget() -{ - auto* tableWidget = m_Controls.m_LabelSetTableWidget; - - tableWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); - tableWidget->setTabKeyNavigation(false); - tableWidget->setAlternatingRowColors(false); - tableWidget->setFocusPolicy(Qt::NoFocus); - tableWidget->setColumnCount(4); - tableWidget->resizeColumnToContents(NAME_COL); - tableWidget->setColumnWidth(LOCKED_COL, 25); - tableWidget->setColumnWidth(COLOR_COL, 25); - tableWidget->setColumnWidth(VISIBLE_COL, 25); - tableWidget->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Stretch); - tableWidget->setContextMenuPolicy(Qt::CustomContextMenu); - tableWidget->horizontalHeader()->hide(); - tableWidget->setSortingEnabled(false); - tableWidget->verticalHeader()->hide(); - tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers); - - using Self = QmitkLabelSetWidget; - - connect(tableWidget, &QTableWidget::itemClicked, this, &Self::OnItemClicked); - connect(tableWidget, &QTableWidget::itemDoubleClicked, this, &Self::OnItemDoubleClicked); - connect(tableWidget, &QTableWidget::customContextMenuRequested, this, &Self::OnTableViewContextMenuRequested); - - auto* model = tableWidget->model(); - - connect(model, &QAbstractItemModel::rowsInserted, this, &Self::OnRowsInserted); - connect(model, &QAbstractItemModel::rowsRemoved, this, &Self::OnRowsRemoved); -} - -void QmitkLabelSetWidget::OnRowsInserted(const QModelIndex&, int, int) -{ - auto* tableWidget = m_Controls.m_LabelSetTableWidget; - - if (tableWidget->rowCount() > 4) - { - tableWidget->setMinimumHeight(160); - tableWidget->setMaximumHeight(tableWidget->minimumHeight()); - } -} - -void QmitkLabelSetWidget::OnRowsRemoved(const QModelIndex&, int, int) -{ - auto* tableWidget = m_Controls.m_LabelSetTableWidget; - - if (tableWidget->rowCount() <= 4) - { - tableWidget->setMinimumHeight(80); - tableWidget->setMaximumHeight(tableWidget->minimumHeight()); - } -} - -void QmitkLabelSetWidget::OnOpacityChanged(int value) -{ - int pixelValue = GetPixelValueOfSelectedItem(); - float opacity = static_cast(value) / 100.0f; - GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->SetOpacity(opacity); - GetWorkingImage()->GetActiveLabelSet()->UpdateLookupTable(pixelValue); -} - -void QmitkLabelSetWidget::setEnabled(bool enabled) -{ - QWidget::setEnabled(enabled); - UpdateControls(); -} - -void QmitkLabelSetWidget::SetDataStorage(mitk::DataStorage *storage) -{ - m_DataStorage = storage; -} - -void QmitkLabelSetWidget::OnSearchLabel() -{ - std::string text = m_Controls.m_LabelSearchBox->text().toStdString(); - int pixelValue = -1; - int row = -1; - for (int i = 0; i < m_Controls.m_LabelSetTableWidget->rowCount(); ++i) - { - if (m_Controls.m_LabelSetTableWidget->item(i, 0)->text().toStdString().compare(text) == 0) - { - pixelValue = m_Controls.m_LabelSetTableWidget->item(i, 0)->data(Qt::UserRole).toInt(); - row = i; - break; - } - } - if (pixelValue == -1) - { - return; - } - - GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); - - QTableWidgetItem *nameItem = m_Controls.m_LabelSetTableWidget->item(row, NAME_COL); - if (!nameItem) - { - return; - } - - m_Controls.m_LabelSetTableWidget->clearSelection(); - m_Controls.m_LabelSetTableWidget->selectRow(row); - m_Controls.m_LabelSetTableWidget->scrollToItem(nameItem); - - GetWorkingImage()->GetActiveLabelSet()->SetActiveLabel(pixelValue); - - this->WaitCursorOn(); - mitk::Point3D pos = - GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); - - m_ToolManager->WorkingDataChanged(); - - if (pos.GetVnlVector().max_value() > 0.0) - { - emit goToLabel(pos); - } - else - { - GetWorkingImage()->UpdateCenterOfMass(pixelValue, GetWorkingImage()->GetActiveLayer()); - mitk::Point3D pos = - GetWorkingImage()->GetLabel(pixelValue, GetWorkingImage()->GetActiveLayer())->GetCenterOfMassCoordinates(); - emit goToLabel(pos); - } - - this->WaitCursorOff(); -} - -void QmitkLabelSetWidget::OnLabelListModified(const QStringList &list) -{ - QStringListModel *completeModel = static_cast(m_Completer->model()); - completeModel->setStringList(list); -} - -mitk::LabelSetImage *QmitkLabelSetWidget::GetWorkingImage() -{ - mitk::DataNode *workingNode = GetWorkingNode(); - mitk::LabelSetImage *workingImage = dynamic_cast(workingNode->GetData()); - assert(workingImage); - return workingImage; -} - -mitk::DataNode *QmitkLabelSetWidget::GetWorkingNode() -{ - mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); - assert(workingNode); - return workingNode; -} - -void QmitkLabelSetWidget::UpdateControls() -{ - mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); - bool hasWorkingData = (workingNode != nullptr); - - m_Controls.m_LabelSetTableWidget->setEnabled(hasWorkingData); - m_Controls.m_LabelSearchBox->setEnabled(hasWorkingData); - - if (!hasWorkingData) - return; - - QStringListModel *completeModel = static_cast(m_Completer->model()); - completeModel->setStringList(GetLabelStringList()); -} - -void QmitkLabelSetWidget::OnCreateCroppedMask(bool) -{ - m_ToolManager->ActivateTool(-1); - - mitk::LabelSetImage *workingImage = GetWorkingImage(); - mitk::Image::Pointer maskImage; - int pixelValue = GetPixelValueOfSelectedItem(); - try - { - this->WaitCursorOn(); - - mitk::AutoCropImageFilter::Pointer cropFilter = mitk::AutoCropImageFilter::New(); - cropFilter->SetInput(workingImage->CreateLabelMask(pixelValue)); - cropFilter->SetBackgroundValue(0); - cropFilter->SetMarginFactor(1.15); - cropFilter->Update(); - - maskImage = cropFilter->GetOutput(); - - this->WaitCursorOff(); - } - catch (mitk::Exception &e) - { - this->WaitCursorOff(); - MITK_ERROR << "Exception caught: " << e.GetDescription(); - QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); - return; - } - - if (maskImage.IsNull()) - { - QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); - return; - } - - mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); - std::string name = workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetName(); - name += "-mask"; - maskNode->SetName(name); - maskNode->SetData(maskImage); - maskNode->SetBoolProperty("binary", true); - maskNode->SetBoolProperty("outline binary", true); - maskNode->SetBoolProperty("outline binary shadow", true); - maskNode->SetFloatProperty("outline width", 2.0); - maskNode->SetColor(workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetColor()); - maskNode->SetOpacity(1.0); - - m_DataStorage->Add(maskNode, GetWorkingNode()); -} - -void QmitkLabelSetWidget::OnCreateMask(bool /*triggered*/) -{ - m_ToolManager->ActivateTool(-1); - - mitk::LabelSetImage *workingImage = GetWorkingImage(); - mitk::Image::Pointer maskImage; - int pixelValue = GetPixelValueOfSelectedItem(); - try - { - this->WaitCursorOn(); - maskImage = workingImage->CreateLabelMask(pixelValue); - this->WaitCursorOff(); - } - catch (mitk::Exception &e) - { - this->WaitCursorOff(); - MITK_ERROR << "Exception caught: " << e.GetDescription(); - QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); - return; - } - - if (maskImage.IsNull()) - { - QMessageBox::information(this, "Create Mask", "Could not create a mask out of the selected label.\n"); - return; - } - - mitk::DataNode::Pointer maskNode = mitk::DataNode::New(); - std::string name = workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetName(); - name += "-mask"; - maskNode->SetName(name); - maskNode->SetData(maskImage); - maskNode->SetBoolProperty("binary", true); - maskNode->SetBoolProperty("outline binary", true); - maskNode->SetBoolProperty("outline binary shadow", true); - maskNode->SetFloatProperty("outline width", 2.0); - maskNode->SetColor(workingImage->GetLabel(pixelValue, workingImage->GetActiveLayer())->GetColor()); - maskNode->SetOpacity(1.0); - - m_DataStorage->Add(maskNode, GetWorkingNode()); -} - -void QmitkLabelSetWidget::OnToggleOutline(bool value) -{ - mitk::DataNode *workingNode = m_ToolManager->GetWorkingData(0); - assert(workingNode); - - workingNode->SetBoolProperty("labelset.contour.active", value); - workingNode->GetData()->Modified(); // fixme: workaround to force data-type rendering (and not only property-type) - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void QmitkLabelSetWidget::OnCreateSmoothedSurface(bool /*triggered*/) -{ - m_ToolManager->ActivateTool(-1); - - mitk::DataNode::Pointer workingNode = GetWorkingNode(); - mitk::LabelSetImage *workingImage = GetWorkingImage(); - int pixelValue = GetPixelValueOfSelectedItem(); - - mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); - - itk::SimpleMemberCommand::Pointer successCommand = - itk::SimpleMemberCommand::New(); - successCommand->SetCallbackFunction(this, &QmitkLabelSetWidget::OnThreadedCalculationDone); - surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); - - itk::SimpleMemberCommand::Pointer errorCommand = - itk::SimpleMemberCommand::New(); - errorCommand->SetCallbackFunction(this, &QmitkLabelSetWidget::OnThreadedCalculationDone); - surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); - - mitk::DataNode::Pointer groupNode = workingNode; - surfaceFilter->SetPointerParameter("Group node", groupNode); - surfaceFilter->SetPointerParameter("Input", workingImage); - surfaceFilter->SetParameter("RequestedLabel", pixelValue); - surfaceFilter->SetParameter("Smooth", true); - surfaceFilter->SetDataStorage(*m_DataStorage); - - mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); - - try - { - surfaceFilter->StartAlgorithm(); - } - catch (mitk::Exception &e) - { - MITK_ERROR << "Exception caught: " << e.GetDescription(); - QMessageBox::information(this, - "Create Surface", - "Could not create a surface mesh out of the selected label. See error log for details.\n"); - } -} - -void QmitkLabelSetWidget::OnCreateDetailedSurface(bool /*triggered*/) -{ - m_ToolManager->ActivateTool(-1); - - mitk::DataNode::Pointer workingNode = GetWorkingNode(); - mitk::LabelSetImage *workingImage = GetWorkingImage(); - int pixelValue = GetPixelValueOfSelectedItem(); - - mitk::LabelSetImageToSurfaceThreadedFilter::Pointer surfaceFilter = mitk::LabelSetImageToSurfaceThreadedFilter::New(); - - itk::SimpleMemberCommand::Pointer successCommand = - itk::SimpleMemberCommand::New(); - successCommand->SetCallbackFunction(this, &QmitkLabelSetWidget::OnThreadedCalculationDone); - surfaceFilter->AddObserver(mitk::ResultAvailable(), successCommand); - - itk::SimpleMemberCommand::Pointer errorCommand = - itk::SimpleMemberCommand::New(); - errorCommand->SetCallbackFunction(this, &QmitkLabelSetWidget::OnThreadedCalculationDone); - surfaceFilter->AddObserver(mitk::ProcessingError(), errorCommand); - - mitk::DataNode::Pointer groupNode = workingNode; - surfaceFilter->SetPointerParameter("Group node", groupNode); - surfaceFilter->SetPointerParameter("Input", workingImage); - surfaceFilter->SetParameter("RequestedLabel", pixelValue); - surfaceFilter->SetParameter("Smooth", false); - surfaceFilter->SetDataStorage(*m_DataStorage); - - mitk::StatusBar::GetInstance()->DisplayText("Surface creation is running in background..."); - - try - { - surfaceFilter->StartAlgorithm(); - } - catch (mitk::Exception &e) - { - MITK_ERROR << "Exception caught: " << e.GetDescription(); - QMessageBox::information(this, - "Create Surface", - "Could not create a surface mesh out of the selected label. See error log for details.\n"); - } -} - -void QmitkLabelSetWidget::WaitCursorOn() -{ - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); -} - -void QmitkLabelSetWidget::WaitCursorOff() -{ - this->RestoreOverrideCursor(); -} - -void QmitkLabelSetWidget::RestoreOverrideCursor() -{ - QApplication::restoreOverrideCursor(); -} - -void QmitkLabelSetWidget::OnThreadedCalculationDone() -{ - mitk::StatusBar::GetInstance()->Clear(); -} diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.h b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.h deleted file mode 100644 index bf6dacb06b..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidget.h +++ /dev/null @@ -1,171 +0,0 @@ -/*============================================================================ - -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 QmitkLabelSetWidget_h -#define QmitkLabelSetWidget_h - -#include - -#include -#include -#include -#include - -class QmitkDataStorageComboBox; -class QCompleter; - -namespace mitk -{ - class LabelSetImage; - class LabelSet; - class Label; - class DataStorage; - class ToolManager; - class DataNode; -} - -class MITK_QT_SEGMENTATION QmitkLabelSetWidget : public QWidget -{ - Q_OBJECT - -public: - explicit QmitkLabelSetWidget(QWidget *parent = nullptr); - ~QmitkLabelSetWidget() override; - - void SetDataStorage(mitk::DataStorage *storage); - - void UpdateControls(); - - virtual void setEnabled(bool enabled); - - QStringList &GetLabelStringList(); - -signals: - - /// \brief Send a signal when it was requested to go to a label. - void goToLabel(const mitk::Point3D &); - void LabelSetWidgetReset(); - -public slots: - - /** - * @brief Updates the current labels in the label set widget table. For each label (widget item) the 'UpdateTableWidgetItem' is called. - * - * Updating means setting the color box of the table, setting the column with and fill it with the label name. - * Furthermore the two push buttons for locking and showing/hiding the layer are checked/unchecked. - * This functions only changes the appearance of the table widget and no render window update is necessary. - */ - void UpdateAllTableWidgetItems(); - void UpdateAllTableWidgetItems(mitk::Label::PixelType); - /** - * @brief Resets the current labels in the label set widget table. For each label a widget item is inserted into the table. - * - * Resetting means removing all rows of the widget table and inserting new rows (labels) from the active label set (= layer) of the current working node. - * The currently active label is selected and 'Number of labels' is set. - * As this function is typically used after one label has been removed or the reference node has been changed (e.g.) the render windows have to be updated. - */ - void ResetAllTableWidgetItems(); - void ResetAllTableWidgetItems(mitk::Label::PixelType); - - void SelectLabelByPixelValue(mitk::Label::PixelType pixelValue); - -private slots: - - // LabelSet dependent - void OnOpacityChanged(int); - void OnUnlockAllLabels(bool); - void OnLockAllLabels(bool); - void OnSetAllLabelsVisible(bool); - void OnSetAllLabelsInvisible(bool); - void OnSetOnlyActiveLabelVisible(bool); - void OnRandomColor(bool); - void OnRemoveLabel(bool); - void OnRemoveLabels(bool); - void OnRenameLabel(bool); - void OnRenameLabelShortcutActivated(); - void OnLockedButtonClicked(); - void OnVisibleButtonClicked(); - void OnColorButtonClicked(); - void OnItemClicked(QTableWidgetItem *item); - void OnItemDoubleClicked(QTableWidgetItem *item); - void OnTableViewContextMenuRequested(const QPoint &); - void InsertTableWidgetItem(mitk::Label *label); - void UpdateTableWidgetItem(QTableWidgetItem *item); - // reaction to "returnPressed" signal from ... - void OnSearchLabel(); - // reaction to the button "Change Label" - void OnActiveLabelChanged(int pixelValue); - - // LabelSetImage Dependent - void OnCreateDetailedSurface(bool); - void OnCreateSmoothedSurface(bool); - // reaction to the signal "createMask" from QmitkLabelSetTableWidget - void OnCreateMask(bool); - void OnCreateMasks(bool); - // reaction to the signal "createCroppedMask" from QmitkLabelSetTableWidget - void OnCreateCroppedMask(bool); - void OnCombineAndCreateMask(bool); - void OnCombineAndCreateSurface(bool); - void OnEraseLabel(bool); - void OnEraseLabels(bool); - void OnMergeLabels(bool); - - // reaction to signal "labelListModified" from QmitkLabelSetTableWidget - void OnLabelListModified(const QStringList &list); - // reaction to the signal "toggleOutline" from QmitkLabelSetTableWidget - void OnToggleOutline(bool); - void OnRowsInserted(const QModelIndex&, int, int); - void OnRowsRemoved(const QModelIndex&, int, int); - -private: - enum TableColumns - { - NAME_COL = 0, - LOCKED_COL, - COLOR_COL, - VISIBLE_COL - }; - - void WaitCursorOn(); - - void WaitCursorOff(); - - void RestoreOverrideCursor(); - - void OnThreadedCalculationDone(); - - void InitializeTableWidget(); - - int GetPixelValueOfSelectedItem(); - - mitk::LabelSetImage *GetWorkingImage(); - - mitk::DataNode *GetWorkingNode(); - - Ui::QmitkLabelSetWidgetControls m_Controls; - - mitk::ColorSequenceRainbow m_ColorSequenceRainbow; - - mitk::DataStorage *m_DataStorage; - - QCompleter *m_Completer; - - mitk::ToolManager *m_ToolManager; - - QStringList m_OrganColors; - - QStringList m_LabelStringList; - - bool m_ProcessingManualSelection; -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidgetControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidgetControls.ui deleted file mode 100644 index 9549002939..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkLabelSetWidgetControls.ui +++ /dev/null @@ -1,94 +0,0 @@ - - - QmitkLabelSetWidgetControls - - - - 0 - 0 - 312 - 137 - - - - - 0 - 0 - - - - - - - - 6 - - - 6 - - - 6 - - - 6 - - - - - - 0 - 0 - - - - Caption - - - - - - - - 0 - 0 - - - - - 0 - 80 - - - - - 16777215 - 80 - - - - QAbstractItemView::SelectRows - - - - - - - - 0 - 0 - - - - - - - - - ctkSearchBox - QLineEdit -
ctkSearchBox.h
-
-
- - -
diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp index b93bb00172..ce1e845b63 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/QmitkNewSegmentationDialog.cpp @@ -1,406 +1,398 @@ /*============================================================================ 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 "QmitkNewSegmentationDialog.h" #include #include #include #include #include #include #include #include #include #include #include #include namespace { // Get standard label name and color suggestions from embedded XML preset file for anatomical structures. QmitkNewSegmentationDialog::SuggestionsType GetStandardSuggestions() { vtkNew presets; presets->LoadPreset(); auto colorPresets = presets->GetColorPresets(); QmitkNewSegmentationDialog::SuggestionsType standardSuggestions; standardSuggestions.reserve(colorPresets.size()); for (const auto& preset : colorPresets) { auto name = QString::fromStdString(preset.first); auto color = QColor::fromRgbF(preset.second.GetRed(), preset.second.GetGreen(), preset.second.GetBlue()); standardSuggestions.emplace_back(name, color); } std::sort(begin(standardSuggestions), end(standardSuggestions), [](const auto& lhs, const auto& rhs) { return lhs.first < rhs.first; }); return standardSuggestions; } // Parse label name and color suggestions from a JSON file. An array of objects is expected, each consisting // of a "name" string and an optional "color" string. If present, the "color" string must follow the conventions // of QColor::setNamedColor(), i.e., #rrggbb or any SVG color keyword name like CornflowerBlue. Everything else // in the JSON file is simply ignored. In case of any error, an empty map is returned. QmitkNewSegmentationDialog::SuggestionsType ParseSuggestions(const std::string& filename) { std::ifstream file(filename); if (!file.is_open()) { MITK_ERROR << "Could not open \"" << filename << "\"!"; return {}; } auto json = nlohmann::json::parse(file, nullptr, false); if (json.is_discarded() || !json.is_array()) { MITK_ERROR << "Could not parse \"" << filename << "\" as JSON array!"; return {}; } QmitkNewSegmentationDialog::SuggestionsType parsedSuggestions; for (const auto& obj : json) { if (!obj.is_object() || !obj.contains("name")) continue; auto name = QString::fromStdString(obj["name"]); QColor color; if (obj.contains("color")) color = QColor::fromString(QString::fromStdString(obj["color"])); auto it = std::find_if(begin(parsedSuggestions), end(parsedSuggestions), [&name](const auto& suggestion) { return name == suggestion.first; }); // Ignore duplicates... if (it != parsedSuggestions.end()) { // unless we can complete the original suggestion with a valid color. if (!it->second.isValid() && color.isValid()) { it->second = color; } else { MITK_WARN << "Ignoring duplicate of suggestion \"" << name.toStdString() << "\"!"; } continue; } parsedSuggestions.emplace_back(name, color); } if (parsedSuggestions.empty()) MITK_WARN << "Could not parse any suggestions from \"" << filename << "\"!"; return parsedSuggestions; } struct Preferences { std::string labelSuggestions; bool replaceStandardSuggestions; bool suggestOnce; std::vector geometry; }; // Get all relevant preferences and consider command-line arguments overrides. Preferences GetPreferences() { auto* nodePrefs = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); Preferences prefs; prefs.labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); if (prefs.labelSuggestions.empty()) prefs.labelSuggestions = nodePrefs->Get("label suggestions", ""); prefs.replaceStandardSuggestions = nodePrefs->GetBool("replace standard suggestions", true); prefs.suggestOnce = nodePrefs->GetBool("suggest once", true); prefs.geometry = nodePrefs->GetByteArray("QmitkNewSegmentationDialog geometry", nullptr, 0); return prefs; } void SaveGeometry(const QByteArray& geometry) { auto* nodePrefs = mitk::CoreServices::GetPreferencesService()->GetSystemPreferences()->Node("/org.mitk.views.segmentation"); nodePrefs->PutByteArray("QmitkNewSegmentationDialog geometry", reinterpret_cast(geometry.data()), geometry.size()); } // Get names of all labels in all layers of a LabelSetImage. QStringList GetExistingLabelNames(mitk::LabelSetImage* labelSetImage) { + auto names = labelSetImage->GetLabelClassNames(); QStringList existingLabelNames; - existingLabelNames.reserve(labelSetImage->GetTotalNumberOfLabels()); + existingLabelNames.reserve(names.size()); - const auto numLayers = labelSetImage->GetNumberOfLayers(); - for (std::remove_const_t layerIndex = 0; layerIndex < numLayers; ++layerIndex) + for (auto name : names) { - const auto* labelSet = labelSetImage->GetLabelSet(layerIndex); + auto qtName = QString::fromStdString(name); - for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) - { - auto name = QString::fromStdString(labelIter->second->GetName()); - - if (!name.isEmpty()) // Potential duplicates do not matter for our purpose - existingLabelNames.push_back(name); - } + if (!qtName.isEmpty()) // Potential duplicates do not matter for our purpose + existingLabelNames.push_back(qtName); } return existingLabelNames; } // Remove blacklisted suggestions. QmitkNewSegmentationDialog::SuggestionsType FilterSuggestions(const QmitkNewSegmentationDialog::SuggestionsType& suggestions, const QStringList& blacklist) { QmitkNewSegmentationDialog::SuggestionsType filteredSuggestions; std::remove_copy_if(begin(suggestions), end(suggestions), std::inserter(filteredSuggestions, end(filteredSuggestions)), [&blacklist](const auto& suggestion) { return blacklist.contains(suggestion.first); }); return filteredSuggestions; } } QmitkNewSegmentationDialog::QmitkNewSegmentationDialog(QWidget *parent, mitk::LabelSetImage* labelSetImage, Mode mode) : QDialog(parent), m_Ui(new Ui::QmitkNewSegmentationDialog), m_SuggestOnce(true), m_Color(Qt::red) { m_Ui->setupUi(this); if (RenameLabel == mode) { this->setWindowTitle("Rename Label"); m_Ui->label->setText("New name and color of the label"); m_Ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Rename label"); } else { m_Ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Create label"); } m_Ui->nameLineEdit->setFocus(); connect(this, &QDialog::finished, this, &QmitkNewSegmentationDialog::OnFinished); connect(m_Ui->colorButton, &QToolButton::clicked, this, &QmitkNewSegmentationDialog::OnColorButtonClicked); connect(m_Ui->nameLineEdit, &QLineEdit::textChanged, this, &QmitkNewSegmentationDialog::OnTextChanged); connect(m_Ui->nameList, &QListWidget::itemSelectionChanged, this, &QmitkNewSegmentationDialog::OnSuggestionSelected); connect(m_Ui->nameList, &QListWidget::itemDoubleClicked, this, &QmitkNewSegmentationDialog::OnAccept); connect(m_Ui->buttonBox, &QDialogButtonBox::accepted, this, &QmitkNewSegmentationDialog::OnAccept); this->UpdateColorButtonBackground(); auto prefs = GetPreferences(); if (!prefs.labelSuggestions.empty()) { auto suggestions = ParseSuggestions(prefs.labelSuggestions); this->SetSuggestions(suggestions, prefs.replaceStandardSuggestions && !suggestions.empty()); } else { this->SetSuggestions(GetStandardSuggestions(), true); } if (nullptr != labelSetImage && prefs.suggestOnce) { auto existingLabelNames = GetExistingLabelNames(labelSetImage); m_Suggestions = FilterSuggestions(m_Suggestions, existingLabelNames); this->UpdateNameList(); } if (!(prefs.geometry.empty())) this->restoreGeometry(QByteArray(reinterpret_cast(prefs.geometry.data()), prefs.geometry.size())); } QmitkNewSegmentationDialog::~QmitkNewSegmentationDialog() { } void QmitkNewSegmentationDialog::OnFinished(int) { SaveGeometry(this->saveGeometry()); } void QmitkNewSegmentationDialog::UpdateColorButtonBackground() { m_Ui->colorButton->setStyleSheet("background-color:" + m_Color.name()); } QString QmitkNewSegmentationDialog::GetName() const { return m_Name; } mitk::Color QmitkNewSegmentationDialog::GetColor() const { mitk::Color color; if (m_Color.isValid()) { color.SetRed(m_Color.redF()); color.SetGreen(m_Color.greenF()); color.SetBlue(m_Color.blueF()); } else { color.Set(1.0f, 0.0f, 0.0f); } return color; } void QmitkNewSegmentationDialog::SetName(const QString& name) { m_Ui->nameLineEdit->setText(name); } void QmitkNewSegmentationDialog::SetColor(const mitk::Color& color) { m_Color.setRgbF(color.GetRed(), color.GetGreen(), color.GetBlue()); this->UpdateColorButtonBackground(); } void QmitkNewSegmentationDialog::SetSuggestions(const SuggestionsType& suggestions, bool replaceStandardSuggestions) { m_Suggestions = suggestions; if (!replaceStandardSuggestions) { auto standardSuggestions = GetStandardSuggestions(); // Append all standard suggestions with unique names to the list of custom suggestions std::remove_copy_if(begin(standardSuggestions), end(standardSuggestions), std::inserter(m_Suggestions, end(m_Suggestions)), [this](const auto& suggestion) { return m_Suggestions.end() != std::find_if(begin(m_Suggestions), end(m_Suggestions), [&suggestion](const auto& otherSuggestion) { return suggestion.first == otherSuggestion.first; }); }); } this->UpdateNameList(); } void QmitkNewSegmentationDialog::UpdateNameList() { QStringList names; for (const auto& suggestion : m_Suggestions) names << suggestion.first; m_Ui->nameList->clear(); m_Ui->nameList->addItems(names); } void QmitkNewSegmentationDialog::OnAccept() { m_Name = m_Ui->nameLineEdit->text(); this->accept(); } void QmitkNewSegmentationDialog::OnTextChanged(const QString& text) { auto finding = m_Ui->nameList->findItems(text, Qt::MatchFlag::MatchExactly); if (!finding.isEmpty()) m_Ui->nameList->setCurrentItem(finding.first()); } void QmitkNewSegmentationDialog::OnColorButtonClicked() { auto color = QColorDialog::getColor(m_Color); if (color.isValid()) { m_Color = color; this->UpdateColorButtonBackground(); } } void QmitkNewSegmentationDialog::OnSuggestionSelected() { const auto* currentItem = m_Ui->nameList->currentItem(); if (currentItem == nullptr) return; auto row = m_Ui->nameList->selectionModel()->selectedIndexes().first().row(); m_Ui->nameLineEdit->setText(m_Suggestions[row].first); auto color = m_Suggestions[row].second; if (color.isValid()) { m_Color = color; this->UpdateColorButtonBackground(); } } bool QmitkNewSegmentationDialog::DoRenameLabel(mitk::Label* label, mitk::LabelSetImage* segmentation, QWidget* parent, Mode mode) { if (nullptr == label) mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed label is null."; const auto labelValue = label->GetValue(); - mitk::LabelSetImage::GroupIndexType groupIndex; - if (nullptr != segmentation && !segmentation->IsLabelInGroup(labelValue, groupIndex)) + if (nullptr != segmentation && !segmentation->ExistLabel(labelValue)) mitkThrow() << "Invalid call of QmitkNewSegmentationDialog::RenameLabel. Passed label value does not exist in segmentation."; QmitkNewSegmentationDialog dialog(parent, segmentation, mode); dialog.SetColor(label->GetColor()); dialog.SetName(QString::fromStdString(label->GetName())); if (dialog.exec() == QDialog::Rejected) return false; auto name = dialog.GetName(); if (name.isEmpty()) name = "Unnamed"; if (nullptr != segmentation) { - auto group = segmentation->GetLabelSet(groupIndex); - group->RenameLabel(labelValue, name.toStdString(), dialog.GetColor()); - group->UpdateLookupTable(labelValue); + segmentation->RenameLabel(labelValue, name.toStdString(), dialog.GetColor()); } else { label->SetName(name.toStdString()); label->SetColor(dialog.GetColor()); } return true; } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.cpp deleted file mode 100644 index 656bd0c271..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.cpp +++ /dev/null @@ -1,205 +0,0 @@ -/*============================================================================ - -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 "QmitkLabelsWidget.h" -#include - -// mitk -#include -#include -#include -#include -#include - -// Qmitk -#include -#include -#include - -#include "../QmitkSaveMultiLabelPresetAction.h" -#include "../QmitkLoadMultiLabelPresetAction.h" - -// Qt -#include -#include - -QmitkLabelsWidget::QmitkLabelsWidget(QWidget *parent) - : QWidget(parent) - , m_Controls(new Ui::QmitkLabelsWidgetControls) - , m_ToolManager(nullptr) - , m_DefaultLabelNaming(true) -{ - m_Controls->setupUi(this); - - m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); - - m_Controls->savePresetButton->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg"))); - m_Controls->loadPresetButton->setIcon(QmitkStyleManager::ThemeIcon(QStringLiteral(":/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg"))); - - connect(m_Controls->newLabelButton, &QToolButton::clicked, this, &QmitkLabelsWidget::OnNewLabel); - connect(m_Controls->lockExteriorButton, &QToolButton::toggled, this, &QmitkLabelsWidget::OnLockExterior); - connect(m_Controls->savePresetButton, &QToolButton::clicked, this, &QmitkLabelsWidget::OnSavePreset); - connect(m_Controls->loadPresetButton, &QToolButton::clicked, this, &QmitkLabelsWidget::OnLoadPreset); - connect(m_Controls->showLabelTableButton, &QToolButton::toggled, this, &QmitkLabelsWidget::ShowLabelTable); - - auto* newLabelShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_N), this); - connect(newLabelShortcut, &QShortcut::activated, this, &QmitkLabelsWidget::OnNewLabelShortcutActivated); - - this->UpdateGUI(); -} - -QmitkLabelsWidget::~QmitkLabelsWidget() -{ - delete m_Controls; -} - -void QmitkLabelsWidget::UpdateGUI() -{ - m_Controls->newLabelButton->setEnabled(false); - m_Controls->lockExteriorButton->setEnabled(false); - m_Controls->lockExteriorButton->setChecked(false); - m_Controls->savePresetButton->setEnabled(false); - m_Controls->loadPresetButton->setEnabled(false); - m_Controls->showLabelTableButton->setEnabled(false); - m_Controls->showLabelTableButton->setChecked(false); - - mitk::LabelSetImage* workingImage = this->GetWorkingImage(); - if (nullptr == workingImage) - { - return; - } - - int activeLayer = workingImage->GetActiveLayer(); - m_Controls->lockExteriorButton->setEnabled(true); - m_Controls->lockExteriorButton->setChecked(workingImage->GetLabel(0, activeLayer)->GetLocked()); - m_Controls->showLabelTableButton->setEnabled(true); - m_Controls->showLabelTableButton->setChecked(true); - m_Controls->newLabelButton->setEnabled(true); - m_Controls->savePresetButton->setEnabled(true); - m_Controls->loadPresetButton->setEnabled(true); -} - -void QmitkLabelsWidget::SetDefaultLabelNaming(bool defaultLabelNaming) -{ - m_DefaultLabelNaming = defaultLabelNaming; -} - -mitk::LabelSetImage* QmitkLabelsWidget::GetWorkingImage() -{ - mitk::DataNode* workingNode = this->GetWorkingNode(); - if (nullptr == workingNode) - { - return nullptr; - } - - auto workingImage = dynamic_cast(workingNode->GetData()); - return workingImage; -} - -mitk::DataNode* QmitkLabelsWidget::GetWorkingNode() -{ - mitk::DataNode* referenceNode = m_ToolManager->GetWorkingData(0); - return referenceNode; -} - -void QmitkLabelsWidget::OnNewLabel() -{ - m_ToolManager->ActivateTool(-1); - - mitk::DataNode* workingNode = this->GetWorkingNode(); - if (nullptr == workingNode) - { - return; - } - - auto workingImage = dynamic_cast(workingNode->GetData()); - if (nullptr == workingImage) - { - return; - } - - mitk::Label::Pointer newLabel = mitk::LabelSetImageHelper::CreateNewLabel(workingImage); - - if (!m_DefaultLabelNaming) - { - QmitkNewSegmentationDialog dialog(this, workingImage); - 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()); - } - - workingImage->GetActiveLabelSet()->AddLabel(newLabel); - - this->UpdateGUI(); - emit LabelsChanged(); -} - -void QmitkLabelsWidget::OnNewLabelShortcutActivated() -{ - m_Controls->newLabelButton->click(); -} - -void QmitkLabelsWidget::OnLockExterior(bool checked) -{ - auto workingImage = this->GetWorkingImage(); - if (nullptr == workingImage) - { - return; - } - - workingImage->GetLabel(0)->SetLocked(checked); -} - -void QmitkLabelsWidget::OnSavePreset() -{ - auto workingNode = this->GetWorkingNode(); - QmitkAbstractNodeSelectionWidget::NodeList nodes; - nodes.append(workingNode); - - QmitkSaveMultiLabelPresetAction action; - action.Run(nodes); -} - -void QmitkLabelsWidget::OnLoadPreset() -{ - auto workingNode = this->GetWorkingNode(); - QmitkAbstractNodeSelectionWidget::NodeList nodes; - nodes.append(workingNode); - - QmitkLoadMultiLabelPresetAction action; - action.Run(nodes); -} - -void QmitkLabelsWidget::WaitCursorOn() -{ - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); -} - -void QmitkLabelsWidget::WaitCursorOff() -{ - this->RestoreOverrideCursor(); -} - -void QmitkLabelsWidget::RestoreOverrideCursor() -{ - QApplication::restoreOverrideCursor(); -} - diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.h deleted file mode 100644 index e1ae7cf042..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidget.h +++ /dev/null @@ -1,87 +0,0 @@ -/*============================================================================ - -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 QmitkLabelsWidget_h -#define QmitkLabelsWidget_h - -// mitk core -#include - -// Qt -#include - -namespace Ui -{ - class QmitkLabelsWidgetControls; -} - -namespace mitk -{ - class DataNode; - class Image; - class LabelSetImage; - class ToolManager; -} - -class QmitkLabelsWidget : public QWidget -{ - Q_OBJECT - -public: - - explicit QmitkLabelsWidget(QWidget* parent = nullptr); - ~QmitkLabelsWidget() override; - - void UpdateGUI(); - - void SetDefaultLabelNaming(bool defaultLabelNaming); - -Q_SIGNALS: - - void LabelsChanged(); - - void ShowLabelTable(bool); - -private: - - mitk::LabelSetImage* GetWorkingImage(); - - mitk::DataNode* GetWorkingNode(); - - // reaction to button "New Label" - void OnNewLabel(); - void OnNewLabelShortcutActivated(); - - // reaction to the button "Lock exterior" - void OnLockExterior(bool); - - // reaction to button "Save Preset" - void OnSavePreset(); - - // reaction to button "Load Preset" - void OnLoadPreset(); - - void WaitCursorOn(); - - void WaitCursorOff(); - - void RestoreOverrideCursor(); - - Ui::QmitkLabelsWidgetControls* m_Controls; - - mitk::ToolManager* m_ToolManager; - - bool m_DefaultLabelNaming; - -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidgetControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidgetControls.ui deleted file mode 100644 index 35fb68f86b..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLabelsWidgetControls.ui +++ /dev/null @@ -1,188 +0,0 @@ - - - QmitkLabelsWidgetControls - - - - 0 - 0 - 300 - 75 - - - - - 0 - 0 - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Labels - - - - - - Add a label to the current segmentation session - - - ... - - - - :/Qmitk/NewLabel_48x48.png:/Qmitk/NewLabel_48x48.png - - - - 28 - 28 - - - - true - - - - - - - Lock / unlock exterior - - - ... - - - - :/Qmitk/UnlockExterior_48x48.png - :/Qmitk/LockExterior_48x48.png:/Qmitk/UnlockExterior_48x48.png - - - - 28 - 28 - - - - true - - - true - - - - - - - Save labelset preset - - - ... - - - - :/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg:/org_mitk_icons/icons/awesome/scalable/actions/document-save.svg - - - - 28 - 28 - - - - true - - - - - - - Load LabelSet Preset - - - ... - - - - :/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg:/org_mitk_icons/icons/awesome/scalable/actions/document-open.svg - - - - 28 - 28 - - - - true - - - - - - - Qt::Horizontal - - - - 0 - 20 - - - - - - - - - 0 - 34 - - - - Show a table with all labels in the current segmentation session - - - >> - - - - 28 - 28 - - - - true - - - false - - - Qt::NoArrow - - - - - - - - - - - diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLayersWidget.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLayersWidget.cpp deleted file mode 100644 index fc0d43bdf8..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLayersWidget.cpp +++ /dev/null @@ -1,237 +0,0 @@ -/*============================================================================ - -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 "QmitkLayersWidget.h" -#include - -// mitk -#include -#include -#include - -// Qt -#include - -QmitkLayersWidget::QmitkLayersWidget(QWidget *parent) - : QWidget(parent) - , m_Controls(new Ui::QmitkLayersWidgetControls) - , m_ToolManager(nullptr) -{ - m_Controls->setupUi(this); - - m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); - - connect(m_Controls->addLayerButton, &QToolButton::clicked, this, &QmitkLayersWidget::OnAddLayer); - connect(m_Controls->deleteLayerButton, &QToolButton::clicked, this, &QmitkLayersWidget::OnDeleteLayer); - connect(m_Controls->previousLayerButton, &QToolButton::clicked, this, &QmitkLayersWidget::OnPreviousLayer); - connect(m_Controls->nextLayerButton, &QToolButton::clicked, this, &QmitkLayersWidget::OnNextLayer); - connect(m_Controls->activeLayerComboBox, QOverload::of(&QComboBox::currentIndexChanged), this, &QmitkLayersWidget::OnChangeLayer); - - this->UpdateGUI(); -} - -QmitkLayersWidget::~QmitkLayersWidget() -{ - delete m_Controls; -} - -void QmitkLayersWidget::UpdateGUI() -{ - m_Controls->addLayerButton->setEnabled(false); - m_Controls->deleteLayerButton->setEnabled(false); - m_Controls->previousLayerButton->setEnabled(false); - m_Controls->nextLayerButton->setEnabled(false); - m_Controls->activeLayerComboBox->setEnabled(false); - - mitk::LabelSetImage* workingImage = this->GetWorkingImage(); - if (nullptr == workingImage) - { - return; - } - - m_Controls->addLayerButton->setEnabled(true); - - m_Controls->activeLayerComboBox->blockSignals(true); - m_Controls->activeLayerComboBox->clear(); - - unsigned int numberOfLayers = workingImage->GetNumberOfLayers(); - for (unsigned int lidx = 0; lidx < numberOfLayers; ++lidx) - { - m_Controls->activeLayerComboBox->addItem(QString::number(lidx)); - } - - unsigned int activeLayer = workingImage->GetActiveLayer(); - m_Controls->activeLayerComboBox->setCurrentIndex(activeLayer); - m_Controls->activeLayerComboBox->blockSignals(false); - - m_Controls->deleteLayerButton->setEnabled(numberOfLayers > 1); - m_Controls->previousLayerButton->setEnabled(activeLayer > 0); - m_Controls->nextLayerButton->setEnabled(activeLayer != numberOfLayers - 1); - m_Controls->activeLayerComboBox->setEnabled(numberOfLayers > 0); -} - -mitk::LabelSetImage* QmitkLayersWidget::GetWorkingImage() -{ - mitk::DataNode* workingNode = GetWorkingNode(); - if (nullptr == workingNode) - { - return nullptr; - } - - auto workingImage = dynamic_cast(workingNode->GetData()); - return workingImage; -} - -mitk::DataNode* QmitkLayersWidget::GetWorkingNode() -{ - mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); - return workingNode; -} - -void QmitkLayersWidget::OnAddLayer() -{ - m_ToolManager->ActivateTool(-1); - - auto workingImage = this->GetWorkingImage(); - if (nullptr == workingImage) - { - return; - } - - try - { - this->WaitCursorOn(); - workingImage->AddLayer(); - this->WaitCursorOff(); - } - catch (mitk::Exception& e) - { - this->WaitCursorOff(); - MITK_ERROR << "Exception caught: " << e.GetDescription(); - QMessageBox::information( - this, "Add layer", "Could not add a new layer. See error log for details.\n"); - return; - } - - this->UpdateGUI(); - emit LayersChanged(); -} - -void QmitkLayersWidget::OnDeleteLayer() -{ - m_ToolManager->ActivateTool(-1); - - auto workingImage = this->GetWorkingImage(); - if (nullptr == workingImage) - { - return; - } - - if (workingImage->GetNumberOfLayers() < 2) - { - return; - } - - QString question = "Do you really want to delete the current layer?"; - QMessageBox::StandardButton answerButton = QMessageBox::question( - this, "Delete layer", question, QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Yes); - - if (answerButton != QMessageBox::Yes) - { - return; - } - - try - { - this->WaitCursorOn(); - workingImage->RemoveLayer(); - this->WaitCursorOff(); - } - catch (mitk::Exception& e) - { - this->WaitCursorOff(); - MITK_ERROR << "Exception caught: " << e.GetDescription(); - QMessageBox::information( - this, "Delete layer", "Could not delete the currently active layer. See error log for details.\n"); - return; - } - - this->UpdateGUI(); - emit LayersChanged(); -} - -void QmitkLayersWidget::OnPreviousLayer() -{ - auto workingImage = this->GetWorkingImage(); - if (nullptr == workingImage) - { - return; - } - - this->OnChangeLayer(workingImage->GetActiveLayer() - 1); -} - -void QmitkLayersWidget::OnNextLayer() -{ - auto workingImage = this->GetWorkingImage(); - if (nullptr == workingImage) - { - return; - } - - this->OnChangeLayer(workingImage->GetActiveLayer() + 1); -} - -void QmitkLayersWidget::OnChangeLayer(int layer) -{ - m_ToolManager->ActivateTool(-1); - - auto workingImage = this->GetWorkingImage(); - if (nullptr == workingImage) - { - return; - } - - try - { - this->WaitCursorOn(); - workingImage->SetActiveLayer(layer); - this->WaitCursorOff(); - } - catch (mitk::Exception& e) - { - this->WaitCursorOff(); - MITK_ERROR << "Exception caught: " << e.GetDescription(); - QMessageBox::information( - this, "Change layer", "Could not change the layer. See error log for details.\n"); - return; - } - - this->UpdateGUI(); - emit LayersChanged(); -} - -void QmitkLayersWidget::WaitCursorOn() -{ - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); -} - -void QmitkLayersWidget::WaitCursorOff() -{ - this->RestoreOverrideCursor(); -} - -void QmitkLayersWidget::RestoreOverrideCursor() -{ - QApplication::restoreOverrideCursor(); -} - diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLayersWidget.h b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLayersWidget.h deleted file mode 100644 index c87d796c5f..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLayersWidget.h +++ /dev/null @@ -1,79 +0,0 @@ -/*============================================================================ - -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 QmitkLayersWidget_h -#define QmitkLayersWidget_h - -// Qt -#include - -namespace Ui -{ - class QmitkLayersWidgetControls; -} - -namespace mitk -{ - class DataNode; - class LabelSetImage; - class ToolManager; -} - -class QmitkLayersWidget : public QWidget -{ - Q_OBJECT - -public: - - explicit QmitkLayersWidget(QWidget* parent = nullptr); - ~QmitkLayersWidget() override; - - void UpdateGUI(); - -Q_SIGNALS: - - void LayersChanged(); - -private: - - mitk::LabelSetImage* GetWorkingImage(); - - mitk::DataNode* GetWorkingNode(); - - // reaction to the button "Add Layer" - void OnAddLayer(); - - // reaction to the button "Delete Layer" - void OnDeleteLayer(); - - // reaction to the button "Previous Layer" - void OnPreviousLayer(); - - // reaction to the button "Next Layer" - void OnNextLayer(); - - // reaction to the combobox change "Change Layer" - void OnChangeLayer(int); - - void WaitCursorOn(); - - void WaitCursorOff(); - - void RestoreOverrideCursor(); - - Ui::QmitkLayersWidgetControls* m_Controls; - - mitk::ToolManager* m_ToolManager; - -}; - -#endif diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLayersWidgetControls.ui b/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLayersWidgetControls.ui deleted file mode 100644 index f2941e4af5..0000000000 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/Common/QmitkLayersWidgetControls.ui +++ /dev/null @@ -1,177 +0,0 @@ - - - QmitkLayersWidgetControls - - - - 0 - 0 - 300 - 75 - - - - - 0 - 0 - - - - - 6 - - - 0 - - - 0 - - - 0 - - - 0 - - - - - Layers - - - - - - Add a layer to the current segmentation session - - - ... - - - - :/Qmitk/AddLayer_48x48.png:/Qmitk/AddLayer_48x48.png - - - - 28 - 28 - - - - true - - - - - - - Delete the active layer - - - ... - - - - :/Qmitk/DeleteLayer_48x48.png:/Qmitk/DeleteLayer_48x48.png - - - - 28 - 28 - - - - true - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - - Change to the previous available layer - - - ... - - - - :/Qmitk/PreviousLayer_48x48.png:/Qmitk/PreviousLayer_48x48.png - - - - 28 - 28 - - - - true - - - - - - - Change to the next available layer - - - ... - - - - :/Qmitk/NextLayer_48x48.png:/Qmitk/NextLayer_48x48.png - - - - 28 - 28 - - - - true - - - - - - - - 50 - 30 - - - - - 40 - 30 - - - - Switch to a layer - - - - 0 - - - - - - - - - - - - diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp index 692a832c4d..6ab7e63f0f 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkAutocropLabelSetImageAction.cpp @@ -1,300 +1,301 @@ /*============================================================================ 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 namespace { // Iterate over all layers, time steps, and dimensions of a LabelSetImage to // determine the overall minimum and maximum indices of labeled pixels. // // Returns false if the input image is empty, minIndex and maxIndex contain // valid indices otherwise. // // Throws an mitk::Exception if read access was denied. // bool DetermineMinimumAndMaximumIndicesOfNonBackgroundPixels(mitk::LabelSetImage::Pointer labelSetImage, itk::Index<3>& minIndex, itk::Index<3>& maxIndex) { // 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); - const auto background = mitk::LabelSetImage::UnlabeledValue; + const auto background = mitk::LabelSetImage::UNLABELED_VALUE; const auto numLayers = labelSetImage->GetNumberOfLayers(); const auto numTimeSteps = labelSetImage->GetTimeSteps(); const itk::Index<3> dim = { labelSetImage->GetDimension(0), labelSetImage->GetDimension(1), labelSetImage->GetDimension(2) }; maxIndex = { 0, 0, 0 }; 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; } mitk::ImagePixelReadAccessor pixelReader(image); 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; } } return !labelSetImageIsEmpty; } // Crop a LabelSetImage. Labels in the cropped LabelSetImage will still have // their original properties like names and colors. // // Returns a cropped LabelSetImage. // // Throws an mitk::Exception if read access was denied. // mitk::LabelSetImage::Pointer Crop(mitk::LabelSetImage::Pointer labelSetImage, const itk::Index<3>& minIndex, const itk::Index<3>& maxIndex) { // 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); const auto numLayers = labelSetImage->GetNumberOfLayers(); const auto numTimeSteps = labelSetImage->GetTimeSteps(); 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(); + auto groupID = croppedLabelSetImage->AddLayer(); + croppedLabelSetImage->SetActiveLayer(groupID); 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; } mitk::ImagePixelReadAccessor pixelReader(image); auto* croppedVolume = new mitk::LabelSetImage::PixelType[numPixels]; itk::Index<3> croppedIndex; itk::Index<3> index; for (croppedIndex[2] = 0; croppedIndex[2] < croppedDim[2]; ++croppedIndex[2]) { for (croppedIndex[1] = 0; croppedIndex[1] < croppedDim[1]; ++croppedIndex[1]) { 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->SetImportVolume(croppedVolume, timeStep, 0, mitk::Image::ReferenceMemory); - croppedLabelSetImage->AddLabelSetToLayer(layer, labelSetImage->GetLabelSet(layer)); + croppedLabelSetImage->ReplaceGroupLabels(layer, labelSetImage->GetConstLabelsByValue(labelSetImage->GetLabelValuesByGroup(layer))); } } return croppedLabelSetImage; } } QmitkAutocropLabelSetImageAction::QmitkAutocropLabelSetImageAction() { } QmitkAutocropLabelSetImageAction::~QmitkAutocropLabelSetImageAction() { } void QmitkAutocropLabelSetImageAction::Run(const QList& selectedNodes) { for (const auto& dataNode : selectedNodes) { mitk::LabelSetImage::Pointer labelSetImage = dynamic_cast(dataNode->GetData()); if (labelSetImage.IsNull()) continue; // Backup currently active layer as we need to restore it later auto activeLayer = labelSetImage->GetActiveLayer(); mitk::LabelSetImage::Pointer croppedLabelSetImage; itk::Index<3> minIndex; itk::Index<3> maxIndex; try { if (!DetermineMinimumAndMaximumIndicesOfNonBackgroundPixels(labelSetImage, minIndex, maxIndex)) { MITK_WARN << "Autocrop was skipped: Image \"" << dataNode->GetName() << "\" is empty."; labelSetImage->SetActiveLayer(activeLayer); // Restore the originally active layer return; } croppedLabelSetImage = Crop(labelSetImage, minIndex, maxIndex); } catch (const mitk::Exception&) { MITK_ERROR << "Autocrop was aborted: Image read access to \"" << dataNode->GetName() << "\" was denied."; labelSetImage->SetActiveLayer(activeLayer); // Restore the originally active layer return; } // Restore the originally active layer in the cropped LabelSetImage 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(croppedLabelSetImage->GetTimeGeometry()); } } 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/QmitkConvertMaskToLabelAction.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkConvertMaskToLabelAction.cpp index d79b525728..95102823fc 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkConvertMaskToLabelAction.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkConvertMaskToLabelAction.cpp @@ -1,96 +1,96 @@ /*============================================================================ 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 "QmitkConvertMaskToLabelAction.h" #include "mitkRenderingManager.h" #include "mitkLabelSetImage.h" #include "mitkToolManagerProvider.h" //needed for qApp #include QmitkConvertMaskToLabelAction::QmitkConvertMaskToLabelAction() { } QmitkConvertMaskToLabelAction::~QmitkConvertMaskToLabelAction() { } void QmitkConvertMaskToLabelAction::Run( const QList &selectedNodes ) { mitk::ToolManager::Pointer toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); mitk::DataNode* workingNode = toolManager->GetWorkingData(0); if (!workingNode) { MITK_INFO << "There is no available segmentation. Please load or create one before using this tool."; return; } mitk::LabelSetImage* workingImage = dynamic_cast( workingNode->GetData() ); assert(workingImage); foreach ( mitk::DataNode::Pointer maskNode, selectedNodes ) { if (maskNode) { mitk::Image* mask = dynamic_cast(maskNode->GetData() ); if (!mask) continue; std::string name = maskNode->GetName(); mitk::Color color; mitk::ColorProperty::Pointer colorProp; maskNode->GetProperty(colorProp,"color"); if (colorProp.IsNull()) continue; color = colorProp->GetValue(); - workingImage->GetLabelSet()->AddLabel(name,color); + workingImage->AddLabel(name,color,0); try { workingImage->MaskStamp( mask, false ); } catch ( mitk::Exception& e ) { MITK_ERROR << "Exception caught: " << e.GetDescription(); return; } maskNode->SetVisibility(false); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { MITK_INFO << " a nullptr node was selected"; } } } void QmitkConvertMaskToLabelAction::SetSmoothed(bool /*smoothed*/) { //not needed } void QmitkConvertMaskToLabelAction::SetDecimated(bool /*decimated*/) { //not needed } void QmitkConvertMaskToLabelAction::SetDataStorage(mitk::DataStorage* /*dataStorage*/) { //not needed } void QmitkConvertMaskToLabelAction::SetFunctionality(berry::QtViewPart* /*functionality*/) { //not needed } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkConvertSurfaceToLabelAction.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkConvertSurfaceToLabelAction.cpp index 5caac30973..d381fc8a59 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkConvertSurfaceToLabelAction.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkConvertSurfaceToLabelAction.cpp @@ -1,101 +1,101 @@ /*============================================================================ 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 "QmitkConvertSurfaceToLabelAction.h" #include "mitkRenderingManager.h" #include "mitkLabelSetImage.h" #include "mitkToolManagerProvider.h" #include //needed for qApp #include #include QmitkConvertSurfaceToLabelAction::QmitkConvertSurfaceToLabelAction() { } QmitkConvertSurfaceToLabelAction::~QmitkConvertSurfaceToLabelAction() { } void QmitkConvertSurfaceToLabelAction::Run( const QList &selectedNodes ) { mitk::ToolManager::Pointer toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); mitk::DataNode* workingNode = toolManager->GetWorkingData(0); if (!workingNode) { MITK_INFO << "There is no available segmentation. Please load or create one before using this tool."; return; } mitk::LabelSetImage* workingImage = dynamic_cast( workingNode->GetData() ); assert(workingImage); foreach ( mitk::DataNode::Pointer surfaceNode, selectedNodes ) { if (surfaceNode) { mitk::Surface* surface = dynamic_cast(surfaceNode->GetData() ); if (!surface) continue; std::string name = surfaceNode->GetName(); mitk::Color color; mitk::ColorProperty::Pointer colorProp; surfaceNode->GetProperty(colorProp,"color"); if (colorProp.IsNull()) continue; color = colorProp->GetValue(); - workingImage->GetLabelSet()->AddLabel(name,color); + workingImage->AddLabel(name,color,0); try { QApplication::setOverrideCursor( QCursor(Qt::WaitCursor) ); // workingImage->SurfaceStamp( surface, false ); QApplication::restoreOverrideCursor(); } catch ( mitk::Exception& e ) { QApplication::restoreOverrideCursor(); MITK_ERROR << "Exception caught: " << e.GetDescription(); return; } surfaceNode->SetVisibility(false); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } else { MITK_INFO << " a nullptr node was selected"; } } } void QmitkConvertSurfaceToLabelAction::SetSmoothed(bool /*smoothed*/) { //not needed } void QmitkConvertSurfaceToLabelAction::SetDecimated(bool /*decimated*/) { //not needed } void QmitkConvertSurfaceToLabelAction::SetDataStorage(mitk::DataStorage* /*dataStorage*/) { //not needed } void QmitkConvertSurfaceToLabelAction::SetFunctionality(berry::QtViewPart* /*functionality*/) { //not needed } diff --git a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkCreateMultiLabelSegmentationAction.cpp b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkCreateMultiLabelSegmentationAction.cpp index caef6ce886..40eefe41ad 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkCreateMultiLabelSegmentationAction.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkCreateMultiLabelSegmentationAction.cpp @@ -1,116 +1,116 @@ /*============================================================================ 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 "QmitkCreateMultiLabelSegmentationAction.h" #include "mitkLabelSetImage.h" #include "mitkLabelSetImageHelper.h" #include #include "QMessageBox" QmitkCreateMultiLabelSegmentationAction::QmitkCreateMultiLabelSegmentationAction() { } QmitkCreateMultiLabelSegmentationAction::~QmitkCreateMultiLabelSegmentationAction() { } void QmitkCreateMultiLabelSegmentationAction::Run(const QList& selectedNodes) { if (m_DataStorage.IsNull()) { auto message = tr("Data storage not set."); MITK_ERROR << message; QMessageBox::warning(nullptr, "New segmentation", message); return; } for (const auto& referenceNode : selectedNodes) { if (referenceNode.IsNull()) { continue; } mitk::Image::ConstPointer referenceImage = dynamic_cast(referenceNode->GetData()); if (referenceImage.IsNull()) { MITK_WARN << "Could not create multi label segmentation for non-image node."; continue; } if (referenceImage->GetDimension() <= 1) { MITK_WARN << "Segmentation is currently not supported for 2D images."; continue; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(nullptr); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(referenceNode, segTemplateImage); } catch (mitk::Exception& e) { MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(nullptr, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } mitk::Label::Pointer newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); - newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); + newLabelSetImage->AddLabel(newLabel, 0); if (!m_DataStorage->Exists(newSegmentationNode)) { m_DataStorage->Add(newSegmentationNode, referenceNode); } } } void QmitkCreateMultiLabelSegmentationAction::SetDataStorage(mitk::DataStorage* dataStorage) { m_DataStorage = dataStorage; } void QmitkCreateMultiLabelSegmentationAction::SetFunctionality(berry::QtViewPart*) { //not needed } void QmitkCreateMultiLabelSegmentationAction::SetSmoothed(bool) { //not needed } void QmitkCreateMultiLabelSegmentationAction::SetDecimated(bool) { //not needed } 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 72ee007341..08afd9849a 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1083 +1,1107 @@ /*============================================================================ 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 #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) { // 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(); this->RemoveObserversFromWorkingImage(); // 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, &Self::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, &Self::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; this->RemoveObserversFromWorkingImage(); // 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); } // Set new working node m_WorkingNode = selectedWorkingNode; m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { this->ApplySelectionModeOnWorkingNode(); // Add visibility observer for the new segmentation node auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &Self::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); this->AddObserversToWorkingImage(); } } // 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::OnLabelAdded(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnLabelRemoved(mitk::LabelSetImage::LabelValueType) { this->ValidateSelectionInput(); } void QmitkSegmentationView::OnGroupRemoved(mitk::LabelSetImage::GroupIndexType) { this->ValidateSelectionInput(); } mitk::LabelSetImage* QmitkSegmentationView::GetWorkingImage() { if (m_WorkingNode.IsNull()) return nullptr; return dynamic_cast(m_WorkingNode->GetData()); } void QmitkSegmentationView::AddObserversToWorkingImage() { auto* workingImage = this->GetWorkingImage(); if (workingImage != nullptr) { - workingImage->AddLabelAddedListener(mitk::MessageDelegate1(this, &Self::OnLabelAdded)); - workingImage->AddLabelRemovedListener(mitk::MessageDelegate1(this, &Self::OnLabelRemoved)); - workingImage->AddGroupRemovedListener(mitk::MessageDelegate1(this, &Self::OnGroupRemoved)); + auto& widget = *this; + m_LabelAddedObserver.Reset(workingImage, mitk::LabelAddedEvent(), [&widget](const itk::EventObject& event) + { + auto labelEvent = dynamic_cast(&event); + widget.OnLabelAdded(labelEvent->GetLabelValue()); + }); + m_LabelRemovedObserver.Reset(workingImage, mitk::LabelRemovedEvent(), [&widget](const itk::EventObject& event) + { + auto labelEvent = dynamic_cast(&event); + widget.OnLabelRemoved(labelEvent->GetLabelValue()); + }); + + m_GroupRemovedObserver.Reset(workingImage, mitk::GroupRemovedEvent(), [&widget](const itk::EventObject& event) + { + auto groupEvent = dynamic_cast(&event); + widget.OnGroupRemoved(groupEvent->GetGroupID()); + }); } } void QmitkSegmentationView::RemoveObserversFromWorkingImage() { - auto* workingImage = this->GetWorkingImage(); - - if (workingImage != nullptr) - { - workingImage->RemoveLabelAddedListener(mitk::MessageDelegate1(this, &Self::OnLabelAdded)); - workingImage->RemoveLabelRemovedListener(mitk::MessageDelegate1(this, &Self::OnLabelRemoved)); - workingImage->RemoveGroupRemovedListener(mitk::MessageDelegate1(this, &Self::OnGroupRemoved)); - } + m_LabelAddedObserver.Reset(); + m_LabelRemovedObserver.Reset(); + m_GroupRemovedObserver.Reset(); } 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(); + auto labels = workingImage->GetLabelValuesByGroup(workingImage->GetActiveLayer()); + auto it = std::find(labels.begin(), labels.end(), workingImage->GetActiveLabel()->GetValue()); + + if (it != labels.end()) + ++it; + + if (it == labels.end()) + { + it = labels.begin(); + } + + workingImage->SetActiveLabel(*it); 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::MultiLabelIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) QmitkNewSegmentationDialog::DoRenameLabel(newLabel, nullptr, m_Parent); - newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); + newLabelSetImage->AddLabel(newLabel, newLabelSetImage->GetActiveLayer()); } 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::OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels) { auto segmentation = this->GetCurrentSegmentation(); const auto labelValue = labels.front(); - const auto groupID = segmentation->GetGroupIndexOfLabel(labelValue); - if (groupID != segmentation->GetActiveLayer()) segmentation->SetActiveLayer(groupID); - if (labelValue != segmentation->GetActiveLabelSet()->GetActiveLabel()->GetValue()) segmentation->GetActiveLabelSet()->SetActiveLabel(labelValue); - - segmentation->Modified(); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + if (nullptr == segmentation->GetActiveLabel() || labelValue != segmentation->GetActiveLabel()->GetValue()) + { + segmentation->SetActiveLabel(labelValue); + mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + } + m_Controls->slicesInterpolator->SetActiveLabelValue(labelValue); } void QmitkSegmentationView::OnGoToLabel(mitk::LabelSetImage::LabelValueType /*label*/, const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelRenameRequested(mitk::Label* label, bool rename) const { auto segmentation = this->GetCurrentSegmentation(); if (rename) { QmitkNewSegmentationDialog::DoRenameLabel(label, segmentation, this->m_Parent, QmitkNewSegmentationDialog::Mode::RenameLabel); return; } QmitkNewSegmentationDialog::DoRenameLabel(label, nullptr, this->m_Parent, QmitkNewSegmentationDialog::Mode::NewLabel); } mitk::LabelSetImage* QmitkSegmentationView::GetCurrentSegmentation() const { auto workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); if (workingNode.IsNull()) mitkThrow() << "Segmentation view is in an invalid state. Working node is null, but a label selection change has been triggered."; auto segmentation = dynamic_cast(workingNode->GetData()); if (nullptr == segmentation) mitkThrow() << "Segmentation view is in an invalid state. Working node contains no segmentation, but a label selection change has been triggered."; return segmentation; } /**********************************************************************/ /* 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, &Self::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &Self::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, &Self::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &Self::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' 'Segment Anything'"); QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut TotalSegmentator"); #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()); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &Self::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->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &Self::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &Self::OnShowMarkerNodes); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::CurrentSelectionChanged, this, &Self::OnCurrentLabelSelectionChanged); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::GoToLabel, this, &Self::OnGoToLabel); connect(m_Controls->multiLabelWidget, &QmitkMultiLabelManager::LabelRenameRequested, this, &Self::OnLabelRenameRequested); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &Self::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) { 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, &Self::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, &Self::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->multiLabelWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool compactView = prefs->GetBool("compact view", false); int numberOfColumns = compactView ? 6 : 4; m_Controls->toolSelectionBox2D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox2D->SetShowNames(!compactView); m_Controls->toolSelectionBox3D->SetLayoutColumns(numberOfColumns); m_Controls->toolSelectionBox3D->SetShowNames(!compactView); } 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()); + auto image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } 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); } 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->multiLabelWidget->setEnabled(hasWorkingNode); m_Controls->toolSelectionBox2D->setEnabled(hasBothNodes); m_Controls->toolSelectionBox3D->setEnabled(hasBothNodes); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); if (hasReferenceNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !referenceNode->IsVisible(nullptr)) { warning += tr("The selected reference image is currently not visible!"); toolSelectionBoxesEnabled = false; } } if (hasWorkingNode) { if (nullptr != m_RenderWindowPart && m_RenderWindowPart->HasCoupledRenderWindows() && !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->multiLabelWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); numberOfLabels = labelSetImage->GetTotalNumberOfLabels(); if (numberOfLabels > 0) m_Controls->slicesInterpolator->setEnabled(true); m_Controls->multiLabelWidget->SetMultiLabelSegmentation(dynamic_cast(workingNode->GetData())); + + if (!m_Controls->multiLabelWidget->GetSelectedLabels().empty()) + { + m_Controls->slicesInterpolator->SetActiveLabelValue(m_Controls->multiLabelWidget->GetSelectedLabels().front()); + } } else { m_Controls->multiLabelWidget->SetMultiLabelSegmentation(nullptr); } toolSelectionBoxesEnabled &= numberOfLabels > 0; // 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. // Additionally this check only has to be performed for render window parts with coupled render windows. // For different render window parts the user is given the option to reinitialize each render window individually // (see QmitkRenderWindow::ShowOverlayMessage). 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(); } } 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 a4f3ea2571..3f0596941c 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.h @@ -1,175 +1,180 @@ /*============================================================================ 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 #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 properties 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 creating new ones, 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 OnCurrentLabelSelectionChanged(QmitkMultiLabelManager::LabelValueVectorType labels); void OnGoToLabel(mitk::LabelSetImage::LabelValueType label, const mitk::Point3D&); void OnLabelRenameRequested(mitk::Label* label, bool rename) const; void OnLabelAdded(mitk::LabelSetImage::LabelValueType labelValue); void OnLabelRemoved(mitk::LabelSetImage::LabelValueType labelValue); void OnGroupRemoved(mitk::LabelSetImage::GroupIndexType groupIndex); private: using Self = QmitkSegmentationView; mitk::LabelSetImage* GetWorkingImage(); void AddObserversToWorkingImage(); void RemoveObserversFromWorkingImage(); 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(); 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 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; mitk::LabelSetImage* GetCurrentSegmentation() 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; + + mitk::ITKEventObserverGuard m_LabelAddedObserver; + mitk::ITKEventObserverGuard m_LabelRemovedObserver; + mitk::ITKEventObserverGuard m_GroupRemovedObserver; }; #endif