diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp index 3656bf83a6..f96f7a385e 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) { if (nullptr == slice || nullptr == contourIn3D) return nullptr; auto projectedContour = ContourModel::New(); projectedContour->Initialize(*contourIn3D); auto sliceGeometry = slice->GetGeometry(); auto numberOfTimesteps = static_cast(contourIn3D->GetTimeSteps()); for (decltype(numberOfTimesteps) 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) { if (nullptr == sliceGeometry || nullptr == contourIn2D) return nullptr; auto worldContour = ContourModel::New(); worldContour->Initialize(*contourIn2D); auto numberOfTimesteps = static_cast(contourIn2D->GetTimeSteps()); for (decltype(numberOfTimesteps) 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, unsigned int contourTimeStep, Image *sliceImage, const Image* workingImage, int paintingPixelValue) + 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; vtkIdType count = image->GetNumberOfPoints(); for (decltype(count) 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) { auto labelImage = dynamic_cast(image); auto numberOfPoints = filledImage->GetNumberOfPoints(); if (nullptr == labelImage) { for (decltype(numberOfPoints) i = 0; i < numberOfPoints; ++i) { if (1 < filledImage->GetPointData()->GetScalars()->GetTuple1(i)) resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } else { auto backgroundValue = labelImage->GetExteriorLabel()->GetValue(); if (paintingPixelValue != backgroundValue) { for (decltype(numberOfPoints) i = 0; i < numberOfPoints; ++i) { if (1 < filledImage->GetPointData()->GetScalars()->GetTuple1(i)) { auto existingValue = resultImage->GetPointData()->GetScalars()->GetTuple1(i); if (!labelImage->GetLabel(existingValue, labelImage->GetActiveLayer())->GetLocked()) resultImage->GetPointData()->GetScalars()->SetTuple1(i, paintingPixelValue); } } } else { auto activePixelValue = labelImage->GetActiveLabel(labelImage->GetActiveLayer())->GetValue(); for (decltype(numberOfPoints) i = 0; i < numberOfPoints; ++i) { if (1 < 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, unsigned int t) +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 ca18c697a9..cfc364b9df 100644 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h @@ -1,113 +1,113 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef 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); /** \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); /** \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, - unsigned int contourTimeStep, + TimeStepType contourTimeStep, Image *sliceImage, const Image* 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, const Image* image, int paintingPixelValue); /** \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, unsigned int timeStep); + 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/ContourModel/Testing/mitkContourModelTest.cpp b/Modules/ContourModel/Testing/mitkContourModelTest.cpp index a04e7318c3..129c4e13f8 100644 --- a/Modules/ContourModel/Testing/mitkContourModelTest.cpp +++ b/Modules/ContourModel/Testing/mitkContourModelTest.cpp @@ -1,464 +1,465 @@ /*============================================================================ 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 // Add a vertex to the contour and see if size changed static void TestAddVertex() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); MITK_TEST_CONDITION(contour->GetNumberOfVertices() == 1, "Add a Vertex, size increased"); MITK_TEST_CONDITION(contour->GetVertexAt(0)->Coordinates == p, "Added vertex has the correct value."); mitk::Point3D outOfTimeBoundPoint; outOfTimeBoundPoint[0] = outOfTimeBoundPoint[1] = outOfTimeBoundPoint[2] = 1; contour->AddVertex(outOfTimeBoundPoint, mitk::TimeStepType(1)); MITK_TEST_CONDITION(contour->GetTimeSteps() == 1, "Add a vertex to an unsupported time step has not changed geometry."); MITK_TEST_CONDITION(contour->IsEmptyTimeStep(1), "Add a vertex to an unsupported time step has not added an contour element."); MITK_TEST_CONDITION(contour->GetNumberOfVertices(1) == -1, "Add a vertex to an unsupported time step has not added an contour element."); } // Select a vertex by index. successful if the selected vertex member of the contour is no longer set to null static void TestSelectVertexAtIndex() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); contour->SelectVertexAt(0); MITK_TEST_CONDITION(contour->GetSelectedVertex() != nullptr, "Vertex was selected at index"); } // Select a vertex by worldposition. successful if the selected vertex member of the contour is no longer set to null static void TestSelectVertexAtWorldposition() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); // same point is used here so the epsilon can be chosen very small contour->SelectVertexAt(p, 0.01); MITK_TEST_CONDITION(contour->GetSelectedVertex() != nullptr, "Vertex was selected at position"); } // Move a vertex by a translation vector static void TestMoveSelectedVertex() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); // Same point is used here so the epsilon can be chosen very small contour->SelectVertexAt(p, 0.01); mitk::Vector3D v; v[0] = 1; v[1] = 3; v[2] = -1; contour->ShiftSelectedVertex(v); const mitk::ContourModel::VertexType *vertex = contour->GetSelectedVertex(); bool correctlyMoved = false; correctlyMoved = (vertex->Coordinates)[0] == (v[0]) && (vertex->Coordinates)[1] == (v[1]) && (vertex->Coordinates)[2] == (v[2]); MITK_TEST_CONDITION(correctlyMoved, "Vertex has been moved"); } // Test to move the whole contour /* static void TestMoveContour() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); mitk::Point3D p2; p2[0] = p2[1] = p2[2] = 0; contour->AddVertex(p2); mitk::Vector3D v; v[0] = 1; v[1] = 3; v[2] = -1; contour->ShiftContour(v); mitk::ContourModel::VertexIterator it = contour->IteratorBegin(); mitk::ContourModel::VertexIterator end = contour->IteratorEnd(); bool correctlyMoved = false; while(it != end) { correctlyMoved &= (*it)->Coordinates[0] == (v[0]) && (*it)->Coordinates[1] == (v[1]) && (*it)->Coordinates[2] == (v[2]); } MITK_TEST_CONDITION(correctlyMoved, "Contour has been moved"); } */ // Remove a vertex by index static void TestRemoveVertexAtIndex() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); contour->RemoveVertexAt(0); MITK_TEST_CONDITION(contour->GetNumberOfVertices() == 0, "removed vertex"); } // Remove a vertex by position static void TestRemoveVertexAtWorldPosition() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); contour->RemoveVertexAt(p, 0.01); MITK_TEST_CONDITION(contour->GetNumberOfVertices() == 0, "removed vertex"); } // Check closeable contour static void TestIsclosed() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); mitk::Point3D p2; p2[0] = p2[1] = p2[2] = 1; contour->AddVertex(p2); contour->Close(); MITK_TEST_CONDITION(contour->IsClosed(), "closed contour"); // no vertices should be added to a closed contour int oldNumberOfVertices = contour->GetNumberOfVertices(); mitk::Point3D p3; p3[0] = p3[1] = p3[2] = 4; contour->AddVertex(p3); int newNumberOfVertices = contour->GetNumberOfVertices(); MITK_TEST_CONDITION(oldNumberOfVertices != newNumberOfVertices, "vertices added to closed contour"); } // Test concatenating two contours static void TestConcatenate() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); mitk::Point3D p2; p2[0] = p2[1] = p2[2] = 1; contour->AddVertex(p2); mitk::ContourModel::Pointer contour2 = mitk::ContourModel::New(); mitk::Point3D p3; p3[0] = -2; p3[1] = 10; p3[2] = 0; contour2->AddVertex(p3); mitk::Point3D p4; p4[0] = -3; p4[1] = 6; p4[2] = -5; contour2->AddVertex(p4); contour->Concatenate(contour2); MITK_TEST_CONDITION(contour->GetNumberOfVertices() == 4, "two contours were concatenated"); } // Try to select a vertex at position (within a epsilon of course) where no vertex is. // So the selected verted member should be null. static void TestSelectVertexAtWrongPosition() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); MITK_TEST_CONDITION_REQUIRED(contour->GetSelectedVertex() == nullptr, "selected vertex is nullptr"); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); mitk::Point3D p2; p2[0] = p2[1] = p2[2] = 2; contour->SelectVertexAt(p2, 0.1); MITK_TEST_CONDITION(contour->GetSelectedVertex() == nullptr, "Vertex was not selected"); } static void TestInsertVertex() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); mitk::Point3D p2; p2[0] = p2[1] = p2[2] = 1; contour->AddVertex(p2); mitk::Point3D pointToInsert; pointToInsert[0] = pointToInsert[1] = pointToInsert[2] = 10; contour->InsertVertexAtIndex(pointToInsert, 1); MITK_TEST_CONDITION(contour->GetNumberOfVertices() == 3, "test insert vertex"); MITK_TEST_CONDITION(contour->GetVertexAt(1)->Coordinates == pointToInsert, "compare inserted vertex"); mitk::Point3D outOfTimeBoundPoint; outOfTimeBoundPoint[0] = outOfTimeBoundPoint[1] = outOfTimeBoundPoint[2] = 1; contour->InsertVertexAtIndex(outOfTimeBoundPoint, 4, false, mitk::TimeStepType(1)); MITK_TEST_CONDITION(contour->GetTimeSteps() == 1, "Insert a vertex to an unsupported time step has not changed geometry."); MITK_TEST_CONDITION(contour->IsEmptyTimeStep(1), "Insert a vertex to an unsupported time step has not added an contour element."); MITK_TEST_CONDITION(contour->GetNumberOfVertices(1) == -1, "Insert a vertex to an unsupported time step has not added an contour element."); } // try to access an invalid timestep static void TestInvalidTimeStep() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); mitk::Point3D p2; p2[0] = p2[1] = p2[2] = 1; contour->AddVertex(p2); mitk::TimeStepType invalidTimeStep = 42; MITK_TEST_CONDITION_REQUIRED(contour->IsEmptyTimeStep(invalidTimeStep), "invalid timestep required"); MITK_TEST_FOR_EXCEPTION(std::exception, contour->IteratorBegin(-1)); contour->Close(invalidTimeStep); MITK_TEST_CONDITION(contour->IsClosed() == false, "test close for timestep 0"); MITK_TEST_CONDITION(contour->IsClosed(invalidTimeStep) == false, "test close at invalid timestep"); contour->SetClosed(true, invalidTimeStep); MITK_TEST_CONDITION(contour->GetNumberOfVertices(invalidTimeStep) == -1, "test number of vertices at invalid timestep"); contour->AddVertex(p2, invalidTimeStep); MITK_TEST_CONDITION(contour->GetNumberOfVertices(invalidTimeStep) == -1, "test add vertex at invalid timestep"); contour->InsertVertexAtIndex(p2, 0, false, invalidTimeStep); MITK_TEST_CONDITION(contour->GetNumberOfVertices(invalidTimeStep) == -1, "test insert vertex at invalid timestep"); MITK_TEST_CONDITION(contour->SelectVertexAt(0, invalidTimeStep) == false, "test select vertex at invalid timestep"); MITK_TEST_CONDITION(contour->RemoveVertexAt(0, invalidTimeStep) == false, "test remove vertex at invalid timestep"); } static void TestEmptyContour() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); MITK_TEST_CONDITION(contour->IteratorBegin() == contour->IteratorEnd(), "test iterator of emtpy contour"); MITK_TEST_CONDITION(contour->GetNumberOfVertices() == 0, "test numberof vertices of empty contour"); } static void TestSetVertices() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); mitk::Point3D newCoordinates; newCoordinates[0] = newCoordinates[1] = newCoordinates[2] = 1; contour->SetVertexAt(0, newCoordinates); MITK_TEST_CONDITION(mitk::Equal(contour->GetVertexAt(0)->Coordinates, newCoordinates), "set coordinates"); mitk::ContourModel::Pointer contour2 = mitk::ContourModel::New(); mitk::Point3D p3; p3[0] = -2; p3[1] = 10; p3[2] = 0; contour2->AddVertex(p3); mitk::Point3D p4; p4[0] = -3; p4[1] = 6; p4[2] = -5; contour2->AddVertex(p4); contour->AddVertex(p); contour->SetVertexAt(1, contour2->GetVertexAt(1), 0); MITK_TEST_CONDITION( mitk::Equal(contour->GetVertexAt(1)->Coordinates, contour2->GetVertexAt(1)->Coordinates), "Use setter and getter combination"); } static void TestContourModelAPI() { mitk::ContourModel::Pointer contour1 = mitk::ContourModel::New(); mitk::Point3D p1; p1[0] = -2; p1[1] = 10; p1[2] = 0; contour1->AddVertex(p1); // adding vertices should always copy the content and not store pointers or references. MITK_TEST_CONDITION(&p1 != &(contour1->GetVertexAt(0)->Coordinates), "copied point"); mitk::Point3D p2; p2[0] = -3; p2[1] = 6; p2[2] = -5; contour1->AddVertex(p2); // test use of setter and getter with const and non-const pointers const mitk::ContourModel::VertexType *vertex = contour1->GetVertexAt(1); MITK_TEST_CONDITION(contour1->GetIndex(vertex) == 1, "Get index"); auto *nonConstVertex = const_cast(vertex); MITK_TEST_CONDITION(contour1->GetIndex(nonConstVertex) == 1, "Get index non-const"); mitk::ContourModel::Pointer contour2 = mitk::ContourModel::New(); contour2->AddVertex(*(contour1->GetVertexAt(0))); MITK_TEST_CONDITION(contour2->GetNumberOfVertices() == 1, "Add call with another contour"); } static void TestClear() { mitk::ContourModel::Pointer contour = mitk::ContourModel::New(); contour->Expand(3); + contour->Expand(3); mitk::Point3D p; p[0] = p[1] = p[2] = 0; contour->AddVertex(p); p[0] = p[1] = p[2] = 1; contour->AddVertex(p, mitk::TimeStepType(1)); p[0] = p[1] = p[2] = 2; contour->AddVertex(p, mitk::TimeStepType(2)); contour->Clear(1); MITK_TEST_CONDITION(contour->GetTimeSteps() == 3, "Check time step count stays 3."); MITK_TEST_CONDITION(!contour->IsEmpty(0), "Check time step 0 is not empty."); MITK_TEST_CONDITION(contour->IsEmpty(1), "Check time step 1 is empty."); MITK_TEST_CONDITION(!contour->IsEmpty(2), "Check time step 2 is not empty."); - MITK_TEST_CONDITION(contour->GetVertexAt(0,2)->Coordinates == p, "compare if vertex at t == 2 is still the same"); + MITK_TEST_CONDITION(contour->GetVertexAt(0, 2)->Coordinates == p, "compare if vertex at t == 2 is still the same"); contour->Clear(); MITK_TEST_CONDITION(contour->GetTimeSteps() == 1, "Check time step count stays 1."); MITK_TEST_CONDITION(contour->IsEmpty(0), "Check time step 0 is empty."); } int mitkContourModelTest(int /*argc*/, char * /*argv*/ []) { MITK_TEST_BEGIN("mitkContourModelTest") TestAddVertex(); TestSelectVertexAtIndex(); TestSelectVertexAtWorldposition(); TestMoveSelectedVertex(); TestRemoveVertexAtIndex(); TestRemoveVertexAtWorldPosition(); TestIsclosed(); TestConcatenate(); TestInvalidTimeStep(); TestInsertVertex(); TestEmptyContour(); TestSetVertices(); TestSelectVertexAtWrongPosition(); TestContourModelAPI(); TestClear(); MITK_TEST_END() } diff --git a/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp b/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp index c7fbf75857..bab4084f3e 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp +++ b/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp @@ -1,100 +1,100 @@ /*============================================================================ 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 "mitkDiffSliceOperation.h" #include #include mitk::DiffSliceOperation::DiffSliceOperation() : Operation(1) { m_TimeStep = 0; m_zlibSliceContainer = nullptr; m_Image = nullptr; m_WorldGeometry = nullptr; m_SliceGeometry = nullptr; m_ImageIsValid = false; m_DeleteObserverTag = 0; } mitk::DiffSliceOperation::DiffSliceOperation(Image *imageVolume, const Image *slice, const SlicedGeometry3D *sliceGeometry, TimeStepType timestep, const BaseGeometry *currentWorldGeometry) : Operation(1) { m_WorldGeometry = currentWorldGeometry->Clone(); /* Quick fix for bug 12338. Guard object - fix this when clone method of PlaneGeometry is cloning the reference geometry (see bug 13392)*/ // xxxx m_GuardReferenceGeometry = mitk::BaseGeometry::New(); - m_GuardReferenceGeometry = dynamic_cast(m_WorldGeometry.GetPointer())->GetReferenceGeometry(); + m_GuardReferenceGeometry = dynamic_cast(m_WorldGeometry.GetPointer())->GetReferenceGeometry(); /*---------------------------------------------------------------------------------------------------*/ m_SliceGeometry = sliceGeometry->Clone(); m_TimeStep = timestep; m_zlibSliceContainer = CompressedImageContainer::New(); m_zlibSliceContainer->SetImage(slice); m_Image = imageVolume; m_DeleteObserverTag = 0; if (m_Image) { /*add an observer to listen to the delete event of the image, this is necessary because the operation is then * invalid*/ itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &DiffSliceOperation::OnImageDeleted); // get the id of the observer, used to remove it later on m_DeleteObserverTag = imageVolume->AddObserver(itk::DeleteEvent(), command); m_ImageIsValid = true; } else m_ImageIsValid = false; } mitk::DiffSliceOperation::~DiffSliceOperation() { m_WorldGeometry = nullptr; m_zlibSliceContainer = nullptr; if (m_ImageIsValid) { // if the image is still there, we have to remove the observer from it m_Image->RemoveObserver(m_DeleteObserverTag); } m_Image = nullptr; } mitk::Image::Pointer mitk::DiffSliceOperation::GetSlice() { Image::Pointer image = m_zlibSliceContainer->GetImage(); return image; } bool mitk::DiffSliceOperation::IsValid() { return m_ImageIsValid && m_zlibSliceContainer.IsNotNull() && (m_WorldGeometry.IsNotNull()); // TODO improve } void mitk::DiffSliceOperation::OnImageDeleted() { // if our imageVolume is removed e.g. from the datastorage the operation is no lnger valid m_ImageIsValid = false; } diff --git a/Modules/Segmentation/Interactions/mitkContourTool.cpp b/Modules/Segmentation/Interactions/mitkContourTool.cpp index e203192d1a..41ec690074 100644 --- a/Modules/Segmentation/Interactions/mitkContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkContourTool.cpp @@ -1,114 +1,111 @@ /*============================================================================ 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 "mitkContourTool.h" mitk::ContourTool::ContourTool(int paintingPixelValue) : FeedbackContourTool("PressMoveReleaseWithCTRLInversion"), m_PaintingPixelValue(paintingPixelValue) { } 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) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); this->ClearsCurrentFeedbackContour(true); mitk::Point3D point = positionEvent->GetPositionInWorld(); this->AddVertexToCurrentFeedbackContour(point); 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) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; mitk::Point3D point = positionEvent->GetPositionInWorld(); this->AddVertexToCurrentFeedbackContour(point); 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) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); this->WriteBackFeedbackContourAsSegmentationResult(positionEvent, m_PaintingPixelValue); - - // 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 *) { // 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(); } } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp index 6f4e44ccac..1ded558ddc 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp @@ -1,336 +1,335 @@ /*============================================================================ 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 "mitkFastMarchingBaseTool.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkInteractionConst.h" #include "mitkRenderingManager.h" #include "mitkInteractionPositionEvent.h" #include "mitkImageAccessByItk.h" #include "mitkSegTool2D.h" #include // itk filter #include "itkBinaryThresholdImageFilter.h" #include "itkCurvatureAnisotropicDiffusionImageFilter.h" #include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" #include "itkSigmoidImageFilter.h" // us #include #include #include #include mitk::FastMarchingBaseTool::FastMarchingBaseTool(unsigned int toolDim) : AutoSegmentationWithPreviewTool(false, "FastMarchingTool"), m_LowerThreshold(0), m_UpperThreshold(200), m_StoppingValue(100), m_Sigma(1.0), m_Alpha(-0.5), m_Beta(3.0), m_ToolDimension(toolDim) { } mitk::FastMarchingBaseTool::~FastMarchingBaseTool() { } bool mitk::FastMarchingBaseTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if(!Superclass::CanHandle(referenceData, workingData)) return false; if (referenceData == nullptr) return false; auto *image = dynamic_cast(referenceData); if (image == nullptr) return false; if (image->GetDimension() < 3) return false; return true; } const char **mitk::FastMarchingBaseTool::GetXPM() const { return nullptr; // mitkFastMarchingBaseTool_xpm; } us::ModuleResource mitk::FastMarchingBaseTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); return resource; } us::ModuleResource mitk::FastMarchingBaseTool::GetCursorIconResource() const { us::Module* module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("FastMarching_Cursor_32x32.png"); return resource; } void mitk::FastMarchingBaseTool::SetUpperThreshold(double value) { m_UpperThreshold = value / 10.0; } void mitk::FastMarchingBaseTool::SetLowerThreshold(double value) { m_LowerThreshold = value / 10.0; } void mitk::FastMarchingBaseTool::SetBeta(double value) { if (m_Beta != value) { m_Beta = value; } } void mitk::FastMarchingBaseTool::SetSigma(double value) { if (m_Sigma != value) { if (value > 0.0) { m_Sigma = value; } } } void mitk::FastMarchingBaseTool::SetAlpha(double value) { if (m_Alpha != value) { m_Alpha = value; } } void mitk::FastMarchingBaseTool::SetStoppingValue(double value) { if (m_StoppingValue != value) { m_StoppingValue = value; } } void mitk::FastMarchingBaseTool::Activated() { Superclass::Activated(); m_SeedsAsPointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_SeedsAsPointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); m_SeedsAsPointSetNode = mitk::DataNode::New(); m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); m_SeedsAsPointSetNode->SetName(std::string(this->GetName()) + "_PointSet"); m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); m_SeedsAsPointSetNode->SetVisibility(true); m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); } void mitk::FastMarchingBaseTool::Deactivated() { this->ClearSeeds(); m_ToolManager->GetDataStorage()->Remove(m_SeedsAsPointSetNode); m_SeedsAsPointSetNode = nullptr; m_SeedsAsPointSet = nullptr; Superclass::Deactivated(); } void mitk::FastMarchingBaseTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); CONNECT_FUNCTION("DeletePoint", OnDelete); } void mitk::FastMarchingBaseTool::OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent) { if (!this->IsUpdating() && m_SeedsAsPointSet.IsNotNull()) { const auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { - auto newWorkingPlaneGeometry = positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone(); + auto workingPlaneGeometry = positionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); - // if click was on another plane and we are in 2D mode wie should reset the seeds - if (m_ToolDimension == 2 && ( nullptr == this->GetWorkingPlaneGeometry() || !this->GetWorkingPlaneGeometry()->IsOnPlane(newWorkingPlaneGeometry.GetPointer()))) + // if click was on another plane and we are in 2D mode we should reset the seeds + if (m_ToolDimension == 2 && ( nullptr == this->GetWorkingPlaneGeometry() || !this->GetWorkingPlaneGeometry()->IsOnPlane(workingPlaneGeometry))) { this->ClearSeeds(); - this->SetWorkingPlaneGeometry(newWorkingPlaneGeometry); + this->SetWorkingPlaneGeometry(workingPlaneGeometry->Clone()); } m_SeedsAsPointSet->InsertPoint(m_SeedsAsPointSet->GetSize(), positionEvent->GetPositionInWorld()); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdatePreview(); } } } void mitk::FastMarchingBaseTool::OnDelete(StateMachineAction*, InteractionEvent* /*interactionEvent*/) { if (!this->IsUpdating() && m_SeedsAsPointSet.IsNotNull()) { // delete last seed point if (this->m_SeedsAsPointSet->GetSize() > 0) { - // delete last point in pointset - somehow ugly - m_SeedsAsPointSet->GetPointSet()->GetPoints()->DeleteIndex(m_SeedsAsPointSet->GetSize() - 1); + m_SeedsAsPointSet->RemovePointAtEnd(0); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); this->UpdatePreview(); } } } void mitk::FastMarchingBaseTool::ClearSeeds() { if (this->m_SeedsAsPointSet.IsNotNull()) { // renew pointset this->m_SeedsAsPointSet = mitk::PointSet::New(); //ensure that the seed points are visible for all timepoints. dynamic_cast(m_SeedsAsPointSet->GetTimeGeometry())->SetStepDuration(std::numeric_limits::max()); this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); } } template void mitk::FastMarchingBaseTool::DoITKFastMarching(const itk::Image* inputImage, - mitk::Image* previewImage, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry) + Image* previewImage, unsigned int timeStep, const BaseGeometry* inputGeometry) { // typedefs for itk pipeline typedef itk::Image InputImageType; typedef float InternalPixelType; typedef itk::Image InternalImageType; typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; typedef itk::Image OutputImageType; typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; typedef itk::SigmoidImageFilter SigmoidFilterType; typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; typedef itk::FastMarchingImageFilter FastMarchingFilterType; typedef typename FastMarchingFilterType::NodeContainer NodeContainer; typedef typename FastMarchingFilterType::NodeType NodeType; //convert point set seed into trialpoint typename NodeContainer::Pointer trialPoints = NodeContainer::New(); trialPoints->Initialize(); for (auto pos = m_SeedsAsPointSet->Begin(); pos != m_SeedsAsPointSet->End(); ++pos) { mitk::Point3D clickInIndex; inputGeometry->WorldToIndex(pos->Value(), clickInIndex); itk::Index seedPosition; for (unsigned int dim = 0; dim < VImageDimension; ++dim) { seedPosition[dim] = clickInIndex[dim]; } NodeType node; const double seedValue = 0.0; node.SetValue(seedValue); node.SetIndex(seedPosition); trialPoints->InsertElement(trialPoints->Size(), node); } // assemble pipeline auto smoothFilter = SmoothingFilterType::New(); smoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); smoothFilter->SetTimeStep(0.05); smoothFilter->SetNumberOfIterations(2); smoothFilter->SetConductanceParameter(9.0); auto gradientMagnitudeFilter = GradientFilterType::New(); gradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); gradientMagnitudeFilter->SetSigma(m_Sigma); auto sigmoidFilter = SigmoidFilterType::New(); sigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); sigmoidFilter->SetAlpha(m_Alpha); sigmoidFilter->SetBeta(m_Beta); sigmoidFilter->SetOutputMinimum(0.0); sigmoidFilter->SetOutputMaximum(1.0); auto fastMarchingFilter = FastMarchingFilterType::New(); fastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); fastMarchingFilter->SetStoppingValue(m_StoppingValue); fastMarchingFilter->SetTrialPoints(trialPoints); auto thresholdFilter = ThresholdingFilterType::New(); thresholdFilter->SetLowerThreshold(m_LowerThreshold); thresholdFilter->SetUpperThreshold(m_UpperThreshold); thresholdFilter->SetOutsideValue(0); thresholdFilter->SetInsideValue(1.0); // set up pipeline smoothFilter->SetInput(inputImage); gradientMagnitudeFilter->SetInput(smoothFilter->GetOutput()); sigmoidFilter->SetInput(gradientMagnitudeFilter->GetOutput()); fastMarchingFilter->SetInput(sigmoidFilter->GetOutput()); thresholdFilter->SetInput(fastMarchingFilter->GetOutput()); thresholdFilter->Update(); if (nullptr == this->GetWorkingPlaneGeometry()) { previewImage->SetVolume((void*)(thresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); } else { mitk::Image::Pointer sliceImage = mitk::Image::New(); mitk::CastToMitkImage(thresholdFilter->GetOutput(), sliceImage); SegTool2D::WriteSliceToVolume(previewImage, this->GetWorkingPlaneGeometry(), sliceImage, timeStep, false); } } void mitk::FastMarchingBaseTool::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) { if (nullptr != inputAtTimeStep && nullptr != previewImage && m_SeedsAsPointSet.IsNotNull() && m_SeedsAsPointSet->GetSize()>0) { if (nullptr == this->GetWorkingPlaneGeometry()) { AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 3, (previewImage, timeStep, inputAtTimeStep->GetGeometry())); } else { AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 2, (previewImage, timeStep, inputAtTimeStep->GetGeometry())); } } } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h index 60059f84a7..ebec9fd0b2 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h @@ -1,118 +1,118 @@ /*============================================================================ 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 mitkFastMarchingBaseTool_h_Included #define mitkFastMarchingBaseTool_h_Included #include "mitkAutoSegmentationWithPreviewTool.h" #include "mitkDataNode.h" #include "mitkPointSet.h" #include "mitkPointSetDataInteractor.h" #include "mitkToolCommand.h" #include "itkImage.h" #include "itkFastMarchingImageFilter.h" #include namespace us { class ModuleResource; } namespace mitk { /** \brief FastMarching semgentation tool base class. The segmentation is done by setting one or more seed points on the image and adapting the time range and threshold. The pipeline is: Smoothing->GradientMagnitude->SigmoidFunction->FastMarching->Threshold The resulting binary image is seen as a segmentation of an object. For detailed documentation see ITK Software Guide section 9.3.1 Fast Marching Segmentation. */ class MITKSEGMENTATION_EXPORT FastMarchingBaseTool : public AutoSegmentationWithPreviewTool { public: mitkClassMacro(FastMarchingBaseTool, AutoSegmentationWithPreviewTool); bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /* icon stuff */ const char **GetXPM() const override; us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; void Activated() override; void Deactivated() override; /// \brief Set parameter used in Threshold filter. void SetUpperThreshold(double); /// \brief Set parameter used in Threshold filter. void SetLowerThreshold(double); /// \brief Set parameter used in Fast Marching filter. void SetStoppingValue(double); /// \brief Set parameter used in Gradient Magnitude filter. void SetSigma(double); /// \brief Set parameter used in Fast Marching filter. void SetAlpha(double); /// \brief Set parameter used in Fast Marching filter. void SetBeta(double); /// \brief Clear all seed points. void ClearSeeds(); protected: FastMarchingBaseTool(unsigned int toolDim); ~FastMarchingBaseTool() override; void ConnectActionsAndFunctions() override; /// \brief Add point action of StateMachine pattern virtual void OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent); /// \brief Delete action of StateMachine pattern virtual void OnDelete(StateMachineAction*, InteractionEvent* interactionEvent); void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; template void DoITKFastMarching(const itk::Image* inputImage, - mitk::Image* segmentation, unsigned int timeStep, const mitk::BaseGeometry* inputGeometry); + Image* segmentation, unsigned int timeStep, const BaseGeometry* inputGeometry); float m_LowerThreshold; // used in Threshold filter float m_UpperThreshold; // used in Threshold filter float m_StoppingValue; // used in Fast Marching filter float m_Sigma; // used in GradientMagnitude filter float m_Alpha; // used in Sigmoid filter float m_Beta; // used in Sigmoid filter - mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points - mitk::PointSet::Pointer m_SeedsAsPointSet; + DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points + PointSet::Pointer m_SeedsAsPointSet; private: /** Indicating if the tool is used in 2D mode (just segment the current slice) * or 3D mode (segment the whole current volume),*/ unsigned int m_ToolDimension; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index 7bce7af83d..7e263384f1 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,813 +1,813 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSegTool2D.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkPlaneGeometry.h" // Include of the new ImageExtractor #include "mitkMorphologicalOperations.h" #include "mitkPlanarCircle.h" #include "usGetModuleContext.h" // Includes for 3DSurfaceInterpolation #include "mitkImageTimeSelector.h" #include "mitkImageToContourFilter.h" #include "mitkSurfaceInterpolationController.h" // includes for resling and overwriting #include #include #include #include #include "mitkOperationEvent.h" #include "mitkUndoController.h" #include #include "mitkAbstractTransformGeometry.h" #include "mitkLabelSetImage.h" #include "mitkContourModelUtils.h" #include "itkImageRegionIterator.h" #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) bool mitk::SegTool2D::m_SurfaceInterpolationEnabled = true; mitk::SegTool2D::SegTool2D(const char *type, const us::Module *interactorModule) : Tool(type, interactorModule), m_Contourmarkername("Position") { Tool::m_EventConfig = "DisplayConfigMITKNoCrosshair.xml"; } mitk::SegTool2D::~SegTool2D() { } bool mitk::SegTool2D::FilterEvents(InteractionEvent *interactionEvent, DataNode *) { const auto *positionEvent = dynamic_cast(interactionEvent); bool isValidEvent = (positionEvent && // Only events of type mitk::InteractionPositionEvent interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard2D // Only events from the 2D renderwindows ); return isValidEvent; } bool mitk::SegTool2D::DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice) { assert(image); assert(plane); // compare normal of plane to the three axis vectors of the image Vector3D normal = plane->GetNormal(); Vector3D imageNormal0 = image->GetSlicedGeometry()->GetAxisVector(0); Vector3D imageNormal1 = image->GetSlicedGeometry()->GetAxisVector(1); Vector3D imageNormal2 = image->GetSlicedGeometry()->GetAxisVector(2); normal.Normalize(); imageNormal0.Normalize(); imageNormal1.Normalize(); imageNormal2.Normalize(); imageNormal0.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal0.GetVnlVector())); imageNormal1.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal1.GetVnlVector())); imageNormal2.SetVnlVector(vnl_cross_3d(normal.GetVnlVector(), imageNormal2.GetVnlVector())); double eps(0.00001); // axial if (imageNormal2.GetNorm() <= eps) { affectedDimension = 2; } // sagittal else if (imageNormal1.GetNorm() <= eps) { affectedDimension = 1; } // frontal else if (imageNormal0.GetNorm() <= eps) { affectedDimension = 0; } else { affectedDimension = -1; // no idea return false; } // determine slice number in image BaseGeometry *imageGeometry = image->GetGeometry(0); Point3D testPoint = imageGeometry->GetCenter(); Point3D projectedPoint; plane->Project(testPoint, projectedPoint); Point3D indexPoint; imageGeometry->WorldToIndex(projectedPoint, indexPoint); affectedSlice = ROUND(indexPoint[affectedDimension]); MITK_DEBUG << "indexPoint " << indexPoint << " affectedDimension " << affectedDimension << " affectedSlice " << affectedSlice; // check if this index is still within the image if (affectedSlice < 0 || affectedSlice >= static_cast(image->GetDimension(affectedDimension))) return false; return true; } void mitk::SegTool2D::UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, const PlaneGeometry *plane, bool detectIntersection) { std::vector slices = { SliceInformation(slice, plane, 0)}; UpdateSurfaceInterpolation(slices, workingImage, detectIntersection); } void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo) { mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; contourInfo.contourNormal = sliceInfo.plane->GetNormal(); contourInfo.contourPoint = sliceInfo.plane->GetOrigin(); mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); } void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection) { if (!m_SurfaceInterpolationEnabled) return; - //Remark the ImageTimeSelector is just needed to extract a timestep/channel of - //of the image in order to get the image dimension (time dimension and channel dimension stripped away). - //Therfore it is OK to always use time step 0 and channel 0 + //Remark: the ImageTimeSelector is just needed to extract a timestep/channel of + //the image in order to get the image dimension (time dimension and channel dimension + //stripped away). Therfore it is OK to always use time step 0 and channel 0 mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); timeSelector->SetInput(workingImage); timeSelector->SetTimeNr(0); timeSelector->SetChannelNr(0); timeSelector->Update(); const auto dimRefImg = timeSelector->GetOutput()->GetDimension(); if (dimRefImg != 3) return; std::vector contourList; contourList.reserve(sliceInfos.size()); ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); std::vector relevantSlices = sliceInfos; if (detectIntersection) { relevantSlices.clear(); for (const auto& sliceInfo : sliceInfos) { // Test whether there is something to extract or whether the slice just contains intersections of others mitk::Image::Pointer slice2 = sliceInfo.slice->Clone(); mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); contourExtractor->SetInput(slice2); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { RemoveContourFromInterpolator(sliceInfo); } else { relevantSlices.push_back(sliceInfo); } } } if (relevantSlices.empty()) return; for (const auto& sliceInfo : relevantSlices) { contourExtractor->SetInput(sliceInfo.slice); contourExtractor->Update(); mitk::Surface::Pointer contour = contourExtractor->GetOutput(); if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) { RemoveContourFromInterpolator(sliceInfo); } else { contour->DisconnectPipeline(); contourList.push_back(contour); } } mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const InteractionPositionEvent *positionEvent, const Image *image, unsigned int component /*= 0*/) { if (!positionEvent) { return nullptr; } assert(positionEvent->GetSender()); // sure, right? const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); // get the timestep of the visible part (time-wise) of the image return GetAffectedImageSliceAs2DImage(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry(), image, timeStep, component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; return SegTool2D::GetAffectedImageSliceAs2DImage(planeGeometry, image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint), component); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const PlaneGeometry *planeGeometry, const Image *image, TimeStepType timeStep, unsigned int component /*= 0*/) { if (!image || !planeGeometry) { return nullptr; } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // set to false to extract a slice reslice->SetOverwriteMode(false); reslice->Modified(); // use ExtractSliceFilter with our specific vtkImageReslice for overwriting and extracting mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(image); extractor->SetTimeStep(timeStep); extractor->SetWorldGeometry(planeGeometry); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(image->GetTimeGeometry()->GetGeometryForTimeStep(timeStep)); // additionally extract the given component // default is 0; the extractor checks for multi-component images extractor->SetComponent(component); extractor->Modified(); extractor->Update(); Image::Pointer slice = extractor->GetOutput(); return slice; } mitk::Image::Pointer mitk::SegTool2D::GetAffectedWorkingSlice(const InteractionPositionEvent *positionEvent) const { const auto workingNode = this->GetWorkingDataNode(); if (!workingNode) { return nullptr; } const auto *workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { return nullptr; } return GetAffectedImageSliceAs2DImage(positionEvent, workingImage); } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const InteractionPositionEvent *positionEvent) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto *referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(positionEvent, referenceImage); } } mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const { DataNode* referenceNode = this->GetReferenceDataNode(); if (!referenceNode) { return nullptr; } auto* referenceImage = dynamic_cast(referenceNode->GetData()); if (!referenceImage) { return nullptr; } int displayedComponent = 0; if (referenceNode->GetIntProperty("Image.Displayed Component", displayedComponent)) { // found the displayed component return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep, displayedComponent); } else { return GetAffectedImageSliceAs2DImage(planeGeometry, referenceImage, timeStep); } } void mitk::SegTool2D::Activated() { Superclass::Activated(); m_ToolManager->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); m_LastTimePointTriggered = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); } void mitk::SegTool2D::Deactivated() { m_ToolManager->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::SegTool2D::OnTimePointChangedInternal); Superclass::Deactivated(); } void mitk::SegTool2D::OnTimePointChangedInternal() { if (m_IsTimePointChangeAware && nullptr != this->GetWorkingDataNode()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (timePoint != m_LastTimePointTriggered) { m_LastTimePointTriggered = timePoint; this->OnTimePointChanged(); } } } void mitk::SegTool2D::OnTimePointChanged() { //default implementation does nothing } mitk::DataNode* mitk::SegTool2D::GetWorkingDataNode() const { if (nullptr != m_ToolManager) { return m_ToolManager->GetWorkingData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetWorkingData() const { auto node = this->GetWorkingDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } mitk::DataNode* mitk::SegTool2D::GetReferenceDataNode() const { if (nullptr != m_ToolManager) { return m_ToolManager->GetReferenceData(0); } return nullptr; } mitk::Image* mitk::SegTool2D::GetReferenceData() const { auto node = this->GetReferenceDataNode(); if (nullptr != node) { return dynamic_cast(node->GetData()); } return nullptr; } void mitk::SegTool2D::WriteBackSegmentationResult(const InteractionPositionEvent *positionEvent, const Image * segmentationResult) { if (!positionEvent) return; const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); const auto *abstractTransformGeometry( dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (planeGeometry && segmentationResult && !abstractTransformGeometry) { const auto workingNode = this->GetWorkingDataNode(); auto *image = dynamic_cast(workingNode->GetData()); const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); this->WriteBackSegmentationResult(planeGeometry, segmentationResult, timeStep); } } void mitk::SegTool2D::WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); WriteBackSegmentationResults(workingNode, { sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image * segmentationResult, TimeStepType timeStep) { if (!planeGeometry || !segmentationResult) return; SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); WriteBackSegmentationResults({ sliceInfo }, true); } void mitk::SegTool2D::WriteBackSegmentationResults(const std::vector &sliceList, bool writeSliceToVolume) { const auto workingNode = this->GetWorkingDataNode(); mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); // the first geometry is needed otherwise restoring the position is not working const auto* plane3 = dynamic_cast(dynamic_cast( m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) ->GetPlaneGeometry(0)); unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetSlice()->GetPos(); /* A cleaner solution would be to add a contour marker for each slice info. It currently does not work as the contour markers expect that the plane is always the plane of slice 0. Had not the time to do it properly no. Should be solved by T28146*/ this->AddContourmarker(plane3, slicePosition); } void mitk::SegTool2D::WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume) { if (nullptr == workingNode) { mitkThrow() << "Cannot write slice to working node. Working node is invalid."; } auto* image = dynamic_cast(workingNode->GetData()); if (nullptr == image) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } for (const auto& sliceInfo : sliceList) { if (writeSliceToVolume && nullptr != sliceInfo.plane && sliceInfo.slice.IsNotNull()) { mitk::SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } mitk::SegTool2D::UpdateSurfaceInterpolation(sliceList, image, false); // also mark its node as modified (T27308). Can be removed if T27307 // is properly solved if (workingNode != nullptr) workingNode->Modified(); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo) { SliceInformation sliceInfo(slice, planeGeometry, timeStep); WriteSliceToVolume(workingImage, sliceInfo , allowUndo); } void mitk::SegTool2D::WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo) { if (nullptr == workingImage) { mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; } DiffSliceOperation* undoOperation = nullptr; if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // Create undo operation by caching the not yet modified slices mitk::Image::Pointer originalSlice = GetAffectedImageSliceAs2DImage(sliceInfo.plane, workingImage, sliceInfo.timestep); undoOperation = new DiffSliceOperation(workingImage, originalSlice, dynamic_cast(originalSlice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); /*============= END undo/redo feature block ========================*/ } // Make sure that for reslicing and overwriting the same alogrithm is used. We can specify the mode of the vtk // reslicer vtkSmartPointer reslice = vtkSmartPointer::New(); // Set the slice as 'input' // casting const away is needed and OK as long the OverwriteMode of // mitkVTKImageOverwrite is true. // Reason: because then the input slice is not touched but // used to overwrite the input of the ExtractSliceFilter. auto noneConstSlice = const_cast(sliceInfo.slice.GetPointer()); reslice->SetInputSlice(noneConstSlice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(workingImage); extractor->SetTimeStep(sliceInfo.timestep); extractor->SetWorldGeometry(sliceInfo.plane); extractor->SetVtkOutputRequest(false); extractor->SetResliceTransformByGeometry(workingImage->GetGeometry(sliceInfo.timestep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so workingImage->Modified(); workingImage->GetVtkImageData()->Modified(); if (allowUndo) { /*============= BEGIN undo/redo feature block ========================*/ // specify the redo operation with the edited slice auto* doOperation = new DiffSliceOperation(workingImage, extractor->GetOutput(), dynamic_cast(sliceInfo.slice->GetGeometry()), sliceInfo.timestep, sliceInfo.plane); // create an operation event for the undo stack OperationEvent* undoStackItem = new OperationEvent(DiffSliceOperationApplier::GetInstance(), doOperation, undoOperation, "Segmentation"); // add it to the undo controller UndoStackItem::IncCurrObjectEventId(); UndoStackItem::IncCurrGroupEventId(); UndoController::GetCurrentUndoModel()->SetOperationEvent(undoStackItem); /*============= END undo/redo feature block ========================*/ } } void mitk::SegTool2D::SetShowMarkerNodes(bool status) { m_ShowMarkerNodes = status; } void mitk::SegTool2D::SetEnable3DInterpolation(bool enabled) { m_SurfaceInterpolationEnabled = enabled; } int mitk::SegTool2D::AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex) { if (planeGeometry == nullptr) return -1; us::ServiceReference serviceRef = us::GetModuleContext()->GetServiceReference(); PlanePositionManagerService *service = us::GetModuleContext()->GetService(serviceRef); unsigned int size = service->GetNumberOfPlanePositions(); unsigned int id = service->AddNewPlanePosition(planeGeometry, sliceIndex); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); mitk::Point2D p1; planeGeometry->Map(planeGeometry->GetCenter(), p1); mitk::Point2D p2 = p1; p2[0] -= planeGeometry->GetSpacing()[0]; p2[1] -= planeGeometry->GetSpacing()[1]; contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); contourMarker->SetPlaneGeometry(planeGeometry->Clone()); std::stringstream markerStream; auto workingNode = this->GetWorkingDataNode(); markerStream << m_Contourmarkername; markerStream << " "; markerStream << id + 1; DataNode::Pointer rotatedContourNode = DataNode::New(); rotatedContourNode->SetData(contourMarker); rotatedContourNode->SetProperty("name", StringProperty::New(markerStream.str())); rotatedContourNode->SetProperty("isContourMarker", BoolProperty::New(true)); rotatedContourNode->SetBoolProperty("PlanarFigureInitializedWindow", true, m_LastEventSender); rotatedContourNode->SetProperty("includeInBoundingBox", BoolProperty::New(false)); rotatedContourNode->SetProperty("helper object", mitk::BoolProperty::New(!m_ShowMarkerNodes)); rotatedContourNode->SetProperty("planarfigure.drawcontrolpoints", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawname", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawoutline", BoolProperty::New(false)); rotatedContourNode->SetProperty("planarfigure.drawshadow", BoolProperty::New(false)); if (planeGeometry) { if (id == size) { m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); } else { mitk::NodePredicateProperty::Pointer isMarker = mitk::NodePredicateProperty::New("isContourMarker", mitk::BoolProperty::New(true)); mitk::DataStorage::SetOfObjects::ConstPointer markers = m_ToolManager->GetDataStorage()->GetDerivations(workingNode, isMarker); for (auto iter = markers->begin(); iter != markers->end(); ++iter) { std::string nodeName = (*iter)->GetName(); unsigned int t = nodeName.find_last_of(" "); unsigned int markerId = atof(nodeName.substr(t + 1).c_str()) - 1; if (id == markerId) { return id; } } m_ToolManager->GetDataStorage()->Add(rotatedContourNode, workingNode); } } return id; } void mitk::SegTool2D::InteractiveSegmentationBugMessage(const std::string &message) const { MITK_ERROR << "********************************************************************************" << std::endl << " " << message << std::endl << "********************************************************************************" << std::endl << " " << std::endl << " If your image is rotated or the 2D views don't really contain the patient image, try to press the " "button next to the image selection. " << std::endl << " " << std::endl << " Please file a BUG REPORT: " << std::endl << " https://phabricator.mitk.org/" << std::endl << " Contain the following information:" << std::endl << " - What image were you working on?" << std::endl << " - Which region of the image?" << std::endl << " - Which tool did you use?" << std::endl << " - What did you do?" << std::endl << " - What happened (not)? What did you expect?" << std::endl; } template void InternalWritePreviewOnWorkingImage(itk::Image *targetSlice, const mitk::Image *sourceSlice, mitk::Image *originalImage, int overwritevalue) { typedef itk::Image SliceType; typename SliceType::Pointer sourceSliceITK; CastToItkImage(sourceSlice, sourceSliceITK); // now the original slice and the ipSegmentation-painted slice are in the same format, and we can just copy all pixels // that are non-zero typedef itk::ImageRegionIterator OutputIteratorType; typedef itk::ImageRegionConstIterator InputIteratorType; InputIteratorType inputIterator(sourceSliceITK, sourceSliceITK->GetLargestPossibleRegion()); OutputIteratorType outputIterator(targetSlice, targetSlice->GetLargestPossibleRegion()); outputIterator.GoToBegin(); inputIterator.GoToBegin(); auto *workingImage = dynamic_cast(originalImage); assert(workingImage); int activePixelValue = workingImage->GetActiveLabel()->GetValue(); if (activePixelValue == 0) // if exterior is the active label { while (!outputIterator.IsAtEnd()) { if (inputIterator.Get() != 0) { outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } else if (overwritevalue != 0) // if we are not erasing { while (!outputIterator.IsAtEnd()) { auto targetValue = static_cast(outputIterator.Get()); if (inputIterator.Get() != 0) { if (!workingImage->GetLabel(targetValue)->GetLocked()) { outputIterator.Set(overwritevalue); } } if (targetValue == overwritevalue) { outputIterator.Set(inputIterator.Get()); } ++outputIterator; ++inputIterator; } } else // if we are erasing { while (!outputIterator.IsAtEnd()) { const int targetValue = outputIterator.Get(); if (inputIterator.Get() != 0) { if (targetValue == activePixelValue) outputIterator.Set(overwritevalue); } ++outputIterator; ++inputIterator; } } } void mitk::SegTool2D::WritePreviewOnWorkingImage( Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue) { if (nullptr == targetSlice) { mitkThrow() << "Cannot write preview on working image. Target slice does not point to a valid instance."; } if (nullptr == sourceSlice) { mitkThrow() << "Cannot write preview on working image. Source slice does not point to a valid instance."; } if (nullptr == workingImage) { mitkThrow() << "Cannot write preview on working image. Working image does not point to a valid instance."; } auto constVtkSource = sourceSlice->GetVtkImageData(); /*Need to const cast because Vtk interface does not support const correctly. (or I am not experienced enough to use it correctly)*/ auto nonConstVtkSource = const_cast(constVtkSource); ContourModelUtils::FillSliceInSlice(nonConstVtkSource, targetSlice->GetVtkImageData(), workingImage, paintingPixelValue); } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.h b/Modules/Segmentation/Interactions/mitkSegTool2D.h index 82ac07b04f..5c27499487 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,296 +1,298 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegTool2D_h_Included #define mitkSegTool2D_h_Included #include "mitkCommon.h" #include "mitkImage.h" #include "mitkTool.h" #include #include "mitkInteractionPositionEvent.h" #include "mitkInteractionConst.h" #include "mitkPlanePositionManager.h" #include "mitkRestorePlanePositionOperation.h" #include namespace mitk { class BaseRenderer; /** \brief Abstract base class for segmentation tools. \sa Tool \ingroup Interaction \ingroup ToolManagerEtAl Implements 2D segmentation specific helper methods, that might be of use to all kind of 2D segmentation tools. At the moment these are: - Determination of the slice where the user paints upon (DetermineAffectedImageSlice) - Projection of a 3D contour onto a 2D plane/slice SegTool2D tries to structure the interaction a bit. If you pass "PressMoveRelease" as the interaction type of your derived tool, you might implement the methods OnMousePressed, OnMouseMoved, and OnMouseReleased. Yes, your guess about when they are called is correct. \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT SegTool2D : public Tool { public: mitkClassMacro(SegTool2D, Tool); /** \brief Calculates for a given Image and PlaneGeometry, which slice of the image (in index corrdinates) is meant by the plane. \return false, if no slice direction seems right (e.g. rotated planes) \param image \param plane \param affectedDimension The image dimension, which is constant for all points in the plane, e.g. Axial --> 2 \param affectedSlice The index of the image slice */ static bool DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice); /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param slice the slice from which the contour should be extracted * @param workingImage the segmentation image * @param plane the plane in which the slice lies * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, const PlaneGeometry *plane, bool detectIntersection); /** - * \brief Extract the slice of an image that the user just scribbles on. The given component denotes the vector component of a dwi-image. + * \brief Extract the slice of an image that the user just scribbles on. The given component denotes the vector component of an vector image. * * \param positionEvent Event that specifies the plane that should be used to slice * \param image Image that should be sliced * \param timeStep TimeStep of the image that shold be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const InteractionPositionEvent* positionEvent, const Image* image, unsigned int component = 0); /** - * \brief Extract the slice of an image cut by given plane. The given component denotes the vector component of a dwi-image. + * \brief Extract the slice of an image cut by given plane. The given component denotes the vector component of a vector image. * * \param planeGeometry Geometry defining the slice that should be cut out. * \param image Image that should be sliced * \param timeStep TimeStep of the image that shold be sliced * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. * * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem * getting the image data at that position. */ static Image::Pointer GetAffectedImageSliceAs2DImage(const PlaneGeometry* planeGeometry, const Image* image, TimeStepType timeStep, unsigned int component = 0); static Image::Pointer GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, const Image* image, TimePointType timePoint, unsigned int component = 0); - /** Convinence overloaded version that can be called for a given planeGeometry, slice image and time step. + /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * Calls static WriteBackSegmentationResults*/ static void WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep); - /** Convinence overloaded version that can be called for a given planeGeometry, slice image and time step. + /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. * For more details see protected WriteSliceToVolume version.*/ static void WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo); void SetShowMarkerNodes(bool); /** * \brief Enables or disables the 3D interpolation after writing back the 2D segmentation result, and defaults to * true. */ void SetEnable3DInterpolation(bool); void Activated() override; void Deactivated() override; itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); protected: SegTool2D(); // purposely hidden SegTool2D(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~SegTool2D() override; /** * @brief returns the segmentation node that should be modified by the tool. */ - mitk::DataNode* GetWorkingDataNode() const; - mitk::Image* GetWorkingData() const; + DataNode* GetWorkingDataNode() const; + Image* GetWorkingData() const; - mitk::DataNode* GetReferenceDataNode() const; - mitk::Image* GetReferenceData() const; + DataNode* GetReferenceDataNode() const; + Image* GetReferenceData() const; /** * This function can be reimplemented by derived classes to react on changes of the current * time point. Default implementation does nothing.*/ virtual void OnTimePointChanged(); struct SliceInformation { mitk::Image::ConstPointer slice; const mitk::PlaneGeometry *plane; mitk::TimeStepType timestep; SliceInformation() {} SliceInformation(const mitk::Image *slice, const mitk::PlaneGeometry *plane, mitk::TimeStepType timestep) { this->slice = slice; this->plane = plane; this->timestep = timestep; } }; /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param sliceInfos vector of slice information instances from which the contours should be extracted * @param workingImage the segmentation image * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const std::vector& sliceInfos, const Image* workingImage, bool detectIntersection); /** - * \brief Filters events that cannot be handle by 2D segmentation tools + * \brief Filters events that cannot be handled by 2D segmentation tools * - * Current an event is discarded if it was not sent by a 2D renderwindow and if it is + * Currently an event is discarded if it was not sent by a 2D renderwindow and if it is * not of type InteractionPositionEvent */ bool FilterEvents(InteractionEvent *interactionEvent, DataNode *dataNode) override; /** \brief Extract the slice of the currently selected working image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no working image is selected. */ Image::Pointer GetAffectedWorkingSlice(const InteractionPositionEvent *) const; /** \brief Extract the slice of the currently selected reference image that the user just scribbles on. \return nullptr if SegTool2D is either unable to determine which slice was affected, or if there was some problem getting the image data at that position, or just no reference image is selected. */ Image::Pointer GetAffectedReferenceSlice(const InteractionPositionEvent *) const; /** Overload version that gets the reference slice passed on the passed plane geometry and timestep.*/ Image::Pointer GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const; - /** Convinence version that can be called for a given event (which is used to deduce timepoint and plane) and a slice image. + /** Convenience version that can be called for a given event (which is used to deduce timepoint and plane) and a slice image. * Calls non static WriteBackSegmentationResults*/ void WriteBackSegmentationResult(const InteractionPositionEvent *, const Image* segmentationResult); - /** Convinence version that can be called for a given planeGeometry, slice image and time step. + /** Convenience version that can be called for a given planeGeometry, slice image and time step. * Calls non static WriteBackSegmentationResults*/ void WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image* segmentationResult, TimeStepType timeStep); /** Overloaded version that calls the static version and also adds the contour markers.*/ void WriteBackSegmentationResults(const std::vector &sliceList, bool writeSliceToVolume = true); /** \brief Writes all provided source slices into the data of the passed workingNode. * The function does the following: 1) for every passed slice write it to workingNode (and generate and undo/redo step); * 2) update the surface interpolation and 3) marke the node as modified. * @param workingNode Pointer to the node that contains the working image. * @param sliceList Vector of all slices that should be written into the workingNode. - * @param writeSliceToVolume. If set to falso the write operation (WriteSliceToVolume will be skipped) + * @param writeSliceToVolume If set to false the write operation (WriteSliceToVolume will be skipped) * and only the surface interpolation will be updated. * @pre workingNode must point to a valid instance and contain an image instance as data.*/ static void WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume = true); /** Writes the provided source slice into the target slice with the given pixel value. * If passed workingImage is a LabelSetImage the label set rules will be applied when * writing all non zero source pixels into the target slice (e.g. locked lables will not be touched) * with the given paintingPixelValue. * @param targetSlice Pointer to the slice that should be filled with the content of the sourceSlice. * @param sourceSlice Pointer to the slice that is the source/preview every pixel will be (tried to be) transfered . - * @param workingImage. Will be used to check if LabeSetImageRules have to be applied and the label set state. + * @param workingImage Will be used to check if LabeSetImageRules have to be applied and the label set state. * @param paintingPixelValue Value that will be used to paint onto target slice. * @pre targetSlice must point to a valid instance. * @pre sourceSlice must point to a valid instance. * @pre workingImage must point to a valid instance.*/ static void WritePreviewOnWorkingImage( Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue); /** Writes a provided slice into the passed working image. The content of working image that is covered - * by the slice, will be completly overwritten. If asked for it also generates the needed + * by the slice will be completly overwritten. If asked for it also generates the needed * undo/redo steps. * @param workingImage Pointer to the image that is the target of the write operation. * @param sliceInfo SliceInfo instance that containes the slice image, the defining plane geometry and time step. - * @param allowUndo Indicates if undo/redo operations should be registered for the write operation. - * true: undo/redo will be generated. + * @param allowUndo Indicates if undo/redo operations should be registered for the write operation + * performed by this call. true: undo/redo will be generated; false: no undo/redo will be generated, so + * this operation cannot be revoked by the user. * @pre workingImage must point to a valid instance.*/ static void WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo); /** \brief Adds a new node called Contourmarker to the datastorage which holds a mitk::PlanarFigure. - By selecting this node the slicestack will be reoriented according to passed the PlanarFigure's Geometry + By selecting this node the slicestack will be reoriented according to the passed + PlanarFigure's Geometry */ int AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex); void InteractiveSegmentationBugMessage(const std::string &message) const; BaseRenderer *m_LastEventSender = nullptr; unsigned int m_LastEventSlice = 0; itkGetMacro(LastTimePointTriggered, TimePointType); private: /** Internal method that gets triggered as soon as the tool manager indicates a * time point change. If the time point has changed since last time and tool * is set to be time point change aware, OnTimePointChanged() will be called.*/ void OnTimePointChangedInternal(); static void RemoveContourFromInterpolator(const SliceInformation& sliceInfo); // The prefix of the contourmarkername. Suffix is a consecutive number const std::string m_Contourmarkername; bool m_ShowMarkerNodes = false; static bool m_SurfaceInterpolationEnabled; bool m_IsTimePointChangeAware = true; TimePointType m_LastTimePointTriggered = 0.; }; } // namespace #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp index 58c966cfd3..a27c23bfec 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp @@ -1,147 +1,153 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "QmitkAutoSegmentationToolGUIBase.h" #include #include #include #include #include bool DefaultEnableConfirmSegBtnFunction(bool enabled) { return enabled; } QmitkAutoSegmentationToolGUIBase::QmitkAutoSegmentationToolGUIBase(bool mode2D) : QmitkToolGUI(), m_EnableConfirmSegBtnFnc(DefaultEnableConfirmSegBtnFunction), m_Mode2D(mode2D) { connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkAutoSegmentationToolGUIBase::~QmitkAutoSegmentationToolGUIBase() { if (m_Tool.IsNotNull()) { m_Tool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkAutoSegmentationToolGUIBase::BusyStateChanged); } } void QmitkAutoSegmentationToolGUIBase::OnNewToolAssociated(mitk::Tool *tool) { if (m_Tool.IsNotNull()) { this->DisconnectOldTool(m_Tool); } m_Tool = dynamic_cast(tool); if (nullptr == m_MainLayout) { // create the visible widgets m_MainLayout = new QVBoxLayout(this); m_ConfirmSegBtn = new QPushButton("Confirm Segmentation", this); connect(m_ConfirmSegBtn, SIGNAL(clicked()), this, SLOT(OnAcceptPreview())); m_CheckProcessAll = new QCheckBox("Process all time steps", this); m_CheckProcessAll->setChecked(false); - m_CheckProcessAll->setToolTip("Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step."); + m_CheckProcessAll->setToolTip("Process all time steps of the dynamic segmentation and not just the currently visible time step."); m_CheckProcessAll->setVisible(!m_Mode2D); + //remark: keept m_CheckProcessAll deactivated in 2D because in this refactoring + //it should be kept to the status quo and it was not clear how interpolation + //would behave. m_CheckCreateNew = new QCheckBox("Create as new segmentation", this); m_CheckCreateNew->setChecked(false); m_CheckCreateNew->setToolTip("Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected."); m_CheckCreateNew->setVisible(!m_Mode2D); + //remark: keept m_CheckCreateNew deactivated in 2D because in this refactoring + //it should be kept to the status quo and it was not clear how interpolation + //would behave. this->InitializeUI(m_MainLayout); m_MainLayout->addWidget(m_ConfirmSegBtn); m_MainLayout->addWidget(m_CheckProcessAll); m_MainLayout->addWidget(m_CheckCreateNew); } if (m_Tool.IsNotNull()) { this->ConnectNewTool(m_Tool); } } void QmitkAutoSegmentationToolGUIBase::OnAcceptPreview() { if (m_Tool.IsNotNull()) { if (m_CheckCreateNew->isChecked()) { m_Tool->SetOverwriteExistingSegmentation(false); } else { m_Tool->SetOverwriteExistingSegmentation(true); } m_Tool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); m_ConfirmSegBtn->setEnabled(false); m_Tool->ConfirmSegmentation(); } } void QmitkAutoSegmentationToolGUIBase::DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool) { oldTool->CurrentlyBusy -= mitk::MessageDelegate1(this, &QmitkAutoSegmentationToolGUIBase::BusyStateChanged); } void QmitkAutoSegmentationToolGUIBase::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) { newTool->CurrentlyBusy += mitk::MessageDelegate1(this, &QmitkAutoSegmentationToolGUIBase::BusyStateChanged); newTool->SetOverwriteExistingSegmentation(true); m_CheckProcessAll->setVisible(newTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); } void QmitkAutoSegmentationToolGUIBase::InitializeUI(QBoxLayout* /*mainLayout*/) { //default implementation does nothing } void QmitkAutoSegmentationToolGUIBase::BusyStateChanged(bool isBusy) { if (isBusy) { QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); } else { QApplication::restoreOverrideCursor(); } this->EnableWidgets(!isBusy); } void QmitkAutoSegmentationToolGUIBase::EnableWidgets(bool enabled) { if (nullptr != m_MainLayout) { if (nullptr != m_ConfirmSegBtn) { m_ConfirmSegBtn->setEnabled(m_EnableConfirmSegBtnFnc(enabled)); } if (nullptr != m_CheckProcessAll) { m_CheckProcessAll->setEnabled(enabled); } if (nullptr != m_CheckCreateNew) { m_CheckCreateNew->setEnabled(enabled); } } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp index d5ea299c95..fb99dff7bf 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp @@ -1,128 +1,129 @@ /*============================================================================ 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 "QmitkOtsuTool3DGUI.h" #include "mitkOtsuTool3D.h" #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkOtsuTool3DGUI, "") QmitkOtsuTool3DGUI::QmitkOtsuTool3DGUI() : QmitkAutoMLSegmentationToolGUIBase() { } void QmitkOtsuTool3DGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) { Superclass::ConnectNewTool(newTool); newTool->IsTimePointChangeAwareOff(); + m_FirstAccept = true; } void QmitkOtsuTool3DGUI::InitializeUI(QBoxLayout* mainLayout) { m_Controls.setupUi(this); mainLayout->addLayout(m_Controls.verticalLayout); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnSpinboxValueAccept())); connect(m_Controls.m_Spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnRegionSpinboxChanged(int))); connect(m_Controls.advancedSettingsButton, SIGNAL(toggled(bool)), this, SLOT(OnAdvancedSettingsButtonToggled(bool))); this->OnAdvancedSettingsButtonToggled(false); Superclass::InitializeUI(mainLayout); } void QmitkOtsuTool3DGUI::OnRegionSpinboxChanged(int numberOfRegions) { // we have to change to minimum number of histogram bins accordingly int curBinValue = m_Controls.m_BinsSpinBox->value(); if (curBinValue < numberOfRegions) m_Controls.m_BinsSpinBox->setValue(numberOfRegions); } void QmitkOtsuTool3DGUI::OnAdvancedSettingsButtonToggled(bool toggled) { m_Controls.m_ValleyCheckbox->setVisible(toggled); m_Controls.binLabel->setVisible(toggled); m_Controls.m_BinsSpinBox->setVisible(toggled); auto tool = this->GetConnectedToolAs(); if (toggled && nullptr != tool) { int max = tool->GetMaxNumberOfBins(); if (max >= m_Controls.m_BinsSpinBox->minimum()) { m_Controls.m_BinsSpinBox->setMaximum(max); } } } void QmitkOtsuTool3DGUI::OnSpinboxValueAccept() { - if (m_NumberOfRegions == m_Controls.m_Spinbox->value() && - m_UseValleyEmphasis == m_Controls.m_ValleyCheckbox->isChecked() && - m_NumberOfBins == m_Controls.m_BinsSpinBox->value()) - return; - auto tool = this->GetConnectedToolAs(); if (nullptr != tool) { + if (m_FirstAccept || + (tool->GetNumberOfRegions() == m_Controls.m_Spinbox->value() && + tool->GetUseValley() == m_Controls.m_ValleyCheckbox->isChecked() && + tool->GetNumberOfBins() == m_Controls.m_BinsSpinBox->value())) + return; + + m_FirstAccept = false; + try { int proceed; QMessageBox *messageBox = new QMessageBox(QMessageBox::Question, nullptr, "The otsu segmentation computation may take several minutes depending " "on the number of Regions you selected. Proceed anyway?", QMessageBox::Ok | QMessageBox::Cancel); if (m_Controls.m_Spinbox->value() >= 5) { proceed = messageBox->exec(); if (proceed != QMessageBox::Ok) return; } - m_NumberOfRegions = m_Controls.m_Spinbox->value(); - m_UseValleyEmphasis = m_Controls.m_ValleyCheckbox->isChecked(); - m_NumberOfBins = m_Controls.m_BinsSpinBox->value(); - tool->SetNumberOfRegions(m_NumberOfRegions); - tool->SetUseValley(m_UseValleyEmphasis); - tool->SetNumberOfBins(m_NumberOfBins); + tool->SetNumberOfRegions(m_Controls.m_Spinbox->value()); + tool->SetUseValley(m_Controls.m_ValleyCheckbox->isChecked()); + tool->SetNumberOfBins(m_Controls.m_BinsSpinBox->value()); tool->UpdatePreview(); } catch (...) { this->setCursor(Qt::ArrowCursor); QMessageBox *messageBox = new QMessageBox(QMessageBox::Critical, nullptr, "itkOtsuFilter error: image dimension must be in {2, 3} and no RGB images can be handled."); messageBox->exec(); delete messageBox; return; } this->SetLabelSetPreview(tool->GetMLPreview()); tool->IsTimePointChangeAwareOn(); } } void QmitkOtsuTool3DGUI::EnableWidgets(bool enabled) { Superclass::EnableWidgets(enabled); m_Controls.m_ValleyCheckbox->setEnabled(enabled); m_Controls.binLabel->setEnabled(enabled); m_Controls.m_BinsSpinBox->setEnabled(enabled); m_Controls.previewButton->setEnabled(enabled); } diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h index 65e98c8dcf..b6afd8dee8 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h @@ -1,66 +1,64 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkOtsuTool3DGUI_h_Included #define QmitkOtsuTool3DGUI_h_Included #include "QmitkAutoMLSegmentationToolGUIBase.h" #include "ui_QmitkOtsuToolWidgetControls.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::. \sa mitk:: This GUI shows ... Last contributor: $Author$ */ class MITKSEGMENTATIONUI_EXPORT QmitkOtsuTool3DGUI : public QmitkAutoMLSegmentationToolGUIBase { Q_OBJECT public: mitkClassMacro(QmitkOtsuTool3DGUI, QmitkAutoMLSegmentationToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots : void OnSpinboxValueAccept(); void OnRegionSpinboxChanged(int); private slots: void OnAdvancedSettingsButtonToggled(bool toggled); protected: QmitkOtsuTool3DGUI(); ~QmitkOtsuTool3DGUI() = default; void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; void InitializeUI(QBoxLayout* mainLayout) override; void EnableWidgets(bool enabled) override; Ui_QmitkOtsuToolWidgetControls m_Controls; - int m_NumberOfRegions = 0; - bool m_UseValleyEmphasis = false; - int m_NumberOfBins = 128; + bool m_FirstAccept = true; }; #endif