diff --git a/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp b/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp index fc8ce4fbdf..dda2126a15 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp +++ b/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp @@ -1,89 +1,78 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkDiffSliceOperationApplier.h" #include "mitkDiffSliceOperation.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include <mitkExtractSliceFilter.h> #include <mitkVtkImageOverwrite.h> // VTK #include <vtkSmartPointer.h> mitk::DiffSliceOperationApplier::DiffSliceOperationApplier() { } mitk::DiffSliceOperationApplier::~DiffSliceOperationApplier() { } void mitk::DiffSliceOperationApplier::ExecuteOperation(Operation *operation) { auto *imageOperation = dynamic_cast<DiffSliceOperation *>(operation); // as we only support DiffSliceOperation return if operation is not type of DiffSliceOperation if (!imageOperation) return; // check if the operation is valid if (imageOperation->IsValid()) { // the actual overwrite filter (vtk) vtkSmartPointer<mitkVtkImageOverwrite> reslice = vtkSmartPointer<mitkVtkImageOverwrite>::New(); mitk::Image::Pointer slice = imageOperation->GetSlice(); // Set the slice as 'input' reslice->SetInputSlice(slice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); // a wrapper for vtkImageOverwrite mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(imageOperation->GetImage()); extractor->SetTimeStep(imageOperation->GetTimeStep()); extractor->SetWorldGeometry(dynamic_cast<const PlaneGeometry *>(imageOperation->GetWorldGeometry())); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(imageOperation->GetImage()->GetGeometry(imageOperation->GetTimeStep())); extractor->Modified(); extractor->Update(); // make sure the modification is rendered RenderingManager::GetInstance()->RequestUpdateAll(); imageOperation->GetImage()->Modified(); - mitk::ExtractSliceFilter::Pointer extractor2 = mitk::ExtractSliceFilter::New(); - extractor2->SetInput(imageOperation->GetImage()); - extractor2->SetTimeStep(imageOperation->GetTimeStep()); - extractor2->SetWorldGeometry(dynamic_cast<const PlaneGeometry *>(imageOperation->GetWorldGeometry())); - extractor2->SetResliceTransformByGeometry(imageOperation->GetImage()->GetGeometry(imageOperation->GetTimeStep())); - extractor2->Modified(); - extractor2->Update(); - - // TODO Move this code to SurfaceInterpolationController! - mitk::Image::Pointer slice2 = extractor2->GetOutput(); - mitk::PlaneGeometry::ConstPointer plane = dynamic_cast<const PlaneGeometry *>(imageOperation->GetWorldGeometry()); - slice2->DisconnectPipeline(); - mitk::SegTool2D::UpdateSurfaceInterpolation(slice2, imageOperation->GetImage(), plane, true); + PlaneGeometry::ConstPointer plane = dynamic_cast<const PlaneGeometry *>(imageOperation->GetWorldGeometry()); + SegTool2D::UpdateAllSurfaceInterpolations(dynamic_cast<LabelSetImage*>(imageOperation->GetImage()), imageOperation->GetTimeStep(), plane, true); } } mitk::DiffSliceOperationApplier *mitk::DiffSliceOperationApplier::GetInstance() { static auto *s_Instance = new DiffSliceOperationApplier(); return s_Instance; } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index f5ef7c5df5..9d6b91f3a1 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,798 +1,821 @@ /*============================================================================ 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 <mitkTimeNavigationController.h> +#include "mitkImageAccessByItk.h" // Include of the new ImageExtractor #include "mitkMorphologicalOperations.h" #include "mitkPlanarCircle.h" #include "usGetModuleContext.h" // Includes for 3DSurfaceInterpolation #include "mitkImageTimeSelector.h" #include "mitkImageToContourFilter.h" #include "mitkSurfaceInterpolationController.h" // includes for resling and overwriting #include <mitkExtractSliceFilter.h> #include <mitkVtkImageOverwrite.h> #include <vtkImageData.h> #include <vtkSmartPointer.h> #include "mitkOperationEvent.h" #include "mitkUndoController.h" #include <mitkDiffSliceOperationApplier.h> #include "mitkAbstractTransformGeometry.h" #include "mitkLabelSetImage.h" #include "mitkContourModelUtils.h" // #include <itkImageRegionIterator.h> #include <vtkAbstractArray.h> #include <vtkFieldData.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<const InteractionPositionEvent *>(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<ScalarType>(normal.GetVnlVector(), imageNormal0.GetVnlVector())); imageNormal1.SetVnlVector(vnl_cross_3d<ScalarType>(normal.GetVnlVector(), imageNormal1.GetVnlVector())); imageNormal2.SetVnlVector(vnl_cross_3d<ScalarType>(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<int>(image->GetDimension(affectedDimension))) return false; return true; } -void mitk::SegTool2D::UpdateSurfaceInterpolation(const Image *slice, - const Image *workingImage, +void mitk::SegTool2D::UpdateAllSurfaceInterpolations(const LabelSetImage *workingImage, + TimeStepType timeStep, const PlaneGeometry *plane, bool detectIntersection) { - std::vector<SliceInformation> slices = { SliceInformation(slice, plane, 0)}; - Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection, 0, 0); + if (nullptr == workingImage) mitkThrow() << "Cannot update surface interpolation. Invalid working image passed."; + if (nullptr == plane) mitkThrow() << "Cannot update surface interpolation. Invalid plane passed."; + + auto affectedLabels = mitk::SurfaceInterpolationController::GetInstance()->GetAffectedLabels(workingImage, timeStep, plane); + for (auto affectedLabel : affectedLabels) + { + auto groupID = workingImage->GetGroupIndexOfLabel(affectedLabel); + auto slice = GetAffectedImageSliceAs2DImage(plane, workingImage->GetGroupImage(groupID), timeStep); + std::vector<SliceInformation> slices = { SliceInformation(slice, plane, timeStep) }; + Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection, affectedLabel); + } } -void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo) +void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo, LabelSetImage::LabelValueType labelValue) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; - contourInfo.ContourNormal = sliceInfo.plane->GetNormal(); - contourInfo.ContourPoint = sliceInfo.plane->GetOrigin(); - mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); + contourInfo.LabelValue = labelValue; + contourInfo.TimeStep = sliceInfo.timestep; + contourInfo.Plane = sliceInfo.plane; + + mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo, true); +} + +template <typename ImageType> +void ClearBufferProcessing(ImageType* itkImage) +{ + itkImage->FillBuffer(0); } void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector<SliceInformation>& sliceInfos, const Image* workingImage, bool detectIntersection, - unsigned int activeLayerID, mitk::Label::PixelType activeLabelValue) { 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<mitk::Surface::Pointer> contourList; contourList.reserve(sliceInfos.size()); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); std::vector<SliceInformation> relevantSlices = sliceInfos; if (detectIntersection) { relevantSlices.clear(); for (const auto& sliceInfo : sliceInfos) { // Test whether there is something to extract or whether the slice just contains intersections of others - mitk::Image::Pointer slice2 = sliceInfo.slice->Clone(); - mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); + //Remark we cannot just errode the clone of sliceInfo.slice, because Erode currently only + //works on pixel value 1. But we need to erode active label. Therefore we use TransferLabelContent + //as workarround. + //If MorphologicalOperations::Erode is supports user defined pixel values, the workarround + //can be removed. + //Workarround starts + mitk::Image::Pointer slice2 = Image::New(); + slice2->Initialize(sliceInfo.slice); + AccessByItk(slice2, ClearBufferProcessing); + LabelSetImage::LabelValueType erodeValue = 1; + auto label = Label::New(erodeValue, ""); + TransferLabelContent(sliceInfo.slice, slice2, { label }, LabelSetImage::UnlabeledValue, LabelSetImage::UnlabeledValue, false, { {activeLabelValue, erodeValue} }); + //Workarround ends + + mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); contourExtractor->SetInput(slice2); + contourExtractor->SetContourValue(erodeValue); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { - Self::RemoveContourFromInterpolator(sliceInfo); + Self::RemoveContourFromInterpolator(sliceInfo, activeLabelValue); } else { relevantSlices.push_back(sliceInfo); } } } if (relevantSlices.empty()) return; - std::vector<const mitk::PlaneGeometry*> contourPlanes; + SurfaceInterpolationController::CPIVector cpis; for (const auto& sliceInfo : relevantSlices) { contourExtractor->SetInput(sliceInfo.slice); contourExtractor->SetContourValue(activeLabelValue); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { - Self::RemoveContourFromInterpolator(sliceInfo); + Self::RemoveContourFromInterpolator(sliceInfo, activeLabelValue); } else { - vtkSmartPointer<vtkIntArray> intArray = vtkSmartPointer<vtkIntArray>::New(); - intArray->InsertNextValue(activeLabelValue); - intArray->InsertNextValue(activeLayerID); - contour->GetVtkPolyData()->GetFieldData()->AddArray(intArray); - contour->DisconnectPipeline(); - contourList.push_back(contour); - contourPlanes.push_back(sliceInfo.plane); + cpis.emplace_back(contour, sliceInfo.plane->Clone(), activeLabelValue, sliceInfo.timestep); } } - mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList, contourPlanes); + mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(cpis); } 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<mitkVtkImageOverwrite> reslice = vtkSmartPointer<mitkVtkImageOverwrite>::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<Image *>(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<Image *>(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<Image*>(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<mitk::SegTool2D>(this, &mitk::SegTool2D::OnTimePointChangedInternal); m_LastTimePointTriggered = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); } void mitk::SegTool2D::Deactivated() { this->GetToolManager()->SelectedTimePointChanged -= mitk::MessageDelegate<mitk::SegTool2D>(this, &mitk::SegTool2D::OnTimePointChangedInternal); Superclass::Deactivated(); } void mitk::SegTool2D::OnTimePointChangedInternal() { if (m_IsTimePointChangeAware && nullptr != this->GetWorkingDataNode()) { const TimePointType timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (timePoint != m_LastTimePointTriggered) { m_LastTimePointTriggered = timePoint; this->OnTimePointChanged(); } } } void mitk::SegTool2D::OnTimePointChanged() { //default implementation does nothing } mitk::DataNode* mitk::SegTool2D::GetWorkingDataNode() const { if (nullptr != this->GetToolManager()) { return this->GetToolManager()->GetWorkingData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetWorkingData() const { auto node = this->GetWorkingDataNode(); if (nullptr != node) { return dynamic_cast<Image*>(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<Image*>(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<const AbstractTransformGeometry *>(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (planeGeometry && segmentationResult && !abstractTransformGeometry) { const auto workingNode = this->GetWorkingDataNode(); auto *image = dynamic_cast<Image *>(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<mitk::PlaneGeometry*>(planeGeometry), timeStep); Self::WriteBackSegmentationResults(workingNode, { sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image * segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; if(m_LastEventSender == nullptr) { return; } unsigned int currentSlicePosition = m_LastEventSender->GetSliceNavigationController()->GetStepper()->GetPos(); SliceInformation sliceInfo(segmentationResult, const_cast<mitk::PlaneGeometry *>(planeGeometry), timeStep); sliceInfo.slicePosition = currentSlicePosition; WriteBackSegmentationResults({ sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResults(const std::vector<SegTool2D::SliceInformation> &sliceList, bool writeSliceToVolume) { if (sliceList.empty()) { return; } if (nullptr == m_LastEventSender) { MITK_WARN << "Cannot write tool results. Tool seems to be in an invalid state, as no interaction event was recieved but is expected."; return; } const auto workingNode = this->GetWorkingDataNode(); // the first geometry is needed otherwise restoring the position is not working const auto* plane3 = dynamic_cast<const PlaneGeometry*>(dynamic_cast<const mitk::SlicedGeometry3D*>( m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) ->GetPlaneGeometry(0)); const unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetStepper()->GetPos(); mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); /* A cleaner solution would be to add a contour marker for each slice info. It currently does not work as the contour markers expect that the plane is always the plane of slice 0. Had not the time to do it properly no. Should be solved by T28146*/ this->AddContourmarker(plane3, slicePosition); } void mitk::SegTool2D::WriteBackSegmentationResults(const DataNode* workingNode, const std::vector<SliceInformation>& 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<Image*>(workingNode->GetData()); mitk::Label::PixelType activeLabelValue = 0; - unsigned int activeLayerID = 0; try{ auto labelSetImage = dynamic_cast<mitk::LabelSetImage*>(workingNode->GetData()); - activeLayerID = labelSetImage->GetActiveLayer(); activeLabelValue = labelSetImage->GetActiveLabel()->GetValue(); } catch(...) { mitkThrow() << "Working node does not contain labelSetImage."; } if (nullptr == image) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } for (const auto& sliceInfo : sliceList) { if (writeSliceToVolume && nullptr != sliceInfo.plane && sliceInfo.slice.IsNotNull()) { SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } - SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false, activeLayerID, activeLabelValue); + SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false, activeLabelValue); // also mark its node as modified (T27308). Can be removed if T27307 // is properly solved if (workingNode != nullptr) workingNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo) { SliceInformation sliceInfo(slice, planeGeometry, timeStep); WriteSliceToVolume(workingImage, sliceInfo, allowUndo); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo) { if (nullptr == workingImage) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } DiffSliceOperation* undoOperation = nullptr; if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // Create undo operation by caching the not yet modified slices mitk::Image::Pointer originalSlice = GetAffectedImageSliceAs2DImage(sliceInfo.plane, workingImage, sliceInfo.timestep); undoOperation = new DiffSliceOperation(workingImage, originalSlice, dynamic_cast<SlicedGeometry3D*>(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<mitkVtkImageOverwrite> reslice = vtkSmartPointer<mitkVtkImageOverwrite>::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<Image*>(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<SlicedGeometry3D*>(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<PlanePositionManagerService> serviceRef = us::GetModuleContext()->GetServiceReference<PlanePositionManagerService>(); PlanePositionManagerService *service = us::GetModuleContext()->GetService(serviceRef); unsigned int size = service->GetNumberOfPlanePositions(); unsigned int id = service->AddNewPlanePosition(planeGeometry, sliceIndex); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); contourMarker->SetPlaneGeometry(planeGeometry->Clone()); contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); contourMarker->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); std::stringstream markerStream; auto workingNode = this->GetWorkingDataNode(); markerStream << m_Contourmarkername; markerStream << " "; markerStream << id + 1; DataNode::Pointer rotatedContourNode = DataNode::New(); rotatedContourNode->SetData(contourMarker); rotatedContourNode->SetProperty("name", StringProperty::New(markerStream.str())); rotatedContourNode->SetProperty("isContourMarker", BoolProperty::New(true)); rotatedContourNode->SetBoolProperty("PlanarFigureInitializedWindow", true, m_LastEventSender); rotatedContourNode->SetProperty("includeInBoundingBox", BoolProperty::New(false)); rotatedContourNode->SetProperty("helper object", mitk::BoolProperty::New(!m_ShowMarkerNodes)); rotatedContourNode->SetProperty("planarfigure.drawcontrolpoints", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawname", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawoutline", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawshadow", BoolProperty::New(false)); if (planeGeometry) { if (id == size) { this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } else { mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer markers = this->GetToolManager()->GetDataStorage()->GetDerivations(workingNode, isMarker); for (auto iter = markers->begin(); iter != markers->end(); ++iter) { std::string nodeName = (*iter)->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int markerId = atof(nodeName.substr(t + 1).c_str()) - 1; if (id == markerId) { return id; } } this->GetToolManager()->GetDataStorage()->Add(rotatedContourNode, workingNode); } } return id; } void mitk::SegTool2D::InteractiveSegmentationBugMessage(const std::string &message) const { MITK_ERROR << "********************************************************************************" << std::endl << " " << message << std::endl << "********************************************************************************" << std::endl << " " << std::endl << " If your image is rotated or the 2D views don't really contain the patient image, try to press the " "button next to the image selection. " << std::endl << " " << std::endl << " Please file a BUG REPORT: " << std::endl << " https://phabricator.mitk.org/" << std::endl << " Contain the following information:" << std::endl << " - What image were you working on?" << std::endl << " - Which region of the image?" << std::endl << " - Which tool did you use?" << std::endl << " - What did you do?" << std::endl << " - What happened (not)? What did you expect?" << std::endl; } void mitk::SegTool2D::WritePreviewOnWorkingImage( Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue) { if (nullptr == targetSlice) { mitkThrow() << "Cannot write preview on working image. Target slice does not point to a valid instance."; } if (nullptr == sourceSlice) { mitkThrow() << "Cannot write preview on working image. Source slice does not point to a valid instance."; } if (nullptr == workingImage) { mitkThrow() << "Cannot write preview on working image. Working image does not point to a valid instance."; } auto constVtkSource = sourceSlice->GetVtkImageData(); /*Need to const cast because Vtk interface does not support const correctly. (or I am not experienced enough to use it correctly)*/ auto nonConstVtkSource = const_cast<vtkImageData*>(constVtkSource); ContourModelUtils::FillSliceInSlice(nonConstVtkSource, targetSlice->GetVtkImageData(), workingImage, paintingPixelValue, 1.0); } bool mitk::SegTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent* positionEvent, const mitk::BaseData* data) { bool isPositionEventInsideImageRegion = nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); if (!isPositionEventInsideImageRegion) MITK_WARN("EditableContourTool") << "PositionEvent is outside ImageRegion!"; return isPositionEventInsideImageRegion; } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.h b/Modules/Segmentation/Interactions/mitkSegTool2D.h index 99d6fdf91a..2a7ac4b830 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,303 +1,303 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegTool2D_h #define mitkSegTool2D_h #include <mitkCommon.h> #include <mitkImage.h> #include <mitkTool.h> #include <MitkSegmentationExports.h> #include <mitkInteractionPositionEvent.h> #include <mitkInteractionConst.h> #include <mitkPlanePositionManager.h> #include <mitkRestorePlanePositionOperation.h> #include <mitkDiffSliceOperation.h> namespace mitk { class BaseRenderer; /** \brief Abstract base class for segmentation tools. \sa Tool \ingroup Interaction \ingroup ToolManagerEtAl Implements 2D segmentation specific helper methods, that might be of use to all kind of 2D segmentation tools. At the moment these are: - Determination of the slice where the user paints upon (DetermineAffectedImageSlice) - Projection of a 3D contour onto a 2D plane/slice SegTool2D tries to structure the interaction a bit. If you pass "PressMoveRelease" as the interaction type of your derived tool, you might implement the methods OnMousePressed, OnMouseMoved, and OnMouseReleased. Yes, your guess about when they are called is correct. \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT SegTool2D : public Tool { public: mitkClassMacro(SegTool2D, Tool); /** \brief Calculates for a given Image and PlaneGeometry, which slice of the image (in index corrdinates) is meant by the plane. \return false, if no slice direction seems right (e.g. rotated planes) \param image \param plane \param affectedDimension The image dimension, which is constant for all points in the plane, e.g. Axial --> 2 \param affectedSlice The index of the image slice */ static bool DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice); /** - * @brief Updates the surface interpolation by extracting the contour form the given slice. + * @brief Updates the surface interpolations by extracting the contour form the given slice for all labels + * that have a surface contour information stored for the given plane at the given timestep. * @param slice the slice from which the contour should be extracted * @param workingImage the segmentation image + * @param timeStep the time step for wich the surface interpolation information should be updated. * @param plane the plane in which the slice lies * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ - static void UpdateSurfaceInterpolation(const Image *slice, - const Image *workingImage, + static void UpdateAllSurfaceInterpolations(const LabelSetImage* workingImage, + TimeStepType timeStep, const PlaneGeometry *plane, bool detectIntersection); /** * \brief Extract the slice of an image that the user just scribbles on. The given component denotes the vector component of an vector image. * * \param positionEvent Event that specifies the plane that should be used to slice * \param image Image that should be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const InteractionPositionEvent* positionEvent, const Image* image, unsigned int component = 0); /** * \brief Extract the slice of an image cut by given plane. The given component denotes the vector component of a vector image. * * \param planeGeometry Geometry defining the slice that should be cut out. * \param image Image that should be sliced * \param timeStep TimeStep of the image that shold be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const PlaneGeometry* planeGeometry, const Image* image, TimeStepType timeStep, unsigned int component = 0); static Image::Pointer GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component = 0); /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * Calls static WriteBackSegmentationResults*/ static void WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep); /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * For more details see protected WriteSliceToVolume version.*/ static void WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo); void SetShowMarkerNodes(bool); /** * \brief Enables or disables the 3D interpolation after writing back the 2D segmentation result, and defaults to * true. */ void SetEnable3DInterpolation(bool); void Activated() override; void Deactivated() override; itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); protected: SegTool2D(); // purposely hidden SegTool2D(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~SegTool2D() override; /** * @brief returns the segmentation node that should be modified by the tool. */ DataNode* GetWorkingDataNode() const; Image* GetWorkingData() const; DataNode* GetReferenceDataNode() const; Image* GetReferenceData() const; /** * This function can be reimplemented by derived classes to react on changes of the current * time point. Default implementation does nothing.*/ virtual void OnTimePointChanged(); struct SliceInformation { mitk::Image::ConstPointer slice; const mitk::PlaneGeometry *plane = nullptr; mitk::TimeStepType timestep = 0; unsigned int slicePosition; SliceInformation() = default; SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep); }; /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param sliceInfos vector of slice information instances from which the contours should be extracted * @param workingImage the segmentation image * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the - * @param activeLayerID The layer ID of the active label. * @param activeLabelValue The label value of the active label. * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const std::vector<SliceInformation>& sliceInfos, const Image* workingImage, bool detectIntersection, - unsigned int activeLayerID, mitk::Label::PixelType activeLabelValue); /** * \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<SliceInformation> &sliceList, bool writeSliceToVolume = true); /** \brief Writes all provided source slices into the data of the passed workingNode. * The function does the following: 1) for every passed slice write it to workingNode (and generate and undo/redo step); * 2) update the surface interpolation and 3) mark the node as modified. * @param workingNode Pointer to the node that contains the working image. * @param sliceList Vector of all slices that should be written into the workingNode. If the list is * empty, the function call does nothing. * @param writeSliceToVolume If set to false the write operation (WriteSliceToVolume will be skipped) * and only the surface interpolation will be updated. * @pre workingNode must point to a valid instance and contain an image instance as data.*/ static void WriteBackSegmentationResults(const DataNode* workingNode, const std::vector<SliceInformation>& sliceList, bool writeSliceToVolume = true); /** Writes the provided source slice into the target slice with the given pixel value. * If passed workingImage is a LabelSetImage the label set rules will be applied when * writing all non zero source pixels into the target slice (e.g. locked lables will not be touched) * with the given paintingPixelValue. * @param targetSlice Pointer to the slice that should be filled with the content of the sourceSlice. * @param sourceSlice Pointer to the slice that is the source/preview every pixel will be (tried to be) transfered . * @param workingImage Will be used to check if LabeSetImageRules have to be applied and the label set state. * @param paintingPixelValue Value that will be used to paint onto target slice. * @pre targetSlice must point to a valid instance. * @pre sourceSlice must point to a valid instance. * @pre workingImage must point to a valid instance.*/ static void WritePreviewOnWorkingImage( Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue); /** Writes a provided slice into the passed working image. The content of working image that is covered * by the slice will be completly overwritten. If asked for it also generates the needed * undo/redo steps. * @param workingImage Pointer to the image that is the target of the write operation. * @param sliceInfo SliceInfo instance that containes the slice image, the defining plane geometry and time step. * @param allowUndo Indicates if undo/redo operations should be registered for the write operation * performed by this call. true: undo/redo will be generated; false: no undo/redo will be generated, so * this operation cannot be revoked by the user. * @pre workingImage must point to a valid instance.*/ static void WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo); /** \brief Adds a new node called Contourmarker to the datastorage which holds a mitk::PlanarFigure. By selecting this node the slicestack will be reoriented according to the passed PlanarFigure's Geometry */ int AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex); void InteractiveSegmentationBugMessage(const std::string &message) const; /** Helper function to check if a position events points to a point inside the boundingbox of a passed data instance.*/ static bool IsPositionEventInsideImageRegion(InteractionPositionEvent* positionEvent, const BaseData* data); BaseRenderer *m_LastEventSender = nullptr; unsigned int m_LastEventSlice = 0; itkGetMacro(LastTimePointTriggered, TimePointType); private: /** Internal method that gets triggered as soon as the tool manager indicates a * time point change. If the time point has changed since last time and tool * is set to be time point change aware, OnTimePointChanged() will be called.*/ void OnTimePointChangedInternal(); - static void RemoveContourFromInterpolator(const SliceInformation& sliceInfo); + static void RemoveContourFromInterpolator(const SliceInformation& sliceInfo, LabelSetImage::LabelValueType labelValue); // The prefix of the contourmarkername. Suffix is a consecutive number const std::string m_Contourmarkername; bool m_ShowMarkerNodes = false; static bool m_SurfaceInterpolationEnabled; bool m_IsTimePointChangeAware = true; TimePointType m_LastTimePointTriggered = 0.; }; } // namespace #endif diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp index 182a833f89..3e2b963371 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.cpp @@ -1,813 +1,824 @@ /*============================================================================ 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 <mitkSurfaceInterpolationController.h> #include <shared_mutex> #include <mitkCreateDistanceImageFromSurfaceFilter.h> #include <mitkComputeContourSetNormalsFilter.h> #include <mitkImageAccessByItk.h> #include <mitkImagePixelReadAccessor.h> #include <mitkImageTimeSelector.h> #include <mitkImageToSurfaceFilter.h> #include <mitkLabelSetImage.h> #include <mitkMemoryUtilities.h> #include <mitkNodePredicateDataUID.h> #include <mitkNodePredicateProperty.h> #include <mitkNodePredicateAnd.h> #include <mitkPlanarCircle.h> #include <mitkPlaneGeometry.h> #include <mitkReduceContourSetFilter.h> #include <vtkFieldData.h> #include <vtkMath.h> #include <vtkPolygon.h> typedef std::map<mitk::TimeStepType, mitk::SurfaceInterpolationController::CPIVector> CPITimeStepMap; typedef std::map<mitk::LabelSetImage::LabelValueType, CPITimeStepMap> CPITimeStepLabelMap; typedef std::map<const mitk::LabelSetImage*, CPITimeStepLabelMap> CPITimeStepLabelSegMap; CPITimeStepLabelSegMap cpiMap; std::shared_mutex cpiMutex; std::map<mitk::LabelSetImage*, unsigned long> segmentationObserverTags; mitk::SurfaceInterpolationController::SurfaceInterpolationController() : m_SelectedSegmentation(nullptr), m_CurrentTimePoint(0.) { m_DistanceImageSpacing = 0.0; m_ReduceFilter = ReduceContourSetFilter::New(); m_NormalsFilter = ComputeContourSetNormalsFilter::New(); m_InterpolateSurfaceFilter = CreateDistanceImageFromSurfaceFilter::New(); m_ReduceFilter->SetUseProgressBar(false); m_NormalsFilter->SetUseProgressBar(true); m_NormalsFilter->SetProgressStepSize(1); m_InterpolateSurfaceFilter->SetUseProgressBar(true); m_InterpolateSurfaceFilter->SetProgressStepSize(7); m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; } mitk::SurfaceInterpolationController::~SurfaceInterpolationController() { this->RemoveObservers(); } void mitk::SurfaceInterpolationController::RemoveObservers() { // Removing all observers while (segmentationObserverTags.size()) { this->RemoveObserversInternal(segmentationObserverTags.begin()->first); } } mitk::SurfaceInterpolationController *mitk::SurfaceInterpolationController::GetInstance() { static mitk::SurfaceInterpolationController::Pointer m_Instance; if (m_Instance.IsNull()) { m_Instance = SurfaceInterpolationController::New(); } return m_Instance; } void mitk::SurfaceInterpolationController::AddNewContours(const std::vector<ContourPositionInformation>& newCPIs, bool reinitializationAction) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return; for (auto cpi : newCPIs) { if (cpi.Contour->GetVtkPolyData()->GetNumberOfPoints() > 0) { - this->AddToInterpolationPipeline(cpi, reinitializationAction); + this->AddToCPIMap(cpi, reinitializationAction); } } this->Modified(); } -mitk::DataNode* GetSegmentationImageNodeInternal(mitk::DataStorage* ds, mitk::LabelSetImage* seg) +mitk::DataNode* GetSegmentationImageNodeInternal(mitk::DataStorage* ds, const mitk::LabelSetImage* seg) { if (nullptr == ds) return nullptr; if (nullptr == seg) return nullptr; mitk::DataNode* segmentationNode = nullptr; mitk::NodePredicateDataUID::Pointer dataUIDPredicate = mitk::NodePredicateDataUID::New(seg->GetUID()); auto dataNodeObjects = ds->GetSubset(dataUIDPredicate); if (dataNodeObjects->Size() != 0) { for (auto it = dataNodeObjects->Begin(); it != dataNodeObjects->End(); ++it) { segmentationNode = it->Value(); } } else { MITK_ERROR << "Unable to find the labelSetImage with the desired UID."; } return segmentationNode; } mitk::DataNode* mitk::SurfaceInterpolationController::GetSegmentationImageNode() const { if (m_DataStorage.IsNull()) return nullptr; auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return nullptr; return GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation); } mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const { DataStorage::SetOfObjects::Pointer relevantNodes = DataStorage::SetOfObjects::New(); if (m_DataStorage.IsNotNull()) { //remove relevant plane nodes auto nodes = this->GetPlaneGeometryNodeFromDataStorage(segNode, labelValue); for (auto it = nodes->Begin(); it != nodes->End(); ++it) { auto aTS = dynamic_cast<mitk::IntProperty*>(it->Value()->GetProperty("timeStep"))->GetValue(); bool sameTS = (timeStep == aTS); if (sameTS) { relevantNodes->push_back(it->Value()); } } } return relevantNodes; } mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const { auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); auto isCorrectLabel = NodePredicateProperty::New("labelID", mitk::UShortProperty::New(labelValue)); auto searchPredicate = NodePredicateAnd::New(isContourPlaneGeometry, isCorrectLabel); mitk::DataStorage::SetOfObjects::ConstPointer result; if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, searchPredicate); return result; } mitk::DataStorage::SetOfObjects::ConstPointer mitk::SurfaceInterpolationController::GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const { auto isContourPlaneGeometry = NodePredicateProperty::New("isContourPlaneGeometry", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer result; if (m_DataStorage.IsNotNull()) result = m_DataStorage->GetDerivations(segNode, isContourPlaneGeometry); return result; } void mitk::SurfaceInterpolationController::AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow()<< "Cannot add plane geometries. No valid segmentation selected."; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested in AddPlaneGeometryNodeToDataStorage."; return; } if (m_DataStorage.IsNull()) { MITK_DEBUG << "Cannot add plane geometry nodes. No data storage is set."; return; } auto planeGeometry = contourInfo.Plane; if (planeGeometry) { auto segmentationNode = this->GetSegmentationImageNode(); mitk::DataStorage::SetOfObjects::ConstPointer contourNodes = this->GetPlaneGeometryNodeFromDataStorage(segmentationNode, contourInfo.LabelValue, contourInfo.TimeStep); mitk::DataNode::Pointer contourPlaneGeometryDataNode; // Go through the pre-existing contours and check if the contour position matches them. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto planeData = dynamic_cast<mitk::PlanarFigure*>(it->Value()->GetData()); if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); if (samePlane) { contourPlaneGeometryDataNode = it->Value(); break; } } // Go through the contourPlaneGeometry Data and add the segmentationNode to it. if (contourPlaneGeometryDataNode.IsNull()) { auto planeGeometryData = mitk::PlanarCircle::New(); planeGeometryData->SetPlaneGeometry(planeGeometry->Clone()); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); planeGeometryData->PlaceFigure(p1); planeGeometryData->SetCurrentControlPoint(p1); planeGeometryData->SetProperty("initiallyplaced", mitk::BoolProperty::New(true)); std::string contourName = "contourPlane L " + std::to_string(contourInfo.LabelValue) + " T " + std::to_string(contourInfo.TimeStep); contourPlaneGeometryDataNode = mitk::DataNode::New(); contourPlaneGeometryDataNode->SetData(planeGeometryData); // No need to change properties contourPlaneGeometryDataNode->SetProperty("helper object", mitk::BoolProperty::New(false)); contourPlaneGeometryDataNode->SetProperty("hidden object", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetProperty("isContourPlaneGeometry", mitk::BoolProperty::New(true)); contourPlaneGeometryDataNode->SetVisibility(false); // Need to change properties contourPlaneGeometryDataNode->SetProperty("name", mitk::StringProperty::New(contourName) ); contourPlaneGeometryDataNode->SetProperty("labelID", mitk::UShortProperty::New(contourInfo.LabelValue)); contourPlaneGeometryDataNode->SetProperty("timeStep", mitk::IntProperty::New(contourInfo.TimeStep)); contourPlaneGeometryDataNode->SetData(planeGeometryData); m_DataStorage->Add(contourPlaneGeometryDataNode, segmentationNode); } } } -void mitk::SurfaceInterpolationController::AddToInterpolationPipeline(ContourPositionInformation& contourInfo, bool reinitializationAction) +void mitk::SurfaceInterpolationController::AddToCPIMap(ContourPositionInformation& contourInfo, bool reinitializationAction) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) return; if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } if (contourInfo.Plane == nullptr) { MITK_ERROR << "contourInfo plane is null."; return; } if (contourInfo.Contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { this->RemoveContour(contourInfo); MITK_DEBUG << "contourInfo contour is empty."; return; } { std::lock_guard<std::shared_mutex> guard(cpiMutex); const auto& currentTimeStep = contourInfo.TimeStep; const auto& currentLabelValue = contourInfo.LabelValue; auto& currentImageContours = cpiMap[selectedSegmentation]; auto& currentLabelContours = currentImageContours[currentLabelValue]; auto& currentContourList = currentLabelContours[currentTimeStep]; auto finding = std::find_if(currentContourList.begin(), currentContourList.end(), [contourInfo](const ContourPositionInformation& element) {return contourInfo.Plane->IsOnPlane(element.Plane); }); if (finding != currentContourList.end()) { MITK_DEBUG << "CPI already exists. CPI is updated. Label: "<< currentLabelValue << "; Time Step: " << currentTimeStep; *finding = contourInfo; } else { currentContourList.push_back(contourInfo); } } if (!reinitializationAction) { this->AddPlaneGeometryNodeToDataStorage(contourInfo); } } -bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo) +bool mitk::SurfaceInterpolationController::RemoveContour(ContourPositionInformation contourInfo, bool keepPlaceholderForUndo) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { return false; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { return false; } bool removedIt = false; { std::lock_guard<std::shared_mutex> cpiGuard(cpiMutex); const auto currentTimeStep = contourInfo.TimeStep; const auto currentLabel = contourInfo.LabelValue; auto it = cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).begin(); while (it != cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).end()) { const ContourPositionInformation& currentContour = (*it); if (currentContour.Plane->IsOnPlane(contourInfo.Plane)) { - cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).erase(it); + if (keepPlaceholderForUndo) + { + it->Contour = nullptr; + } + else + { + cpiMap.at(selectedSegmentation).at(currentLabel).at(currentTimeStep).erase(it); + } removedIt = true; if (m_DataStorage.IsNotNull()) { mitk::DataNode::Pointer contourPlaneGeometryDataNode; auto contourNodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(m_DataStorage, selectedSegmentation), currentLabel, currentTimeStep); // Go through the nodes and check if the contour position matches them. for (auto it = contourNodes->Begin(); it != contourNodes->End(); ++it) { auto planeData = dynamic_cast<mitk::PlanarFigure*>(it->Value()->GetData()); if (nullptr == planeData) mitkThrow() << "Invalid ContourPlaneGeometry data node. Does not contion a planar figure as data."; bool samePlane = contourInfo.Plane->IsOnPlane(planeData->GetPlaneGeometry()); if (samePlane) { m_DataStorage->Remove(it->Value()); break; } } } break; } ++it; } } if (removedIt) { this->ReinitializeInterpolation(); + this->Modified(); } return removedIt; } void mitk::SurfaceInterpolationController::AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel) { this->ReinitializeInterpolation(); auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot add active label contours. No valid segmentation selected."; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_ERROR << "Invalid time point requested for interpolation pipeline."; return; } const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); std::shared_lock<std::shared_mutex> guard(cpiMutex); auto& currentImageContours = cpiMap.at(selectedSegmentation); auto finding = currentImageContours.find(activeLabel); if (finding == currentImageContours.end()) { MITK_INFO << "Contours for label don't exist. Label value: " << activeLabel; return; } auto currentLabelContoursMap = finding->second; auto tsfinding = currentLabelContoursMap.find(currentTimeStep); if (tsfinding == currentLabelContoursMap.end()) { MITK_INFO << "Contours for current time step don't exist."; return; } CPIVector& currentContours = tsfinding->second; unsigned int index = 0; m_ReduceFilter->Reset(); for (const auto& cpi : currentContours) { - m_ReduceFilter->SetInput(index, cpi.Contour); - ++index; + if (!cpi.IsPlaceHolder()) + { + m_ReduceFilter->SetInput(index, cpi.Contour); + ++index; + } } } void mitk::SurfaceInterpolationController::Interpolate() { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot interpolate. No valid segmentation selected."; } if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_WARN << "No interpolation possible, currently selected timepoint is not in the time bounds of currently selected segmentation. Time point: " << m_CurrentTimePoint; m_InterpolationResult = nullptr; return; } const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); m_ReduceFilter->Update(); m_CurrentNumberOfReducedContours = m_ReduceFilter->GetNumberOfOutputs(); if (m_CurrentNumberOfReducedContours == 1) { vtkPolyData *tmp = m_ReduceFilter->GetOutput(0)->GetVtkPolyData(); if (tmp == nullptr) { m_CurrentNumberOfReducedContours = 0; } } // We use the timeSelector to get the segmentation image for the current segmentation. mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(selectedSegmentation); timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); m_NormalsFilter->SetSegmentationBinaryImage(refSegImage); for (size_t i = 0; i < m_CurrentNumberOfReducedContours; ++i) { mitk::Surface::Pointer reducedContour = m_ReduceFilter->GetOutput(i); reducedContour->DisconnectPipeline(); m_NormalsFilter->SetInput(i, reducedContour); m_InterpolateSurfaceFilter->SetInput(i, m_NormalsFilter->GetOutput(i)); } if (m_CurrentNumberOfReducedContours < 2) { // If no interpolation is possible reset the interpolation result MITK_INFO << "Interpolation impossible: not enough contours."; m_InterpolationResult = nullptr; return; } // Setting up progress bar mitk::ProgressBar::GetInstance()->AddStepsToDo(10); // create a surface from the distance-image mitk::ImageToSurfaceFilter::Pointer imageToSurfaceFilter = mitk::ImageToSurfaceFilter::New(); imageToSurfaceFilter->SetInput(m_InterpolateSurfaceFilter->GetOutput()); imageToSurfaceFilter->SetThreshold(0); imageToSurfaceFilter->SetSmooth(true); imageToSurfaceFilter->SetSmoothIteration(1); imageToSurfaceFilter->Update(); mitk::Surface::Pointer interpolationResult = mitk::Surface::New(); interpolationResult->Expand(selectedSegmentation->GetTimeSteps()); auto geometry = selectedSegmentation->GetTimeGeometry()->Clone(); geometry->ReplaceTimeStepGeometries(mitk::Geometry3D::New()); interpolationResult->SetTimeGeometry(geometry); interpolationResult->SetVtkPolyData(imageToSurfaceFilter->GetOutput()->GetVtkPolyData(), currentTimeStep); m_InterpolationResult = interpolationResult; m_DistanceImageSpacing = m_InterpolateSurfaceFilter->GetDistanceImageSpacing(); // Last progress step mitk::ProgressBar::GetInstance()->Progress(20); m_InterpolationResult->DisconnectPipeline(); } mitk::Surface::Pointer mitk::SurfaceInterpolationController::GetInterpolationResult() { return m_InterpolationResult; } void mitk::SurfaceInterpolationController::SetDataStorage(DataStorage::Pointer ds) { m_DataStorage = ds; } void mitk::SurfaceInterpolationController::SetMinSpacing(double minSpacing) { m_ReduceFilter->SetMinSpacing(minSpacing); } void mitk::SurfaceInterpolationController::SetMaxSpacing(double maxSpacing) { m_ReduceFilter->SetMaxSpacing(maxSpacing); m_NormalsFilter->SetMaxSpacing(maxSpacing); } void mitk::SurfaceInterpolationController::SetDistanceImageVolume(unsigned int distImgVolume) { m_InterpolateSurfaceFilter->SetDistanceImageVolume(distImgVolume); } mitk::LabelSetImage* mitk::SurfaceInterpolationController::GetCurrentSegmentation() { return m_SelectedSegmentation.Lock(); } mitk::Image *mitk::SurfaceInterpolationController::GetInterpolationImage() { return m_InterpolateSurfaceFilter->GetOutput(); } unsigned int mitk::SurfaceInterpolationController::GetNumberOfInterpolationSessions() { return cpiMap.size(); } template <typename TPixel, unsigned int VImageDimension> void mitk::SurfaceInterpolationController::GetImageBase(itk::Image<TPixel, VImageDimension> *input, itk::ImageBase<3>::Pointer &result) { result->Graft(input); } void mitk::SurfaceInterpolationController::SetCurrentInterpolationSession(mitk::LabelSetImage* currentSegmentationImage) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (currentSegmentationImage == selectedSegmentation) { return; } m_SelectedSegmentation = currentSegmentationImage; selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNotNull()) { std::lock_guard<std::shared_mutex> guard(cpiMutex); auto it = cpiMap.find(selectedSegmentation); if (it == cpiMap.end()) { cpiMap[selectedSegmentation] = CPITimeStepLabelMap(); auto command = itk::MemberCommand<SurfaceInterpolationController>::New(); command->SetCallbackFunction(this, &SurfaceInterpolationController::OnSegmentationDeleted); segmentationObserverTags[selectedSegmentation] = selectedSegmentation->AddObserver(itk::DeleteEvent(), command); selectedSegmentation->AddLabelRemovedListener(mitk::MessageDelegate1<SurfaceInterpolationController, mitk::LabelSetImage::LabelValueType>( this, &SurfaceInterpolationController::OnRemoveLabel)); } } m_InterpolationResult = nullptr; m_CurrentNumberOfReducedContours = 0; m_NormalsFilter->SetSegmentationBinaryImage(nullptr); this->ReinitializeInterpolation(); } -void mitk::SurfaceInterpolationController::RemoveInterpolationSession(mitk::LabelSetImage* segmentationImage) +void mitk::SurfaceInterpolationController::RemoveInterpolationSession(const mitk::LabelSetImage* segmentationImage) { if (nullptr != segmentationImage) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation == segmentationImage) { this->SetCurrentInterpolationSession(nullptr); } { std::lock_guard<std::shared_mutex> guard(cpiMutex); this->RemoveObserversInternal(segmentationImage); cpiMap.erase(segmentationImage); if (m_DataStorage.IsNotNull()) { auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, segmentationImage)); this->m_DataStorage->Remove(nodes); } } } } -void mitk::SurfaceInterpolationController::RemoveObserversInternal(mitk::LabelSetImage* segmentationImage) +void mitk::SurfaceInterpolationController::RemoveObserversInternal(const mitk::LabelSetImage* segmentationImage) { - auto pos = segmentationObserverTags.find(segmentationImage); + auto pos = segmentationObserverTags.find(const_cast<mitk::LabelSetImage*>(segmentationImage)); if (pos != segmentationObserverTags.end()) { - segmentationImage->RemoveObserver((*pos).second); + pos->first->RemoveObserver((*pos).second); segmentationImage->RemoveLabelRemovedListener(mitk::MessageDelegate1<SurfaceInterpolationController, mitk::LabelSetImage::LabelValueType>( this, &SurfaceInterpolationController::OnRemoveLabel)); - segmentationObserverTags.erase(segmentationImage); + segmentationObserverTags.erase(const_cast<mitk::LabelSetImage*>(segmentationImage)); } } void mitk::SurfaceInterpolationController::RemoveAllInterpolationSessions() { - this->RemoveObservers(); - m_NormalsFilter->SetSegmentationBinaryImage(nullptr); - m_SelectedSegmentation = nullptr; - - std::lock_guard<std::shared_mutex> guard(cpiMutex); - cpiMap.clear(); + while (!cpiMap.empty()) + { + this->RemoveInterpolationSession(cpiMap.begin()->first); + } } void mitk::SurfaceInterpolationController::RemoveContours(mitk::Label::PixelType label, TimeStepType timeStep) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot remove contours. No valid segmentation selected."; } std::lock_guard<std::shared_mutex> guard(cpiMutex); auto& cpiLabelMap = cpiMap[selectedSegmentation]; auto finding = cpiLabelMap.find(label); if (finding != cpiLabelMap.end()) { finding->second.erase(timeStep); } if (m_DataStorage.IsNotNull()) { //remove relevant plane nodes auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation), label, timeStep); this->m_DataStorage->Remove(nodes); } + this->Modified(); } void mitk::SurfaceInterpolationController::RemoveContours(mitk::Label::PixelType label) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNull()) { mitkThrow() << "Cannot remove contours. No valid segmentation selected."; } std::lock_guard<std::shared_mutex> guard(cpiMutex); cpiMap[selectedSegmentation].erase(label); if (m_DataStorage.IsNotNull()) { //remove relevant plane nodes auto nodes = this->GetPlaneGeometryNodeFromDataStorage(GetSegmentationImageNodeInternal(this->m_DataStorage, selectedSegmentation), label); this->m_DataStorage->Remove(nodes); } + this->Modified(); } void mitk::SurfaceInterpolationController::OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject & /*event*/) { auto tempImage = dynamic_cast<mitk::LabelSetImage *>(const_cast<itk::Object *>(caller)); if (tempImage) { this->RemoveInterpolationSession(tempImage); } } void mitk::SurfaceInterpolationController::ReinitializeInterpolation() { // If session has changed reset the pipeline m_ReduceFilter->Reset(); m_NormalsFilter->Reset(); m_InterpolateSurfaceFilter->Reset(); itk::ImageBase<3>::Pointer itkImage = itk::ImageBase<3>::New(); auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNotNull()) { if (!selectedSegmentation->GetTimeGeometry()->IsValidTimePoint(m_CurrentTimePoint)) { MITK_WARN << "Interpolation cannot be reinitialized. Currently selected timepoint is not in the time bounds of the currently selected segmentation. Time point: " << m_CurrentTimePoint; return; } const auto currentTimeStep = selectedSegmentation->GetTimeGeometry()->TimePointToTimeStep(m_CurrentTimePoint); // Set reference image for interpolation surface filter mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(selectedSegmentation); timeSelector->SetTimeNr(currentTimeStep); timeSelector->SetChannelNr(0); timeSelector->Update(); mitk::Image::Pointer refSegImage = timeSelector->GetOutput(); AccessFixedDimensionByItk_1(refSegImage, GetImageBase, 3, itkImage); m_InterpolateSurfaceFilter->SetReferenceImage(itkImage.GetPointer()); } } void mitk::SurfaceInterpolationController::OnRemoveLabel(mitk::Label::PixelType removedLabelValue) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation.IsNotNull()) { this->RemoveContours(removedLabelValue); } } mitk::SurfaceInterpolationController::CPIVector* mitk::SurfaceInterpolationController::GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation == nullptr) return nullptr; std::shared_lock<std::shared_mutex> guard(cpiMutex); auto labelFinding = cpiMap[selectedSegmentation].find(labelValue); if (labelFinding != cpiMap[selectedSegmentation].end()) { auto tsFinding = labelFinding->second.find(timeStep); if (tsFinding != labelFinding->second.end()) { return &(tsFinding->second); } } return nullptr; } std::vector<mitk::LabelSetImage::LabelValueType> mitk::SurfaceInterpolationController::GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const { std::lock_guard<std::shared_mutex> guard(cpiMutex); std::vector<mitk::LabelSetImage::LabelValueType> result; auto finding = cpiMap.find(seg); if (finding == cpiMap.end()) return result; auto currentImageContours = finding->second; for (auto [label, contours] : currentImageContours) { auto tsFinding = contours.find(timeStep); if (tsFinding != contours.end()) { auto cpis = tsFinding->second; auto finding = std::find_if(cpis.begin(), cpis.end(), [plane](const ContourPositionInformation& element) {return plane->IsOnPlane(element.Plane); }); if (finding != cpis.end()) { result.push_back(label); } } } return result; } void mitk::SurfaceInterpolationController::CompleteReinitialization(const std::vector<ContourPositionInformation>& newCPIs) { this->ClearInterpolationSession(); // Now the layers should be empty and the new layers can be added. this->AddNewContours(newCPIs, true); } void mitk::SurfaceInterpolationController::ClearInterpolationSession() { auto selectedSegmentation = m_SelectedSegmentation.Lock(); if (selectedSegmentation != nullptr) { std::lock_guard<std::shared_mutex> guard(cpiMutex); cpiMap[selectedSegmentation].clear(); } } diff --git a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h index 4f09448856..3a3e32c9ca 100644 --- a/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h +++ b/Modules/SurfaceInterpolation/mitkSurfaceInterpolationController.h @@ -1,289 +1,295 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSurfaceInterpolationController_h #define mitkSurfaceInterpolationController_h #include <mitkDataStorage.h> #include <mitkLabelSetImage.h> #include <mitkLabel.h> #include <mitkSurface.h> #include <MitkSurfaceInterpolationExports.h> namespace mitk { class ComputeContourSetNormalsFilter; class CreateDistanceImageFromSurfaceFilter; class LabelSetImage; class ReduceContourSetFilter; class MITKSURFACEINTERPOLATION_EXPORT SurfaceInterpolationController : public itk::Object { public: mitkClassMacroItkParent(SurfaceInterpolationController, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); itkGetMacro(DistanceImageSpacing, double); struct MITKSURFACEINTERPOLATION_EXPORT ContourPositionInformation { Surface::ConstPointer Contour; PlaneGeometry::ConstPointer Plane; Label::PixelType LabelValue; TimeStepType TimeStep; ContourPositionInformation() : Plane(nullptr), LabelValue(std::numeric_limits<Label::PixelType>::max()), TimeStep(std::numeric_limits<TimeStepType>::max()) { } + ContourPositionInformation(Surface::ConstPointer contour, PlaneGeometry::ConstPointer plane, Label::PixelType labelValue, TimeStepType timeStep) : Contour(contour), Plane(plane), LabelValue(labelValue), TimeStep(timeStep) { } + + bool IsPlaceHolder() const + { + return Contour.IsNull(); + } }; typedef std::vector<ContourPositionInformation> CPIVector; static SurfaceInterpolationController *GetInstance(); void SetCurrentTimePoint(TimePointType tp) { if (m_CurrentTimePoint != tp) { m_CurrentTimePoint = tp; if (m_SelectedSegmentation) { this->ReinitializeInterpolation(); } } }; TimePointType GetCurrentTimePoint() const { return m_CurrentTimePoint; }; /** * @brief Adds new extracted contours to the list. If one or more contours at a given position * already exist they will be updated respectively */ void AddNewContours(const std::vector<ContourPositionInformation>& newCPIs, bool reinitializeAction = false); /** * @brief Removes the contour for a given plane for the current selected segmenation * @param contourInfo the contour which should be removed * @return true if a contour was found and removed, false if no contour was found */ - bool RemoveContour(ContourPositionInformation contourInfo); + bool RemoveContour(ContourPositionInformation contourInfo, bool keepPlaceholderForUndo = false); /** * @brief Resets the pipeline for interpolation. The various filters used are reset. * */ void ReinitializeInterpolation(); void RemoveObservers(); /** * @brief Performs the interpolation. * */ void Interpolate(); /** * @brief Get the Result of the interpolation operation. * * @return mitk::Surface::Pointer */ mitk::Surface::Pointer GetInterpolationResult(); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface. * * @param minSpacing Paramter to set */ void SetMinSpacing(double minSpacing); /** * @brief Sets the minimum spacing of the current selected segmentation * This is needed since the contour points we reduced before they are used to interpolate the surface * @param maxSpacing Set the max Spacing for interpolation */ void SetMaxSpacing(double maxSpacing); /** * Sets the volume i.e. the number of pixels that the distance image should have * By evaluation we found out that 50.000 pixel delivers a good result */ void SetDistanceImageVolume(unsigned int distImageVolume); /** * @brief Get the current selected segmentation for which the interpolation is performed * @return the current segmentation image */ mitk::LabelSetImage* GetCurrentSegmentation(); void SetDataStorage(DataStorage::Pointer ds); /** * Sets the current list of contourpoints which is used for the surface interpolation * @param currentSegmentationImage The current selected segmentation */ void SetCurrentInterpolationSession(LabelSetImage* currentSegmentationImage); /** * @brief Remove interpolation session * @param segmentationImage the session to be removed */ - void RemoveInterpolationSession(LabelSetImage* segmentationImage); + void RemoveInterpolationSession(const LabelSetImage* segmentationImage); /** * @brief Removes all sessions */ void RemoveAllInterpolationSessions(); mitk::Image *GetInterpolationImage(); /** * @brief Get the Contours at a certain timeStep and layerID. * * @param timeStep Time Step from which to get the contours. * @param labelValue label from which to get the contours. * @return std::vector<ContourPositionInformation> Returns contours. */ CPIVector* GetContours(LabelSetImage::LabelValueType labelValue, TimeStepType timeStep); std::vector<LabelSetImage::LabelValueType> GetAffectedLabels(const LabelSetImage* seg, TimeStepType timeStep, const PlaneGeometry* plane) const; /** * @brief Trigerred with the "Reinit Interpolation" action. The contours are used to repopulate the * surfaceInterpolator data structures so that interpolation can be performed after reloading data. * * @param contourList List of contours extracted * @param contourPlanes List of planes at which the contours were extracted */ void CompleteReinitialization(const std::vector<ContourPositionInformation>& newCPIs); /** * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. * * @param label Label of contour to remove. * @param timeStep Time step in which to remove the contours. * @remark if the label or time step does not exist, nothing happens. */ void RemoveContours(mitk::Label::PixelType label, TimeStepType timeStep); /** * @brief Removes contours of a particular label and at a given time step for the current session/segmentation. * * @param label Label of contour to remove. * @param timeStep Time step in which to remove the contours. * @remark if the label or time step does not exist, nothing happens. */ void RemoveContours(mitk::Label::PixelType label); /** * Adds Contours from the active Label to the interpolation pipeline */ void AddActiveLabelContoursForInterpolation(mitk::Label::PixelType activeLabel); unsigned int GetNumberOfInterpolationSessions(); /** * @brief Get the Segmentation Image Node object * * @return DataNode* returns the DataNode containing the segmentation image. */ mitk::DataNode* GetSegmentationImageNode() const; protected: SurfaceInterpolationController(); ~SurfaceInterpolationController() override; template <typename TPixel, unsigned int VImageDimension> void GetImageBase(itk::Image<TPixel, VImageDimension> *input, itk::ImageBase<3>::Pointer &result); private: /** * @brief * * @param caller * @param event */ void OnSegmentationDeleted(const itk::Object *caller, const itk::EventObject &event); /** * @brief Function that removes contours of a particular label when the "Remove Label" event is trigerred in the labelSetImage. * */ void OnRemoveLabel(mitk::Label::PixelType removedLabelValue); /** * @brief When a new contour is added to the pipeline or an existing contour is replaced, * the plane geometry information of that contour is added as a child node to the * current node of the segmentation image. This is useful in the retrieval of contour information * when data is reloaded after saving. * * @param contourInfo contourInfo struct to add to data storage. */ void AddPlaneGeometryNodeToDataStorage(const ContourPositionInformation& contourInfo) const; DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode) const; DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue) const; DataStorage::SetOfObjects::ConstPointer GetPlaneGeometryNodeFromDataStorage(const DataNode* segNode, LabelSetImage::LabelValueType labelValue, TimeStepType timeStep) const; /** * @brief Clears the interpolation data structures. Called from CompleteReinitialization(). * */ void ClearInterpolationSession(); - void RemoveObserversInternal(mitk::LabelSetImage* segmentationImage); + void RemoveObserversInternal(const mitk::LabelSetImage* segmentationImage); /** * @brief Add contour to the interpolation pipeline * * @param contourInfo Contour information to be added * @param reinitializationAction If the contour is coming from a reinitialization process or not */ - void AddToInterpolationPipeline(ContourPositionInformation& contourInfo, bool reinitializationAction = false); + void AddToCPIMap(ContourPositionInformation& contourInfo, bool reinitializationAction = false); itk::SmartPointer<ReduceContourSetFilter> m_ReduceFilter; itk::SmartPointer<ComputeContourSetNormalsFilter> m_NormalsFilter; itk::SmartPointer<CreateDistanceImageFromSurfaceFilter> m_InterpolateSurfaceFilter; double m_DistanceImageSpacing; mitk::DataStorage::Pointer m_DataStorage; mitk::Surface::Pointer m_InterpolationResult; unsigned int m_CurrentNumberOfReducedContours; WeakPointer<LabelSetImage> m_SelectedSegmentation; mitk::TimePointType m_CurrentTimePoint; }; } #endif