diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp index 1f8ab4a0bf..34f1cfedb6 100755 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp @@ -1,293 +1,296 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkContourModelUtils.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "itkCastImageFilter.h" #include "mitkContourModel.h" #include "mitkContourModelToSurfaceFilter.h" #include "mitkImage.h" #include "mitkImageVtkAccessor.h" #include "mitkSurface.h" #include "vtkImageData.h" #include "vtkImageLogic.h" #include "vtkImageStencil.h" #include "vtkPointData.h" #include "vtkPolyData.h" #include "vtkPolyDataToImageStencil.h" #include "vtkSmartPointer.h" #include "mitkLabelSetImage.h" mitk::ContourModelUtils::ContourModelUtils() { } mitk::ContourModelUtils::~ContourModelUtils() { } mitk::ContourModel::Pointer mitk::ContourModelUtils::ProjectContourTo2DSlice( Image *slice, ContourModel *contourIn3D, bool itkNotUsed(correctionForIpSegmentation), bool constrainToInside) { if (!slice || !contourIn3D) return nullptr; ContourModel::Pointer projectedContour = ContourModel::New(); projectedContour->Initialize(*contourIn3D); const BaseGeometry *sliceGeometry = slice->GetGeometry(); int numberOfTimesteps = contourIn3D->GetTimeGeometry()->CountTimeSteps(); for (int currentTimestep = 0; currentTimestep < numberOfTimesteps; currentTimestep++) { auto iter = contourIn3D->Begin(currentTimestep); auto end = contourIn3D->End(currentTimestep); while (iter != end) { Point3D currentPointIn3D = (*iter)->Coordinates; Point3D projectedPointIn2D; projectedPointIn2D.Fill(0.0); sliceGeometry->WorldToIndex(currentPointIn3D, projectedPointIn2D); // MITK_INFO << "world point " << currentPointIn3D << " in index is " << projectedPointIn2D; if (!sliceGeometry->IsIndexInside(projectedPointIn2D) && constrainToInside) { MITK_DEBUG << "**" << currentPointIn3D << " is " << projectedPointIn2D << " --> correct it (TODO)" << std::endl; } projectedContour->AddVertex(projectedPointIn2D, currentTimestep); iter++; } } return projectedContour; } mitk::ContourModel::Pointer mitk::ContourModelUtils::BackProjectContourFrom2DSlice( const BaseGeometry *sliceGeometry, ContourModel *contourIn2D, bool itkNotUsed(correctionForIpSegmentation)) { if (!sliceGeometry || !contourIn2D) return nullptr; ContourModel::Pointer worldContour = ContourModel::New(); worldContour->Initialize(*contourIn2D); int numberOfTimesteps = contourIn2D->GetTimeGeometry()->CountTimeSteps(); for (int currentTimestep = 0; currentTimestep < numberOfTimesteps; currentTimestep++) { auto iter = contourIn2D->Begin(currentTimestep); auto end = contourIn2D->End(currentTimestep); while (iter != end) { Point3D currentPointIn2D = (*iter)->Coordinates; Point3D worldPointIn3D; worldPointIn3D.Fill(0.0); sliceGeometry->IndexToWorld(currentPointIn2D, worldPointIn3D); // MITK_INFO << "index " << currentPointIn2D << " world " << worldPointIn3D << std::endl; worldContour->AddVertex(worldPointIn3D, currentTimestep); iter++; } } return worldContour; } void mitk::ContourModelUtils::FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, mitk::Image::Pointer workingImage, int paintingPixelValue) { mitk::ContourModelUtils::FillContourInSlice(projectedContour, 0, sliceImage, workingImage, paintingPixelValue); } void mitk::ContourModelUtils::FillContourInSlice(ContourModel *projectedContour, unsigned int timeStep, Image *sliceImage, mitk::Image::Pointer workingImage, - int eraseMode) + int paintingPixelValue) { // create a surface of the input ContourModel mitk::Surface::Pointer surface = mitk::Surface::New(); mitk::ContourModelToSurfaceFilter::Pointer contourModelFilter = mitk::ContourModelToSurfaceFilter::New(); contourModelFilter->SetInput(projectedContour); contourModelFilter->Update(); surface = contourModelFilter->GetOutput(); // that's our vtkPolyData-Surface vtkSmartPointer surface2D = vtkSmartPointer::New(); if (surface->GetVtkPolyData(timeStep) == nullptr) { MITK_WARN << "No surface has been created from contour model. Add more points to fill contour in slice."; return; } surface2D->SetPoints(surface->GetVtkPolyData(timeStep)->GetPoints()); surface2D->SetLines(surface->GetVtkPolyData(timeStep)->GetLines()); surface2D->Modified(); // surface2D->Update(); // prepare the binary image's voxel grid vtkSmartPointer whiteImage = vtkSmartPointer::New(); whiteImage->DeepCopy(sliceImage->GetVtkImageData()); // fill the image with foreground voxels: unsigned char inval = 255; unsigned char outval = 0; vtkIdType count = whiteImage->GetNumberOfPoints(); for (vtkIdType i = 0; i < count; ++i) { whiteImage->GetPointData()->GetScalars()->SetTuple1(i, inval); } // polygonal data --> image stencil: vtkSmartPointer pol2stenc = vtkSmartPointer::New(); // Set a minimal tolerance, so that clipped pixels will be added to contour as well. pol2stenc->SetTolerance(mitk::eps); pol2stenc->SetInputData(surface2D); pol2stenc->Update(); // cut the corresponding white image and set the background: vtkSmartPointer imgstenc = vtkSmartPointer::New(); imgstenc->SetInputData(whiteImage); imgstenc->SetStencilConnection(pol2stenc->GetOutputPort()); imgstenc->ReverseStencilOff(); imgstenc->SetBackgroundValue(outval); imgstenc->Update(); // mitk::LabelSetImage* labelImage; // Todo: Get the working Image // int activePixelValue = eraseMode; // labelImage = dynamic_cast(workingImage.GetPointer()); // if (labelImage) //{ // activePixelValue = labelImage->GetActiveLabel()->GetValue(); //} // Fill according to the Color Team vtkSmartPointer filledImage = imgstenc->GetOutput(); vtkSmartPointer resultImage = sliceImage->GetVtkImageData(); - FillSliceInSlice(filledImage, resultImage, workingImage, eraseMode); + FillSliceInSlice(filledImage, resultImage, workingImage, paintingPixelValue); /* count = filledImage->GetNumberOfPoints(); if (activePixelValue == 0) { for (vtkIdType i = 0; i < count; ++i) { if (filledImage->GetPointData()->GetScalars()->GetTuple1(i) > 1) { resultImage->GetPointData()->GetScalars()->SetTuple1(i, eraseMode); } } } else if (eraseMode != 0) // We are not erasing... { for (vtkIdType i = 0; i < count; ++i) { if (filledImage->GetPointData()->GetScalars()->GetTuple1(i) > 1) { int targetValue = resultImage->GetPointData()->GetScalars()->GetTuple1(i); if (labelImage) { if (!labelImage->GetLabel(targetValue)->GetLocked()) { resultImage->GetPointData()->GetScalars()->SetTuple1(i, eraseMode); } } else { resultImage->GetPointData()->GetScalars()->SetTuple1(i, eraseMode); } } } } else { for (vtkIdType i = 0; i < count; ++i) { if ((resultImage->GetPointData()->GetScalars()->GetTuple1(i) == activePixelValue) & (filledImage->GetPointData()->GetScalars()->GetTuple1(i) > 1)) { resultImage->GetPointData()->GetScalars()->SetTuple1(i, eraseMode); } } }*/ sliceImage->SetVolume(resultImage->GetScalarPointer()); } void mitk::ContourModelUtils::FillSliceInSlice(vtkSmartPointer filledImage, vtkSmartPointer resultImage, mitk::Image::Pointer image, - int eraseMode) + int paintingPixelValue) { mitk::LabelSetImage *labelImage; // Todo: Get the working Image - int activePixelValue = eraseMode; labelImage = dynamic_cast(image.GetPointer()); - if (labelImage) - { - activePixelValue = labelImage->GetActiveLabel()->GetValue(); - } - int count = filledImage->GetNumberOfPoints(); - if (activePixelValue == 0) + + // if image is not a LabelSetImage just paint or erase + if (!labelImage) { for (vtkIdType i = 0; i < count; ++i) { if (filledImage->GetPointData()->GetScalars()->GetTuple1(i) > 1) { - resultImage->GetPointData()->GetScalars()->SetTuple1(i, eraseMode); + resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } } - else if (eraseMode != 0) // We are not erasing... + + // now for LabelSetImages + else { - for (vtkIdType i = 0; i < count; ++i) + auto backgroundValue = labelImage->GetExteriorLabel()->GetValue(); + + // paint, but do not overwrite locked pixels + if (paintingPixelValue != backgroundValue) { - if (filledImage->GetPointData()->GetScalars()->GetTuple1(i) > 1) + for (vtkIdType i = 0; i < count; ++i) { - int targetValue = resultImage->GetPointData()->GetScalars()->GetTuple1(i); - if (labelImage) + if (filledImage->GetPointData()->GetScalars()->GetTuple1(i) > 1) { - if (!labelImage->GetLabel(targetValue)->GetLocked()) + auto existingValue = resultImage->GetPointData()->GetScalars()->GetTuple1(i); + if (!labelImage->GetLabel(existingValue, labelImage->GetActiveLayer())->GetLocked()) { - resultImage->GetPointData()->GetScalars()->SetTuple1(i, eraseMode); + resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } - else - { - resultImage->GetPointData()->GetScalars()->SetTuple1(i, eraseMode); - } } } - } - else - { - for (vtkIdType i = 0; i < count; ++i) + + // erase, but only active label (regardless of locked state) + else { - if ((resultImage->GetPointData()->GetScalars()->GetTuple1(i) == activePixelValue) & - (filledImage->GetPointData()->GetScalars()->GetTuple1(i) > 1)) + auto activePixelValue = labelImage->GetActiveLabel(labelImage->GetActiveLayer())->GetValue(); + + for (vtkIdType i = 0; i < count; ++i) { - resultImage->GetPointData()->GetScalars()->SetTuple1(i, eraseMode); + if (filledImage->GetPointData()->GetScalars()->GetTuple1(i) > 1) + { + if (resultImage->GetPointData()->GetScalars()->GetTuple1(i) == activePixelValue) + { + resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); + } + } } - } + } } } diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h index 5bfa55fd00..520089710f 100644 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h @@ -1,91 +1,91 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef mitkContourModelUtilshIncludett #define mitkContourModelUtilshIncludett #include "mitkContourModel.h" #include "mitkImage.h" #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 correctionForIpSegmentation adds 0.5 to x and y index coordinates (difference between ipSegmentation and MITK contours) */ static ContourModel::Pointer ProjectContourTo2DSlice(Image *slice, ContourModel *contourIn3D, bool correctionForIpSegmentation, bool constrainToInside); /** \brief Projects a slice index coordinates of a contour back into world coordinates. \param correctionForIpSegmentation subtracts 0.5 to x and y index coordinates (difference between ipSegmentation and MITK contours) */ static ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, ContourModel *contourIn2D, bool correctionForIpSegmentation = false); /** \brief Fill a contour in a 2D slice with a specified pixel value at time step 0. */ static void FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, mitk::Image::Pointer workingImage, int paintingPixelValue = 1); /** \brief Fill a contour in a 2D slice with a specified pixel value at a given time step. */ static void FillContourInSlice(ContourModel *projectedContour, unsigned int timeStep, Image *sliceImage, mitk::Image::Pointer workingImage, int paintingPixelValue = 1); /** \brief Fills a image (filledImage) into another image (resultImage) by incorporating the rules of LabelSet-Images */ static void FillSliceInSlice(vtkSmartPointer filledImage, vtkSmartPointer resultImage, mitk::Image::Pointer image, - int eraseMode); + int paintingPixelValue); protected: ContourModelUtils(); virtual ~ContourModelUtils(); }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkContourTool.cpp b/Modules/Segmentation/Interactions/mitkContourTool.cpp index a1985e94b2..e4f6152dae 100644 --- a/Modules/Segmentation/Interactions/mitkContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkContourTool.cpp @@ -1,200 +1,201 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkContourTool.h" #include "mitkAbstractTransformGeometry.h" #include "mitkOverwriteDirectedPlaneImageFilter.h" #include "mitkOverwriteSliceImageFilter.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" //#include "mitkProperties.h" //#include "mitkPlanarCircle.h" #include "mitkLabelSetImage.h" #include "mitkInteractionEvent.h" #include "mitkStateMachineAction.h" mitk::ContourTool::ContourTool(int paintingPixelValue) : FeedbackContourTool("PressMoveReleaseWithCTRLInversion"), m_PaintingPixelValue(paintingPixelValue), m_CurrentLabelID(1) { } mitk::ContourTool::~ContourTool() { } void mitk::ContourTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); CONNECT_FUNCTION("InvertLogic", OnInvertLogic); } void mitk::ContourTool::Activated() { Superclass::Activated(); } void mitk::ContourTool::Deactivated() { Superclass::Deactivated(); } /** Just show the contour, insert the first point. */ void mitk::ContourTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); int timestep = positionEvent->GetSender()->GetTimeStep(); ContourModel *contour = FeedbackContourTool::GetFeedbackContour(); // Clear feedback contour contour->Initialize(); // expand time bounds because our contour was initialized contour->Expand(timestep + 1); // draw as a closed contour contour->SetClosed(true, timestep); // add first mouse position mitk::Point3D point = positionEvent->GetPositionInWorld(); contour->AddVertex(point, timestep); FeedbackContourTool::SetFeedbackContourVisible(true); assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } /** Insert the point to the feedback contour. */ void mitk::ContourTool::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { mitk::InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; int timestep = positionEvent->GetSender()->GetTimeStep(); ContourModel *contour = FeedbackContourTool::GetFeedbackContour(); mitk::Point3D point = positionEvent->GetPositionInWorld(); contour->AddVertex(point, timestep); assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } /** Close the contour, project it to the image slice and fill it in 2D. */ void mitk::ContourTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { // 1. Hide the feedback contour, find out which slice the user clicked, find out which slice of the toolmanager's // working image corresponds to that FeedbackContourTool::SetFeedbackContourVisible(false); mitk::InteractionPositionEvent *positionEvent = dynamic_cast(interactionEvent); // const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return; assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); DataNode *workingNode(m_ToolManager->GetWorkingData(0)); if (!workingNode) return; Image *image = dynamic_cast(workingNode->GetData()); const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (!image || !planeGeometry) return; // Check if it is a multilabel-image // If yes, get the new drawing color from it. // Otherwise nothing happens. mitk::LabelSetImage *labelSetImage = dynamic_cast(image); if (labelSetImage) { mitk::Label *label = labelSetImage->GetActiveLabel(labelSetImage->GetActiveLayer()); m_CurrentLabelID = label->GetValue(); } else { m_CurrentLabelID = 1; } const AbstractTransformGeometry *abstractTransformGeometry( dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (!image || abstractTransformGeometry) return; // 2. Slice is known, now we try to get it as a 2D image and project the contour into index coordinates of this slice Image::Pointer slice = SegTool2D::GetAffectedImageSliceAs2DImage(positionEvent, image); if (slice.IsNull()) { MITK_ERROR << "Unable to extract slice." << std::endl; return; } ContourModel *feedbackContour = FeedbackContourTool::GetFeedbackContour(); ContourModel::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( slice, feedbackContour, true, false); // true: actually no idea why this is neccessary, but it works :-( if (projectedContour.IsNull()) return; int timestep = positionEvent->GetSender()->GetTimeStep(); + // m_PaintingPixelValue only decides whether to paint or erase mitk::ContourModelUtils::FillContourInSlice( projectedContour, timestep, slice, image, (m_PaintingPixelValue * m_CurrentLabelID)); // this->WriteBackSegmentationResult(positionEvent, slice); SegTool2D::WriteBackSegmentationResult(positionEvent, slice); // 4. Make sure the result is drawn again --> is visible then. assert(positionEvent->GetSender()->GetRenderWindow()); } /** Called when the CTRL key is pressed. Will change the painting pixel value from 0 to 1 or from 1 to 0. */ void mitk::ContourTool::OnInvertLogic(StateMachineAction *, InteractionEvent *interactionEvent) { // Inversion only for 0 and 1 as painting values if (m_PaintingPixelValue == 1) { m_PaintingPixelValue = 0; FeedbackContourTool::SetFeedbackContourColor(1.0, 0.0, 0.0); } else if (m_PaintingPixelValue == 0) { m_PaintingPixelValue = 1; FeedbackContourTool::SetFeedbackContourColorDefault(); } }