diff --git a/CMakeExternals/ITK.cmake b/CMakeExternals/ITK.cmake index 24803c0ffc..b6ab2759cd 100644 --- a/CMakeExternals/ITK.cmake +++ b/CMakeExternals/ITK.cmake @@ -1,88 +1,89 @@ #----------------------------------------------------------------------------- # ITK #----------------------------------------------------------------------------- # Sanity checks if(DEFINED ITK_DIR AND NOT EXISTS ${ITK_DIR}) message(FATAL_ERROR "ITK_DIR variable is defined but corresponds to non-existing directory") endif() set(proj ITK) set(proj_DEPENDENCIES GDCM) if(MITK_USE_OpenCV) list(APPEND proj_DEPENDENCIES OpenCV) endif() if(MITK_USE_HDF5) list(APPEND proj_DEPENDENCIES HDF5) endif() set(ITK_DEPENDS ${proj}) if(NOT DEFINED ITK_DIR) set(additional_cmake_args -DUSE_WRAP_ITK:BOOL=OFF) list(APPEND additional_cmake_args -DITKV4_COMPATIBILITY:BOOL=OFF -DITK_LEGACY_REMOVE:BOOL=ON ) if(MITK_USE_OpenCV) list(APPEND additional_cmake_args -DModule_ITKVideoBridgeOpenCV:BOOL=ON -DOpenCV_DIR:PATH=${OpenCV_DIR} "-DCMAKE_CONFIGURATION_TYPES:STRING=Debug$Release" ) endif() # Keep the behaviour of ITK 4.3 which by default turned on ITK Review # see MITK bug #17338 list(APPEND additional_cmake_args -DModule_ITKReview:BOOL=ON -DModule_ITKOpenJPEG:BOOL=ON # for 4.7, the OpenJPEG is needed by review but the variable must be set -DModule_IsotropicWavelets:BOOL=ON ) if(CTEST_USE_LAUNCHERS) list(APPEND additional_cmake_args "-DCMAKE_PROJECT_${proj}_INCLUDE:FILEPATH=${CMAKE_ROOT}/Modules/CTestUseLaunchers.cmake" ) endif() mitk_query_custom_ep_vars() ExternalProject_Add(${proj} LIST_SEPARATOR ${sep} UPDATE_COMMAND "" GIT_REPOSITORY https://github.com/MITK/ITK.git GIT_TAG v5.2.1-patched CMAKE_GENERATOR ${gen} CMAKE_GENERATOR_PLATFORM ${gen_platform} CMAKE_ARGS ${ep_common_args} ${additional_cmake_args} -DBUILD_EXAMPLES:BOOL=OFF -DITK_USE_SYSTEM_GDCM:BOOL=ON -DGDCM_DIR:PATH=${GDCM_DIR} -DITK_USE_SYSTEM_HDF5:BOOL=ON -DHDF5_DIR:PATH=${HDF5_DIR} + -DModule_GrowCut:BOOL=ON ${${proj}_CUSTOM_CMAKE_ARGS} CMAKE_CACHE_ARGS ${ep_common_cache_args} ${${proj}_CUSTOM_CMAKE_CACHE_ARGS} CMAKE_CACHE_DEFAULT_ARGS ${ep_common_cache_default_args} ${${proj}_CUSTOM_CMAKE_CACHE_DEFAULT_ARGS} DEPENDS ${proj_DEPENDENCIES} ) set(ITK_DIR ${ep_prefix}) mitkFunctionInstallExternalCMakeProject(${proj}) else() mitkMacroEmptyExternalProject(${proj} "${proj_DEPENDENCIES}") endif() diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp index 134106e179..25c916e624 100755 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp @@ -1,238 +1,294 @@ /*============================================================================ 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 { const auto backgroundValue = labelImage->GetExteriorLabel()->GetValue(); if (paintingPixelValue != backgroundValue) { 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->GetLabel(existingValue, labelImage->GetActiveLayer())->GetLocked()) resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } } else { const auto activePixelValue = labelImage->GetActiveLabel(labelImage->GetActiveLayer())->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(); } return activePixelValue; } diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h index 6fe5f072bd..50272c7dd5 100644 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h @@ -1,116 +1,152 @@ /*============================================================================ 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 mitkContourModelUtils_h #define mitkContourModelUtils_h #include #include #include #include namespace mitk { /** * \brief Helpful methods for working with contours and images * * */ class MITKCONTOURMODEL_EXPORT ContourModelUtils : public itk::Object { public: mitkClassMacroItkParent(ContourModelUtils, itk::Object); /** \brief Projects a contour onto an image point by point. Converts from world to index coordinates. \param slice \param contourIn3D */ static ContourModel::Pointer ProjectContourTo2DSlice(const Image *slice, const ContourModel *contourIn3D); /** \brief Projects a slice index coordinates of a contour back into world coordinates. \param sliceGeometry \param contourIn2D */ static ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, const ContourModel *contourIn2D); /** \brief Fill a contour in a 2D slice with a specified pixel value. This version always uses the contour of time step 0 and fills the image. + \deprecated Ths function is deprecated. Use FillContourInSlice2() (in + conjunction e.g. with TransferLabelContent()) instead. \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ + [[deprecated]] static void FillContourInSlice(const ContourModel *projectedContour, Image *sliceImage, const Image* workingImage, int paintingPixelValue = 1); /** \brief Fill a contour in a 2D slice with a specified pixel value. This overloaded version uses the contour at the passed contourTimeStep to fill the passed image slice. + \deprecated Ths function is deprecated. Use FillContourInSlice2() (in + conjunction e.g. with TransferLabelContent()) instead. \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ + [[deprecated]] static void FillContourInSlice(const ContourModel *projectedContour, TimeStepType contourTimeStep, Image *sliceImage, const Image* workingImage, int paintingPixelValue = 1); + /** + \brief Fill a contour in a 2D slice with a specified pixel value. + This version always uses the contour of time step 0 and fills the image. + \param projectedContour Pointer to the contour that should be projected. + \param sliceImage Pointer to the image which content should be altered by + adding the contour with the specified paintingPixelValue. + \pre sliceImage points to a valid instance + \pre projectedContour points to a valid instance + */ + static void FillContourInSlice2(const ContourModel* projectedContour, + Image* sliceImage, + int paintingPixelValue = 1); + + /** + \brief Fill a contour in a 2D slice with a specified pixel value. + This overloaded version uses the contour at the passed contourTimeStep + to fill the passed image slice. + \param projectedContour Pointer to the contour that should be projected. + \param sliceImage Pointer to the image which content should be altered by + adding the contour with the specified paintingPixelValue. + \pre sliceImage points to a valid instance + \pre projectedContour points to a valid instance + */ + static void FillContourInSlice2(const ContourModel* projectedContour, + TimeStepType contourTimeStep, + Image* sliceImage, + int paintingPixelValue = 1); + /** \brief Fills the paintingPixelValue into every pixel of resultImage as indicated by filledImage. If a LableSet image is specified it also by incorporating the rules of LabelSet images when filling the content. \param filledImage Pointer to the image content that should be checked to decied of a pixel in resultImage should be filled with paintingPixelValue or not. \param resultImage Pointer to the image content that should be overwritten guided by the content of filledImage. \param image Pointer to an mitk image that allows to define the LabelSet image which states steer the filling process. If an LabelSet instance is passed its states (e.g. locked labels etc...) will be used. If nullptr or an normal image is passed, then simply any pixel position indicated by filledImage will be overwritten. \param paintingPixelValue the pixelvalue/label that should be used in the result image when filling. \param fillForegroundThreshold The threshold value that decides if a pixel in the filled image counts as foreground (>=fillForegroundThreshold) or not. + \deprecated Ths function is deprecated. Use TransferLabelContent() instead. */ + [[deprecated]] static void FillSliceInSlice(vtkSmartPointer filledImage, vtkSmartPointer resultImage, const Image* image, int paintingPixelValue, double fillForegroundThreshold = 1.0); /** \brief Move the contour in time step 0 to to a new contour model at the given time step. */ static ContourModel::Pointer MoveZerothContourTimeStep(const ContourModel *contour, TimeStepType timeStep); /** \brief Retrieves the active pixel value of a (labelset) image. If the image is basic image, the pixel value 1 (one) will be returned. If the image is actually a labelset image, the pixel value of the active label of the active layer will be returned. \param workingImage The (labelset) image to retrieve the active pixel value of. */ static int GetActivePixelValue(const Image* workingImage); protected: ContourModelUtils(); ~ContourModelUtils() override; }; } #endif diff --git a/Modules/Core/files.cmake b/Modules/Core/files.cmake index 3cdffc2334..eab67f18d2 100644 --- a/Modules/Core/files.cmake +++ b/Modules/Core/files.cmake @@ -1,324 +1,323 @@ 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/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/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/mitkIPropertyExtensions.cpp DataManagement/mitkIPropertyFilters.cpp DataManagement/mitkIPropertyOwner.cpp DataManagement/mitkIPropertyPersistence.cpp DataManagement/mitkIPropertyProvider.cpp DataManagement/mitkLandmarkProjectorBasedCurvedGeometry.cpp DataManagement/mitkLandmarkProjector.cpp DataManagement/mitkLevelWindow.cpp DataManagement/mitkLevelWindowManager.cpp DataManagement/mitkLevelWindowPreset.cpp DataManagement/mitkLevelWindowProperty.cpp DataManagement/mitkLine.cpp DataManagement/mitkLookupTable.cpp DataManagement/mitkLookupTableProperty.cpp DataManagement/mitkLookupTables.cpp # specializations of GenericLookupTable 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/mitkNodePredicateFirstLevel.cpp DataManagement/mitkNodePredicateFunction.cpp DataManagement/mitkNodePredicateGeometry.cpp DataManagement/mitkNodePredicateNot.cpp DataManagement/mitkNodePredicateOr.cpp DataManagement/mitkNodePredicateProperty.cpp DataManagement/mitkNodePredicateDataProperty.cpp DataManagement/mitkNodePredicateSource.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/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/mitkVector.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/mitkCrosshairPositionEvent.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/mitkImageGenerator.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/mitkLog.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 Rendering/mitkAbstractAnnotationRenderer.cpp Rendering/mitkAnnotationUtils.cpp Rendering/mitkBaseRenderer.cpp Rendering/mitkBaseRendererHelper.cpp #Rendering/mitkGLMapper.cpp Moved to deprecated LegacyGL Module 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/mitkSurfaceGLMapper2D.cpp Moved to deprecated LegacyGL Module Rendering/mitkSurfaceVtkMapper2D.cpp Rendering/mitkSurfaceVtkMapper3D.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/mitkCrosshairPositionEvent.h b/Modules/Core/include/mitkCrosshairPositionEvent.h deleted file mode 100644 index 0d22b2969f..0000000000 --- a/Modules/Core/include/mitkCrosshairPositionEvent.h +++ /dev/null @@ -1,30 +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 mitkCrosshairPositionEvent_h -#define mitkCrosshairPositionEvent_h - -#include -#include - -namespace mitk -{ - /** A special mitk::Event thrown by the SliceNavigationController on mouse scroll - */ - class MITKCORE_EXPORT CrosshairPositionEvent : public mitk::InteractionEvent - { - public: - CrosshairPositionEvent(BaseRenderer *sender); - }; -} - -#endif diff --git a/Modules/Core/include/mitkDisplayActionEventBroadcast.h b/Modules/Core/include/mitkDisplayActionEventBroadcast.h index d2470f5854..30066977f8 100644 --- a/Modules/Core/include/mitkDisplayActionEventBroadcast.h +++ b/Modules/Core/include/mitkDisplayActionEventBroadcast.h @@ -1,224 +1,218 @@ /*============================================================================ 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 MITKDISPLAYACTIONEVENTBROADCAST_H #define MITKDISPLAYACTIONEVENTBROADCAST_H #include "mitkInteractionEventObserver.h" #include namespace mitk { /** * @brief This class serves as an event state machine while simultaneously observing interaction events. * It connects the actions from the event state machine .xml-file with concrete functions of this class. * * The observed interaction events are mouse events that trigger certain actions, according to an event configuration (e.g. PACS mode). * These actions are defined and connected inside this broadcast class. * They typically contain some preprocessing steps and use the results of the preprocessing to broadcast a specific display event. * * Any instance that wants to react on the invoked events can call 'AddObserver' on a specific broadcast instance, * given an itkEventObject and an itkCommand. */ class MITKCORE_EXPORT DisplayActionEventBroadcast : public EventStateMachine, public InteractionEventObserver { public: mitkClassMacro(DisplayActionEventBroadcast, EventStateMachine); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** * By this function this observer is notified about about every 'InteractionEvent'. * The interaction event is passed to the state machine in order to use its infrastructure. * For more information see @see InteractionEventObserver. * * @par interactionEvent The event that was observed and triggered this notification. * @par isHandled Flag that indicates if a 'DataInteractor' has already handled the event. */ void Notify(InteractionEvent* interactionEvent, bool isHandled) override; protected: DisplayActionEventBroadcast(); ~DisplayActionEventBroadcast() override; /** * @brief Connects the action names used in the state machine pattern with functions implemented within this InteractionEventObserver. */ void ConnectActionsAndFunctions() override; /** * @brief This function is executed when a config object is set / changed (via 'SetEventConfig' or 'AddEventConfig' in 'InteractionEventObserver'). * It is used to read out the parameters set in the configuration file and to set the member variables accordingly. */ void ConfigurationChanged() override; /** * @brief Filters the event resp. the sender of the event. * * @par interactionEvent The event whose sender has to be checked * @par data node The data node is ignored in this specific implementation. * * @return True, if the sender of the event is a valid sender and the sending renderer is a 2D-renderer. False, if not. */ bool FilterEvents(InteractionEvent* interactionEvent, DataNode* dataNode) override; ////////////////////////////////////////////////////////////////////////// // Functions to react to interaction events (actions) ////////////////////////////////////////////////////////////////////////// /** * @brief Check if the given interaction event is actually an 'InteractionPositionEvent'. * * @par interactionEvent The interaction event that is checked. * * @return True, if the given event can be dynamically cast to an 'InteractionPositionEvent'. False, if not. */ bool CheckPositionEvent(const InteractionEvent* interactionEvent); bool CheckRotationPossible(const InteractionEvent* interactionEvent); bool CheckSwivelPossible(const InteractionEvent* interactionEvent); void Init(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void Move(StateMachineAction* stateMachineAction , InteractionEvent* interactionEvent); void SetCrosshair(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void Zoom(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void Scroll(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void ScrollOneUp(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void ScrollOneDown(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void AdjustLevelWindow(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void StartRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void EndRotation(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void Rotate(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void Swivel(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void IncreaseTimeStep(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); void DecreaseTimeStep(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); private: - void UpdateStatusbar(StateMachineAction* stateMachineAction, InteractionEvent* interactionEvent); - bool GetBoolProperty(PropertyList::Pointer propertyList, const char* propertyName, bool defaultValue); /** * @brief Reference to the service registration of the observer. * This is needed to unregister the observer on unload. */ us::ServiceRegistration m_ServiceRegistration; /** * @brief Determines if this broadcast class reacts to events that already have been processed by a DataInteractor. * The default value is false. */ bool m_AlwaysReact; /** * @brief Coordinate of the mouse pointer at beginning of an interaction (translated to mm unit). */ Point2D m_StartCoordinateInMM; /** * @brief Coordinate of the mouse pointer in the last step within an interaction. */ Point2D m_LastDisplayCoordinate; /** - * @brief Coordinate of the mouse pointer in the last step within an interaction (translated to mm unit). - */ - Point2D m_LastCoordinateInMM; - /** * \brief Current coordinates of the pointer. */ Point2D m_CurrentDisplayCoordinate; /** * @brief Defines the behavior at the end of a data set. * If set to true, it will restart at end of data set from the beginning. */ bool m_AutoRepeat; /** * @brief Defines how many slices are scrolled per pixel that the mouse pointer was moved. * By default the modifier is 4. This means that when the user moves the cursor by 4 pixels in Y-direction, * the scene is scrolled by one slice. If the user has moved the the cursor by 20 pixels, the scene is * scrolled by 5 slices. * * If the cursor has moved less than m_IndexToSliceModifier pixels, the scene is scrolled by one slice. */ int m_IndexToSliceModifier; /** * @brief Defines the scroll behavior. * Default is up/down movement of pointer performs scrolling */ std::string m_ScrollDirection; /** * @brief Defines how the axis of interaction influences scroll behavior. */ bool m_InvertScrollDirection; /** * @brief Defines the zoom behavior. * Default is up/down movement of pointer performs zooming */ std::string m_ZoomDirection; /** * @brief Defines how the axis of interaction influences zoom behavior. */ bool m_InvertZoomDirection; /** * @brief Factor to adjust zooming speed. */ float m_ZoomFactor; /** * @brief Defines how the axis of interaction influences move behavior. */ bool m_InvertMoveDirection; /** * @brief Defines the level-window behavior. * Default is left/right movement of pointer modifies the level. */ std::string m_LevelDirection; /** * @brief Defines how the axis of interaction influences level-window behavior. */ bool m_InvertLevelWindowDirection; /** * @brief Determines if the angle between crosshair remains fixed when rotating. */ bool m_LinkPlanes; typedef std::vector SNCVector; SNCVector m_RotatableSNCs; SNCVector m_SNCsToBeRotated; Point3D m_LastCursorPosition; Point3D m_CenterOfRotation; Point2D m_ReferenceCursor; Vector3D m_RotationPlaneNormal; Vector3D m_RotationPlaneXVector; Vector3D m_RotationPlaneYVector; Vector3D m_PreviousRotationAxis; ScalarType m_PreviousRotationAngle; }; } // end namespace #endif // MITKDISPLAYACTIONEVENTBROADCAST_H diff --git a/Modules/Core/include/mitkStatusBar.h b/Modules/Core/include/mitkStatusBar.h index 7e45957ffe..16a5edab5c 100755 --- a/Modules/Core/include/mitkStatusBar.h +++ b/Modules/Core/include/mitkStatusBar.h @@ -1,91 +1,92 @@ /*============================================================================ 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 MITKSTATUSBAR_H #define MITKSTATUSBAR_H #include "mitkStatusBarImplementation.h" #include #include #include +#include #include namespace mitk { //##Documentation //## @brief Sending a message to the applications StatusBar //## //## Holds a GUI dependent StatusBarImplementation and sends the text further. //## nearly equal to itk::OutputWindow, //## no Window, but one line of text and a delay for clear. //## all mitk-classes use this class to display text on GUI-StatusBar. //## The mainapplication has to set the internal held StatusBarImplementation with SetInstance(..). //## @ingroup Interaction class MITKCORE_EXPORT StatusBar : public itk::Object { public: itkTypeMacro(StatusBar, itk::Object); //##Documentation //## @brief static method to get the GUI dependent StatusBar-instance //## so the methods DisplayText, etc. can be called //## No reference counting, cause of decentral static use! static StatusBar *GetInstance(); //##Documentation //## @brief Supply a GUI- dependent StatusBar. Has to be set by the application //## to connect the application dependent subclass of mitkStatusBar //## if you create an instance, then call ->Delete() on the supplied //## instance after setting it. static void SetImplementation(StatusBarImplementation *instance); //##Documentation //## @brief Send a string to the applications StatusBar void DisplayText(const char *t); //##Documentation //## @brief Send a string with a time delay to the applications StatusBar void DisplayText(const char *t, int ms); void DisplayErrorText(const char *t); void DisplayWarningText(const char *t); void DisplayWarningText(const char *t, int ms); void DisplayGenericOutputText(const char *t); void DisplayDebugText(const char *t); void DisplayGreyValueText(const char *t); //##Documentation + void DisplayRendererInfo(Point3D point, TimePointType time); //## @brief Display position, index, time and pixel value - void DisplayImageInfo(mitk::Point3D point, itk::Index<3> index, mitk::ScalarType time, mitk::ScalarType pixelValue); + void DisplayImageInfo(Point3D point, itk::Index<3> index, TimePointType time, ScalarType pixelValue); //## @brief Display rotation, index, time and custom pixel value - void DisplayImageInfo(mitk::Point3D point, itk::Index<3> index, mitk::ScalarType time, const char *pixelValue); - //##Documentation + void DisplayImageInfo(Point3D point, itk::Index<3> index, TimePointType time, const char* pixelValue); //## @brief Display placeholder text for invalid information void DisplayImageInfoInvalid(); //##Documentation //## @brief removes any temporary message being shown. void Clear(); //##Documentation //## @brief Set the SizeGrip of the window //## (the triangle in the lower right Windowcorner for changing the size) //## to enabled or disabled void SetSizeGripEnabled(bool enable); protected: StatusBar(); ~StatusBar() override; static StatusBarImplementation *m_Implementation; static StatusBar *m_Instance; }; } // end namespace mitk #endif /* define MITKSTATUSBAR_H */ diff --git a/Modules/Core/resource/Interactions/DisplayInteraction.xml b/Modules/Core/resource/Interactions/DisplayInteraction.xml index 81e8d9e23f..82a24b1299 100644 --- a/Modules/Core/resource/Interactions/DisplayInteraction.xml +++ b/Modules/Core/resource/Interactions/DisplayInteraction.xml @@ -1,175 +1,165 @@ - - - - - - - - - - diff --git a/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp b/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp index 6201016e0c..5d1b7aacc4 100644 --- a/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp +++ b/Modules/Core/src/Controllers/mitkSliceNavigationController.cpp @@ -1,562 +1,560 @@ /*============================================================================ 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 "mitkSliceNavigationController.h" #include #include "mitkBaseRenderer.h" -#include "mitkCrosshairPositionEvent.h" #include "mitkOperation.h" #include "mitkOperationActor.h" #include "mitkPlaneGeometry.h" #include "mitkProportionalTimeGeometry.h" #include "mitkArbitraryTimeGeometry.h" #include "mitkSlicedGeometry3D.h" #include "mitkVtkPropRenderer.h" #include "mitkImage.h" #include "mitkImagePixelReadAccessor.h" #include "mitkInteractionConst.h" #include "mitkNodePredicateDataType.h" #include "mitkOperationEvent.h" #include "mitkPixelTypeMultiplex.h" #include "mitkPlaneOperation.h" #include "mitkPointOperation.h" -#include "mitkStatusBar.h" #include "mitkApplyTransformMatrixOperation.h" namespace mitk { PlaneGeometry::PlaneOrientation ViewDirectionToPlaneOrientation(SliceNavigationController::ViewDirection viewDiretion) { switch (viewDiretion) { case SliceNavigationController::Axial: return PlaneGeometry::Axial; case SliceNavigationController::Sagittal: return PlaneGeometry::Sagittal; case SliceNavigationController::Coronal: return PlaneGeometry::Coronal; case SliceNavigationController::Original: default: return PlaneGeometry::None; } } SliceNavigationController::SliceNavigationController() : BaseController() , m_InputWorldTimeGeometry(TimeGeometry::ConstPointer()) , m_CreatedWorldGeometry(TimeGeometry::Pointer()) , m_ViewDirection(Axial) , m_DefaultViewDirection(Axial) , m_RenderingManager(RenderingManager::Pointer()) , m_Renderer(nullptr) , m_BlockUpdate(false) , m_SliceLocked(false) , m_SliceRotationLocked(false) { typedef itk::SimpleMemberCommand SNCCommandType; SNCCommandType::Pointer sliceStepperChangedCommand, timeStepperChangedCommand; sliceStepperChangedCommand = SNCCommandType::New(); timeStepperChangedCommand = SNCCommandType::New(); sliceStepperChangedCommand->SetCallbackFunction(this, &SliceNavigationController::SendSlice); timeStepperChangedCommand->SetCallbackFunction(this, &SliceNavigationController::SendTime); m_Slice->AddObserver(itk::ModifiedEvent(), sliceStepperChangedCommand); m_Time->AddObserver(itk::ModifiedEvent(), timeStepperChangedCommand); m_Slice->SetUnitName("mm"); m_Time->SetUnitName("ms"); } SliceNavigationController::~SliceNavigationController() { // nothing here } void SliceNavigationController::SetInputWorldTimeGeometry(const TimeGeometry* geometry) { if (nullptr != geometry) { if (geometry->GetBoundingBoxInWorld()->GetDiagonalLength2() < eps) { itkWarningMacro("setting an empty bounding-box"); geometry = nullptr; } } if (m_InputWorldTimeGeometry != geometry) { m_InputWorldTimeGeometry = geometry; this->Modified(); } } void SliceNavigationController::SetViewDirectionToDefault() { m_ViewDirection = m_DefaultViewDirection; } const char* SliceNavigationController::GetViewDirectionAsString() const { const char* viewDirectionString; switch (m_ViewDirection) { case SliceNavigationController::Axial: viewDirectionString = "Axial"; break; case SliceNavigationController::Sagittal: viewDirectionString = "Sagittal"; break; case SliceNavigationController::Coronal: viewDirectionString = "Coronal"; break; case SliceNavigationController::Original: viewDirectionString = "Original"; break; default: viewDirectionString = "No View Direction Available"; break; } return viewDirectionString; } void SliceNavigationController::Update() { if (!m_BlockUpdate) { if (m_ViewDirection == Sagittal) { this->Update(Sagittal, true, true, false); } else if (m_ViewDirection == Coronal) { this->Update(Coronal, false, true, false); } else if (m_ViewDirection == Axial) { this->Update(Axial, false, false, true); } else { this->Update(m_ViewDirection); } } } void SliceNavigationController::Update(SliceNavigationController::ViewDirection viewDirection, bool top, bool frontside, bool rotated) { if (m_BlockUpdate) { return; } if (m_InputWorldTimeGeometry.IsNull()) { return; } if (0 == m_InputWorldTimeGeometry->CountTimeSteps()) { return; } m_BlockUpdate = true; if (m_LastUpdateTime < m_InputWorldTimeGeometry->GetMTime()) { Modified(); } this->SetViewDirection(viewDirection); if (m_LastUpdateTime < GetMTime()) { m_LastUpdateTime = GetMTime(); this->CreateWorldGeometry(top, frontside, rotated); } // unblock update; we may do this now, because if m_BlockUpdate was already // true before this method was entered, then we will never come here. m_BlockUpdate = false; // Send the geometry. Do this even if nothing was changed, because maybe // Update() was only called to re-send the old geometry and time/slice data. this->SendCreatedWorldGeometry(); this->SendSlice(); this->SendTime(); // Adjust the stepper range of slice stepper according to geometry this->AdjustSliceStepperRange(); } void SliceNavigationController::SendCreatedWorldGeometry() { if (!m_BlockUpdate) { this->InvokeEvent(GeometrySendEvent(m_CreatedWorldGeometry, 0)); } } void SliceNavigationController::SendCreatedWorldGeometryUpdate() { if (!m_BlockUpdate) { this->InvokeEvent(GeometryUpdateEvent(m_CreatedWorldGeometry, m_Slice->GetPos())); } } void SliceNavigationController::SendSlice() { if (!m_BlockUpdate) { if (m_CreatedWorldGeometry.IsNotNull()) { this->InvokeEvent(GeometrySliceEvent(m_CreatedWorldGeometry, m_Slice->GetPos())); RenderingManager::GetInstance()->RequestUpdateAll(); } } } void SliceNavigationController::SendTime() { if (!m_BlockUpdate) { if (m_CreatedWorldGeometry.IsNotNull()) { this->InvokeEvent(GeometryTimeEvent(m_CreatedWorldGeometry, m_Time->GetPos())); RenderingManager::GetInstance()->RequestUpdateAll(); } } } void SliceNavigationController::SetGeometry(const itk::EventObject&) { // not implemented } void SliceNavigationController::SetGeometryTime(const itk::EventObject& geometryTimeEvent) { if (m_CreatedWorldGeometry.IsNull()) { return; } const auto* timeEvent = dynamic_cast(&geometryTimeEvent); assert(timeEvent != nullptr); TimeGeometry* timeGeometry = timeEvent->GetTimeGeometry(); assert(timeGeometry != nullptr); auto timeStep = (int)timeEvent->GetPos(); ScalarType timeInMS; timeInMS = timeGeometry->TimeStepToTimePoint(timeStep); timeStep = m_CreatedWorldGeometry->TimePointToTimeStep(timeInMS); this->GetTime()->SetPos(timeStep); } void SliceNavigationController::SetGeometrySlice(const itk::EventObject& geometrySliceEvent) { const auto* sliceEvent = dynamic_cast(&geometrySliceEvent); assert(sliceEvent != nullptr); this->GetSlice()->SetPos(sliceEvent->GetPos()); } void SliceNavigationController::SelectSliceByPoint(const Point3D& point) { if (m_CreatedWorldGeometry.IsNull()) { return; } int selectedSlice = -1; try { selectedSlice = SliceNavigationHelper::SelectSliceByPoint(m_CreatedWorldGeometry, point); } catch (const mitk::Exception& e) { MITK_ERROR << "Unable to select a slice by the given point " << point << "\n" << "Reason: " << e.GetDescription(); } if (-1 == selectedSlice) { return; } this->GetSlice()->SetPos(selectedSlice); this->SendCreatedWorldGeometryUpdate(); // send crosshair event SetCrosshairEvent.Send(point); } void SliceNavigationController::ReorientSlices(const Point3D& point, const Vector3D& normal) { if (m_CreatedWorldGeometry.IsNull()) { return; } PlaneOperation op(OpORIENT, point, normal); m_CreatedWorldGeometry->ExecuteOperation(&op); this->SendCreatedWorldGeometryUpdate(); } void SliceNavigationController::ReorientSlices(const Point3D& point, const Vector3D& axisVec0, const Vector3D& axisVec1) { if (m_CreatedWorldGeometry.IsNull()) { return; } PlaneOperation op(OpORIENT, point, axisVec0, axisVec1); m_CreatedWorldGeometry->ExecuteOperation(&op); this->SendCreatedWorldGeometryUpdate(); } const BaseGeometry* SliceNavigationController::GetCurrentGeometry3D() { if (m_CreatedWorldGeometry.IsNull()) { return nullptr; } return m_CreatedWorldGeometry->GetGeometryForTimeStep(this->GetTime()->GetPos()); } const PlaneGeometry* SliceNavigationController::GetCurrentPlaneGeometry() { const auto* slicedGeometry = dynamic_cast(this->GetCurrentGeometry3D()); if (nullptr == slicedGeometry) { return nullptr; } return slicedGeometry->GetPlaneGeometry(this->GetSlice()->GetPos()); } void SliceNavigationController::AdjustSliceStepperRange() { const auto* slicedGeometry = dynamic_cast(this->GetCurrentGeometry3D()); const Vector3D& direction = slicedGeometry->GetDirectionVector(); int c = 0; int i, k = 0; for (i = 0; i < 3; ++i) { if (fabs(direction[i]) < 0.000000001) { ++c; } else { k = i; } } if (c == 2) { ScalarType min = slicedGeometry->GetOrigin()[k]; ScalarType max = min + slicedGeometry->GetExtentInMM(k); m_Slice->SetRange(min, max); } else { m_Slice->InvalidateRange(); } } void SliceNavigationController::ExecuteOperation(Operation* operation) { // switch on type // - select best slice for a given point // - rotate created world geometry according to Operation->SomeInfo() if (!operation || m_CreatedWorldGeometry.IsNull()) { return; } switch (operation->GetOperationType()) { case OpMOVE: // should be a point operation { if (!m_SliceLocked) // do not move the cross position { // select a slice auto* po = dynamic_cast(operation); if (po && po->GetIndex() == -1) { this->SelectSliceByPoint(po->GetPoint()); } else if (po && po->GetIndex() != -1) // undo case because index != -1, index holds the old position of this slice { this->GetSlice()->SetPos(po->GetIndex()); } } break; } case OpRESTOREPLANEPOSITION: { m_CreatedWorldGeometry->ExecuteOperation(operation); this->SendCreatedWorldGeometryUpdate(); break; } case OpAPPLYTRANSFORMMATRIX: { m_CreatedWorldGeometry->ExecuteOperation(operation); this->SendCreatedWorldGeometryUpdate(); break; } default: { // do nothing break; } } } TimeStepType SliceNavigationController::GetSelectedTimeStep() const { return this->GetTime()->GetPos(); } TimePointType SliceNavigationController::GetSelectedTimePoint() const { auto timeStep = this->GetSelectedTimeStep(); if (m_CreatedWorldGeometry.IsNull()) { return 0.0; } if (!m_CreatedWorldGeometry->IsValidTimeStep(timeStep)) { mitkThrow() << "SliceNavigationController is in an invalid state. It has a time step" << "selected that is not covered by its time geometry. Selected time step: " << timeStep << "; TimeGeometry steps count: " << m_CreatedWorldGeometry->CountTimeSteps(); } return m_CreatedWorldGeometry->TimeStepToTimePoint(timeStep); } void SliceNavigationController::CreateWorldGeometry(bool top, bool frontside, bool rotated) { // initialize the viewplane SlicedGeometry3D::Pointer slicedWorldGeometry; BaseGeometry::ConstPointer currentGeometry; // get the BaseGeometry (ArbitraryTimeGeometry or ProportionalTimeGeometry) of the current time step auto currentTimeStep = this->GetTime()->GetPos(); if (m_InputWorldTimeGeometry->IsValidTimeStep(currentTimeStep)) { currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(currentTimeStep); } else { currentGeometry = m_InputWorldTimeGeometry->GetGeometryForTimeStep(0); } if (Original == m_ViewDirection) { slicedWorldGeometry = dynamic_cast( m_InputWorldTimeGeometry->GetGeometryForTimeStep(currentTimeStep).GetPointer()); if (slicedWorldGeometry.IsNull()) { slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes( currentGeometry, PlaneGeometry::None, top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); } } else { slicedWorldGeometry = SlicedGeometry3D::New(); slicedWorldGeometry->InitializePlanes( currentGeometry, ViewDirectionToPlaneOrientation(m_ViewDirection), top, frontside, rotated); slicedWorldGeometry->SetSliceNavigationController(this); } // reset the stepper m_Slice->SetSteps(slicedWorldGeometry->GetSlices()); m_Slice->SetPos(0); TimeStepType inputTimeSteps = m_InputWorldTimeGeometry->CountTimeSteps(); const TimeBounds& timeBounds = m_InputWorldTimeGeometry->GetTimeBounds(); m_Time->SetSteps(inputTimeSteps); m_Time->SetPos(0); m_Time->SetRange(timeBounds[0], timeBounds[1]); currentTimeStep = this->GetTime()->GetPos(); assert(m_InputWorldTimeGeometry->GetGeometryForTimeStep(currentTimeStep).IsNotNull()); // create new time geometry and initialize it according to the type of the 'm_InputWorldTimeGeometry' // the created world geometry will either have equidistant timesteps (ProportionalTimeGeometry) // or individual time bounds for each timestep (ArbitraryTimeGeometry) m_CreatedWorldGeometry = mitk::TimeGeometry::Pointer(); if (nullptr != dynamic_cast(m_InputWorldTimeGeometry.GetPointer())) { const TimePointType minimumTimePoint = m_InputWorldTimeGeometry->TimeStepToTimePoint(currentTimeStep); const TimePointType stepDuration = m_InputWorldTimeGeometry->TimeStepToTimePoint(currentTimeStep + 1) - minimumTimePoint; auto createdTimeGeometry = ProportionalTimeGeometry::New(); createdTimeGeometry->Initialize(slicedWorldGeometry, inputTimeSteps); createdTimeGeometry->SetFirstTimePoint(minimumTimePoint); createdTimeGeometry->SetStepDuration(stepDuration); m_CreatedWorldGeometry = createdTimeGeometry; } else { auto createdTimeGeometry = mitk::ArbitraryTimeGeometry::New(); createdTimeGeometry->ReserveSpaceForGeometries(inputTimeSteps); const BaseGeometry::Pointer clonedGeometry = slicedWorldGeometry->Clone(); for (TimeStepType i = 0; i < inputTimeSteps; ++i) { const auto bounds = m_InputWorldTimeGeometry->GetTimeBounds(i); createdTimeGeometry->AppendNewTimeStep(clonedGeometry, bounds[0], bounds[1]); } createdTimeGeometry->Update(); m_CreatedWorldGeometry = createdTimeGeometry; } } } // namespace mitk diff --git a/Modules/Core/src/Controllers/mitkStatusBar.cpp b/Modules/Core/src/Controllers/mitkStatusBar.cpp index adba4af8cd..66bae1893c 100755 --- a/Modules/Core/src/Controllers/mitkStatusBar.cpp +++ b/Modules/Core/src/Controllers/mitkStatusBar.cpp @@ -1,164 +1,189 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkStatusBar.h" #include #include namespace mitk { StatusBarImplementation *StatusBar::m_Implementation = nullptr; StatusBar *StatusBar::m_Instance = nullptr; /** * Display the text in the statusbar of the application */ void StatusBar::DisplayText(const char *t) { if (m_Implementation != nullptr) m_Implementation->DisplayText(t); } /** * Display the text in the statusbar of the application for ms seconds */ void StatusBar::DisplayText(const char *t, int ms) { if (m_Implementation != nullptr) m_Implementation->DisplayText(t, ms); } void StatusBar::DisplayErrorText(const char *t) { if (m_Implementation != nullptr) m_Implementation->DisplayErrorText(t); } void StatusBar::DisplayWarningText(const char *t) { if (m_Implementation != nullptr) m_Implementation->DisplayWarningText(t); } void StatusBar::DisplayWarningText(const char *t, int ms) { if (m_Implementation != nullptr) m_Implementation->DisplayWarningText(t, ms); } void StatusBar::DisplayGenericOutputText(const char *t) { if (m_Implementation != nullptr) m_Implementation->DisplayGenericOutputText(t); } void StatusBar::DisplayDebugText(const char *t) { if (m_Implementation != nullptr) m_Implementation->DisplayDebugText(t); } void StatusBar::DisplayGreyValueText(const char *t) { if (m_Implementation != nullptr) m_Implementation->DisplayGreyValueText(t); } - static void WriteCommonImageInfo( - std::ostringstream &stream, Point3D point, itk::Index<3> index, ScalarType time) + static void WriteCommonRendererInfo(std::ostringstream& stream, Point3D point, TimePointType time) + { + stream << "Position: <" << std::fixed << point[0] << ", " + << std::fixed << point[1] << ", " + << std::fixed << point[2] << "> mm; "; + + stream << "Time: " << time << " ms"; + } + + static void WriteCommonImageInfo(std::ostringstream& stream, Point3D point, itk::Index<3> index, TimePointType time) { stream << "Position: <" << std::fixed << point[0] << ", " << std::fixed << point[1] << ", " << std::fixed << point[2] << "> mm; "; stream << "Index: <" << index[0] << ", " << index[1] << ", " << index[2] << "> ; "; stream << "Time: " << time << " ms"; } - void StatusBar::DisplayImageInfo(Point3D point, itk::Index<3> index, ScalarType time, ScalarType pixelValue) + void StatusBar::DisplayRendererInfo(Point3D point, TimePointType time) + { + if (m_Implementation == nullptr) + return; + + std::ostringstream stream; + stream.imbue(std::locale::classic()); + stream.precision(2); + + WriteCommonRendererInfo(stream, point, time); + + m_Implementation->DisplayGreyValueText(stream.str().c_str()); + } + + void StatusBar::DisplayImageInfo(Point3D point, itk::Index<3> index, TimePointType time, ScalarType pixelValue) { if (m_Implementation == nullptr) return; std::ostringstream stream; stream.imbue(std::locale::classic()); stream.precision(2); WriteCommonImageInfo(stream, point, index, time); stream << "; Pixel value: "; if (fabs(pixelValue) > 1000000 || fabs(pixelValue) < 0.01) stream << std::scientific; stream << pixelValue; m_Implementation->DisplayGreyValueText(stream.str().c_str()); } - void StatusBar::DisplayImageInfo(Point3D point, itk::Index<3> index, ScalarType time, const char *pixelValue) + void StatusBar::DisplayImageInfo(Point3D point, itk::Index<3> index, TimePointType time, const char* pixelValue) { if (m_Implementation == nullptr) return; std::ostringstream stream; stream.imbue(std::locale::classic()); stream.precision(2); WriteCommonImageInfo(stream, point, index, time); stream << "; " << pixelValue; m_Implementation->DisplayGreyValueText(stream.str().c_str()); } void StatusBar::DisplayImageInfoInvalid() { if (m_Implementation != nullptr) m_Implementation->DisplayGreyValueText("No image information at this position!"); } + void StatusBar::Clear() { if (m_Implementation != nullptr) m_Implementation->Clear(); } + void StatusBar::SetSizeGripEnabled(bool enable) { if (m_Implementation != nullptr) { m_Implementation->SetSizeGripEnabled(enable); } } + /** * Get the instance of this StatusBar */ StatusBar *StatusBar::GetInstance() { if (m_Instance == nullptr) // if not set, then send a errormessage on OutputWindow { m_Instance = new StatusBar(); } return m_Instance; } /** * Set an instance of this; application must do this!See Header! */ void StatusBar::SetImplementation(StatusBarImplementation *implementation) { if (m_Implementation == implementation) { return; } m_Implementation = implementation; } StatusBar::StatusBar() {} StatusBar::~StatusBar() {} } // end namespace mitk diff --git a/Modules/Core/src/Interactions/mitkCrosshairPositionEvent.cpp b/Modules/Core/src/Interactions/mitkCrosshairPositionEvent.cpp deleted file mode 100644 index c85c6d970c..0000000000 --- a/Modules/Core/src/Interactions/mitkCrosshairPositionEvent.cpp +++ /dev/null @@ -1,16 +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 "mitkCrosshairPositionEvent.h" - -mitk::CrosshairPositionEvent::CrosshairPositionEvent(BaseRenderer *sender) : InteractionEvent(sender) -{ -} diff --git a/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp b/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp index 673085af97..c42172fe75 100644 --- a/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp +++ b/Modules/Core/src/Interactions/mitkDisplayActionEventBroadcast.cpp @@ -1,917 +1,807 @@ /*============================================================================ 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 "mitkDisplayActionEventBroadcast.h" // us #include "usGetModuleContext.h" #include "usModuleContext.h" // mitk core module -#include #include #include -#include #include #include #include -#include -#include #include -#include #include mitk::DisplayActionEventBroadcast::DisplayActionEventBroadcast() : m_AlwaysReact(false) , m_AutoRepeat(false) , m_IndexToSliceModifier(4) , m_InvertScrollDirection(false) , m_InvertZoomDirection(false) , m_ZoomFactor(2) , m_InvertMoveDirection(false) , m_InvertLevelWindowDirection(false) , m_LinkPlanes(true) { m_StartCoordinateInMM.Fill(0); m_LastDisplayCoordinate.Fill(0); - m_LastCoordinateInMM.Fill(0); m_CurrentDisplayCoordinate.Fill(0); // register the broadcast class (itself) as an interaction event observer via micro services us::ServiceProperties props; props["name"] = std::string("DisplayActionEventBroadcast"); m_ServiceRegistration = us::GetModuleContext()->RegisterService(this, props); } mitk::DisplayActionEventBroadcast::~DisplayActionEventBroadcast() { m_ServiceRegistration.Unregister(); } void mitk::DisplayActionEventBroadcast::Notify(InteractionEvent* interactionEvent, bool isHandled) { // the event is passed to the state machine interface to be handled if (!isHandled || m_AlwaysReact) { HandleEvent(interactionEvent, nullptr); } } void mitk::DisplayActionEventBroadcast::ConnectActionsAndFunctions() { CONNECT_CONDITION("check_position_event", CheckPositionEvent); CONNECT_CONDITION("check_can_rotate", CheckRotationPossible); CONNECT_CONDITION("check_can_swivel", CheckSwivelPossible); CONNECT_FUNCTION("init", Init); CONNECT_FUNCTION("move", Move); CONNECT_FUNCTION("zoom", Zoom); CONNECT_FUNCTION("scroll", Scroll); CONNECT_FUNCTION("ScrollOneUp", ScrollOneUp); CONNECT_FUNCTION("ScrollOneDown", ScrollOneDown); CONNECT_FUNCTION("levelWindow", AdjustLevelWindow); CONNECT_FUNCTION("setCrosshair", SetCrosshair); - CONNECT_FUNCTION("updateStatusbar", UpdateStatusbar) - CONNECT_FUNCTION("startRotation", StartRotation); CONNECT_FUNCTION("endRotation", EndRotation); CONNECT_FUNCTION("rotate", Rotate); CONNECT_FUNCTION("swivel", Swivel); CONNECT_FUNCTION("IncreaseTimeStep", IncreaseTimeStep); CONNECT_FUNCTION("DecreaseTimeStep", DecreaseTimeStep); } void mitk::DisplayActionEventBroadcast::ConfigurationChanged() { PropertyList::Pointer properties = GetAttributes(); // alwaysReact std::string strAlwaysReact = ""; m_AlwaysReact = false; if (properties->GetStringProperty("alwaysReact", strAlwaysReact)) { if (strAlwaysReact == "true") { m_AlwaysReact = true; } } // auto repeat std::string strAutoRepeat = ""; m_AutoRepeat = false; if (properties->GetStringProperty("autoRepeat", strAutoRepeat)) { if (strAutoRepeat == "true") { m_AutoRepeat = true; } } // pixel movement for scrolling one slice std::string strPixelPerSlice = ""; m_IndexToSliceModifier = 4; if (properties->GetStringProperty("pixelPerSlice", strPixelPerSlice)) { m_IndexToSliceModifier = atoi(strPixelPerSlice.c_str()); } // scroll direction if (!properties->GetStringProperty("scrollDirection", m_ScrollDirection)) { m_ScrollDirection = "updown"; } m_InvertScrollDirection = GetBoolProperty(properties, "invertScrollDirection", false); // zoom direction if (!properties->GetStringProperty("zoomDirection", m_ZoomDirection)) { m_ZoomDirection = "updown"; } m_InvertZoomDirection = GetBoolProperty(properties, "invertZoomDirection", false); m_InvertMoveDirection = GetBoolProperty(properties, "invertMoveDirection", false); if (!properties->GetStringProperty("levelWindowDirection", m_LevelDirection)) { m_LevelDirection = "leftright"; } m_InvertLevelWindowDirection = GetBoolProperty(properties, "invertLevelWindowDirection", false); // coupled rotation std::string strCoupled = ""; m_LinkPlanes = false; if (properties->GetStringProperty("coupled", strCoupled)) { if (strCoupled == "true") { m_LinkPlanes = true; } } // zoom factor std::string strZoomFactor = ""; properties->GetStringProperty("zoomFactor", strZoomFactor); m_ZoomFactor = .05; if (atoi(strZoomFactor.c_str()) > 0) { m_ZoomFactor = 1.0 + (atoi(strZoomFactor.c_str()) / 100.0); } } bool mitk::DisplayActionEventBroadcast::FilterEvents(InteractionEvent* interactionEvent, DataNode * /*dataNode*/) { BaseRenderer* sendingRenderer = interactionEvent->GetSender(); if (nullptr == sendingRenderer) { return false; } if (BaseRenderer::Standard3D == sendingRenderer->GetMapperID()) { return false; } return true; } bool mitk::DisplayActionEventBroadcast::CheckPositionEvent(const InteractionEvent *interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } return true; } bool mitk::DisplayActionEventBroadcast::CheckRotationPossible(const InteractionEvent *interactionEvent) { // Decide between moving and rotation slices. /* Detailed logic: 1. Find the SliceNavigationController that has sent the event: this one defines our rendering plane and will NOT be rotated. Needs not even be counted or checked. 2. Inspect every other SliceNavigationController - calculate the line intersection of this SliceNavigationController's plane with our rendering plane - if there is NO intersection, ignore and continue - IF there is an intersection - check the mouse cursor's distance from that line. 0. if the line is NOT near the cursor, remember the plane as "one of the other planes" (which can be rotated in "locked" mode) 1. on first line near the cursor, just remember this intersection line as THE other plane that we want to rotate 2. on every consecutive line near the cursor, check if the line is geometrically identical to the line that we want to rotate - if yes, we just push this line to the "other" lines and rotate it along - if no, then we have a situation where the mouse is near two other lines (e.g. crossing point) and don't want to rotate */ const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } BaseRenderer* renderer = positionEvent->GetSender(); if (nullptr == renderer) { return false; } const PlaneGeometry* rendererWorldPlaneGeometry = renderer->GetCurrentWorldPlaneGeometry(); if (nullptr == rendererWorldPlaneGeometry) { return false; } Point3D position = positionEvent->GetPositionInWorld(); const auto spacing = rendererWorldPlaneGeometry->GetSpacing(); const PlaneGeometry *geometryToBeRotated = nullptr; // this one is under the mouse cursor const PlaneGeometry *anyOtherGeometry = nullptr; // this is also visible (for calculation of intersection ONLY) Line3D intersectionLineWithGeometryToBeRotated; bool hitMultipleLines(false); m_SNCsToBeRotated.clear(); const ScalarType threshholdDistancePixels = 12.0; auto allRenderWindows = RenderingManager::GetInstance()->GetAllRegisteredRenderWindows(); for (auto renderWindow : allRenderWindows) { SliceNavigationController* snc = BaseRenderer::GetInstance(renderWindow)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::Standard3D == BaseRenderer::GetInstance(renderWindow)->GetMapperID()) { continue; } const PlaneGeometry* rendererPlaneGeometry = snc->GetCurrentPlaneGeometry(); if (nullptr == rendererPlaneGeometry) { continue; // ignore, we don't see a plane } // check if there is an intersection between rendered / clicked geometry and the one being analyzed Line3D intersectionLine; if (!rendererWorldPlaneGeometry->IntersectionLine(rendererPlaneGeometry, intersectionLine)) { continue; // we ignore this plane, it's parallel to our plane } // check distance from intersection line const double distanceFromIntersectionLine = intersectionLine.Distance(position) / spacing[snc->GetDefaultViewDirection()]; // far away line, only remember for linked rotation if necessary if (distanceFromIntersectionLine > threshholdDistancePixels) { // we just take the last one, so overwrite each iteration (we just need some crossing point) // TODO what about multiple crossings? NOW we have undefined behavior / random crossing point is used anyOtherGeometry = rendererPlaneGeometry; if (m_LinkPlanes) { // if planes are linked, apply rotation to all planes m_SNCsToBeRotated.push_back(snc); } } else // close to cursor { if (nullptr == geometryToBeRotated) // first one close to the cursor { geometryToBeRotated = rendererPlaneGeometry; intersectionLineWithGeometryToBeRotated = intersectionLine; m_SNCsToBeRotated.push_back(snc); } else { // compare to the line defined by geometryToBeRotated: if identical, just rotate this otherRenderersRenderPlane // together with the primary one // if different, DON'T rotate if (intersectionLine.IsParallel(intersectionLineWithGeometryToBeRotated) && intersectionLine.Distance(intersectionLineWithGeometryToBeRotated.GetPoint1()) < eps) { m_SNCsToBeRotated.push_back(snc); } else { hitMultipleLines = true; } } } } bool moveSlices(true); if (geometryToBeRotated && anyOtherGeometry && rendererWorldPlaneGeometry && !hitMultipleLines) { // assure all three are valid, so calculation of center of rotation can be done moveSlices = false; } // question in state machine is: "rotate?" if (moveSlices) // i.e. NOT rotate { return false; } else { // we have enough information for rotation // remember where the last cursor position ON THE LINE has been observed m_LastCursorPosition = intersectionLineWithGeometryToBeRotated.Project(position); // find center of rotation by intersection with any of the OTHER lines if (anyOtherGeometry->IntersectionPoint(intersectionLineWithGeometryToBeRotated, m_CenterOfRotation)) { return true; } else { return false; } } return false; } bool mitk::DisplayActionEventBroadcast::CheckSwivelPossible(const InteractionEvent *interactionEvent) { // Decide between moving and rotation: if we're close to the crossing // point of the planes, moving mode is entered, otherwise // rotation/swivel mode const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return false; } BaseRenderer* renderer = positionEvent->GetSender(); if (nullptr == renderer) { return false; } const Point3D& position = positionEvent->GetPositionInWorld(); m_SNCsToBeRotated.clear(); const PlaneGeometry* clickedGeometry(nullptr); const PlaneGeometry* otherGeometry1(nullptr); const PlaneGeometry* otherGeometry2(nullptr); const ScalarType threshholdDistancePixels = 6.0; auto allRenderWindows = RenderingManager::GetInstance()->GetAllRegisteredRenderWindows(); for (auto renderWindow : allRenderWindows) { SliceNavigationController* snc = BaseRenderer::GetInstance(renderWindow)->GetSliceNavigationController(); // If the mouse cursor is in 3D Renderwindow, do not check for intersecting planes. if (BaseRenderer::Standard3D == BaseRenderer::GetInstance(renderWindow)->GetMapperID()) { continue; } const PlaneGeometry* rendererPlaneGeometry = snc->GetCurrentPlaneGeometry(); if (nullptr == rendererPlaneGeometry) { continue; // ignore, we don't see a plane } if (snc == renderer->GetSliceNavigationController()) { clickedGeometry = rendererPlaneGeometry; m_SNCsToBeRotated.push_back(snc); } else { if (nullptr == otherGeometry1) { otherGeometry1 = rendererPlaneGeometry; } else { otherGeometry2 = rendererPlaneGeometry; } if (m_LinkPlanes) { // if planes are linked, apply rotation to all planes m_SNCsToBeRotated.push_back(snc); } } } Line3D line; Point3D point; if ((nullptr != clickedGeometry) && (nullptr != otherGeometry1) && (nullptr != otherGeometry2) && clickedGeometry->IntersectionLine(otherGeometry1, line) && otherGeometry2->IntersectionPoint(line, point)) { m_CenterOfRotation = point; if (m_CenterOfRotation.EuclideanDistanceTo(position) < threshholdDistancePixels) { return false; } else { m_ReferenceCursor = positionEvent->GetPointerPositionOnScreen(); // Get main axes of rotation plane and store it for rotation step m_RotationPlaneNormal = clickedGeometry->GetNormal(); ScalarType xVector[] = { 1.0, 0.0, 0.0 }; ScalarType yVector[] = { 0.0, 1.0, 0.0 }; clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(xVector), m_RotationPlaneXVector); clickedGeometry->BaseGeometry::IndexToWorld(Vector3D(yVector), m_RotationPlaneYVector); m_RotationPlaneNormal.Normalize(); m_RotationPlaneXVector.Normalize(); m_RotationPlaneYVector.Normalize(); m_PreviousRotationAxis.Fill(0.0); m_PreviousRotationAxis[2] = 1.0; m_PreviousRotationAngle = 0.0; return true; } } else { return false; } return false; } void mitk::DisplayActionEventBroadcast::Init(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); m_CurrentDisplayCoordinate = m_LastDisplayCoordinate; positionEvent->GetSender()->DisplayToPlane(m_LastDisplayCoordinate, m_StartCoordinateInMM); - m_LastCoordinateInMM = m_StartCoordinateInMM; } void mitk::DisplayActionEventBroadcast::Move(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } BaseRenderer* sender = interactionEvent->GetSender(); Vector2D moveVector = m_LastDisplayCoordinate - positionEvent->GetPointerPositionOnScreen(); if (m_InvertMoveDirection) { moveVector *= -1.0; } moveVector *= sender->GetScaleFactorMMPerDisplayUnit(); // #TODO: put here? // store new display coordinate m_LastDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate move event with computed geometry values InvokeEvent(DisplayMoveEvent(interactionEvent, moveVector)); } void mitk::DisplayActionEventBroadcast::SetCrosshair(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } Point3D position = positionEvent->GetPositionInWorld(); // propagate set crosshair event with computed geometry values InvokeEvent(DisplaySetCrosshairEvent(interactionEvent, position)); } void mitk::DisplayActionEventBroadcast::Zoom(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } float factor = 1.0; float distance = 0; if (m_ZoomDirection == "updown") { distance = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; } else { distance = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; } if (m_InvertZoomDirection) { distance *= -1.0; } // set zooming speed if (distance < 0.0) { factor = 1.0 / m_ZoomFactor; } else if (distance > 0.0) { factor = 1.0 * m_ZoomFactor; } // store new display coordinates m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate zoom event with computed geometry values InvokeEvent(DisplayZoomEvent(interactionEvent, factor, m_StartCoordinateInMM)); } void mitk::DisplayActionEventBroadcast::Scroll(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } int sliceDelta = 0; // scroll direction if (m_ScrollDirection == "updown") { sliceDelta = static_cast(m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]); } else { sliceDelta = static_cast(m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]); } if (m_InvertScrollDirection) { sliceDelta *= -1; } // set how many pixels the mouse has to be moved to scroll one slice // if the mouse has been moved less than 'm_IndexToSliceModifier', pixels slice ONE slice only if (sliceDelta > 0 && sliceDelta < m_IndexToSliceModifier) { sliceDelta = m_IndexToSliceModifier; } else if (sliceDelta < 0 && sliceDelta > -m_IndexToSliceModifier) { sliceDelta = -m_IndexToSliceModifier; } sliceDelta /= m_IndexToSliceModifier; // store new display coordinates m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate scroll event with computed geometry values InvokeEvent(DisplayScrollEvent(interactionEvent, sliceDelta, m_AutoRepeat)); } void mitk::DisplayActionEventBroadcast::ScrollOneUp(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { int sliceDelta = 1; if (m_InvertScrollDirection) { sliceDelta = -1; } // propagate scroll event with a single slice delta (increase) InvokeEvent(DisplayScrollEvent(interactionEvent, sliceDelta, m_AutoRepeat)); } void mitk::DisplayActionEventBroadcast::ScrollOneDown(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { int sliceDelta = -1; if (m_InvertScrollDirection) { sliceDelta = 1; } // propagate scroll event with a single slice delta (decrease) InvokeEvent(DisplayScrollEvent(interactionEvent, sliceDelta, m_AutoRepeat)); } void mitk::DisplayActionEventBroadcast::AdjustLevelWindow(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } ScalarType level; ScalarType window; if (m_LevelDirection == "leftright") { level = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; window = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; } else { level = m_CurrentDisplayCoordinate[1] - m_LastDisplayCoordinate[1]; window = m_CurrentDisplayCoordinate[0] - m_LastDisplayCoordinate[0]; } if (m_InvertLevelWindowDirection) { level *= -1; window *= -1; } level *= static_cast(2); window *= static_cast(2); // store new display coordinates m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); // propagate set level window event with the level and window delta InvokeEvent(DisplaySetLevelWindowEvent(interactionEvent, level, window)); } void mitk::DisplayActionEventBroadcast::StartRotation(StateMachineAction* /*stateMachineAction*/, InteractionEvent* /*interactionEvent*/) { SetMouseCursor(rotate_cursor_xpm, 0, 0); } void mitk::DisplayActionEventBroadcast::EndRotation(StateMachineAction* /*stateMachineAction*/, InteractionEvent* /*interactionEvent*/) { ResetMouseCursor(); } void mitk::DisplayActionEventBroadcast::Rotate(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } Point3D position = positionEvent->GetPositionInWorld(); Vector3D toProjected = m_LastCursorPosition - m_CenterOfRotation; Vector3D toCursor = position - m_CenterOfRotation; // cross product: | A x B | = |A| * |B| * sin(angle) Vector3D axisOfRotation; vnl_vector_fixed vnlDirection = vnl_cross_3d(toCursor.GetVnlVector(), toProjected.GetVnlVector()); axisOfRotation.SetVnlVector(vnlDirection.as_ref()); // scalar product: A * B = |A| * |B| * cos(angle) // tan = sin / cos ScalarType angle = -atan2((double)(axisOfRotation.GetNorm()), (double)(toCursor * toProjected)); angle *= 180.0 / vnl_math::pi; m_LastCursorPosition = position; // create RotationOperation and apply to all SNCs that should be rotated RotationOperation rotationOperation(OpROTATE, m_CenterOfRotation, axisOfRotation, angle); // iterate the OTHER slice navigation controllers for (auto iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { TimeGeometry* timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (nullptr == timeGeometry) { continue; } timeGeometry->ExecuteOperation(&rotationOperation); (*iter)->SendCreatedWorldGeometryUpdate(); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::DisplayActionEventBroadcast::Swivel(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) { const auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } // Determine relative mouse movement projected onto world space Point2D position = positionEvent->GetPointerPositionOnScreen(); Vector2D relativeCursor = position - m_ReferenceCursor; Vector3D relativeCursorAxis = m_RotationPlaneXVector * relativeCursor[0] + m_RotationPlaneYVector * relativeCursor[1]; // Determine rotation axis (perpendicular to rotation plane and cursor movement) Vector3D rotationAxis = itk::CrossProduct(m_RotationPlaneNormal, relativeCursorAxis); ScalarType rotationAngle = relativeCursor.GetNorm() / 2.0; // Restore the initial plane pose by undoing the previous rotation operation RotationOperation op(OpROTATE, m_CenterOfRotation, m_PreviousRotationAxis, -m_PreviousRotationAngle); SNCVector::iterator iter; for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { TimeGeometry* timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (nullptr == timeGeometry) { continue; } timeGeometry->ExecuteOperation(&op); (*iter)->SendCreatedWorldGeometryUpdate(); } } // Apply new rotation operation to all relevant SNCs RotationOperation op2(OpROTATE, m_CenterOfRotation, rotationAxis, rotationAngle); for (iter = m_SNCsToBeRotated.begin(); iter != m_SNCsToBeRotated.end(); ++iter) { if (!(*iter)->GetSliceRotationLocked()) { // Retrieve the TimeGeometry of this SliceNavigationController TimeGeometry *timeGeometry = (*iter)->GetCreatedWorldGeometry(); if (nullptr == timeGeometry) { continue; } // Execute the new rotation timeGeometry->ExecuteOperation(&op2); // Notify listeners (*iter)->SendCreatedWorldGeometryUpdate(); } } m_PreviousRotationAxis = rotationAxis; m_PreviousRotationAngle = rotationAngle; RenderingManager::GetInstance()->RequestUpdateAll(); return; } void mitk::DisplayActionEventBroadcast::IncreaseTimeStep(StateMachineAction*, InteractionEvent*) { auto sliceNaviController = RenderingManager::GetInstance()->GetTimeNavigationController(); auto stepper = sliceNaviController->GetTime(); stepper->SetAutoRepeat(true); stepper->Next(); } void mitk::DisplayActionEventBroadcast::DecreaseTimeStep(StateMachineAction*, InteractionEvent*) { auto sliceNaviController = RenderingManager::GetInstance()->GetTimeNavigationController(); auto stepper = sliceNaviController->GetTime(); stepper->SetAutoRepeat(true); stepper->Previous(); } -void mitk::DisplayActionEventBroadcast::UpdateStatusbar(StateMachineAction* /*stateMachineAction*/, InteractionEvent* interactionEvent) -{ - const auto* positionEvent = dynamic_cast(interactionEvent); - if (nullptr == positionEvent) - { - return; - } - - BaseRenderer::Pointer renderer = positionEvent->GetSender(); - - TNodePredicateDataType::Pointer isImageData = TNodePredicateDataType::New(); - DataStorage::SetOfObjects::ConstPointer nodes = renderer->GetDataStorage()->GetSubset(isImageData).GetPointer(); - if (nodes.IsNull()) - { - return; - } - - // A rendering update is required before getting the pointer position. - // This is required when scrolling through slices of an image using the mouse wheel. - renderer->ForceImmediateUpdate(); - Point3D worldposition; - renderer->DisplayToWorld(positionEvent->GetPointerPositionOnScreen(), worldposition); - auto globalCurrentTimePoint = renderer->GetTime(); - - Image::Pointer image3D; - DataNode::Pointer node; - DataNode::Pointer topSourceNode; - - int component = 0; - - node = FindTopmostVisibleNode(nodes, worldposition, globalCurrentTimePoint, renderer); - if (node.IsNull()) - { - return; - } - - bool isBinary(false); - node->GetBoolProperty("binary", isBinary); - if (isBinary) - { - DataStorage::SetOfObjects::ConstPointer sourcenodes = renderer->GetDataStorage()->GetSources(node, nullptr, true); - if (!sourcenodes->empty()) - { - topSourceNode = FindTopmostVisibleNode(nodes, worldposition, globalCurrentTimePoint, renderer); - } - if (topSourceNode.IsNotNull()) - { - image3D = dynamic_cast(topSourceNode->GetData()); - topSourceNode->GetIntProperty("Image.Displayed Component", component); - } - else - { - image3D = dynamic_cast(node->GetData()); - node->GetIntProperty("Image.Displayed Component", component); - } - } - else - { - image3D = dynamic_cast(node->GetData()); - node->GetIntProperty("Image.Displayed Component", component); - } - - // get the position and pixel value from the image and build up status bar text - auto statusBar = StatusBar::GetInstance(); - if (image3D.IsNotNull() && statusBar != nullptr) - { - itk::Index<3> p; - image3D->GetGeometry()->WorldToIndex(worldposition, p); - - auto pixelType = image3D->GetChannelDescriptor().GetPixelType().GetPixelType(); - if (pixelType == itk::IOPixelEnum::RGB || pixelType == itk::IOPixelEnum::RGBA) - { - std::string pixelValue = "Pixel RGB(A) value: "; - pixelValue.append(ConvertCompositePixelValueToString(image3D, p)); - statusBar->DisplayImageInfo(worldposition, p, renderer->GetTime(), pixelValue.c_str()); - } - else if (pixelType == itk::IOPixelEnum::DIFFUSIONTENSOR3D || pixelType == itk::IOPixelEnum::SYMMETRICSECONDRANKTENSOR) - { - std::string pixelValue = "See ODF Details view. "; - statusBar->DisplayImageInfo(worldposition, p, renderer->GetTime(), pixelValue.c_str()); - } - else - { - ScalarType pixelValue; - mitkPixelTypeMultiplex5( - FastSinglePixelAccess, - image3D->GetChannelDescriptor().GetPixelType(), - image3D, - image3D->GetVolumeData(renderer->GetTimeStep()), - p, - pixelValue, - component); - statusBar->DisplayImageInfo(worldposition, p, renderer->GetTime(), pixelValue); - } - } - else - { - statusBar->DisplayImageInfoInvalid(); - } -} - bool mitk::DisplayActionEventBroadcast::GetBoolProperty(PropertyList::Pointer propertyList, const char* propertyName, bool defaultValue) { std::string valueAsString; if (!propertyList->GetStringProperty(propertyName, valueAsString)) { return defaultValue; } else { if (valueAsString == "true") { return true; } else { return false; } } } diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 6612d48157..3a01c4ee36 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,1160 +1,1170 @@ /*============================================================================ 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 template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { 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(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLayer(0), m_activeLayerInvalid(false), m_ExteriorLabel(nullptr) { // Iniitlaize Background Label mitk::Color color; color.Set(0, 0, 0); m_ExteriorLabel = mitk::Label::New(); m_ExteriorLabel->SetColor(color); m_ExteriorLabel->SetName("Exterior"); m_ExteriorLabel->SetOpacity(0.0); m_ExteriorLabel->SetLocked(false); m_ExteriorLabel->SetValue(0); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false), m_ExteriorLabel(other.GetExteriorLabel()->Clone()) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); lsClone->AddObserver(itk::ModifiedEvent(), command); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::SetExteriorLabel(mitk::Label *label) { m_ExteriorLabel = label; } mitk::Label *mitk::LabelSetImage::GetExteriorLabel() { return m_ExteriorLabel; } const mitk::Label *mitk::LabelSetImage::GetExteriorLabel() const { return m_ExteriorLabel; } 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); } // 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() { 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]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->Modified(); } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer labelSet) { 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); } unsigned int newLabelSetId = this->AddLayer(newImage, labelSet); return newLabelSetId; } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer labelSet) { 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->AddLabel(GetExteriorLabel()); ls->SetActiveLabel(0 /*Exterior Label*/); } ls->SetLayer(newLabelSetId); // Add exterior Label to label set // mitk::Label::Pointer exteriorLabel = CreateExteriorLabel(); // 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); // 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); SetActiveLayer(newLabelSetId); // MITK_INFO << GetActiveLayer(); this->Modified(); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet::Pointer labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } if (layerIdx < m_LabelSetContainer.size()) { m_LabelSetContainer[layerIdx] = labelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->AddLabel(GetExteriorLabel()); defaultLabelSet->SetActiveLabel(0 /*Exterior Label*/); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); m_LabelSetContainer.push_back(defaultLabelSet); } m_LabelSetContainer.push_back(labelSet); } } 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 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 AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->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); } 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) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::RemoveLabel(PixelType pixelValue, unsigned int layer) { this->GetLabelSet(layer)->RemoveLabel(pixelValue); this->EraseLabel(pixelValue); } void mitk::LabelSetImage::RemoveLabels(std::vector& VectorOfLabelPixelValues, unsigned int layer) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->RemoveLabel(VectorOfLabelPixelValues[idx], layer); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue) { try { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_1(this, EraseLabelProcessing, 4, pixelValue); } else { AccessByItk_1(this, EraseLabelProcessing, pixelValue); } } catch (const itk::ExceptionObject& e) { mitkThrow() << e.GetDescription(); } Modified(); } void mitk::LabelSetImage::EraseLabels(std::vector& VectorOfLabelPixelValues) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { this->EraseLabel(VectorOfLabelPixelValues[idx]); } } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetActiveLabel(); } const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) 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::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } 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; } 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) { 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()); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); 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); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); 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 (...) { mitkThrow() << "Could not intialize by provided labeled image."; } 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()); targetIter.Set(sourceValue); if (!this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->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(); } ++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(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != 0) && (forceOverwrite || !this->GetLabel(targetValue)->GetLocked())) // skip exterior and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { // for now, we just retrieve the voxel in the middle typedef itk::ImageRegionConstIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); std::vector indexVector; while (!iter.IsAtEnd()) { // TODO fix comparison warning more effective if (iter.Get() == pixelValue) { indexVector.push_back(iter.GetIndex()); } ++iter; } mitk::Point3D pos; pos.Fill(0.0); if (!indexVector.empty()) { typename itk::ImageRegionConstIteratorWithIndex::IndexType centerIndex; centerIndex = indexVector.at(indexVector.size() / 2); if (centerIndex.GetIndexDimension() == 3) { pos[0] = centerIndex[0]; pos[1] = centerIndex[1]; pos[2] = centerIndex[2]; } else return; } 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); } 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; } } 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 ---"; // 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; } } 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); 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) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } /** 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, 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_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; } LabelTransferFunctor& operator=(const LabelTransferFunctor& other) { this->m_DestinationLabelSet = other.m_DestinationLabelSet; 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 { auto label = this->m_DestinationLabelSet->GetLabel(existingDestinationValue); if (nullptr == label || !label->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; 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 TransferLabelContent to allow the templating over different image dimensions in conjunction of AccessFixedPixelTypeByItk_n.*/ template void TransferLabelContentHelper(const itk::Image* itkSourceImage, mitk::Image* destinationImage, const mitk::LabelSet* destinationLabelSet, 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 TransferLabelContent; sourceImage and destinationImage seem to have no overlapping image region."; + } + typedef LabelTransferFunctor LabelTransferFunctorType; typedef itk::BinaryFunctorImageFilter FilterType; LabelTransferFunctorType transferFunctor(destinationLabelSet, 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::TransferLabelContent( const Image* sourceImage, Image* destinationImage, const mitk::LabelSet* destinationLabelSet, mitk::Label::PixelType sourceBackground, mitk::Label::PixelType destinationBackground, bool destinationBackgroundLocked, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye, const TimeStepType timeStep) { 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."; } if (nullptr == destinationLabelSet) { mitkThrow() << "Invalid call of TransferLabelContent; destinationLabelSet must not be null"; } Image::ConstPointer sourceImageAtTimeStep = SelectImageByTimeStep(sourceImage, timeStep); Image::Pointer destinationImageAtTimeStep = SelectImageByTimeStep(destinationImage, timeStep); if (nullptr == sourceImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage does not have the requested time step: " << timeStep; } if (nullptr == destinationImageAtTimeStep) { mitkThrow() << "Invalid call of TransferLabelContent; destinationImage does not have the requested time step: " << timeStep; } for (const auto& [sourceLabel, newDestinationLabel] : labelMapping) { if (nullptr == destinationLabelSet->GetLabel(newDestinationLabel)) { mitkThrow() << "Invalid call of TransferLabelContent. Defined destination label does not exist in destinationImage. newDestinationLabel: " << newDestinationLabel; } AccessFixedPixelTypeByItk_n(sourceImageAtTimeStep, TransferLabelContentHelper, (Label::PixelType), (destinationImageAtTimeStep, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, sourceLabel, newDestinationLabel, mergeStyle, overwriteStlye)); } destinationImage->Modified(); } void mitk::TransferLabelContent( const LabelSetImage* sourceImage, LabelSetImage* destinationImage, std::vector > labelMapping, MultiLabelSegmentation::MergeStyle mergeStyle, MultiLabelSegmentation::OverwriteStyle overwriteStlye, const TimeStepType timeStep) { if (nullptr == sourceImage) { mitkThrow() << "Invalid call of TransferLabelContent; sourceImage must not be null."; } const auto sourceBackground = sourceImage->GetExteriorLabel()->GetValue(); const auto destinationBackground = destinationImage->GetExteriorLabel()->GetValue(); const auto destinationBackgroundLocked = destinationImage->GetExteriorLabel()->GetLocked(); const auto destinationLabelSet = destinationImage->GetLabelSet(destinationImage->GetActiveLayer()); for (const auto& mappingElement : labelMapping) { if (!sourceImage->ExistLabel(mappingElement.first, sourceImage->GetActiveLayer())) { mitkThrow() << "Invalid call of TransferLabelContent. Defined source label does not exist in sourceImage. SourceLabel: " << mappingElement.first; } } TransferLabelContent(sourceImage, destinationImage, destinationLabelSet, sourceBackground, destinationBackground, destinationBackgroundLocked, labelMapping, mergeStyle, overwriteStlye, timeStep); } diff --git a/Modules/QtWidgets/include/QmitkRenderWindow.h b/Modules/QtWidgets/include/QmitkRenderWindow.h index 51f83d8b84..b92b71d859 100644 --- a/Modules/QtWidgets/include/QmitkRenderWindow.h +++ b/Modules/QtWidgets/include/QmitkRenderWindow.h @@ -1,154 +1,156 @@ /*============================================================================ 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 QMITKRENDERWINDOW_H #define QMITKRENDERWINDOW_H #include "mitkRenderWindowBase.h" #include "QmitkRenderWindowMenu.h" #include #include #include #include "mitkBaseRenderer.h" #include "mitkInteractionEventConst.h" class QDragEnterEvent; class QDropEvent; class QInputEvent; class QMouseEvent; /** * \ingroup QmitkModule * \brief MITK implementation of the QVTKWidget */ class MITKQTWIDGETS_EXPORT QmitkRenderWindow : public QVTKOpenGLNativeWidget, public mitk::RenderWindowBase { Q_OBJECT public: QmitkRenderWindow( QWidget *parent = nullptr, const QString &name = "unnamed renderwindow", mitk::VtkPropRenderer *renderer = nullptr); ~QmitkRenderWindow() override; /** * \brief Whether Qt events should be passed to parent (default: true) * * With introduction of the QVTKWidget the behaviour regarding Qt events changed. * QVTKWidget "accepts" Qt events like mouse clicks (i.e. set an "accepted" flag). * When this flag is set, Qt fininshed handling of this event -- otherwise it is * reached through to the widget's parent. * * This reaching through to the parent was implicitly required by QmitkMaterialWidget / QmitkMaterialShowCase. * * The default behaviour of QmitkRenderWindow is now to clear the "accepted" flag * of Qt events after they were handled by QVTKWidget. This way parents can also * handle events. * * If you don't want this behaviour, call SetResendQtEvents(true) on your render window. */ virtual void SetResendQtEvents(bool resend); // Set Layout Index to define the Layout Type void SetLayoutIndex(QmitkRenderWindowMenu::LayoutIndex layoutIndex); // Get Layout Index to define the Layout Type QmitkRenderWindowMenu::LayoutIndex GetLayoutIndex(); // MenuWidget need to update the Layout Design List when Layout had changed void UpdateLayoutDesignList(QmitkRenderWindowMenu::LayoutDesign layoutDesign); void UpdateCrosshairVisibility(bool); void UpdateCrosshairRotationMode(int); // Activate or Deactivate MenuWidget. void ActivateMenuWidget(bool state); bool GetActivateMenuWidgetFlag() { return m_MenuWidgetActivated; } // Get it from the QVTKWidget parent vtkRenderWindow *GetVtkRenderWindow() override { return this->renderWindow(); } vtkRenderWindowInteractor *GetVtkRenderWindowInteractor() override { return nullptr; } protected: // catch-all event handler bool event(QEvent *e) override; // overloaded move handler void moveEvent(QMoveEvent *event) override; // overloaded show handler void showEvent(QShowEvent *event) override; // overloaded enter handler void enterEvent(QEvent *) override; // overloaded leave handler void leaveEvent(QEvent *) override; // Overloaded resize handler, see decs in QVTKOpenGLWidget. // Basically, we have to ensure the VTK rendering is updated for each change in window size. void resizeGL(int w, int h) override; /// \brief Simply says we accept the event type. void dragEnterEvent(QDragEnterEvent *event) override; /// \brief If the dropped type is application/x-mitk-datanodes we process the request by converting to mitk::DataNode /// pointers and emitting the NodesDropped signal. void dropEvent(QDropEvent *event) override; Q_SIGNALS: void LayoutDesignChanged(QmitkRenderWindowMenu::LayoutDesign); void ResetView(); void CrosshairRotationModeChanged(int); void CrosshairVisibilityChanged(bool); void moved(); /// \brief Emits a signal to say that this window has had the following nodes dropped on it. void NodesDropped(QmitkRenderWindow *thisWindow, std::vector nodes); private Q_SLOTS: void DeferredHideMenu(); private: // Helper Functions to Convert Qt-Events to Mitk-Events mitk::Point2D GetMousePosition(QMouseEvent *me) const; mitk::Point2D GetMousePosition(QWheelEvent *we) const; mitk::InteractionEvent::MouseButtons GetEventButton(QMouseEvent *me) const; mitk::InteractionEvent::MouseButtons GetButtonState(QMouseEvent *me) const; mitk::InteractionEvent::ModifierKeys GetModifiers(QInputEvent *me) const; mitk::InteractionEvent::MouseButtons GetButtonState(QWheelEvent *we) const; std::string GetKeyLetter(QKeyEvent *ke) const; int GetDelta(QWheelEvent *we) const; + void UpdateStatusBar(mitk::Point2D pointerPositionOnScreen); + bool m_ResendQtEvents; QmitkRenderWindowMenu *m_MenuWidget; bool m_MenuWidgetActivated; QmitkRenderWindowMenu::LayoutIndex m_LayoutIndex; vtkSmartPointer m_InternalRenderWindow; }; #endif // QMITKRENDERWINDOW_H diff --git a/Modules/QtWidgets/include/QmitkStdMultiWidget.h b/Modules/QtWidgets/include/QmitkStdMultiWidget.h index 1ecd723bb7..0f45a0bcae 100644 --- a/Modules/QtWidgets/include/QmitkStdMultiWidget.h +++ b/Modules/QtWidgets/include/QmitkStdMultiWidget.h @@ -1,162 +1,151 @@ /*============================================================================ 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 QMITKSTDMULTIWIDGET_H #define QMITKSTDMULTIWIDGET_H // qt widgets module #include "MitkQtWidgetsExports.h" #include "QmitkAbstractMultiWidget.h" /** * @brief The 'QmitkStdMultiWidget' is a 'QmitkAbstractMultiWidget' that is used to display multiple render windows at once. * Render windows are predefined in a 2x2 design with 3 different 2D view planes and a 3D render window. */ class MITKQTWIDGETS_EXPORT QmitkStdMultiWidget : public QmitkAbstractMultiWidget { Q_OBJECT public: QmitkStdMultiWidget( QWidget *parent = nullptr, Qt::WindowFlags f = nullptr, const QString &name = "stdmulti"); ~QmitkStdMultiWidget() override; virtual void InitializeMultiWidget() override; virtual QmitkRenderWindow* GetRenderWindow(const QString& widgetName) const override; virtual QmitkRenderWindow* GetRenderWindow(const mitk::BaseRenderer::ViewDirection& viewDirection) const override; virtual void SetSelectedPosition(const mitk::Point3D& newPosition, const QString& widgetName) override; virtual const mitk::Point3D GetSelectedPosition(const QString& widgetName) const override; virtual void SetCrosshairVisibility(bool) override; virtual bool GetCrosshairVisibility() const override; void SetCrosshairGap(unsigned int gapSize) override; virtual void ResetCrosshair() override; virtual void SetWidgetPlaneMode(int mode) override; mitk::SliceNavigationController* GetTimeNavigationController(); void AddPlanesToDataStorage(); void RemovePlanesFromDataStorage(); - /** \brief Listener to the CrosshairPositionEvent - - Ensures the CrosshairPositionEvent is handled only once and at the end of the Qt-Event loop - */ - void HandleCrosshairPositionEvent(); - /** * @brief Convenience method to get a render window widget. * @param number of the widget (0-3) * @return The render window widget */ QmitkRenderWindow* GetRenderWindow(unsigned int number) const; QmitkRenderWindow* GetRenderWindow1() const; QmitkRenderWindow* GetRenderWindow2() const; QmitkRenderWindow* GetRenderWindow3() const; QmitkRenderWindow* GetRenderWindow4() const; /** * @brief Convenience method to get a widget plane. * @param number of the widget plane (1-3) * @return The widget plane as data node */ mitk::DataNode::Pointer GetWidgetPlane(unsigned int number) const; mitk::DataNode::Pointer GetWidgetPlane1() const; mitk::DataNode::Pointer GetWidgetPlane2() const; mitk::DataNode::Pointer GetWidgetPlane3() const; /** * @brief SetDecorationColor Set the color of the decoration of the 4 widgets. * * This is used to color the frame of the renderwindow and the corner annatation. * For the first 3 widgets, this color is a property of the helper object nodes * which contain the respective plane geometry. For widget 4, this is a member, * since there is no data node for this widget. */ void SetDecorationColor(unsigned int widgetNumber, mitk::Color color); /** * @brief GetDecorationColorForWidget Get the color for annotation, crosshair and rectangle. * @param widgetNumber Number of the renderwindow (0-3). * @return Color in mitk format. */ mitk::Color GetDecorationColor(unsigned int widgetNumber); public Q_SLOTS: // mouse events virtual void mousePressEvent(QMouseEvent*) override; virtual void moveEvent(QMoveEvent* e) override; virtual void wheelEvent(QWheelEvent* e) override; - /// Receives the signal from HandleCrosshairPositionEvent, executes the StatusBar update - void HandleCrosshairPositionEventDelayed(); - void Fit(); void AddDisplayPlaneSubTree(); void EnsureDisplayContainsPoint(mitk::BaseRenderer *renderer, const mitk::Point3D &p); void SetWidgetPlaneVisibility(const char *widgetName, bool visible, mitk::BaseRenderer *renderer = nullptr); void SetWidgetPlanesVisibility(bool visible, mitk::BaseRenderer *renderer = nullptr); Q_SIGNALS: void NotifyCrosshairVisibilityChanged(bool visible); void NotifyCrosshairRotationModeChanged(int mode); void WheelMoved(QWheelEvent *); void Moved(); private: virtual void SetLayoutImpl() override; virtual void SetInteractionSchemeImpl() override { } void CreateRenderWindowWidgets(); mitk::SliceNavigationController* m_TimeNavigationController; /** * @brief The 3 helper objects which contain the plane geometry. */ mitk::DataNode::Pointer m_PlaneNode1; mitk::DataNode::Pointer m_PlaneNode2; mitk::DataNode::Pointer m_PlaneNode3; /** * @brief m_ParentNodeForGeometryPlanes This helper object is added to the datastorage * and contains the 3 planes for displaying the image geometry (crosshair and 3D planes). */ mitk::DataNode::Pointer m_ParentNodeForGeometryPlanes; /** * @brief m_DecorationColorWidget4 color for annotation and rectangle of widget 4. * * For other widgets1-3, the color is a property of the respective data node. * There is no node for widget 4, hence, we need an extra member. */ mitk::Color m_DecorationColorWidget4; - bool m_PendingCrosshairPositionEvent; - }; #endif // QMITKSTDMULTIWIDGET_H diff --git a/Modules/QtWidgets/src/QmitkRenderWindow.cpp b/Modules/QtWidgets/src/QmitkRenderWindow.cpp index fc835a97cc..af81ae89ec 100644 --- a/Modules/QtWidgets/src/QmitkRenderWindow.cpp +++ b/Modules/QtWidgets/src/QmitkRenderWindow.cpp @@ -1,467 +1,492 @@ /*============================================================================ 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 "QmitkRenderWindow.h" #include "mitkInteractionKeyEvent.h" #include "mitkInternalEvent.h" #include "mitkMouseDoubleClickEvent.h" #include "mitkMouseMoveEvent.h" #include "mitkMousePressEvent.h" #include "mitkMouseReleaseEvent.h" #include "mitkMouseWheelEvent.h" +#include + #include #include #include #include #include #include #include #include #include #include #include "QmitkMimeTypes.h" #include "QmitkRenderWindowMenu.h" QmitkRenderWindow::QmitkRenderWindow(QWidget *parent, const QString &name, mitk::VtkPropRenderer *) : QVTKOpenGLNativeWidget(parent) , m_ResendQtEvents(true) , m_MenuWidget(nullptr) , m_MenuWidgetActivated(false) , m_LayoutIndex(QmitkRenderWindowMenu::LayoutIndex::AXIAL) { m_InternalRenderWindow = vtkSmartPointer::New(); m_InternalRenderWindow->SetMultiSamples(0); m_InternalRenderWindow->SetAlphaBitPlanes(0); setRenderWindow(m_InternalRenderWindow); Initialize(name.toStdString().c_str()); setFocusPolicy(Qt::StrongFocus); setMouseTracking(true); QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setSizePolicy(sizePolicy); } QmitkRenderWindow::~QmitkRenderWindow() { Destroy(); // Destroy mitkRenderWindowBase } void QmitkRenderWindow::SetResendQtEvents(bool resend) { m_ResendQtEvents = resend; } void QmitkRenderWindow::SetLayoutIndex(QmitkRenderWindowMenu::LayoutIndex layoutIndex) { m_LayoutIndex = layoutIndex; if (nullptr != m_MenuWidget) { m_MenuWidget->SetLayoutIndex(layoutIndex); } } QmitkRenderWindowMenu::LayoutIndex QmitkRenderWindow::GetLayoutIndex() { if (nullptr != m_MenuWidget) { return m_MenuWidget->GetLayoutIndex(); } else { return QmitkRenderWindowMenu::LayoutIndex::AXIAL; } } void QmitkRenderWindow::UpdateLayoutDesignList(QmitkRenderWindowMenu::LayoutDesign layoutDesign) { if (nullptr != m_MenuWidget) { m_MenuWidget->UpdateLayoutDesignList(layoutDesign); } } void QmitkRenderWindow::UpdateCrosshairVisibility(bool visible) { m_MenuWidget->UpdateCrosshairVisibility(visible); } void QmitkRenderWindow::UpdateCrosshairRotationMode(int mode) { m_MenuWidget->UpdateCrosshairRotationMode(mode); } void QmitkRenderWindow::ActivateMenuWidget(bool state) { if (nullptr == m_MenuWidget) { m_MenuWidget = new QmitkRenderWindowMenu(this, nullptr, m_Renderer); m_MenuWidget->SetLayoutIndex(m_LayoutIndex); } m_MenuWidgetActivated = state; if (m_MenuWidgetActivated) { connect(m_MenuWidget, &QmitkRenderWindowMenu::LayoutDesignChanged, this, &QmitkRenderWindow::LayoutDesignChanged); connect(m_MenuWidget, &QmitkRenderWindowMenu::ResetView, this, &QmitkRenderWindow::ResetView); connect(m_MenuWidget, &QmitkRenderWindowMenu::CrosshairVisibilityChanged, this, &QmitkRenderWindow::CrosshairVisibilityChanged); connect(m_MenuWidget, &QmitkRenderWindowMenu::CrosshairRotationModeChanged, this, &QmitkRenderWindow::CrosshairRotationModeChanged); } else { disconnect(m_MenuWidget, &QmitkRenderWindowMenu::LayoutDesignChanged, this, &QmitkRenderWindow::LayoutDesignChanged); disconnect(m_MenuWidget, &QmitkRenderWindowMenu::ResetView, this, &QmitkRenderWindow::ResetView); disconnect(m_MenuWidget, &QmitkRenderWindowMenu::CrosshairVisibilityChanged, this, &QmitkRenderWindow::CrosshairVisibilityChanged); disconnect(m_MenuWidget, &QmitkRenderWindowMenu::CrosshairRotationModeChanged, this, &QmitkRenderWindow::CrosshairRotationModeChanged); m_MenuWidget->hide(); } } void QmitkRenderWindow::moveEvent(QMoveEvent *event) { QVTKOpenGLNativeWidget::moveEvent(event); // after a move the overlays need to be positioned emit moved(); } void QmitkRenderWindow::showEvent(QShowEvent *event) { QVTKOpenGLNativeWidget::showEvent(event); // this singleshot is necessary to have the overlays positioned correctly after initial show // simple call of moved() is no use here!! QTimer::singleShot(0, this, SIGNAL(moved())); } bool QmitkRenderWindow::event(QEvent* e) { mitk::InteractionEvent::Pointer mitkEvent = nullptr; + mitk::Point2D mousePosition; + bool updateStatusBar = false; switch (e->type()) { case QEvent::MouseMove: { auto me = static_cast(e); - mitkEvent = mitk::MouseMoveEvent::New(m_Renderer, GetMousePosition(me), GetButtonState(me), GetModifiers(me)); + mousePosition = this->GetMousePosition(me); + mitkEvent = mitk::MouseMoveEvent::New(m_Renderer, mousePosition, GetButtonState(me), GetModifiers(me)); + updateStatusBar = true; break; } case QEvent::MouseButtonPress: { auto me = static_cast(e); mitkEvent = mitk::MousePressEvent::New( m_Renderer, GetMousePosition(me), GetButtonState(me), GetModifiers(me), GetEventButton(me)); break; } case QEvent::MouseButtonRelease: { auto me = static_cast(e); mitkEvent = mitk::MouseReleaseEvent::New( m_Renderer, GetMousePosition(me), GetButtonState(me), GetModifiers(me), GetEventButton(me)); break; } case QEvent::MouseButtonDblClick: { auto me = static_cast(e); mitkEvent = mitk::MouseDoubleClickEvent::New( m_Renderer, GetMousePosition(me), GetButtonState(me), GetModifiers(me), GetEventButton(me)); break; } case QEvent::Wheel: { auto we = static_cast(e); - mitkEvent = mitk::MouseWheelEvent::New( m_Renderer, GetMousePosition(we), GetButtonState(we), GetModifiers(we), GetDelta(we)); + mousePosition = this->GetMousePosition(we); + mitkEvent = mitk::MouseWheelEvent::New(m_Renderer, mousePosition, GetButtonState(we), GetModifiers(we), GetDelta(we)); + updateStatusBar = true; break; } case QEvent::KeyPress: { auto ke = static_cast(e); mitkEvent = mitk::InteractionKeyEvent::New(m_Renderer, GetKeyLetter(ke), GetModifiers(ke)); break; } case QEvent::Resize: { if (nullptr != m_MenuWidget) m_MenuWidget->MoveWidgetToCorrectPos(); } default: { break; } } if (mitkEvent != nullptr) { if (this->HandleEvent(mitkEvent.GetPointer())) { return m_ResendQtEvents ? false : true; } } + if (updateStatusBar) + { + this->UpdateStatusBar(mousePosition); + } + return QVTKOpenGLNativeWidget::event(e); } void QmitkRenderWindow::enterEvent(QEvent *e) { mitk::InternalEvent::Pointer internalEvent = mitk::InternalEvent::New(m_Renderer, nullptr, "EnterRenderWindow"); this->HandleEvent(internalEvent.GetPointer()); if (nullptr != m_MenuWidget) m_MenuWidget->ShowMenu(); QVTKOpenGLNativeWidget::enterEvent(e); } void QmitkRenderWindow::leaveEvent(QEvent *e) { + auto statusBar = mitk::StatusBar::GetInstance(); + statusBar->DisplayGreyValueText(""); + mitk::InternalEvent::Pointer internalEvent = mitk::InternalEvent::New(m_Renderer, nullptr, "LeaveRenderWindow"); this->HandleEvent(internalEvent.GetPointer()); if (nullptr != m_MenuWidget) m_MenuWidget->HideMenu(); QVTKOpenGLNativeWidget::leaveEvent(e); } void QmitkRenderWindow::resizeGL(int w, int h) { QVTKOpenGLNativeWidget::resizeGL(w, h); mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(renderWindow()); } void QmitkRenderWindow::dragEnterEvent(QDragEnterEvent *event) { if (event->mimeData()->hasFormat("application/x-mitk-datanodes")) { event->accept(); } } void QmitkRenderWindow::dropEvent(QDropEvent *event) { QList dataNodeList = QmitkMimeTypes::ToDataNodePtrList(event->mimeData()); if (!dataNodeList.empty()) { emit NodesDropped(this, dataNodeList.toVector().toStdVector()); } } void QmitkRenderWindow::DeferredHideMenu() { MITK_DEBUG << "QmitkRenderWindow::DeferredHideMenu"; if (nullptr != m_MenuWidget) { m_MenuWidget->HideMenu(); } } mitk::Point2D QmitkRenderWindow::GetMousePosition(QMouseEvent *me) const { mitk::Point2D point; point[0] = me->x(); // We need to convert the y component, as the display and vtk have other definitions for the y direction point[1] = m_Renderer->GetSizeY() - me->y(); return point; } mitk::Point2D QmitkRenderWindow::GetMousePosition(QWheelEvent *we) const { mitk::Point2D point; point[0] = we->x(); // We need to convert the y component, as the display and vtk have other definitions for the y direction point[1] = m_Renderer->GetSizeY() - we->y(); return point; } mitk::InteractionEvent::MouseButtons QmitkRenderWindow::GetEventButton(QMouseEvent *me) const { mitk::InteractionEvent::MouseButtons eventButton; switch (me->button()) { case Qt::LeftButton: eventButton = mitk::InteractionEvent::LeftMouseButton; break; case Qt::RightButton: eventButton = mitk::InteractionEvent::RightMouseButton; break; case Qt::MidButton: eventButton = mitk::InteractionEvent::MiddleMouseButton; break; default: eventButton = mitk::InteractionEvent::NoButton; break; } return eventButton; } mitk::InteractionEvent::MouseButtons QmitkRenderWindow::GetButtonState(QMouseEvent *me) const { mitk::InteractionEvent::MouseButtons buttonState = mitk::InteractionEvent::NoButton; if (me->buttons() & Qt::LeftButton) { buttonState = buttonState | mitk::InteractionEvent::LeftMouseButton; } if (me->buttons() & Qt::RightButton) { buttonState = buttonState | mitk::InteractionEvent::RightMouseButton; } if (me->buttons() & Qt::MidButton) { buttonState = buttonState | mitk::InteractionEvent::MiddleMouseButton; } return buttonState; } mitk::InteractionEvent::ModifierKeys QmitkRenderWindow::GetModifiers(QInputEvent *me) const { mitk::InteractionEvent::ModifierKeys modifiers = mitk::InteractionEvent::NoKey; if (me->modifiers() & Qt::ALT) { modifiers = modifiers | mitk::InteractionEvent::AltKey; } if (me->modifiers() & Qt::CTRL) { modifiers = modifiers | mitk::InteractionEvent::ControlKey; } if (me->modifiers() & Qt::SHIFT) { modifiers = modifiers | mitk::InteractionEvent::ShiftKey; } return modifiers; } mitk::InteractionEvent::MouseButtons QmitkRenderWindow::GetButtonState(QWheelEvent *we) const { mitk::InteractionEvent::MouseButtons buttonState = mitk::InteractionEvent::NoButton; if (we->buttons() & Qt::LeftButton) { buttonState = buttonState | mitk::InteractionEvent::LeftMouseButton; } if (we->buttons() & Qt::RightButton) { buttonState = buttonState | mitk::InteractionEvent::RightMouseButton; } if (we->buttons() & Qt::MidButton) { buttonState = buttonState | mitk::InteractionEvent::MiddleMouseButton; } return buttonState; } std::string QmitkRenderWindow::GetKeyLetter(QKeyEvent *ke) const { // Converting Qt Key Event to string element. std::string key = ""; int tkey = ke->key(); if (tkey < 128) { // standard ascii letter key = (char)toupper(tkey); } else { // special keys switch (tkey) { case Qt::Key_Return: key = mitk::InteractionEvent::KeyReturn; break; case Qt::Key_Enter: key = mitk::InteractionEvent::KeyEnter; break; case Qt::Key_Escape: key = mitk::InteractionEvent::KeyEsc; break; case Qt::Key_Delete: key = mitk::InteractionEvent::KeyDelete; break; case Qt::Key_Up: key = mitk::InteractionEvent::KeyArrowUp; break; case Qt::Key_Down: key = mitk::InteractionEvent::KeyArrowDown; break; case Qt::Key_Left: key = mitk::InteractionEvent::KeyArrowLeft; break; case Qt::Key_Right: key = mitk::InteractionEvent::KeyArrowRight; break; case Qt::Key_F1: key = mitk::InteractionEvent::KeyF1; break; case Qt::Key_F2: key = mitk::InteractionEvent::KeyF2; break; case Qt::Key_F3: key = mitk::InteractionEvent::KeyF3; break; case Qt::Key_F4: key = mitk::InteractionEvent::KeyF4; break; case Qt::Key_F5: key = mitk::InteractionEvent::KeyF5; break; case Qt::Key_F6: key = mitk::InteractionEvent::KeyF6; break; case Qt::Key_F7: key = mitk::InteractionEvent::KeyF7; break; case Qt::Key_F8: key = mitk::InteractionEvent::KeyF8; break; case Qt::Key_F9: key = mitk::InteractionEvent::KeyF9; break; case Qt::Key_F10: key = mitk::InteractionEvent::KeyF10; break; case Qt::Key_F11: key = mitk::InteractionEvent::KeyF11; break; case Qt::Key_F12: key = mitk::InteractionEvent::KeyF12; break; case Qt::Key_End: key = mitk::InteractionEvent::KeyEnd; break; case Qt::Key_Home: key = mitk::InteractionEvent::KeyPos1; break; case Qt::Key_Insert: key = mitk::InteractionEvent::KeyInsert; break; case Qt::Key_PageDown: key = mitk::InteractionEvent::KeyPageDown; break; case Qt::Key_PageUp: key = mitk::InteractionEvent::KeyPageUp; break; case Qt::Key_Space: key = mitk::InteractionEvent::KeySpace; break; } } return key; } int QmitkRenderWindow::GetDelta(QWheelEvent *we) const { return we->delta(); } + +void QmitkRenderWindow::UpdateStatusBar(mitk::Point2D pointerPositionOnScreen) +{ + mitk::Point3D worldPosition; + m_Renderer->ForceImmediateUpdate(); + m_Renderer->DisplayToWorld(pointerPositionOnScreen, worldPosition); + auto statusBar = mitk::StatusBar::GetInstance(); + statusBar->DisplayRendererInfo(worldPosition, m_Renderer->GetTime()); +} diff --git a/Modules/QtWidgets/src/QmitkStdMultiWidget.cpp b/Modules/QtWidgets/src/QmitkStdMultiWidget.cpp index 7a2973bfd3..3ea503ba4a 100644 --- a/Modules/QtWidgets/src/QmitkStdMultiWidget.cpp +++ b/Modules/QtWidgets/src/QmitkStdMultiWidget.cpp @@ -1,815 +1,713 @@ /*============================================================================ 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. ============================================================================*/ #define SMW_INFO MITK_INFO("widget.stdmulti") #include "QmitkStdMultiWidget.h" #include "QmitkRenderWindowWidget.h" // mitk core #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // qt #include #include #include // vtk #include // c++ #include QmitkStdMultiWidget::QmitkStdMultiWidget(QWidget *parent, Qt::WindowFlags f/* = 0*/, const QString &name/* = "stdmulti"*/) : QmitkAbstractMultiWidget(parent, f, name) , m_TimeNavigationController(nullptr) - , m_PendingCrosshairPositionEvent(false) { m_TimeNavigationController = mitk::RenderingManager::GetInstance()->GetTimeNavigationController(); } QmitkStdMultiWidget::~QmitkStdMultiWidget() { auto allRenderWindows = this->GetRenderWindows(); for (auto& renderWindow : allRenderWindows) { m_TimeNavigationController->Disconnect(renderWindow->GetSliceNavigationController()); } } void QmitkStdMultiWidget::InitializeMultiWidget() { // yellow is default color for widget4 m_DecorationColorWidget4[0] = 1.0f; m_DecorationColorWidget4[1] = 1.0f; m_DecorationColorWidget4[2] = 0.0f; SetLayout(2, 2); // transfer colors in WorldGeometry-Nodes of the associated Renderer mitk::IntProperty::Pointer layer; // of widget 1 m_PlaneNode1 = mitk::BaseRenderer::GetInstance(GetRenderWindow1()->renderWindow())->GetCurrentWorldPlaneGeometryNode(); m_PlaneNode1->SetColor(GetDecorationColor(0)); layer = mitk::IntProperty::New(1000); m_PlaneNode1->SetProperty("layer", layer); // of widget 2 m_PlaneNode2 = mitk::BaseRenderer::GetInstance(GetRenderWindow2()->renderWindow())->GetCurrentWorldPlaneGeometryNode(); m_PlaneNode2->SetColor(GetDecorationColor(1)); layer = mitk::IntProperty::New(1000); m_PlaneNode2->SetProperty("layer", layer); // of widget 3 m_PlaneNode3 = mitk::BaseRenderer::GetInstance(GetRenderWindow3()->renderWindow())->GetCurrentWorldPlaneGeometryNode(); m_PlaneNode3->SetColor(GetDecorationColor(2)); layer = mitk::IntProperty::New(1000); m_PlaneNode3->SetProperty("layer", layer); // the parent node m_ParentNodeForGeometryPlanes = mitk::BaseRenderer::GetInstance(GetRenderWindow4()->renderWindow())->GetCurrentWorldPlaneGeometryNode(); layer = mitk::IntProperty::New(1000); m_ParentNodeForGeometryPlanes->SetProperty("layer", layer); AddDisplayPlaneSubTree(); SetDisplayActionEventHandler(std::make_unique()); auto displayActionEventHandler = GetDisplayActionEventHandler(); if (nullptr != displayActionEventHandler) { displayActionEventHandler->InitActions(); } } QmitkRenderWindow* QmitkStdMultiWidget::GetRenderWindow(const QString& widgetName) const { if ("axial" == widgetName) { return GetRenderWindow1(); } if ("sagittal" == widgetName) { return GetRenderWindow2(); } if ("coronal" == widgetName) { return GetRenderWindow3(); } if ("3d" == widgetName) { return GetRenderWindow4(); } return QmitkAbstractMultiWidget::GetRenderWindow(widgetName); } QmitkRenderWindow* QmitkStdMultiWidget::GetRenderWindow(const mitk::BaseRenderer::ViewDirection& viewDirection) const { return GetRenderWindow(static_cast(viewDirection)); } void QmitkStdMultiWidget::SetSelectedPosition(const mitk::Point3D& newPosition, const QString& /*widgetName*/) { GetRenderWindow1()->GetSliceNavigationController()->SelectSliceByPoint(newPosition); GetRenderWindow2()->GetSliceNavigationController()->SelectSliceByPoint(newPosition); GetRenderWindow3()->GetSliceNavigationController()->SelectSliceByPoint(newPosition); RequestUpdateAll(); } const mitk::Point3D QmitkStdMultiWidget::GetSelectedPosition(const QString& /*widgetName*/) const { const mitk::PlaneGeometry* plane1 = GetRenderWindow1()->GetSliceNavigationController()->GetCurrentPlaneGeometry(); const mitk::PlaneGeometry* plane2 = GetRenderWindow2()->GetSliceNavigationController()->GetCurrentPlaneGeometry(); const mitk::PlaneGeometry* plane3 = GetRenderWindow3()->GetSliceNavigationController()->GetCurrentPlaneGeometry(); mitk::Line3D line; if ((plane1 != nullptr) && (plane2 != nullptr) && (plane1->IntersectionLine(plane2, line))) { mitk::Point3D point; if ((plane3 != nullptr) && (plane3->IntersectionPoint(line, point))) { return point; } } return mitk::Point3D(); } void QmitkStdMultiWidget::SetCrosshairVisibility(bool visible) { if (m_PlaneNode1.IsNotNull()) { m_PlaneNode1->SetVisibility(visible); } if (m_PlaneNode2.IsNotNull()) { m_PlaneNode2->SetVisibility(visible); } if (m_PlaneNode3.IsNotNull()) { m_PlaneNode3->SetVisibility(visible); } emit NotifyCrosshairVisibilityChanged(visible); RequestUpdateAll(); } bool QmitkStdMultiWidget::GetCrosshairVisibility() const { bool crosshairVisibility = true; if (m_PlaneNode1.IsNotNull()) { bool visibilityProperty = false; m_PlaneNode1->GetVisibility(visibilityProperty, nullptr); crosshairVisibility &= visibilityProperty; } if (m_PlaneNode2.IsNotNull()) { bool visibilityProperty = false; crosshairVisibility &= m_PlaneNode2->GetVisibility(visibilityProperty, nullptr); crosshairVisibility &= visibilityProperty; } if (m_PlaneNode3.IsNotNull()) { bool visibilityProperty = false; crosshairVisibility &= m_PlaneNode3->GetVisibility(visibilityProperty, nullptr); crosshairVisibility &= visibilityProperty; } return crosshairVisibility; } void QmitkStdMultiWidget::SetCrosshairGap(unsigned int gapSize) { m_PlaneNode1->SetIntProperty("Crosshair.Gap Size", gapSize); m_PlaneNode2->SetIntProperty("Crosshair.Gap Size", gapSize); m_PlaneNode3->SetIntProperty("Crosshair.Gap Size", gapSize); } void QmitkStdMultiWidget::ResetCrosshair() { auto dataStorage = GetDataStorage(); if (nullptr == dataStorage) { return; } mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(dataStorage); SetWidgetPlaneMode(mitk::InteractionSchemeSwitcher::MITKStandard); } void QmitkStdMultiWidget::SetWidgetPlaneMode(int userMode) { MITK_DEBUG << "Changing crosshair mode to " << userMode; switch (userMode) { case 0: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKStandard); break; case 1: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationUncoupled); break; case 2: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKRotationCoupled); break; case 3: SetInteractionScheme(mitk::InteractionSchemeSwitcher::MITKSwivel); break; } emit NotifyCrosshairRotationModeChanged(userMode); } mitk::SliceNavigationController* QmitkStdMultiWidget::GetTimeNavigationController() { return m_TimeNavigationController; } void QmitkStdMultiWidget::AddPlanesToDataStorage() { auto dataStorage = GetDataStorage(); if (nullptr == dataStorage) { return; } if (m_PlaneNode1.IsNotNull() && m_PlaneNode2.IsNotNull() && m_PlaneNode3.IsNotNull() && m_ParentNodeForGeometryPlanes.IsNotNull()) { dataStorage->Add(m_ParentNodeForGeometryPlanes); dataStorage->Add(m_PlaneNode1, m_ParentNodeForGeometryPlanes); dataStorage->Add(m_PlaneNode2, m_ParentNodeForGeometryPlanes); dataStorage->Add(m_PlaneNode3, m_ParentNodeForGeometryPlanes); } } void QmitkStdMultiWidget::RemovePlanesFromDataStorage() { auto dataStorage = GetDataStorage(); if (nullptr == dataStorage) { return; } if (m_PlaneNode1.IsNotNull() && m_PlaneNode2.IsNotNull() && m_PlaneNode3.IsNotNull() && m_ParentNodeForGeometryPlanes.IsNotNull()) { dataStorage->Remove(m_PlaneNode1); dataStorage->Remove(m_PlaneNode2); dataStorage->Remove(m_PlaneNode3); dataStorage->Remove(m_ParentNodeForGeometryPlanes); } } -void QmitkStdMultiWidget::HandleCrosshairPositionEvent() -{ - if (!m_PendingCrosshairPositionEvent) - { - m_PendingCrosshairPositionEvent = true; - QTimer::singleShot(0, this, SLOT(HandleCrosshairPositionEventDelayed())); - } -} - QmitkRenderWindow* QmitkStdMultiWidget::GetRenderWindow(unsigned int number) const { switch (number) { case 0: return GetRenderWindow1(); case 1: return GetRenderWindow2(); case 2: return GetRenderWindow3(); case 3: return GetRenderWindow4(); default: MITK_ERROR << "Requested unknown render window"; break; } return nullptr; } QmitkRenderWindow* QmitkStdMultiWidget::GetRenderWindow1() const { return QmitkAbstractMultiWidget::GetRenderWindow(GetNameFromIndex(0, 0)); } QmitkRenderWindow* QmitkStdMultiWidget::GetRenderWindow2() const { return QmitkAbstractMultiWidget::GetRenderWindow(GetNameFromIndex(0, 1)); } QmitkRenderWindow* QmitkStdMultiWidget::GetRenderWindow3() const { return QmitkAbstractMultiWidget::GetRenderWindow(GetNameFromIndex(1, 0)); } QmitkRenderWindow* QmitkStdMultiWidget::GetRenderWindow4() const { return QmitkAbstractMultiWidget::GetRenderWindow(GetNameFromIndex(1, 1)); } mitk::DataNode::Pointer QmitkStdMultiWidget::GetWidgetPlane1() const { return m_PlaneNode1; } mitk::DataNode::Pointer QmitkStdMultiWidget::GetWidgetPlane2() const { return m_PlaneNode2; } mitk::DataNode::Pointer QmitkStdMultiWidget::GetWidgetPlane3() const { return m_PlaneNode3; } mitk::DataNode::Pointer QmitkStdMultiWidget::GetWidgetPlane(unsigned number) const { switch (number) { case 1: return m_PlaneNode1; case 2: return m_PlaneNode2; case 3: return m_PlaneNode3; default: MITK_ERROR << "Requested unknown render window"; break; } return nullptr; } void QmitkStdMultiWidget::SetDecorationColor(unsigned int widgetNumber, mitk::Color color) { switch (widgetNumber) { case 0: if (m_PlaneNode1.IsNotNull()) { m_PlaneNode1->SetColor(color); } break; case 1: if (m_PlaneNode2.IsNotNull()) { m_PlaneNode2->SetColor(color); } break; case 2: if (m_PlaneNode3.IsNotNull()) { m_PlaneNode3->SetColor(color); } break; case 3: m_DecorationColorWidget4 = color; break; default: MITK_ERROR << "Decoration color for unknown widget!"; break; } } mitk::Color QmitkStdMultiWidget::GetDecorationColor(unsigned int widgetNumber) { // The implementation looks a bit messy here, but it avoids // synchronization of the color of the geometry nodes and an // internal member here. // Default colors were chosen for decent visibility. // Feel free to change your preferences in the workbench. float tmp[3] = { 0.0f, 0.0f, 0.0f }; switch (widgetNumber) { case 0: { if (m_PlaneNode1.IsNotNull()) { if (m_PlaneNode1->GetColor(tmp)) { return dynamic_cast(m_PlaneNode1->GetProperty("color"))->GetColor(); } } float red[3] = { 0.753f, 0.0f, 0.0f }; // This is #C00000 in hex return mitk::Color(red); } case 1: { if (m_PlaneNode2.IsNotNull()) { if (m_PlaneNode2->GetColor(tmp)) { return dynamic_cast(m_PlaneNode2->GetProperty("color"))->GetColor(); } } float green[3] = { 0.0f, 0.69f, 0.0f }; // This is #00B000 in hex return mitk::Color(green); } case 2: { if (m_PlaneNode3.IsNotNull()) { if (m_PlaneNode3->GetColor(tmp)) { return dynamic_cast(m_PlaneNode3->GetProperty("color"))->GetColor(); } } float blue[3] = { 0.0, 0.502f, 1.0f }; // This is #0080FF in hex return mitk::Color(blue); } case 3: { return m_DecorationColorWidget4; } default: MITK_ERROR << "Decoration color for unknown widget!"; float black[3] = { 0.0f, 0.0f, 0.0f }; return mitk::Color(black); } } void QmitkStdMultiWidget::mousePressEvent(QMouseEvent*) { // nothing here, but necessary for mouse interactions (.xml-configuration files) } void QmitkStdMultiWidget::moveEvent(QMoveEvent* e) { QWidget::moveEvent(e); // it is necessary to readjust the position of the Annotation as the StdMultiWidget has moved // unfortunately it's not done by QmitkRenderWindow::moveEvent -> must be done here emit Moved(); } void QmitkStdMultiWidget::wheelEvent(QWheelEvent* e) { emit WheelMoved(e); } -void QmitkStdMultiWidget::HandleCrosshairPositionEventDelayed() -{ - auto dataStorage = GetDataStorage(); - if (nullptr == dataStorage) - { - return; - } - - m_PendingCrosshairPositionEvent = false; - - // find image with highest layer - mitk::TNodePredicateDataType::Pointer isImageData = mitk::TNodePredicateDataType::New(); - mitk::DataStorage::SetOfObjects::ConstPointer nodes = dataStorage->GetSubset(isImageData).GetPointer(); - mitk::Point3D crosshairPos = GetSelectedPosition(""); - mitk::BaseRenderer* baseRenderer = GetRenderWindow1()->GetSliceNavigationController()->GetRenderer(); - auto globalCurrentTimePoint = baseRenderer->GetTime(); - mitk::DataNode::Pointer node = mitk::FindTopmostVisibleNode(nodes, crosshairPos, globalCurrentTimePoint, baseRenderer); - - mitk::DataNode::Pointer topSourceNode; - mitk::Image::Pointer image; - bool isBinary = false; - int component = 0; - - if (node.IsNotNull()) - { - node->GetBoolProperty("binary", isBinary); - if (isBinary) - { - mitk::DataStorage::SetOfObjects::ConstPointer sourcenodes = dataStorage->GetSources(node, nullptr, true); - if (!sourcenodes->empty()) - { - topSourceNode = mitk::FindTopmostVisibleNode(sourcenodes, crosshairPos, globalCurrentTimePoint, baseRenderer); - } - if (topSourceNode.IsNotNull()) - { - image = dynamic_cast(topSourceNode->GetData()); - topSourceNode->GetIntProperty("Image.Displayed Component", component); - } - else - { - image = dynamic_cast(node->GetData()); - node->GetIntProperty("Image.Displayed Component", component); - } - } - else - { - image = dynamic_cast(node->GetData()); - node->GetIntProperty("Image.Displayed Component", component); - } - } - - std::string statusText; - std::stringstream stream; - itk::Index<3> p; - unsigned int timestep = baseRenderer->GetTimeStep(); - - if (image.IsNotNull() && (image->GetTimeSteps() > timestep)) - { - image->GetGeometry()->WorldToIndex(crosshairPos, p); - stream.precision(2); - stream << "Position: <" << std::fixed << crosshairPos[0] << ", " << std::fixed << crosshairPos[1] << ", " - << std::fixed << crosshairPos[2] << "> mm"; - stream << "; Index: <" << p[0] << ", " << p[1] << ", " << p[2] << "> "; - - mitk::ScalarType pixelValue; - - mitkPixelTypeMultiplex5(mitk::FastSinglePixelAccess, - image->GetChannelDescriptor().GetPixelType(), - image, - image->GetVolumeData(image->GetTimeGeometry()->TimePointToTimeStep(globalCurrentTimePoint)), - p, - pixelValue, - component); - - if (fabs(pixelValue) > 1000000 || fabs(pixelValue) < 0.01) - { - stream << "; Time: " << globalCurrentTimePoint << " ms; Pixelvalue: " << std::scientific << pixelValue << " "; - } - else - { - stream << "; Time: " << globalCurrentTimePoint << " ms; Pixelvalue: " << pixelValue << " "; - } - } - else - { - stream << "No image information at this position!"; - } - - statusText = stream.str(); - mitk::StatusBar::GetInstance()->DisplayGreyValueText(statusText.c_str()); -} - void QmitkStdMultiWidget::Fit() { vtkSmartPointer vtkrenderer; vtkrenderer = mitk::BaseRenderer::GetInstance(GetRenderWindow1()->renderWindow())->GetVtkRenderer(); if (nullptr != vtkrenderer) { vtkrenderer->ResetCamera(); } vtkrenderer = mitk::BaseRenderer::GetInstance(GetRenderWindow2()->renderWindow())->GetVtkRenderer(); if (nullptr != vtkrenderer) { vtkrenderer->ResetCamera(); } vtkrenderer = mitk::BaseRenderer::GetInstance(GetRenderWindow3()->renderWindow())->GetVtkRenderer(); if (nullptr != vtkrenderer) { vtkrenderer->ResetCamera(); } vtkrenderer = mitk::BaseRenderer::GetInstance(GetRenderWindow4()->renderWindow())->GetVtkRenderer(); if (nullptr != vtkrenderer) { vtkrenderer->ResetCamera(); } mitk::BaseRenderer::GetInstance(GetRenderWindow1()->renderWindow())->GetCameraController()->Fit(); mitk::BaseRenderer::GetInstance(GetRenderWindow2()->renderWindow())->GetCameraController()->Fit(); mitk::BaseRenderer::GetInstance(GetRenderWindow3()->renderWindow())->GetCameraController()->Fit(); mitk::BaseRenderer::GetInstance(GetRenderWindow4()->renderWindow())->GetCameraController()->Fit(); int w = vtkObject::GetGlobalWarningDisplay(); vtkObject::GlobalWarningDisplayOff(); vtkObject::SetGlobalWarningDisplay(w); } void QmitkStdMultiWidget::AddDisplayPlaneSubTree() { // add the displayed planes of the multiwidget to a node to which the subtree // @a planesSubTree points ... mitk::PlaneGeometryDataMapper2D::Pointer mapper; // ... of widget 1 mitk::BaseRenderer* renderer1 = mitk::BaseRenderer::GetInstance(GetRenderWindow1()->renderWindow()); m_PlaneNode1 = renderer1->GetCurrentWorldPlaneGeometryNode(); m_PlaneNode1->SetProperty("visible", mitk::BoolProperty::New(true)); m_PlaneNode1->SetProperty("name", mitk::StringProperty::New(std::string(renderer1->GetName()) + ".plane")); m_PlaneNode1->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_PlaneNode1->SetProperty("helper object", mitk::BoolProperty::New(true)); mapper = mitk::PlaneGeometryDataMapper2D::New(); m_PlaneNode1->SetMapper(mitk::BaseRenderer::Standard2D, mapper); // ... of widget 2 mitk::BaseRenderer* renderer2 = mitk::BaseRenderer::GetInstance(GetRenderWindow2()->renderWindow()); m_PlaneNode2 = renderer2->GetCurrentWorldPlaneGeometryNode(); m_PlaneNode2->SetProperty("visible", mitk::BoolProperty::New(true)); m_PlaneNode2->SetProperty("name", mitk::StringProperty::New(std::string(renderer2->GetName()) + ".plane")); m_PlaneNode2->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_PlaneNode2->SetProperty("helper object", mitk::BoolProperty::New(true)); mapper = mitk::PlaneGeometryDataMapper2D::New(); m_PlaneNode2->SetMapper(mitk::BaseRenderer::Standard2D, mapper); // ... of widget 3 mitk::BaseRenderer *renderer3 = mitk::BaseRenderer::GetInstance(GetRenderWindow3()->renderWindow()); m_PlaneNode3 = renderer3->GetCurrentWorldPlaneGeometryNode(); m_PlaneNode3->SetProperty("visible", mitk::BoolProperty::New(true)); m_PlaneNode3->SetProperty("name", mitk::StringProperty::New(std::string(renderer3->GetName()) + ".plane")); m_PlaneNode3->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_PlaneNode3->SetProperty("helper object", mitk::BoolProperty::New(true)); mapper = mitk::PlaneGeometryDataMapper2D::New(); m_PlaneNode3->SetMapper(mitk::BaseRenderer::Standard2D, mapper); m_ParentNodeForGeometryPlanes = mitk::DataNode::New(); m_ParentNodeForGeometryPlanes->SetProperty("name", mitk::StringProperty::New("Widgets")); m_ParentNodeForGeometryPlanes->SetProperty("helper object", mitk::BoolProperty::New(true)); } void QmitkStdMultiWidget::EnsureDisplayContainsPoint(mitk::BaseRenderer *renderer, const mitk::Point3D &p) { mitk::Point2D pointOnDisplay; renderer->WorldToDisplay(p, pointOnDisplay); if (pointOnDisplay[0] < renderer->GetVtkRenderer()->GetOrigin()[0] || pointOnDisplay[1] < renderer->GetVtkRenderer()->GetOrigin()[1] || pointOnDisplay[0] > renderer->GetVtkRenderer()->GetOrigin()[0] + renderer->GetViewportSize()[0] || pointOnDisplay[1] > renderer->GetVtkRenderer()->GetOrigin()[1] + renderer->GetViewportSize()[1]) { mitk::Point2D pointOnPlane; renderer->GetCurrentWorldPlaneGeometry()->Map(p, pointOnPlane); renderer->GetCameraController()->MoveCameraToPoint(pointOnPlane); } } void QmitkStdMultiWidget::SetWidgetPlaneVisibility(const char *widgetName, bool visible, mitk::BaseRenderer *renderer) { auto dataStorage = GetDataStorage(); if (nullptr != dataStorage) { mitk::DataNode* dataNode = dataStorage->GetNamedNode(widgetName); if (dataNode != nullptr) { dataNode->SetVisibility(visible, renderer); } } } void QmitkStdMultiWidget::SetWidgetPlanesVisibility(bool visible, mitk::BaseRenderer *renderer) { if (m_PlaneNode1.IsNotNull()) { m_PlaneNode1->SetVisibility(visible, renderer); } if (m_PlaneNode2.IsNotNull()) { m_PlaneNode2->SetVisibility(visible, renderer); } if (m_PlaneNode3.IsNotNull()) { m_PlaneNode3->SetVisibility(visible, renderer); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } ////////////////////////////////////////////////////////////////////////// // PRIVATE ////////////////////////////////////////////////////////////////////////// void QmitkStdMultiWidget::SetLayoutImpl() { CreateRenderWindowWidgets(); GetMultiWidgetLayoutManager()->SetLayoutDesign(QmitkMultiWidgetLayoutManager::LayoutDesign::DEFAULT); // Initialize views as axial, sagittal, coronal to all data objects in DataStorage auto geo = GetDataStorage()->ComputeBoundingGeometry3D(GetDataStorage()->GetAll()); mitk::RenderingManager::GetInstance()->InitializeViews(geo); } void QmitkStdMultiWidget::CreateRenderWindowWidgets() { // create axial render window (widget) QString renderWindowWidgetName = GetNameFromIndex(0, 0); RenderWindowWidgetPointer renderWindowWidget1 = std::make_shared(this, renderWindowWidgetName, GetDataStorage()); auto renderWindow1 = renderWindowWidget1->GetRenderWindow(); renderWindow1->GetSliceNavigationController()->SetDefaultViewDirection(mitk::SliceNavigationController::Axial); renderWindowWidget1->SetDecorationColor(GetDecorationColor(0)); renderWindowWidget1->SetCornerAnnotationText("Axial"); renderWindowWidget1->GetRenderWindow()->SetLayoutIndex(ViewDirection::AXIAL); AddRenderWindowWidget(renderWindowWidgetName, renderWindowWidget1); // create sagittal render window (widget) renderWindowWidgetName = GetNameFromIndex(0, 1); RenderWindowWidgetPointer renderWindowWidget2 = std::make_shared(this, renderWindowWidgetName, GetDataStorage()); auto renderWindow2 = renderWindowWidget2->GetRenderWindow(); renderWindow2->GetSliceNavigationController()->SetDefaultViewDirection(mitk::SliceNavigationController::Sagittal); renderWindowWidget2->SetDecorationColor(GetDecorationColor(1)); renderWindowWidget2->setStyleSheet("border: 0px"); renderWindowWidget2->SetCornerAnnotationText("Sagittal"); renderWindowWidget2->GetRenderWindow()->SetLayoutIndex(ViewDirection::SAGITTAL); AddRenderWindowWidget(renderWindowWidgetName, renderWindowWidget2); // create coronal render window (widget) renderWindowWidgetName = GetNameFromIndex(1, 0); RenderWindowWidgetPointer renderWindowWidget3 = std::make_shared(this, renderWindowWidgetName, GetDataStorage()); auto renderWindow3 = renderWindowWidget3->GetRenderWindow(); renderWindow3->GetSliceNavigationController()->SetDefaultViewDirection(mitk::SliceNavigationController::Coronal); renderWindowWidget3->SetDecorationColor(GetDecorationColor(2)); renderWindowWidget3->SetCornerAnnotationText("Coronal"); renderWindowWidget3->GetRenderWindow()->SetLayoutIndex(ViewDirection::CORONAL); AddRenderWindowWidget(renderWindowWidgetName, renderWindowWidget3); // create 3D render window (widget) renderWindowWidgetName = GetNameFromIndex(1, 1); RenderWindowWidgetPointer renderWindowWidget4 = std::make_shared(this, renderWindowWidgetName, GetDataStorage()); auto renderWindow4 = renderWindowWidget4->GetRenderWindow(); renderWindow4->GetSliceNavigationController()->SetDefaultViewDirection(mitk::SliceNavigationController::Original); renderWindowWidget4->SetDecorationColor(GetDecorationColor(3)); renderWindowWidget4->SetCornerAnnotationText("3D"); renderWindowWidget4->GetRenderWindow()->SetLayoutIndex(ViewDirection::THREE_D); mitk::BaseRenderer::GetInstance(renderWindowWidget4->GetRenderWindow()->renderWindow())->SetMapperID(mitk::BaseRenderer::Standard3D); AddRenderWindowWidget(renderWindowWidgetName, renderWindowWidget4); SetActiveRenderWindowWidget(renderWindowWidget1); // connect to the "time navigation controller": send time via sliceNavigationControllers m_TimeNavigationController->ConnectGeometryTimeEvent(renderWindow1->GetSliceNavigationController()); m_TimeNavigationController->ConnectGeometryTimeEvent(renderWindow2->GetSliceNavigationController()); m_TimeNavigationController->ConnectGeometryTimeEvent(renderWindow3->GetSliceNavigationController()); m_TimeNavigationController->ConnectGeometryTimeEvent(renderWindow4->GetSliceNavigationController()); renderWindow1->GetSliceNavigationController()->ConnectGeometrySendEvent( mitk::BaseRenderer::GetInstance(renderWindow4->renderWindow())); // reverse connection between sliceNavigationControllers and timeNavigationController renderWindow1->GetSliceNavigationController()->ConnectGeometryTimeEvent(m_TimeNavigationController); renderWindow2->GetSliceNavigationController()->ConnectGeometryTimeEvent(m_TimeNavigationController); renderWindow3->GetSliceNavigationController()->ConnectGeometryTimeEvent(m_TimeNavigationController); //renderWindow4->GetSliceNavigationController()->ConnectGeometryTimeEvent(m_TimeNavigationController); auto layoutManager = GetMultiWidgetLayoutManager(); connect(renderWindow1, &QmitkRenderWindow::ResetView, this, &QmitkStdMultiWidget::ResetCrosshair); connect(renderWindow1, &QmitkRenderWindow::CrosshairVisibilityChanged, this, &QmitkStdMultiWidget::SetCrosshairVisibility); connect(renderWindow1, &QmitkRenderWindow::CrosshairRotationModeChanged, this, &QmitkStdMultiWidget::SetWidgetPlaneMode); connect(renderWindow1, &QmitkRenderWindow::LayoutDesignChanged, layoutManager, &QmitkMultiWidgetLayoutManager::SetLayoutDesign); connect(this, &QmitkStdMultiWidget::NotifyCrosshairVisibilityChanged, renderWindow1, &QmitkRenderWindow::UpdateCrosshairVisibility); connect(this, &QmitkStdMultiWidget::NotifyCrosshairRotationModeChanged, renderWindow1, &QmitkRenderWindow::UpdateCrosshairRotationMode); connect(renderWindow2, &QmitkRenderWindow::ResetView, this, &QmitkStdMultiWidget::ResetCrosshair); connect(renderWindow2, &QmitkRenderWindow::CrosshairVisibilityChanged, this, &QmitkStdMultiWidget::SetCrosshairVisibility); connect(renderWindow2, &QmitkRenderWindow::CrosshairRotationModeChanged, this, &QmitkStdMultiWidget::SetWidgetPlaneMode); connect(renderWindow2, &QmitkRenderWindow::LayoutDesignChanged, layoutManager, &QmitkMultiWidgetLayoutManager::SetLayoutDesign); connect(this, &QmitkStdMultiWidget::NotifyCrosshairVisibilityChanged, renderWindow2, &QmitkRenderWindow::UpdateCrosshairVisibility); connect(this, &QmitkStdMultiWidget::NotifyCrosshairRotationModeChanged, renderWindow2, &QmitkRenderWindow::UpdateCrosshairRotationMode); connect(renderWindow3, &QmitkRenderWindow::ResetView, this, &QmitkStdMultiWidget::ResetCrosshair); connect(renderWindow3, &QmitkRenderWindow::CrosshairVisibilityChanged, this, &QmitkStdMultiWidget::SetCrosshairVisibility); connect(renderWindow3, &QmitkRenderWindow::CrosshairRotationModeChanged, this, &QmitkStdMultiWidget::SetWidgetPlaneMode); connect(renderWindow3, &QmitkRenderWindow::LayoutDesignChanged, layoutManager, &QmitkMultiWidgetLayoutManager::SetLayoutDesign); connect(this, &QmitkStdMultiWidget::NotifyCrosshairVisibilityChanged, renderWindow3, &QmitkRenderWindow::UpdateCrosshairVisibility); connect(this, &QmitkStdMultiWidget::NotifyCrosshairRotationModeChanged, renderWindow3, &QmitkRenderWindow::UpdateCrosshairRotationMode); connect(renderWindow4, &QmitkRenderWindow::ResetView, this, &QmitkStdMultiWidget::ResetCrosshair); connect(renderWindow4, &QmitkRenderWindow::CrosshairVisibilityChanged, this, &QmitkStdMultiWidget::SetCrosshairVisibility); connect(renderWindow4, &QmitkRenderWindow::CrosshairRotationModeChanged, this, &QmitkStdMultiWidget::SetWidgetPlaneMode); connect(renderWindow4, &QmitkRenderWindow::LayoutDesignChanged, layoutManager, &QmitkMultiWidgetLayoutManager::SetLayoutDesign); connect(this, &QmitkStdMultiWidget::NotifyCrosshairVisibilityChanged, renderWindow4, &QmitkRenderWindow::UpdateCrosshairVisibility); connect(this, &QmitkStdMultiWidget::NotifyCrosshairRotationModeChanged, renderWindow4, &QmitkRenderWindow::UpdateCrosshairRotationMode); } diff --git a/Modules/Segmentation/Algorithms/mitkGrowCutSegmentationFilter.cpp b/Modules/Segmentation/Algorithms/mitkGrowCutSegmentationFilter.cpp new file mode 100644 index 0000000000..c1348d4f7f --- /dev/null +++ b/Modules/Segmentation/Algorithms/mitkGrowCutSegmentationFilter.cpp @@ -0,0 +1,74 @@ +/*============================================================================ + +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 "itkFastGrowCut.h" +#include "mitkStringProperty.h" +#include "mitkLabel.h" +#include "mitkLabelSetImage.h" +#include "mitkImageAccessByItk.h" +#include "mitkImageCast.h" +#include "mitkGrowCutSegmentationFilter.h" + +template +void AccessItkGrowCutFilter(const itk::Image *inputImage, + const itk::Image *seedImage, + const double distancePenalty, + mitk::Image::Pointer output) +{ + using ImageType = itk::Image; + using LabelType = itk::Image; + + using FGCType = itk::FastGrowCut; + typename FGCType::Pointer fgcFilter = FGCType::New(); + + fgcFilter->SetInput(inputImage); + fgcFilter->SetSeedImage(seedImage); + fgcFilter->SetDistancePenalty(distancePenalty); + + try + { + fgcFilter->Update(); + } + catch (...) + { + mitkThrow() << "An error occurred while using the itkFastGrowCut filter Update()-Method."; + } + + mitk::CastToMitkImage(fgcFilter->GetOutput(), output); + return; +} + +namespace mitk +{ + GrowCutSegmentationFilter::GrowCutSegmentationFilter() : m_DistancePenalty(0) + { + } + + GrowCutSegmentationFilter::~GrowCutSegmentationFilter() {} + + void GrowCutSegmentationFilter::GenerateData() + { + if (nullptr == m_itkSeedImage) + { + return; + } + + mitk::Image::ConstPointer mitkInputImage = GetInput(); + + AccessFixedDimensionByItk_n(mitkInputImage, + AccessItkGrowCutFilter, + 3, + (m_itkSeedImage.GetPointer(), + m_DistancePenalty, + this->GetOutput())); + } +} // namespace mitk diff --git a/Modules/Segmentation/Algorithms/mitkGrowCutSegmentationFilter.h b/Modules/Segmentation/Algorithms/mitkGrowCutSegmentationFilter.h new file mode 100644 index 0000000000..a064526080 --- /dev/null +++ b/Modules/Segmentation/Algorithms/mitkGrowCutSegmentationFilter.h @@ -0,0 +1,57 @@ +/*============================================================================ + +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 mitkGrowCutSegmentationFilter_h_Included +#define mitkGrowCutSegmentationFilter_h_Included + +#include "itkImage.h" +#include "mitkITKImageImport.h" +#include "mitkImage.h" +#include "mitkImageToImageFilter.h" +#include + +namespace mitk +{ + /** + \brief A filter that performs a growcut image segmentation. + + This class being an mitk::ImageToImageFilter performs a growcut image segmentation based on a + given seedimage. + Internally, itk::FastGrowCut is used. + + $Author: Jan Sahrhage + */ + class MITKSEGMENTATION_EXPORT GrowCutSegmentationFilter : public ImageToImageFilter + { + public: + mitkClassMacro(GrowCutSegmentationFilter, ImageToImageFilter); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + + void SetSeedImage(itk::Image::Pointer itkSeedImage) { m_itkSeedImage = itkSeedImage; } + + void SetDistancePenalty(double distancePenalty) { m_DistancePenalty = distancePenalty; } + + protected: + GrowCutSegmentationFilter(); + ~GrowCutSegmentationFilter() override; + void GenerateData() override; + + private: + itk::Image::Pointer m_itkSeedImage = nullptr; + double m_DistancePenalty; + + }; // class + +} // namespace mitk + +#endif diff --git a/Modules/Segmentation/CMakeLists.txt b/Modules/Segmentation/CMakeLists.txt index 1c31c05044..83b9c461ac 100644 --- a/Modules/Segmentation/CMakeLists.txt +++ b/Modules/Segmentation/CMakeLists.txt @@ -1,9 +1,10 @@ mitk_create_module( INCLUDE_DIRS Algorithms Controllers DataManagement Interactions Rendering SegmentationUtilities/BooleanOperations SegmentationUtilities/MorphologicalOperations DEPENDS MitkAlgorithmsExt MitkSurfaceInterpolation MitkGraphAlgorithms MitkContourModel MitkMultilabel PACKAGE_DEPENDS PUBLIC ITK|QuadEdgeMesh+RegionGrowing PRIVATE ITK|LabelMap+MathematicalMorphology VTK|ImagingGeneral + TARGET_DEPENDS PRIVATE GrowCut ) add_subdirectory(Testing) diff --git a/Modules/Segmentation/Interactions/mitkAddContourTool.cpp b/Modules/Segmentation/Interactions/mitkAddContourTool.cpp index 0526c5fec6..6b74b472cc 100644 --- a/Modules/Segmentation/Interactions/mitkAddContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAddContourTool.cpp @@ -1,58 +1,58 @@ /*============================================================================ 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 "mitkAddContourTool.h" #include "mitkAddContourTool.xpm" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, AddContourTool, "Add tool"); } mitk::AddContourTool::AddContourTool() : ContourTool(1) { } mitk::AddContourTool::~AddContourTool() { } const char **mitk::AddContourTool::GetXPM() const { return mitkAddContourTool_xpm; } us::ModuleResource mitk::AddContourTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Add_48x48.png"); + us::ModuleResource resource = module->GetResource("Add.svg"); return resource; } us::ModuleResource mitk::AddContourTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Add_Cursor_32x32.png"); + us::ModuleResource resource = module->GetResource("Add_Cursor.svg"); return resource; } const char *mitk::AddContourTool::GetName() const { return "Add"; } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp index eb65387552..21aa9c0a22 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdTool.cpp @@ -1,55 +1,55 @@ /*============================================================================ 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 "mitkBinaryThresholdTool.h" // us #include "usGetModuleContext.h" #include "usModule.h" #include "usModuleResource.h" namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, BinaryThresholdTool, "Thresholding tool"); } mitk::BinaryThresholdTool::BinaryThresholdTool() : BinaryThresholdBaseTool() { LockedUpperThresholdOn(); } mitk::BinaryThresholdTool::~BinaryThresholdTool() { } const char **mitk::BinaryThresholdTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::BinaryThresholdTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Threshold_48x48.png"); + us::ModuleResource resource = module->GetResource("Threshold.svg"); return resource; } const char *mitk::BinaryThresholdTool::GetName() const { return "Threshold"; } void mitk::BinaryThresholdTool::SetThresholdValue(double value) { this->SetThresholdValues(value, this->GetSensibleMaximumThreshold()); } diff --git a/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.cpp b/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.cpp index 7e729be00e..eb81cdf160 100644 --- a/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.cpp +++ b/Modules/Segmentation/Interactions/mitkBinaryThresholdULTool.cpp @@ -1,50 +1,50 @@ /*============================================================================ 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 "mitkBinaryThresholdULTool.h" // us #include "usGetModuleContext.h" #include "usModule.h" #include "usModuleContext.h" #include "usModuleResource.h" namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, BinaryThresholdULTool, "ThresholdingUL tool"); } mitk::BinaryThresholdULTool::BinaryThresholdULTool() : BinaryThresholdBaseTool() { } mitk::BinaryThresholdULTool::~BinaryThresholdULTool() { } const char **mitk::BinaryThresholdULTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::BinaryThresholdULTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("TwoThresholds_48x48.png"); + us::ModuleResource resource = module->GetResource("ULThreshold.svg"); return resource; } const char *mitk::BinaryThresholdULTool::GetName() const { return "UL Threshold"; } diff --git a/Modules/Segmentation/Interactions/mitkCloseRegionTool.cpp b/Modules/Segmentation/Interactions/mitkCloseRegionTool.cpp new file mode 100644 index 0000000000..1c173b24be --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkCloseRegionTool.cpp @@ -0,0 +1,120 @@ +/*============================================================================ + +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)); + + auto labelSetImage = dynamic_cast(this->GetWorkingData()); + if (seedLabelValue == labelSetImage->GetExteriorLabel()->GetValue()) + { + 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/mitkFillRegionTool.h b/Modules/Segmentation/Interactions/mitkCloseRegionTool.h similarity index 64% copy from Modules/Segmentation/Interactions/mitkFillRegionTool.h copy to Modules/Segmentation/Interactions/mitkCloseRegionTool.h index 3643b13992..1d907a2582 100644 --- a/Modules/Segmentation/Interactions/mitkFillRegionTool.h +++ b/Modules/Segmentation/Interactions/mitkCloseRegionTool.h @@ -1,60 +1,64 @@ /*============================================================================ 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 mitkFillRegionTool_h_Included -#define mitkFillRegionTool_h_Included +#ifndef mitkCloseRegionTool_h_Included +#define mitkCloseRegionTool_h_Included -#include "mitkSetRegionTool.h" +#include "mitkFillRegionBaseTool.h" #include namespace us { class ModuleResource; } namespace mitk { /** - \brief Fill the inside of a contour with the foreground pixel value. + \brief Closes/Fills the inside of a contour with the foreground pixel value. - \sa SetRegionTool + \sa FillRegionBaseTool \ingroup Interactions Finds the outer contour of a shape in 2D (possibly including holes) and sets all the pixels inside to the foreground pixel value (filling holes in a segmentation). If clicked on the background, the outer contour might contain the whole image and thus fill the whole image with the foreground pixel value. \warning Only to be instantiated by mitk::ToolManager. */ - class MITKSEGMENTATION_EXPORT FillRegionTool : public SetRegionTool + class MITKSEGMENTATION_EXPORT CloseRegionTool : public FillRegionBaseTool { public: - mitkClassMacro(FillRegionTool, SetRegionTool); + mitkClassMacro(CloseRegionTool, FillRegionBaseTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); const char **GetXPM() const override; us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; protected: - FillRegionTool(); // purposely hidden - ~FillRegionTool() override; + CloseRegionTool() = default; // purposely hidden + ~CloseRegionTool() = default; + + Image::Pointer GenerateFillImage(const Image* workingSlice, Point3D seedPoint, mitk::Label::PixelType& seedLabelValue) const override; + void PrepareFilling(const Image* workingSlice, Point3D seedPoint) override; + }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp index 1f25e5d026..48cfaf8ff7 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp @@ -1,333 +1,331 @@ /*============================================================================ 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 "mitkContourModelLiveWireInteractor.h" #include "mitkInteractionPositionEvent.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include #include "mitkIOUtil.h" mitk::ContourModelLiveWireInteractor::ContourModelLiveWireInteractor() : ContourModelInteractor() { m_LiveWireFilter = mitk::ImageLiveWireContourModelFilter::New(); m_LiveWireFilter->SetUseCostFunction(true); m_NextActiveVertexDown.Fill(0); m_NextActiveVertexUp.Fill(0); } mitk::ContourModelLiveWireInteractor::~ContourModelLiveWireInteractor() { } void mitk::ContourModelLiveWireInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("checkisOverPoint", OnCheckPointClick); CONNECT_CONDITION("mouseMove", IsHovering); CONNECT_FUNCTION("movePoint", OnMovePoint); CONNECT_FUNCTION("deletePoint", OnDeletePoint); CONNECT_FUNCTION("addPoint", OnAddPoint) CONNECT_FUNCTION("finish", OnFinishEditing); } bool mitk::ContourModelLiveWireInteractor::OnCheckPointClick(const InteractionEvent *interactionEvent) { auto isVertexSelected = Superclass::OnCheckPointClick(interactionEvent); if (isVertexSelected) { auto* contour = dynamic_cast(this->GetDataNode()->GetData()); const auto* positionEvent = dynamic_cast(interactionEvent); mitk::Point3D click = positionEvent->GetPositionInWorld(); const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto controlVertices = contour->GetControlVertices(timeStep); const mitk::ContourModel::VertexType* nextPoint = contour->GetNextControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); const mitk::ContourModel::VertexType* previousPoint = contour->GetPreviousControlVertexAt(click, mitk::ContourModelLiveWireInteractor::eps, timeStep); this->SplitContourFromSelectedVertex(contour, nextPoint, previousPoint, timeStep); m_NextActiveVertexUp = nextPoint->Coordinates; m_NextActiveVertexDown = previousPoint->Coordinates; // clear previous void positions this->m_LiveWireFilter->ClearRepulsivePoints(); // all points in lower and upper part should be marked as repulsive points to not be changed this->SetRepulsivePoints(previousPoint, m_ContourLeft, timeStep); this->SetRepulsivePoints(nextPoint, m_ContourRight, timeStep); // clear container with void points between neighboring control points m_ContourBeingModified.clear(); } return isVertexSelected; } void mitk::ContourModelLiveWireInteractor::SetWorkingImage(mitk::Image *_arg) { if (this->m_WorkingSlice != _arg) { this->m_WorkingSlice = _arg; this->m_LiveWireFilter->SetInput(this->m_WorkingSlice); } } void mitk::ContourModelLiveWireInteractor::OnAddPoint(StateMachineAction* sm, InteractionEvent* interactionEvent) { Superclass::OnAddPoint(sm, interactionEvent); } void mitk::ContourModelLiveWireInteractor::OnDeletePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "Invalid Contour!"; return; } if (contour->GetSelectedVertex()) { mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); newContour->Concatenate(m_ContourLeft, timeStep); // recompute contour between neighbored two active control points this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(this->m_NextActiveVertexUp); this->m_LiveWireFilter->Update(); mitk::ContourModel *liveWireContour = this->m_LiveWireFilter->GetOutput(); assert(liveWireContour); if (liveWireContour->IsEmpty(timeStep)) return; liveWireContour->RemoveVertexAt(0, timeStep); liveWireContour->RemoveVertexAt(liveWireContour->GetNumberOfVertices(timeStep) - 1, timeStep); // insert new live wire computed points newContour->Concatenate(liveWireContour, timeStep); // insert right side of original contour newContour->Concatenate(this->m_ContourRight, timeStep); newContour->SetClosed(contour->IsClosed(timeStep), timeStep); // instead of leaving a single point, delete all points if (newContour->GetNumberOfVertices(timeStep) <= 2) { newContour->Clear(timeStep); } this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } void mitk::ContourModelLiveWireInteractor::OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "invalid contour"; return; } - std::cout << currentPosition << std::endl; - mitk::ContourModel::Pointer editingContour = mitk::ContourModel::New(); editingContour->Expand(contour->GetTimeSteps()); editingContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // recompute left live wire, i.e. the contour between previous active vertex and selected vertex this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(currentPosition); // remove void positions between previous active vertex and next active vertex. if (!m_ContourBeingModified.empty()) { std::vector>::const_iterator iter = m_ContourBeingModified.begin(); for (; iter != m_ContourBeingModified.end(); iter++) { this->m_LiveWireFilter->RemoveRepulsivePoint((*iter)); } } // update to get the left livewire. Remember that the points in the rest of the contour are already // set as void positions in the filter this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer leftLiveWire = this->m_LiveWireFilter->GetOutput(); assert(leftLiveWire); if (!leftLiveWire->IsEmpty(timeStep)) leftLiveWire->RemoveVertexAt(0, timeStep); editingContour->Concatenate(leftLiveWire, timeStep); // the new index of the selected vertex unsigned int selectedVertexIndex = this->m_ContourLeft->GetNumberOfVertices(timeStep) + leftLiveWire->GetNumberOfVertices(timeStep) - 1; // at this point the container has to be empty m_ContourBeingModified.clear(); // add points from left live wire contour auto iter = leftLiveWire->IteratorBegin(timeStep); for (; iter != leftLiveWire->IteratorEnd(timeStep); iter++) { itk::Index<2> idx; this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); // add indices m_ContourBeingModified.push_back(idx); } // recompute right live wire, i.e. the contour between selected vertex and next active vertex this->m_LiveWireFilter->SetStartPoint(currentPosition); this->m_LiveWireFilter->SetEndPoint(m_NextActiveVertexUp); // update filter with all contour points set as void but the right live wire portion to be calculated now this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer rightLiveWire = this->m_LiveWireFilter->GetOutput(); assert(rightLiveWire); if (!leftLiveWire->IsEmpty(timeStep)) leftLiveWire->SetControlVertexAt(leftLiveWire->GetNumberOfVertices() - 1, timeStep); if (!rightLiveWire->IsEmpty(timeStep)) rightLiveWire->RemoveVertexAt(0, timeStep); editingContour->Concatenate(rightLiveWire, timeStep); mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // concatenate left original contour newContour->Concatenate(this->m_ContourLeft, timeStep); newContour->Concatenate(editingContour, timeStep, true); // set last inserted vertex as selected newContour->SelectVertexAt(selectedVertexIndex, timeStep); // set as control point newContour->SetSelectedVertexAsControlPoint(true); // concatenate right original contour newContour->Concatenate(this->m_ContourRight, timeStep); newContour->SetClosed(contour->IsClosed(timeStep), timeStep); this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::ContourModelLiveWireInteractor::SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, const mitk::ContourModel::VertexType *nextPoint, const mitk::ContourModel::VertexType *previousPoint, int timeStep) { m_ContourLeft = mitk::ContourModel::New(); m_ContourRight = mitk::ContourModel::New(); auto it = srcContour->IteratorBegin(); // part between nextPoint and end of Countour bool upperPart = false; // part between start of countour and previousPoint bool lowerPart = true; // edge cases when point right before first control vertex is selected or first control vertex is selected if (nextPoint == (*it) || srcContour->GetSelectedVertex() == (*it)) { upperPart = true; lowerPart = false; m_ContourLeft->AddVertex(previousPoint->Coordinates, previousPoint->IsControlPoint, timeStep); } // if first control vertex is selected, move to next point before adding vertices to m_ContourRight // otherwise, second line appears when moving the vertex if (srcContour->GetSelectedVertex() == (*it)) { while (*it != nextPoint) { it++; } } for (; it != srcContour->IteratorEnd(timeStep); it++) { // everything in lower part should be added to m_CountoutLeft if (lowerPart) { m_ContourLeft->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timeStep); } // start of "restricted area" where no vertex should be added to m_CountoutLeft or m_CountoutRight if (*it == previousPoint) { lowerPart = false; upperPart = false; } // start of upperPart if (*it == nextPoint) { upperPart = true; } // everything in upper part should be added to m_CountoutRight if (upperPart) { m_ContourRight->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timeStep); } } } void mitk::ContourModelLiveWireInteractor::SetRepulsivePoints(const mitk::ContourModel::VertexType *pointToExclude, mitk::ContourModel *contour, int timeStep) { auto it = contour->IteratorBegin(); for (; it != contour->IteratorEnd(timeStep); it++) { if (*it != pointToExclude) { itk::Index<2> idx; this->m_WorkingSlice->GetGeometry()->WorldToIndex((*it)->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); } } } void mitk::ContourModelLiveWireInteractor::OnFinishEditing(StateMachineAction *, InteractionEvent *) { } diff --git a/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.cpp b/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.cpp index 644e2de507..0ccdcdfae6 100644 --- a/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.cpp +++ b/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.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 "mitkDrawPaintbrushTool.h" #include "mitkDrawPaintbrushTool.xpm" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, DrawPaintbrushTool, "Paintbrush drawing tool"); } mitk::DrawPaintbrushTool::DrawPaintbrushTool() : PaintbrushTool(1) { FeedbackContourTool::SetFeedbackContourColorDefault(); } mitk::DrawPaintbrushTool::~DrawPaintbrushTool() { } const char **mitk::DrawPaintbrushTool::GetXPM() const { return mitkDrawPaintbrushTool_xpm; } us::ModuleResource mitk::DrawPaintbrushTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Paint_48x48.png"); + us::ModuleResource resource = module->GetResource("Paint.svg"); return resource; } us::ModuleResource mitk::DrawPaintbrushTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Paint_Cursor_32x32.png"); + us::ModuleResource resource = module->GetResource("Paint_Cursor.svg"); return resource; } const char *mitk::DrawPaintbrushTool::GetName() const { return "Paint"; } diff --git a/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp b/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp index f25e261373..81db18aa4a 100644 --- a/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.cpp @@ -1,471 +1,459 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkEditableContourTool.h" #include mitk::EditableContourTool::EditableContourTool() : FeedbackContourTool("EditableContourTool"), m_AutoConfirm(true), m_AddMode(true) {} mitk::EditableContourTool::~EditableContourTool() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); } void mitk::EditableContourTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("InitObject", OnInitContour); CONNECT_FUNCTION("AddPoint", OnAddPoint); CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); CONNECT_FUNCTION("Drawing", OnDrawing); CONNECT_FUNCTION("EndDrawing", OnEndDrawing); CONNECT_FUNCTION("FinishContour", OnFinish); CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); } void mitk::EditableContourTool::Activated() { Superclass::Activated(); this->ResetToStartState(); this->EnableContourInteraction(true); } void mitk::EditableContourTool::Deactivated() { this->ClearSegmentation(); Superclass::Deactivated(); } void mitk::EditableContourTool::ConfirmSegmentation(bool resetStatMachine) { auto referenceImage = this->GetReferenceData(); auto workingImage = this->GetWorkingData(); if (nullptr != referenceImage && nullptr != workingImage) { std::vector sliceInfos; const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); auto contour = this->GetContour(); if (nullptr == contour || contour->IsEmpty()) return; auto workingSlice = this->GetAffectedImageSliceAs2DImage(m_PlaneGeometry, workingImage, workingImageTimeStep)->Clone(); sliceInfos.emplace_back(workingSlice, m_PlaneGeometry, workingImageTimeStep); auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); if (!m_AddMode) { activePixelValue = 0; } ContourModelUtils::FillContourInSlice(projectedContour, workingSlice, workingImage, activePixelValue); this->WriteBackSegmentationResults(sliceInfos); } this->ReleaseHelperObjects(); this->ReleaseInteractors(); if (resetStatMachine) this->ResetToStartState(); } void mitk::EditableContourTool::ClearSegmentation() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); this->ResetToStartState(); } bool mitk::EditableContourTool::IsEditingContour() const { return (nullptr != GetContour()) && !this->IsDrawingContour(); }; bool mitk::EditableContourTool::IsDrawingContour() const { return m_PreviewContour.IsNotNull(); }; -bool mitk::EditableContourTool::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent *positionEvent, - mitk::BaseData *data) -{ - bool isPositionEventInsideImageRegion = - nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); - - if (!isPositionEventInsideImageRegion) - MITK_WARN("EditableContourTool") << "PositionEvent is outside ImageRegion!"; - - return isPositionEventInsideImageRegion; -} - mitk::Point3D mitk::EditableContourTool::PrepareInitContour(const Point3D& clickedPoint) { //default implementation does nothing return clickedPoint; } void mitk::EditableContourTool::OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; auto workingDataNode = this->GetWorkingDataNode(); if (nullptr != this->GetContour()) { this->ConfirmSegmentation(false); } if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) { this->ResetToStartState(); return; } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); auto contour = this->CreateNewContour(); m_ContourNode = mitk::DataNode::New(); m_ContourNode->SetData(contour); m_ContourNode->SetName("working contour node"); m_ContourNode->SetProperty("layer", IntProperty::New(100)); m_ContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_ContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_ContourNode->AddProperty("contour.color", ColorProperty::New(1.0f, 1.0f, 0.0f), nullptr, true); m_ContourNode->AddProperty("contour.points.color", ColorProperty::New(1.0f, 0.0f, 0.1f), nullptr, true); m_ContourNode->AddProperty("contour.controlpoints.show", BoolProperty::New(true), nullptr, true); m_PreviewContour = this->CreateNewContour(); m_PreviewContourNode = mitk::DataNode::New(); m_PreviewContourNode->SetData(m_PreviewContour); m_PreviewContourNode->SetName("active preview node"); m_PreviewContourNode->SetProperty("layer", IntProperty::New(101)); m_PreviewContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_PreviewContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_PreviewContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_PreviewContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); m_ClosureContour = this->CreateNewContour(); m_ClosureContourNode = mitk::DataNode::New(); m_ClosureContourNode->SetData(m_ClosureContour); m_ClosureContourNode->SetName("active closure node"); m_ClosureContourNode->SetProperty("layer", IntProperty::New(101)); m_ClosureContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_ClosureContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_ClosureContourNode->AddProperty("contour.color", ColorProperty::New(0.0f, 1.0f, 0.1f), nullptr, true); m_ClosureContourNode->AddProperty("contour.width", mitk::FloatProperty::New(2.0f), nullptr, true); m_CurrentRestrictedArea = this->CreateNewContour(); auto dataStorage = this->GetToolManager()->GetDataStorage(); dataStorage->Add(m_ContourNode, workingDataNode); dataStorage->Add(m_PreviewContourNode, workingDataNode); dataStorage->Add(m_ClosureContourNode, workingDataNode); m_ReferenceDataSlice = this->GetAffectedReferenceSlice(positionEvent); auto origin = m_ReferenceDataSlice->GetSlicedGeometry()->GetOrigin(); m_ReferenceDataSlice->GetSlicedGeometry()->WorldToIndex(origin, origin); m_ReferenceDataSlice->GetSlicedGeometry()->IndexToWorld(origin, origin); m_ReferenceDataSlice->GetSlicedGeometry()->SetOrigin(origin); // Remember PlaneGeometry to determine if events were triggered in the same plane m_PlaneGeometry = interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); // Map click to pixel coordinates auto click = positionEvent->GetPositionInWorld(); click = this->PrepareInitContour(click); this->InitializePreviewContour(click); // Set initial start point contour->AddVertex(click, true); m_PreviewContour->AddVertex(click, false); m_ClosureContour->AddVertex(click); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::FinalizePreviewContour(const Point3D& /*clickedPoint*/) { // Remove duplicate first vertex, it's already contained in m_ContourNode m_PreviewContour->RemoveVertexAt(0); m_PreviewContour->SetControlVertexAt(m_PreviewContour->GetNumberOfVertices() - 1); } void mitk::EditableContourTool::InitializePreviewContour(const Point3D& clickedPoint) { //default implementation only clears the preview and sets the start point m_PreviewContour = this->CreateNewContour(); m_PreviewContour->AddVertex(clickedPoint); m_PreviewContourNode->SetData(m_PreviewContour); } void mitk::EditableContourTool::UpdatePreviewContour(const Point3D& clickedPoint) { //default implementation draws just a simple line to position if (m_PreviewContour->GetNumberOfVertices() > 2) { auto contour = this->GetContour(); this->InitializePreviewContour(contour->GetVertexAt(contour->GetNumberOfVertices() - 1)->Coordinates); } if (m_PreviewContour->GetNumberOfVertices() == 2) { m_PreviewContour->RemoveVertexAt(m_PreviewContour->GetNumberOfVertices()-1); } m_PreviewContour->AddVertex(clickedPoint); } void mitk::EditableContourTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } this->FinalizePreviewContour(positionEvent->GetPositionInWorld()); // Merge contours auto contour = this->GetContour(); contour->Concatenate(m_PreviewContour); this->InitializePreviewContour(positionEvent->GetPositionInWorld()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::OnDrawing(StateMachineAction*, InteractionEvent* interactionEvent) { auto* positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_PreviewContourNode->SetVisibility(false); auto contour = this->GetContour(); contour->AddVertex(positionEvent->GetPositionInWorld(), false); UpdateClosureContour(positionEvent->GetPositionInWorld()); assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::OnEndDrawing(StateMachineAction*, InteractionEvent* interactionEvent) { auto* positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; auto contour = this->GetContour(); auto controlVs = contour->GetControlVertices(0); if (!controlVs.empty()) { //add the last control point (after that the draw part start) m_CurrentRestrictedArea->AddVertex(controlVs.back()->Coordinates); } m_PreviewContourNode->SetVisibility(true); contour->SetControlVertexAt(contour->GetNumberOfVertices() - 1); //add the just created/set last control point (with it the draw part ends) m_CurrentRestrictedArea->AddVertex(contour->GetVertexAt(contour->GetNumberOfVertices() - 1)->Coordinates); this->InitializePreviewContour(positionEvent->GetPositionInWorld()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::OnMouseMoved(StateMachineAction*, InteractionEvent* interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } this->UpdatePreviewContour(positionEvent->GetPositionInWorld()); this->UpdateClosureContour(positionEvent->GetPositionInWorld()); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::EditableContourTool::OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } this->FinalizePreviewContour(positionEvent->GetPositionInWorld()); this->FinishTool(); // Merge contours auto contour = this->GetContour(); contour->Concatenate(m_PreviewContour); auto numberOfTimesteps = static_cast(contour->GetTimeSteps()); for (int i = 0; i <= numberOfTimesteps; ++i) contour->Close(i); this->ReleaseHelperObjects(false); if (m_AutoConfirm) { this->ConfirmSegmentation(); } } void mitk::EditableContourTool::ReleaseHelperObjects(bool includeWorkingContour) { this->RemoveHelperObjectsFromDataStorage(includeWorkingContour); if (includeWorkingContour) { m_ContourNode = nullptr; m_CurrentRestrictedArea = nullptr; } m_PreviewContourNode = nullptr; m_PreviewContour = nullptr; m_ClosureContourNode = nullptr; m_ClosureContour = nullptr; } void mitk::EditableContourTool::RemoveHelperObjectsFromDataStorage(bool includeWorkingContour) { auto dataStorage = this->GetToolManager()->GetDataStorage(); if (nullptr == dataStorage) return; if (includeWorkingContour) { if (m_ContourNode.IsNotNull()) dataStorage->Remove(m_ContourNode); } if (m_PreviewContourNode.IsNotNull()) dataStorage->Remove(m_PreviewContourNode); if (m_ClosureContourNode.IsNotNull()) dataStorage->Remove(m_ClosureContourNode); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } mitk::ContourModel::Pointer mitk::EditableContourTool::CreateNewContour() const { auto workingData = this->GetWorkingData(); if (nullptr == workingData) { this->InteractiveSegmentationBugMessage( "Cannot create new contour. No valid working data is set. Application is in invalid state."); mitkThrow() << "Cannot create new contour. No valid working data is set. Application is in invalid state."; } auto contour = ContourModel::New(); // generate a time geometry that is always visible as the working contour should always be. auto contourTimeGeometry = ProportionalTimeGeometry::New(); contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); contour->SetTimeGeometry(contourTimeGeometry); return contour; } void mitk::EditableContourTool::UpdateClosureContour(mitk::Point3D endpoint) { if (m_ClosureContour->GetNumberOfVertices() > 2) { m_ClosureContour = this->CreateNewContour(); m_ClosureContourNode->SetData(m_ClosureContour); } if (m_ClosureContour->GetNumberOfVertices() == 0) { auto contour = this->GetContour(); m_ClosureContour->AddVertex(contour->GetVertexAt(0)->Coordinates); m_ClosureContour->Update(); } if (m_ClosureContour->GetNumberOfVertices() == 2) { m_ClosureContour->RemoveVertexAt(0); } m_ClosureContour->AddVertexAtFront(endpoint); } void mitk::EditableContourTool::EnableContourInteraction(bool on) { if (m_ContourInteractor.IsNotNull()) { m_ContourInteractor->EnableInteraction(on); } } void mitk::EditableContourTool::ReleaseInteractors() { this->EnableContourInteraction(false); m_ContourInteractor = nullptr; } mitk::ContourModel* mitk::EditableContourTool::GetContour() { if (this->m_ContourNode.IsNotNull()) { return dynamic_cast(this->m_ContourNode->GetData()); } return nullptr; }; const mitk::ContourModel* mitk::EditableContourTool::GetContour() const { if (this->m_ContourNode.IsNotNull()) { return dynamic_cast(this->m_ContourNode->GetData()); } return nullptr; }; \ No newline at end of file diff --git a/Modules/Segmentation/Interactions/mitkEditableContourTool.h b/Modules/Segmentation/Interactions/mitkEditableContourTool.h index 8be7c24ac9..1189e3b406 100644 --- a/Modules/Segmentation/Interactions/mitkEditableContourTool.h +++ b/Modules/Segmentation/Interactions/mitkEditableContourTool.h @@ -1,127 +1,125 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkEditbaleContourTool_h_Included #define mitkEditbaleContourTool_h_Included #include namespace mitk { /** * Base class for lasso like tools that allow to draw closed contours with multiple ancor points. * The segments between the ancor points may be freehand contour segments or computed segments * (e.g. straight lines or live wire). Derive from the class to implement the computation of the non-freehand * segments. * @sa LassoTool LivewWireTool2D */ class MITKSEGMENTATION_EXPORT EditableContourTool : public FeedbackContourTool { public: mitkClassMacro(EditableContourTool, FeedbackContourTool); /// \brief Convert current contour to binary segmentations. virtual void ConfirmSegmentation(bool resetStatMachine = true); /// \brief Delete all current contours. virtual void ClearSegmentation(); itkBooleanMacro(AutoConfirm); itkSetMacro(AutoConfirm, bool); itkGetMacro(AutoConfirm, bool); itkBooleanMacro(AddMode); itkSetMacro(AddMode, bool); itkGetMacro(AddMode, bool); /* Indicated if a contour is drawn, but not confirmed yet. This the tool is in interactor mode to allow users to edit the contour. This state can be reached if AutoConfirm is false, after the finalizing double click before the contour is confirmed.*/ bool IsEditingContour() const; /* Indicate if a contour is currently drawn by the user (state between the initializing double click and the finalizing double click).*/ bool IsDrawingContour() const; protected: EditableContourTool(); ~EditableContourTool() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; virtual Point3D PrepareInitContour(const Point3D& clickedPoint); virtual void FinalizePreviewContour(const Point3D& clickedPoint); virtual void InitializePreviewContour(const Point3D& clickedPoint); virtual void UpdatePreviewContour(const Point3D& clickedPoint); /// \brief Initialize tool. virtual void OnInitContour(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Add a control point and finish current segment. virtual void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Draw a contour according to the mouse movement when mouse button is pressed and mouse is moved. virtual void OnDrawing(StateMachineAction *, InteractionEvent *interactionEvent); virtual void OnEndDrawing(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Computation of the preview contour. virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Finish EditableContour tool. virtual void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Finish contour interaction. virtual void FinishTool() = 0; void EnableContourInteraction(bool on); void ReleaseInteractors(); virtual void ReleaseHelperObjects(bool includeWorkingContour = true); virtual void RemoveHelperObjectsFromDataStorage(bool includeWorkingContour = true); ContourModel::Pointer CreateNewContour() const; virtual void UpdateClosureContour(mitk::Point3D endpoint); - bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); - mitk::ContourModel* GetContour(); const mitk::ContourModel* GetContour() const; mitk::DataNode::Pointer m_ContourNode; mitk::ContourModel::Pointer m_PreviewContour; mitk::DataNode::Pointer m_PreviewContourNode; mitk::ContourModel::Pointer m_ClosureContour; mitk::DataNode::Pointer m_ClosureContourNode; mitk::ContourModel::Pointer m_CurrentRestrictedArea; /** Slice of the reference data the tool is currently actively working on to define contours.*/ mitk::Image::Pointer m_ReferenceDataSlice; PlaneGeometry::ConstPointer m_PlaneGeometry; mitk::DataInteractor::Pointer m_ContourInteractor; bool m_AutoConfirm; bool m_AddMode; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkErasePaintbrushTool.cpp b/Modules/Segmentation/Interactions/mitkErasePaintbrushTool.cpp index 88a3589bcc..f57cc037be 100644 --- a/Modules/Segmentation/Interactions/mitkErasePaintbrushTool.cpp +++ b/Modules/Segmentation/Interactions/mitkErasePaintbrushTool.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 "mitkErasePaintbrushTool.h" #include "mitkErasePaintbrushTool.xpm" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, ErasePaintbrushTool, "Paintbrush erasing tool"); } mitk::ErasePaintbrushTool::ErasePaintbrushTool() : PaintbrushTool(0) { FeedbackContourTool::SetFeedbackContourColor(1.0, 0.0, 0.0); } mitk::ErasePaintbrushTool::~ErasePaintbrushTool() { } const char **mitk::ErasePaintbrushTool::GetXPM() const { return mitkErasePaintbrushTool_xpm; } us::ModuleResource mitk::ErasePaintbrushTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Wipe_48x48.png"); + us::ModuleResource resource = module->GetResource("Wipe.svg"); return resource; } us::ModuleResource mitk::ErasePaintbrushTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Wipe_Cursor_32x32.png"); + us::ModuleResource resource = module->GetResource("Wipe_Cursor.svg"); return resource; } const char *mitk::ErasePaintbrushTool::GetName() const { return "Wipe"; } diff --git a/Modules/Segmentation/Interactions/mitkEraseRegionTool.cpp b/Modules/Segmentation/Interactions/mitkEraseRegionTool.cpp index e1d480645d..c3003cf49d 100644 --- a/Modules/Segmentation/Interactions/mitkEraseRegionTool.cpp +++ b/Modules/Segmentation/Interactions/mitkEraseRegionTool.cpp @@ -1,59 +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. ============================================================================*/ #include "mitkEraseRegionTool.h" #include "mitkEraseRegionTool.xpm" +#include +#include +#include // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, EraseRegionTool, "Erase tool"); } -mitk::EraseRegionTool::EraseRegionTool() : SetRegionTool(0) -{ - FeedbackContourTool::SetFeedbackContourColor(1.0, 1.0, 0.0); -} - -mitk::EraseRegionTool::~EraseRegionTool() -{ -} - 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_48x48.png"); + 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_32x32.png"); + 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 +{ + auto labelSetImage = dynamic_cast(this->GetWorkingData()); + + 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->GetExteriorLabel()->GetValue()) + { //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*/) +{ + auto labelSetImage = dynamic_cast(this->GetWorkingData()); + if (nullptr != labelSetImage) + { + m_FillLabelValue = labelSetImage->GetExteriorLabel()->GetValue(); + } +}; diff --git a/Modules/Segmentation/Interactions/mitkEraseRegionTool.h b/Modules/Segmentation/Interactions/mitkEraseRegionTool.h index 5316ce5dc1..abeeeb476b 100644 --- a/Modules/Segmentation/Interactions/mitkEraseRegionTool.h +++ b/Modules/Segmentation/Interactions/mitkEraseRegionTool.h @@ -1,61 +1,64 @@ /*============================================================================ 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 mitkEraseRegionTool_h_Included #define mitkEraseRegionTool_h_Included -#include "mitkSetRegionTool.h" +#include "mitkFillRegionBaseTool.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief Erase the inside of a contour by filling the inside of a contour with the background pixel value. \sa SetRegionTool \ingroup Interactions Finds the outer contour of a shape in 2D (possibly including single patches) and sets all the pixels inside to the background pixel value (erasing a segmentation). If clicked on the background, the outer contour might contain the whole image and thus fill the whole image with the background pixel value. \warning Only to be instantiated by mitk::ToolManager. */ - class MITKSEGMENTATION_EXPORT EraseRegionTool : public SetRegionTool + class MITKSEGMENTATION_EXPORT EraseRegionTool : public FillRegionBaseTool { public: - mitkClassMacro(EraseRegionTool, SetRegionTool); + mitkClassMacro(EraseRegionTool, FillRegionBaseTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - const char **GetXPM() const override; + const char **GetXPM() const override; us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; protected: - EraseRegionTool(); // purposely hidden - ~EraseRegionTool() override; + EraseRegionTool() = default; // purposely hidden + ~EraseRegionTool() = default; + + Image::Pointer GenerateFillImage(const Image* workingSlice, Point3D seedPoint, mitk::Label::PixelType& seedLabelValue) const override; + void PrepareFilling(const Image* workingSlice, Point3D seedPoint) override; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFillRegionBaseTool.cpp b/Modules/Segmentation/Interactions/mitkFillRegionBaseTool.cpp new file mode 100644 index 0000000000..403b705cd7 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkFillRegionBaseTool.cpp @@ -0,0 +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 "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; + } + + auto label = labelSetImage->GetLabel(m_SeedLabelValue, labelSetImage->GetActiveLayer()); + + if (label->GetLocked() && label->GetValue()!=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()->GetValue()); + if (nullptr != activeLabelClone) + { + activeLabelClone->SetLocked(false); + } + + TransferLabelContent(fillImage, workingSlice, fillLabelSet, 0, labelSetImage->GetExteriorLabel()->GetValue(), 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/mitkFillRegionBaseTool.h b/Modules/Segmentation/Interactions/mitkFillRegionBaseTool.h new file mode 100644 index 0000000000..8235310900 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkFillRegionBaseTool.h @@ -0,0 +1,83 @@ +/*============================================================================ + +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 mitkFillRegionBaseTool_h_Included +#define mitkFillRegionBaseTool_h_Included + +#include "mitkCommon.h" +#include "mitkContourModelUtils.h" +#include "mitkContourUtils.h" //TODO remove legacy support +#include "mitkImage.h" +#include "mitkSegTool2D.h" +#include + +#include "mitkDataNode.h" + +#include "mitkImageCast.h" + +namespace mitk +{ + /** + \brief Base class for tools that fill a connected region of a 2D slice + + \sa Tool + + \ingroup Interaction + \ingroup ToolManagerEtAl + + \warning Only to be instantiated by mitk::ToolManager. + + $Author: nolden $ + */ + class MITKSEGMENTATION_EXPORT FillRegionBaseTool : public SegTool2D + { + public: + mitkClassMacro(FillRegionBaseTool, SegTool2D); + + protected: + FillRegionBaseTool(); // purposely hidden + ~FillRegionBaseTool() override; + + void ConnectActionsAndFunctions() override; + + /// \brief Add a control point and finish current segment. + virtual void OnClick(StateMachineAction*, InteractionEvent* interactionEvent); + + /** Function that generates the mask image that indicates which pixels should be filled. + * Caller of this function assumes that all pixels that should be filled have the value 1. + * Pixels that should stay untouched should have the value 0. + * The default implementation marks the connected reagion around seedPoint, that has + * the same pixel value/label like the seedPoint. + * You may reimplement this function to change the strategy to determin the fill region. + * @param workingSlice part of the segmentation image that should be used to determin the fill image. + * @param seedPoint The world coordinate position where the user has cliced. + * @param [out] seedLabelValue The function should return the label value that should be assumed + * as clicked on, given the seedPoint. + * @return Return the image maske that indicates which pixels should be filled. Returning + * a null pointer indicates that there is nothing to fill. + */ + virtual Image::Pointer GenerateFillImage(const Image* workingSlice, Point3D seedPoint, mitk::Label::PixelType& seedLabelValue) const; + + /** Function that is called by OnClick before the filling is executed. If you want to do special + * preperation (e.g. change m_FillLabelValue, you can overwrite this function. */ + virtual void PrepareFilling(const Image* workingSlice, Point3D seedPoint) = 0; + + Label::PixelType m_FillLabelValue = 0; + Label::PixelType m_SeedLabelValue = 0; + + MultiLabelSegmentation::MergeStyle m_MergeStyle = MultiLabelSegmentation::MergeStyle::Replace; + private: + }; + +} // namespace + +#endif diff --git a/Modules/Segmentation/Interactions/mitkFillRegionTool.cpp b/Modules/Segmentation/Interactions/mitkFillRegionTool.cpp index 1ef851a209..bde58bce1a 100644 --- a/Modules/Segmentation/Interactions/mitkFillRegionTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFillRegionTool.cpp @@ -1,58 +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" -#include "mitkFillRegionTool.xpm" - // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, FillRegionTool, "Fill tool"); } -mitk::FillRegionTool::FillRegionTool() : SetRegionTool(1) -{ -} - -mitk::FillRegionTool::~FillRegionTool() -{ -} - const char **mitk::FillRegionTool::GetXPM() const { - return mitkFillRegionTool_xpm; + return nullptr; } us::ModuleResource mitk::FillRegionTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Fill_48x48.png"); + 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_32x32.png"); + 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()->GetValue(); + m_MergeStyle = MultiLabelSegmentation::MergeStyle::Merge; +}; + diff --git a/Modules/Segmentation/Interactions/mitkFillRegionTool.h b/Modules/Segmentation/Interactions/mitkFillRegionTool.h index 3643b13992..e7a1247858 100644 --- a/Modules/Segmentation/Interactions/mitkFillRegionTool.h +++ b/Modules/Segmentation/Interactions/mitkFillRegionTool.h @@ -1,60 +1,62 @@ /*============================================================================ 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 mitkFillRegionTool_h_Included #define mitkFillRegionTool_h_Included -#include "mitkSetRegionTool.h" +#include "mitkFillRegionBaseTool.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief Fill the inside of a contour with the foreground pixel value. \sa SetRegionTool \ingroup Interactions Finds the outer contour of a shape in 2D (possibly including holes) and sets all the pixels inside to the foreground pixel value (filling holes in a segmentation). If clicked on the background, the outer contour might contain the whole image and thus fill the whole image with the foreground pixel value. \warning Only to be instantiated by mitk::ToolManager. */ - class MITKSEGMENTATION_EXPORT FillRegionTool : public SetRegionTool + class MITKSEGMENTATION_EXPORT FillRegionTool : public FillRegionBaseTool { public: - mitkClassMacro(FillRegionTool, SetRegionTool); + mitkClassMacro(FillRegionTool, FillRegionBaseTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - const char **GetXPM() const override; + const char **GetXPM() const override; us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; protected: - FillRegionTool(); // purposely hidden - ~FillRegionTool() override; + void PrepareFilling(const Image* workingSlice, Point3D seedPoint) override; + + FillRegionTool() = default; // purposely hidden + ~FillRegionTool() = default; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkGrowCutTool.cpp b/Modules/Segmentation/Interactions/mitkGrowCutTool.cpp new file mode 100644 index 0000000000..005804c177 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkGrowCutTool.cpp @@ -0,0 +1,153 @@ +/*============================================================================ + +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 "mitkGrowCutTool.h" +#include "mitkToolManager.h" +#include "mitkImageCast.h" +#include "mitkTool.h" +#include +#include +#include "mitkGrowCutSegmentationFilter.h" + +// us +#include +#include +#include +#include + +// ITK +#include + +namespace mitk +{ + MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, GrowCutTool, "GrowCutTool"); +} + +mitk::GrowCutTool::GrowCutTool() : SegWithPreviewTool(false, "PressMoveReleaseAndPointSetting") +{ + this->ResetsToEmptyPreviewOn(); +} + +mitk::GrowCutTool::~GrowCutTool() {} + +bool mitk::GrowCutTool::CanHandle(const BaseData *referenceData, const BaseData *workingData) const +{ + if (!Superclass::CanHandle(referenceData, workingData)) + return false; + + auto *image = dynamic_cast(referenceData); + + if (image == nullptr) + return false; + + return true; +} + +const char **mitk::GrowCutTool::GetXPM() const +{ + return nullptr; +} + +const char *mitk::GrowCutTool::GetName() const +{ + return "GrowCut"; +} + +us::ModuleResource mitk::GrowCutTool::GetIconResource() const +{ + us::Module *module = us::GetModuleContext()->GetModule(); + us::ModuleResource resource = module->GetResource("GrowCut.svg"); + return resource; +} + +void mitk::GrowCutTool::Activated() +{ + Superclass::Activated(); + + m_DistancePenalty = 0.0; +} + +void mitk::GrowCutTool::Deactivated() +{ + Superclass::Deactivated(); +} + +bool mitk::GrowCutTool::HasMoreThanTwoSeedLabel() +{ + if (nullptr == this->GetToolManager()->GetWorkingData(0)) + { + return false; + } + + auto *workingDataLabelSetImage = + dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); + + if (nullptr == workingDataLabelSetImage) + { + return false; + } + + workingDataLabelSetImage->SetActiveLayer(0); + auto numberOfLabels = workingDataLabelSetImage->GetNumberOfLabels(); + + if (numberOfLabels >= 3) + { + return true; + } + + return false; +} + +void mitk::GrowCutTool::DoUpdatePreview(const Image *inputAtTimeStep, + const Image * /*oldSegAtTimeStep*/, + LabelSetImage *previewImage, + TimeStepType timeStep) +{ + if (nullptr != inputAtTimeStep && + nullptr != previewImage) + { + mitk::GrowCutSegmentationFilter::Pointer growCutFilter = mitk::GrowCutSegmentationFilter::New(); + + if (nullptr == this->GetToolManager()->GetWorkingData(0)) + { + return; + } + + auto *workingDataLabelSetImage = dynamic_cast(this->GetToolManager()->GetWorkingData(0)->GetData()); + + if (nullptr == workingDataLabelSetImage) + { + return; + } + + SeedImageType::Pointer seedImage = SeedImageType::New(); + workingDataLabelSetImage->SetActiveLayer(0); + CastToItkImage(workingDataLabelSetImage, seedImage); + + growCutFilter->SetSeedImage(seedImage); + growCutFilter->SetDistancePenalty(m_DistancePenalty); + growCutFilter->SetInput(inputAtTimeStep); + + try + { + growCutFilter->Update(); + } + catch (...) + { + mitkThrow() << "itkGrowCutFilter error"; + } + + auto growCutResultImage = mitk::LabelSetImage::New(); + growCutResultImage->InitializeByLabeledImage(growCutFilter->GetOutput()); + TransferLabelSetImageContent(growCutResultImage, previewImage, timeStep); + } +} diff --git a/Modules/Segmentation/Interactions/mitkGrowCutTool.h b/Modules/Segmentation/Interactions/mitkGrowCutTool.h new file mode 100644 index 0000000000..4860df13a2 --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkGrowCutTool.h @@ -0,0 +1,64 @@ +/*============================================================================ + +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 mitkGrowCutTool_h_Included +#define mitkGrowCutTool_h_Included + +#include "mitkSegWithPreviewTool.h" +#include + +namespace us +{ + class ModuleResource; +} + +namespace mitk +{ + class MITKSEGMENTATION_EXPORT GrowCutTool : public SegWithPreviewTool + { + public: + mitkClassMacro(GrowCutTool, SegWithPreviewTool); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + + itkSetMacro(DistancePenalty, double); + itkGetConstMacro(DistancePenalty, double); + + typedef itk::Image SeedImageType; + typedef typename SeedImageType::IndexType IndexType; + + const char **GetXPM() const override; + const char *GetName() const override; + us::ModuleResource GetIconResource() const override; + + bool CanHandle(const BaseData *referenceData, const BaseData *workingData) const override; + + void Activated() override; + void Deactivated() override; + + bool HasMoreThanTwoSeedLabel(); + + protected: + GrowCutTool(); // purposely hidden + ~GrowCutTool() override; + + void DoUpdatePreview(const Image *inputAtTimeStep, + const Image *oldSegAtTimeStep, + LabelSetImage *previewImage, + TimeStepType timeStep) override; + + double m_DistancePenalty = 0.0; + }; + +} // namespace mitk + +#endif diff --git a/Modules/Segmentation/Interactions/mitkLassoTool.cpp b/Modules/Segmentation/Interactions/mitkLassoTool.cpp index 84adb3ee05..0db153a207 100644 --- a/Modules/Segmentation/Interactions/mitkLassoTool.cpp +++ b/Modules/Segmentation/Interactions/mitkLassoTool.cpp @@ -1,70 +1,70 @@ /*============================================================================ 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 mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, LassoTool, "Lasso tool"); } mitk::LassoTool::LassoTool() : EditableContourTool() { } mitk::LassoTool::~LassoTool() {} void mitk::LassoTool::ConnectActionsAndFunctions() { mitk::EditableContourTool::ConnectActionsAndFunctions(); CONNECT_FUNCTION("MovePoint", OnMouseMoved); } const char **mitk::LassoTool::GetXPM() const { return nullptr; } us::ModuleResource mitk::LassoTool::GetIconResource() const { - return us::GetModuleContext()->GetModule()->GetResource("NewAdd_48x48.png"); + return us::GetModuleContext()->GetModule()->GetResource("Lasso.svg"); } us::ModuleResource mitk::LassoTool::GetCursorIconResource() const { - return us::GetModuleContext()->GetModule()->GetResource("NewAdd_Cursor_32x32.png"); + return us::GetModuleContext()->GetModule()->GetResource("Lasso_Cursor.svg"); } const char *mitk::LassoTool::GetName() const { return "Lasso"; } void mitk::LassoTool::FinishTool() { auto contourInteractor = mitk::ContourModelInteractor::New(); contourInteractor->SetDataNode(m_ContourNode); contourInteractor->LoadStateMachine("ContourModelModificationInteractor.xml", us::GetModuleContext()->GetModule()); contourInteractor->SetEventConfig("ContourModelModificationConfig.xml", us::GetModuleContext()->GetModule()); contourInteractor->SetRestrictedArea(this->m_CurrentRestrictedArea); this->m_ContourInteractor = contourInteractor; m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); } diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp index 861f2bd557..b7a4cd5ad5 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -1,261 +1,261 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, LiveWireTool2D, "LiveWire tool"); } mitk::LiveWireTool2D::LiveWireTool2D() : EditableContourTool(), m_CreateAndUseDynamicCosts(false) { } mitk::LiveWireTool2D::~LiveWireTool2D() { } void mitk::LiveWireTool2D::ConnectActionsAndFunctions() { mitk::EditableContourTool::ConnectActionsAndFunctions(); CONNECT_FUNCTION("MovePoint", OnMouseMoveNoDynamicCosts); } const char **mitk::LiveWireTool2D::GetXPM() const { return mitkLiveWireTool2D_xpm; } us::ModuleResource mitk::LiveWireTool2D::GetIconResource() const { - return us::GetModuleContext()->GetModule()->GetResource("LiveWire_48x48.png"); + return us::GetModuleContext()->GetModule()->GetResource("LiveWire.svg"); } us::ModuleResource mitk::LiveWireTool2D::GetCursorIconResource() const { - return us::GetModuleContext()->GetModule()->GetResource("LiveWire_Cursor_32x32.png"); + return us::GetModuleContext()->GetModule()->GetResource("LiveWire_Cursor.svg"); } const char *mitk::LiveWireTool2D::GetName() const { return "Live Wire"; } void mitk::LiveWireTool2D::UpdateLiveWireContour() { auto contour = this->GetContour(); if (nullptr != contour) { auto timeGeometry = contour->GetTimeGeometry()->Clone(); m_PreviewContour = this->m_LiveWireFilter->GetOutput(); // needed because the results of the filter are always from 0 ms // to 1 ms and the filter also resets its outputs. // generate a time geometry that is always visible as the working contour should always be. auto contourTimeGeometry = ProportionalTimeGeometry::New(); contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); m_PreviewContour->SetTimeGeometry(contourTimeGeometry); m_PreviewContourNode->SetData(this->m_PreviewContour); } } void mitk::LiveWireTool2D::OnTimePointChanged() { auto reference = this->GetReferenceData(); if (nullptr == reference || m_PlaneGeometry.IsNull() || m_LiveWireFilter.IsNull() || m_PreviewContourNode.IsNull()) return; auto timeStep = reference->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); m_ReferenceDataSlice = GetAffectedImageSliceAs2DImageByTimePoint(m_PlaneGeometry, reference, timeStep); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); m_LiveWireFilter->Update(); this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdateAll(); }; mitk::Point3D mitk::LiveWireTool2D::PrepareInitContour(const mitk::Point3D& clickedPoint) { // Set current slice as input for ImageToLiveWireContourFilter m_LiveWireFilter = ImageLiveWireContourModelFilter::New(); m_LiveWireFilter->SetUseCostFunction(true); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); itk::Index<3> idx; m_ReferenceDataSlice->GetGeometry()->WorldToIndex(clickedPoint, idx); // Get the pixel with the highest gradient in a 7x7 region itk::Index<3> indexWithHighestGradient; AccessFixedDimensionByItk_2(m_ReferenceDataSlice, FindHighestGradientMagnitudeByITK, 2, idx, indexWithHighestGradient); Point3D adaptedClick; m_ReferenceDataSlice->GetGeometry()->IndexToWorld(indexWithHighestGradient, adaptedClick); m_CreateAndUseDynamicCosts = true; return adaptedClick; } void mitk::LiveWireTool2D::FinalizePreviewContour(const Point3D& clickedPoint) { // Add repulsive points to avoid getting the same path again std::for_each(m_PreviewContour->IteratorBegin(), m_PreviewContour->IteratorEnd(), [this](ContourElement::VertexType* vertex) { ImageLiveWireContourModelFilter::InternalImageType::IndexType idx; this->m_ReferenceDataSlice->GetGeometry()->WorldToIndex(vertex->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); }); EditableContourTool::FinalizePreviewContour(clickedPoint); } void mitk::LiveWireTool2D::InitializePreviewContour(const Point3D& clickedPoint) { m_PreviewContour->Clear(); // Set new start point m_LiveWireFilter->SetStartPoint(clickedPoint); if (m_CreateAndUseDynamicCosts) { auto contour = this->GetContour(); // Use dynamic cost map for next update m_LiveWireFilter->CreateDynamicCostMap(contour); m_LiveWireFilter->SetUseDynamicCostMap(true); } } void mitk::LiveWireTool2D::UpdatePreviewContour(const Point3D& clickedPoint) { // Compute LiveWire segment from last control point to current mouse position m_LiveWireFilter->SetEndPoint(clickedPoint); m_LiveWireFilter->Update(); this->UpdateLiveWireContour(); } void mitk::LiveWireTool2D::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) { m_LiveWireFilter->SetUseDynamicCostMap(false); this->OnMouseMoved(nullptr, interactionEvent); m_LiveWireFilter->SetUseDynamicCostMap(true); } void mitk::LiveWireTool2D::FinishTool() { m_LiveWireFilter->SetUseDynamicCostMap(false); m_ContourInteractor = mitk::ContourModelLiveWireInteractor::New(); m_ContourInteractor->SetDataNode(m_ContourNode); m_ContourInteractor->LoadStateMachine("ContourModelModificationInteractor.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetEventConfig("ContourModelModificationConfig.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetWorkingImage(this->m_ReferenceDataSlice); m_ContourInteractor->SetRestrictedArea(this->m_CurrentRestrictedArea); m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); } template void mitk::LiveWireTool2D::FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; const auto MAX_X = inputImage->GetLargestPossibleRegion().GetSize()[0]; const auto MAX_Y = inputImage->GetLargestPossibleRegion().GetSize()[1]; returnIndex[0] = index[0]; returnIndex[1] = index[1]; returnIndex[2] = 0.0; double gradientMagnitude = 0.0; double maxGradientMagnitude = 0.0; // The size and thus the region of 7x7 is only used to calculate the gradient magnitude in that region, // not for searching the maximum value. // Maximum value in each direction for size typename InputImageType::SizeType size; size[0] = 7; size[1] = 7; // Minimum value in each direction for startRegion IndexType startRegion; startRegion[0] = index[0] - 3; startRegion[1] = index[1] - 3; if (startRegion[0] < 0) startRegion[0] = 0; if (startRegion[1] < 0) startRegion[1] = 0; if (MAX_X - index[0] < 7) startRegion[0] = MAX_X - 7; if (MAX_Y - index[1] < 7) startRegion[1] = MAX_Y - 7; index[0] = startRegion[0] + 3; index[1] = startRegion[1] + 3; typename InputImageType::RegionType region; region.SetSize(size); region.SetIndex(startRegion); typedef typename itk::GradientMagnitudeImageFilter GradientMagnitudeFilterType; typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); gradientFilter->SetInput(inputImage); gradientFilter->GetOutput()->SetRequestedRegion(region); gradientFilter->Update(); typename InputImageType::Pointer gradientMagnitudeImage; gradientMagnitudeImage = gradientFilter->GetOutput(); IndexType currentIndex; currentIndex[0] = 0; currentIndex[1] = 0; // Search max (approximate) gradient magnitude for (int x = -1; x <= 1; ++x) { currentIndex[0] = index[0] + x; for (int y = -1; y <= 1; ++y) { currentIndex[1] = index[1] + y; gradientMagnitude = gradientMagnitudeImage->GetPixel(currentIndex); // Check for new max if (maxGradientMagnitude < gradientMagnitude) { maxGradientMagnitude = gradientMagnitude; returnIndex[0] = currentIndex[0]; returnIndex[1] = currentIndex[1]; returnIndex[2] = 0.0; } } currentIndex[1] = index[1]; } } diff --git a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp b/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp index a7ce47fcec..ec9cc831fc 100644 --- a/Modules/Segmentation/Interactions/mitkOtsuTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkOtsuTool3D.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. ============================================================================*/ // MITK #include "mitkOtsuTool3D.h" #include "mitkOtsuSegmentationFilter.h" // us #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, OtsuTool3D, "Otsu Segmentation"); } void mitk::OtsuTool3D::Activated() { Superclass::Activated(); m_NumberOfBins = 128; m_NumberOfRegions = 2; m_UseValley = false; this->SetLabelTransferMode(LabelTransferMode::AllLabels); } 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_48x48.png"); + 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 = mitk::LabelSetImage::New(); otsuResultImage->InitializeByLabeledImage(otsuFilter->GetOutput()); TransferLabelSetImageContent(otsuResultImage, previewImage, timeStep); } 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 1482e9018c..432abb62de 100644 --- a/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp @@ -1,579 +1,572 @@ /*============================================================================ 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(int paintingPixelValue) : FeedbackContourTool("PressMoveReleaseWithCTRLInversionAllMouseMoves"), m_PaintingPixelValue(paintingPixelValue), m_LastContourSize(0) // other than initial mitk::PaintbrushTool::m_Size (around l. 28) { m_MasterContour = ContourModel::New(); m_MasterContour->Initialize(); m_CurrentPlane = nullptr; - - m_WorkingNode = DataNode::New(); - m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); - m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true)); } 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(); FeedbackContourTool::SetFeedbackContourVisible(true); 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)); + m_PaintingNode->SetVisibility( + false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); + + this->GetToolManager()->GetDataStorage()->Add(m_PaintingNode); } void mitk::PaintbrushTool::Deactivated() { FeedbackContourTool::SetFeedbackContourVisible(false); - if (this->GetToolManager()->GetDataStorage()->Exists(m_WorkingNode)) - this->GetToolManager()->GetDataStorage()->Remove(m_WorkingNode); + 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; } -/** - Just show the contour, get one point as the central point and add surrounding points to the contour. - */ void mitk::PaintbrushTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { if (m_WorkingSlice.IsNull()) return; auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_WorkingSlice->GetGeometry()->WorldToIndex(positionEvent->GetPositionInWorld(), m_LastPosition); - // create new working node - // a fresh node is needed to only display the actual drawing process for - // the undo function - if (this->GetToolManager()->GetDataStorage()->Exists(m_WorkingNode)) - this->GetToolManager()->GetDataStorage()->Remove(m_WorkingNode); - m_WorkingSlice = nullptr; - m_CurrentPlane = nullptr; - - m_WorkingNode = DataNode::New(); - m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); - m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true)); - - this->m_WorkingNode->SetVisibility(true); + this->m_PaintingNode->SetVisibility(true); m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); + m_PaintingSlice = nullptr; //force reset of the painting slice. Will be triggered in MouseMoved() by + //CheckIfCurrentSliceHasChanged 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); CheckIfCurrentSliceHasChanged(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; - DataNode *workingNode(this->GetToolManager()->GetWorkingData(0)); - auto workingImage = dynamic_cast(workingNode->GetData()); - int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); - - // m_PaintingPixelValue only decides whether to paint or erase - mitk::ContourModelUtils::FillContourInSlice( - contour, m_WorkingSlice, workingImage, m_PaintingPixelValue * activePixelValue); - - m_WorkingNode->SetData(m_WorkingSlice); - m_WorkingNode->Modified(); - // 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]; - contour->Clear(); + auto gapContour = ContourModel::New(); // upper left corner vertex[0] = m_LastPosition[0] + (normal[0] * radius); vertex[1] = m_LastPosition[1] + (normal[1] * radius); - contour->AddVertex(vertex); + gapContour->AddVertex(vertex); // upper right corner vertex[0] = currentPos[0] + (normal[0] * radius); vertex[1] = currentPos[1] + (normal[1] * radius); - contour->AddVertex(vertex); + gapContour->AddVertex(vertex); // lower right corner vertex[0] = currentPos[0] - (normal[0] * radius); vertex[1] = currentPos[1] - (normal[1] * radius); - contour->AddVertex(vertex); + gapContour->AddVertex(vertex); // lower left corner vertex[0] = m_LastPosition[0] - (normal[0] * radius); vertex[1] = m_LastPosition[1] - (normal[1] * radius); - contour->AddVertex(vertex); + gapContour->AddVertex(vertex); - mitk::ContourModelUtils::FillContourInSlice(contour, m_WorkingSlice, workingImage, m_PaintingPixelValue * activePixelValue); - m_WorkingNode->SetData(m_WorkingSlice); - m_WorkingNode->Modified(); + ContourModelUtils::FillContourInSlice2(gapContour, m_PaintingSlice, m_InternalFillValue); } } else { // switched from different renderwindow // no activate hover highlighting. Otherwise undo / redo wont work - this->m_WorkingNode->SetVisibility(false); + this->m_PaintingNode->SetVisibility(false); } m_LastPosition = indexCoordinates; // visualize contour ContourModel::Pointer tmp = FeedbackContourTool::BackProjectContourFrom2DSlice(m_WorkingSlice->GetGeometry(), contour); this->UpdateCurrentFeedbackContour(tmp); 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()); + int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); + + //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 + //this fillLabelSet is used for the transfer. + auto fillLabelSet = workingImage->GetActiveLabelSet()->Clone(); + auto activeLabelClone = fillLabelSet->GetLabel(workingImage->GetActiveLabel()->GetValue()); + if (nullptr != activeLabelClone) + { + activeLabelClone->SetLocked(false); + } + + TransferLabelContent(m_PaintingSlice, m_WorkingSlice, fillLabelSet, 0, workingImage->GetExteriorLabel()->GetValue(), false, { {m_InternalFillValue, m_PaintingPixelValue * activePixelValue} }, mitk::MultiLabelSegmentation::MergeStyle::Merge); + this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice->Clone()); // deactivate visibility of helper node - m_WorkingNode->SetVisibility(false); + m_PaintingNode->SetVisibility(false); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } /** Called when the CTRL key is pressed. Will change the painting pixel value from 0 to 1 or from 1 to 0. */ void mitk::PaintbrushTool::OnInvertLogic(StateMachineAction *, InteractionEvent *) { // Inversion only for 0 and 1 as painting values if (m_PaintingPixelValue == 1) { m_PaintingPixelValue = 0; FeedbackContourTool::SetFeedbackContourColor(1.0, 0.0, 0.0); } else if (m_PaintingPixelValue == 0) { m_PaintingPixelValue = 1; FeedbackContourTool::SetFeedbackContourColorDefault(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void 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; } DataNode* workingNode = this->GetToolManager()->GetWorkingData(0); if (nullptr == workingNode) { return; } Image::Pointer image = dynamic_cast(workingNode->GetData()); if (nullptr == image) { return; } - if (m_CurrentPlane.IsNull() || m_WorkingSlice.IsNull()) + 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; m_WorkingSlice = SegTool2D::GetAffectedImageSliceAs2DImage(event, image)->Clone(); - m_WorkingNode->SetData(m_WorkingSlice); } - else - { - bool isSameSlice(false); - isSameSlice = mitk::MatrixEqualElementWise(planeGeometry->GetIndexToWorldTransform()->GetMatrix(), - m_CurrentPlane->GetIndexToWorldTransform()->GetMatrix()); - isSameSlice = mitk::Equal(planeGeometry->GetIndexToWorldTransform()->GetOffset(), - m_CurrentPlane->GetIndexToWorldTransform()->GetOffset()); - if (!isSameSlice) - { - this->GetToolManager()->GetDataStorage()->Remove(m_WorkingNode); - m_CurrentPlane = planeGeometry; - m_WorkingSlice = SegTool2D::GetAffectedImageSliceAs2DImage(event, image)->Clone(); - m_WorkingNode = mitk::DataNode::New(); - m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); - m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true)); - - m_WorkingNode->SetData(m_WorkingSlice); + if (m_PaintingSlice.IsNull()) + { + m_PaintingSlice = Image::New(); + m_PaintingSlice->Initialize(m_WorkingSlice); - // So that the paintbrush contour vanished in the previous render window - RenderingManager::GetInstance()->RequestUpdateAll(); + 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); } mitk::Color currentColor; if (m_PaintingPixelValue == 1) { currentColor.Set(0.0, 1.0, 0.); } else { currentColor.Set(1.0, 0.0, 0.); } - m_WorkingNode->SetProperty("color", mitk::ColorProperty::New(currentColor[0], currentColor[1], currentColor[2])); - - if (!this->GetToolManager()->GetDataStorage()->Exists(m_WorkingNode)) - { - m_WorkingNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); - m_WorkingNode->SetProperty("name", mitk::StringProperty::New("Paintbrush_Node")); - m_WorkingNode->SetProperty("helper object", mitk::BoolProperty::New(true)); - m_WorkingNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); - m_WorkingNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); - m_WorkingNode->SetVisibility( - false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); - - this->GetToolManager()->GetDataStorage()->Add(m_WorkingNode); - } + m_PaintingNode->SetProperty("color", mitk::ColorProperty::New(currentColor[0], currentColor[1], currentColor[2])); } 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/mitkPaintbrushTool.h b/Modules/Segmentation/Interactions/mitkPaintbrushTool.h index d0d5731aae..7b931405b9 100644 --- a/Modules/Segmentation/Interactions/mitkPaintbrushTool.h +++ b/Modules/Segmentation/Interactions/mitkPaintbrushTool.h @@ -1,100 +1,106 @@ /*============================================================================ 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 mitkPaintbrushTool_h_Included #define mitkPaintbrushTool_h_Included #include "mitkCommon.h" #include "mitkFeedbackContourTool.h" #include namespace mitk { class StateMachineAction; class InteractionEvent; class InteractionPositionEvent; /** \brief Paintbrush tool for InteractiveSegmentation \sa FeedbackContourTool \sa ExtractImageFilter \ingroup Interaction \ingroup ToolManagerEtAl Simple paintbrush drawing tool. Right now there are only circular pens of varying size. \warning Only to be instantiated by mitk::ToolManager. $Author: maleike $ */ class MITKSEGMENTATION_EXPORT PaintbrushTool : public FeedbackContourTool { public: // sent when the pen size is changed or should be updated in a GUI. Message1 SizeChanged; mitkClassMacro(PaintbrushTool, FeedbackContourTool); void SetSize(int value); protected: PaintbrushTool(int paintingPixelValue = 1); // purposely hidden ~PaintbrushTool() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; virtual void OnMousePressed(StateMachineAction *, InteractionEvent *); virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *); virtual void OnPrimaryButtonPressedMoved(StateMachineAction *, InteractionEvent *); virtual void MouseMoved(mitk::InteractionEvent *interactionEvent, bool leftMouseButtonPressed); virtual void OnMouseReleased(StateMachineAction *, InteractionEvent *); virtual void OnInvertLogic(StateMachineAction *, InteractionEvent *); /** * \todo This is a possible place where to introduce * different types of pens */ void UpdateContour(const InteractionPositionEvent *); /** * Little helper function. Returns the upper left corner of the given pixel. */ mitk::Point2D upperLeft(mitk::Point2D p); /** * Checks if the current slice has changed */ void CheckIfCurrentSliceHasChanged(const InteractionPositionEvent *event); void OnToolManagerWorkingDataModified(); + void ResetPaintingSlice(); + int m_PaintingPixelValue; static int m_Size; ContourModel::Pointer m_MasterContour; int m_LastContourSize; + const int m_InternalFillValue = 255; + Image::Pointer m_WorkingSlice; + Image::Pointer m_PaintingSlice; PlaneGeometry::ConstPointer m_CurrentPlane; - DataNode::Pointer m_WorkingNode; + DataNode::Pointer m_PaintingNode; mitk::Point3D m_LastPosition; + }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkPickingTool.cpp b/Modules/Segmentation/Interactions/mitkPickingTool.cpp index 6f94213db6..caccce9710 100644 --- a/Modules/Segmentation/Interactions/mitkPickingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPickingTool.cpp @@ -1,276 +1,275 @@ /*============================================================================ 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 "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() { } bool mitk::PickingTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData,workingData)) return false; auto* image = dynamic_cast(referenceData); if (image == nullptr) return false; return true; } 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("Pick_48x48.png"); + 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()) { Label::PixelType backgroundValue = 0; auto labelSetImage = dynamic_cast(oldSegAtTimeStep); if (nullptr != labelSetImage) { backgroundValue = labelSetImage->GetExteriorLabel()->GetValue(); } AccessFixedDimensionByItk_n(oldSegAtTimeStep, DoITKRegionGrowing, 3, (previewImage, this->m_PointSet, timeStep, oldSegAtTimeStep->GetGeometry(), this->GetUserDefinedActiveLabel(), backgroundValue, emptyTimeStep)); } if (emptyTimeStep) { this->ResetPreviewContentAtTimeStep(timeStep); } } } diff --git a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp index bd0a9d911e..d214716309 100644 --- a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp @@ -1,463 +1,463 @@ /*============================================================================ 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 "mitkRegionGrowingTool.h" #include "mitkBaseRenderer.h" #include "mitkImageToContourModelFilter.h" #include "mitkRegionGrowingTool.xpm" #include "mitkRenderingManager.h" #include "mitkToolManager.h" // us #include #include #include #include // ITK #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, RegionGrowingTool, "Region growing tool"); } #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) mitk::RegionGrowingTool::RegionGrowingTool() : FeedbackContourTool("PressMoveRelease"), m_SeedValue(0), m_ScreenYDifference(0), m_ScreenXDifference(0), m_MouseDistanceScaleFactor(0.5), m_PaintingPixelValue(1), m_FillFeedbackContour(true), m_ConnectedComponentValue(1) { } mitk::RegionGrowingTool::~RegionGrowingTool() { } void mitk::RegionGrowingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); } const char **mitk::RegionGrowingTool::GetXPM() const { return mitkRegionGrowingTool_xpm; } us::ModuleResource mitk::RegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); + us::ModuleResource resource = module->GetResource("RegionGrowing.svg"); return resource; } us::ModuleResource mitk::RegionGrowingTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("RegionGrowing_Cursor_32x32.png"); + us::ModuleResource resource = module->GetResource("RegionGrowing_Cursor.svg"); return resource; } const char *mitk::RegionGrowingTool::GetName() const { return "Region Growing"; } void mitk::RegionGrowingTool::Activated() { Superclass::Activated(); } void mitk::RegionGrowingTool::Deactivated() { Superclass::Deactivated(); } // Get the average pixel value of square/cube with radius=neighborhood around index template void mitk::RegionGrowingTool::GetNeighborhoodAverage(const itk::Image *itkImage, const itk::Index& index, ScalarType *result, unsigned int neighborhood) { // maybe assert that image dimension is only 2 or 3? auto neighborhoodInt = (int)neighborhood; TPixel averageValue(0); unsigned int numberOfPixels = (2 * neighborhood + 1) * (2 * neighborhood + 1); if (imageDimension == 3) { numberOfPixels *= (2 * neighborhood + 1); } MITK_DEBUG << "Getting neighborhood of " << numberOfPixels << " pixels around " << index; itk::Index currentIndex; for (int i = (0 - neighborhoodInt); i <= neighborhoodInt; ++i) { currentIndex[0] = index[0] + i; for (int j = (0 - neighborhoodInt); j <= neighborhoodInt; ++j) { currentIndex[1] = index[1] + j; if (imageDimension == 3) { for (int k = (0 - neighborhoodInt); k <= neighborhoodInt; ++k) { currentIndex[2] = index[2] + k; if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } else { if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } } *result = (ScalarType)averageValue; *result /= numberOfPixels; } // Do the region growing (i.e. call an ITK filter that does it) template void mitk::RegionGrowingTool::StartRegionGrowing(const itk::Image *inputImage, const itk::Index& seedIndex, const std::array& thresholds, mitk::Image::Pointer &outputImage) { MITK_DEBUG << "Starting region growing at index " << seedIndex << " with lower threshold " << thresholds[0] << " and upper threshold " << thresholds[1]; typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); // perform region growing in desired segmented region regionGrower->SetInput(inputImage); regionGrower->SetSeed(seedIndex); regionGrower->SetLower(thresholds[0]); regionGrower->SetUpper(thresholds[1]); try { regionGrower->Update(); } catch (...) { return; // Should we do something? } typename OutputImageType::Pointer resultImage = regionGrower->GetOutput(); // Smooth result: Every pixel is replaced by the majority of the neighborhood typedef itk::NeighborhoodIterator NeighborhoodIteratorType; typedef itk::ImageRegionIterator ImageIteratorType; typename NeighborhoodIteratorType::RadiusType radius; radius.Fill(2); // for now, maybe make this something the user can adjust in the preferences? typedef itk::ImageDuplicator< OutputImageType > DuplicatorType; typename DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage(resultImage); duplicator->Update(); typename OutputImageType::Pointer resultDup = duplicator->GetOutput(); NeighborhoodIteratorType neighborhoodIterator(radius, resultDup, resultDup->GetRequestedRegion()); ImageIteratorType imageIterator(resultImage, resultImage->GetRequestedRegion()); for (neighborhoodIterator.GoToBegin(), imageIterator.GoToBegin(); !neighborhoodIterator.IsAtEnd(); ++neighborhoodIterator, ++imageIterator) { DefaultSegmentationDataType voteYes(0); DefaultSegmentationDataType voteNo(0); for (unsigned int i = 0; i < neighborhoodIterator.Size(); ++i) { if (neighborhoodIterator.GetPixel(i) > 0) { voteYes += 1; } else { voteNo += 1; } } if (voteYes > voteNo) { imageIterator.Set(1); } else { imageIterator.Set(0); } } if (resultImage.IsNull()) { MITK_DEBUG << "Region growing result is empty."; } // Can potentially have multiple regions, use connected component image filter to label disjunct regions typedef itk::ConnectedComponentImageFilter ConnectedComponentImageFilterType; typename ConnectedComponentImageFilterType::Pointer connectedComponentFilter = ConnectedComponentImageFilterType::New(); connectedComponentFilter->SetInput(resultImage); connectedComponentFilter->Update(); typename OutputImageType::Pointer resultImageCC = connectedComponentFilter->GetOutput(); m_ConnectedComponentValue = resultImageCC->GetPixel(seedIndex); outputImage = mitk::GrabItkImageMemory(resultImageCC); } template void mitk::RegionGrowingTool::CalculateInitialThresholds(const itk::Image*) { LevelWindow levelWindow; this->GetToolManager()->GetReferenceData(0)->GetLevelWindow(levelWindow); m_ThresholdExtrema[0] = static_cast(std::numeric_limits::lowest()); m_ThresholdExtrema[1] = static_cast(std::numeric_limits::max()); const ScalarType lowerWindowBound = std::max(m_ThresholdExtrema[0], levelWindow.GetLowerWindowBound()); const ScalarType upperWindowBound = std::min(m_ThresholdExtrema[1], levelWindow.GetUpperWindowBound()); if (m_SeedValue < lowerWindowBound) { m_InitialThresholds = { m_ThresholdExtrema[0], lowerWindowBound }; } else if (m_SeedValue > upperWindowBound) { m_InitialThresholds = { upperWindowBound, m_ThresholdExtrema[1] }; } else { const ScalarType range = 0.1 * (upperWindowBound - lowerWindowBound); // 10% of the visible window m_InitialThresholds[0] = std::min(std::max(lowerWindowBound, m_SeedValue - 0.5 * range), upperWindowBound - range); m_InitialThresholds[1] = m_InitialThresholds[0] + range; } } void mitk::RegionGrowingTool::OnMousePressed(StateMachineAction*, InteractionEvent* interactionEvent) { auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_LastScreenPosition = Point2I(positionEvent->GetPointerPositionOnScreen()); // ReferenceSlice is from the underlying image, WorkingSlice from the active segmentation (can be empty) m_ReferenceSlice = FeedbackContourTool::GetAffectedReferenceSlice(positionEvent); m_WorkingSlice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); if (m_WorkingSlice.IsNull()) { // can't do anything without a working slice (i.e. a possibly empty segmentation) return; } // Determine if the user clicked inside or outside of the working slice (i.e. the whole volume) mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); workingSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), m_SeedPoint); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; if (!workingSliceGeometry->IsIndexInside(m_SeedPoint)) { MITK_DEBUG << "OnMousePressed: point " << positionEvent->GetPositionInWorld() << " (index coordinates " << m_SeedPoint << ") is not inside working slice"; return; } mitk::BaseGeometry::Pointer referenceSliceGeometry; referenceSliceGeometry = m_ReferenceSlice->GetGeometry(); itk::Index<3> indexInReferenceSlice; itk::Index<2> indexInReferenceSlice2D; referenceSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), indexInReferenceSlice); indexInReferenceSlice2D[0] = indexInReferenceSlice[0]; indexInReferenceSlice2D[1] = indexInReferenceSlice[1]; // Get seed neighborhood ScalarType averageValue(0); AccessFixedDimensionByItk_3(m_ReferenceSlice, GetNeighborhoodAverage, 2, indexInReferenceSlice2D, &averageValue, 1); m_SeedValue = averageValue; MITK_DEBUG << "Seed value is " << m_SeedValue; // Calculate initial thresholds AccessFixedDimensionByItk(m_ReferenceSlice, CalculateInitialThresholds, 2); m_Thresholds[0] = m_InitialThresholds[0]; m_Thresholds[1] = m_InitialThresholds[1]; // Perform region growing mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Extract contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(m_LastEventSender->GetRenderWindow()); } } } void mitk::RegionGrowingTool::OnMouseMoved(StateMachineAction*, InteractionEvent* interactionEvent) { auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } if (m_ReferenceSlice.IsNull()) { return; } // Get geometry and indices mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; m_ScreenYDifference += positionEvent->GetPointerPositionOnScreen()[1] - m_LastScreenPosition[1]; m_ScreenXDifference += positionEvent->GetPointerPositionOnScreen()[0] - m_LastScreenPosition[0]; m_LastScreenPosition = Point2I(positionEvent->GetPointerPositionOnScreen()); // Moving the mouse up and down adjusts the width of the threshold window, // moving it left and right shifts the threshold window m_Thresholds[0] = std::min( m_SeedValue, m_InitialThresholds[0] - (m_ScreenYDifference - m_ScreenXDifference) * m_MouseDistanceScaleFactor); m_Thresholds[1] = std::max( m_SeedValue, m_InitialThresholds[1] + (m_ScreenYDifference + m_ScreenXDifference) * m_MouseDistanceScaleFactor); // Do not exceed the pixel type extrema of the reference slice, though m_Thresholds[0] = std::max(m_ThresholdExtrema[0], m_Thresholds[0]); m_Thresholds[1] = std::min(m_ThresholdExtrema[1], m_Thresholds[1]); // Perform region growing again and show the result mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Update the contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(positionEvent->GetSender()->GetRenderWindow()); } } } void mitk::RegionGrowingTool::OnMouseReleased(StateMachineAction*, InteractionEvent* interactionEvent) { auto* positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) { return; } if (m_WorkingSlice.IsNull() && m_FillFeedbackContour) { return; } if (m_FillFeedbackContour) { this->WriteBackFeedbackContourAsSegmentationResult(positionEvent, m_PaintingPixelValue); m_ScreenYDifference = 0; m_ScreenXDifference = 0; } } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index ca9a769dfd..80f4bcba3a 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,757 +1,769 @@ /*============================================================================ 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 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 "itkImageRegionIterator.h" #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, const PlaneGeometry *plane, bool detectIntersection) { std::vector slices = { SliceInformation(slice, plane, 0)}; Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection); } void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; contourInfo.contourNormal = sliceInfo.plane->GetNormal(); contourInfo.contourPoint = sliceInfo.plane->GetOrigin(); mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); } void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection) { 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); contourExtractor->SetInput(slice2); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { Self::RemoveContourFromInterpolator(sliceInfo); } else { relevantSlices.push_back(sliceInfo); } } } if (relevantSlices.empty()) return; for (const auto& sliceInfo : relevantSlices) { contourExtractor->SetInput(sliceInfo.slice); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { Self::RemoveContourFromInterpolator(sliceInfo); } else { contour->DisconnectPipeline(); contourList.push_back(contour); } } mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList); } 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 auto 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; SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); 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(); mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); // 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)); unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetSlice()->GetPos(); /* 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()); 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()) { mitk::SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } mitk::SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false); // 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); mitk::Point2D p2 = p1; p2[0] -= planeGeometry->GetSpacing()[0]; p2[1] -= planeGeometry->GetSpacing()[1]; contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); contourMarker->SetPlaneGeometry(planeGeometry->Clone()); 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 1c58bbf035..ed13ab6d29 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,294 +1,298 @@ /*============================================================================ 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_Included #define mitkSegTool2D_h_Included #include "mitkCommon.h" #include "mitkImage.h" #include "mitkTool.h" #include #include "mitkInteractionPositionEvent.h" #include "mitkInteractionConst.h" #include "mitkPlanePositionManager.h" #include "mitkRestorePlanePositionOperation.h" #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. * @param slice the slice from which the contour should be extracted * @param workingImage the segmentation image * @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, 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; 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 * 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); /** * \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) marke 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); // 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/mitkSetRegionTool.cpp b/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp deleted file mode 100644 index 486679ddaf..0000000000 --- a/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp +++ /dev/null @@ -1,138 +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 "mitkSetRegionTool.h" - -#include "mitkToolManager.h" - -#include "mitkBaseRenderer.h" - -#include -#include - -#include -#include - -mitk::SetRegionTool::SetRegionTool(int paintingPixelValue) - : FeedbackContourTool("PressMoveRelease"), m_PaintingPixelValue(paintingPixelValue) -{ -} - -mitk::SetRegionTool::~SetRegionTool() -{ -} - -void mitk::SetRegionTool::ConnectActionsAndFunctions() -{ - CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); - CONNECT_FUNCTION("Release", OnMouseReleased); - CONNECT_FUNCTION("Move", OnMouseMoved); -} - -void mitk::SetRegionTool::Activated() -{ - Superclass::Activated(); -} - -void mitk::SetRegionTool::Deactivated() -{ - Superclass::Deactivated(); -} - -void mitk::SetRegionTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) -{ - auto *positionEvent = dynamic_cast(interactionEvent); - if (!positionEvent) - return; - - m_LastEventSender = positionEvent->GetSender(); - m_LastEventSlice = m_LastEventSender->GetSlice(); - - // 1. Get the working image - Image::Pointer workingSlice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); - if (workingSlice.IsNull()) - return; // can't do anything without the segmentation - - // if click was outside the image, don't continue - const BaseGeometry *sliceGeometry = workingSlice->GetGeometry(); - itk::Index<3> projectedPointIn2D; - sliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), projectedPointIn2D); - if (!sliceGeometry->IsIndexInside(projectedPointIn2D)) - { - MITK_WARN << "Point outside of segmentation slice." << std::endl; - return; // can't use that as a seed point - } - - typedef itk::Image InputImageType; - typedef InputImageType::IndexType IndexType; - typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; - RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); - - // convert world coordinates to image indices - IndexType seedIndex; - sliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), seedIndex); - - // perform region growing in desired segmented region - InputImageType::Pointer itkImage = InputImageType::New(); - CastToItkImage(workingSlice, itkImage); - regionGrower->SetInput(itkImage); - regionGrower->AddSeed(seedIndex); - - InputImageType::PixelType bound = itkImage->GetPixel(seedIndex); - - regionGrower->SetLower(bound); - regionGrower->SetUpper(bound); - regionGrower->SetReplaceValue(1); - - itk::BinaryFillholeImageFilter::Pointer fillHolesFilter = - itk::BinaryFillholeImageFilter::New(); - - fillHolesFilter->SetInput(regionGrower->GetOutput()); - fillHolesFilter->SetForegroundValue(1); - - // Store result and preview - mitk::Image::Pointer resultImage = mitk::GrabItkImageMemory(fillHolesFilter->GetOutput()); - resultImage->SetGeometry(workingSlice->GetGeometry()); - // Get the current working color - DataNode *workingNode(this->GetToolManager()->GetWorkingData(0)); - if (!workingNode) - return; - - mitk::ImageToContourModelFilter::Pointer contourextractor = mitk::ImageToContourModelFilter::New(); - contourextractor->SetInput(resultImage); - contourextractor->Update(); - - mitk::ContourModel::Pointer awesomeContour = contourextractor->GetOutput(); - - this->UpdateCurrentFeedbackContour(awesomeContour); - - FeedbackContourTool::SetFeedbackContourVisible(true); - mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); -} - -void mitk::SetRegionTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) -{ - auto *positionEvent = dynamic_cast(interactionEvent); - if (!positionEvent) - return; - - assert(positionEvent->GetSender()->GetRenderWindow()); - // 1. Hide the feedback contour, find out which slice the user clicked, find out which slice of the toolmanager's - // working image corresponds to that - mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); - - this->WriteBackFeedbackContourAsSegmentationResult(positionEvent, m_PaintingPixelValue); -} - -void mitk::SetRegionTool::OnMouseMoved(mitk::StateMachineAction *, mitk::InteractionEvent *) -{ -} diff --git a/Modules/Segmentation/Interactions/mitkSetRegionTool.h b/Modules/Segmentation/Interactions/mitkSetRegionTool.h deleted file mode 100644 index 9fab9b8030..0000000000 --- a/Modules/Segmentation/Interactions/mitkSetRegionTool.h +++ /dev/null @@ -1,60 +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 mitkSetRegionTool_h_Included -#define mitkSetRegionTool_h_Included - -#include "mitkCommon.h" -#include "mitkFeedbackContourTool.h" -#include - -namespace mitk -{ - class Image; - class StateMachineAction; - class InteractionEvent; - /** - \brief Fills or erases a 2D region - - \sa FeedbackContourTool - \sa ExtractImageFilter - - \ingroup Interactions - - Finds the outer contour of a shape in 2D (possibly including holes or single patches) and sets all - the inside pixels to a specified value. This might fill holes or erase segmentations. - - \warning Only to be instantiated by mitk::ToolManager. - */ - class MITKSEGMENTATION_EXPORT SetRegionTool : public FeedbackContourTool - { - public: - mitkClassMacro(SetRegionTool, FeedbackContourTool); - - protected: - SetRegionTool(int paintingPixelValue = 1); // purposely hidden - ~SetRegionTool() override; - - void ConnectActionsAndFunctions() override; - - void Activated() override; - void Deactivated() override; - - virtual void OnMousePressed(StateMachineAction *, InteractionEvent *); - virtual void OnMouseReleased(StateMachineAction *, InteractionEvent *); - virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *); - int m_PaintingPixelValue; - }; - -} // namespace - -#endif diff --git a/Modules/Segmentation/Interactions/mitkSubtractContourTool.cpp b/Modules/Segmentation/Interactions/mitkSubtractContourTool.cpp index 17a3fb3cd7..1d3391b08b 100644 --- a/Modules/Segmentation/Interactions/mitkSubtractContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSubtractContourTool.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 "mitkSubtractContourTool.h" #include "mitkSubtractContourTool.xpm" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, SubtractContourTool, "Subtract tool"); } mitk::SubtractContourTool::SubtractContourTool() : ContourTool(0) { FeedbackContourTool::SetFeedbackContourColor(1.0, 0.0, 0.0); } mitk::SubtractContourTool::~SubtractContourTool() { } const char **mitk::SubtractContourTool::GetXPM() const { return mitkSubtractContourTool_xpm; } us::ModuleResource mitk::SubtractContourTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Subtract_48x48.png"); + us::ModuleResource resource = module->GetResource("Subtract.svg"); return resource; } us::ModuleResource mitk::SubtractContourTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("Subtract_Cursor_32x32.png"); + us::ModuleResource resource = module->GetResource("Subtract_Cursor.svg"); return resource; } const char *mitk::SubtractContourTool::GetName() const { return "Subtract"; } diff --git a/Modules/Segmentation/Interactions/mitkTool.h b/Modules/Segmentation/Interactions/mitkTool.h index af0224ea3c..2eec4b6f7b 100644 --- a/Modules/Segmentation/Interactions/mitkTool.h +++ b/Modules/Segmentation/Interactions/mitkTool.h @@ -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 mitkTool_h_Included #define mitkTool_h_Included #include "itkObjectFactoryBase.h" #include "itkVersion.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkEventStateMachine.h" #include "mitkInteractionEventObserver.h" #include "mitkLabelSetImage.h" #include "mitkMessage.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateDimension.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateProperty.h" #include "mitkToolEvents.h" #include "mitkToolFactoryMacro.h" #include #include #include #include #include #include #include "usServiceRegistration.h" namespace us { class ModuleResource; } namespace mitk { class ToolManager; /** \brief Base class of all tools used by mitk::ToolManager. \sa ToolManager \sa SegTool2D \ingroup Interaction \ingroup ToolManagerEtAl Every tool is a mitk::EventStateMachine, which can follow any transition pattern that it likes. Every derived tool should always call SuperClass::Deactivated() at the end of its own implementation of Deactivated, because mitk::Tool resets the interaction configuration in this method. Only if you are very sure that you covered all possible things that might happen to your own tool, you should consider not to reset the configuration. To learn about the MITK implementation of state machines in general, have a look at \ref InteractionPage. To derive a non-abstract tool, you inherit from mitk::Tool (or some other base class further down the inheritance tree), and in your own parameterless constructor (that is called from the itkFactorylessNewMacro that you use) you pass a state machine name (interactor type). Names and .xml-files for valid state machines can be found in different "Interaction" directories (which might be enhanced by you). You have to implement at least GetXPM() and GetName() to provide some identification. Each Tool knows its ToolManager, which can provide the data that the tool should work on. \warning Only to be instantiated by mitk::ToolManager (because SetToolManager has to be called). All other uses are unsupported. $Author$ */ class MITKSEGMENTATION_EXPORT Tool : public EventStateMachine, public InteractionEventObserver { public: typedef mitk::Label::PixelType DefaultSegmentationDataType; /** * \brief To let GUI process new events (e.g. qApp->processEvents() ) */ Message<> GUIProcessEventsMessage; /** * \brief To send error messages (to be shown by some GUI) */ Message1 ErrorMessage; /** * \brief To send whether the tool is busy (to be shown by some GUI) */ Message1 CurrentlyBusy; /** * \brief To send general messages (to be shown by some GUI) */ Message1 GeneralMessage; mitkClassMacro(Tool, EventStateMachine); // no New(), there should only be subclasses /** \brief Returns an icon in the XPM format. This icon has to fit into some kind of button in most applications, so make it smaller than 25x25 pixels. XPM is e.g. supported by The Gimp. But if you open any XPM file in your text editor, you will see that you could also "draw" it with an editor. */ + [[deprecated]] virtual const char **GetXPM() const = 0; /** * \brief Returns the path of an icon. * * This icon is preferred to the XPM icon. */ virtual std::string GetIconPath() const { return ""; } /** * \brief Returns the path of a cursor icon. * */ virtual us::ModuleResource GetCursorIconResource() const; /** * @brief Returns the tool button icon of the tool wrapped by a usModuleResource * @return a valid ModuleResource or an invalid if this function * is not reimplemented */ virtual us::ModuleResource GetIconResource() const; /** \brief Returns the name of this tool. Make it short! This name has to fit into some kind of button in most applications, so take some time to think of a good name! */ virtual const char *GetName() const = 0; /** \brief Name of a group. You can group several tools by assigning a group name. Graphical tool selectors might use this information to group tools. (What other reason could there be?) */ virtual const char *GetGroup() const; virtual void InitializeStateMachine(); /** * \brief Interface for GUI creation. * * This is the basic interface for creation of a GUI object belonging to one tool. * * Tools that support a GUI (e.g. for display/editing of parameters) should follow some rules: * * - A Tool and its GUI are two separate classes * - There may be several instances of a GUI at the same time. * - mitk::Tool is toolkit (Qt, wxWidgets, etc.) independent, the GUI part is of course dependent * - The GUI part inherits both from itk::Object and some GUI toolkit class * - The GUI class name HAS to be constructed like "toolkitPrefix" tool->GetClassName() + "toolkitPostfix", e.g. * MyTool -> wxMyToolGUI * - For each supported toolkit there is a base class for tool GUIs, which contains some convenience methods * - Tools notify the GUI about changes using ITK events. The GUI must observe interesting events. * - The GUI base class may convert all ITK events to the GUI toolkit's favoured messaging system (Qt -> signals) * - Calling methods of a tool by its GUI is done directly. * In some cases GUIs don't want to be notified by the tool when they cause a change in a tool. * There is a macro CALL_WITHOUT_NOTICE(method()), which will temporarily disable all notifications during a * method call. */ virtual itk::Object::Pointer GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix); virtual NodePredicateBase::ConstPointer GetReferenceDataPreference() const; virtual NodePredicateBase::ConstPointer GetWorkingDataPreference() const; DataNode::Pointer CreateEmptySegmentationNode(const Image *original, const std::string &organName, const mitk::Color &color) const; DataNode::Pointer CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) const; /** Function used to check if a tool can handle the referenceData and (if specified) the working data. @pre referenceData must be a valid pointer @param referenceData Pointer to the data that should be checked as valid reference for the tool. @param workingData Pointer to the data that should be checked as valid working data for this tool. This parameter can be null if no working data is specified so far.*/ virtual bool CanHandle(const BaseData *referenceData, const BaseData *workingData) const; protected: friend class ToolManager; virtual void SetToolManager(ToolManager *); /** Returns the pointer to the tool manager of the tool. May be null.*/ ToolManager* GetToolManager() const; /** Returns the data storage provided by the toolmanager. May be null (e.g. if ToolManager is not set).*/ mitk::DataStorage* GetDataStorage() const; void ConnectActionsAndFunctions() override; /** \brief Called when the tool gets activated. Derived tools should call their parents implementation at the beginning of the overriding function. */ virtual void Activated(); /** \brief Called when the tool gets deactivated. Derived tools should call their parents implementation at the end of the overriding function. */ virtual void Deactivated(); /** \brief Let subclasses change their event configuration. */ std::string m_EventConfig; Tool(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~Tool() override; void Notify(InteractionEvent *interactionEvent, bool isHandled) override; bool FilterEvents(InteractionEvent *, DataNode *) override; private: ToolManager* m_ToolManager; // for reference data NodePredicateDataType::Pointer m_PredicateImages; NodePredicateDimension::Pointer m_PredicateDim3; NodePredicateDimension::Pointer m_PredicateDim4; NodePredicateOr::Pointer m_PredicateDimension; NodePredicateAnd::Pointer m_PredicateImage3D; NodePredicateProperty::Pointer m_PredicateBinary; NodePredicateNot::Pointer m_PredicateNotBinary; NodePredicateProperty::Pointer m_PredicateSegmentation; NodePredicateNot::Pointer m_PredicateNotSegmentation; NodePredicateProperty::Pointer m_PredicateHelper; NodePredicateNot::Pointer m_PredicateNotHelper; NodePredicateAnd::Pointer m_PredicateImageColorful; NodePredicateAnd::Pointer m_PredicateImageColorfulNotHelper; NodePredicateAnd::Pointer m_PredicateReference; // for working data NodePredicateAnd::Pointer m_IsSegmentationPredicate; std::string m_InteractorType; std::map m_DisplayInteractionConfigs; const us::Module *m_InteractorModule; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp index 1245ee2124..9a64f9d539 100644 --- a/Modules/Segmentation/Interactions/mitknnUnetTool.cpp +++ b/Modules/Segmentation/Interactions/mitknnUnetTool.cpp @@ -1,320 +1,320 @@ /*============================================================================ 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 namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, nnUNetTool, "nnUNet tool"); } mitk::nnUNetTool::nnUNetTool() { this->SetMitkTempDir(IOUtil::CreateTemporaryDirectory("mitk-XXXXXX")); } mitk::nnUNetTool::~nnUNetTool() { itksys::SystemTools::RemoveADirectory(this->GetMitkTempDir()); } void mitk::nnUNetTool::Activated() { Superclass::Activated(); this->SetLabelTransferMode(LabelTransferMode::AllLabels); } 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_48x48.png"); + 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); } 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*/) { 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) { /* Can't throw mitk exception to the caller. Refer: T28691 */ 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) { /* Can't throw mitk exception to the caller. Refer: T28691 */ MITK_ERROR << e.GetDescription(); return; } } diff --git a/Modules/Segmentation/Resources/AI.svg b/Modules/Segmentation/Resources/AI.svg new file mode 100644 index 0000000000..a74640f02c --- /dev/null +++ b/Modules/Segmentation/Resources/AI.svg @@ -0,0 +1,208 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + AI + + + + diff --git a/Modules/Segmentation/Resources/AI_48x48.png b/Modules/Segmentation/Resources/AI_48x48.png deleted file mode 100644 index 9e546998a8..0000000000 Binary files a/Modules/Segmentation/Resources/AI_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/AI_Cursor.svg b/Modules/Segmentation/Resources/AI_Cursor.svg new file mode 100644 index 0000000000..c7b144b6ec --- /dev/null +++ b/Modules/Segmentation/Resources/AI_Cursor.svg @@ -0,0 +1,183 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + AI + + + diff --git a/Modules/Segmentation/Resources/AI_Cursor_32x32.png b/Modules/Segmentation/Resources/AI_Cursor_32x32.png deleted file mode 100644 index fc16240c92..0000000000 Binary files a/Modules/Segmentation/Resources/AI_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Add.svg b/Modules/Segmentation/Resources/Add.svg new file mode 100644 index 0000000000..a2f0c1a24a --- /dev/null +++ b/Modules/Segmentation/Resources/Add.svg @@ -0,0 +1,109 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Add_48x48.png b/Modules/Segmentation/Resources/Add_48x48.png deleted file mode 100644 index 30bda319f1..0000000000 Binary files a/Modules/Segmentation/Resources/Add_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Add_Cursor.svg b/Modules/Segmentation/Resources/Add_Cursor.svg new file mode 100644 index 0000000000..e8bc968006 --- /dev/null +++ b/Modules/Segmentation/Resources/Add_Cursor.svg @@ -0,0 +1,126 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Add_Cursor_32x32.png b/Modules/Segmentation/Resources/Add_Cursor_32x32.png deleted file mode 100644 index f67e1e0437..0000000000 Binary files a/Modules/Segmentation/Resources/Add_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Close.svg b/Modules/Segmentation/Resources/Close.svg new file mode 100644 index 0000000000..24c2cc29fb --- /dev/null +++ b/Modules/Segmentation/Resources/Close.svg @@ -0,0 +1,168 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Close_Cursor.svg b/Modules/Segmentation/Resources/Close_Cursor.svg new file mode 100644 index 0000000000..857d32fb1b --- /dev/null +++ b/Modules/Segmentation/Resources/Close_Cursor.svg @@ -0,0 +1,184 @@ + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Erase.svg b/Modules/Segmentation/Resources/Erase.svg new file mode 100644 index 0000000000..37e445a85c --- /dev/null +++ b/Modules/Segmentation/Resources/Erase.svg @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Erase_48x48.png b/Modules/Segmentation/Resources/Erase_48x48.png deleted file mode 100644 index 04bed2302d..0000000000 Binary files a/Modules/Segmentation/Resources/Erase_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Erase_Cursor.svg b/Modules/Segmentation/Resources/Erase_Cursor.svg new file mode 100644 index 0000000000..1fe0b85370 --- /dev/null +++ b/Modules/Segmentation/Resources/Erase_Cursor.svg @@ -0,0 +1,169 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Erase_Cursor_32x32.png b/Modules/Segmentation/Resources/Erase_Cursor_32x32.png deleted file mode 100644 index c218382e39..0000000000 Binary files a/Modules/Segmentation/Resources/Erase_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Fill.svg b/Modules/Segmentation/Resources/Fill.svg new file mode 100644 index 0000000000..d4ad22fd22 --- /dev/null +++ b/Modules/Segmentation/Resources/Fill.svg @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Fill_48x48.png b/Modules/Segmentation/Resources/Fill_48x48.png deleted file mode 100644 index 9f1da9fd4e..0000000000 Binary files a/Modules/Segmentation/Resources/Fill_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Fill_Cursor.svg b/Modules/Segmentation/Resources/Fill_Cursor.svg new file mode 100644 index 0000000000..449620ebdd --- /dev/null +++ b/Modules/Segmentation/Resources/Fill_Cursor.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Fill_Cursor_32x32.png b/Modules/Segmentation/Resources/Fill_Cursor_32x32.png deleted file mode 100644 index 9953754248..0000000000 Binary files a/Modules/Segmentation/Resources/Fill_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/GrowCut.svg b/Modules/Segmentation/Resources/GrowCut.svg new file mode 100644 index 0000000000..be3da852b4 --- /dev/null +++ b/Modules/Segmentation/Resources/GrowCut.svg @@ -0,0 +1,88 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Icons.svg b/Modules/Segmentation/Resources/Icons.svg deleted file mode 100644 index 1404173765..0000000000 --- a/Modules/Segmentation/Resources/Icons.svg +++ /dev/null @@ -1,1433 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FM - - - - - - - - - - - - - - AI - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 大津 - - - - - - - - - - - - - - - - diff --git a/Modules/Segmentation/Resources/Interactions/GrowCutTool.xml b/Modules/Segmentation/Resources/Interactions/GrowCutTool.xml new file mode 100644 index 0000000000..c5f61a9dfd --- /dev/null +++ b/Modules/Segmentation/Resources/Interactions/GrowCutTool.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Modules/Segmentation/Resources/Interactions/MouseReleaseOnly.xml b/Modules/Segmentation/Resources/Interactions/MouseReleaseOnly.xml new file mode 100644 index 0000000000..98f74e2964 --- /dev/null +++ b/Modules/Segmentation/Resources/Interactions/MouseReleaseOnly.xml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/Modules/Segmentation/Resources/Lasso.svg b/Modules/Segmentation/Resources/Lasso.svg new file mode 100644 index 0000000000..e25c4facd2 --- /dev/null +++ b/Modules/Segmentation/Resources/Lasso.svg @@ -0,0 +1,122 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Lasso_Cursor.svg b/Modules/Segmentation/Resources/Lasso_Cursor.svg new file mode 100644 index 0000000000..de1cef7545 --- /dev/null +++ b/Modules/Segmentation/Resources/Lasso_Cursor.svg @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/LiveWire.svg b/Modules/Segmentation/Resources/LiveWire.svg new file mode 100644 index 0000000000..3874d7e73b --- /dev/null +++ b/Modules/Segmentation/Resources/LiveWire.svg @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/LiveWire_48x48.png b/Modules/Segmentation/Resources/LiveWire_48x48.png deleted file mode 100644 index d2a0e96dbc..0000000000 Binary files a/Modules/Segmentation/Resources/LiveWire_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/LiveWire_Cursor.svg b/Modules/Segmentation/Resources/LiveWire_Cursor.svg new file mode 100644 index 0000000000..7380cb4083 --- /dev/null +++ b/Modules/Segmentation/Resources/LiveWire_Cursor.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/LiveWire_Cursor_32x32.png b/Modules/Segmentation/Resources/LiveWire_Cursor_32x32.png deleted file mode 100644 index 8c0101ba8a..0000000000 Binary files a/Modules/Segmentation/Resources/LiveWire_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/NewAdd_48x48.png b/Modules/Segmentation/Resources/NewAdd_48x48.png deleted file mode 100644 index 3336c07b3c..0000000000 Binary files a/Modules/Segmentation/Resources/NewAdd_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/NewAdd_Cursor_32x32.png b/Modules/Segmentation/Resources/NewAdd_Cursor_32x32.png deleted file mode 100644 index a22b98f416..0000000000 Binary files a/Modules/Segmentation/Resources/NewAdd_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Otsu.svg b/Modules/Segmentation/Resources/Otsu.svg new file mode 100644 index 0000000000..b95436c262 --- /dev/null +++ b/Modules/Segmentation/Resources/Otsu.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Otsu_48x48.png b/Modules/Segmentation/Resources/Otsu_48x48.png deleted file mode 100644 index a67ab936c6..0000000000 Binary files a/Modules/Segmentation/Resources/Otsu_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Paint.svg b/Modules/Segmentation/Resources/Paint.svg new file mode 100644 index 0000000000..d3b3a2ebc5 --- /dev/null +++ b/Modules/Segmentation/Resources/Paint.svg @@ -0,0 +1,167 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Paint_48x48.png b/Modules/Segmentation/Resources/Paint_48x48.png deleted file mode 100644 index 65d1fb394a..0000000000 Binary files a/Modules/Segmentation/Resources/Paint_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Paint_Cursor.svg b/Modules/Segmentation/Resources/Paint_Cursor.svg new file mode 100644 index 0000000000..868582cde7 --- /dev/null +++ b/Modules/Segmentation/Resources/Paint_Cursor.svg @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Paint_Cursor_32x32.png b/Modules/Segmentation/Resources/Paint_Cursor_32x32.png deleted file mode 100644 index a7e90c1126..0000000000 Binary files a/Modules/Segmentation/Resources/Paint_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Pick_48x48.png b/Modules/Segmentation/Resources/Pick_48x48.png deleted file mode 100644 index df85a7a8eb..0000000000 Binary files a/Modules/Segmentation/Resources/Pick_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Picking.svg b/Modules/Segmentation/Resources/Picking.svg new file mode 100644 index 0000000000..b2f4eb849e --- /dev/null +++ b/Modules/Segmentation/Resources/Picking.svg @@ -0,0 +1,87 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/RegionGrowing.svg b/Modules/Segmentation/Resources/RegionGrowing.svg new file mode 100644 index 0000000000..7de4b45524 --- /dev/null +++ b/Modules/Segmentation/Resources/RegionGrowing.svg @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/RegionGrowing_48x48.png b/Modules/Segmentation/Resources/RegionGrowing_48x48.png deleted file mode 100644 index e0e6c6e9dc..0000000000 Binary files a/Modules/Segmentation/Resources/RegionGrowing_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/RegionGrowing_Cursor.svg b/Modules/Segmentation/Resources/RegionGrowing_Cursor.svg new file mode 100644 index 0000000000..6920c99261 --- /dev/null +++ b/Modules/Segmentation/Resources/RegionGrowing_Cursor.svg @@ -0,0 +1,199 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/RegionGrowing_Cursor_32x32.png b/Modules/Segmentation/Resources/RegionGrowing_Cursor_32x32.png deleted file mode 100644 index cd5ce47f87..0000000000 Binary files a/Modules/Segmentation/Resources/RegionGrowing_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Subtract.svg b/Modules/Segmentation/Resources/Subtract.svg new file mode 100644 index 0000000000..3f35be692b --- /dev/null +++ b/Modules/Segmentation/Resources/Subtract.svg @@ -0,0 +1,104 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Subtract_48x48.png b/Modules/Segmentation/Resources/Subtract_48x48.png deleted file mode 100644 index c61d3e3690..0000000000 Binary files a/Modules/Segmentation/Resources/Subtract_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Subtract_Cursor.svg b/Modules/Segmentation/Resources/Subtract_Cursor.svg new file mode 100644 index 0000000000..ea85d6326f --- /dev/null +++ b/Modules/Segmentation/Resources/Subtract_Cursor.svg @@ -0,0 +1,155 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Subtract_Cursor_32x32.png b/Modules/Segmentation/Resources/Subtract_Cursor_32x32.png deleted file mode 100644 index 8c6be06ed6..0000000000 Binary files a/Modules/Segmentation/Resources/Subtract_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Templates/ToolCursor.svg b/Modules/Segmentation/Resources/Templates/ToolCursor.svg new file mode 100644 index 0000000000..48f25e7450 --- /dev/null +++ b/Modules/Segmentation/Resources/Templates/ToolCursor.svg @@ -0,0 +1,119 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Templates/ToolIcon.svg b/Modules/Segmentation/Resources/Templates/ToolIcon.svg new file mode 100644 index 0000000000..f23edcce3a --- /dev/null +++ b/Modules/Segmentation/Resources/Templates/ToolIcon.svg @@ -0,0 +1,103 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Threshold.svg b/Modules/Segmentation/Resources/Threshold.svg new file mode 100644 index 0000000000..ce379f1676 --- /dev/null +++ b/Modules/Segmentation/Resources/Threshold.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Threshold_48x48.png b/Modules/Segmentation/Resources/Threshold_48x48.png deleted file mode 100644 index d73bd74d02..0000000000 Binary files a/Modules/Segmentation/Resources/Threshold_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/TwoThresholds_48x48.png b/Modules/Segmentation/Resources/TwoThresholds_48x48.png deleted file mode 100644 index 81354ce68d..0000000000 Binary files a/Modules/Segmentation/Resources/TwoThresholds_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/ULThreshold.svg b/Modules/Segmentation/Resources/ULThreshold.svg new file mode 100644 index 0000000000..16a21b4009 --- /dev/null +++ b/Modules/Segmentation/Resources/ULThreshold.svg @@ -0,0 +1,154 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Wipe.svg b/Modules/Segmentation/Resources/Wipe.svg new file mode 100644 index 0000000000..db1c84a1c3 --- /dev/null +++ b/Modules/Segmentation/Resources/Wipe.svg @@ -0,0 +1,148 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Wipe_48x48.png b/Modules/Segmentation/Resources/Wipe_48x48.png deleted file mode 100644 index b5b55b18dd..0000000000 Binary files a/Modules/Segmentation/Resources/Wipe_48x48.png and /dev/null differ diff --git a/Modules/Segmentation/Resources/Wipe_Cursor.svg b/Modules/Segmentation/Resources/Wipe_Cursor.svg new file mode 100644 index 0000000000..5322ac85cd --- /dev/null +++ b/Modules/Segmentation/Resources/Wipe_Cursor.svg @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + diff --git a/Modules/Segmentation/Resources/Wipe_Cursor_32x32.png b/Modules/Segmentation/Resources/Wipe_Cursor_32x32.png deleted file mode 100644 index c130dc00cf..0000000000 Binary files a/Modules/Segmentation/Resources/Wipe_Cursor_32x32.png and /dev/null differ diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 3f30867055..4520f0edc0 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,110 +1,117 @@ 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/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkPickingTool.cpp Interactions/mitknnUnetTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO Interactions/mitkProcessExecutor.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_48x48.png - Add_Cursor_32x32.png - AI_48x48.png - AI_Cursor_32x32.png - Erase_48x48.png - Erase_Cursor_32x32.png - Fill_48x48.png - Fill_Cursor_32x32.png - LiveWire_48x48.png - LiveWire_Cursor_32x32.png - NewAdd_48x48.png - NewAdd_Cursor_32x32.png - Otsu_48x48.png - Paint_48x48.png - Paint_Cursor_32x32.png - Pick_48x48.png - RegionGrowing_48x48.png - RegionGrowing_Cursor_32x32.png - Subtract_48x48.png - Subtract_Cursor_32x32.png - Threshold_48x48.png - TwoThresholds_48x48.png - Wipe_48x48.png - Wipe_Cursor_32x32.png + 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/QmitkGrowCutToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkGrowCutToolGUI.cpp new file mode 100644 index 0000000000..b4ca6da252 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkGrowCutToolGUI.cpp @@ -0,0 +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 "QmitkGrowCutToolGUI.h" +#include +#include + +// Qt +#include +#include +#include +#include +#include +#include + +MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkGrowCutToolGUI, "") + +QmitkGrowCutToolGUI::QmitkGrowCutToolGUI() : QmitkMultiLabelSegWithPreviewToolGUIBase() +{ + auto enableConfirmSegBtnFnc = [this](bool enabled) { return enabled && m_FirstPreviewComputation; }; + + m_EnableConfirmSegBtnFnc = enableConfirmSegBtnFnc; +} + +QmitkGrowCutToolGUI::~QmitkGrowCutToolGUI() {} + +void QmitkGrowCutToolGUI::ConnectNewTool(mitk::SegWithPreviewTool *newTool) +{ + Superclass::ConnectNewTool(newTool); + + newTool->IsTimePointChangeAwareOff(); +} + +void QmitkGrowCutToolGUI::InitializeUI(QBoxLayout *mainLayout) +{ + m_Controls.setupUi(this); + + this->OnAdvancedSettingsButtonToggled(false); + + std::function isPreviewAvailable; + isPreviewAvailable = [this]() + { + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + return tool->HasMoreThanTwoSeedLabel(); + } + return false; + }; + + auto previewAvailable = isPreviewAvailable(); + m_Controls.m_previewButton->setEnabled(previewAvailable); + m_Controls.m_warningLabel->setVisible(!previewAvailable); + + mainLayout->addLayout(m_Controls.verticalLayout); + + connect(m_Controls.m_previewButton, &QPushButton::clicked, this, &QmitkGrowCutToolGUI::OnPreviewBtnClicked); + connect(m_Controls.m_advancedSettingsButton, + &ctkExpandButton::toggled, + this, + &QmitkGrowCutToolGUI::OnAdvancedSettingsButtonToggled); + + connect(m_Controls.m_distancePenaltyDoubleSpinBox, + qOverload(&QDoubleSpinBox::valueChanged), + this, + &QmitkGrowCutToolGUI::SetValueOfDistancePenaltySlider); + + connect(m_Controls.m_distancePenaltySlider, + &QSlider::valueChanged, + this, + &QmitkGrowCutToolGUI::SetValueOfDistancePenaltyDoubleSpinBox); + + Superclass::InitializeUI(mainLayout); +} + +void QmitkGrowCutToolGUI::SetValueOfDistancePenaltySlider(double val) +{ + m_Controls.m_distancePenaltySlider->setValue(val * 100); +} + +void QmitkGrowCutToolGUI::SetValueOfDistancePenaltyDoubleSpinBox(int val) +{ + m_Controls.m_distancePenaltyDoubleSpinBox->setValue((static_cast(val) / 100)); +} + +void QmitkGrowCutToolGUI::OnAdvancedSettingsButtonToggled(bool toggled) +{ + m_Controls.m_distancePenaltyLabel->setVisible(toggled); + m_Controls.m_distancePenaltyDoubleSpinBox->setVisible(toggled); + m_Controls.m_distancePenaltySlider->setVisible(toggled); + + double distancePenaltyMinium = 0.0; + double distancePenaltyMaximum = 1.0; + + m_Controls.m_distancePenaltyDoubleSpinBox->setMinimum(distancePenaltyMinium); + m_Controls.m_distancePenaltyDoubleSpinBox->setMaximum(distancePenaltyMaximum); + m_Controls.m_distancePenaltySlider->setMinimum(distancePenaltyMinium); + m_Controls.m_distancePenaltySlider->setMaximum(distancePenaltyMaximum * 100); + + m_Controls.m_distancePenaltyDoubleSpinBox->setSingleStep(0.01); +} + +void QmitkGrowCutToolGUI::OnPreviewBtnClicked() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + try + { + tool->SetDistancePenalty(m_Controls.m_distancePenaltyDoubleSpinBox->value()); + + tool->UpdatePreview(); + } + catch (...) + { + this->setCursor(Qt::ArrowCursor); + QMessageBox *messageBox = + new QMessageBox(QMessageBox::Critical, + nullptr, + "itkGrowCutFilter error."); + messageBox->exec(); + delete messageBox; + return; + } + + m_FirstPreviewComputation = true; + this->SetLabelSetPreview(tool->GetPreviewSegmentation()); + tool->IsTimePointChangeAwareOn(); + this->ActualizePreviewLabelVisibility(); + } +} + +void QmitkGrowCutToolGUI::EnableWidgets(bool enabled) +{ + Superclass::EnableWidgets(enabled); + + m_Controls.m_distancePenaltyLabel->setEnabled(enabled); + m_Controls.m_distancePenaltyDoubleSpinBox->setEnabled(enabled); + m_Controls.m_distancePenaltySlider->setEnabled(enabled); +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkGrowCutToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkGrowCutToolGUI.h new file mode 100644 index 0000000000..62109b6147 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkGrowCutToolGUI.h @@ -0,0 +1,67 @@ +/*============================================================================ + +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 QmitkGrowCutToolGUI_h_Included +#define QmitkGrowCutToolGUI_h_Included + +#include "QmitkMultiLabelSegWithPreviewToolGUIBase.h" + +#include "ui_QmitkGrowCutToolWidgetControls.h" + +#include + +/** + \ingroup org_mitk_gui_qt_interactivesegmentation_internal + \brief GUI for mitk::GrowCutTool. + \sa mitk:: + + This GUI shows ... + + Last contributor: $Author$ +*/ +class MITKSEGMENTATIONUI_EXPORT QmitkGrowCutToolGUI : public QmitkMultiLabelSegWithPreviewToolGUIBase +{ + Q_OBJECT + +public: + mitkClassMacro(QmitkGrowCutToolGUI, QmitkMultiLabelSegWithPreviewToolGUIBase); + itkFactorylessNewMacro(Self); + itkCloneMacro(Self); + +protected slots: + + void OnPreviewBtnClicked(); + +private slots: + + void OnAdvancedSettingsButtonToggled(bool toggled); + +protected: + QmitkGrowCutToolGUI(); + ~QmitkGrowCutToolGUI() override; + + void ConnectNewTool(mitk::SegWithPreviewTool *newTool) override; + + void InitializeUI(QBoxLayout *mainLayout) override; + + void SetValueOfDistancePenaltySlider(double val); + + void SetValueOfDistancePenaltyDoubleSpinBox(int val); + + void EnableWidgets(bool enabled) override; + + Ui_QmitkGrowCutToolWidgetControls m_Controls; + + bool m_FirstPreviewComputation = false; +}; + +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkGrowCutToolWidgetControls.ui b/Modules/SegmentationUI/Qmitk/QmitkGrowCutToolWidgetControls.ui new file mode 100644 index 0000000000..6b26104279 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkGrowCutToolWidgetControls.ui @@ -0,0 +1,159 @@ + + + QmitkGrowCutToolWidgetControls + + + + 0 + 0 + 699 + 352 + + + + + 0 + 0 + + + + + 100 + 0 + + + + + 100000 + 100000 + + + + QmitkOtsuToolWidget + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + <html><head/><body><p><span style=" font-weight:600; color:#ff0000;">The segmentation is required to have at least two labels.</span></p><p><span style=" color:#ff0000;">Add labels and sparsely add pixels using other tools before using GrowCut.</span></p></body></html> + + + true + + + 9 + + + + + + + + 0 + 0 + + + + + 0 + 32 + + + + Advanced settings + + + Qt::ToolButtonTextBesideIcon + + + true + + + + + + + + + + 128 + 0 + + + + Distance penalty + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + Qt::Horizontal + + + + + + + Qt::AlignCenter + + + + + + + + + + 0 + 0 + + + + + 0 + 32 + + + + + 100000 + 16777215 + + + + Preview + + + + + + + + + ctkExpandButton + QToolButton +
ctkExpandButton.h
+
+
+ + +
diff --git a/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp index 2ba34c9581..b42faefc7e 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->GetLabelTransferMode() == mitk::SegWithPreviewTool::LabelTransferMode::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 label when confirmed."); + 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 label when confirmed."); + 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 selectedLabels = tool->GetSelectedLabels(); for (auto labelIter = labelSet->IteratorBegin(); labelIter != labelSet->IteratorEnd(); ++labelIter) { bool isVisible = tool->GetLabelTransferMode() == mitk::SegWithPreviewTool::LabelTransferMode::AllLabels || (std::find(selectedLabels.begin(), selectedLabels.end(), labelIter->second->GetValue()) != selectedLabels.end()); labelIter->second->SetVisible(isVisible); labelSet->UpdateLookupTable(labelIter->second->GetValue()); } } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkMultiLabelSegWithPreviewToolGUIBase::OnRadioTransferAllClicked(bool checked) { m_LabelSelectionList->setVisible(!checked); auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { if (checked) { tool->SetLabelTransferMode(mitk::SegWithPreviewTool::LabelTransferMode::AllLabels); } else { tool->SetLabelTransferMode(mitk::SegWithPreviewTool::LabelTransferMode::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/files.cmake b/Modules/SegmentationUI/files.cmake index aa38f2c30f..5974d42e8d 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,76 +1,79 @@ set( CPP_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.cpp Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp Qmitk/QmitkEditableContourToolGUIBase.cpp +Qmitk/QmitkGrowCutToolGUI.cpp Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkLassoToolGUI.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitknnUNetFolderParser.cpp Qmitk/QmitknnUNetToolGUI.cpp Qmitk/QmitknnUNetWorker.cpp Qmitk/QmitknnUNetGPU.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkStaticDynamicSegmentationDialog.cpp Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp ) set(MOC_H_FILES Qmitk/QmitkSegWithPreviewToolGUIBase.h Qmitk/QmitkMultiLabelSegWithPreviewToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h Qmitk/QmitkEditableContourToolGUIBase.h +Qmitk/QmitkGrowCutToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkLassoToolGUI.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitknnUNetFolderParser.h Qmitk/QmitknnUNetToolGUI.h Qmitk/QmitknnUNetGPU.h Qmitk/QmitknnUNetWorker.h Qmitk/QmitknnUNetEnsembleLayout.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkStaticDynamicSegmentationDialog.h Qmitk/QmitkSurfaceBasedInterpolatorWidget.h Qmitk/QmitkSimpleLabelSetListWidget.h ) set(UI_FILES Qmitk/QmitkConfirmSegmentationDialog.ui +Qmitk/QmitkGrowCutToolWidgetControls.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitkSurfaceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitknnUNetToolGUIControls.ui Qmitk/QmitkEditableContourToolGUIControls.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Plugins/org.mitk.gui.qt.common/src/QmitkSliceNavigationListener.cpp b/Plugins/org.mitk.gui.qt.common/src/QmitkSliceNavigationListener.cpp index 9c8bf772ba..53919dd2f1 100644 --- a/Plugins/org.mitk.gui.qt.common/src/QmitkSliceNavigationListener.cpp +++ b/Plugins/org.mitk.gui.qt.common/src/QmitkSliceNavigationListener.cpp @@ -1,205 +1,203 @@ /*============================================================================ 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. ============================================================================*/ // Qmitk #include "QmitkRenderWindow.h" #include "QmitkSliceNavigationListener.h" #include "mitkIRenderWindowPart.h" // Qt #include #include ///********************************************** QmitkSliceNavigationListener::QmitkSliceNavigationListener() : m_renderWindowPart(nullptr), m_PendingSliceChangedEvent(false), m_CurrentSelectedPosition(std::numeric_limits::lowest()), m_CurrentSelectedTimePoint(std::numeric_limits::lowest()) { } QmitkSliceNavigationListener::~QmitkSliceNavigationListener() { this->RemoveAllObservers(); -}; +} mitk::TimePointType QmitkSliceNavigationListener::GetCurrentSelectedTimePoint() const { return m_CurrentSelectedTimePoint; } mitk::Point3D QmitkSliceNavigationListener::GetCurrentSelectedPosition() const { return m_CurrentSelectedPosition; } void QmitkSliceNavigationListener::OnSliceChangedDelayed() { m_PendingSliceChangedEvent = false; emit SliceChanged(); if (nullptr != m_renderWindowPart) { const auto newSelectedPosition = m_renderWindowPart->GetSelectedPosition(); const auto newSelectedTimePoint = m_renderWindowPart->GetSelectedTimePoint(); if (newSelectedPosition != m_CurrentSelectedPosition) { m_CurrentSelectedPosition = newSelectedPosition; emit SelectedPositionChanged(newSelectedPosition); } if (newSelectedTimePoint != m_CurrentSelectedTimePoint) { m_CurrentSelectedTimePoint = newSelectedTimePoint; emit SelectedTimePointChanged(newSelectedTimePoint); } } -}; +} -void -QmitkSliceNavigationListener::OnSliceChangedInternal(const itk::EventObject&) +void QmitkSliceNavigationListener::OnSliceChangedInternal(const itk::EventObject&) { - // Taken from QmitkStdMultiWidget::HandleCrosshairPositionEvent(). // Since there are always 3 events arriving (one for each render window) every time the slice // or time changes, the slot OnSliceChangedDelayed is triggered - and only if it hasn't been // triggered yet - so it is only executed once for every slice/time change. if (!m_PendingSliceChangedEvent) { m_PendingSliceChangedEvent = true; QTimer::singleShot(0, this, SLOT(OnSliceChangedDelayed())); } -}; +} void QmitkSliceNavigationListener::OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/) { const mitk::SliceNavigationController* sendingSlicer = dynamic_cast(sender); this->RemoveObservers(sendingSlicer); -}; +} void QmitkSliceNavigationListener::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_renderWindowPart != renderWindowPart) { m_renderWindowPart = renderWindowPart; if (!InitObservers()) { QMessageBox::information(nullptr, "Error", "Unable to set up the event observers. The " \ "plot will not be triggered on changing the crosshair, " \ "position or time step."); } m_CurrentSelectedPosition = m_renderWindowPart->GetSelectedPosition(); m_CurrentSelectedTimePoint = m_renderWindowPart->GetSelectedTimePoint(); } -}; +} void QmitkSliceNavigationListener::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) { m_renderWindowPart = nullptr; this->RemoveAllObservers(renderWindowPart); -}; +} bool QmitkSliceNavigationListener::InitObservers() { bool result = true; typedef QHash WindowMapType; WindowMapType windowMap = m_renderWindowPart->GetQmitkRenderWindows(); auto i = windowMap.begin(); while (i != windowMap.end()) { mitk::SliceNavigationController* sliceNavController = i.value()->GetSliceNavigationController(); if (sliceNavController) { itk::ReceptorMemberCommand::Pointer cmdSliceEvent = itk::ReceptorMemberCommand::New(); cmdSliceEvent->SetCallbackFunction(this, &QmitkSliceNavigationListener::OnSliceChangedInternal); int tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), cmdSliceEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_renderWindowPart))); itk::ReceptorMemberCommand::Pointer cmdTimeEvent = itk::ReceptorMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &QmitkSliceNavigationListener::OnSliceChangedInternal); tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometryTimeEvent(nullptr, 0), cmdTimeEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_renderWindowPart))); itk::MemberCommand::Pointer cmdDelEvent = itk::MemberCommand::New(); cmdDelEvent->SetCallbackFunction(this, &QmitkSliceNavigationListener::OnSliceNavigationControllerDeleted); tag = sliceNavController->AddObserver( itk::DeleteEvent(), cmdDelEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_renderWindowPart))); } ++i; result = result && sliceNavController; } return result; -}; +} void QmitkSliceNavigationListener::RemoveObservers(const mitk::SliceNavigationController* deletedSlicer) { std::pair < ObserverMapType::const_iterator, ObserverMapType::const_iterator> obsRange = m_ObserverMap.equal_range(deletedSlicer); for (ObserverMapType::const_iterator pos = obsRange.first; pos != obsRange.second; ++pos) { pos->second.controller->RemoveObserver(pos->second.observerTag); } m_ObserverMap.erase(deletedSlicer); -}; +} void QmitkSliceNavigationListener::RemoveAllObservers(mitk::IRenderWindowPart* deletedPart) { for (ObserverMapType::const_iterator pos = m_ObserverMap.begin(); pos != m_ObserverMap.end();) { ObserverMapType::const_iterator delPos = pos++; if (deletedPart == nullptr || deletedPart == delPos->second.renderWindowPart) { delPos->second.controller->RemoveObserver(delPos->second.observerTag); m_ObserverMap.erase(delPos); } } -}; +} QmitkSliceNavigationListener::ObserverInfo::ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, const std::string& renderWindowName, mitk::IRenderWindowPart* part) : controller(controller), observerTag(observerTag), renderWindowName(renderWindowName), renderWindowPart(part) { -}; +} diff --git a/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp b/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp index 9004dac7f8..d3b1171e2c 100644 --- a/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp +++ b/Plugins/org.mitk.gui.qt.dicominspector/src/internal/QmitkDicomInspectorView.cpp @@ -1,378 +1,377 @@ /*============================================================================ 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. ============================================================================*/ // Blueberry #include #include #include // mitk #include #include #include // Qt #include #include #include #include #include "QmitkDicomInspectorView.h" const std::string QmitkDicomInspectorView::VIEW_ID = "org.mitk.views.dicominspector"; QmitkDicomInspectorView::ObserverInfo::ObserverInfo(mitk::SliceNavigationController* controller, int observerTag, const std::string& renderWindowName, mitk::IRenderWindowPart* part) : controller(controller), observerTag(observerTag), renderWindowName(renderWindowName), renderWindowPart(part) { } QmitkDicomInspectorView::QmitkDicomInspectorView() : m_RenderWindowPart(nullptr) , m_PendingSliceChangedEvent(false) , m_SelectedNode(nullptr) , m_SelectedTimePoint(0.) , m_CurrentSelectedZSlice(0) { m_SelectedPosition.Fill(0.0); } QmitkDicomInspectorView::~QmitkDicomInspectorView() { this->RemoveAllObservers(); } void QmitkDicomInspectorView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (m_RenderWindowPart != renderWindowPart) { m_RenderWindowPart = renderWindowPart; if (!InitObservers()) { QMessageBox::information(nullptr, "Error", "Unable to set up the event observers. The " \ "plot will not be triggered on changing the crosshair, " \ "position or time step."); } } } void QmitkDicomInspectorView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* renderWindowPart) { m_RenderWindowPart = nullptr; this->RemoveAllObservers(renderWindowPart); } void QmitkDicomInspectorView::CreateQtPartControl(QWidget* parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); m_Controls.singleSlot->SetDataStorage(GetDataStorage()); m_Controls.singleSlot->SetSelectionIsOptional(true); m_Controls.singleSlot->SetAutoSelectNewNodes(true); m_Controls.singleSlot->SetEmptyInfo(QString("Please select a data node")); m_Controls.singleSlot->SetPopUpTitel(QString("Select data node")); m_SelectionServiceConnector = std::make_unique(); SetAsSelectionListener(true); m_Controls.timePointValueLabel->setText(QString("")); m_Controls.sliceNumberValueLabel->setText(QString("")); connect(m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkDicomInspectorView::OnCurrentSelectionChanged); mitk::IRenderWindowPart* renderWindowPart = GetRenderWindowPart(); RenderWindowPartActivated(renderWindowPart); } bool QmitkDicomInspectorView::InitObservers() { bool result = true; typedef QHash WindowMapType; WindowMapType windowMap = m_RenderWindowPart->GetQmitkRenderWindows(); auto i = windowMap.begin(); while (i != windowMap.end()) { mitk::SliceNavigationController* sliceNavController = i.value()->GetSliceNavigationController(); if (sliceNavController) { auto cmdSliceEvent = itk::SimpleMemberCommand::New(); cmdSliceEvent->SetCallbackFunction(this, &QmitkDicomInspectorView::OnSliceChanged); int tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometrySliceEvent(nullptr, 0), cmdSliceEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_RenderWindowPart))); auto cmdTimeEvent = itk::SimpleMemberCommand::New(); cmdTimeEvent->SetCallbackFunction(this, &QmitkDicomInspectorView::OnSliceChanged); tag = sliceNavController->AddObserver( mitk::SliceNavigationController::GeometryTimeEvent(nullptr, 0), cmdTimeEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_RenderWindowPart))); auto cmdDelEvent = itk::MemberCommand::New(); cmdDelEvent->SetCallbackFunction(this, &QmitkDicomInspectorView::OnSliceNavigationControllerDeleted); tag = sliceNavController->AddObserver(itk::DeleteEvent(), cmdDelEvent); m_ObserverMap.insert(std::make_pair(sliceNavController, ObserverInfo(sliceNavController, tag, i.key().toStdString(), m_RenderWindowPart))); } ++i; result = result && sliceNavController; } return result; } void QmitkDicomInspectorView::RemoveObservers(const mitk::SliceNavigationController* deletedSlicer) { std::pair obsRange = m_ObserverMap.equal_range(deletedSlicer); for (ObserverMapType::const_iterator pos = obsRange.first; pos != obsRange.second; ++pos) { pos->second.controller->RemoveObserver(pos->second.observerTag); } m_ObserverMap.erase(deletedSlicer); } void QmitkDicomInspectorView::RemoveAllObservers(mitk::IRenderWindowPart* deletedPart) { for (ObserverMapType::const_iterator pos = m_ObserverMap.begin(); pos != m_ObserverMap.end();) { ObserverMapType::const_iterator delPos = pos++; if (nullptr == deletedPart || deletedPart == delPos->second.renderWindowPart) { delPos->second.controller->RemoveObserver(delPos->second.observerTag); m_ObserverMap.erase(delPos); } } } void QmitkDicomInspectorView::OnCurrentSelectionChanged(QList nodes) { if (nodes.empty() || nodes.front().IsNull()) { m_SelectedNode = nullptr; m_SelectedData = nullptr; UpdateData(); return; } if (nodes.front() != this->m_SelectedNode) { // node is selected, create DICOM tag table m_SelectedNode = nodes.front(); m_SelectedData = this->m_SelectedNode->GetData(); m_SelectedNodeTime.Modified(); UpdateData(); OnSliceChangedDelayed(); } } void QmitkDicomInspectorView::OnSliceChanged() { - // Taken from QmitkStdMultiWidget::HandleCrosshairPositionEvent(). // Since there are always 3 events arriving (one for each render window) every time the slice // or time changes, the slot OnSliceChangedDelayed is triggered - and only if it hasn't been // triggered yet - so it is only executed once for every slice/time change. if (!m_PendingSliceChangedEvent) { m_PendingSliceChangedEvent = true; QTimer::singleShot(0, this, SLOT(OnSliceChangedDelayed())); } } void QmitkDicomInspectorView::OnSliceNavigationControllerDeleted(const itk::Object* sender, const itk::EventObject& /*e*/) { auto sendingSlicer = dynamic_cast(sender); this->RemoveObservers(sendingSlicer); } void QmitkDicomInspectorView::ValidateAndSetCurrentPosition() { auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto currentSelectedPosition = renderWindowPart->GetSelectedPosition(nullptr); const auto currentSelectedTimePoint = renderWindowPart->GetSelectedTimePoint(); if (m_SelectedPosition != currentSelectedPosition || m_SelectedTimePoint != currentSelectedTimePoint || m_SelectedNodeTime > m_CurrentPositionTime) { // the current position has been changed, the selected node has been changed since // the last position validation or the current time position has been changed -> check position m_SelectedPosition = currentSelectedPosition; m_SelectedTimePoint = currentSelectedTimePoint; m_CurrentPositionTime.Modified(); m_ValidSelectedPosition = false; if (m_SelectedData.IsNull()) { return; } mitk::BaseGeometry::Pointer geometry = m_SelectedData->GetTimeGeometry()->GetGeometryForTimePoint(m_SelectedTimePoint); // check for invalid time step if (geometry.IsNull()) { geometry = m_SelectedData->GetTimeGeometry()->GetGeometryForTimeStep(0); } if (geometry.IsNull()) { return; } m_ValidSelectedPosition = geometry->IsInside(m_SelectedPosition); itk::Index<3> index; geometry->WorldToIndex(m_SelectedPosition, index); m_CurrentSelectedZSlice = index[2]; } } void QmitkDicomInspectorView::OnSliceChangedDelayed() { m_PendingSliceChangedEvent = false; ValidateAndSetCurrentPosition(); m_Controls.tableTags->setEnabled(m_ValidSelectedPosition); if (m_SelectedNode.IsNotNull()) { RenderTable(); } } void QmitkDicomInspectorView::RenderTable() { assert(nullptr != m_RenderWindowPart); const auto timeStep = (m_SelectedData.IsNull()) ? 0 : m_SelectedData->GetTimeGeometry()->TimePointToTimeStep(m_SelectedTimePoint); unsigned int rowIndex = 0; for (const auto& element : m_Tags) { QTableWidgetItem* newItem = new QTableWidgetItem(QString::fromStdString( element.second.prop->GetValue(timeStep, m_CurrentSelectedZSlice, true, true))); m_Controls.tableTags->setItem(rowIndex, 3, newItem); ++rowIndex; } UpdateLabels(); } void QmitkDicomInspectorView::UpdateData() { QStringList headers; m_Controls.tableTags->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); m_Tags.clear(); if (m_SelectedData.IsNotNull()) { for (const auto& element : *(m_SelectedData->GetPropertyList()->GetMap())) { if (element.first.find("DICOM") == 0) { std::istringstream stream(element.first); std::string token; std::getline(stream, token, '.'); //drop the DICOM suffix std::getline(stream, token, '.'); //group id unsigned long dcmgroup = std::stoul(token, nullptr, 16); std::getline(stream, token, '.'); //element id unsigned long dcmelement = std::stoul(token, nullptr, 16); TagInfo info(mitk::DICOMTag(dcmgroup, dcmelement), dynamic_cast(element.second.GetPointer())); m_Tags.insert(std::make_pair(element.first, info)); } } } m_Controls.tableTags->setRowCount(m_Tags.size()); unsigned int rowIndex = 0; for (const auto& element : m_Tags) { QTableWidgetItem* newItem = new QTableWidgetItem(QString::number(element.second.tag.GetGroup(), 16)); m_Controls.tableTags->setItem(rowIndex, 0, newItem); newItem = new QTableWidgetItem(QString::number(element.second.tag.GetElement(), 16)); m_Controls.tableTags->setItem(rowIndex, 1, newItem); newItem = new QTableWidgetItem(QString::fromStdString(element.second.tag.GetName())); m_Controls.tableTags->setItem(rowIndex, 2, newItem); newItem = new QTableWidgetItem(QString::fromStdString(element.second.prop->GetValue())); m_Controls.tableTags->setItem(rowIndex, 3, newItem); ++rowIndex; } UpdateLabels(); } void QmitkDicomInspectorView::UpdateLabels() { if (m_SelectedData.IsNull()) { m_Controls.timePointValueLabel->setText(QString("")); m_Controls.sliceNumberValueLabel->setText(QString("")); } else { const auto timeStep = m_SelectedData->GetTimeGeometry()->TimePointToTimeStep(m_SelectedTimePoint); if (m_ValidSelectedPosition) { m_Controls.timePointValueLabel->setText(QString::number(timeStep) + QStringLiteral("(")+ QString::number(m_SelectedTimePoint/1000.) + QStringLiteral(" [s])")); m_Controls.sliceNumberValueLabel->setText(QString::number(m_CurrentSelectedZSlice)); } else { m_Controls.timePointValueLabel->setText(QString("outside data geometry")); m_Controls.sliceNumberValueLabel->setText(QString("outside data geometry")); } } } void QmitkDicomInspectorView::SetAsSelectionListener(bool checked) { if (checked) { m_SelectionServiceConnector->AddPostSelectionListener(GetSite()->GetWorkbenchWindow()->GetSelectionService()); connect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } else { m_SelectionServiceConnector->RemovePostSelectionListener(); disconnect(m_SelectionServiceConnector.get(), &QmitkSelectionServiceConnector::ServiceSelectionChanged, m_Controls.singleSlot, &QmitkSingleNodeSelectionWidget::SetCurrentSelection); } } diff --git a/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.cpp b/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.cpp index b0423f5a91..af22d86bbf 100644 --- a/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.cpp +++ b/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.cpp @@ -1,563 +1,456 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkImageNavigatorView.h" #include #include #include #include #include #include -#include -#include -#include -#include -#include - -#include const std::string QmitkImageNavigatorView::VIEW_ID = "org.mitk.views.imagenavigator"; QmitkImageNavigatorView::QmitkImageNavigatorView() : m_AxialStepper(nullptr) , m_SagittalStepper(nullptr) , m_CoronalStepper(nullptr) , m_TimeStepper(nullptr) , m_Parent(nullptr) , m_IRenderWindowPart(nullptr) { } QmitkImageNavigatorView::~QmitkImageNavigatorView() { } void QmitkImageNavigatorView::CreateQtPartControl(QWidget *parent) { // create GUI widgets m_Parent = parent; m_Controls.setupUi(parent); connect(m_Controls.m_XWorldCoordinateSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnMillimetreCoordinateValueChanged())); connect(m_Controls.m_YWorldCoordinateSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnMillimetreCoordinateValueChanged())); connect(m_Controls.m_ZWorldCoordinateSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnMillimetreCoordinateValueChanged())); m_Parent->setEnabled(false); mitk::IRenderWindowPart* renderPart = this->GetRenderWindowPart(); this->RenderWindowPartActivated(renderPart); } void QmitkImageNavigatorView::SetFocus () { m_Controls.m_XWorldCoordinateSpinBox->setFocus(); } void QmitkImageNavigatorView::RenderWindowPartActivated(mitk::IRenderWindowPart* renderWindowPart) { if (this->m_IRenderWindowPart != renderWindowPart) { this->m_IRenderWindowPart = renderWindowPart; this->m_Parent->setEnabled(true); QmitkRenderWindow* renderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); if (renderWindow) { if (m_AxialStepper) m_AxialStepper->deleteLater(); m_AxialStepper = new QmitkStepperAdapter(m_Controls.m_SliceNavigatorAxial, renderWindow->GetSliceNavigationController()->GetSlice(), "sliceNavigatorAxialFromSimpleExample"); m_Controls.m_SliceNavigatorAxial->setEnabled(true); m_Controls.m_AxialLabel->setEnabled(true); m_Controls.m_ZWorldCoordinateSpinBox->setEnabled(true); connect(m_AxialStepper, SIGNAL(Refetch()), this, SLOT(OnRefetch())); - connect(m_AxialStepper, SIGNAL(Refetch()), this, SLOT(UpdateStatusBar())); } else { m_Controls.m_SliceNavigatorAxial->setEnabled(false); m_Controls.m_AxialLabel->setEnabled(false); m_Controls.m_ZWorldCoordinateSpinBox->setEnabled(false); } renderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); if (renderWindow) { if (m_SagittalStepper) m_SagittalStepper->deleteLater(); m_SagittalStepper = new QmitkStepperAdapter(m_Controls.m_SliceNavigatorSagittal, renderWindow->GetSliceNavigationController()->GetSlice(), "sliceNavigatorSagittalFromSimpleExample"); m_Controls.m_SliceNavigatorSagittal->setEnabled(true); m_Controls.m_SagittalLabel->setEnabled(true); m_Controls.m_YWorldCoordinateSpinBox->setEnabled(true); connect(m_SagittalStepper, SIGNAL(Refetch()), this, SLOT(OnRefetch())); - connect(m_SagittalStepper, SIGNAL(Refetch()), this, SLOT(UpdateStatusBar())); } else { m_Controls.m_SliceNavigatorSagittal->setEnabled(false); m_Controls.m_SagittalLabel->setEnabled(false); m_Controls.m_YWorldCoordinateSpinBox->setEnabled(false); } renderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); if (renderWindow) { if (m_CoronalStepper) m_CoronalStepper->deleteLater(); m_CoronalStepper = new QmitkStepperAdapter(m_Controls.m_SliceNavigatorCoronal, renderWindow->GetSliceNavigationController()->GetSlice(), "sliceNavigatorCoronalFromSimpleExample"); m_Controls.m_SliceNavigatorCoronal->setEnabled(true); m_Controls.m_CoronalLabel->setEnabled(true); m_Controls.m_XWorldCoordinateSpinBox->setEnabled(true); connect(m_CoronalStepper, SIGNAL(Refetch()), this, SLOT(OnRefetch())); - connect(m_CoronalStepper, SIGNAL(Refetch()), this, SLOT(UpdateStatusBar())); } else { m_Controls.m_SliceNavigatorCoronal->setEnabled(false); m_Controls.m_CoronalLabel->setEnabled(false); m_Controls.m_XWorldCoordinateSpinBox->setEnabled(false); } mitk::SliceNavigationController* timeController = renderWindowPart->GetTimeNavigationController(); if (timeController) { if (m_TimeStepper) m_TimeStepper->deleteLater(); m_TimeStepper = new QmitkStepperAdapter(m_Controls.m_SliceNavigatorTime, timeController->GetTime(), "sliceNavigatorTimeFromSimpleExample"); m_Controls.m_SliceNavigatorTime->setEnabled(true); m_Controls.m_TimeLabel->setEnabled(true); - connect(m_TimeStepper, SIGNAL(Refetch()), this, SLOT(UpdateStatusBar())); } else { m_Controls.m_SliceNavigatorTime->setEnabled(false); m_Controls.m_TimeLabel->setEnabled(false); } this->OnRefetch(); - this->UpdateStatusBar(); - } -} - -void QmitkImageNavigatorView::UpdateStatusBar() -{ - if (m_IRenderWindowPart != nullptr) - { - mitk::Point3D position = m_IRenderWindowPart->GetSelectedPosition(); - mitk::BaseRenderer::Pointer baseRenderer = mitk::BaseRenderer::GetInstance(m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetVtkRenderWindow()); - auto globalCurrentTimePoint = baseRenderer->GetTime(); - mitk::TNodePredicateDataType::Pointer isImageData = mitk::TNodePredicateDataType::New(); - - mitk::DataStorage::SetOfObjects::ConstPointer nodes = GetDataStorage()->GetSubset(isImageData).GetPointer(); - - if (nodes.IsNotNull()) - { - mitk::Image::Pointer image3D; - mitk::DataNode::Pointer node; - mitk::DataNode::Pointer topSourceNode; - - int component = 0; - - node = mitk::FindTopmostVisibleNode(nodes, position, globalCurrentTimePoint, baseRenderer); - - if (node.IsNotNull()) - { - bool isBinary(false); - node->GetBoolProperty("binary", isBinary); - if (isBinary) - { - mitk::DataStorage::SetOfObjects::ConstPointer sourcenodes = GetDataStorage()->GetSources(node, nullptr, true); - if (!sourcenodes->empty()) - { - topSourceNode = mitk::FindTopmostVisibleNode(sourcenodes, position, globalCurrentTimePoint, baseRenderer); - } - if (topSourceNode.IsNotNull()) - { - image3D = dynamic_cast(topSourceNode->GetData()); - topSourceNode->GetIntProperty("Image.Displayed Component", component); - } - else - { - image3D = dynamic_cast(node->GetData()); - node->GetIntProperty("Image.Displayed Component", component); - } - } - else - { - image3D = dynamic_cast(node->GetData()); - node->GetIntProperty("Image.Displayed Component", component); - } - } - - // get the position and pixel value from the image and build up status bar text - auto statusBar = mitk::StatusBar::GetInstance(); - - if (image3D.IsNotNull() && statusBar != nullptr) - { - itk::Index<3> p; - image3D->GetGeometry()->WorldToIndex(position, p); - - auto pixelType = image3D->GetChannelDescriptor().GetPixelType().GetPixelType(); - - if (pixelType == itk::IOPixelEnum::RGB || pixelType == itk::IOPixelEnum::RGBA) - { - std::string pixelValue = "Pixel RGB(A) value: "; - pixelValue.append(ConvertCompositePixelValueToString(image3D, p)); - statusBar->DisplayImageInfo(position, p, globalCurrentTimePoint, pixelValue.c_str()); - } - else if (pixelType == itk::IOPixelEnum::DIFFUSIONTENSOR3D || pixelType == itk::IOPixelEnum::SYMMETRICSECONDRANKTENSOR) - { - std::string pixelValue = "See ODF Details view. "; - statusBar->DisplayImageInfo(position, p, globalCurrentTimePoint, pixelValue.c_str()); - } - else - { - itk::Index<3> p; - image3D->GetGeometry()->WorldToIndex(position, p); - mitk::ScalarType pixelValue; - mitkPixelTypeMultiplex5( - mitk::FastSinglePixelAccess, - image3D->GetChannelDescriptor().GetPixelType(), - image3D, - image3D->GetVolumeData(image3D->GetTimeGeometry()->TimePointToTimeStep(globalCurrentTimePoint)), - p, - pixelValue, - component); - statusBar->DisplayImageInfo(position, p, globalCurrentTimePoint, pixelValue); - } - } - else - { - statusBar->DisplayImageInfoInvalid(); - } - } } } void QmitkImageNavigatorView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_IRenderWindowPart = nullptr; m_Parent->setEnabled(false); } int QmitkImageNavigatorView::GetSizeFlags(bool width) { if(!width) { return berry::Constants::MIN | berry::Constants::MAX | berry::Constants::FILL; } else { return 0; } } int QmitkImageNavigatorView::ComputePreferredSize(bool width, int /*availableParallel*/, int /*availablePerpendicular*/, int preferredResult) { if(width==false) { return 200; } else { return preferredResult; } } int QmitkImageNavigatorView::GetClosestAxisIndex(mitk::Vector3D normal) { // cos(theta) = normal . axis // cos(theta) = (a, b, c) . (d, e, f) // cos(theta) = (a, b, c) . (1, 0, 0) = a // cos(theta) = (a, b, c) . (0, 1, 0) = b // cos(theta) = (a, b, c) . (0, 0, 1) = c double absCosThetaWithAxis[3]; for (int i = 0; i < 3; i++) { absCosThetaWithAxis[i] = fabs(normal[i]); } int largestIndex = 0; double largestValue = absCosThetaWithAxis[0]; for (int i = 1; i < 3; i++) { if (absCosThetaWithAxis[i] > largestValue) { largestValue = absCosThetaWithAxis[i]; largestIndex = i; } } return largestIndex; } void QmitkImageNavigatorView::SetBorderColors() { if (m_IRenderWindowPart) { QString decoColor; QmitkRenderWindow* renderWindow = m_IRenderWindowPart->GetQmitkRenderWindow("axial"); if (renderWindow) { decoColor = GetDecorationColorOfGeometry(renderWindow); mitk::PlaneGeometry::ConstPointer geometry = renderWindow->GetSliceNavigationController()->GetCurrentPlaneGeometry(); if (geometry.IsNotNull()) { mitk::Vector3D normal = geometry->GetNormal(); int axis = this->GetClosestAxisIndex(normal); this->SetBorderColor(axis, decoColor); } } renderWindow = m_IRenderWindowPart->GetQmitkRenderWindow("sagittal"); if (renderWindow) { decoColor = GetDecorationColorOfGeometry(renderWindow); mitk::PlaneGeometry::ConstPointer geometry = renderWindow->GetSliceNavigationController()->GetCurrentPlaneGeometry(); if (geometry.IsNotNull()) { mitk::Vector3D normal = geometry->GetNormal(); int axis = this->GetClosestAxisIndex(normal); this->SetBorderColor(axis, decoColor); } } renderWindow = m_IRenderWindowPart->GetQmitkRenderWindow("coronal"); if (renderWindow) { decoColor = GetDecorationColorOfGeometry(renderWindow); mitk::PlaneGeometry::ConstPointer geometry = renderWindow->GetSliceNavigationController()->GetCurrentPlaneGeometry(); if (geometry.IsNotNull()) { mitk::Vector3D normal = geometry->GetNormal(); int axis = this->GetClosestAxisIndex(normal); this->SetBorderColor(axis, decoColor); } } } } QString QmitkImageNavigatorView::GetDecorationColorOfGeometry(QmitkRenderWindow* renderWindow) { QColor color; float rgb[3] = {1.0f, 1.0f, 1.0f}; float rgbMax = 255.0f; mitk::BaseRenderer::GetInstance(renderWindow->GetVtkRenderWindow())->GetCurrentWorldPlaneGeometryNode()->GetColor(rgb); color.setRed(static_cast(rgb[0]*rgbMax + 0.5)); color.setGreen(static_cast(rgb[1]*rgbMax + 0.5)); color.setBlue(static_cast(rgb[2]*rgbMax + 0.5)); QString colorAsString = QString(color.name()); return colorAsString; } void QmitkImageNavigatorView::SetBorderColor(int axis, QString colorAsStyleSheetString) { if (axis == 0) { this->SetBorderColor(m_Controls.m_XWorldCoordinateSpinBox, colorAsStyleSheetString); } else if (axis == 1) { this->SetBorderColor(m_Controls.m_YWorldCoordinateSpinBox, colorAsStyleSheetString); } else if (axis == 2) { this->SetBorderColor(m_Controls.m_ZWorldCoordinateSpinBox, colorAsStyleSheetString); } } void QmitkImageNavigatorView::SetBorderColor(QDoubleSpinBox *spinBox, QString colorAsStyleSheetString) { assert(spinBox); spinBox->setStyleSheet(QString("border: 2px solid ") + colorAsStyleSheetString + ";"); } void QmitkImageNavigatorView::OnMillimetreCoordinateValueChanged() { if (m_IRenderWindowPart) { mitk::TimeGeometry::ConstPointer geometry = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetInputWorldTimeGeometry(); if (geometry.IsNotNull()) { mitk::Point3D positionInWorldCoordinates; positionInWorldCoordinates[0] = m_Controls.m_XWorldCoordinateSpinBox->value(); positionInWorldCoordinates[1] = m_Controls.m_YWorldCoordinateSpinBox->value(); positionInWorldCoordinates[2] = m_Controls.m_ZWorldCoordinateSpinBox->value(); m_IRenderWindowPart->SetSelectedPosition(positionInWorldCoordinates); } } } void QmitkImageNavigatorView::OnRefetch() { if (nullptr == m_IRenderWindowPart) { return; } mitk::TimeGeometry::ConstPointer timeGeometry = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetInputWorldTimeGeometry(); if (timeGeometry.IsNull()) { return; } SetVisibilityOfTimeSlider(timeGeometry->CountTimeSteps()); mitk::TimeStepType timeStep = m_IRenderWindowPart->GetActiveQmitkRenderWindow()->GetSliceNavigationController()->GetTime()->GetPos(); mitk::BaseGeometry::Pointer geometry = timeGeometry->GetGeometryForTimeStep(timeStep); if (geometry.IsNotNull()) { mitk::BoundingBox::BoundsArrayType bounds = geometry->GetBounds(); mitk::Point3D cornerPoint1InIndexCoordinates; cornerPoint1InIndexCoordinates[0] = bounds[0]; cornerPoint1InIndexCoordinates[1] = bounds[2]; cornerPoint1InIndexCoordinates[2] = bounds[4]; mitk::Point3D cornerPoint2InIndexCoordinates; cornerPoint2InIndexCoordinates[0] = bounds[1]; cornerPoint2InIndexCoordinates[1] = bounds[3]; cornerPoint2InIndexCoordinates[2] = bounds[5]; if (!geometry->GetImageGeometry()) { cornerPoint1InIndexCoordinates[0] += 0.5; cornerPoint1InIndexCoordinates[1] += 0.5; cornerPoint1InIndexCoordinates[2] += 0.5; cornerPoint2InIndexCoordinates[0] -= 0.5; cornerPoint2InIndexCoordinates[1] -= 0.5; cornerPoint2InIndexCoordinates[2] -= 0.5; } mitk::Point3D crossPositionInWorldCoordinates = m_IRenderWindowPart->GetSelectedPosition(); mitk::Point3D cornerPoint1InWorldCoordinates; mitk::Point3D cornerPoint2InWorldCoordinates; geometry->IndexToWorld(cornerPoint1InIndexCoordinates, cornerPoint1InWorldCoordinates); geometry->IndexToWorld(cornerPoint2InIndexCoordinates, cornerPoint2InWorldCoordinates); m_Controls.m_XWorldCoordinateSpinBox->blockSignals(true); m_Controls.m_YWorldCoordinateSpinBox->blockSignals(true); m_Controls.m_ZWorldCoordinateSpinBox->blockSignals(true); m_Controls.m_XWorldCoordinateSpinBox->setMinimum( std::min(cornerPoint1InWorldCoordinates[0], cornerPoint2InWorldCoordinates[0])); m_Controls.m_YWorldCoordinateSpinBox->setMinimum( std::min(cornerPoint1InWorldCoordinates[1], cornerPoint2InWorldCoordinates[1])); m_Controls.m_ZWorldCoordinateSpinBox->setMinimum( std::min(cornerPoint1InWorldCoordinates[2], cornerPoint2InWorldCoordinates[2])); m_Controls.m_XWorldCoordinateSpinBox->setMaximum( std::max(cornerPoint1InWorldCoordinates[0], cornerPoint2InWorldCoordinates[0])); m_Controls.m_YWorldCoordinateSpinBox->setMaximum( std::max(cornerPoint1InWorldCoordinates[1], cornerPoint2InWorldCoordinates[1])); m_Controls.m_ZWorldCoordinateSpinBox->setMaximum( std::max(cornerPoint1InWorldCoordinates[2], cornerPoint2InWorldCoordinates[2])); m_Controls.m_XWorldCoordinateSpinBox->setValue(crossPositionInWorldCoordinates[0]); m_Controls.m_YWorldCoordinateSpinBox->setValue(crossPositionInWorldCoordinates[1]); m_Controls.m_ZWorldCoordinateSpinBox->setValue(crossPositionInWorldCoordinates[2]); m_Controls.m_XWorldCoordinateSpinBox->blockSignals(false); m_Controls.m_YWorldCoordinateSpinBox->blockSignals(false); m_Controls.m_ZWorldCoordinateSpinBox->blockSignals(false); /// Calculating 'inverse direction' property. mitk::AffineTransform3D::MatrixType matrix = geometry->GetIndexToWorldTransform()->GetMatrix(); matrix.GetVnlMatrix().normalize_columns(); mitk::AffineTransform3D::MatrixType::InternalMatrixType inverseMatrix = matrix.GetInverse(); for (int worldAxis = 0; worldAxis < 3; ++worldAxis) { QmitkRenderWindow *renderWindow = worldAxis == 0 ? m_IRenderWindowPart->GetQmitkRenderWindow("sagittal") : worldAxis == 1 ? m_IRenderWindowPart->GetQmitkRenderWindow("coronal") : m_IRenderWindowPart->GetQmitkRenderWindow("axial"); if (renderWindow) { const mitk::BaseGeometry *rendererGeometry = renderWindow->GetRenderer()->GetCurrentWorldGeometry(); /// Because of some problems with the current way of event signalling, /// 'Modified' events are sent out from the stepper while the renderer /// does not have a geometry yet. Therefore, we do a nullptr check here. /// See bug T22122. This check can be resolved after T22122 got fixed. if (rendererGeometry) { int dominantAxis = itk::Function::Max3(inverseMatrix[0][worldAxis], inverseMatrix[1][worldAxis], inverseMatrix[2][worldAxis]); bool referenceGeometryAxisInverted = inverseMatrix[dominantAxis][worldAxis] < 0; bool rendererZAxisInverted = rendererGeometry->GetAxisVector(2)[worldAxis] < 0; /// `referenceGeometryAxisInverted` tells if the direction of the corresponding axis /// of the reference geometry is flipped compared to the 'world direction' or not. /// /// `rendererZAxisInverted` tells if direction of the renderer geometry z axis is /// flipped compared to the 'world direction' or not. This is the same as the indexing /// direction in the slice navigation controller and matches the 'top' property when /// initialising the renderer planes. (If 'top' was true then the direction is /// inverted.) /// /// The world direction can be +1 ('up') that means right, anterior or superior, or /// it can be -1 ('down') that means left, posterior or inferior, respectively. /// /// If these two do not match, we have to invert the index between the slice navigation /// controller and the slider navigator widget, so that the user can see and control /// the index according to the reference geometry, rather than the slice navigation /// controller. The index in the slice navigation controller depends on in which way /// the reference geometry has been resliced for the renderer, and it does not necessarily /// match neither the world direction, nor the direction of the corresponding axis of /// the reference geometry. Hence, it is a merely internal information that should not /// be exposed to the GUI. /// /// So that one can navigate in the same world direction by dragging the slider /// right, regardless of the direction of the corresponding axis of the reference /// geometry, we invert the direction of the controls if the reference geometry axis /// is inverted but the direction is not ('inversDirection' is false) or the other /// way around. bool inverseDirection = referenceGeometryAxisInverted != rendererZAxisInverted; QmitkSliderNavigatorWidget* navigatorWidget = worldAxis == 0 ? m_Controls.m_SliceNavigatorSagittal : worldAxis == 1 ? m_Controls.m_SliceNavigatorCoronal : m_Controls.m_SliceNavigatorAxial; navigatorWidget->SetInverseDirection(inverseDirection); // This should be a preference (see T22254) // bool invertedControls = referenceGeometryAxisInverted != inverseDirection; // navigatorWidget->SetInvertedControls(invertedControls); } } } } this->SetBorderColors(); } void QmitkImageNavigatorView::SetVisibilityOfTimeSlider(std::size_t timeSteps) { m_Controls.m_SliceNavigatorTime->setVisible(timeSteps > 1); m_Controls.m_TimeLabel->setVisible(timeSteps > 1); } diff --git a/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.h b/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.h index 110ef123c4..419a40aa99 100644 --- a/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.h +++ b/Plugins/org.mitk.gui.qt.imagenavigator/src/internal/QmitkImageNavigatorView.h @@ -1,95 +1,94 @@ /*============================================================================ 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 QMITKIMAGENAVIGATORVIEW_H #define QMITKIMAGENAVIGATORVIEW_H #include #include #include #include "ui_QmitkImageNavigatorViewControls.h" class QmitkStepperAdapter; /*! * \ingroup org_mitk_gui_qt_imagenavigator_internal * * \class QmitkImageNavigatorView * * \brief Provides a means to scan quickly through a dataset via Axial, * Coronal and Sagittal sliders, displaying millimetre location and stepper position. * * For images, the stepper position corresponds to a voxel index. For other datasets * such as a surface, it corresponds to a sub-division of the bounding box. * * \sa QmitkAbstractView */ class QmitkImageNavigatorView : public QmitkAbstractView, public mitk::IRenderWindowPartListener, public berry::ISizeProvider { Q_OBJECT public: static const std::string VIEW_ID; QmitkImageNavigatorView(); ~QmitkImageNavigatorView() override; void CreateQtPartControl(QWidget *parent) override; int GetSizeFlags(bool width) override; int ComputePreferredSize(bool width, int /*availableParallel*/, int /*availablePerpendicular*/, int preferredResult) override; protected slots: void OnMillimetreCoordinateValueChanged(); void OnRefetch(); - void UpdateStatusBar(); protected: void SetFocus() override; void RenderWindowPartActivated(mitk::IRenderWindowPart *renderWindowPart) override; void RenderWindowPartDeactivated(mitk::IRenderWindowPart *renderWindowPart) override; void SetBorderColors(); void SetBorderColor(QDoubleSpinBox *spinBox, QString colorAsStyleSheetString); void SetBorderColor(int axis, QString colorAsStyleSheetString); int GetClosestAxisIndex(mitk::Vector3D normal); void SetVisibilityOfTimeSlider(std::size_t timeSteps); Ui::QmitkImageNavigatorViewControls m_Controls; QmitkStepperAdapter* m_AxialStepper; QmitkStepperAdapter* m_SagittalStepper; QmitkStepperAdapter* m_CoronalStepper; QmitkStepperAdapter* m_TimeStepper; QWidget* m_Parent; mitk::IRenderWindowPart* m_IRenderWindowPart; /** * @brief GetDecorationColorOfGeometry helper method to get the color of a helper geometry node. * @param renderWindow The renderwindow of the geometry * @return the color for decoration in QString format (#RRGGBB). */ QString GetDecorationColorOfGeometry(QmitkRenderWindow *renderWindow); }; #endif // QMITKIMAGENAVIGATORVIEW_H diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox index 8fff9e1c8c..600bec623f 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox +++ b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation.dox @@ -1,415 +1,432 @@ /** \page org_mitk_views_segmentation The Segmentation View \imageMacro{segmentation-dox.svg,"Icon of the segmentation view",2.00} Some of the features described below are closed source additions to the open source toolkit MITK and are not available in every application. \tableofcontents \section org_mitk_views_segmentationoverview Overview Segmentation is the act of partitioning an image into subsets by either manual or automated delineation to create i.e. a distinction between foreground and background. A multilabel segmentation can contain more than one label and more than one layer. This allows you to create different labels for different regions of interest encapsulated in one single image. The difference between labels and layers is that labels on one layer cannot overlap but labels on different layers can. The MITK segmentation plugin allows you to create multilabel segmentations of anatomical and pathological structures in medical images. The plugin consists of two views:
  • Segmentation View: Manual and (semi-)automatic segmentation
  • \subpage org_mitk_views_segmentationutilities : Segmentation post-processing
In this documentation, the features and usage of the segmentation view will be described. For an introduction to the segmentation utilities please be referred to the respective documentation pages. \imageMacro{QmitkSegmentationPlugin_Overview.png,"Segmentation plugin overview", 16.00} \section org_mitk_views_segmentationpreferences Preferences The segmentation plugin offers a number of preferences which can be set via the MITK Workbench application preferences: \imageMacro{QmitkSegmentationPreferences.png,"Segmentation preferences", 10.00}
  • Slim view: Allows to show or hide the tool button description of the segmentation view
  • 2D display: Specify whether the segmentation is drawn as outline or as a transparent overlay
  • Show only selected nodes: Enable if only the selected segmentation and the reference image should be visible
  • Smoothed surface creation: Set certain smoothing parameters for surface creation
  • Default label set preset: Start a new segmentation with this preset instead of a default label
  • Label creation: Assign default names and colors to new labels or ask users for them
  • Label suggestions: Specify custom suggestions for label names and colors
\section org_mitk_views_segmentationtechnicalissues Technical issues The segmentation plugin makes a number of assumptions:
  • Images must be 2D, 3D, or 3D+t.
  • Images must be single-values, i.e. CT, MRI or "normal" ultrasound. Images from color doppler or photographic (RGB) images are only partially supported (please be aware that some tools might not be compatible with this image type).
  • Segmentations are handled as multilabel images of the same extent as the original image.
\section org_mitk_views_segmentationdataselection Data selection & creating new segmentations To select a reference image for the segmentation, click on the Selected image selection widget and choose a suitable image from the selection available in the data manager. Once an image is selected, a new segmentation can be created on this reference image by clicking the button to the right of the Selected segmentation selection widget. A new multilabel segmentation with an initial label is automatically generated. The new segmentation will be added to the data manager as sub-node of the reference image. This item is then automatically selected in the data selection, which allows to start editing the new segmentation right away. \imageMacro{"QmitkSegmentation_DataSelection.png","Data selection",12} Alternatively to creating a new segmentation, an existing one can be edited as well. If a reference image is selected for which a segmentation already exists in the data manager, the auto selection mode will automatically select a fitting segmentation. Clicking on the segmentation selection widget a drop down list will open, containing all suitable segmentations for the selected reference dataset available in the data manager. \section org_mitk_views_segmentationlayers Segmentation layers For each multilabel segmentation different layers can be added or deleted. The layers can be used independently and layers can be switched using the left and right arrows. A layer is a set of labels that occupy a non-overlapping anatomical space. The best way to describe them is by a real use case: Imagine you are working on a radiotherapy planning application. In the first layer of your segmentation session, you would like to trace the contours of the liver and neighboring organs. You can accommodate all these segmentations in separate labels because they all occupy different anatomical regions and do not overlap. Now say you would like to segment the arteries and veins inside the liver. If you don't trace them in a different layer, you will overwrite the previous ones. You may also need a third layer for segmenting the different irrigation territories in the liver and a fourth layer to contain the lesion you would like to treat. \imageMacro{"QmitkSegmentation_LayerSelection.png","Layer selection",12} \section org_mitk_views_segmentationlabels Segmentation labels For each layer, one or more labels can be added. Pressing the double arrow on the right, all created labels are shown in the 'Label Table'. The following label properties are available:
  • Name:
  • the name of the label. Can be a predefined one or any other.
  • Locked:
  • whether the label is locked or editable. A locked label cannot be overwritten by another.
  • Color:
  • the color of the label.
  • Visible:
  • whether the label is currently visible or hidden.
\imageMacro{"QmitkSegmentation_LabelTable.png","The 'Label Table' shows all labels in the current segmentation session",12} The 'New Label' button can be used to add a new label. This will automatically add a new label with a distinct name and color to the list of available labels.\n In the current implementation of the plugin, the maximum number of labels is restricted to 255. If you need more, you will have to create a new segmentation session. \subsection org_mitk_views_segmentationlabelsuggestions Label name and color suggestions When renaming labels or creating new labels with enforced manual naming in the Segmentation preferences, entering names is supported by auto-completion for common label names. The list of predefined label names and colors for the auto-completion feature can be either extented or replaced by a custom list of label name and color suggestions. This custom list must be specified as a JSON file, just containing an array of objects, each with a mandatory "name" string and an optional "color" string. The JSON file can be set in the Segmentation preferences as well as a few options on how to apply these suggestions. \subsection org_mitk_views_segmentationlabelpresets Saving and loading label set presets Label set presets are useful to share a certain style or scheme between different segmentation sessions or to provide templates for new segmentation sessions. The properties of all labels in all layers like their names, colors, and visibilities are saved as a label set preset by clicking on the 'Save label set preset' button. Label set presets are applied to any segmentation session by clicking on the 'Load label set preset' button. If a label for a certain value already exists, its properties are overridden by the preset. If a label for a certain value does not yet exist, an empty label with the label properties of the preset is created. The actual segmentations of labels are unaffected as label set presets only store label properties. \subsubsection org_mitk_views_segmentationdefaultlabelpresets Applying label set presets by default If you work on a repetetive segmentation task, manually loading the same label set preset for each and every new segmentation can be tedious. To streamline your workflow, you can set a default label set preset in the Segmentation preferences (Ctrl+P). When set, this label set preset will be applied to all new segmentations instead of creating the default red "New label 1" label. \subsection org_mitk_views_segmentationlabelsearch Searching for a label It may happen that many labels (e.g. > 200) are present in a segmentation session and therefore manual searching can be time-consuming. The 'Label Search' edit box allows for quickly finding a label by providing assistance for label name completion. If the label is found, it will become the active one after pressing 'enter'. To start editing a label needs to be activated by clicking on the corresponding row in the 'Label Table'. Only one label can be active at the time. Then the segmentation tools in the toolbox can be used for mask generation. \subsection org_mitk_views_multilabelsegmentationoperationsonlabels Operations on labels Depending on the selection in the 'Label Table', several actions are offered: \subsubsection org_mitk_views_segmentationoperationssingleselection Operations with single label selection Right-clicking on any label opens a pop-up menu that offers the following actions to be performed on the selected label:
  • Rename...
  • : change the name and / or color of the selected label.
  • Remove...
  • : delete the selected label.
  • Erase...
  • : only clear the contents of the selected label.
  • Merge...
  • : merge two labels by selecting a second label.
  • Random color
  • : assign a random color to the label.
  • View only
  • : make all labels except the current selected label invisible.
  • View/Hide all
  • : make all labels visible / invisible
  • Lock/Unlock all
  • : lock or unlock all labels.
  • Create surface
  • : generate a surface out of the selected label.
  • Create mask
  • : generate a mask out of the selected label. A mask is a binary image with "1" inside and "0" outside.
  • Create cropped mask
  • : generate a binary mask out of the selected label. Crop changes the extent of the resulting image to the extent of the label.
\imageMacro{"QmitkSegmentation_OperationsSingleSelection.png","Context menu for single label selection",12} \subsubsection org_mitk_views_segmentationoperationsmultiselection Operations with multiple label selection Shift-clickink on multiple labels allows to select more than one label. If more than one label is selected, different options will appear in the menu:
  • Merge selection on current label
  • : transfer the contents of the selected labels in the 'Label Table' into the current one.
  • Remove selected labels
  • : delete the selected labels.
  • Erase selected labels
  • : only clear the contents of the selected labels.
\imageMacro{"QmitkSegmentation_OperationsMultiSelection.png","Context menu for multiple label selection",12} \section org_mitk_views_segmentationtooloverview Segmentation tool overview MITK offers a comprehensive set of slice-based 2D and (semi-)automated 3D segmentation tools. The manual 2D tools require some user interaction and can only be applied to a single image slice whereas the 3D tools operate on the whole image. The 3D tools usually only require a small amount of user interaction, i.e. placing seed points or setting / adjusting parameters. You can switch between the different toolsets by selecting the 2D or 3D tab in the segmentation view. \imageMacro{QmitkSegmentation_ToolOverview.png,"An overview of the existing 2D and 3D tools in MITK.",5.50} \section org_mitk_views_segmentation2dsegmentation 2D segmentation tools With 2D manual contouring you define which voxels are part of the segmentation and which are not. This allows you to create segmentations of any structures of interest in an image. You can also use manual contouring to correct segmentations that result from sub-optimal automatic methods. The drawback of manual contouring is that you might need to define contours on many 2D slices. However, this is mitigated by the interpolation feature, which will make suggestions for a segmentation. To start using one of the editing tools, click its button from the displayed toolbox. The selected editing tool will be active and its corresponding button will stay pressed until you click the button again. Selecting a different tool also deactivates the previous one.\n If you have to delineate a lot of images, shortcuts to switch between tools becomes convenient. For that, just hit the first letter of each tool to activate it (A for Add, S for Subtract, etc.). All of the editing tools work by the same principle: using the mouse (left button) to click anywhere in a 2D window (any of the orientations axial, sagittal, or coronal), moving the mouse while holding the mouse button and releasing the button to finish the editing action. Multi-step undo and redo is fully supported by all editing tools by using the application-wide undo / redo buttons in the toolbar. Remark: Clicking and moving the mouse in any of the 2D render windows will move the crosshair that defines what part of the image is displayed. This behavior is disabled as long as any of the manual segmentation tools are active - otherwise you might have a hard time concentrating on the contour you are drawing. \subsection org_mitk_views_segmentationaddsubtracttools Add and subtract tools \imageMacro{QmitkSegmentation_IMGIconAddSubtract.png,"Add and subtract tools",7.70} Use the left mouse button to draw a closed contour. When releasing the mouse button, the contour will be added (Add tool) to or removed (Subtract tool) from the current segmentation. Adding and subtracting voxels can be iteratively repeated for the same segmentation. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of adding voxels, they will be subtracted). \subsection org_mitk_views_segmentationlassotool Lasso tool \imageMacro{QmitkSegmentation_Lasso.png,"Lasso tool",7.70} The tool is a more advanced version of the add/substract tool. It offers you the following features:
  1. Generating a polygon segmentation (click left mouse button to set ancor point)
  2. Freehand contouring (like the add tool; press left mouse button while moving the mouse)
  3. Move ancor points (select an ancor point, press left mouse button while moving the mouse)
  4. Add new ancor points (press CTRL while click left mouse to add an ancor to the contour)
  5. Delete an ancor point (press Del while ancor point is selected)
  6. Segmentation can be added to the label (Add mode) or substracted (Substract mode)
To start a segmentation double left click where the first ancor point should be. To end the segmentation double left click where the last ancor point should be. Please note that:
  • feature 3-6 are only available, if auto confirm is *not* activated
  • feature 3-5 is not available for freehand contour segments
\subsection org_mitk_views_segmentationpaintwipetools Paint and wipe tools \imageMacro{QmitkSegmentation_IMGIconPaintWipe.png,"Paint and wipe tools",7.68} Use the Size slider to change the radius of the round paintbrush tool. Move the mouse in any 2D window and press the left button to draw or erase pixels. Holding CTRL / CMD while drawing will invert the current tool's behavior (i.e. instead of painting voxels, they will be wiped). \subsection org_mitk_views_segmentationregiongrowingtool Region growing tool \imageMacro{QmitkSegmentation_IMGIconRegionGrowing.png,"Region growing tool",3.81} Click at one point in a 2D slice widget to add an image region to the segmentation with the region growing tool. Region Growing selects all pixels around the mouse cursor that have a similar gray value as the pixel below the mouse cursor. This allows to quickly create segmentations of structures that have a good contrast to surrounding tissue. The tool operates based on the current level window, so changing the level window to optimize the contrast for the ROI is encouraged. Moving the mouse up / down is different from left / right: Moving up the cursor while holding the left mouse button widens the range for the included grey values; moving it down narrows it. Moving the mouse left and right will shift the range. The tool will select more or less pixels, corresponding to the changing gray value range. \if THISISNOTIMPLEMENTEDATTHEMOMENT A common issue with region growing is the so called "leakage" which happens when the structure of interest is connected to other pixels, of similar gray values, through a narrow "bridge" at the border of the structure. The Region Growing tool comes with a "leakage detection/removal" feature. If leakage happens, you can left-click into the leakage region and the tool will try to automatically remove this region (see illustration below). \imageMacro{QmitkSegmentation_Leakage.png,"Leakage correction feature of the region growing tool",11.28} \endif \subsection org_mitk_views_segmentationfilltool Fill tool \imageMacro{QmitkSegmentation_IMGIconFill.png,"Fill tool",3.81} -Left-click inside a segmentation with holes to completely fill all holes. Left-click inside a hole to fill only this specific hole. +Left-click inside a region/segmentation to flood fill all connected pixels that have the same label with the active label. This tool will only work on regions of unlocked labels or on regions that are not labeled at all. \subsection org_mitk_views_segmentationerasetool Erase tool - \imageMacro{QmitkSegmentation_IMGIconErase.png,"Erase tool",3.79} This tool removes a connected part of pixels that form a segmentation. -You may use it to remove single segmentations (left-click on specific segmentation) or to clear a whole slice at once (left-click outside a segmentation). +You may use it to remove single segmented regions (left-click on specific segmentation) or to clear a whole slice at once (left-click at the non-labeled background). +This tool will only work and regions of unlocked labels or on regions of the active label. + +\subsection org_mitk_views_segmentationclosetool Close tool +\imageMacro{QmitkSegmentation_IMGIconClose.png,"Close tool",3.79} + +Left-click inside the region/segmentation to fill all "holes" (pixels labelled with another label or no label) inside the region. +Therefore this tool behaves like a local closing operation. This tool will not work, when a non-labeled region is selected and holes of locked labels will not be filled. +\remark This tool always uses the label of the selected region (even if this label is not the active label). Therefore you can use this tool on regions of the active label and of none locked labels (without the need to change the active label). \subsection org_mitk_views_segmentationlivewiretool Live wire tool \imageMacro{QmitkSegmentation_IMGIconLiveWire.png,"Live wire tool",3.01} The Live Wire Tool acts as a magnetic lasso with a contour snapping to edges of objects. \imageMacro{QmitkSegmentation_IMGLiveWireUsage.PNG,"Steps for using the Live Wire Tool",16.00} The tool handling is the same like the Lasso tool (see for more info), except it generates live wire contours instead of straight lines. \subsection org_mitk_views_segmentationinterpolation 2D and 3D Interpolation Creating segmentations using 2D manual contouring for large image volumes may be very time-consuming, because structures of interest may cover a large range of slices. Note: Interpolation is currently disabled for segmentations containing more than one label. The segmentation view offers two helpful features to mitigate this drawback:
  • 2D Interpolation
  • 3D Interpolation
The 2D Interpolation creates suggestions for a segmentation whenever you have a slice that
  • has got neighboring slices with segmentations (these do not need to be direct neighbors but could also be a couple of slices away) AND
  • is completely clear of a manual segmentation, i.e. there will be no suggestion if there is even only a single pixel of segmentation in the current slice.
\imageMacro{QmitkSegmentation_2DInterpolation.png,"2D interpolation usage",3.01} Interpolated suggestions are displayed as outlines, until you confirm them as part of the segmentation. To confirm single slices, click the Confirm for single slice button below the toolbox. You may also review the interpolations visually and then accept all of them at once by selecting Confirm for all slices. The 3D interpolation creates suggestions for 3D segmentations. That means if you start contouring, from the second contour onwards, the surface of the segmented area will be interpolated based on the given contour information. The interpolation works with all available manual tools. Please note that this is currently a pure mathematical interpolation, i.e. image intensity information is not taken into account. With each further contour the interpolation result will be improved, but the more contours you provide the longer the recalculation will take. To achieve an optimal interpolation result and in this way a most accurate segmentation you should try to describe the surface with sparse contours by segmenting in arbitrary oriented planes. The 3D interpolation is not meant to be used for parallel slice-wise segmentation, but rather segmentations in i.e. the axial, coronal and sagittal plane. \imageMacro{QmitkSegmentation_3DInterpolationWrongRight.png,"3D interpolation usage",16.00} You can accept the interpolation result by clicking the Confirm-button below the tool buttons. In this case the 3D interpolation will be deactivated automatically so that the result can be post-processed without any interpolation running in the background. Additional to the surface, black contours are shown in the 3D render window, which mark all the drawn contours used for the interpolation. You can navigate between the drawn contours by clicking on the corresponding position nodes in the data manager which are stored as sub-nodes of the selected segmentation. If you do not want to see these nodes just uncheck the Show Position Nodes checkbox and these nodes will be hidden. If you want to delete a drawn contour we recommend to use the Erase-Tool since undo / redo is not yet working for 3D interpolation. The current state of the 3D interpolation can be saved across application restart. For that, just click on save project during the interpolation is active. After restarting the application and load your project you can click on "Reinit Interpolation" within the 3D interpolation GUI area. \section org_mitk_views_segmentation3dsegmentation 3D segmentation tools The 3D tools operate on the whole image and require usually a small amount of interaction like placing seed-points or specifying certain parameters. All 3D tools provide an immediate segmentation feedback, which is displayed as a transparent green overlay. For accepting a preview you have to press the Confirm button of the selected tool. The following 3D tools are available: \subsection org_mitk_views_segmentation3dthresholdtool 3D Threshold tool The thresholding tool simply applies a 3D threshold to the patient image. All pixels with values equal or above the selected threshold are labeled as part of the segmentation. You can change the threshold by either moving the slider of setting a certain value in the spinbox. \imageMacro{QmitkSegmentation_3DThresholdTool.png,"3D Threshold tool",10.00} \subsection org_mitk_views_segmentation3dulthresholdTool 3D upper / lower threshold tool The Upper/Lower Thresholding tool works similar to the simple 3D threshold tool but allows you to define an upper and lower threshold. All pixels with values within this threshold interval will be labeled as part of the segmentation. \imageMacro{QmitkSegmentation_3DULThresholdTool.png,"3D upper / lower threshold tool",10.00} \subsection org_mitk_views_segmentation3dotsutool 3D Otsu tool The 3D Otsu tool provides a more sophisticated thresholding algorithm. It allows you to define a number of regions. Based on the image histogram the pixels will then be divided into different regions. The more regions you define the longer the calculation will take. You can select afterwards which of these regions you want to confirm as segmentation. - \imageMacro{QmitkSegmentation_3DOtsuTool.png,"3D Otsu tool",10.00} +\subsection org_mitk_views_segmentation3dgrowcuttool 3D GrowCut tool + +The 3D GrowCut tool uses previously created segmentation labels (e.g. by the "Add"-tool) stored in the segmentation layer 0. +The GrowCut tool will use these segmentation labels to create a seedimage that will serve as input to the algorithm. +As an advanced setting option, a Distance penalty can be set, which increases the cohesion in the immediate surroundings of the initial labels. +Based on the seedimage and the Distance penalty, a growing is started, which includes all areas that are not initially assigned to a specific label. +During this process, initially unassigned areas are assigned to the best fitting labels. +After the segmentation process, the user can decide which newly generated labels should be confirmed. + +\imageMacro{QmitkSegmentation_3DGrowCutTool.png,"3D GrowCut tool",16.00} + \subsection org_mitk_views_segmentation3drgtool 3D Region growing tool The 3D Region Growing tool works similar to the 2D pendant. At the beginning you have to place a seedpoint and define a threshold interval. If you press Run Segmentation a preview is calculated. By moving the Adapt region growing slider you can interactively adapt the segmentation result. \imageMacro{QmitkSegmentation_3DRGTool.png,"3D Region growing tool",10.00} \subsection org_mitk_views_segmentationpickingtool Picking Tool The Picking tool offers two modes that allow you to manipulate "islands" within your segmentation. This is especially useful if e.g. a thresholding provided you with several areas within your image but you are just interested in one special region. - Picking mode: Allows you to select certain "islands". When the pick is confirmed, the complete content of the active label will be removed except the pick. This mode is beneficial if you have a lot segmentation noise and want to pick the relevant parts and dismiss the rest. Hint: You can also pick from other labels, but this will only work if these labels are unlocked. - Relabel mode: Allows you to select certain "islands". When the pick is confirmed, it will be relabeled and added to the active label content. Hint: This mode ignores the locks of other labels, hence you do not need to unlock them explicitly. \imageMacro{QmitkSegmentation_PickingTool.png,"Picking tool",10.00} \subsection org_mitk_views_segmentationnnUNetTool nnU-Net Tool (Ubuntu only) \imageMacro{QmitkSegmentation_nnUnetTool.png,"nnUNet tool",10.00} This tool provides a GUI to the deep learning-based segmentation algorithm called the nnUNet. With this tool, you can get a segmentation mask predicted for the loaded image in MITK. Be ready with the pre-trained weights (a.k.a RESULTS_FOLDER) for your organ or task concerned, before using the tool. For a detailed explanation of the parameters and pre-trained weights folder structure etc., please refer to https://github.com/MIC-DKFZ/nnUNet.
Remark: The tool assumes that you have a Python3 environment with nnUNet (pip) installed. Your machine should be also equipped with a CUDA enabled GPU. \subsubsection org_mitk_views_segmentationnnUNetToolWorkflow Workflow: -# Select the "Python Path" drop-down to see if MITK has automatically detected other Python environments. Click on a fitting environment for the nnUNet inference or click "Select" in the dropdown to choose an unlisted python environment. Note that, while selecting an arbitrary environment folder, only select the base folder, e.g. "myenv". No need to select all the way until "../myenv/bin/python", for example. -# Click on the "nnUNet Results Folder" directory icon to navigate to the results folder on your hard disk. This is equivalent to setting the RESULTS_FOLDER environment variable. If your results folder is as per the nnUNet required folder structure, the configuration, trainers, tasks and folds are automatically parsed and correspondingly loaded in the drop-down boxes as shown below. Note that MITK automatically checks for the RESULTS_FOLDER environment variable value and, if found, auto parses that directory when the tool is started. \imageMacro{QmitkSegmentation_nnUNet_Settings.png,"nnUNet Segmentation Settings",10} -# Choose your required Task-Configuration-Trainer-Planner-Fold parameters, sequentially. By default, all entries are selected inside the "Fold" dropdown (shown: "All"). Note that, even if you uncheck all entries from the "Fold" dropdown (shown: "None"), then too, all folds would be considered for inferencing. -# For ensemble predictions, you will get the option to select parameters irrespective on postprocessing files available in the ensembles folder of RESULTS_FOLDER. Note that, if a postprocessing json file exists for the selected combination then it will used for ensembling, by default. To choose not to, uncheck the "Use PostProcessing JSON" in the "Advanced" section. \imageMacro{QmitkSegmentation_nnUNet_ensemble.png,"nnUNet Segmentation Settings",10} -# If your task is trained with multi-modal inputs, then "Multi-Modal" checkbox is checked and the no.of modalities are preloaded and shown next to "Required Modalities". Instantly, as much node selectors with corresponding modality names should appear below to select the Data Manager along including a selector with preselected with the reference node. Now, select the image nodes in the node selectors accordingly for accurate inferencing. \imageMacro{QmitkSegmentation_nnUNet_multimodal.png,"nnUNet Multi Modal Settings",10.00} -# Click on "Preview". -# In the "Advanced" section, you can also activate other options like "Mixed Precision" and "Enable Mirroring" (for test time data augmentation) pertaining to nnUNet. \imageMacro{QmitkSegmentation_nnUNet_Advanced.png,"nnUNet Advanced Settings",10.00} -# Use "Advanced" > "GPU Id" combobox to change the preferred GPU for inferencing. This is internally equivalent to setting the CUDA_VISIBLE_DEVICES environment variable. -# Every inferred segmentation is cached to prevent a redundant computation. In case, a user doesn't wish to cache a Preview, uncheck the "Enable Caching" in the "Advanced" section. This will ensure that the current parameters will neither be checked against the existing cache nor a segmentation be loaded from it when Preview is clicked. -# You may always clear all the cached segmentations by clicking "Clear Cache" button. \subsubsection org_mitk_views_segmentationnnUNetToolMisc Miscellaneous: -# In case you want to reload/reparse the folders in the "nnUNet Results Folder", eg. after adding new tasks into it, you may do so without reselecting the folder again by clicking the "Refresh Results Folder" button. -# The "Advanced" > "GPU Id" combobox lists the Nvidia GPUs available by parsing the nvidia-smi utility output. In case your machine has Nvidia CUDA enabled GPUs but the nvidia-smi fails for some reason, the "GPU Id" combobox will show no entries. In such a situation, it's still possible to execute inferencing by manually entering the preferred GPU Id, eg. 0 in the combobox. -# The "Advanced" > "Available Models" lists the available pre-trained tasks for download. Make sure you have internet connection. Then, choose a Task from the dropdown and click the Download button. The pre-trained models for the selected Task will be downloaded and placed to the RESULTS_FOLDER directory automatically. -# In the RESULTS_FOLDER directory, inside the trainer-planner folder of every task, MITK keeps a "mitk_export.json" file for fast loading for multi-modal information. It is recommended not to delete this file(s) for a fast responsive UI. Tip: If multi-modal information shown on MITK is not correct for a given task, you may modify this JSON file and try again. \section org_mitk_views_segmentationpostprocessing Additional things you can do with segmentations Segmentations are never an end in themselves. Consequently, the segmentation view adds a couple of "post-processing" actions, accessible through the context-menu of the data manager. \imageMacro{QmitkSegmentation_IMGDataManagerContextMenu.png,"Context menu items for segmentations",10.58}
  • Create polygon %model applies the marching cubes algorithm to the segmentation. This polygon %model can be used for visualization in 3D or other applications such as stereolithography (3D printing).
  • Create smoothed polygon %model uses smoothing in addition to the marching cubes algorithm, which creates models that do not follow the exact outlines of the segmentation, but look smoother.
  • Autocrop can save memory. Manual segmentations have the same extent as the patient image, even if the segmentation comprises only a small sub-volume. This invisible and meaningless margin is removed by autocropping.
\section org_mitk_views_segmentationof3dtimages Segmentation of 3D+t images For segmentation of 3D+t images, some tools give you the option to choose between creating dynamic and static masks.
  • Dynamic masks can be created on each time frame individually.
  • Static masks will be defined on one time frame and will be the same for all other time frames.
In general, segmentation is applied on the time frame that is selected when execution is performed. If you alter the time frame, the segmentation preview is adapted. \section org_mitk_views_segmentationtechnicaldetail Technical information for developers For technical specifications see \subpage QmitkSegmentationTechnicalPage and for information on the extensions of the tools system \subpage toolextensions. */ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_3DGrowCutTool.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_3DGrowCutTool.png new file mode 100644 index 0000000000..2100b942d7 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_3DGrowCutTool.png differ diff --git a/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_IMGIconClose.png b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_IMGIconClose.png new file mode 100644 index 0000000000..956c2a0c69 Binary files /dev/null and b/Plugins/org.mitk.gui.qt.segmentation/documentation/UserManual/QmitkSegmentation_IMGIconClose.png differ 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 de42402f43..bd093a5ff8 100644 --- a/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp +++ b/Plugins/org.mitk.gui.qt.segmentation/src/internal/QmitkSegmentationView.cpp @@ -1,1122 +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 "QmitkSegmentationView.h" #include "mitkPluginActivator.h" // blueberry #include // mitk #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // Qmitk #include #include #include // us #include #include // Qt #include #include #include // vtk #include #include 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_RendererAnnotation(mitk::LogoAnnotation::New()) , m_DrawOutline(true) , m_SelectionMode(false) , m_MouseCursorSet(false) , m_DefaultLabelNaming(true) { 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); auto isBinary = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); auto isMask = mitk::NodePredicateAnd::New(isBinary, isImage); auto validSegmentations = mitk::NodePredicateOr::New(); validSegmentations->AddPredicate(mitk::TNodePredicateDataType::New()); validSegmentations->AddPredicate(isMask); m_SegmentationPredicate = mitk::NodePredicateAnd::New(); m_SegmentationPredicate->AddPredicate(validSegmentations); 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"))); // setup annotation for an overlay image to indicated a blocked renderer m_RendererAnnotation->SetOpacity(0.5); m_RendererAnnotation->SetRelativeSize(0.25); m_RendererAnnotation->SetCornerPosition(4); QImage* qimage = new QImage(qPrintable(":/Qmitk/Cancel_48x48.png")); vtkSmartPointer qImageToVtk; qImageToVtk = vtkSmartPointer::New(); qImageToVtk->SetQImage(qimage); qImageToVtk->Update(); vtkSmartPointer vtkLogo = qImageToVtk->GetOutput(); m_RendererAnnotation->SetLogoImage(vtkLogo); m_SegmentationInteractor = mitk::SegmentationInteractor::New(); // add observer for the 'SegmentationInteractionEvent' itk::ReceptorMemberCommand::Pointer geometryNotAlignedCommand = itk::ReceptorMemberCommand::New(); geometryNotAlignedCommand->SetCallbackFunction(this, &QmitkSegmentationView::ValidateRendererGeometry); m_SegmentationInteractor->AddObserver(mitk::SegmentationInteractionEvent(nullptr, true), geometryNotAlignedCommand); m_SegmentationInteractor->Disable(); } QmitkSegmentationView::~QmitkSegmentationView() { if (nullptr != m_Controls) { OnLooseLabelSetConnection(); // deactivate all tools m_ToolManager->ActivateTool(-1); // removing all observers from working data for (NodeTagMapType::iterator dataIter = m_WorkingDataObserverTags.begin(); dataIter != m_WorkingDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_WorkingDataObserverTags.clear(); // removing all observers from reference data for (NodeTagMapType::iterator dataIter = m_ReferenceDataObserverTags.begin(); dataIter != m_ReferenceDataObserverTags.end(); ++dataIter) { (*dataIter).first->GetProperty("visible")->RemoveObserver((*dataIter).second); } m_ReferenceDataObserverTags.clear(); mitk::RenderingManager::GetInstance()->RemoveObserver(m_RenderingManagerObserverTag); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); service->RemoveAllPlanePositions(); context->ungetService(ppmRef); m_ToolManager->SetReferenceData(nullptr); m_ToolManager->SetWorkingData(nullptr); } m_ToolManager->ActiveToolChanged -= mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); delete m_Controls; } /**********************************************************************/ /* private Q_SLOTS */ /**********************************************************************/ void QmitkSegmentationView::OnReferenceSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); // Remove observer if one was registered to the reference node auto finding = m_ReferenceDataObserverTags.find(m_ReferenceNode); if (finding != m_ReferenceDataObserverTags.end()) { m_ReferenceNode->GetProperty("visible")->RemoveObserver(m_ReferenceDataObserverTags[m_ReferenceNode]); m_ReferenceDataObserverTags.erase(m_ReferenceNode); } if (nodes.empty()) { m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_ReferenceNode = nullptr; m_ToolManager->SetReferenceData(m_ReferenceNode); this->UpdateGUI(); return; } m_ReferenceNode = nodes.first(); m_ToolManager->SetReferenceData(m_ReferenceNode); if (m_ReferenceNode.IsNotNull()) { // set a predicate such that a segmentation fits the selected reference image geometry auto segPredicate = mitk::NodePredicateAnd::New(m_SegmentationPredicate.GetPointer(), mitk::NodePredicateSubGeometry::New(m_ReferenceNode->GetData()->GetGeometry())); m_Controls->workingNodeSelector->SetNodePredicate(segPredicate); if (m_SelectionMode) { // hide all image nodes to later show only the automatically selected ones mitk::DataStorage::SetOfObjects::ConstPointer imageNodes = this->GetDataStorage()->GetSubset(m_ReferencePredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = imageNodes->begin(); iter != imageNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_ReferenceNode->SetVisibility(true); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_ReferenceDataObserverTags[m_ReferenceNode] = m_ReferenceNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); } this->UpdateGUI(); } void QmitkSegmentationView::OnSegmentationSelectionChanged(QList nodes) { m_ToolManager->ActivateTool(-1); // Remove observer if one was registered auto finding = m_WorkingDataObserverTags.find(m_WorkingNode); if (finding != m_WorkingDataObserverTags.end()) { m_WorkingNode->GetProperty("visible")->RemoveObserver(m_WorkingDataObserverTags[m_WorkingNode]); m_WorkingDataObserverTags.erase(m_WorkingNode); } if (nodes.empty()) { m_WorkingNode = nullptr; m_ToolManager->SetWorkingData(m_WorkingNode); this->UpdateGUI(); return; } if (m_ReferenceNode.IsNull()) { this->UpdateGUI(); return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { this->UpdateGUI(); return; } m_WorkingNode = nodes.first(); m_ToolManager->SetWorkingData(m_WorkingNode); if (m_WorkingNode.IsNotNull()) { if (m_SelectionMode) { // hide all segmentation nodes to later show only the selected ones mitk::DataStorage::SetOfObjects::ConstPointer segmentationNodes = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = segmentationNodes->begin(); iter != segmentationNodes->end(); ++iter) { (*iter)->SetVisibility(false); } } m_WorkingNode->SetVisibility(true); this->OnEstablishLabelSetConnection(); m_Controls->labelSetWidget->ResetAllTableWidgetItems(); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_WorkingDataObserverTags[m_WorkingNode] = m_WorkingNode->GetProperty("visible")->AddObserver(itk::ModifiedEvent(), command); this->InitializeRenderWindows(referenceImage->GetTimeGeometry(), mitk::RenderingManager::REQUEST_UPDATE_ALL, false); } this->UpdateGUI(); } void QmitkSegmentationView::OnVisibilityShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } bool isVisible = false; m_WorkingNode->GetBoolProperty("visible", isVisible); m_WorkingNode->SetVisibility(!isVisible); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnLabelToggleShortcutActivated() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } this->WaitCursorOn(); workingImage->GetActiveLabelSet()->SetNextActiveLabel(); workingImage->Modified(); this->WaitCursorOff(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::OnNewSegmentation() { m_ToolManager->ActivateTool(-1); if (m_ReferenceNode.IsNull()) { MITK_ERROR << "'Create new segmentation' button should never be clickable unless a reference image is selected."; return; } mitk::Image::ConstPointer referenceImage = dynamic_cast(m_ReferenceNode->GetData()); if (referenceImage.IsNull()) { QMessageBox::information( m_Parent, "New segmentation", "Please load and select an image before starting some action."); return; } if (referenceImage->GetDimension() <= 1) { QMessageBox::information( m_Parent, "New segmentation", "Segmentation is currently not supported for 2D images"); return; } auto segTemplateImage = referenceImage; if (referenceImage->GetDimension() > 3) { QmitkStaticDynamicSegmentationDialog dialog(m_Parent); dialog.SetReferenceImage(referenceImage.GetPointer()); dialog.exec(); segTemplateImage = dialog.GetSegmentationTemplate(); } mitk::DataNode::Pointer newSegmentationNode; try { this->WaitCursorOn(); newSegmentationNode = mitk::LabelSetImageHelper::CreateNewSegmentationNode(m_ReferenceNode, segTemplateImage); this->WaitCursorOff(); } catch (mitk::Exception& e) { this->WaitCursorOff(); MITK_ERROR << "Exception caught: " << e.GetDescription(); QMessageBox::warning(m_Parent, "New segmentation", "Could not create a new segmentation."); return; } auto newLabelSetImage = dynamic_cast(newSegmentationNode->GetData()); if (nullptr == newLabelSetImage) { // something went wrong return; } const auto labelSetPreset = this->GetDefaultLabelSetPreset(); if (labelSetPreset.empty() || !mitk::LabelSetIOHelper::LoadLabelSetImagePreset(labelSetPreset, newLabelSetImage)) { auto newLabel = mitk::LabelSetImageHelper::CreateNewLabel(newLabelSetImage); if (!m_DefaultLabelNaming) { QmitkNewSegmentationDialog dialog(m_Parent); dialog.SetName(QString::fromStdString(newLabel->GetName())); dialog.SetColor(newLabel->GetColor()); if (QDialog::Rejected == dialog.exec()) return; auto name = dialog.GetName(); if (!name.isEmpty()) newLabel->SetName(name.toStdString()); newLabel->SetColor(dialog.GetColor()); } newLabelSetImage->GetActiveLabelSet()->AddLabel(newLabel); } if (!this->GetDataStorage()->Exists(newSegmentationNode)) { this->GetDataStorage()->Add(newSegmentationNode, m_ReferenceNode); } if (m_ToolManager->GetWorkingData(0)) { m_ToolManager->GetWorkingData(0)->SetSelected(false); } newSegmentationNode->SetSelected(true); m_Controls->workingNodeSelector->SetCurrentSelectedNode(newSegmentationNode); } std::string QmitkSegmentationView::GetDefaultLabelSetPreset() const { auto labelSetPreset = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABELSET_PRESET.toStdString(), ""); if (labelSetPreset.empty()) labelSetPreset = m_LabelSetPresetPreference.toStdString(); return labelSetPreset; } void QmitkSegmentationView::OnManualTool2DSelected(int id) { this->ResetMouseCursor(); mitk::StatusBar::GetInstance()->DisplayText(""); if (id >= 0) { std::string text = "Active Tool: \""; text += m_ToolManager->GetToolById(id)->GetName(); text += "\""; mitk::StatusBar::GetInstance()->DisplayText(text.c_str()); us::ModuleResource resource = m_ToolManager->GetToolById(id)->GetCursorIconResource(); this->SetMouseCursor(resource, 0, 0); } } void QmitkSegmentationView::OnShowMarkerNodes(bool state) { mitk::SegTool2D::Pointer manualSegmentationTool; unsigned int numberOfExistingTools = m_ToolManager->GetTools().size(); for (unsigned int i = 0; i < numberOfExistingTools; i++) { manualSegmentationTool = dynamic_cast(m_ToolManager->GetToolById(i)); if (nullptr == manualSegmentationTool) { continue; } manualSegmentationTool->SetShowMarkerNodes(state); } } void QmitkSegmentationView::OnLayersChanged() { this->OnEstablishLabelSetConnection(); m_Controls->labelSetWidget->ResetAllTableWidgetItems(); } void QmitkSegmentationView::OnShowLabelTable(bool value) { m_Controls->labelSetWidget->setVisible(value); } void QmitkSegmentationView::OnGoToLabel(const mitk::Point3D& pos) { if (m_RenderWindowPart) { m_RenderWindowPart->SetSelectedPosition(pos); } } void QmitkSegmentationView::OnLabelSetWidgetReset() { this->ValidateSelectionInput(); } /**********************************************************************/ /* private */ /**********************************************************************/ void QmitkSegmentationView::CreateQtPartControl(QWidget* parent) { m_Parent = parent; m_Controls = new Ui::QmitkSegmentationViewControls; m_Controls->setupUi(parent); // *------------------------ // * SHORTCUTS // *------------------------ QShortcut* visibilityShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_H), parent); connect(visibilityShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnVisibilityShortcutActivated); QShortcut* labelToggleShortcut = new QShortcut(QKeySequence(Qt::CTRL | Qt::Key::Key_L, Qt::CTRL | Qt::Key::Key_I), parent); connect(labelToggleShortcut, &QShortcut::activated, this, &QmitkSegmentationView::OnLabelToggleShortcutActivated); // *------------------------ // * DATA SELECTION WIDGETS // *------------------------ m_Controls->referenceNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->referenceNodeSelector->SetNodePredicate(m_ReferencePredicate); m_Controls->referenceNodeSelector->SetInvalidInfo("Select an image"); m_Controls->referenceNodeSelector->SetPopUpTitel("Select an image"); m_Controls->referenceNodeSelector->SetPopUpHint("Select an image that should be used to define the geometry and bounds of the segmentation."); m_Controls->workingNodeSelector->SetDataStorage(GetDataStorage()); m_Controls->workingNodeSelector->SetNodePredicate(m_SegmentationPredicate); m_Controls->workingNodeSelector->SetInvalidInfo("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpTitel("Select a segmentation"); m_Controls->workingNodeSelector->SetPopUpHint("Select a segmentation that should be modified. Only segmentation with the same geometry and within the bounds of the reference image are selected."); connect(m_Controls->referenceNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnReferenceSelectionChanged); connect(m_Controls->workingNodeSelector, &QmitkAbstractNodeSelectionWidget::CurrentSelectionChanged, this, &QmitkSegmentationView::OnSegmentationSelectionChanged); // *------------------------ // * TOOLMANAGER // *------------------------ m_ToolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager(); m_ToolManager->SetDataStorage(*(this->GetDataStorage())); m_ToolManager->InitializeTools(); // react if the active tool changed m_ToolManager->ActiveToolChanged += mitk::MessageDelegate(this, &QmitkSegmentationView::ActiveToolChanged); - QString segTools2D = tr("Add Subtract Lasso Fill Erase Paint Wipe 'Region Growing' 'Live Wire'"); - QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking"); + QString segTools2D = tr("Add Subtract Lasso Fill Erase Close Paint Wipe 'Region Growing' 'Live Wire'"); + QString segTools3D = tr("Threshold 'UL Threshold' Otsu 'Region Growing 3D' Picking GrowCut"); + #ifdef __linux__ segTools3D.append(" nnUNet"); // plugin not enabled for MacOS / Windows #endif std::regex extSegTool2DRegEx("SegTool2D$"); std::regex extSegTool3DRegEx("SegTool3D$"); auto tools = m_ToolManager->GetTools(); for (const auto &tool : tools) { if (std::regex_search(tool->GetNameOfClass(), extSegTool2DRegEx)) { segTools2D.append(QString(" '%1'").arg(tool->GetName())); } else if (std::regex_search(tool->GetNameOfClass(), extSegTool3DRegEx)) { segTools3D.append(QString(" '%1'").arg(tool->GetName())); } } // setup 2D tools m_Controls->toolSelectionBox2D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox2D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox2D->SetToolGUIArea(m_Controls->toolGUIArea2D); m_Controls->toolSelectionBox2D->SetDisplayedToolGroups(segTools2D.toStdString()); m_Controls->toolSelectionBox2D->SetLayoutColumns(3); m_Controls->toolSelectionBox2D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); connect(m_Controls->toolSelectionBox2D, &QmitkToolSelectionBox::ToolSelected, this, &QmitkSegmentationView::OnManualTool2DSelected); // setup 3D Tools m_Controls->toolSelectionBox3D->SetToolManager(*m_ToolManager); m_Controls->toolSelectionBox3D->SetGenerateAccelerators(true); m_Controls->toolSelectionBox3D->SetToolGUIArea(m_Controls->toolGUIArea3D); m_Controls->toolSelectionBox3D->SetDisplayedToolGroups(segTools3D.toStdString()); m_Controls->toolSelectionBox3D->SetLayoutColumns(3); m_Controls->toolSelectionBox3D->SetEnabledMode( QmitkToolSelectionBox::EnabledWithReferenceAndWorkingDataVisible); m_Controls->slicesInterpolator->SetDataStorage(this->GetDataStorage()); // create general signal / slot connections connect(m_Controls->newSegmentationButton, &QToolButton::clicked, this, &QmitkSegmentationView::OnNewSegmentation); connect(m_Controls->slicesInterpolator, &QmitkSlicesInterpolator::SignalShowMarkerNodes, this, &QmitkSegmentationView::OnShowMarkerNodes); connect(m_Controls->layersWidget, &QmitkLayersWidget::LayersChanged, this, &QmitkSegmentationView::OnLayersChanged); connect(m_Controls->labelsWidget, &QmitkLabelsWidget::ShowLabelTable, this, &QmitkSegmentationView::OnShowLabelTable); // *------------------------ // * LABELSETWIDGET // *------------------------ connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::goToLabel, this, &QmitkSegmentationView::OnGoToLabel); connect(m_Controls->labelSetWidget, &QmitkLabelSetWidget::LabelSetWidgetReset, this, &QmitkSegmentationView::OnLabelSetWidgetReset); m_Controls->labelSetWidget->SetDataStorage(this->GetDataStorage()); m_Controls->labelSetWidget->hide(); auto command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &QmitkSegmentationView::ValidateSelectionInput); m_RenderingManagerObserverTag = mitk::RenderingManager::GetInstance()->AddObserver(mitk::RenderingManagerViewsInitializedEvent(), command); m_RenderWindowPart = this->GetRenderWindowPart(); if (nullptr != m_RenderWindowPart) { this->RenderWindowPartActivated(m_RenderWindowPart); } // Make sure the GUI notices if appropriate data is already present on creation. // Should be done last, if everything else is configured because it triggers the autoselection of data. m_Controls->referenceNodeSelector->SetAutoSelectNewNodes(true); m_Controls->workingNodeSelector->SetAutoSelectNewNodes(true); this->UpdateGUI(); } void QmitkSegmentationView::ActiveToolChanged() { auto activeTool = m_ToolManager->GetActiveTool(); if (nullptr == activeTool) { // no tool activated, deactivate the segmentation interactor m_SegmentationInteractor->Disable(); return; } // activate segmentation interactor to get informed about render window entered / left events m_SegmentationInteractor->Enable(); } void QmitkSegmentationView::ValidateRendererGeometry(const itk::EventObject& event) { if (!mitk::SegmentationInteractionEvent().CheckEvent(&event)) { return; } const auto* segmentationInteractionEvent = dynamic_cast(&event); const mitk::BaseRenderer::Pointer sendingRenderer = segmentationInteractionEvent->GetSender(); if (nullptr == sendingRenderer) { return; } bool entered = segmentationInteractionEvent->HasEnteredRenderWindow(); if (entered) { // mouse cursor of tool inside render window // check if tool can be used: reference geometry needs to be aligned with renderer geometry const auto* referenceDataNode = m_ToolManager->GetReferenceData(0); if (nullptr != referenceDataNode) { const auto workingImage = dynamic_cast(referenceDataNode->GetData()); if (nullptr != workingImage) { const mitk::TimeGeometry* workingImageGeometry = workingImage->GetTimeGeometry(); if (nullptr != workingImageGeometry) { bool isGeometryAligned = mitk::BaseRendererHelper::IsRendererAlignedWithSegmentation(sendingRenderer, workingImageGeometry); if (!isGeometryAligned) { this->BlockRenderer(sendingRenderer, true); this->UpdateWarningLabel( tr("Please perform a reinit on the segmentation image inside the entered Render Window!")); return; } } } } } this->BlockRenderer(sendingRenderer, false); this->UpdateWarningLabel(tr("")); } void QmitkSegmentationView::BlockRenderer(mitk::BaseRenderer* baseRenderer, bool blocked) { if (blocked) { mitk::ManualPlacementAnnotationRenderer::AddAnnotation(m_RendererAnnotation.GetPointer(), baseRenderer); m_RendererAnnotation->Update(baseRenderer); baseRenderer->RequestUpdate(); } else { m_RendererAnnotation->RemoveFromBaseRenderer(baseRenderer); } } 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; } // tell the interpolation about tool manager, data storage and render window part if (nullptr != m_RenderWindowPart) { QList controllers; controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("axial")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("sagittal")->GetSliceNavigationController()); controllers.push_back(m_RenderWindowPart->GetQmitkRenderWindow("coronal")->GetSliceNavigationController()); m_Controls->slicesInterpolator->Initialize(m_ToolManager, controllers); } } void QmitkSegmentationView::RenderWindowPartDeactivated(mitk::IRenderWindowPart* /*renderWindowPart*/) { m_RenderWindowPart = nullptr; if (nullptr != m_Parent) { m_Parent->setEnabled(false); } } void QmitkSegmentationView::OnPreferencesChanged(const berry::IBerryPreferences* prefs) { auto labelSuggestions = mitk::BaseApplication::instance().config().getString(mitk::BaseApplication::ARG_SEGMENTATION_LABEL_SUGGESTIONS.toStdString(), ""); m_DefaultLabelNaming = labelSuggestions.empty() ? prefs->GetBool("default label naming", true) : false; // No default label naming when label suggestions are enforced via command-line argument if (nullptr != m_Controls) { m_Controls->labelsWidget->SetDefaultLabelNaming(m_DefaultLabelNaming); bool slimView = prefs->GetBool("slim view", false); m_Controls->toolSelectionBox2D->SetShowNames(!slimView); m_Controls->toolSelectionBox3D->SetShowNames(!slimView); } m_DrawOutline = prefs->GetBool("draw outline", true); m_SelectionMode = prefs->GetBool("selection mode", false); m_LabelSetPresetPreference = prefs->Get("label set preset", ""); this->ApplyDisplayOptions(); } void QmitkSegmentationView::NodeAdded(const mitk::DataNode* node) { if (m_SegmentationPredicate->CheckNode(node)) { this->ApplyDisplayOptions(const_cast(node)); } } void QmitkSegmentationView::NodeRemoved(const mitk::DataNode* node) { if (!m_SegmentationPredicate->CheckNode(node)) { return; } // remove all possible contour markers of the segmentation mitk::DataStorage::SetOfObjects::ConstPointer allContourMarkers = this->GetDataStorage()->GetDerivations( node, mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true))); ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); for (mitk::DataStorage::SetOfObjects::ConstIterator it = allContourMarkers->Begin(); it != allContourMarkers->End(); ++it) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; service->RemovePlanePosition(id); this->GetDataStorage()->Remove(it->Value()); } context->ungetService(ppmRef); service = nullptr; mitk::Image* image = dynamic_cast(node->GetData()); mitk::SurfaceInterpolationController::GetInstance()->RemoveInterpolationSession(image); } void QmitkSegmentationView::OnEstablishLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } workingImage->GetActiveLabelSet()->AddLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent += mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent += mitk::MessageDelegate1(m_Controls->labelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent += mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent += mitk::MessageDelegate( this, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::OnLooseLabelSetConnection() { if (m_WorkingNode.IsNull()) { return; } auto workingImage = dynamic_cast(m_WorkingNode->GetData()); if (nullptr == workingImage) { return; } // Reset LabelSetWidget Events workingImage->GetActiveLabelSet()->AddLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->RemoveLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::ResetAllTableWidgetItems); workingImage->GetActiveLabelSet()->ModifyLabelEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->AllLabelsModifiedEvent -= mitk::MessageDelegate( m_Controls->labelSetWidget, &QmitkLabelSetWidget::UpdateAllTableWidgetItems); workingImage->GetActiveLabelSet()->ActiveLabelEvent -= mitk::MessageDelegate1(m_Controls->labelSetWidget, &QmitkLabelSetWidget::SelectLabelByPixelValue); // Removed in T27851 to have a chance to react to AfterChangeLayerEvent. Did it brake something? // workingImage->BeforeChangeLayerEvent -= mitk::MessageDelegate( // this, &QmitkMultiLabelSegmentationView::OnLooseLabelSetConnection); workingImage->AfterChangeLayerEvent -= mitk::MessageDelegate( this, &QmitkSegmentationView::UpdateGUI); } void QmitkSegmentationView::ApplyDisplayOptions() { if (nullptr == m_Parent) { return; } if (nullptr == m_Controls) { return; // might happen on initialization (preferences loaded) } mitk::DataStorage::SetOfObjects::ConstPointer allImages = this->GetDataStorage()->GetSubset(m_SegmentationPredicate); for (mitk::DataStorage::SetOfObjects::const_iterator iter = allImages->begin(); iter != allImages->end(); ++iter) { this->ApplyDisplayOptions(*iter); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void QmitkSegmentationView::ApplyDisplayOptions(mitk::DataNode* node) { if (nullptr == node) { return; } auto labelSetImage = dynamic_cast(node->GetData()); if (nullptr != labelSetImage) { // node is a multi label segmentation // its 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 node->GetData()->Modified(); } else if (nullptr != node->GetData()) { // node is a legacy binary segmentation bool isBinary = false; node->GetBoolProperty("binary", isBinary); if (isBinary) { node->SetProperty("outline binary", mitk::BoolProperty::New(m_DrawOutline)); node->SetProperty("outline width", mitk::FloatProperty::New(2.0)); // force render window update to show outline node->GetData()->Modified(); } } } void QmitkSegmentationView::OnContourMarkerSelected(const mitk::DataNode* node) { QmitkRenderWindow* selectedRenderWindow = nullptr; auto* renderWindowPart = this->GetRenderWindowPart(mitk::WorkbenchUtil::OPEN); auto* axialRenderWindow = renderWindowPart->GetQmitkRenderWindow("axial"); auto* sagittalRenderWindow = renderWindowPart->GetQmitkRenderWindow("sagittal"); auto* coronalRenderWindow = renderWindowPart->GetQmitkRenderWindow("coronal"); auto* threeDRenderWindow = renderWindowPart->GetQmitkRenderWindow("3d"); bool PlanarFigureInitializedWindow = false; // find initialized renderwindow if (node->GetBoolProperty("PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, axialRenderWindow->GetRenderer())) { selectedRenderWindow = axialRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, sagittalRenderWindow->GetRenderer())) { selectedRenderWindow = sagittalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, coronalRenderWindow->GetRenderer())) { selectedRenderWindow = coronalRenderWindow; } if (!selectedRenderWindow && node->GetBoolProperty( "PlanarFigureInitializedWindow", PlanarFigureInitializedWindow, threeDRenderWindow->GetRenderer())) { selectedRenderWindow = threeDRenderWindow; } // make node visible if (nullptr != selectedRenderWindow) { std::string nodeName = node->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int id = atof(nodeName.substr(t + 1).c_str()) - 1; ctkPluginContext* context = mitk::PluginActivator::getContext(); ctkServiceReference ppmRef = context->getServiceReference(); mitk::PlanePositionManagerService* service = context->getService(ppmRef); selectedRenderWindow->GetSliceNavigationController()->ExecuteOperation(service->GetPlanePosition(id)); context->ungetService(ppmRef); selectedRenderWindow->GetRenderer()->GetCameraController()->Fit(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } } void QmitkSegmentationView::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*part*/, const QList& nodes) { if (0 == nodes.size()) { return; } std::string markerName = "Position"; unsigned int numberOfNodes = nodes.size(); std::string nodeName = nodes.at(0)->GetName(); if ((numberOfNodes == 1) && (nodeName.find(markerName) == 0)) { this->OnContourMarkerSelected(nodes.at(0)); return; } } void QmitkSegmentationView::ResetMouseCursor() { if (m_MouseCursorSet) { mitk::ApplicationCursor::GetInstance()->PopCursor(); m_MouseCursorSet = false; } } void QmitkSegmentationView::SetMouseCursor(const us::ModuleResource& resource, int hotspotX, int hotspotY) { // Remove previously set mouse cursor if (m_MouseCursorSet) { this->ResetMouseCursor(); } if (resource) { us::ModuleResourceStream cursor(resource, std::ios::binary); mitk::ApplicationCursor::GetInstance()->PushCursor(cursor, hotspotX, hotspotY); m_MouseCursorSet = true; } } void QmitkSegmentationView::UpdateGUI() { mitk::DataNode* referenceNode = m_ToolManager->GetReferenceData(0); bool hasReferenceNode = referenceNode != nullptr; mitk::DataNode* workingNode = m_ToolManager->GetWorkingData(0); bool hasWorkingNode = workingNode != nullptr; m_Controls->newSegmentationButton->setEnabled(false); if (hasReferenceNode) { m_Controls->newSegmentationButton->setEnabled(true); } if (hasWorkingNode && hasReferenceNode) { int layer = -1; referenceNode->GetIntProperty("layer", layer); workingNode->SetIntProperty("layer", layer + 1); } m_Controls->layersWidget->UpdateGUI(); m_Controls->labelsWidget->UpdateGUI(); this->ValidateSelectionInput(); } void QmitkSegmentationView::ValidateSelectionInput() { this->UpdateWarningLabel(""); m_Controls->layersWidget->setEnabled(false); m_Controls->labelsWidget->setEnabled(false); m_Controls->labelSetWidget->setEnabled(false); // the argument is actually not used // enable status depends on the tool manager selection m_Controls->toolSelectionBox2D->setEnabled(false); m_Controls->toolSelectionBox3D->setEnabled(false); m_Controls->slicesInterpolator->setEnabled(false); m_Controls->interpolatorWarningLabel->hide(); mitk::DataNode* referenceNode = m_Controls->referenceNodeSelector->GetSelectedNode(); mitk::DataNode* workingNode = m_Controls->workingNodeSelector->GetSelectedNode(); if (nullptr == referenceNode) { return; } if (nullptr == workingNode) { return; } mitk::IRenderWindowPart* renderWindowPart = this->GetRenderWindowPart(); auto workingNodeIsVisible = renderWindowPart && workingNode->IsVisible(renderWindowPart->GetQmitkRenderWindow("axial")->GetRenderer()); if (!workingNodeIsVisible) { this->UpdateWarningLabel(tr("The selected segmentation is currently not visible!")); return; } /* * Here we check whether the geometry of the selected segmentation image is aligned with the worldgeometry. * At the moment it is not supported to use a geometry different from the selected image for reslicing. * For further information see Bug 16063 */ const mitk::BaseGeometry* workingNodeGeo = workingNode->GetData()->GetGeometry(); const mitk::BaseGeometry* worldGeo = renderWindowPart->GetQmitkRenderWindow("3d")->GetSliceNavigationController()->GetCurrentGeometry3D(); if (nullptr != workingNodeGeo && nullptr != worldGeo) { if (/*mitk::Equal(*workingNodeGeo->GetBoundingBox(), *worldGeo->GetBoundingBox(), mitk::eps, true)*/true) { m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(workingNode); m_Controls->layersWidget->setEnabled(true); m_Controls->labelsWidget->setEnabled(true); m_Controls->labelSetWidget->setEnabled(true); m_Controls->toolSelectionBox2D->setEnabled(true); m_Controls->toolSelectionBox3D->setEnabled(true); auto labelSetImage = dynamic_cast(workingNode->GetData()); if (nullptr != labelSetImage) { int numberOfLabels = labelSetImage->GetNumberOfLabels(labelSetImage->GetActiveLayer()); if (2 == numberOfLabels) // fix for T27319: exterior is label 0, first label is label 1 { m_Controls->slicesInterpolator->setEnabled(true); } else { m_Controls->interpolatorWarningLabel->show(); m_Controls->interpolatorWarningLabel->setText("Interpolation only works for single label segmentations."); } } return; } } m_ToolManager->SetReferenceData(referenceNode); m_ToolManager->SetWorkingData(nullptr); this->UpdateWarningLabel(tr("Please perform a reinit on the segmentation image!")); } void QmitkSegmentationView::UpdateWarningLabel(QString text) { if (text.size() == 0) { m_Controls->selectionWarningLabel->hide(); } else { m_Controls->selectionWarningLabel->show(); m_Controls->selectionWarningLabel->setText("" + text + ""); } }