diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp index c3ea18a797..134106e179 100755 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp @@ -1,238 +1,238 @@ /*============================================================================ 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, bool, bool) + 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, bool) + 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::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 7c567a697e..6fe5f072bd 100644 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h @@ -1,124 +1,116 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #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 - \param correctionForIpSegmentation adds 0.5 to x and y index coordinates (difference between ipSegmentation and - MITK contours) - \param constrainToInside */ static ContourModel::Pointer ProjectContourTo2DSlice(const Image *slice, - const ContourModel *contourIn3D, - bool correctionForIpSegmentation, - bool constrainToInside); + const ContourModel *contourIn3D); /** \brief Projects a slice index coordinates of a contour back into world coordinates. \param sliceGeometry \param contourIn2D - \param correctionForIpSegmentation subtracts 0.5 to x and y index coordinates (difference between ipSegmentation - and MITK contours) */ static ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, - const ContourModel *contourIn2D, - bool correctionForIpSegmentation = false); + 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. \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ 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. \pre sliceImage points to a valid instance \pre projectedContour points to a valid instance */ static void FillContourInSlice(const ContourModel *projectedContour, TimeStepType contourTimeStep, Image *sliceImage, const Image* workingImage, 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. */ 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/Segmentation/Algorithms/mitkContourModelSetToImageFilter.cpp b/Modules/Segmentation/Algorithms/mitkContourModelSetToImageFilter.cpp index 65cae86e50..3a2d7f7262 100644 --- a/Modules/Segmentation/Algorithms/mitkContourModelSetToImageFilter.cpp +++ b/Modules/Segmentation/Algorithms/mitkContourModelSetToImageFilter.cpp @@ -1,271 +1,271 @@ /*============================================================================ 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 "mitkContourModelSetToImageFilter.h" #include #include #include #include #include #include #include mitk::ContourModelSetToImageFilter::ContourModelSetToImageFilter() : m_MakeOutputBinary(true), m_TimeStep(0), m_ReferenceImage(nullptr) { // Create the output. itk::DataObject::Pointer output = this->MakeOutput(0); Superclass::SetNumberOfRequiredInputs(1); Superclass::SetNumberOfRequiredOutputs(1); Superclass::SetNthOutput(0, output); } mitk::ContourModelSetToImageFilter::~ContourModelSetToImageFilter() { } void mitk::ContourModelSetToImageFilter::GenerateInputRequestedRegion() { mitk::Image *output = this->GetOutput(); if ((output->IsInitialized() == false)) return; GenerateTimeInInputRegion(output, const_cast(m_ReferenceImage)); } void mitk::ContourModelSetToImageFilter::GenerateOutputInformation() { mitk::Image::Pointer output = this->GetOutput(); itkDebugMacro(<< "GenerateOutputInformation()"); if ((m_ReferenceImage == nullptr) || (m_ReferenceImage->IsInitialized() == false) || (m_ReferenceImage->GetTimeGeometry() == nullptr)) return; if (m_MakeOutputBinary) { output->Initialize(mitk::MakeScalarPixelType(), *m_ReferenceImage->GetTimeGeometry(), 1); } else { output->Initialize(m_ReferenceImage->GetPixelType(), *m_ReferenceImage->GetTimeGeometry()); } output->SetPropertyList(m_ReferenceImage->GetPropertyList()->Clone()); } itk::DataObject::Pointer mitk::ContourModelSetToImageFilter::MakeOutput(DataObjectPointerArraySizeType /*idx*/) { return OutputType::New().GetPointer(); } itk::DataObject::Pointer mitk::ContourModelSetToImageFilter::MakeOutput(const DataObjectIdentifierType &name) { itkDebugMacro("MakeOutput(" << name << ")"); if (this->IsIndexedOutputName(name)) { return this->MakeOutput(this->MakeIndexFromOutputName(name)); } return OutputType::New().GetPointer(); } const mitk::ContourModelSet *mitk::ContourModelSetToImageFilter::GetInput(void) { if (this->GetNumberOfInputs() < 1) { return nullptr; } return static_cast(this->ProcessObject::GetInput(0)); } void mitk::ContourModelSetToImageFilter::SetInput(const ContourModelSet *input) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput(0, const_cast(input)); } void mitk::ContourModelSetToImageFilter::SetImage(const mitk::Image *refImage) { m_ReferenceImage = refImage; } const mitk::Image *mitk::ContourModelSetToImageFilter::GetImage(void) { return m_ReferenceImage; } void mitk::ContourModelSetToImageFilter::GenerateData() { auto *contourSet = const_cast(this->GetInput()); // Initializing progressbar unsigned int num_contours = contourSet->GetContourModelList()->size(); mitk::ProgressBar::GetInstance()->AddStepsToDo(num_contours); // Assure that the volume data of the output is set (fill volume with zeros) this->InitializeOutputEmpty(); mitk::Image::Pointer outputImage = const_cast(this->GetOutput()); if (outputImage.IsNull() || outputImage->IsInitialized() == false || !outputImage->IsVolumeSet(m_TimeStep)) { mitkThrow() << "Error creating output for specified image!"; } if (!contourSet || contourSet->GetContourModelList()->size() == 0) { mitkThrow() << "No contours specified!"; } mitk::BaseGeometry *outputImageGeo = outputImage->GetGeometry(m_TimeStep); // Create mitkVtkImageOverwrite which is needed to write the slice back into the volume vtkSmartPointer reslice = vtkSmartPointer::New(); // Create ExtractSliceFilter for extracting the corresponding slices from the volume mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(outputImage); extractor->SetTimeStep(m_TimeStep); extractor->SetResliceTransformByGeometry(outputImageGeo); // Fill each contour of the contourmodelset into the image auto it = contourSet->Begin(); auto end = contourSet->End(); while (it != end) { mitk::ContourModel *contour = it->GetPointer(); // 1. Create slice geometry using the contour points mitk::PlaneGeometry::Pointer plane = mitk::PlaneGeometry::New(); mitk::Point3D point3D, tempPoint; mitk::Vector3D normal; mitk::Image::Pointer slice; int sliceIndex; bool isFrontside = true; bool isRotated = false; // Determine plane orientation point3D = contour->GetVertexAt(0)->Coordinates; tempPoint = contour->GetVertexAt(contour->GetNumberOfVertices() * 0.25)->Coordinates; mitk::Vector3D vec = point3D - tempPoint; vec.Normalize(); outputImageGeo->WorldToIndex(point3D, point3D); mitk::PlaneGeometry::PlaneOrientation orientation; if (mitk::Equal(vec[0], 0)) { orientation = mitk::PlaneGeometry::Sagittal; sliceIndex = point3D[0]; } else if (mitk::Equal(vec[1], 0)) { orientation = mitk::PlaneGeometry::Frontal; sliceIndex = point3D[1]; } else if (mitk::Equal(vec[2], 0)) { orientation = mitk::PlaneGeometry::Axial; sliceIndex = point3D[2]; } else { // TODO Maybe rotate geometry to extract slice? MITK_ERROR << "Cannot detect correct slice number! Only axial, sagittal and frontal oriented contours are supported!"; return; } // Initialize plane using the detected orientation plane->InitializeStandardPlane(outputImageGeo, orientation, sliceIndex, isFrontside, isRotated); point3D = plane->GetOrigin(); normal = plane->GetNormal(); normal.Normalize(); point3D += normal * 0.5; // pixelspacing is 1, so half the spacing is 0.5 plane->SetOrigin(point3D); // 2. Extract slice at the given position extractor->SetWorldGeometry(plane); extractor->SetVtkOutputRequest(false); reslice->SetOverwriteMode(false); extractor->Modified(); extractor->Update(); slice = extractor->GetOutput(); slice->DisconnectPipeline(); // 3. Fill contour into slice mitk::ContourModel::Pointer projectedContour = - mitk::ContourModelUtils::ProjectContourTo2DSlice(slice, contour, true, false); + mitk::ContourModelUtils::ProjectContourTo2DSlice(slice, contour); mitk::ContourModelUtils::FillContourInSlice(projectedContour, slice, outputImage); // 4. Write slice back into image volume reslice->SetInputSlice(slice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); extractor->Modified(); extractor->Update(); reslice->SetInputSlice(nullptr); // Progress mitk::ProgressBar::GetInstance()->Progress(); ++it; } outputImage->Modified(); outputImage->GetVtkImageData()->Modified(); } void mitk::ContourModelSetToImageFilter::InitializeOutputEmpty() { // Initialize the output's volume with zeros mitk::Image *output = this->GetOutput(); unsigned int byteSize = output->GetPixelType().GetSize(); if (output->GetDimension() < 4) { for (unsigned int dim = 0; dim < output->GetDimension(); ++dim) { byteSize *= output->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(output, output->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); } else { // if we have a time-resolved image we need to set memory to 0 for each time step for (unsigned int dim = 0; dim < 3; ++dim) { byteSize *= output->GetDimension(dim); } for (unsigned int volumeNumber = 0; volumeNumber < output->GetDimension(3); volumeNumber++) { mitk::ImageWriteAccessor writeAccess(output, output->GetVolumeData(volumeNumber)); memset(writeAccess.GetData(), 0, byteSize); } } } diff --git a/Modules/Segmentation/Algorithms/mitkContourUtils.cpp b/Modules/Segmentation/Algorithms/mitkContourUtils.cpp index 718ff2d18a..40eb9c05a0 100755 --- a/Modules/Segmentation/Algorithms/mitkContourUtils.cpp +++ b/Modules/Segmentation/Algorithms/mitkContourUtils.cpp @@ -1,71 +1,69 @@ /*============================================================================ 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 "mitkContourUtils.h" #include "mitkContourModelUtils.h" mitk::ContourUtils::ContourUtils() { } mitk::ContourUtils::~ContourUtils() { } mitk::ContourModel::Pointer mitk::ContourUtils::ProjectContourTo2DSlice(Image *slice, - Contour *contourIn3D, - bool itkNotUsed(correctionForIpSegmentation), - bool constrainToInside) + Contour *contourIn3D) { mitk::Contour::PointsContainerIterator it = contourIn3D->GetPoints()->Begin(); mitk::Contour::PointsContainerIterator end = contourIn3D->GetPoints()->End(); mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); while (it != end) { contour->AddVertex(it.Value()); it++; } - return mitk::ContourModelUtils::ProjectContourTo2DSlice(slice, contour, false /*not used*/, constrainToInside); + return mitk::ContourModelUtils::ProjectContourTo2DSlice(slice, contour); } mitk::ContourModel::Pointer mitk::ContourUtils::BackProjectContourFrom2DSlice( - const BaseGeometry *sliceGeometry, Contour *contourIn2D, bool itkNotUsed(correctionForIpSegmentation)) + const BaseGeometry *sliceGeometry, Contour *contourIn2D) { mitk::Contour::PointsContainerIterator it = contourIn2D->GetPoints()->Begin(); mitk::Contour::PointsContainerIterator end = contourIn2D->GetPoints()->End(); mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); while (it != end) { contour->AddVertex(it.Value()); it++; } - return mitk::ContourModelUtils::BackProjectContourFrom2DSlice(sliceGeometry, contour, false /*not used*/); + return mitk::ContourModelUtils::BackProjectContourFrom2DSlice(sliceGeometry, contour); } void mitk::ContourUtils::FillContourInSlice(Contour *projectedContour, Image *sliceImage, int paintingPixelValue) { mitk::Contour::PointsContainerIterator it = projectedContour->GetPoints()->Begin(); mitk::Contour::PointsContainerIterator end = projectedContour->GetPoints()->End(); mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); while (it != end) { contour->AddVertex(it.Value()); it++; } mitk::ContourModelUtils::FillContourInSlice(contour, sliceImage, sliceImage, paintingPixelValue); } diff --git a/Modules/Segmentation/Algorithms/mitkContourUtils.h b/Modules/Segmentation/Algorithms/mitkContourUtils.h index 66bb8b4a97..af5db548ee 100644 --- a/Modules/Segmentation/Algorithms/mitkContourUtils.h +++ b/Modules/Segmentation/Algorithms/mitkContourUtils.h @@ -1,73 +1,65 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkContourUtilshIncludett #define mitkContourUtilshIncludett #include "mitkContour.h" #include "mitkContourModel.h" #include "mitkImage.h" #include namespace mitk { /** * \brief Helpful methods for working with contours and images * * Legacy support for mitk::Contour * TODO remove this class when mitk::Contour is removed */ class MITKSEGMENTATION_EXPORT ContourUtils : public itk::Object { public: mitkClassMacroItkParent(ContourUtils, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** \brief Projects a contour onto an image point by point. Converts from world to index coordinates. \param slice \param contourIn3D - \param correctionForIpSegmentation adds 0.5 to x and y index coordinates (difference between ipSegmentation and - MITK contours) - \param constrainToInside */ ContourModel::Pointer ProjectContourTo2DSlice(Image *slice, - Contour *contourIn3D, - bool correctionForIpSegmentation, - bool constrainToInside); + Contour *contourIn3D); /** \brief Projects a slice index coordinates of a contour back into world coordinates. \param sliceGeometry \param contourIn2D - \param correctionForIpSegmentation subtracts 0.5 to x and y index coordinates (difference between - ipSegmentation and MITK contours) */ ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, - Contour *contourIn2D, - bool correctionForIpSegmentation = false); + Contour *contourIn2D); /** \brief Fill a contour in a 2D slice with a specified pixel value. */ void FillContourInSlice(Contour *projectedContour, Image *sliceImage, int paintingPixelValue = 1); protected: ContourUtils(); ~ContourUtils() override; }; } #endif diff --git a/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp b/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp index 7b5208ceb5..27a8beb883 100644 --- a/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp +++ b/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.cpp @@ -1,486 +1,486 @@ /*============================================================================ 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 "mitkCorrectorAlgorithm.h" #include "mitkContourUtils.h" #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageDataItem.h" #include #include "itkCastImageFilter.h" #include "itkImageDuplicator.h" #include "itkImageRegionIterator.h" mitk::CorrectorAlgorithm::CorrectorAlgorithm() : ImageToImageFilter(), m_FillColor(1), m_EraseColor(0) { } mitk::CorrectorAlgorithm::~CorrectorAlgorithm() { } template void ConvertBackToCorrectPixelType( itk::Image *, mitk::Image::Pointer target, itk::Image::Pointer segmentationPixelTypeImage) { typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::CastImageFilter CastImageFilterType; typename CastImageFilterType::Pointer castImageFilter = CastImageFilterType::New(); castImageFilter->SetInput(segmentationPixelTypeImage); castImageFilter->Update(); typename OutputImageType::Pointer tempItkImage = castImageFilter->GetOutput(); tempItkImage->DisconnectPipeline(); mitk::CastToMitkImage(tempItkImage, target); } void mitk::CorrectorAlgorithm::GenerateData() { Image::Pointer inputImage = ImageToImageFilter::GetInput(0); if (inputImage.IsNull() || inputImage->GetDimension() != 2) { itkExceptionMacro("CorrectorAlgorithm needs a 2D image as input."); } if (m_Contour.IsNull()) { itkExceptionMacro("CorrectorAlgorithm needs a Contour object as input."); } // copy the input (since m_WorkingImage will be changed later) m_WorkingImage = inputImage; TimeGeometry::Pointer originalGeometry = nullptr; if (inputImage->GetTimeGeometry()) { originalGeometry = inputImage->GetTimeGeometry()->Clone(); m_WorkingImage->SetTimeGeometry(originalGeometry); } else { itkExceptionMacro("Original image does not have a 'Time sliced geometry'! Cannot copy."); } Image::Pointer temporarySlice; // Convert to DefaultSegmentationDataType (because TobiasHeimannCorrectionAlgorithm relys on that data type) { itk::Image::Pointer correctPixelTypeImage; CastToItkImage(m_WorkingImage, correctPixelTypeImage); assert(correctPixelTypeImage.IsNotNull()); // possible bug in CastToItkImage ? // direction maxtrix is wrong/broken/not working after CastToItkImage, leading to a failed assertion in // mitk/Core/DataStructures/mitkSlicedGeometry3D.cpp, 479: // virtual void mitk::SlicedGeometry3D::SetSpacing(const mitk::Vector3D&): Assertion `aSpacing[0]>0 && aSpacing[1]>0 // && aSpacing[2]>0' failed // solution here: we overwrite it with an unity matrix itk::Image::DirectionType imageDirection; imageDirection.SetIdentity(); // correctPixelTypeImage->SetDirection(imageDirection); temporarySlice = this->GetOutput(); // temporarySlice = ImportItkImage( correctPixelTypeImage ); // m_FillColor = 1; m_EraseColor = 0; ImprovedHeimannCorrectionAlgorithm(correctPixelTypeImage); // this is suboptimal, needs to be kept synchronous to DefaultSegmentationDataType if (inputImage->GetChannelDescriptor().GetPixelType().GetComponentType() == itk::ImageIOBase::USHORT) { // the cast at the beginning did not copy the data CastToMitkImage(correctPixelTypeImage, temporarySlice); } else { // it did copy the data and cast the pixel type AccessByItk_n(m_WorkingImage, ConvertBackToCorrectPixelType, (temporarySlice, correctPixelTypeImage)); } } temporarySlice->SetTimeGeometry(originalGeometry); } template itk::Index<2> mitk::CorrectorAlgorithm::ensureIndexInImage(ScalarType i0, ScalarType i1) { itk::Index<2> toReturn; itk::Size<5> size = m_WorkingImage->GetLargestPossibleRegion().GetSize(); toReturn[0] = std::min((ScalarType)(size[0] - 1), std::max((ScalarType)0.0, i0)); toReturn[1] = std::min((ScalarType)(size[1] - 1), std::max((ScalarType)0.0, i1)); return toReturn; } bool mitk::CorrectorAlgorithm::ImprovedHeimannCorrectionAlgorithm( itk::Image::Pointer pic) { /*! Some documentation (not by the original author) TobiasHeimannCorrectionAlgorithm will be called, when the user has finished drawing a freehand line. There should be different results, depending on the line's properties: 1. Without any prior segmentation, the start point and the end point of the drawn line will be connected to a contour and the area enclosed by the contour will be marked as segmentation. 2. When the whole line is inside a segmentation, start and end point will be connected to a contour and the area of this contour will be subtracted from the segmentation. 3. When the line starts inside a segmentation and ends outside with only a single transition from segmentation to no-segmentation, nothing will happen. 4. When there are multiple transitions between inside-segmentation and outside-segmentation, the line will be divided in so called segments. Each segment is either fully inside or fully outside a segmentation. When it is inside a segmentation, its enclosed area will be subtracted from the segmentation. When the segment is outside a segmentation, its enclosed area it will be added to the segmentation. The algorithm is described in full length in Tobias Heimann's diploma thesis (MBI Technical Report 145, p. 37 - 40). */ ContourModel::Pointer projectedContour = - mitk::ContourModelUtils::ProjectContourTo2DSlice(m_WorkingImage, m_Contour, true, false); + mitk::ContourModelUtils::ProjectContourTo2DSlice(m_WorkingImage, m_Contour); if (projectedContour.IsNull() || projectedContour->GetNumberOfVertices() < 2) return false; // Read the first point of the contour auto contourIter = projectedContour->Begin(); if (contourIter == projectedContour->End()) return false; itk::Index<2> previousIndex; previousIndex = ensureIndexInImage((*contourIter)->Coordinates[0], (*contourIter)->Coordinates[1]); ++contourIter; int currentColor = (pic->GetPixel(previousIndex) == m_FillColor); TSegData currentSegment; int countOfSegments = 1; bool firstSegment = true; auto contourEnd = projectedContour->End(); for (; contourIter != contourEnd; ++contourIter) { // Get current point itk::Index<2> currentIndex; currentIndex = ensureIndexInImage((*contourIter)->Coordinates[0] + 0.5, (*contourIter)->Coordinates[1] + 0.5); // Calculate length and slope double slopeX = currentIndex[0] - previousIndex[0]; double slopeY = currentIndex[1] - previousIndex[1]; double length = std::sqrt(slopeX * slopeX + slopeY * slopeY); double deltaX = slopeX / length; double deltaY = slopeY / length; for (double i = 0; i <= length && length > 0; i += 1) { itk::Index<2> temporaryIndex; temporaryIndex = ensureIndexInImage(previousIndex[0] + deltaX * i, previousIndex[1] + deltaY * i); if (!pic->GetLargestPossibleRegion().IsInside(temporaryIndex)) continue; if ((pic->GetPixel(temporaryIndex) == m_FillColor) != currentColor) { currentSegment.points.push_back(temporaryIndex); if (!firstSegment) { ModifySegment(currentSegment, pic); } else { firstSegment = false; } currentSegment = TSegData(); ++countOfSegments; currentColor = (pic->GetPixel(temporaryIndex) == m_FillColor); } currentSegment.points.push_back(temporaryIndex); } previousIndex = currentIndex; } return true; } void mitk::CorrectorAlgorithm::ColorSegment( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic) { int colorMode = (pic->GetPixel(segment.points[0]) == m_FillColor); int color = 0; if (colorMode) color = m_EraseColor; else color = m_FillColor; std::vector>::const_iterator indexIterator; std::vector>::const_iterator indexEnd; indexIterator = segment.points.begin(); indexEnd = segment.points.end(); for (; indexIterator != indexEnd; ++indexIterator) { pic->SetPixel(*indexIterator, color); } } itk::Image::Pointer mitk::CorrectorAlgorithm::CloneImage( itk::Image::Pointer pic) { typedef itk::Image ItkImageType; typedef itk::ImageDuplicator DuplicatorType; DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage(pic); duplicator->Update(); return duplicator->GetOutput(); } itk::Index<2> mitk::CorrectorAlgorithm::GetFirstPoint( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic) { int colorMode = (pic->GetPixel(segment.points[0]) == m_FillColor); std::vector>::const_iterator indexIterator; std::vector>::const_iterator indexEnd; indexIterator = segment.points.begin(); indexEnd = segment.points.end(); itk::Index<2> index; for (; indexIterator != indexEnd; ++indexIterator) { for (int xOffset = -1; xOffset < 2; ++xOffset) { for (int yOffset = -1; yOffset < 2; ++yOffset) { index = ensureIndexInImage((*indexIterator)[0] - xOffset, (*indexIterator)[1] - yOffset); if ((pic->GetPixel(index) == m_FillColor) != colorMode) { return index; } } } } mitkThrow() << "No Starting point is found next to the curve."; } std::vector> mitk::CorrectorAlgorithm::FindSeedPoints( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic) { typedef itk::Image::Pointer ItkImagePointerType; std::vector> seedPoints; try { itk::Index<2> firstPoint = GetFirstPoint(segment, pic); seedPoints.push_back(firstPoint); } catch (const mitk::Exception&) { return seedPoints; } if (segment.points.size() < 4) return seedPoints; std::vector>::const_iterator indexIterator; std::vector>::const_iterator indexEnd; indexIterator = segment.points.begin(); indexEnd = segment.points.end(); ItkImagePointerType listOfPoints = CloneImage(pic); listOfPoints->FillBuffer(0); listOfPoints->SetPixel(seedPoints[0], 1); for (; indexIterator != indexEnd; ++indexIterator) { listOfPoints->SetPixel(*indexIterator, 2); } indexIterator = segment.points.begin(); indexIterator++; indexIterator++; indexEnd--; indexEnd--; for (; indexIterator != indexEnd; ++indexIterator) { bool pointFound = true; while (pointFound) { pointFound = false; itk::Index<2> index; itk::Index<2> index2; for (int xOffset = -1; xOffset < 2; ++xOffset) { for (int yOffset = -1; yOffset < 2; ++yOffset) { index = ensureIndexInImage((*indexIterator)[0] - xOffset, (*indexIterator)[1] - yOffset); index2 = index; if (listOfPoints->GetPixel(index2) > 0) continue; index[0] = index[0] - 1; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } index[0] = index[0] + 2; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } index[0] = index[0] - 1; index[1] = index[1] - 1; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } index[1] = index[1] + 2; index = ensureIndexInImage(index[0], index[1]); if (listOfPoints->GetPixel(index) == 1) { pointFound = true; seedPoints.push_back(index2); listOfPoints->SetPixel(index2, 1); continue; } } } } } return seedPoints; } int mitk::CorrectorAlgorithm::FillRegion( const std::vector> &seedPoints, itk::Image::Pointer pic) { int numberOfPixel = 0; int mode = (pic->GetPixel(seedPoints[0]) == m_FillColor); int drawColor = m_FillColor; if (mode) { drawColor = m_EraseColor; } std::vector> workPoints; workPoints = seedPoints; // workPoints.push_back(seedPoints[0]); while (workPoints.size() > 0) { itk::Index<2> currentIndex = workPoints.back(); workPoints.pop_back(); if ((pic->GetPixel(currentIndex) == m_FillColor) == mode) ++numberOfPixel; pic->SetPixel(currentIndex, drawColor); currentIndex = ensureIndexInImage(currentIndex[0] - 1, currentIndex[1]); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); currentIndex = ensureIndexInImage(currentIndex[0] + 2, currentIndex[1]); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); currentIndex = ensureIndexInImage(currentIndex[0] - 1, currentIndex[1] - 1); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); currentIndex = ensureIndexInImage(currentIndex[0], currentIndex[1] + 2); if (pic->GetLargestPossibleRegion().IsInside(currentIndex) && (pic->GetPixel(currentIndex) == m_FillColor) == mode) workPoints.push_back(currentIndex); } return numberOfPixel; } void mitk::CorrectorAlgorithm::OverwriteImage( itk::Image::Pointer source, itk::Image::Pointer target) { typedef itk::Image ItkImageType; typedef itk::ImageRegionIterator ImageIteratorType; ImageIteratorType sourceIter(source, source->GetLargestPossibleRegion()); ImageIteratorType targetIter(target, target->GetLargestPossibleRegion()); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } bool mitk::CorrectorAlgorithm::ModifySegment(const TSegData &segment, itk::Image::Pointer pic) { typedef itk::Image::Pointer ItkImagePointerType; ItkImagePointerType firstSideImage = CloneImage(pic); ColorSegment(segment, firstSideImage); ItkImagePointerType secondSideImage = CloneImage(firstSideImage); std::vector> seedPoints = FindSeedPoints(segment, firstSideImage); if (seedPoints.size() < 1) return false; int firstSidePixel = FillRegion(seedPoints, firstSideImage); std::vector> secondSeedPoints = FindSeedPoints(segment, firstSideImage); if (secondSeedPoints.size() < 1) return false; int secondSidePixel = FillRegion(secondSeedPoints, secondSideImage); if (firstSidePixel < secondSidePixel) { OverwriteImage(firstSideImage, pic); } else { OverwriteImage(secondSideImage, pic); } return true; } diff --git a/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.h b/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.h index ae3eba97a3..0223978c6b 100644 --- a/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.h +++ b/Modules/Segmentation/Algorithms/mitkCorrectorAlgorithm.h @@ -1,109 +1,108 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkCorrectorAlgorithmhIncluded #define mitkCorrectorAlgorithmhIncluded -#include "ipSegmentation.h" #include "mitkContourModel.h" #include "mitkImageToImageFilter.h" #include #include #include #define multilabelSegmentationType unsigned short namespace mitk { /** * This class encapsulates an algorithm, which takes a 2D binary image and a contour. * The algorithm tests if the line begins and ends inside or outside a segmentation * and whether areas should be added to or subtracted from the segmentation shape. * * This class has two outputs: * \li the modified input image from GetOutput() * * The output image is a combination of the original input with the generated difference image. * * \sa CorrectorTool2D */ class MITKSEGMENTATION_EXPORT CorrectorAlgorithm : public ImageToImageFilter { public: mitkClassMacro(CorrectorAlgorithm, ImageToImageFilter); itkFactorylessNewMacro(Self); itkCloneMacro(Self); typedef mitk::Label::PixelType DefaultSegmentationDataType; /** * \brief User drawn contour */ void SetContour(ContourModel *contour) { this->m_Contour = contour; } itkSetMacro(FillColor, int); itkGetConstMacro(FillColor, int); itkSetMacro(EraseColor, int); itkGetConstMacro(EraseColor, int); /** * \brief Calculated difference image. */ // itkGetObjectMacro(DifferenceImage, Image); // used by TobiasHeimannCorrectionAlgorithm typedef struct { int lineStart; int lineEnd; bool modified; std::vector> points; } TSegData; protected: CorrectorAlgorithm(); ~CorrectorAlgorithm() override; // does the actual processing void GenerateData() override; bool ImprovedHeimannCorrectionAlgorithm(itk::Image::Pointer pic); bool ModifySegment(const TSegData &segment, itk::Image::Pointer pic); Image::Pointer m_WorkingImage; ContourModel::Pointer m_Contour; Image::Pointer m_DifferenceImage; int m_FillColor; int m_EraseColor; private: template itk::Index<2> ensureIndexInImage(ScalarType i0, ScalarType i1); void ColorSegment(const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic); itk::Image::Pointer CloneImage( itk::Image::Pointer pic); itk::Index<2> GetFirstPoint(const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic); std::vector> FindSeedPoints( const mitk::CorrectorAlgorithm::TSegData &segment, itk::Image::Pointer pic); int FillRegion(const std::vector> &seedPoints, itk::Image::Pointer pic); void OverwriteImage(itk::Image::Pointer source, itk::Image::Pointer target); }; } #endif diff --git a/Modules/Segmentation/CMakeLists.txt b/Modules/Segmentation/CMakeLists.txt index b73d0ba055..9c77de4682 100644 --- a/Modules/Segmentation/CMakeLists.txt +++ b/Modules/Segmentation/CMakeLists.txt @@ -1,9 +1,9 @@ mitk_create_module( INCLUDE_DIRS Algorithms Controllers DataManagement Interactions Rendering SegmentationUtilities/BooleanOperations SegmentationUtilities/MorphologicalOperations - DEPENDS MitkAlgorithmsExt MitkIpSegmentation MitkIpFunc MitkSurfaceInterpolation MitkGraphAlgorithms MitkContourModel MitkMultilabel + DEPENDS MitkAlgorithmsExt MitkSurfaceInterpolation MitkGraphAlgorithms MitkContourModel MitkMultilabel PACKAGE_DEPENDS PUBLIC ITK|QuadEdgeMesh PRIVATE ITK|LabelMap+Watersheds VTK|ImagingGeneral ) add_subdirectory(Testing) diff --git a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp index e37f014527..ba1be71de4 100644 --- a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp @@ -1,278 +1,272 @@ /*============================================================================ 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 "mitkFeedbackContourTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkStringProperty.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include "mitkAbstractTransformGeometry.h" mitk::FeedbackContourTool::FeedbackContourTool(const char *type) : SegTool2D(type), m_FeedbackContourVisible(false) { m_FeedbackContourNode = DataNode::New(); m_FeedbackContourNode->SetProperty("name", StringProperty::New("One of FeedbackContourTool's feedback nodes")); m_FeedbackContourNode->SetProperty("visible", BoolProperty::New(true)); m_FeedbackContourNode->SetProperty("helper object", BoolProperty::New(true)); m_FeedbackContourNode->SetProperty("layer", IntProperty::New(1000)); m_FeedbackContourNode->SetProperty("contour.project-onto-plane", BoolProperty::New(false)); m_FeedbackContourNode->SetProperty("contour.width", FloatProperty::New(1.0)); // set to max short, max int doesn't work, max value somewhere hardcoded? m_FeedbackContourNode->SetProperty("layer", IntProperty::New(std::numeric_limits::max())); m_FeedbackContourNode->SetProperty("fixedLayer", BoolProperty::New(true)); this->InitializeFeedbackContour(true); SetFeedbackContourColorDefault(); } mitk::FeedbackContourTool::~FeedbackContourTool() { } void mitk::FeedbackContourTool::SetFeedbackContourColor(float r, float g, float b) { m_FeedbackContourNode->SetProperty("contour.color", ColorProperty::New(r, g, b)); } void mitk::FeedbackContourTool::SetFeedbackContourColorDefault() { m_FeedbackContourNode->SetProperty("contour.color", ColorProperty::New(0.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0)); } void mitk::FeedbackContourTool::Deactivated() { Superclass::Deactivated(); DataStorage *storage = this->GetToolManager()->GetDataStorage(); if (storage && m_FeedbackContourNode.IsNotNull()) { storage->Remove(m_FeedbackContourNode); m_FeedbackContour->Clear(); SetFeedbackContourVisible(false); } } void mitk::FeedbackContourTool::Activated() { Superclass::Activated(); this->InitializeFeedbackContour(true); this->SetFeedbackContourVisible(true); } const mitk::ContourModel *mitk::FeedbackContourTool::GetFeedbackContour() const { return m_FeedbackContour; } void mitk::FeedbackContourTool::InitializeFeedbackContour(bool isClosed) { m_FeedbackContour = ContourModel::New(); m_FeedbackContour->SetClosed(isClosed); auto workingImage = this->GetWorkingData(); if (nullptr != workingImage) { m_FeedbackContour->Expand(workingImage->GetTimeSteps()); auto contourTimeGeometry = workingImage->GetTimeGeometry()->Clone(); contourTimeGeometry->ReplaceTimeStepGeometries(m_FeedbackContour->GetGeometry()); m_FeedbackContour->SetTimeGeometry(contourTimeGeometry); for (unsigned int t = 0; t < m_FeedbackContour->GetTimeSteps(); ++t) { m_FeedbackContour->SetClosed(isClosed, t); } } m_FeedbackContourNode->SetData(m_FeedbackContour); } void mitk::FeedbackContourTool::ClearsCurrentFeedbackContour(bool isClosed) { if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) { MITK_WARN << "Cannot clear feedback contour at current time step. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: " << this->GetLastTimePointTriggered(); return; } auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); m_FeedbackContour->Clear(feedbackTimeStep); m_FeedbackContour->SetClosed(isClosed, feedbackTimeStep); } void mitk::FeedbackContourTool::UpdateCurrentFeedbackContour(const ContourModel* sourceModel, TimeStepType sourceTimeStep) { if (nullptr == sourceModel) return; if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) { MITK_WARN << "Cannot update feedback contour. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: "<GetLastTimePointTriggered(); return; } auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); this->UpdateFeedbackContour(sourceModel, feedbackTimeStep, sourceTimeStep); } void mitk::FeedbackContourTool::UpdateFeedbackContour(const ContourModel* sourceModel, TimeStepType feedbackTimeStep, TimeStepType sourceTimeStep) { if (nullptr == sourceModel) return; if (!sourceModel->GetTimeGeometry()->IsValidTimeStep(sourceTimeStep)) { MITK_WARN << "Cannot update feedback contour. Source contour time geometry does not support passed time step. Invalid time step: " << sourceTimeStep; return; } if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimeStep(feedbackTimeStep)) { MITK_WARN << "Cannot update feedback contour. Feedback contour time geometry does not support passed time step. Invalid time step: "<UpdateContour(sourceModel, feedbackTimeStep, sourceTimeStep); } void mitk::FeedbackContourTool::AddVertexToCurrentFeedbackContour(const Point3D& point) { if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) { MITK_WARN << "Cannot add vertex to feedback contour. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: " << this->GetLastTimePointTriggered(); return; } auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); this->AddVertexToFeedbackContour(point, feedbackTimeStep); }; /** Adds a vertex to the feedback contour for the passed time step. If time step is invalid, nothing will be added.*/ void mitk::FeedbackContourTool::AddVertexToFeedbackContour(const Point3D& point, TimeStepType feedbackTimeStep) { if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimeStep(feedbackTimeStep)) { MITK_WARN << "Cannot add vertex to feedback contour. Feedback contour time geometry does not support passed time step. Invalid time step: " << feedbackTimeStep; return; } m_FeedbackContour->AddVertex(point, feedbackTimeStep); } void mitk::FeedbackContourTool::SetFeedbackContourVisible(bool visible) { if (m_FeedbackContourVisible == visible) return; // nothing changed if (DataStorage *storage = this->GetToolManager()->GetDataStorage()) { if (visible) { // Add the feedback contour node as a derived node of the first working data. // If there is no working data, the node is added at the top level. storage->Add(m_FeedbackContourNode, this->GetWorkingDataNode()); } else { storage->Remove(m_FeedbackContourNode); } } m_FeedbackContourVisible = visible; } mitk::ContourModel::Pointer mitk::FeedbackContourTool::ProjectContourTo2DSlice(const Image *slice, - const ContourModel *contourIn3D, - bool correctionForIpSegmentation, - bool constrainToInside) + const ContourModel *contourIn3D) { - return mitk::ContourModelUtils::ProjectContourTo2DSlice( - slice, contourIn3D, correctionForIpSegmentation, constrainToInside); + return mitk::ContourModelUtils::ProjectContourTo2DSlice(slice, contourIn3D); } mitk::ContourModel::Pointer mitk::FeedbackContourTool::BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, - const ContourModel *contourIn2D, - bool correctionForIpSegmentation) + const ContourModel *contourIn2D) { - return mitk::ContourModelUtils::BackProjectContourFrom2DSlice( - sliceGeometry, contourIn2D, correctionForIpSegmentation); + return mitk::ContourModelUtils::BackProjectContourFrom2DSlice(sliceGeometry, contourIn2D); } void mitk::FeedbackContourTool::WriteBackFeedbackContourAsSegmentationResult(const InteractionPositionEvent* positionEvent, int paintingPixelValue, bool setInvisibleAfterSuccess) { if (!positionEvent) return; auto workingImage = this->GetWorkingData(); const auto planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (!workingImage || !planeGeometry) return; const auto* abstractTransformGeometry( dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (nullptr != abstractTransformGeometry) return; Image::Pointer slice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); if (slice.IsNull()) { MITK_ERROR << "Unable to extract slice." << std::endl; return; } const auto feedbackContour = FeedbackContourTool::GetFeedbackContour(); auto contourTimeStep = positionEvent->GetSender()->GetTimeStep(feedbackContour); ContourModel::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( - slice, feedbackContour, false, false); // false: don't add 0.5 (done by FillContourInSlice) - // false: don't constrain the contour to the image's inside + slice, feedbackContour); if (projectedContour.IsNull()) return; auto activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); mitk::ContourModelUtils::FillContourInSlice( projectedContour, contourTimeStep, slice, workingImage, paintingPixelValue * activePixelValue); this->WriteBackSegmentationResult(positionEvent, slice); if (setInvisibleAfterSuccess) { this->SetFeedbackContourVisible(false); } } void mitk::FeedbackContourTool::FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, int paintingPixelValue) { this->FillContourInSlice(projectedContour, 0, sliceImage, paintingPixelValue); } void mitk::FeedbackContourTool::FillContourInSlice(ContourModel *projectedContour, unsigned int timeStep, Image *sliceImage, int paintingPixelValue) { mitk::ContourModelUtils::FillContourInSlice(projectedContour, timeStep, sliceImage, sliceImage, paintingPixelValue); } diff --git a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h index 60c6b60f34..cac430741d 100644 --- a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h +++ b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h @@ -1,149 +1,141 @@ /*============================================================================ 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 mitkFeedbackContourTool_h_Included #define mitkFeedbackContourTool_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 use a contour for feedback \sa Tool \sa ContourModel \ingroup Interaction \ingroup ToolManagerEtAl Implements helper methods, that might be of use to all kind of 2D segmentation tools that use a contour for user feedback. - Providing a feedback contour that might be added or removed from the visible scene (SetFeedbackContourVisible). - Filling of a contour into a 2D slice These helper methods are actually implemented in ContourUtils now. FeedbackContourTool only forwards such requests. \warning Only to be instantiated by mitk::ToolManager. $Author: nolden $ */ class MITKSEGMENTATION_EXPORT FeedbackContourTool : public SegTool2D { public: mitkClassMacro(FeedbackContourTool, SegTool2D); protected: FeedbackContourTool(); // purposely hidden FeedbackContourTool(const char *); // purposely hidden ~FeedbackContourTool() override; const ContourModel *GetFeedbackContour() const; /** (Re)initialize the feesback contour by creating a new instance. * It is assured that the new instance as the same time geometry than * the working image.*/ void InitializeFeedbackContour(bool isClosed); /** Clears the current time step of the feedback contour and resets its closed state.*/ void ClearsCurrentFeedbackContour(bool isClosed); /** Updates the feedback contour of the currently selected time point. The update will be done * by clearing all existings vertices at the current time point and copying the vertics of the * source model at the specified source time step.*/ void UpdateCurrentFeedbackContour(const ContourModel* sourceModel, TimeStepType sourceTimeStep = 0); /** Updates the feedback contour at the time step specified by feedbackTimeStep. The update will be done * by clearing all existings vertices at feedbackTimeStep and copying the vertics of the * source model at the specified source time step.*/ void UpdateFeedbackContour(const ContourModel* sourceModel, TimeStepType feedbackTimeStep, TimeStepType sourceTimeStep = 0); /** Adds a vertex to the feedback contour for the current time point. */ void AddVertexToCurrentFeedbackContour(const Point3D& point); /** Adds a vertex to the feedback contour for the passed time step. If time step is invalid, nothing will be added.*/ void AddVertexToFeedbackContour(const Point3D& point, TimeStepType feedbackTimeStep); void SetFeedbackContourVisible(bool); /// Provide values from 0.0 (black) to 1.0 (full color) void SetFeedbackContourColor(float r, float g, float b); void SetFeedbackContourColorDefault(); void Deactivated() override; void Activated() override; /** \brief Projects a contour onto an image point by point. Converts from world to index coordinates. \param slice \param contourIn3D - \param correctionForIpSegmentation adds 0.5 to x and y index coordinates (difference between ipSegmentation and - MITK contours) - \param constrainToInside */ ContourModel::Pointer ProjectContourTo2DSlice(const Image *slice, - const ContourModel *contourIn3D, - bool correctionForIpSegmentation = false, - bool constrainToInside = true); + const ContourModel *contourIn3D); /** \brief Projects a slice index coordinates of a contour back into world coordinates. \param sliceGeometry \param contourIn2D - \param correctionForIpSegmentation subtracts 0.5 to x and y index coordinates (difference between ipSegmentation - and MITK contours) */ ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, - const ContourModel *contourIn2D, - bool correctionForIpSegmentation = false); + const ContourModel *contourIn2D); /** Helper methods that checks all precondition and if they are fullfilled does the following: * 1. Gets the contour of the time point specified by positionEvent. * 2. Gets the affacted working slice of the time point specified by positionEvent. * 3. projects the contour onto the working slice and then fills it with the passed paintingPixelValue (adjusted by the current active lable value) * to the slice. * 4. writes the slice back into the working image using SegTool2D::WriteBackSegmentationResult().*/ void WriteBackFeedbackContourAsSegmentationResult(const InteractionPositionEvent* positionEvent, int paintingPixelValue, bool setInvisibleAfterSuccess = true); /** \brief Fill a contour in a 2D slice with a specified pixel value. */ void FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, int paintingPixelValue = 1); /** \brief Fill a contour in a 2D slice with a specified pixel value at a given time step. */ void FillContourInSlice(ContourModel *projectedContour, unsigned int timeStep, Image *sliceImage, int paintingPixelValue = 1); private: ContourModel::Pointer m_FeedbackContour; DataNode::Pointer m_FeedbackContourNode; bool m_FeedbackContourVisible; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp index 9ba41907cd..49c56b305e 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -1,620 +1,620 @@ /*============================================================================ 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() : SegTool2D("LiveWireTool"), m_CreateAndUseDynamicCosts(false) { } mitk::LiveWireTool2D::~LiveWireTool2D() { this->ClearSegmentation(); } void mitk::LiveWireTool2D::RemoveHelperObjects() { auto dataStorage = this->GetToolManager()->GetDataStorage(); if (nullptr == dataStorage) return; for (const auto &editingContour : m_EditingContours) dataStorage->Remove(editingContour.first); for (const auto &workingContour : m_WorkingContours) dataStorage->Remove(workingContour.first); if (m_EditingContourNode.IsNotNull()) dataStorage->Remove(m_EditingContourNode); if (m_LiveWireContourNode.IsNotNull()) dataStorage->Remove(m_LiveWireContourNode); if (m_ContourNode.IsNotNull()) dataStorage->Remove(m_ContourNode); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::LiveWireTool2D::ReleaseHelperObjects() { this->RemoveHelperObjects(); m_EditingContours.clear(); m_WorkingContours.clear(); m_EditingContourNode = nullptr; m_EditingContour = nullptr; m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; m_ContourNode = nullptr; m_Contour = nullptr; } void mitk::LiveWireTool2D::ReleaseInteractors() { this->EnableContourLiveWireInteraction(false); m_LiveWireInteractors.clear(); } void mitk::LiveWireTool2D::ConnectActionsAndFunctions() { CONNECT_CONDITION("CheckContourClosed", OnCheckPoint); CONNECT_FUNCTION("InitObject", OnInitLiveWire); CONNECT_FUNCTION("AddPoint", OnAddPoint); CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); CONNECT_FUNCTION("MovePoint", OnMouseMoveNoDynamicCosts); CONNECT_FUNCTION("FinishContour", OnFinish); CONNECT_FUNCTION("DeletePoint", OnLastSegmentDelete); CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); } const char **mitk::LiveWireTool2D::GetXPM() const { return mitkLiveWireTool2D_xpm; } us::ModuleResource mitk::LiveWireTool2D::GetIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_48x48.png"); } us::ModuleResource mitk::LiveWireTool2D::GetCursorIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_Cursor_32x32.png"); } const char *mitk::LiveWireTool2D::GetName() const { return "Live Wire"; } void mitk::LiveWireTool2D::Activated() { Superclass::Activated(); this->ResetToStartState(); this->EnableContourLiveWireInteraction(true); } void mitk::LiveWireTool2D::Deactivated() { this->ConfirmSegmentation(); Superclass::Deactivated(); } void mitk::LiveWireTool2D::UpdateLiveWireContour() { if (m_Contour.IsNotNull()) { auto timeGeometry = m_Contour->GetTimeGeometry()->Clone(); m_LiveWireContour = this->m_LiveWireFilter->GetOutput(); m_LiveWireContour->SetTimeGeometry(timeGeometry); //needed because the results of the filter are always from 0 ms to 1 ms and the filter also resets its outputs. m_LiveWireContourNode->SetData(this->m_LiveWireContour); } } void mitk::LiveWireTool2D::OnTimePointChanged() { auto reference = this->GetReferenceData(); if (nullptr == reference || m_PlaneGeometry.IsNull() || m_LiveWireFilter.IsNull() || m_LiveWireContourNode.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(); }; void mitk::LiveWireTool2D::EnableContourLiveWireInteraction(bool on) { for (const auto &interactor : m_LiveWireInteractors) interactor->EnableInteraction(on); } void mitk::LiveWireTool2D::ConfirmSegmentation() { auto referenceImage = this->GetReferenceData(); auto workingImage = this->GetWorkingData(); if (nullptr != referenceImage && nullptr != workingImage) { std::vector sliceInfos; sliceInfos.reserve(m_WorkingContours.size()); const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); for (const auto &workingContour : m_WorkingContours) { auto contour = dynamic_cast(workingContour.first->GetData()); if (nullptr == contour || contour->IsEmpty()) continue; auto sameSlicePredicate = [&workingContour, workingImageTimeStep](const SliceInformation& si) { return workingContour.second->IsOnPlane(si.plane) && workingImageTimeStep == si.timestep; }; auto finding = std::find_if(sliceInfos.begin(), sliceInfos.end(), sameSlicePredicate); if (finding == sliceInfos.end()) { auto workingSlice = this->GetAffectedImageSliceAs2DImage(workingContour.second, workingImage, workingImageTimeStep)->Clone(); sliceInfos.emplace_back(workingSlice, workingContour.second, workingImageTimeStep); finding = std::prev(sliceInfos.end()); } //cast const away is OK in this case, because these are all slices created and manipulated //localy in this function call. And we want to keep the high constness of SliceInformation for //public interfaces. auto workingSlice = const_cast(finding->slice.GetPointer()); - auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour, true, false); + auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); ContourModelUtils::FillContourInSlice( projectedContour, workingSlice, workingImage, activePixelValue); } this->WriteBackSegmentationResults(sliceInfos); } this->ClearSegmentation(); } void mitk::LiveWireTool2D::ClearSegmentation() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); this->ResetToStartState(); } bool mitk::LiveWireTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent *positionEvent, mitk::BaseData *data) { bool isPositionEventInsideImageRegion = nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); if (!isPositionEventInsideImageRegion) MITK_WARN("LiveWireTool2D") << "PositionEvent is outside ImageRegion!"; return isPositionEventInsideImageRegion; } mitk::ContourModel::Pointer mitk::LiveWireTool2D::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::LiveWireTool2D::OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; auto workingDataNode = this->GetWorkingDataNode(); if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) { this->ResetToStartState(); return; } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_Contour = this->CreateNewContour(); m_ContourNode = mitk::DataNode::New(); m_ContourNode->SetData(m_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_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode = mitk::DataNode::New(); m_LiveWireContourNode->SetData(m_LiveWireContour); m_LiveWireContourNode->SetName("active livewire node"); m_LiveWireContourNode->SetProperty("layer", IntProperty::New(101)); m_LiveWireContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_LiveWireContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_LiveWireContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_LiveWireContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); m_EditingContour = this->CreateNewContour(); m_EditingContourNode = mitk::DataNode::New(); m_EditingContourNode->SetData(m_EditingContour); m_EditingContourNode->SetName("editing node"); m_EditingContourNode->SetProperty("layer", IntProperty::New(102)); m_EditingContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_EditingContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_EditingContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_EditingContourNode->AddProperty("contour.points.color", ColorProperty::New(0.0f, 0.0f, 1.0f), nullptr, true); m_EditingContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); auto dataStorage = this->GetToolManager()->GetDataStorage(); dataStorage->Add(m_ContourNode, workingDataNode); dataStorage->Add(m_LiveWireContourNode, workingDataNode); dataStorage->Add(m_EditingContourNode, workingDataNode); // Set current slice as input for ImageToLiveWireContourFilter 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); m_LiveWireFilter = ImageLiveWireContourModelFilter::New(); m_LiveWireFilter->SetInput(m_ReferenceDataSlice); // Map click to pixel coordinates auto click = positionEvent->GetPositionInWorld(); itk::Index<3> idx; m_ReferenceDataSlice->GetGeometry()->WorldToIndex(click, idx); // Get the pixel with the highest gradient in a 7x7 region itk::Index<3> indexWithHighestGradient; AccessFixedDimensionByItk_2(m_ReferenceDataSlice, FindHighestGradientMagnitudeByITK, 2, idx, indexWithHighestGradient); click[0] = indexWithHighestGradient[0]; click[1] = indexWithHighestGradient[1]; click[2] = indexWithHighestGradient[2]; m_ReferenceDataSlice->GetGeometry()->IndexToWorld(click, click); // Set initial start point m_Contour->AddVertex(click, true); m_LiveWireFilter->SetStartPoint(click); // Remember PlaneGeometry to determine if events were triggered in the same plane m_PlaneGeometry = interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); m_CreateAndUseDynamicCosts = true; mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { // Complete LiveWire interaction for the last segment. Add current LiveWire contour to // the finished contour and reset to start a new segment and computation. 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; } // Add repulsive points to avoid getting the same path again std::for_each(m_LiveWireContour->IteratorBegin(), m_LiveWireContour->IteratorEnd(), [this](ContourElement::VertexType *vertex) { ImageLiveWireContourModelFilter::InternalImageType::IndexType idx; this->m_ReferenceDataSlice->GetGeometry()->WorldToIndex(vertex->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); }); // Remove duplicate first vertex, it's already contained in m_Contour m_LiveWireContour->RemoveVertexAt(0); // Set last point as control point m_LiveWireContour->SetControlVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1); // Merge contours m_Contour->Concatenate(m_LiveWireContour); // Clear the LiveWire contour and reset the corresponding DataNode m_LiveWireContour->Clear(); // Set new start point m_LiveWireFilter->SetStartPoint(positionEvent->GetPositionInWorld()); if (m_CreateAndUseDynamicCosts) { // Use dynamic cost map for next update m_LiveWireFilter->CreateDynamicCostMap(m_Contour); m_LiveWireFilter->SetUseDynamicCostMap(true); } mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { // Compute LiveWire segment from last control point to current mouse position auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; m_LiveWireFilter->SetEndPoint(positionEvent->GetPositionInWorld()); m_LiveWireFilter->Update(); this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) { m_LiveWireFilter->SetUseDynamicCostMap(false); this->OnMouseMoved(nullptr, interactionEvent); m_LiveWireFilter->SetUseDynamicCostMap(true); } bool mitk::LiveWireTool2D::OnCheckPoint(const InteractionEvent *interactionEvent) { // Check double click on first control point to finish the LiveWire tool auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return false; mitk::Point3D click = positionEvent->GetPositionInWorld(); mitk::Point3D first = this->m_Contour->GetVertexAt(0)->Coordinates; return first.EuclideanDistanceTo(click) < 4.5; } void mitk::LiveWireTool2D::OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) { // Finish LiveWire tool interaction auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; // Remove last control point added by double click m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices() - 1); // Save contour and corresponding plane geometry to list this->m_WorkingContours.emplace_back(std::make_pair(m_ContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); this->m_EditingContours.emplace_back(std::make_pair(m_EditingContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); m_LiveWireFilter->SetUseDynamicCostMap(false); this->FinishTool(); } void mitk::LiveWireTool2D::FinishTool() { auto numberOfTimesteps = static_cast(m_Contour->GetTimeSteps()); for (int i = 0; i <= numberOfTimesteps; ++i) m_Contour->Close(i); this->GetToolManager()->GetDataStorage()->Remove(m_LiveWireContourNode); m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; 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->SetEditingContourModelNode(this->m_EditingContourNode); m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); this->m_LiveWireInteractors.push_back(m_ContourInteractor); } void mitk::LiveWireTool2D::OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) { // If last point of current contour will be removed go to start state and remove nodes if (m_Contour->GetNumberOfVertices() <= 1) { auto dataStorage = this->GetToolManager()->GetDataStorage(); dataStorage->Remove(m_LiveWireContourNode); dataStorage->Remove(m_ContourNode); dataStorage->Remove(m_EditingContourNode); m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); m_Contour = this->CreateNewContour(); m_ContourNode->SetData(m_Contour); this->ResetToStartState(); } else // Remove last segment from contour and reset LiveWire contour { m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); auto newContour = this->CreateNewContour(); auto begin = m_Contour->IteratorBegin(); // Iterate from last point to next active point auto newLast = m_Contour->IteratorBegin() + (m_Contour->GetNumberOfVertices() - 1); // Go at least one down if (newLast != begin) --newLast; // Search next active control point while (newLast != begin && !((*newLast)->IsControlPoint)) --newLast; // Set position of start point for LiveWire filter to coordinates of the new last point m_LiveWireFilter->SetStartPoint((*newLast)->Coordinates); auto it = m_Contour->IteratorBegin(); // Fll new Contour while (it <= newLast) { newContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint); ++it; } newContour->SetClosed(m_Contour->IsClosed()); m_ContourNode->SetData(newContour); m_Contour = newContour; mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } 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]; } }