diff --git a/Examples/Plugins/org.mitk.example.gui.pcaexample/src/internal/PCAExample.cpp b/Examples/Plugins/org.mitk.example.gui.pcaexample/src/internal/PCAExample.cpp index de72b6f722..f1f35e83a3 100644 --- a/Examples/Plugins/org.mitk.example.gui.pcaexample/src/internal/PCAExample.cpp +++ b/Examples/Plugins/org.mitk.example.gui.pcaexample/src/internal/PCAExample.cpp @@ -1,167 +1,167 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ // Blueberry #include #include // Qmitk #include "PCAExample.h" // Qt #include // mitk image #include const std::string PCAExample::VIEW_ID = "org.mitk.views.pcaexample"; void PCAExample::SetFocus() { m_Controls.buttonPerformImageProcessing->setFocus(); } void PCAExample::CreateQtPartControl(QWidget *parent) { // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); connect(m_Controls.buttonPerformImageProcessing, SIGNAL(clicked()), this, SLOT(BtnPerfomPCAClicked())); //initialize point set widget and point set node mitk::DataNode::Pointer PointSetNode = mitk::DataNode::New(); PointSetNode->SetName("PCA Example Pointset"); mitk::PointSet::Pointer newPtSet = mitk::PointSet::New(); PointSetNode->SetData(newPtSet); m_Controls.m_pointSetWidget->SetPointSetNode(PointSetNode); this->GetDataStorage()->Add(PointSetNode); } PCAExample::PCAExample() { } PCAExample::~PCAExample() { //clean up mitk::DataNode::Pointer ptSetNode = m_Controls.m_pointSetWidget->GetPointSetNode(); m_Controls.m_pointSetWidget->SetPointSetNode(nullptr); this->GetDataStorage()->Remove(ptSetNode); this->GetDataStorage()->Remove(m_Axis1Node); this->GetDataStorage()->Remove(m_Axis2Node); this->GetDataStorage()->Remove(m_Axis3Node); } void PCAExample::OnSelectionChanged(berry::IWorkbenchPart::Pointer /*source*/, const QList &nodes) { // iterate all selected objects, adjust warning visibility foreach (mitk::DataNode::Pointer node, nodes) { if (node.IsNotNull() && dynamic_cast(node->GetData())) { m_Controls.buttonPerformImageProcessing->setEnabled(true); return; } } } void PCAExample::BtnPerfomPCAClicked() { std::vector eigenVectors; std::vector eigenValues; mitk::Vector3D mean; bool success = comutePCA(m_Controls.m_pointSetWidget->GetPointSet(), eigenVectors, eigenValues, mean); this->showEigenvectors(eigenVectors, eigenValues, mean); MITK_INFO << "PCA: " << success; } bool PCAExample::comutePCA(mitk::PointSet::Pointer input, std::vector &eigenVectors, std::vector &eigenValues, mitk::Vector3D &pointsMean) { //Step 1: Construct data matrix vnl_matrix dataMatrix(3, input->GetSize(), 0.0); int size = input->GetSize(); for (int i=0; iGetPoint(i)[0]; dataMatrix[1][i] = input->GetPoint(i)[1]; dataMatrix[2][i] = input->GetPoint(i)[2]; } //Step 2: Remove average for each row (Mittelwertbefreiung) mitk::Vector3D mean; for (int i = 0; i < size; i++) { mean += mitk::Vector3D(dataMatrix.get_column(i)); } mean /= size; for (int i = 0; i covMatrix = (1.0 / (size - 1.0)) * dataMatrix * dataMatrix.transpose(); //Step 4: Singular value composition vnl_svd svd(covMatrix); //Store results and print them to the console MITK_INFO << "DataMatrix: " << "\n" << dataMatrix; MITK_INFO << "CovMatrix: " << "\n" << covMatrix; for (int i = 0; i < 3; i++) { eigenVectors.push_back(svd.U().get_column(i)); eigenValues.push_back(sqrt(svd.W(i))); MITK_INFO << "Eigenvector " << i << ": " << eigenVectors.at(i); MITK_INFO << "Eigenvalue " << i << ": " << eigenValues.at(i); } //Compute center of points for (int i = 0; i < size; i++) { pointsMean += input->GetPoint(i).GetVectorFromOrigin(); } pointsMean /= size; return true; } void PCAExample::showEigenvectors(std::vector eigenVectors, std::vector eigenValues, mitk::Vector3D center) { m_Axis1Node = mitk::DataNode::New(); m_Axis1Node->SetName("Eigenvector 1"); mitk::PointSet::Pointer axis1 = mitk::PointSet::New(); - axis1->InsertPoint(0, center); - axis1->InsertPoint(1, (center + eigenVectors.at(0)*eigenValues.at(0))); + axis1->InsertPoint(0, mitk::Point3D(center)); + axis1->InsertPoint(1, mitk::Point3D(center + eigenVectors.at(0)*eigenValues.at(0))); m_Axis1Node->SetData(axis1); m_Axis1Node->SetBoolProperty("show contour", true); m_Axis1Node->SetColor(1, 0, 0); this->GetDataStorage()->Add(m_Axis1Node); m_Axis2Node = mitk::DataNode::New(); m_Axis2Node->SetName("Eigenvector 2"); mitk::PointSet::Pointer axis2 = mitk::PointSet::New(); - axis2->InsertPoint(0, center); - axis2->InsertPoint(1, (center + eigenVectors.at(1)*eigenValues.at(1))); + axis2->InsertPoint(0, mitk::Point3D(center)); + axis2->InsertPoint(1, mitk::Point3D(center + eigenVectors.at(1)*eigenValues.at(1))); m_Axis2Node->SetData(axis2); m_Axis2Node->SetBoolProperty("show contour", true); m_Axis2Node->SetColor(1, 0, 0); this->GetDataStorage()->Add(m_Axis2Node); m_Axis3Node = mitk::DataNode::New(); m_Axis3Node->SetName("Eigenvector 3"); mitk::PointSet::Pointer axis3 = mitk::PointSet::New(); - axis3->InsertPoint(0, center); - axis3->InsertPoint(1, (center + eigenVectors.at(2)*eigenValues.at(2))); + axis3->InsertPoint(0, mitk::Point3D(center)); + axis3->InsertPoint(1, mitk::Point3D(center + eigenVectors.at(2)*eigenValues.at(2))); m_Axis3Node->SetData(axis3); m_Axis3Node->SetBoolProperty("show contour", true); m_Axis3Node->SetColor(1, 0, 0); this->GetDataStorage()->Add(m_Axis3Node); } diff --git a/Modules/ContourModel/Algorithms/mitkContourModelSubDivisionFilter.cpp b/Modules/ContourModel/Algorithms/mitkContourModelSubDivisionFilter.cpp index dec4e7d708..a7840e854f 100644 --- a/Modules/ContourModel/Algorithms/mitkContourModelSubDivisionFilter.cpp +++ b/Modules/ContourModel/Algorithms/mitkContourModelSubDivisionFilter.cpp @@ -1,202 +1,202 @@ /*============================================================================ 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 "mitkContourModelSubDivisionFilter.h" #include #include mitk::ContourModelSubDivisionFilter::ContourModelSubDivisionFilter() { OutputType::Pointer output = dynamic_cast(this->MakeOutput(0).GetPointer()); this->SetNumberOfRequiredInputs(1); this->SetNumberOfIndexedOutputs(1); this->SetNthOutput(0, output.GetPointer()); this->m_InterpolationIterations = 4; } mitk::ContourModelSubDivisionFilter::~ContourModelSubDivisionFilter() { } void mitk::ContourModelSubDivisionFilter::SetInput(const mitk::ContourModelSubDivisionFilter::InputType *input) { this->SetInput(0, input); } void mitk::ContourModelSubDivisionFilter::SetInput(unsigned int idx, const mitk::ContourModelSubDivisionFilter::InputType *input) { if (idx + 1 > this->GetNumberOfInputs()) { this->SetNumberOfRequiredInputs(idx + 1); } if (input != static_cast(this->ProcessObject::GetInput(idx))) { this->ProcessObject::SetNthInput(idx, const_cast(input)); this->Modified(); } } const mitk::ContourModelSubDivisionFilter::InputType *mitk::ContourModelSubDivisionFilter::GetInput(void) { if (this->GetNumberOfInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(0)); } const mitk::ContourModelSubDivisionFilter::InputType *mitk::ContourModelSubDivisionFilter::GetInput(unsigned int idx) { if (this->GetNumberOfInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(idx)); } void mitk::ContourModelSubDivisionFilter::GenerateData() { mitk::ContourModel::Pointer input = const_cast(this->GetInput(0)); // mitk::ContourModelSubDivisionFilter::OutputType::Pointer outputContour = this->GetOutput(); mitk::ContourModel::Pointer contour(input); - auto timestep = static_cast(input->GetTimeSteps()); + auto timestep = static_cast(input->GetTimeSteps()); - for (int currentTimestep = 0; currentTimestep < timestep; currentTimestep++) + for (decltype(timestep) currentTimestep = 0; currentTimestep < timestep; currentTimestep++) { if (input->GetNumberOfVertices(currentTimestep) >= 4) { for (int iterations = 0; iterations < this->m_InterpolationIterations; iterations++) { auto it = contour->IteratorBegin(); auto end = contour->IteratorEnd(); auto first = contour->IteratorBegin(); auto last = contour->IteratorEnd() - 1; // tempory contour to store result of a subdivision iteration mitk::ContourModel::Pointer tempContour = mitk::ContourModel::New(); // insert subpoints while (it != end) { // add the current point to the temp contour tempContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, currentTimestep); // control points for interpolation auto Ci = it; InputType::VertexIterator CiPlus1; InputType::VertexIterator CiPlus2; InputType::VertexIterator CiMinus1; // consider all possible cases if (it == first) { if (input->IsClosed(currentTimestep)) { CiPlus1 = it + 1; CiPlus2 = it + 2; CiMinus1 = last; } else { CiPlus1 = it + 1; CiPlus2 = it + 2; CiMinus1 = it; } } else if (it == last) { if (input->IsClosed(currentTimestep)) { CiPlus1 = first; CiPlus2 = first + 1; CiMinus1 = it - 1; } else { // don't add point after last break; } } else if (it == (last - 1)) { if (input->IsClosed(currentTimestep)) { CiPlus1 = it + 1; CiPlus2 = first; CiMinus1 = it - 1; } else { CiPlus1 = it + 1; CiPlus2 = it + 1; CiMinus1 = it - 1; } } else { CiPlus1 = it + 1; CiPlus2 = it + 2; CiMinus1 = it - 1; } /* F2i = Ci * F2i+1 = -1/16Ci-1 + 9/16Ci + 9/16Ci+1 - 1/16Ci+2 */ mitk::Point3D subpoint; mitk::Point3D a; a[0] = (-1.0 / 16.0) * (*CiMinus1)->Coordinates[0]; a[1] = (-1.0 / 16.0) * (*CiMinus1)->Coordinates[1]; a[2] = (-1.0 / 16.0) * (*CiMinus1)->Coordinates[2]; mitk::Point3D b; b[0] = (9.0 / 16.0) * (*Ci)->Coordinates[0]; b[1] = (9.0 / 16.0) * (*Ci)->Coordinates[1]; b[2] = (9.0 / 16.0) * (*Ci)->Coordinates[2]; mitk::Point3D c; c[0] = (9.0 / 16.0) * (*CiPlus1)->Coordinates[0]; c[1] = (9.0 / 16.0) * (*CiPlus1)->Coordinates[1]; c[2] = (9.0 / 16.0) * (*CiPlus1)->Coordinates[2]; mitk::Point3D d; d[0] = (-1.0 / 16.0) * (*CiPlus2)->Coordinates[0]; d[1] = (-1.0 / 16.0) * (*CiPlus2)->Coordinates[1]; d[2] = (-1.0 / 16.0) * (*CiPlus2)->Coordinates[2]; subpoint[0] = a[0] + b[0] + c[0] + d[0]; subpoint[1] = a[1] + b[1] + c[1] + d[1]; subpoint[2] = a[2] + b[2] + c[2] + d[2]; InputType::VertexType subdivisionPoint(subpoint, false); // add the new subdivision point to our tempContour tempContour->AddVertex(subdivisionPoint.Coordinates, currentTimestep); it++; } // set the interpolated contour as the contour for the next iteration contour = tempContour; } } else { // filter not executeable - set input to output contour = input; } } // somehow the isClosed property is not set via copy constructor contour->SetClosed(input->IsClosed()); this->SetNthOutput(0, contour); } diff --git a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp index c2eff3c43f..f96f7a385e 100755 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.cpp @@ -1,228 +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( - Image *slice, ContourModel *contourIn3D, bool, bool) + 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()); + 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, ContourModel *contourIn2D, bool) + 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()); + 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( - ContourModel *projectedContour, Image *sliceImage, Image::Pointer workingImage, int paintingPixelValue) + const ContourModel *projectedContour, Image *sliceImage, const Image* workingImage, int paintingPixelValue) { FillContourInSlice(projectedContour, 0, sliceImage, workingImage, paintingPixelValue); } void mitk::ContourModelUtils::FillContourInSlice( - ContourModel *projectedContour, unsigned int t, Image *sliceImage, Image::Pointer 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(t)) + if (nullptr == surface->GetVtkPolyData(contourTimeStep)) { MITK_WARN << "Could not create surface from contour model."; return; } auto surface2D = vtkSmartPointer::New(); - surface2D->SetPoints(surface->GetVtkPolyData(t)->GetPoints()); - surface2D->SetLines(surface->GetVtkPolyData(t)->GetLines()); + 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, mitk::Image::Pointer image, int paintingPixelValue) + vtkSmartPointer filledImage, vtkSmartPointer resultImage, const Image* image, int paintingPixelValue) { - auto labelImage = dynamic_cast(image.GetPointer()); + 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); + resultContour->AddVertex(*vertex, t); }); return resultContour; } -int mitk::ContourModelUtils::GetActivePixelValue(mitk::Image* workingImage) +int mitk::ContourModelUtils::GetActivePixelValue(const Image* workingImage) { - auto* labelSetImage = dynamic_cast(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 61a33c0c87..cfc364b9df 100644 --- a/Modules/ContourModel/Algorithms/mitkContourModelUtils.h +++ b/Modules/ContourModel/Algorithms/mitkContourModelUtils.h @@ -1,106 +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(Image *slice, - ContourModel *contourIn3D, + 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, - ContourModel *contourIn2D, + const ContourModel *contourIn2D, bool correctionForIpSegmentation = false); /** - \brief Fill a contour in a 2D slice with a specified pixel value at time step 0. + \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(ContourModel *projectedContour, + static void FillContourInSlice(const ContourModel *projectedContour, Image *sliceImage, - mitk::Image::Pointer workingImage, + const Image* workingImage, int paintingPixelValue = 1); /** - \brief Fill a contour in a 2D slice with a specified pixel value at a given time step. + \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(ContourModel *projectedContour, - unsigned int timeStep, + static void FillContourInSlice(const ContourModel *projectedContour, + TimeStepType contourTimeStep, Image *sliceImage, - mitk::Image::Pointer workingImage, + 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, - mitk::Image::Pointer image, + 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(mitk::Image* workingImage); + static int GetActivePixelValue(const Image* workingImage); protected: ContourModelUtils(); ~ContourModelUtils() override; }; } #endif diff --git a/Modules/ContourModel/DataManagement/mitkContourElement.cpp b/Modules/ContourModel/DataManagement/mitkContourElement.cpp index fd14eacedb..bd906acb7b 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourElement.cpp @@ -1,471 +1,419 @@ /*============================================================================ 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 -mitk::ContourElement::ContourElement() +bool mitk::ContourElement::ContourModelVertex::operator ==(const ContourModelVertex& other) const { - this->m_Vertices = new VertexListType(); - this->m_IsClosed = false; + return this->Coordinates == other.Coordinates && this->IsControlPoint == other.IsControlPoint; +} + +mitk::ContourElement::ConstVertexIterator mitk::ContourElement::ConstIteratorBegin() const +{ + return this->begin(); +} + +mitk::ContourElement::ConstVertexIterator mitk::ContourElement::ConstIteratorEnd() const +{ + return this->end(); +} + +mitk::ContourElement::VertexIterator mitk::ContourElement::IteratorBegin() +{ + return this->begin(); +} + +mitk::ContourElement::VertexIterator mitk::ContourElement::IteratorEnd() +{ + return this->end(); +} + +mitk::ContourElement::ConstVertexIterator mitk::ContourElement::begin() const +{ + return this->m_Vertices.begin(); +} + +mitk::ContourElement::ConstVertexIterator mitk::ContourElement::end() const +{ + return this->m_Vertices.end(); +} + +mitk::ContourElement::VertexIterator mitk::ContourElement::begin() +{ + return this->m_Vertices.begin(); +} + +mitk::ContourElement::VertexIterator mitk::ContourElement::end() +{ + return this->m_Vertices.end(); } mitk::ContourElement::ContourElement(const mitk::ContourElement &other) - : itk::LightObject(), m_Vertices(other.m_Vertices), m_IsClosed(other.m_IsClosed) + : itk::LightObject(), m_IsClosed(other.m_IsClosed) { + for (const auto& v : other.m_Vertices) + { + m_Vertices.push_back(new ContourModelVertex(*v)); + } } -mitk::ContourElement::~ContourElement() +mitk::ContourElement& mitk::ContourElement::operator = (const ContourElement& other) { - delete this->m_Vertices; + if (this != &other) + { + this->Clear(); + for (const auto& v : other.m_Vertices) + { + m_Vertices.push_back(new ContourModelVertex(*v)); + } + } + + this->m_IsClosed = other.m_IsClosed; + return *this; } -void mitk::ContourElement::AddVertex(mitk::Point3D &vertex, bool isControlPoint) +mitk::ContourElement::~ContourElement() { - this->m_Vertices->push_back(new VertexType(vertex, isControlPoint)); + this->Clear(); } -void mitk::ContourElement::AddVertex(VertexType &vertex) +mitk::ContourElement::VertexSizeType mitk::ContourElement::GetSize() const { - this->m_Vertices->push_back(&vertex); + return this->m_Vertices.size(); } -void mitk::ContourElement::AddVertexAtFront(mitk::Point3D &vertex, bool isControlPoint) +void mitk::ContourElement::AddVertex(const mitk::Point3D &vertex, bool isControlPoint) { - this->m_Vertices->push_front(new VertexType(vertex, isControlPoint)); + this->m_Vertices.push_back(new VertexType(vertex, isControlPoint)); } -void mitk::ContourElement::AddVertexAtFront(VertexType &vertex) +void mitk::ContourElement::AddVertexAtFront(const mitk::Point3D &vertex, bool isControlPoint) { - this->m_Vertices->push_front(&vertex); + this->m_Vertices.push_front(new VertexType(vertex, isControlPoint)); } -void mitk::ContourElement::InsertVertexAtIndex(mitk::Point3D &vertex, bool isControlPoint, int index) +void mitk::ContourElement::InsertVertexAtIndex(const mitk::Point3D &vertex, bool isControlPoint, VertexSizeType index) { if (index >= 0 && this->GetSize() > index) { - auto _where = this->m_Vertices->begin(); + auto _where = this->m_Vertices.begin(); _where += index; - this->m_Vertices->insert(_where, new VertexType(vertex, isControlPoint)); + this->m_Vertices.insert(_where, new VertexType(vertex, isControlPoint)); } } -void mitk::ContourElement::SetVertexAt(int pointId, const Point3D &point) +void mitk::ContourElement::SetVertexAt(VertexSizeType pointId, const Point3D &point) { if (pointId >= 0 && this->GetSize() > pointId) { - this->m_Vertices->at(pointId)->Coordinates = point; + this->m_Vertices[pointId]->Coordinates = point; } } -void mitk::ContourElement::SetVertexAt(int pointId, const VertexType *vertex) +void mitk::ContourElement::SetVertexAt(VertexSizeType pointId, const VertexType *vertex) { + if (nullptr == vertex) + { + mitkThrow() << "Cannot set vertex. Passed vertex instance is invalid. Index to set: " << pointId; + } + if (pointId >= 0 && this->GetSize() > pointId) { - this->m_Vertices->at(pointId)->Coordinates = vertex->Coordinates; - this->m_Vertices->at(pointId)->IsControlPoint = vertex->IsControlPoint; + this->m_Vertices[pointId]->Coordinates = vertex->Coordinates; + this->m_Vertices[pointId]->IsControlPoint = vertex->IsControlPoint; } } -mitk::ContourElement::VertexType *mitk::ContourElement::GetVertexAt(int index) +mitk::ContourElement::VertexType *mitk::ContourElement::GetVertexAt(VertexSizeType index) { - return this->m_Vertices->at(index); + return this->m_Vertices.at(index); } -bool mitk::ContourElement::IsEmpty() +const mitk::ContourElement::VertexType* mitk::ContourElement::GetVertexAt(VertexSizeType index) const { - return this->m_Vertices->empty(); + return this->m_Vertices.at(index); +} + +bool mitk::ContourElement::IsEmpty() const +{ + return this->m_Vertices.empty(); } mitk::ContourElement::VertexType *mitk::ContourElement::GetVertexAt(const mitk::Point3D &point, float eps) { /* current version iterates over the whole deque - should some kind of an octree with spatial query*/ if (eps > 0) { // currently no method with better performance is available return BruteForceGetVertexAt(point, eps); } // if eps < 0 return nullptr; } -mitk::ContourElement::VertexType *mitk::ContourElement::BruteForceGetVertexAt(const mitk::Point3D &point, float eps) +mitk::ContourElement::VertexType *mitk::ContourElement::BruteForceGetVertexAt(const mitk::Point3D &point, double eps) { if (eps > 0) { std::deque> nearestlist; - ConstVertexIterator it = this->m_Vertices->begin(); + ConstVertexIterator it = this->m_Vertices.begin(); - ConstVertexIterator end = this->m_Vertices->end(); + ConstVertexIterator end = this->m_Vertices.end(); while (it != end) { mitk::Point3D currentPoint = (*it)->Coordinates; double distance = currentPoint.EuclideanDistanceTo(point); if (distance < eps) { // if list is emtpy, add point to list if (nearestlist.size() < 1) { nearestlist.push_front(std::pair((*it)->Coordinates.EuclideanDistanceTo(point), (*it))); } // found an approximate point - check if current is closer then first in nearestlist else if (distance < nearestlist.front().first) { // found even closer vertex nearestlist.push_front(std::pair((*it)->Coordinates.EuclideanDistanceTo(point), (*it))); } } // if distance > eps it++; } // while if (nearestlist.size() > 0) { /*++++++++++++++++++++ return the nearest active point if one was found++++++++++++++++++*/ auto it = nearestlist.begin(); auto end = nearestlist.end(); while (it != end) { if ((*it).second->IsControlPoint) { return (*it).second; } it++; } /*---------------------------------------------------------------------------------------*/ // return closest point return nearestlist.front().second; } } return nullptr; } -/*mitk::ContourElement::VertexType* mitk::ContourElement::OptimizedGetVertexAt(const mitk::Point3D &point, float eps) -{ - if( (eps > 0) && (this->m_Vertices->size()>0) ) - { - int k = 1; - int dim = 3; - int nPoints = this->m_Vertices->size(); - ANNpointArray pointsArray; - ANNpoint queryPoint; - ANNidxArray indexArray; - ANNdistArray distanceArray; - ANNkd_tree* kdTree; - - queryPoint = annAllocPt(dim); - pointsArray = annAllocPts(nPoints, dim); - indexArray = new ANNidx[k]; - distanceArray = new ANNdist[k]; - - - int i = 0; - - //fill points array with our control points - for(VertexIterator it = this->m_Vertices->begin(); it != this->m_Vertices->end(); it++, i++) - { - mitk::Point3D cur = (*it)->Coordinates; - pointsArray[i][0]= cur[0]; - pointsArray[i][1]= cur[1]; - pointsArray[i][2]= cur[2]; - } - - //create the kd tree - kdTree = new ANNkd_tree(pointsArray,nPoints, dim); - - //fill mitk::Point3D into ANN query point - queryPoint[0] = point[0]; - queryPoint[1] = point[1]; - queryPoint[2] = point[2]; - - //k nearest neighbour search - kdTree->annkSearch(queryPoint, k, indexArray, distanceArray, eps); - - VertexType* ret = nullptr; - - try - { - ret = this->m_Vertices->at(indexArray[0]); - } - catch(std::out_of_range ex) - { - //ret stays nullptr - return ret; - } - - //clean up ANN - delete [] indexArray; - delete [] distanceArray; - delete kdTree; - annClose(); - - return ret; - } - return nullptr; -} -*/ - -mitk::ContourElement::VertexListType *mitk::ContourElement::GetVertexList() +const mitk::ContourElement::VertexListType *mitk::ContourElement::GetVertexList() const { - return this->m_Vertices; + return &(this->m_Vertices); } -bool mitk::ContourElement::IsClosed() +bool mitk::ContourElement::IsClosed() const { return this->m_IsClosed; } -bool mitk::ContourElement::IsNearContour(const mitk::Point3D &point, float eps) +bool mitk::ContourElement::IsNearContour(const mitk::Point3D &point, float eps) const { - ConstVertexIterator it1 = this->m_Vertices->begin(); - ConstVertexIterator it2 = this->m_Vertices->begin(); + ConstVertexIterator it1 = this->m_Vertices.begin(); + ConstVertexIterator it2 = this->m_Vertices.begin(); it2++; // it2 runs one position ahead - ConstVertexIterator end = this->m_Vertices->end(); + ConstVertexIterator end = this->m_Vertices.end(); int counter = 0; for (; it1 != end; it1++, it2++, counter++) { if (it2 == end) - it2 = this->m_Vertices->begin(); + it2 = this->m_Vertices.begin(); mitk::Point3D v1 = (*it1)->Coordinates; mitk::Point3D v2 = (*it2)->Coordinates; const float l2 = v1.SquaredEuclideanDistanceTo(v2); mitk::Vector3D p_v1 = point - v1; mitk::Vector3D v2_v1 = v2 - v1; double tc = (p_v1 * v2_v1) / l2; // take into account we have line segments and not (infinite) lines if (tc < 0.0) tc = 0.0; if (tc > 1.0) tc = 1.0; mitk::Point3D crossPoint = v1 + v2_v1 * tc; double distance = point.SquaredEuclideanDistanceTo(crossPoint); if (distance < eps) { return true; } } return false; } void mitk::ContourElement::Close() { this->m_IsClosed = true; } void mitk::ContourElement::Open() { this->m_IsClosed = false; } void mitk::ContourElement::SetClosed(bool isClosed) { isClosed ? this->Close() : this->Open(); } -mitk::ContourElement::VertexListType *mitk::ContourElement::GetControlVertices() +mitk::ContourElement::VertexListType mitk::ContourElement::GetControlVertices() const { - auto newVertices = new VertexListType(); + VertexListType controlVertices; - auto it = this->m_Vertices->begin(); - auto end = this->m_Vertices->end(); + std::copy_if(this->m_Vertices.begin(), this->m_Vertices.end(), std::back_inserter(controlVertices), [](const VertexType* v) {return v->IsControlPoint; }); - while (it != end) - { - if ((*it)->IsControlPoint) - { - newVertices->push_back((*it)); - } - it++; - } - - return newVertices; + return controlVertices; } -void mitk::ContourElement::Concatenate(mitk::ContourElement *other, bool check) +void mitk::ContourElement::Concatenate(const mitk::ContourElement *other, bool check) { if (other->GetSize() > 0) { - ConstVertexIterator otherIt = other->m_Vertices->begin(); - ConstVertexIterator otherEnd = other->m_Vertices->end(); - while (otherIt != otherEnd) + for (const auto& sourceVertex : other->m_Vertices) { if (check) { - ConstVertexIterator thisIt = this->m_Vertices->begin(); - ConstVertexIterator thisEnd = this->m_Vertices->end(); + auto finding = std::find_if(this->m_Vertices.begin(), this->m_Vertices.end(), [sourceVertex](const VertexType* v) {return sourceVertex->Coordinates == v->Coordinates; }); - bool found = false; - while (thisIt != thisEnd) + if (finding == this->m_Vertices.end()) { - if ((*thisIt)->Coordinates == (*otherIt)->Coordinates) - { - found = true; - break; - } - - thisIt++; + this->m_Vertices.push_back(new ContourModelVertex(*sourceVertex)); } - if (!found) - this->m_Vertices->push_back(*otherIt); } else { - this->m_Vertices->push_back(*otherIt); + this->m_Vertices.push_back(new ContourModelVertex(*sourceVertex)); } - otherIt++; } } } -bool mitk::ContourElement::RemoveVertex(const VertexType *vertex) +mitk::ContourElement::VertexSizeType mitk::ContourElement::GetIndex(const VertexType* vertex) const { - auto it = this->m_Vertices->begin(); + VertexSizeType result = NPOS; - auto end = this->m_Vertices->end(); + auto finding = std::find(this->m_Vertices.begin(), this->m_Vertices.end(), vertex); - // search for vertex and remove it if exists - while (it != end) + if (finding != this->m_Vertices.end()) { - if ((*it) == vertex) - { - this->m_Vertices->erase(it); - return true; - } - - it++; + result = finding - this->m_Vertices.begin(); } - return false; + return result; } -int mitk::ContourElement::GetIndex(const VertexType *vertex) +bool mitk::ContourElement::RemoveVertex(const VertexType *vertex) { - auto it = this->m_Vertices->begin(); + auto finding = std::find(this->m_Vertices.begin(), this->m_Vertices.end(), vertex); - auto end = this->m_Vertices->end(); - - int index = 0; + return RemoveVertexByIterator(finding); +} - // search for vertex - while (it != end) +bool mitk::ContourElement::RemoveVertexAt(VertexSizeType index) +{ + if (index >= 0 && index < this->m_Vertices.size()) { - if ((*it) == vertex) - { - return index; - } - - it++; - ++index; + auto delIter = this->m_Vertices.begin() + index; + return RemoveVertexByIterator(delIter); } - return -1; // not found + return false; } -bool mitk::ContourElement::RemoveVertexAt(int index) +bool mitk::ContourElement::RemoveVertexAt(const mitk::Point3D &point, double eps) { - if (index >= 0 && static_cast(index) < this->m_Vertices->size()) - { - this->m_Vertices->erase(this->m_Vertices->begin() + index); - return true; - } - else + if (eps > 0) { - return false; + auto finding = std::find_if(this->m_Vertices.begin(), this->m_Vertices.end(), [point, eps](const VertexType* v) {return v->Coordinates.EuclideanDistanceTo(point) < eps; }); + + return RemoveVertexByIterator(finding); } + return false; } -bool mitk::ContourElement::RemoveVertexAt(mitk::Point3D &point, float eps) +bool mitk::ContourElement::RemoveVertexByIterator(VertexListType::iterator& iter) { - /* current version iterates over the whole deque - should be some kind of an octree with spatial query*/ - - if (eps > 0) + if (iter != this->m_Vertices.end()) { - auto it = this->m_Vertices->begin(); - - auto end = this->m_Vertices->end(); - - while (it != end) - { - mitk::Point3D currentPoint = (*it)->Coordinates; - - if (currentPoint.EuclideanDistanceTo(point) < eps) - { - // approximate point found - // now erase it - this->m_Vertices->erase(it); - return true; - } - - it++; - } + delete* iter; + this->m_Vertices.erase(iter); + return true; } + return false; } void mitk::ContourElement::Clear() { - this->m_Vertices->clear(); + for (auto vertex : m_Vertices) + { + delete vertex; + } + this->m_Vertices.clear(); } + //---------------------------------------------------------------------- void mitk::ContourElement::RedistributeControlVertices(const VertexType *selected, int period) { int counter = 0; - auto _where = this->m_Vertices->begin(); + auto _where = this->m_Vertices.begin(); if (selected != nullptr) { - while (_where != this->m_Vertices->end()) + auto finding = std::find(this->m_Vertices.begin(), this->m_Vertices.end(), selected); + + if (finding != this->m_Vertices.end()) { - if ((*_where) == selected) - { - break; - } - _where++; + _where = finding; } } auto _iter = _where; - while (_iter != this->m_Vertices->end()) + while (_iter != this->m_Vertices.end()) { div_t divresult; divresult = div(counter, period); (*_iter)->IsControlPoint = (divresult.rem == 0); counter++; _iter++; } _iter = _where; counter = 0; - while (_iter != this->m_Vertices->begin()) + while (_iter != this->m_Vertices.begin()) { div_t divresult; divresult = div(counter, period); (*_iter)->IsControlPoint = (divresult.rem == 0); counter++; _iter--; } } diff --git a/Modules/ContourModel/DataManagement/mitkContourElement.h b/Modules/ContourModel/DataManagement/mitkContourElement.h index ea18abc41d..42be258cc3 100644 --- a/Modules/ContourModel/DataManagement/mitkContourElement.h +++ b/Modules/ContourModel/DataManagement/mitkContourElement.h @@ -1,231 +1,260 @@ /*============================================================================ 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 _mitkContourElement_H_ #define _mitkContourElement_H_ #include "mitkCommon.h" #include #include -//#include - #include namespace mitk { /** \brief Represents a contour in 3D space. A ContourElement is consisting of linked vertices implicitely defining the contour. They are stored in a double ended queue making it possible to add vertices at front and end of the contour and to iterate in both directions. To mark a vertex as a special one it can be set as a control point. - \note It is highly not recommend to use this class directly as no secure mechanism is used here. - Use mitk::ContourModel instead providing some additional features. + \note This class assumes that it manages its vertices. So if a vertex instance is added to this + class the ownership of the vertex is transfered to the ContourElement instance. + The ContourElement instance takes care of deleting vertex instances if needed. + It is highly not recommend to use this class directly as it is designed as a internal class of + ContourModel. Therefore it is adviced to use ContourModel if contour representations are needed in + MITK. */ class MITKCONTOURMODEL_EXPORT ContourElement : public itk::LightObject { public: mitkClassMacroItkParent(ContourElement, itk::LightObject); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - // Data container representing vertices - - /** \brief Represents a single vertex of contour. - */ - struct ContourModelVertex + /** \brief Represents a single vertex of a contour. + */ + struct MITKCONTOURMODEL_EXPORT ContourModelVertex { - ContourModelVertex(mitk::Point3D &point, bool active = false) : IsControlPoint(active), Coordinates(point) {} - ContourModelVertex(const ContourModelVertex &other) + ContourModelVertex(const mitk::Point3D& point, bool active = false) : IsControlPoint(active), Coordinates(point) {}; + ContourModelVertex(const ContourModelVertex& other) : IsControlPoint(other.IsControlPoint), Coordinates(other.Coordinates) { - } + }; /** \brief Treat point special. */ bool IsControlPoint; /** \brief Coordinates in 3D space. */ mitk::Point3D Coordinates; + + bool operator ==(const ContourModelVertex& other) const; }; - // END Data container representing vertices - typedef ContourModelVertex VertexType; - typedef std::deque VertexListType; - typedef VertexListType::iterator VertexIterator; - typedef VertexListType::const_iterator ConstVertexIterator; + using VertexType = ContourModelVertex; + using VertexListType = std::deque; + using VertexIterator = VertexListType::iterator; + using ConstVertexIterator = VertexListType::const_iterator; + using VertexSizeType = VertexListType::size_type; + + /**Indicates an invalid index. + * It is always the maximum of the unsigned int type.*/ + static const VertexSizeType NPOS = -1; - // start of inline methods + /** \brief Return a const iterator a the front. + */ + ConstVertexIterator ConstIteratorBegin() const; + /** \brief Return a const iterator a the end. + */ + ConstVertexIterator ConstIteratorEnd() const; + /** \brief Return an iterator a the front. + */ + VertexIterator IteratorBegin(); + /** \brief Return an iterator a the end. + */ + VertexIterator IteratorEnd(); /** \brief Return a const iterator a the front. + * For easier support of stl functionality. */ - virtual ConstVertexIterator ConstIteratorBegin() { return this->m_Vertices->begin(); } + ConstVertexIterator begin() const; /** \brief Return a const iterator a the end. + * For easier support of stl functionality. */ - virtual ConstVertexIterator ConstIteratorEnd() { return this->m_Vertices->end(); } + ConstVertexIterator end() const; /** \brief Return an iterator a the front. + * For easier support of stl functionality. */ - virtual VertexIterator IteratorBegin() { return this->m_Vertices->begin(); } + VertexIterator begin(); /** \brief Return an iterator a the end. + * For easier support of stl functionality. */ - virtual VertexIterator IteratorEnd() { return this->m_Vertices->end(); } + VertexIterator end(); + /** \brief Returns the number of contained vertices. */ - virtual int GetSize() { return this->m_Vertices->size(); } - // end of inline methods + VertexSizeType GetSize() const; /** \brief Add a vertex at the end of the contour \param point - coordinates in 3D space. \param isControlPoint - is the vertex a special control point. */ - virtual void AddVertex(mitk::Point3D &point, bool isControlPoint); - - /** \brief Add a vertex at the end of the contour - \param vertex - a contour element vertex. - */ - virtual void AddVertex(VertexType &vertex); + void AddVertex(const mitk::Point3D &point, bool isControlPoint); /** \brief Add a vertex at the front of the contour \param point - coordinates in 3D space. \param isControlPoint - is the vertex a control point. */ - virtual void AddVertexAtFront(mitk::Point3D &point, bool isControlPoint); - - /** \brief Add a vertex at the front of the contour - \param vertex - a contour element vertex. - */ - virtual void AddVertexAtFront(VertexType &vertex); + void AddVertexAtFront(const mitk::Point3D &point, bool isControlPoint); /** \brief Add a vertex at a given index of the contour \param point - coordinates in 3D space. \param isControlPoint - is the vertex a special control point. \param index - the index to be inserted at. */ - virtual void InsertVertexAtIndex(mitk::Point3D &point, bool isControlPoint, int index); + void InsertVertexAtIndex(const mitk::Point3D &point, bool isControlPoint, VertexSizeType index); /** \brief Set coordinates a given index. \param pointId Index of vertex. \param point Coordinates. */ - virtual void SetVertexAt(int pointId, const mitk::Point3D &point); + void SetVertexAt(VertexSizeType pointId, const mitk::Point3D &point); - /** \brief Set vertex a given index. + /** \brief Set vertex a given index (by copying the values). \param pointId Index of vertex. \param vertex Vertex. + \pre Passed vertex is a valid instance */ - virtual void SetVertexAt(int pointId, const VertexType *vertex); + void SetVertexAt(VertexSizeType pointId, const VertexType* vertex); /** \brief Returns the vertex a given index \param index + \pre index must be valid. */ - virtual VertexType *GetVertexAt(int index); + VertexType* GetVertexAt(VertexSizeType index); + const VertexType* GetVertexAt(VertexSizeType index) const; /** \brief Returns the approximate nearest vertex a given posoition in 3D space \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ - virtual VertexType *GetVertexAt(const mitk::Point3D &point, float eps); + VertexType *GetVertexAt(const mitk::Point3D &point, float eps); /** \brief Returns the index of the given vertex within the contour. \param vertex - the vertex to be searched. - \return index of vertex. -1 if not found. + \return index of vertex. Returns ContourElement::NPOS if not found. */ - virtual int GetIndex(const VertexType *vertex); + VertexSizeType GetIndex(const VertexType *vertex) const; /** \brief Returns the container of the vertices. */ - VertexListType *GetVertexList(); + const VertexListType *GetVertexList() const; /** \brief Returns whether the contour element is empty. */ - bool IsEmpty(); + bool IsEmpty() const; /** \brief Returns if the conour is closed or not. */ - virtual bool IsClosed(); + bool IsClosed() const; /** \brief Returns whether a given point is near a contour, according to eps. \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ - virtual bool IsNearContour(const mitk::Point3D &point, float eps); + bool IsNearContour(const mitk::Point3D &point, float eps) const; /** \brief Close the contour. Connect first with last element. */ - virtual void Close(); + void Close(); /** \brief Open the contour. Disconnect first and last element. */ - virtual void Open(); + void Open(); /** \brief Set the contours IsClosed property. \param isClosed - true = closed; false = open; */ - virtual void SetClosed(bool isClosed); + void SetClosed(bool isClosed); /** \brief Concatenate the contuor with a another contour. - All vertices of the other contour will be added after last vertex. + All vertices of the other contour will be cloned and added after last vertex. \param other - the other contour - \param check - set it true to avoid intersections + \param check - set it true to avoid adding of vertices that are already in the source contour */ - void Concatenate(mitk::ContourElement *other, bool check); + void Concatenate(const mitk::ContourElement *other, bool check); /** \brief Remove the given vertex from the container if exists. \param vertex - the vertex to be removed. */ - virtual bool RemoveVertex(const VertexType *vertex); + bool RemoveVertex(const VertexType *vertex); /** \brief Remove a vertex at given index within the container if exists. \param index - the index where the vertex should be removed. */ - virtual bool RemoveVertexAt(int index); + bool RemoveVertexAt(VertexSizeType index); /** \brief Remove the approximate nearest vertex at given position in 3D space if one exists. \param point - query point in 3D space. \param eps - error bound for search algorithm. */ - virtual bool RemoveVertexAt(mitk::Point3D &point, float eps); + bool RemoveVertexAt(const mitk::Point3D &point, double eps); /** \brief Clear the storage container. */ - virtual void Clear(); + void Clear(); /** \brief Returns the approximate nearest vertex a given posoition in 3D space \param point - query position in 3D space. \param eps - the error bound for search algorithm. */ - VertexType *BruteForceGetVertexAt(const mitk::Point3D &point, float eps); + VertexType *BruteForceGetVertexAt(const mitk::Point3D &point, double eps); - VertexListType *GetControlVertices(); + /** Returns a list pointing to all vertices that are indicated to be control + points. + \remark It is important to note, that the vertex pointers in the returned + list directly point to the vertices stored interanlly. So they are still + owned by the ContourElement instance that returns the list. If one wants + to take over ownership, one has to clone the vertex instances. + */ + VertexListType GetControlVertices() const; /** \brief Uniformly redistribute control points with a given period (in number of vertices) \param vertex - the vertex around which the redistribution is done. \param period - number of vertices between control points. */ void RedistributeControlVertices(const VertexType *vertex, int period); protected: mitkCloneMacro(Self); - ContourElement(); + ContourElement() = default; ContourElement(const mitk::ContourElement &other); - ~ContourElement() override; + ~ContourElement(); + + ContourElement& operator = (const ContourElement & other); + + /** Internal helper function to correctly remove the element indicated by the iterator + from the list. After the call the iterator is invalid. + Caller of the function must ensure that the iterator is valid!. + \result Indicates if the element indicated by the iterator was removed. If iterator points to end it returns false.*/ + bool RemoveVertexByIterator(VertexListType::iterator& iter); - VertexListType *m_Vertices; // double ended queue with vertices - bool m_IsClosed; + VertexListType m_Vertices; // double ended queue with vertices + bool m_IsClosed = false; }; } // namespace mitk #endif // _mitkContourElement_H_ diff --git a/Modules/ContourModel/DataManagement/mitkContourModel.cpp b/Modules/ContourModel/DataManagement/mitkContourModel.cpp index 6d88174b43..d6e04cb150 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.cpp +++ b/Modules/ContourModel/DataManagement/mitkContourModel.cpp @@ -1,635 +1,628 @@ /*============================================================================ 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 mitk::ContourModel::ContourModel() : m_UpdateBoundingBox(true) { // set to initial state this->InitializeEmpty(); } -mitk::ContourModel::ContourModel(const mitk::ContourModel &other) - : mitk::BaseData(other), m_ContourSeries(other.m_ContourSeries), m_lineInterpolation(other.m_lineInterpolation) +mitk::ContourModel::ContourModel(const ContourModel &other) + : BaseData(other), m_ContourSeries(other.m_ContourSeries), m_lineInterpolation(other.m_lineInterpolation) { m_SelectedVertex = nullptr; } mitk::ContourModel::~ContourModel() { m_SelectedVertex = nullptr; this->m_ContourSeries.clear(); // TODO check destruction } -void mitk::ContourModel::AddVertex(mitk::Point3D &vertex, int timestep) +void mitk::ContourModel::AddVertex(const Point3D &vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->AddVertex(vertex, false, timestep); } } -void mitk::ContourModel::AddVertex(mitk::Point3D &vertex, bool isControlPoint, int timestep) +void mitk::ContourModel::AddVertex(const Point3D &vertex, bool isControlPoint, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->AddVertex(vertex, isControlPoint); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } -void mitk::ContourModel::AddVertex(VertexType &vertex, int timestep) +void mitk::ContourModel::AddVertex(const VertexType &vertex, TimeStepType timestep) { - if (!this->IsEmptyTimeStep(timestep)) - { - this->m_ContourSeries[timestep]->AddVertex(vertex); - this->InvokeEvent(ContourModelSizeChangeEvent()); - this->Modified(); - this->m_UpdateBoundingBox = true; - } -} - -void mitk::ContourModel::AddVertex(const VertexType *vertex, int timestep) -{ - if (vertex != nullptr) - { - this->m_ContourSeries[timestep]->AddVertex(*const_cast(vertex)); - } + this->AddVertex(vertex.Coordinates, vertex.IsControlPoint, timestep); } -void mitk::ContourModel::AddVertexAtFront(mitk::Point3D &vertex, int timestep) +void mitk::ContourModel::AddVertexAtFront(const Point3D &vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->AddVertexAtFront(vertex, false, timestep); } } -void mitk::ContourModel::AddVertexAtFront(mitk::Point3D &vertex, bool isControlPoint, int timestep) +void mitk::ContourModel::AddVertexAtFront(const Point3D &vertex, bool isControlPoint, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->AddVertexAtFront(vertex, isControlPoint); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } -void mitk::ContourModel::AddVertexAtFront(VertexType &vertex, int timestep) +void mitk::ContourModel::AddVertexAtFront(const VertexType &vertex, TimeStepType timestep) { - if (!this->IsEmptyTimeStep(timestep)) - { - this->m_ContourSeries[timestep]->AddVertexAtFront(vertex); - this->InvokeEvent(ContourModelSizeChangeEvent()); - this->Modified(); - this->m_UpdateBoundingBox = true; - } + this->AddVertexAtFront(vertex.Coordinates, vertex.IsControlPoint, timestep); } -bool mitk::ContourModel::SetVertexAt(int pointId, const Point3D &point, unsigned int timestep) +bool mitk::ContourModel::SetVertexAt(int pointId, const Point3D &point, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { - if (pointId >= 0 && this->m_ContourSeries[timestep]->GetSize() > pointId) + if (pointId >= 0 && this->m_ContourSeries[timestep]->GetSize() > ContourElement::VertexSizeType(pointId)) { this->m_ContourSeries[timestep]->SetVertexAt(pointId, point); this->Modified(); this->m_UpdateBoundingBox = true; return true; } return false; } return false; } -bool mitk::ContourModel::SetVertexAt(int pointId, const VertexType *vertex, unsigned int timestep) +bool mitk::ContourModel::SetVertexAt(int pointId, const VertexType *vertex, TimeStepType timestep) { if (vertex == nullptr) return false; if (!this->IsEmptyTimeStep(timestep)) { - if (pointId >= 0 && this->m_ContourSeries[timestep]->GetSize() > pointId) + if (pointId >= 0 && this->m_ContourSeries[timestep]->GetSize() > ContourElement::VertexSizeType(pointId)) { this->m_ContourSeries[timestep]->SetVertexAt(pointId, vertex); this->Modified(); this->m_UpdateBoundingBox = true; return true; } return false; } return false; } -void mitk::ContourModel::InsertVertexAtIndex(mitk::Point3D &vertex, int index, bool isControlPoint, int timestep) +void mitk::ContourModel::InsertVertexAtIndex(const Point3D &vertex, int index, bool isControlPoint, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { - if (index >= 0 && this->m_ContourSeries[timestep]->GetSize() > index) + if (index >= 0 && this->m_ContourSeries[timestep]->GetSize() > ContourElement::VertexSizeType(index)) { this->m_ContourSeries[timestep]->InsertVertexAtIndex(vertex, isControlPoint, index); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } } -bool mitk::ContourModel::IsEmpty(int timestep) const +void mitk::ContourModel::UpdateContour(const ContourModel* sourceModel, TimeStepType destinationTimeStep, TimeStepType sourceTimeStep) +{ + if (nullptr == sourceModel) + { + mitkThrow() << "Cannot update contour. Passed source model is invalid."; + } + + if (!sourceModel->GetTimeGeometry()->IsValidTimeStep(sourceTimeStep)) + { + mitkThrow() << "Cannot update contour. Source contour time geometry does not support passed time step. Invalid time step: " << sourceTimeStep; + } + + if (!this->GetTimeGeometry()->IsValidTimeStep(destinationTimeStep)) + { + MITK_WARN << "Cannot update contour. Contour time geometry does not support passed time step. Invalid time step: " << destinationTimeStep; + return; + } + + this->Clear(destinationTimeStep); + + std::for_each(sourceModel->Begin(sourceTimeStep), sourceModel->End(sourceTimeStep), [this, destinationTimeStep](ContourElement::VertexType* vertex) { + this->m_ContourSeries[destinationTimeStep]->AddVertex(vertex->Coordinates, vertex->IsControlPoint); + }); + + this->InvokeEvent(ContourModelSizeChangeEvent()); + this->Modified(); + this->m_UpdateBoundingBox = true; +} + +bool mitk::ContourModel::IsEmpty(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsEmpty(); } return true; } bool mitk::ContourModel::IsEmpty() const { return this->IsEmpty(0); } -int mitk::ContourModel::GetNumberOfVertices(int timestep) const +int mitk::ContourModel::GetNumberOfVertices(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->GetSize(); } return -1; } -const mitk::ContourModel::VertexType *mitk::ContourModel::GetVertexAt(int index, int timestep) const +const mitk::ContourModel::VertexType *mitk::ContourModel::GetVertexAt(int index, TimeStepType timestep) const { - if (!this->IsEmptyTimeStep(timestep)) + if (!this->IsEmptyTimeStep(timestep) && this->m_ContourSeries[timestep]->GetSize() > mitk::ContourElement::VertexSizeType(index)) { return this->m_ContourSeries[timestep]->GetVertexAt(index); } return nullptr; } -int mitk::ContourModel::GetIndex(const VertexType *vertex, int timestep) +int mitk::ContourModel::GetIndex(const VertexType *vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->GetIndex(vertex); } return -1; } -void mitk::ContourModel::Close(int timestep) +void mitk::ContourModel::Close(TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->Close(); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } -void mitk::ContourModel::Open(int timestep) +void mitk::ContourModel::Open(TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->Open(); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } -void mitk::ContourModel::SetClosed(bool isClosed, int timestep) +void mitk::ContourModel::SetClosed(bool isClosed, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->SetClosed(isClosed); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } bool mitk::ContourModel::IsEmptyTimeStep(unsigned int t) const { return (this->m_ContourSeries.size() <= t); } -bool mitk::ContourModel::IsNearContour(mitk::Point3D &point, float eps, int timestep) +bool mitk::ContourModel::IsNearContour(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsNearContour(point, eps); } return false; } -void mitk::ContourModel::Concatenate(mitk::ContourModel *other, int timestep, bool check) +void mitk::ContourModel::Concatenate(ContourModel *other, TimeStepType timestep, bool check) { if (!this->IsEmptyTimeStep(timestep)) { if (!this->m_ContourSeries[timestep]->IsClosed()) { this->m_ContourSeries[timestep]->Concatenate(other->m_ContourSeries[timestep], check); this->InvokeEvent(ContourModelSizeChangeEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } } -mitk::ContourModel::VertexIterator mitk::ContourModel::Begin(int timestep) const +mitk::ContourModel::VertexIterator mitk::ContourModel::Begin(TimeStepType timestep) const { return this->IteratorBegin(timestep); } -mitk::ContourModel::VertexIterator mitk::ContourModel::IteratorBegin(int timestep) const +mitk::ContourModel::VertexIterator mitk::ContourModel::IteratorBegin(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IteratorBegin(); } else { mitkThrow() << "No iterator at invalid timestep " << timestep << ". There are only " << this->GetTimeSteps() << " timesteps available."; } } -mitk::ContourModel::VertexIterator mitk::ContourModel::End(int timestep) const +mitk::ContourModel::VertexIterator mitk::ContourModel::End(TimeStepType timestep) const { return this->IteratorEnd(timestep); } -mitk::ContourModel::VertexIterator mitk::ContourModel::IteratorEnd(int timestep) const +mitk::ContourModel::VertexIterator mitk::ContourModel::IteratorEnd(TimeStepType timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IteratorEnd(); } else { mitkThrow() << "No iterator at invalid timestep " << timestep << ". There are only " << this->GetTimeSteps() << " timesteps available."; } } bool mitk::ContourModel::IsClosed(int timestep) const { if (!this->IsEmptyTimeStep(timestep)) { return this->m_ContourSeries[timestep]->IsClosed(); } return false; } -bool mitk::ContourModel::SelectVertexAt(mitk::Point3D &point, float eps, int timestep) +bool mitk::ContourModel::SelectVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_SelectedVertex = this->m_ContourSeries[timestep]->GetVertexAt(point, eps); } return this->m_SelectedVertex != nullptr; } -bool mitk::ContourModel::SelectVertexAt(int index, int timestep) +bool mitk::ContourModel::SelectVertexAt(int index, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep) && index >= 0) { return (this->m_SelectedVertex = this->m_ContourSeries[timestep]->GetVertexAt(index)); } return false; } -bool mitk::ContourModel::SetControlVertexAt(mitk::Point3D &point, float eps, int timestep) +bool mitk::ContourModel::SetControlVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { VertexType *vertex = this->m_ContourSeries[timestep]->GetVertexAt(point, eps); if (vertex != nullptr) { vertex->IsControlPoint = true; return true; } } return false; } -bool mitk::ContourModel::SetControlVertexAt(int index, int timestep) +bool mitk::ContourModel::SetControlVertexAt(int index, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep) && index >= 0) { VertexType *vertex = this->m_ContourSeries[timestep]->GetVertexAt(index); if (vertex != nullptr) { vertex->IsControlPoint = true; return true; } } return false; } -bool mitk::ContourModel::RemoveVertex(const VertexType *vertex, int timestep) +bool mitk::ContourModel::RemoveVertex(const VertexType *vertex, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (this->m_ContourSeries[timestep]->RemoveVertex(vertex)) { this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelSizeChangeEvent()); return true; } } return false; } -bool mitk::ContourModel::RemoveVertexAt(int index, int timestep) +bool mitk::ContourModel::RemoveVertexAt(int index, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (this->m_ContourSeries[timestep]->RemoveVertexAt(index)) { this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelSizeChangeEvent()); return true; } } return false; } -bool mitk::ContourModel::RemoveVertexAt(mitk::Point3D &point, float eps, int timestep) +bool mitk::ContourModel::RemoveVertexAt(Point3D &point, float eps, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { if (this->m_ContourSeries[timestep]->RemoveVertexAt(point, eps)) { this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelSizeChangeEvent()); return true; } } return false; } -void mitk::ContourModel::ShiftSelectedVertex(mitk::Vector3D &translate) +void mitk::ContourModel::ShiftSelectedVertex(Vector3D &translate) { if (this->m_SelectedVertex) { this->ShiftVertex(this->m_SelectedVertex, translate); this->Modified(); this->m_UpdateBoundingBox = true; } } -void mitk::ContourModel::ShiftContour(mitk::Vector3D &translate, int timestep) +void mitk::ContourModel::ShiftContour(Vector3D &translate, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { - VertexListType *vList = this->m_ContourSeries[timestep]->GetVertexList(); - auto it = vList->begin(); - auto end = vList->end(); - // shift all vertices - while (it != end) + for (auto vertex : *(this->m_ContourSeries[timestep])) { - this->ShiftVertex((*it), translate); - it++; + this->ShiftVertex(vertex, translate); } this->Modified(); this->m_UpdateBoundingBox = true; this->InvokeEvent(ContourModelShiftEvent()); } } -void mitk::ContourModel::ShiftVertex(VertexType *vertex, mitk::Vector3D &vector) +void mitk::ContourModel::ShiftVertex(VertexType *vertex, Vector3D &vector) { vertex->Coordinates[0] += vector[0]; vertex->Coordinates[1] += vector[1]; vertex->Coordinates[2] += vector[2]; } -void mitk::ContourModel::Clear(int timestep) +void mitk::ContourModel::Clear(TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { // clear data at timestep this->m_ContourSeries[timestep]->Clear(); - this->InitializeEmpty(); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::Expand(unsigned int timeSteps) { std::size_t oldSize = this->m_ContourSeries.size(); if (static_cast(timeSteps) > oldSize) { Superclass::Expand(timeSteps); // insert contours for each new timestep for (std::size_t i = oldSize; i < static_cast(timeSteps); i++) { - m_ContourSeries.push_back(mitk::ContourElement::New()); + m_ContourSeries.push_back(ContourElement::New()); } this->InvokeEvent(ContourModelExpandTimeBoundsEvent()); } } void mitk::ContourModel::SetRequestedRegionToLargestPossibleRegion() { // no support for regions } bool mitk::ContourModel::RequestedRegionIsOutsideOfTheBufferedRegion() { // no support for regions return false; } bool mitk::ContourModel::VerifyRequestedRegion() { // no support for regions return true; } -const mitk::BaseGeometry *mitk::ContourModel::GetUpdatedGeometry(int t) -{ - return Superclass::GetUpdatedGeometry(t); -} - -mitk::BaseGeometry *mitk::ContourModel::GetGeometry(int t) const -{ - return Superclass::GetGeometry(t); -} - void mitk::ContourModel::SetRequestedRegion(const itk::DataObject * /*data*/) { // no support for regions } void mitk::ContourModel::Clear() { // clear data and set to initial state again this->ClearData(); this->InitializeEmpty(); this->Modified(); this->m_UpdateBoundingBox = true; } -void mitk::ContourModel::RedistributeControlVertices(int period, int timestep) +void mitk::ContourModel::RedistributeControlVertices(int period, TimeStepType timestep) { if (!this->IsEmptyTimeStep(timestep)) { this->m_ContourSeries[timestep]->RedistributeControlVertices(this->GetSelectedVertex(), period); this->InvokeEvent(ContourModelClosedEvent()); this->Modified(); this->m_UpdateBoundingBox = true; } } void mitk::ContourModel::ClearData() { // call the superclass, this releases the data of BaseData Superclass::ClearData(); // clear out the time resolved contours this->m_ContourSeries.clear(); } void mitk::ContourModel::Initialize() { this->InitializeEmpty(); this->Modified(); this->m_UpdateBoundingBox = true; } -void mitk::ContourModel::Initialize(mitk::ContourModel &other) +void mitk::ContourModel::Initialize(const ContourModel &other) { - mitk::TimeStepType numberOfTimesteps = other.GetTimeGeometry()->CountTimeSteps(); + TimeStepType numberOfTimesteps = other.GetTimeGeometry()->CountTimeSteps(); this->InitializeTimeGeometry(numberOfTimesteps); - for (mitk::TimeStepType currentTimestep = 0; currentTimestep < numberOfTimesteps; currentTimestep++) + for (TimeStepType currentTimestep = 0; currentTimestep < numberOfTimesteps; currentTimestep++) { - this->m_ContourSeries.push_back(mitk::ContourElement::New()); + this->m_ContourSeries.push_back(ContourElement::New()); this->SetClosed(other.IsClosed(currentTimestep), currentTimestep); } m_SelectedVertex = nullptr; this->m_lineInterpolation = other.m_lineInterpolation; this->Modified(); this->m_UpdateBoundingBox = true; } void mitk::ContourModel::InitializeEmpty() { // clear data at timesteps this->m_ContourSeries.resize(0); - this->m_ContourSeries.push_back(mitk::ContourElement::New()); + this->m_ContourSeries.push_back(ContourElement::New()); // set number of timesteps to one this->InitializeTimeGeometry(1); m_SelectedVertex = nullptr; this->m_lineInterpolation = ContourModel::LINEAR; } void mitk::ContourModel::UpdateOutputInformation() { if (this->GetSource()) { this->GetSource()->UpdateOutputInformation(); } if (this->m_UpdateBoundingBox) { // update the bounds of the geometry according to the stored vertices ScalarType mitkBounds[6]; // calculate the boundingbox at each timestep typedef itk::BoundingBox BoundingBoxType; typedef BoundingBoxType::PointsContainer PointsContainer; int timesteps = this->GetTimeSteps(); // iterate over the timesteps for (int currenTimeStep = 0; currenTimeStep < timesteps; currenTimeStep++) { - if (dynamic_cast(this->GetGeometry(currenTimeStep))) + if (dynamic_cast(this->GetGeometry(currenTimeStep))) { // do not update bounds for 2D geometries, as they are unfortunately defined with min bounds 0! return; } else { // we have a 3D geometry -> let's update bounds // only update bounds if the contour was modified if (this->GetMTime() > this->GetGeometry(currenTimeStep)->GetBoundingBox()->GetMTime()) { mitkBounds[0] = 0.0; mitkBounds[1] = 0.0; mitkBounds[2] = 0.0; mitkBounds[3] = 0.0; mitkBounds[4] = 0.0; mitkBounds[5] = 0.0; BoundingBoxType::Pointer boundingBox = BoundingBoxType::New(); PointsContainer::Pointer points = PointsContainer::New(); auto it = this->IteratorBegin(currenTimeStep); auto end = this->IteratorEnd(currenTimeStep); // fill the boundingbox with the points while (it != end) { Point3D currentP = (*it)->Coordinates; BoundingBoxType::PointType p; p.CastFrom(currentP); points->InsertElement(points->Size(), p); it++; } // construct the new boundingBox boundingBox->SetPoints(points); boundingBox->ComputeBoundingBox(); BoundingBoxType::BoundsArrayType tmp = boundingBox->GetBounds(); mitkBounds[0] = tmp[0]; mitkBounds[1] = tmp[1]; mitkBounds[2] = tmp[2]; mitkBounds[3] = tmp[3]; mitkBounds[4] = tmp[4]; mitkBounds[5] = tmp[5]; // set boundingBox at current timestep BaseGeometry *geometry3d = this->GetGeometry(currenTimeStep); geometry3d->SetBounds(mitkBounds); } } } this->m_UpdateBoundingBox = false; } GetTimeGeometry()->Update(); } -void mitk::ContourModel::ExecuteOperation(mitk::Operation * /*operation*/) +void mitk::ContourModel::ExecuteOperation(Operation * /*operation*/) { // not supported yet } diff --git a/Modules/ContourModel/DataManagement/mitkContourModel.h b/Modules/ContourModel/DataManagement/mitkContourModel.h index 45aae9bcda..d234d9cefb 100644 --- a/Modules/ContourModel/DataManagement/mitkContourModel.h +++ b/Modules/ContourModel/DataManagement/mitkContourModel.h @@ -1,453 +1,437 @@ /*============================================================================ 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 _MITK_CONTOURMODEL_H_ #define _MITK_CONTOURMODEL_H_ #include "mitkBaseData.h" #include "mitkCommon.h" #include #include namespace mitk { /** \brief ContourModel is a structure of linked vertices defining a contour in 3D space. The vertices are stored in a mitk::ContourElement is stored for each timestep. The contour line segments are implicitly defined by the given linked vertices. By default two control points are are linked by a straight line.It is possible to add vertices at front and end of the contour and to iterate in both directions. Points are specified containing coordinates and additional (data) information, see mitk::ContourElement. For accessing a specific vertex either an index or a position in 3D Space can be used. The vertices are best accessed by using a VertexIterator. Interaction with the contour is thus available without any mitk interactor class using the api of ContourModel. It is possible to shift single vertices also as shifting the whole contour. A contour can be either open like a single curved line segment or closed. A closed contour can for example represent a jordan curve. \section mitkContourModelDisplayOptions Display Options The default mappers for this data structure are mitk::ContourModelGLMapper2D and mitk::ContourModelMapper3D. See these classes for display options which can can be set via properties. */ class MITKCONTOURMODEL_EXPORT ContourModel : public BaseData { public: mitkClassMacro(ContourModel, BaseData); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - /*+++++++++++++++ typedefs +++++++++++++++++++++++++++++++*/ - typedef mitk::ContourElement::VertexType VertexType; - typedef mitk::ContourElement::VertexListType VertexListType; - typedef mitk::ContourElement::VertexIterator VertexIterator; - typedef mitk::ContourElement::ConstVertexIterator ConstVertexIterator; - typedef std::vector ContourModelSeries; + /*+++++++++++++++ typedefs +++++++++++++++++++++++++++++++*/ + typedef ContourElement::VertexType VertexType; + typedef ContourElement::VertexListType VertexListType; + typedef ContourElement::VertexIterator VertexIterator; + typedef ContourElement::ConstVertexIterator ConstVertexIterator; + typedef std::vector ContourModelSeries; /*+++++++++++++++ END typedefs ++++++++++++++++++++++++++++*/ /** \brief Possible interpolation of the line segments between control points */ enum LineSegmentInterpolation { LINEAR, B_SPLINE }; /*++++++++++++++++ inline methods +++++++++++++++++++++++*/ /** \brief Get the current selected vertex. */ VertexType *GetSelectedVertex() { return this->m_SelectedVertex; } /** \brief Deselect vertex. */ void Deselect() { this->m_SelectedVertex = nullptr; } /** \brief Set selected vertex as control point */ void SetSelectedVertexAsControlPoint(bool isControlPoint = true) { if (this->m_SelectedVertex) { m_SelectedVertex->IsControlPoint = isControlPoint; this->Modified(); } } /** \brief Set the interpolation of the line segments between control points. */ void SetLineSegmentInterpolation(LineSegmentInterpolation interpolation) { this->m_lineInterpolation = interpolation; this->Modified(); } /** \brief Get the interpolation of the line segments between control points. */ LineSegmentInterpolation GetLineSegmentInterpolation() { return this->m_lineInterpolation; } /*++++++++++++++++ END inline methods +++++++++++++++++++++++*/ /** \brief Add a vertex to the contour at given timestep. The vertex is added at the end of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ - void AddVertex(mitk::Point3D &vertex, int timestep = 0); + void AddVertex(const Point3D &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour at given timestep. - The vertex is added at the end of contour. - + A copy of the passed vertex is added at the end of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) - @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ - void AddVertex(VertexType &vertex, int timestep = 0); - - /** \brief Add a vertex to the contour at given timestep. - The vertex is added at the end of contour. - - \param vertex - coordinate representation of a control point - \param timestep - the timestep at which the vertex will be add ( default 0) - - @note Adding a vertex to a timestep which exceeds the timebounds of the contour - will not be added, the TimeSlicedGeometry will not be expanded. - */ - void AddVertex(const VertexType *vertex, int timestep = 0); + void AddVertex(const VertexType &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour. - \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) \param isControlPoint - specifies the vertex to be handled in a special way (e.g. control points will be rendered). - - @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ - void AddVertex(mitk::Point3D &vertex, bool isControlPoint, int timestep = 0); + void AddVertex(const Point3D& vertex, bool isControlPoint, TimeStepType timestep = 0); + + /** Clears the contour of destinationTimeStep and copies + the contour of the passed source model at the sourceTimeStep. + @pre soureModel must point to a valid instance + @pre sourceTimePoint must be valid + @note Updateing a vertex to a timestep which exceeds the timebounds of the contour + will not be added, the TimeGeometry will not be expanded. + */ + void UpdateContour(const ContourModel* sourceModel, TimeStepType destinationTimeStep, TimeStepType sourceTimeStep); /** \brief Add a vertex to the contour at given timestep AT THE FRONT of the contour. The vertex is added at the FRONT of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ - void AddVertexAtFront(mitk::Point3D &vertex, int timestep = 0); + void AddVertexAtFront(const Point3D &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour at given timestep AT THE FRONT of the contour. The vertex is added at the FRONT of contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ - void AddVertexAtFront(VertexType &vertex, int timestep = 0); + void AddVertexAtFront(const VertexType &vertex, TimeStepType timestep = 0); /** \brief Add a vertex to the contour at given timestep AT THE FRONT of the contour. \param vertex - coordinate representation of a control point \param timestep - the timestep at which the vertex will be add ( default 0) \param isControlPoint - specifies the vertex to be handled in a special way (e.g. control points will be rendered). @note Adding a vertex to a timestep which exceeds the timebounds of the contour will not be added, the TimeGeometry will not be expanded. */ - void AddVertexAtFront(mitk::Point3D &vertex, bool isControlPoint, int timestep = 0); + void AddVertexAtFront(const Point3D &vertex, bool isControlPoint, TimeStepType timestep = 0); /** \brief Insert a vertex at given index. */ - void InsertVertexAtIndex(mitk::Point3D &vertex, int index, bool isControlPoint = false, int timestep = 0); + void InsertVertexAtIndex(const Point3D &vertex, int index, bool isControlPoint = false, TimeStepType timestep = 0); /** \brief Set a coordinates for point at given index. */ - bool SetVertexAt(int pointId, const mitk::Point3D &point, unsigned int timestep = 0); + bool SetVertexAt(int pointId, const Point3D &point, TimeStepType timestep = 0); - /** \brief Set a coordinates for point at given index. + /** \brief Set a coordinates and control state for point at given index. */ - bool SetVertexAt(int pointId, const VertexType *vertex, unsigned int timestep = 0); + bool SetVertexAt(int pointId, const VertexType *vertex, TimeStepType timestep = 0); /** \brief Return if the contour is closed or not. */ bool IsClosed(int timestep = 0) const; /** \brief Concatenate two contours. The starting control point of the other will be added at the end of the contour. \param other \param timestep - the timestep at which the vertex will be add ( default 0) \param check - check for intersections ( default false) */ - void Concatenate(mitk::ContourModel *other, int timestep = 0, bool check = false); + void Concatenate(ContourModel *other, TimeStepType timestep = 0, bool check = false); /** \brief Returns a const VertexIterator at the start element of the contour. @throw mitk::Exception if the timestep is invalid. */ - VertexIterator Begin(int timestep = 0) const; + VertexIterator Begin(TimeStepType timestep = 0) const; /** \brief Returns a const VertexIterator at the start element of the contour. @throw mitk::Exception if the timestep is invalid. */ - VertexIterator IteratorBegin(int timestep = 0) const; + VertexIterator IteratorBegin(TimeStepType timestep = 0) const; /** \brief Returns a const VertexIterator at the end element of the contour. @throw mitk::Exception if the timestep is invalid. */ - VertexIterator End(int timestep = 0) const; + VertexIterator End(TimeStepType timestep = 0) const; /** \brief Returns a const VertexIterator at the end element of the contour. @throw mitk::Exception if the timestep is invalid. */ - VertexIterator IteratorEnd(int timestep = 0) const; + VertexIterator IteratorEnd(TimeStepType timestep = 0) const; /** \brief Close the contour. The last control point will be linked with the first point. */ - virtual void Close(int timestep = 0); + virtual void Close(TimeStepType timestep = 0); /** \brief Set isClosed to false contour. The link between the last control point the first point will be removed. */ - virtual void Open(int timestep = 0); + virtual void Open(TimeStepType timestep = 0); /** \brief Set closed property to given boolean. false - The link between the last control point the first point will be removed. true - The last control point will be linked with the first point. */ - virtual void SetClosed(bool isClosed, int timestep = 0); + virtual void SetClosed(bool isClosed, TimeStepType timestep = 0); /** \brief Returns the number of vertices at a given timestep. \param timestep - default = 0 */ - int GetNumberOfVertices(int timestep = 0) const; + int GetNumberOfVertices(TimeStepType timestep = 0) const; /** \brief Returns whether the contour model is empty at a given timestep. \param timestep - default = 0 */ - virtual bool IsEmpty(int timestep) const; + virtual bool IsEmpty(TimeStepType timestep) const; /** \brief Returns whether the contour model is empty. */ bool IsEmpty() const override; /** \brief Returns the vertex at the index position within the container. + * If the index or timestep is invalid a nullptr will be returned. */ - virtual const VertexType *GetVertexAt(int index, int timestep = 0) const; + virtual const VertexType *GetVertexAt(int index, TimeStepType timestep = 0) const; /** \brief Remove a vertex at given timestep within the container. \return index of vertex. -1 if not found. */ - int GetIndex(const VertexType *vertex, int timestep = 0); + int GetIndex(const VertexType *vertex, TimeStepType timestep = 0); /** \brief Check if there isn't something at this timestep. */ bool IsEmptyTimeStep(unsigned int t) const override; /** \brief Check if mouse cursor is near the contour. */ - virtual bool IsNearContour(mitk::Point3D &point, float eps, int timestep); + virtual bool IsNearContour(Point3D &point, float eps, TimeStepType timestep); /** \brief Mark a vertex at an index in the container as selected. */ - bool SelectVertexAt(int index, int timestep = 0); + bool SelectVertexAt(int index, TimeStepType timestep = 0); /** \brief Mark a vertex at an index in the container as control point. */ - bool SetControlVertexAt(int index, int timestep = 0); + bool SetControlVertexAt(int index, TimeStepType timestep = 0); /** \brief Mark a vertex at a given position in 3D space. \param point - query point in 3D space \param eps - radius for nearest neighbour search (error bound). \param timestep - search at this timestep @return true = vertex found; false = no vertex found */ - bool SelectVertexAt(mitk::Point3D &point, float eps, int timestep = 0); + bool SelectVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); /* \pararm point - query point in 3D space \pararm eps - radius for nearest neighbour search (error bound). \pararm timestep - search at this timestep @return true = vertex found; false = no vertex found */ - bool SetControlVertexAt(mitk::Point3D &point, float eps, int timestep = 0); + bool SetControlVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); /** \brief Remove a vertex at given index within the container. @return true = the vertex was successfuly removed; false = wrong index. */ - bool RemoveVertexAt(int index, int timestep = 0); + bool RemoveVertexAt(int index, TimeStepType timestep = 0); /** \brief Remove a vertex at given timestep within the container. @return true = the vertex was successfuly removed. */ - bool RemoveVertex(const VertexType *vertex, int timestep = 0); + bool RemoveVertex(const VertexType *vertex, TimeStepType timestep = 0); /** \brief Remove a vertex at a query position in 3D space. The vertex to be removed will be search by nearest neighbour search. Note that possibly no vertex at this position and eps is stored inside the contour. @return true = the vertex was successfuly removed; false = no vertex found. */ - bool RemoveVertexAt(mitk::Point3D &point, float eps, int timestep = 0); + bool RemoveVertexAt(Point3D &point, float eps, TimeStepType timestep = 0); /** \brief Shift the currently selected vertex by a translation vector. \param translate - the translation vector. */ - void ShiftSelectedVertex(mitk::Vector3D &translate); + void ShiftSelectedVertex(Vector3D &translate); /** \brief Shift the whole contour by a translation vector at given timestep. \param translate - the translation vector. \param timestep - at this timestep the contour will be shifted. */ - void ShiftContour(mitk::Vector3D &translate, int timestep = 0); + void ShiftContour(Vector3D &translate, TimeStepType timestep = 0); /** \brief Clear the storage container at given timestep. All control points are removed at timestep. */ - virtual void Clear(int timestep); + virtual void Clear(TimeStepType timestep); /** \brief Initialize all data objects */ void Initialize() override; /** \brief Initialize object with specs of other contour. Note: No data will be copied. */ - void Initialize(mitk::ContourModel &other); + void Initialize(const ContourModel &other); /*++++++++++++++++++ method inherit from base data +++++++++++++++++++++++++++*/ /** \brief Inherit from base data - no region support available for contourModel objects. */ void SetRequestedRegionToLargestPossibleRegion() override; /** \brief Inherit from base data - no region support available for contourModel objects. */ bool RequestedRegionIsOutsideOfTheBufferedRegion() override; /** \brief Inherit from base data - no region support available for contourModel objects. */ bool VerifyRequestedRegion() override; - /** - \brief Get the updated geometry with recomputed bounds. - */ - virtual const mitk::BaseGeometry *GetUpdatedGeometry(int t = 0); - - /** - \brief Get the BaseGeometry for timestep t. - */ - virtual mitk::BaseGeometry *GetGeometry(int t = 0) const; - /** \brief Inherit from base data - no region support available for contourModel objects. */ void SetRequestedRegion(const itk::DataObject *data) override; /** - \brief Expand the timebounds of the TimeGeometry to given number of timesteps. + \brief Expand the contour model and its TimeGeometry to given number of timesteps. */ void Expand(unsigned int timeSteps) override; /** \brief Update the OutputInformation of a ContourModel object The BoundingBox of the contour will be updated, if necessary. */ void UpdateOutputInformation() override; /** \brief Clear the storage container. The object is set to initial state. All control points are removed and the number of timesteps are set to 1. */ void Clear() override; /** \brief overwrite if the Data can be called by an Interactor (StateMachine). */ void ExecuteOperation(Operation *operation) override; /** \brief Redistributes ontrol vertices with a given period (as number of vertices) \param period - the number of vertices between control points. \param timestep - at this timestep all lines will be rebuilt. */ - virtual void RedistributeControlVertices(int period, int timestep); + virtual void RedistributeControlVertices(int period, TimeStepType timestep); protected: mitkCloneMacro(Self); ContourModel(); - ContourModel(const mitk::ContourModel &other); + ContourModel(const ContourModel &other); ~ContourModel() override; // inherit from BaseData. called by Clear() void ClearData() override; // inherit from BaseData. Initial state of a contour with no vertices and a single timestep. void InitializeEmpty() override; // Shift a vertex - void ShiftVertex(VertexType *vertex, mitk::Vector3D &vector); + static void ShiftVertex(VertexType *vertex, Vector3D &vector); // Storage with time resolved support. ContourModelSeries m_ContourSeries; // The currently selected vertex. VertexType *m_SelectedVertex; // The interpolation of the line segment between control points. LineSegmentInterpolation m_lineInterpolation; // only update the bounding geometry if necessary bool m_UpdateBoundingBox; }; itkEventMacro(ContourModelEvent, itk::AnyEvent); itkEventMacro(ContourModelShiftEvent, ContourModelEvent); itkEventMacro(ContourModelSizeChangeEvent, ContourModelEvent); itkEventMacro(ContourModelAddEvent, ContourModelSizeChangeEvent); itkEventMacro(ContourModelRemoveEvent, ContourModelSizeChangeEvent); itkEventMacro(ContourModelExpandTimeBoundsEvent, ContourModelEvent); itkEventMacro(ContourModelClosedEvent, ContourModelEvent); } #endif diff --git a/Modules/ContourModel/Rendering/mitkContourModelGLMapper2DBase.cpp b/Modules/ContourModel/Rendering/mitkContourModelGLMapper2DBase.cpp index 675fa1b921..980672e655 100644 --- a/Modules/ContourModel/Rendering/mitkContourModelGLMapper2DBase.cpp +++ b/Modules/ContourModel/Rendering/mitkContourModelGLMapper2DBase.cpp @@ -1,396 +1,396 @@ /*============================================================================ 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 "mitkContourModelSetGLMapper2D.h" #include "mitkColorProperty.h" #include "mitkContourModelSet.h" #include "mitkPlaneGeometry.h" #include "mitkProperties.h" #include #include "vtkPen.h" #include "vtkContext2D.h" #include "vtkContextDevice2D.h" #include "vtkOpenGLContextDevice2D.h" #include "mitkManualPlacementAnnotationRenderer.h" #include "mitkBaseRenderer.h" #include "mitkContourModel.h" #include "mitkTextAnnotation2D.h" mitk::ContourModelGLMapper2DBase::ContourModelGLMapper2DBase(): m_Initialized(false) { m_PointNumbersAnnotation = mitk::TextAnnotation2D::New(); m_ControlPointNumbersAnnotation = mitk::TextAnnotation2D::New(); } mitk::ContourModelGLMapper2DBase::~ContourModelGLMapper2DBase() { } void mitk::ContourModelGLMapper2DBase::Initialize(mitk::BaseRenderer *) { vtkOpenGLContextDevice2D *device = nullptr; device = vtkOpenGLContextDevice2D::New(); if (device) { this->m_Context->Begin(device); device->Delete(); this->m_Initialized = true; } else { } } void mitk::ContourModelGLMapper2DBase::ApplyColorAndOpacityProperties(mitk::BaseRenderer *renderer, vtkActor * /*actor*/) { float rgba[4] = { 1.0f, 1.0f, 1.0f, 1.0f }; // check for color prop and use it for rendering if it exists GetDataNode()->GetColor(rgba, renderer, "color"); // check for opacity prop and use it for rendering if it exists GetDataNode()->GetOpacity(rgba[3], renderer, "opacity"); if (this->m_Context->GetPen() == nullptr) { return; } this->m_Context->GetPen()->SetColorF((double)rgba[0], (double)rgba[1], (double)rgba[2], (double)rgba[3]); } void mitk::ContourModelGLMapper2DBase::DrawContour(mitk::ContourModel *renderingContour, mitk::BaseRenderer *renderer) { if (std::find(m_RendererList.begin(), m_RendererList.end(), renderer) == m_RendererList.end()) { m_RendererList.push_back(renderer); } mitk::ManualPlacementAnnotationRenderer::AddAnnotation(m_PointNumbersAnnotation.GetPointer(), renderer); m_PointNumbersAnnotation->SetVisibility(false); mitk::ManualPlacementAnnotationRenderer::AddAnnotation(m_ControlPointNumbersAnnotation.GetPointer(), renderer); m_ControlPointNumbersAnnotation->SetVisibility(false); InternalDrawContour(renderingContour, renderer); } void mitk::ContourModelGLMapper2DBase::InternalDrawContour(mitk::ContourModel *renderingContour, mitk::BaseRenderer *renderer) { if (!renderingContour) return; if (!this->m_Initialized) { this->Initialize(renderer); } vtkOpenGLContextDevice2D::SafeDownCast( this->m_Context->GetDevice())->Begin(renderer->GetVtkRenderer()); mitk::DataNode *dataNode = this->GetDataNode(); renderingContour->UpdateOutputInformation(); - unsigned int timestep = renderer->GetTimeStep(); + const auto timestep = this->GetTimestep(); if (!renderingContour->IsEmptyTimeStep(timestep)) { // apply color and opacity read from the PropertyList ApplyColorAndOpacityProperties(renderer); mitk::ColorProperty::Pointer colorprop = dynamic_cast(dataNode->GetProperty("contour.color", renderer)); float opacity = 0.5; dataNode->GetFloatProperty("opacity", opacity, renderer); if (colorprop) { // set the color of the contour double red = colorprop->GetColor().GetRed(); double green = colorprop->GetColor().GetGreen(); double blue = colorprop->GetColor().GetBlue(); this->m_Context->GetPen()->SetColorF(red, green, blue, opacity); } mitk::ColorProperty::Pointer selectedcolor = dynamic_cast(dataNode->GetProperty("contour.points.color", renderer)); if (!selectedcolor) { selectedcolor = mitk::ColorProperty::New(1.0, 0.0, 0.1); } vtkLinearTransform *transform = dataNode->GetVtkTransform(); // ContourModel::OutputType point; mitk::Point3D point; mitk::Point3D p; float vtkp[3]; float lineWidth = 3.0; bool drawit = false; bool isHovering = false; dataNode->GetBoolProperty("contour.hovering", isHovering); if (isHovering) dataNode->GetFloatProperty("contour.hovering.width", lineWidth); else dataNode->GetFloatProperty("contour.width", lineWidth); bool showSegments = false; dataNode->GetBoolProperty("contour.segments.show", showSegments); bool showControlPoints = false; dataNode->GetBoolProperty("contour.controlpoints.show", showControlPoints); bool showPoints = false; dataNode->GetBoolProperty("contour.points.show", showPoints); bool showPointsNumbers = false; dataNode->GetBoolProperty("contour.points.text", showPointsNumbers); bool showControlPointsNumbers = false; dataNode->GetBoolProperty("contour.controlpoints.text", showControlPointsNumbers); bool projectmode = false; dataNode->GetVisibility(projectmode, renderer, "contour.project-onto-plane"); auto pointsIt = renderingContour->IteratorBegin(timestep); Point2D pt2d; // projected_p in display coordinates Point2D lastPt2d; int index = 0; mitk::ScalarType maxDiff = 0.25; while (pointsIt != renderingContour->IteratorEnd(timestep)) { lastPt2d = pt2d; point = (*pointsIt)->Coordinates; itk2vtk(point, vtkp); transform->TransformPoint(vtkp, vtkp); vtk2itk(vtkp, p); renderer->WorldToView(p, pt2d); ScalarType scalardiff = fabs(renderer->GetCurrentWorldPlaneGeometry()->SignedDistance(p)); // project to plane if (projectmode) { drawit = true; } else if (scalardiff < maxDiff) // point is close enough to be drawn { drawit = true; } else { drawit = false; } // draw line if (drawit) { if (showSegments) { // lastPt2d is not valid in first step if (!(pointsIt == renderingContour->IteratorBegin(timestep))) { this->m_Context->GetPen()->SetWidth(lineWidth); this->m_Context->DrawLine(pt2d[0], pt2d[1], lastPt2d[0], lastPt2d[1]); this->m_Context->GetPen()->SetWidth(1); } } if (showControlPoints) { // draw ontrol points if ((*pointsIt)->IsControlPoint) { float pointsize = 4; Point2D tmp; Vector2D horz, vert; horz[1] = 0; vert[0] = 0; horz[0] = pointsize; vert[1] = pointsize; this->m_Context->GetPen()->SetColorF(selectedcolor->GetColor().GetRed(), selectedcolor->GetColor().GetBlue(), selectedcolor->GetColor().GetGreen()); this->m_Context->GetPen()->SetWidth(1); // a rectangle around the point with the selected color auto* rectPts = new float[8]; tmp = pt2d - horz; rectPts[0] = tmp[0]; rectPts[1] = tmp[1]; tmp = pt2d + vert; rectPts[2] = tmp[0]; rectPts[3] = tmp[1]; tmp = pt2d + horz; rectPts[4] = tmp[0]; rectPts[5] = tmp[1]; tmp = pt2d - vert; rectPts[6] = tmp[0]; rectPts[7] = tmp[1]; this->m_Context->DrawPolygon(rectPts,4); // the actual point in the specified color to see the usual color of the point this->m_Context->GetPen()->SetColorF( colorprop->GetColor().GetRed(), colorprop->GetColor().GetGreen(), colorprop->GetColor().GetBlue()); this->m_Context->DrawPoint(pt2d[0], pt2d[1]); } } if (showPoints) { float pointsize = 3; Point2D tmp; Vector2D horz, vert; horz[1] = 0; vert[0] = 0; horz[0] = pointsize; vert[1] = pointsize; this->m_Context->GetPen()->SetColorF(0.0, 0.0, 0.0); this->m_Context->GetPen()->SetWidth(1); // a rectangle around the point with the selected color auto* rectPts = new float[8]; tmp = pt2d - horz; rectPts[0] = tmp[0]; rectPts[1] = tmp[1]; tmp = pt2d + vert; rectPts[2] = tmp[0]; rectPts[3] = tmp[1]; tmp = pt2d + horz; rectPts[4] = tmp[0]; rectPts[5] = tmp[1]; tmp = pt2d - vert; rectPts[6] = tmp[0]; rectPts[7] = tmp[1]; this->m_Context->DrawPolygon(rectPts, 4); // the actual point in the specified color to see the usual color of the point this->m_Context->GetPen()->SetColorF( colorprop->GetColor().GetRed(), colorprop->GetColor().GetGreen(), colorprop->GetColor().GetBlue()); this->m_Context->DrawPoint(pt2d[0], pt2d[1]); } if (showPointsNumbers) { std::string l; std::stringstream ss; ss << index; l.append(ss.str()); float rgb[3]; rgb[0] = 0.0; rgb[1] = 0.0; rgb[2] = 0.0; WriteTextWithAnnotation(m_PointNumbersAnnotation, l.c_str(), rgb, pt2d, renderer); } if (showControlPointsNumbers && (*pointsIt)->IsControlPoint) { std::string l; std::stringstream ss; ss << index; l.append(ss.str()); float rgb[3]; rgb[0] = 1.0; rgb[1] = 1.0; rgb[2] = 0.0; WriteTextWithAnnotation(m_ControlPointNumbersAnnotation, l.c_str(), rgb, pt2d, renderer); } index++; } pointsIt++; } // end while iterate over controlpoints // close contour if necessary if (renderingContour->IsClosed(timestep) && drawit && showSegments) { lastPt2d = pt2d; point = renderingContour->GetVertexAt(0, timestep)->Coordinates; itk2vtk(point, vtkp); transform->TransformPoint(vtkp, vtkp); vtk2itk(vtkp, p); renderer->WorldToDisplay(p, pt2d); this->m_Context->GetPen()->SetWidth(lineWidth); this->m_Context->DrawLine(lastPt2d[0], lastPt2d[1], pt2d[0], pt2d[1]); this->m_Context->GetPen()->SetWidth(1); } // draw selected vertex if exists if (renderingContour->GetSelectedVertex()) { // transform selected vertex point = renderingContour->GetSelectedVertex()->Coordinates; itk2vtk(point, vtkp); transform->TransformPoint(vtkp, vtkp); vtk2itk(vtkp, p); renderer->WorldToDisplay(p, pt2d); ScalarType scalardiff = fabs(renderer->GetCurrentWorldPlaneGeometry()->SignedDistance(p)); //---------------------------------- // draw point if close to plane if (scalardiff < maxDiff) { float pointsize = 5; Point2D tmp; this->m_Context->GetPen()->SetColorF(0.0, 1.0, 0.0); this->m_Context->GetPen()->SetWidth(1); // a rectangle around the point with the selected color auto* rectPts = new float[8]; // a diamond around the point // begin from upper left corner and paint clockwise rectPts[0] = pt2d[0] - pointsize; rectPts[1] = pt2d[1] + pointsize; rectPts[2] = pt2d[0] + pointsize; rectPts[3] = pt2d[1] + pointsize; rectPts[4] = pt2d[0] + pointsize; rectPts[5] = pt2d[1] - pointsize; rectPts[6] = pt2d[0] - pointsize; rectPts[7] = pt2d[1] - pointsize; this->m_Context->DrawPolygon(rectPts, 4); } //------------------------------------ } } this->m_Context->GetDevice()->End(); } void mitk::ContourModelGLMapper2DBase::WriteTextWithAnnotation(TextAnnotationPointerType textAnnotation, const char *text, float rgb[3], Point2D /*pt2d*/, mitk::BaseRenderer * /*renderer*/) { textAnnotation->SetText(text); textAnnotation->SetColor(rgb); textAnnotation->SetOpacity(1); textAnnotation->SetFontSize(16); textAnnotation->SetBoolProperty("drawShadow", false); textAnnotation->SetVisibility(true); } diff --git a/Modules/ContourModel/Rendering/mitkContourModelMapper2D.cpp b/Modules/ContourModel/Rendering/mitkContourModelMapper2D.cpp index 3e2df04f9d..4fdf5d812d 100644 --- a/Modules/ContourModel/Rendering/mitkContourModelMapper2D.cpp +++ b/Modules/ContourModel/Rendering/mitkContourModelMapper2D.cpp @@ -1,372 +1,372 @@ /*============================================================================ 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 #include #include #include #include mitk::ContourModelMapper2D::ContourModelMapper2D() { } mitk::ContourModelMapper2D::~ContourModelMapper2D() { } const mitk::ContourModel *mitk::ContourModelMapper2D::GetInput(void) { // convient way to get the data from the dataNode return static_cast(GetDataNode()->GetData()); } vtkProp *mitk::ContourModelMapper2D::GetVtkProp(mitk::BaseRenderer *renderer) { // return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actor; } void mitk::ContourModelMapper2D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { /*++ convert the contour to vtkPolyData and set it as input for our mapper ++*/ LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); auto *inputContour = static_cast(GetDataNode()->GetData()); - unsigned int timestep = renderer->GetTimeStep(); + const auto timestep = this->GetTimestep(); // if there's something to be rendered if (inputContour->GetNumberOfVertices(timestep) > 0) { localStorage->m_OutlinePolyData = this->CreateVtkPolyDataFromContour(inputContour, renderer); } this->ApplyContourProperties(renderer); localStorage->m_Mapper->SetInputData(localStorage->m_OutlinePolyData); } void mitk::ContourModelMapper2D::Update(mitk::BaseRenderer *renderer) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); if (!visible) return; // check if there is something to be rendered auto *data = static_cast(GetDataNode()->GetData()); if (data == nullptr) { return; } // Calculate time step of the input data for the specified renderer (integer value) this->CalculateTimeStep(renderer); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // Check if time step is valid const TimeGeometry *dataTimeGeometry = data->GetTimeGeometry(); if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || (!dataTimeGeometry->IsValidTimeStep(renderer->GetTimeStep()))) { // clear the rendered polydata localStorage->m_Mapper->RemoveAllInputs(); // SetInput(vtkSmartPointer::New()); return; } const DataNode *node = this->GetDataNode(); data->UpdateOutputInformation(); // check if something important has changed and we need to rerender if ((localStorage->m_LastUpdateTime < node->GetMTime()) // was the node modified? || (localStorage->m_LastUpdateTime < data->GetPipelineMTime()) // Was the data modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) // was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || (localStorage->m_LastUpdateTime < node->GetPropertyList()->GetMTime()) // was a property modified? || (localStorage->m_LastUpdateTime < node->GetPropertyList(renderer)->GetMTime())) { this->GenerateDataForRenderer(renderer); } // since we have checked that nothing important has changed, we can set // m_LastUpdateTime to the current time localStorage->m_LastUpdateTime.Modified(); } vtkSmartPointer mitk::ContourModelMapper2D::CreateVtkPolyDataFromContour(mitk::ContourModel *inputContour, mitk::BaseRenderer *renderer) { - unsigned int timestep = this->GetTimestep(); + const auto timestep = this->GetTimestep(); // Create a polydata to store everything in vtkSmartPointer resultingPolyData = vtkSmartPointer::New(); // check for the worldgeometry from the current render window const mitk::PlaneGeometry *currentWorldGeometry = renderer->GetCurrentWorldPlaneGeometry(); if (currentWorldGeometry) { // origin and normal of vtkPlane mitk::Point3D origin = currentWorldGeometry->GetOrigin(); mitk::Vector3D normal = currentWorldGeometry->GetNormal(); // the implicit function to slice through the polyData vtkSmartPointer plane = vtkSmartPointer::New(); plane->SetOrigin(origin[0], origin[1], origin[2]); plane->SetNormal(normal[0], normal[1], normal[2]); /* First of all convert the control points of the contourModel to vtk points * and add lines in between them */ // the points to draw vtkSmartPointer points = vtkSmartPointer::New(); // the lines to connect the points vtkSmartPointer lines = vtkSmartPointer::New(); // Create a polydata to store everything in vtkSmartPointer polyDataIn3D = vtkSmartPointer::New(); vtkSmartPointer appendPoly = vtkSmartPointer::New(); mitk::ContourModel::Pointer renderingContour = mitk::ContourModel::New(); renderingContour = inputContour; bool subdivision = false; this->GetDataNode()->GetBoolProperty("subdivision curve", subdivision, renderer); if (subdivision) { mitk::ContourModel::Pointer subdivContour = mitk::ContourModel::New(); mitk::ContourModelSubDivisionFilter::Pointer subdivFilter = mitk::ContourModelSubDivisionFilter::New(); subdivFilter->SetInput(inputContour); subdivFilter->Update(); subdivContour = subdivFilter->GetOutput(); if (subdivContour->GetNumberOfVertices() == 0) { subdivContour = inputContour; } renderingContour = subdivContour; } // iterate over all control points auto current = renderingContour->IteratorBegin(timestep); auto next = renderingContour->IteratorBegin(timestep); if (next != renderingContour->IteratorEnd(timestep)) { next++; auto end = renderingContour->IteratorEnd(timestep); while (next != end) { mitk::ContourModel::VertexType *currentControlPoint = *current; mitk::ContourModel::VertexType *nextControlPoint = *next; vtkIdType p1 = points->InsertNextPoint(currentControlPoint->Coordinates[0], currentControlPoint->Coordinates[1], currentControlPoint->Coordinates[2]); vtkIdType p2 = points->InsertNextPoint( nextControlPoint->Coordinates[0], nextControlPoint->Coordinates[1], nextControlPoint->Coordinates[2]); // add the line between both contorlPoints lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); if (currentControlPoint->IsControlPoint) { double coordinates[3]; coordinates[0] = currentControlPoint->Coordinates[0]; coordinates[1] = currentControlPoint->Coordinates[1]; coordinates[2] = currentControlPoint->Coordinates[2]; double distance = plane->DistanceToPlane(coordinates); if (distance < 0.1) { vtkSmartPointer sphere = vtkSmartPointer::New(); sphere->SetRadius(1.2); sphere->SetCenter(coordinates[0], coordinates[1], coordinates[2]); sphere->Update(); appendPoly->AddInputConnection(sphere->GetOutputPort()); } } current++; next++; } // end while (it!=end) // check if last control point is enabled to draw it if ((*current)->IsControlPoint) { double coordinates[3]; coordinates[0] = (*current)->Coordinates[0]; coordinates[1] = (*current)->Coordinates[1]; coordinates[2] = (*current)->Coordinates[2]; double distance = plane->DistanceToPlane(coordinates); if (distance < 0.1) { vtkSmartPointer sphere = vtkSmartPointer::New(); sphere->SetRadius(1.2); sphere->SetCenter(coordinates[0], coordinates[1], coordinates[2]); sphere->Update(); appendPoly->AddInputConnection(sphere->GetOutputPort()); } } /* If the contour is closed an additional line has to be created between the very first point * and the last point */ if (renderingContour->IsClosed(timestep)) { // add a line from the last to the first control point mitk::ContourModel::VertexType *firstControlPoint = *(renderingContour->IteratorBegin(timestep)); mitk::ContourModel::VertexType *lastControlPoint = *(--(renderingContour->IteratorEnd(timestep))); vtkIdType p2 = points->InsertNextPoint( lastControlPoint->Coordinates[0], lastControlPoint->Coordinates[1], lastControlPoint->Coordinates[2]); vtkIdType p1 = points->InsertNextPoint( firstControlPoint->Coordinates[0], firstControlPoint->Coordinates[1], firstControlPoint->Coordinates[2]); // add the line between both contorlPoints lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } // end if(isClosed) // Add the points to the dataset polyDataIn3D->SetPoints(points); // Add the lines to the dataset polyDataIn3D->SetLines(lines); // cut through polyData bool useCuttingPlane = false; this->GetDataNode()->GetBoolProperty("use cutting plane", useCuttingPlane, renderer); if (useCuttingPlane) { // slice through the data to get a 2D representation of the (possible) 3D contour // needed because currently there is no outher solution if the contour is within the plane vtkSmartPointer tubeFilter = vtkSmartPointer::New(); tubeFilter->SetInputData(polyDataIn3D); tubeFilter->SetRadius(0.05); // cuts through vtkPolyData with a given implicit function. In our case a plane vtkSmartPointer cutter = vtkSmartPointer::New(); cutter->SetCutFunction(plane); cutter->SetInputConnection(tubeFilter->GetOutputPort()); // we want the scalars of the input - so turn off generating the scalars within vtkCutter cutter->GenerateCutScalarsOff(); cutter->Update(); // set to 2D representation of the contour resultingPolyData = cutter->GetOutput(); } // end if(project contour) else { // set to 3D polyData resultingPolyData = polyDataIn3D; } } // end if (it != end) appendPoly->AddInputData(resultingPolyData); appendPoly->Update(); // return contour with control points return appendPoly->GetOutput(); } else { // return empty polyData return resultingPolyData; } } void mitk::ContourModelMapper2D::ApplyContourProperties(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); float lineWidth(1.0); if (this->GetDataNode()->GetFloatProperty("width", lineWidth, renderer)) { localStorage->m_Actor->GetProperty()->SetLineWidth(lineWidth); } mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty("color", renderer)); if (colorprop) { // set the color of the contour double red = colorprop->GetColor().GetRed(); double green = colorprop->GetColor().GetGreen(); double blue = colorprop->GetColor().GetBlue(); localStorage->m_Actor->GetProperty()->SetColor(red, green, blue); } // make sure that directional lighting isn't used for our contour localStorage->m_Actor->GetProperty()->SetAmbient(1.0); localStorage->m_Actor->GetProperty()->SetDiffuse(0.0); localStorage->m_Actor->GetProperty()->SetSpecular(0.0); } /*+++++++++++++++++++ LocalStorage part +++++++++++++++++++++++++*/ mitk::ContourModelMapper2D::LocalStorage *mitk::ContourModelMapper2D::GetLocalStorage(mitk::BaseRenderer *renderer) { return m_LSH.GetLocalStorage(renderer); } mitk::ContourModelMapper2D::LocalStorage::LocalStorage() { m_Mapper = vtkSmartPointer::New(); m_Actor = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); // set the mapper for the actor m_Actor->SetMapper(m_Mapper); } void mitk::ContourModelMapper2D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { node->AddProperty("color", ColorProperty::New(0.9, 1.0, 0.1), renderer, overwrite); node->AddProperty("width", mitk::FloatProperty::New(1.0), renderer, overwrite); node->AddProperty("use cutting plane", mitk::BoolProperty::New(true), renderer, overwrite); node->AddProperty("subdivision curve", mitk::BoolProperty::New(false), renderer, overwrite); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/ContourModel/Rendering/mitkContourModelMapper3D.cpp b/Modules/ContourModel/Rendering/mitkContourModelMapper3D.cpp index 84e7bacc3b..03fa9641fc 100644 --- a/Modules/ContourModel/Rendering/mitkContourModelMapper3D.cpp +++ b/Modules/ContourModel/Rendering/mitkContourModelMapper3D.cpp @@ -1,233 +1,233 @@ /*============================================================================ 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 mitk::ContourModelMapper3D::ContourModelMapper3D() { } mitk::ContourModelMapper3D::~ContourModelMapper3D() { } const mitk::ContourModel *mitk::ContourModelMapper3D::GetInput(void) { // convient way to get the data from the dataNode return static_cast(GetDataNode()->GetData()); } vtkProp *mitk::ContourModelMapper3D::GetVtkProp(mitk::BaseRenderer *renderer) { // return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Actor; } void mitk::ContourModelMapper3D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { /* First convert the contourModel to vtkPolyData, then tube filter it and * set it input for our mapper */ LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); auto *inputContour = static_cast(GetDataNode()->GetData()); localStorage->m_OutlinePolyData = this->CreateVtkPolyDataFromContour(inputContour); this->ApplyContourProperties(renderer); // tube filter the polyData localStorage->m_TubeFilter->SetInputData(localStorage->m_OutlinePolyData); float lineWidth(1.0); if (this->GetDataNode()->GetFloatProperty("contour.3D.width", lineWidth, renderer)) { localStorage->m_TubeFilter->SetRadius(lineWidth); } else { localStorage->m_TubeFilter->SetRadius(0.5); } localStorage->m_TubeFilter->CappingOn(); localStorage->m_TubeFilter->SetNumberOfSides(10); localStorage->m_TubeFilter->Update(); localStorage->m_Mapper->SetInputConnection(localStorage->m_TubeFilter->GetOutputPort()); } void mitk::ContourModelMapper3D::Update(mitk::BaseRenderer *renderer) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); auto *data = static_cast(GetDataNode()->GetData()); if (data == nullptr) { return; } // Calculate time step of the input data for the specified renderer (integer value) this->CalculateTimeStep(renderer); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); // Check if time step is valid const TimeGeometry *dataTimeGeometry = data->GetTimeGeometry(); if ((dataTimeGeometry == nullptr) || (dataTimeGeometry->CountTimeSteps() == 0) || (!dataTimeGeometry->IsValidTimePoint(renderer->GetTime())) || (this->GetTimestep() == -1)) { // clear the rendered polydata localStorage->m_Mapper->SetInputData(vtkSmartPointer::New()); return; } const DataNode *node = this->GetDataNode(); data->UpdateOutputInformation(); // check if something important has changed and we need to rerender if ((localStorage->m_LastUpdateTime < node->GetMTime()) // was the node modified? || (localStorage->m_LastUpdateTime < data->GetPipelineMTime()) // Was the data modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) // was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || (localStorage->m_LastUpdateTime < node->GetPropertyList()->GetMTime()) // was a property modified? || (localStorage->m_LastUpdateTime < node->GetPropertyList(renderer)->GetMTime())) { this->GenerateDataForRenderer(renderer); } // since we have checked that nothing important has changed, we can set // m_LastUpdateTime to the current time localStorage->m_LastUpdateTime.Modified(); } vtkSmartPointer mitk::ContourModelMapper3D::CreateVtkPolyDataFromContour(mitk::ContourModel *inputContour) { - unsigned int timestep = this->GetTimestep(); + const auto timestep = this->GetTimestep(); // the points to draw vtkSmartPointer points = vtkSmartPointer::New(); // the lines to connect the points vtkSmartPointer lines = vtkSmartPointer::New(); // Create a polydata to store everything in vtkSmartPointer polyData = vtkSmartPointer::New(); // iterate over the control points auto current = inputContour->IteratorBegin(timestep); auto next = inputContour->IteratorBegin(timestep); if (next != inputContour->IteratorEnd(timestep)) { next++; auto end = inputContour->IteratorEnd(timestep); while (next != end) { mitk::ContourModel::VertexType *currentControlPoint = *current; mitk::ContourModel::VertexType *nextControlPoint = *next; if (!(currentControlPoint->Coordinates[0] == nextControlPoint->Coordinates[0] && currentControlPoint->Coordinates[1] == nextControlPoint->Coordinates[1] && currentControlPoint->Coordinates[2] == nextControlPoint->Coordinates[2])) { vtkIdType p1 = points->InsertNextPoint(currentControlPoint->Coordinates[0], currentControlPoint->Coordinates[1], currentControlPoint->Coordinates[2]); vtkIdType p2 = points->InsertNextPoint( nextControlPoint->Coordinates[0], nextControlPoint->Coordinates[1], nextControlPoint->Coordinates[2]); // add the line between both contorlPoints lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } current++; next++; } if (inputContour->IsClosed(timestep)) { // If the contour is closed add a line from the last to the first control point mitk::ContourModel::VertexType *firstControlPoint = *(inputContour->IteratorBegin(timestep)); mitk::ContourModel::VertexType *lastControlPoint = *(--(inputContour->IteratorEnd(timestep))); if (lastControlPoint->Coordinates[0] != firstControlPoint->Coordinates[0] || lastControlPoint->Coordinates[1] != firstControlPoint->Coordinates[1] || lastControlPoint->Coordinates[2] != firstControlPoint->Coordinates[2]) { vtkIdType p2 = points->InsertNextPoint( lastControlPoint->Coordinates[0], lastControlPoint->Coordinates[1], lastControlPoint->Coordinates[2]); vtkIdType p1 = points->InsertNextPoint( firstControlPoint->Coordinates[0], firstControlPoint->Coordinates[1], firstControlPoint->Coordinates[2]); // add the line to the cellArray lines->InsertNextCell(2); lines->InsertCellPoint(p1); lines->InsertCellPoint(p2); } } // Add the points to the dataset polyData->SetPoints(points); // Add the lines to the dataset polyData->SetLines(lines); } return polyData; } void mitk::ContourModelMapper3D::ApplyContourProperties(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty("contour.color", renderer)); if (colorprop) { // set the color of the contour double red = colorprop->GetColor().GetRed(); double green = colorprop->GetColor().GetGreen(); double blue = colorprop->GetColor().GetBlue(); localStorage->m_Actor->GetProperty()->SetColor(red, green, blue); } } /*+++++++++++++++++++ LocalStorage part +++++++++++++++++++++++++*/ mitk::ContourModelMapper3D::LocalStorage *mitk::ContourModelMapper3D::GetLocalStorage(mitk::BaseRenderer *renderer) { return m_LSH.GetLocalStorage(renderer); } mitk::ContourModelMapper3D::LocalStorage::LocalStorage() { m_Mapper = vtkSmartPointer::New(); m_Actor = vtkSmartPointer::New(); m_OutlinePolyData = vtkSmartPointer::New(); m_TubeFilter = vtkSmartPointer::New(); // set the mapper for the actor m_Actor->SetMapper(m_Mapper); } void mitk::ContourModelMapper3D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { node->AddProperty("color", ColorProperty::New(1.0, 0.0, 0.0), renderer, overwrite); node->AddProperty("contour.3D.width", mitk::FloatProperty::New(0.5), renderer, overwrite); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/ContourModel/Rendering/mitkContourModelSetMapper3D.cpp b/Modules/ContourModel/Rendering/mitkContourModelSetMapper3D.cpp index 0cd56a24a1..cab6397f83 100644 --- a/Modules/ContourModel/Rendering/mitkContourModelSetMapper3D.cpp +++ b/Modules/ContourModel/Rendering/mitkContourModelSetMapper3D.cpp @@ -1,232 +1,232 @@ /*============================================================================ 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 "mitkSurface.h" #include #include #include #include mitk::ContourModelSetMapper3D::ContourModelSetMapper3D() { } mitk::ContourModelSetMapper3D::~ContourModelSetMapper3D() { } const mitk::ContourModelSet *mitk::ContourModelSetMapper3D::GetInput(void) { // convenient way to get the data from the dataNode return static_cast(GetDataNode()->GetData()); } vtkProp *mitk::ContourModelSetMapper3D::GetVtkProp(mitk::BaseRenderer *renderer) { // return the actor corresponding to the renderer return m_LSH.GetLocalStorage(renderer)->m_Assembly; } void mitk::ContourModelSetMapper3D::GenerateDataForRenderer(mitk::BaseRenderer *renderer) { /* First convert the contourModel to vtkPolyData, then tube filter it and * set it input for our mapper */ LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); auto *contourModelSet = dynamic_cast(this->GetDataNode()->GetData()); if (contourModelSet != nullptr) { vtkSmartPointer points = vtkSmartPointer::New(); vtkSmartPointer cells = vtkSmartPointer::New(); vtkIdType baseIndex = 0; auto it = contourModelSet->Begin(); auto end = contourModelSet->End(); while (it != end) { ContourModel *contourModel = it->GetPointer(); auto vertIt = contourModel->Begin(); auto vertEnd = contourModel->End(); while (vertIt != vertEnd) { points->InsertNextPoint((*vertIt)->Coordinates[0], (*vertIt)->Coordinates[1], (*vertIt)->Coordinates[2]); ++vertIt; } vtkSmartPointer line = vtkSmartPointer::New(); vtkIdList *pointIds = line->GetPointIds(); vtkIdType numPoints = contourModel->GetNumberOfVertices(); pointIds->SetNumberOfIds(numPoints + 1); for (vtkIdType i = 0; i < numPoints; ++i) pointIds->SetId(i, baseIndex + i); pointIds->SetId(numPoints, baseIndex); cells->InsertNextCell(line); baseIndex += numPoints; ++it; } vtkSmartPointer polyData = vtkSmartPointer::New(); polyData->SetPoints(points); polyData->SetLines(cells); vtkSmartPointer mapper = vtkSmartPointer::New(); vtkSmartPointer actor = vtkSmartPointer::New(); actor->SetMapper(mapper); mapper->SetInputData(polyData); localStorage->m_Assembly->AddPart(actor); } this->ApplyContourProperties(renderer); this->ApplyContourModelSetProperties(renderer); } void mitk::ContourModelSetMapper3D::Update(mitk::BaseRenderer *renderer) { bool visible = true; GetDataNode()->GetVisibility(visible, renderer, "visible"); auto *data = static_cast(GetDataNode()->GetData()); if (data == nullptr) { return; } // Calculate time step of the input data for the specified renderer (integer value) this->CalculateTimeStep(renderer); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); if (this->GetTimestep() == -1) { return; } const DataNode *node = this->GetDataNode(); data->UpdateOutputInformation(); // check if something important has changed and we need to rerender if ((localStorage->m_LastUpdateTime < node->GetMTime()) // was the node modified? || (localStorage->m_LastUpdateTime < data->GetPipelineMTime()) // Was the data modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometryUpdateTime()) // was the geometry modified? || (localStorage->m_LastUpdateTime < renderer->GetCurrentWorldPlaneGeometry()->GetMTime()) || (localStorage->m_LastUpdateTime < node->GetPropertyList()->GetMTime()) // was a property modified? || (localStorage->m_LastUpdateTime < node->GetPropertyList(renderer)->GetMTime())) { this->GenerateDataForRenderer(renderer); } // since we have checked that nothing important has changed, we can set // m_LastUpdateTime to the current time localStorage->m_LastUpdateTime.Modified(); } vtkSmartPointer mitk::ContourModelSetMapper3D::CreateVtkPolyDataFromContour( mitk::ContourModel *inputContour, mitk::BaseRenderer *renderer) { - unsigned int timestep = this->GetTimestep(); + const auto timestep = this->GetTimestep(); LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); localStorage->m_contourToPolyData->SetInput(inputContour); localStorage->m_contourToPolyData->Update(); vtkSmartPointer polyData = vtkSmartPointer::New(); polyData = localStorage->m_contourToPolyData->GetOutput()->GetVtkPolyData(timestep); return polyData; } void mitk::ContourModelSetMapper3D::ApplyContourModelSetProperties(BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); DataNode *dataNode = this->GetDataNode(); if (dataNode != nullptr) { float lineWidth = 1; dataNode->GetFloatProperty("contour.3D.width", lineWidth, renderer); vtkSmartPointer collection = vtkSmartPointer::New(); localStorage->m_Assembly->GetActors(collection); collection->InitTraversal(); for (vtkIdType i = 0; i < collection->GetNumberOfItems(); i++) { vtkActor::SafeDownCast(collection->GetNextProp())->GetProperty()->SetLineWidth(lineWidth); } } } void mitk::ContourModelSetMapper3D::ApplyContourProperties(mitk::BaseRenderer *renderer) { LocalStorage *localStorage = m_LSH.GetLocalStorage(renderer); mitk::ColorProperty::Pointer colorprop = dynamic_cast(GetDataNode()->GetProperty("contour.color", renderer)); if (colorprop) { // set the color of the contour double red = colorprop->GetColor().GetRed(); double green = colorprop->GetColor().GetGreen(); double blue = colorprop->GetColor().GetBlue(); vtkSmartPointer collection = vtkSmartPointer::New(); localStorage->m_Assembly->GetActors(collection); collection->InitTraversal(); for (vtkIdType i = 0; i < collection->GetNumberOfItems(); i++) { vtkActor::SafeDownCast(collection->GetNextProp())->GetProperty()->SetColor(red, green, blue); } } } /*+++++++++++++++++++ LocalStorage part +++++++++++++++++++++++++*/ mitk::ContourModelSetMapper3D::LocalStorage *mitk::ContourModelSetMapper3D::GetLocalStorage( mitk::BaseRenderer *renderer) { return m_LSH.GetLocalStorage(renderer); } mitk::ContourModelSetMapper3D::LocalStorage::LocalStorage() { m_Assembly = vtkSmartPointer::New(); m_contourToPolyData = mitk::ContourModelToSurfaceFilter::New(); } void mitk::ContourModelSetMapper3D::SetDefaultProperties(mitk::DataNode *node, mitk::BaseRenderer *renderer, bool overwrite) { node->AddProperty("color", ColorProperty::New(1.0, 0.0, 0.0), renderer, overwrite); node->AddProperty("contour.3D.width", mitk::FloatProperty::New(0.5), renderer, overwrite); Superclass::SetDefaultProperties(node, renderer, overwrite); } diff --git a/Modules/ContourModel/Testing/CMakeLists.txt b/Modules/ContourModel/Testing/CMakeLists.txt index 263e573340..153cd81e2e 100644 --- a/Modules/ContourModel/Testing/CMakeLists.txt +++ b/Modules/ContourModel/Testing/CMakeLists.txt @@ -1,2 +1 @@ MITK_CREATE_MODULE_TESTS() -#mitkAddCustomModuleTest(mitkSegmentationInterpolationTest mitkSegmentationInterpolationTest ${MITK_DATA_DIR}/interpolation_test_manual.nrrd ${MITK_DATA_DIR}/interpolation_test_result.nrrd) diff --git a/Modules/ContourModel/Testing/files.cmake b/Modules/ContourModel/Testing/files.cmake index 8d8021a143..804bb86385 100644 --- a/Modules/ContourModel/Testing/files.cmake +++ b/Modules/ContourModel/Testing/files.cmake @@ -1,13 +1,14 @@ set(MODULE_TESTS + mitkContourElementTest.cpp mitkContourModelTest.cpp mitkContourModelIOTest.cpp mitkContourModelSetTest.cpp ) set(MODULE_IMAGE_TESTS ) set(MODULE_CUSTOM_TESTS ) set(MODULE_TESTIMAGE ) diff --git a/Modules/ContourModel/Testing/mitkContourElementTest.cpp b/Modules/ContourModel/Testing/mitkContourElementTest.cpp new file mode 100644 index 0000000000..b75a434883 --- /dev/null +++ b/Modules/ContourModel/Testing/mitkContourElementTest.cpp @@ -0,0 +1,349 @@ +/*============================================================================ + +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 "mitkContourElement.h" + +#include "mitkTestFixture.h" +#include "mitkTestingMacros.h" +#include + +class mitkContourElementTestSuite : public mitk::TestFixture +{ + CPPUNIT_TEST_SUITE(mitkContourElementTestSuite); + // Test the append method + MITK_TEST(Iterator); + MITK_TEST(AddVertex); + MITK_TEST(AddVertexAtFront); + MITK_TEST(InsertVertexAtIndex); + MITK_TEST(GetSetVertexAt); + MITK_TEST(OpenAndClose); + MITK_TEST(OtherGetters); + MITK_TEST(Concatenate); + MITK_TEST(RemoveVertex); + MITK_TEST(Clear); + MITK_TEST(GetControlVertices); + MITK_TEST(RedistributeControlVertices); + MITK_TEST(Others); + + CPPUNIT_TEST_SUITE_END(); + +private: + mitk::ContourElement::Pointer m_Contour1to4; + mitk::ContourElement::Pointer m_Contour5to6; + mitk::ContourElement::Pointer m_Contour_empty; + mitk::Point3D m_p1; + mitk::Point3D m_p2; + mitk::Point3D m_p3; + mitk::Point3D m_p4; + mitk::Point3D m_p5; + mitk::Point3D m_p6; + mitk::Point3D m_p7; + +public: + static mitk::Point3D GeneratePoint(double val) + { + return mitk::Point3D(val); + } + + void setUp() override + { + m_p1 = GeneratePoint(1); + m_p2 = GeneratePoint(2); + m_p3 = GeneratePoint(3); + m_p4 = GeneratePoint(4); + m_p5 = GeneratePoint(5); + m_p6 = GeneratePoint(6); + m_p7 = GeneratePoint(7); + + m_Contour1to4 = mitk::ContourElement::New(); + m_Contour5to6 = mitk::ContourElement::New(); + m_Contour_empty = mitk::ContourElement::New(); + + m_Contour1to4->AddVertex(m_p1, true); + m_Contour1to4->AddVertex(m_p2, false); + m_Contour1to4->AddVertex(m_p3, true); + m_Contour1to4->AddVertex(m_p4, false); + + m_Contour5to6->AddVertex(m_p5, false); + m_Contour5to6->AddVertex(m_p6, true); + + } + + void tearDown() override {} + + void Iterator() + { + mitk::ContourElement::ConstPointer constcontour = m_Contour5to6.GetPointer(); + CPPUNIT_ASSERT_MESSAGE("Begin does not point to correct element", m_Contour1to4->IteratorBegin().operator*()->Coordinates == m_p1); + CPPUNIT_ASSERT_MESSAGE("Begin does not point to correct element", m_Contour1to4->ConstIteratorBegin().operator*()->Coordinates == m_p1); + CPPUNIT_ASSERT_MESSAGE("Begin does not point to correct element", m_Contour5to6->begin().operator*()->Coordinates == m_p5); + CPPUNIT_ASSERT_MESSAGE("Begin does not point to correct element", constcontour->begin().operator*()->Coordinates == m_p5); + + CPPUNIT_ASSERT_MESSAGE("End does not point to correct element", m_Contour_empty->ConstIteratorBegin() == m_Contour_empty->ConstIteratorEnd()); + CPPUNIT_ASSERT_MESSAGE("End does not point to correct element", m_Contour_empty->IteratorBegin() == m_Contour_empty->IteratorEnd()); + CPPUNIT_ASSERT_MESSAGE("End does not point to correct element", m_Contour_empty->begin() == m_Contour_empty->end()); + } + + void AddVertex() + { + m_Contour_empty->AddVertex(m_p1, false); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(0)->Coordinates == m_p1); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(0)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour_empty->GetSize() == 1); + + m_Contour_empty->AddVertex(m_p7, true); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(0)->Coordinates == m_p1); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(0)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(1)->Coordinates == m_p7); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(1)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour_empty->GetSize() == 2); + } + + void AddVertexAtFront() + { + m_Contour_empty->AddVertexAtFront(m_p1, false); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(0)->Coordinates == m_p1); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(0)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour_empty->GetSize() == 1); + + m_Contour_empty->AddVertexAtFront(m_p7, true); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(1)->Coordinates == m_p1); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(1)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(0)->Coordinates == m_p7); + CPPUNIT_ASSERT(m_Contour_empty->GetVertexAt(0)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour_empty->GetSize() == 2); + } + + void InsertVertexAtIndex() + { + mitk::Point3D outOfBountPoint; + m_Contour1to4->InsertVertexAtIndex(m_p5, false, 0); + m_Contour1to4->InsertVertexAtIndex(m_p7, true, 2); + m_Contour1to4->InsertVertexAtIndex(outOfBountPoint, true, 6); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(0)->Coordinates == m_p5); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(0)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(1)->Coordinates == m_p1); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(1)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(2)->Coordinates == m_p7); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(2)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(3)->Coordinates == m_p2); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(3)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetSize() == 6); + } + + void GetSetVertexAt() + { + mitk::ContourElement::ConstPointer constcontour = m_Contour1to4.GetPointer(); + + auto v0 = m_Contour1to4->GetVertexAt(0); + auto v1 = m_Contour1to4->GetVertexAt(1); + + CPPUNIT_ASSERT(v0->Coordinates == m_p1); + CPPUNIT_ASSERT(v0->IsControlPoint == true); + CPPUNIT_ASSERT(v1->Coordinates == m_p2); + CPPUNIT_ASSERT(v1->IsControlPoint == false); + CPPUNIT_ASSERT(constcontour->GetVertexAt(0)->Coordinates == m_p1); + CPPUNIT_ASSERT(constcontour->GetVertexAt(0)->IsControlPoint == true); + CPPUNIT_ASSERT(constcontour->GetVertexAt(1)->Coordinates == m_p2); + CPPUNIT_ASSERT(constcontour->GetVertexAt(1)->IsControlPoint == false); + + m_Contour1to4->SetVertexAt(0, m_p7); + CPPUNIT_ASSERT(v0->Coordinates == m_p7); + CPPUNIT_ASSERT(v0->IsControlPoint == true); + + m_Contour1to4->SetVertexAt(1, m_Contour5to6->GetVertexAt(1)); + CPPUNIT_ASSERT(v1->Coordinates == m_Contour5to6->GetVertexAt(1)->Coordinates); + CPPUNIT_ASSERT(v1->IsControlPoint == m_Contour5to6->GetVertexAt(1)->IsControlPoint); + CPPUNIT_ASSERT(v1 != m_Contour5to6->GetVertexAt(1)); + + mitk::Point3D search = GeneratePoint(6.05); + + auto finding = m_Contour1to4->GetVertexAt(search, 0.); + CPPUNIT_ASSERT(nullptr == finding); + + finding = m_Contour1to4->GetVertexAt(search, 0.1); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(1) == finding); + } + + void OpenAndClose() + { + CPPUNIT_ASSERT(!m_Contour1to4->IsClosed()); + CPPUNIT_ASSERT(!m_Contour_empty->IsClosed()); + m_Contour1to4->Close(); + m_Contour_empty->SetClosed(true); + CPPUNIT_ASSERT(m_Contour1to4->IsClosed()); + CPPUNIT_ASSERT(m_Contour_empty->IsClosed()); + m_Contour1to4->Open(); + m_Contour_empty->SetClosed(false); + CPPUNIT_ASSERT(!m_Contour1to4->IsClosed()); + CPPUNIT_ASSERT(!m_Contour_empty->IsClosed()); + } + + void OtherGetters() + { + CPPUNIT_ASSERT(m_Contour1to4->GetIndex(m_Contour1to4->GetVertexAt(0)) == 0); + CPPUNIT_ASSERT(m_Contour1to4->GetIndex(m_Contour1to4->GetVertexAt(1)) == 1); + CPPUNIT_ASSERT(m_Contour1to4->GetIndex(m_Contour1to4->GetVertexAt(3)) == 3); + CPPUNIT_ASSERT(m_Contour1to4->GetIndex(m_Contour5to6->GetVertexAt(0)) == mitk::ContourElement::NPOS); + } + + void Concatenate() + { + m_Contour5to6->Concatenate(m_Contour1to4, true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(0)->Coordinates == m_p5); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(0)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(1)->Coordinates == m_p6); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(1)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(2)->Coordinates == m_p1); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(2)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(3)->Coordinates == m_p2); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(3)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(4)->Coordinates == m_p3); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(4)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(5)->Coordinates == m_p4); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(5)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetSize() == 6); + + m_Contour_empty->AddVertex(m_p1, false); + m_Contour5to6->Concatenate(m_Contour_empty, true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(0)->Coordinates == m_p5); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(0)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(1)->Coordinates == m_p6); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(1)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(2)->Coordinates == m_p1); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(2)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(3)->Coordinates == m_p2); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(3)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(4)->Coordinates == m_p3); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(4)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(5)->Coordinates == m_p4); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(5)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetSize() == 6); + + m_Contour5to6->Concatenate(m_Contour_empty, false); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(0)->Coordinates == m_p5); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(0)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(1)->Coordinates == m_p6); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(1)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(2)->Coordinates == m_p1); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(2)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(3)->Coordinates == m_p2); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(3)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(4)->Coordinates == m_p3); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(4)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(5)->Coordinates == m_p4); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(5)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(6)->Coordinates == m_p1); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(6)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour5to6->GetSize() == 7); + } + + void RemoveVertex() + { + CPPUNIT_ASSERT(m_Contour1to4->RemoveVertex(m_Contour1to4->GetVertexAt(0))); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(0)->Coordinates == m_p2); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(0)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetSize() == 3); + CPPUNIT_ASSERT(!m_Contour1to4->RemoveVertex(m_Contour5to6->GetVertexAt(0))); + CPPUNIT_ASSERT(m_Contour1to4->GetSize() == 3); + + CPPUNIT_ASSERT(m_Contour1to4->RemoveVertexAt(1)); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(0)->Coordinates == m_p2); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(0)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(1)->Coordinates == m_p4); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(1)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetSize() == 2); + + CPPUNIT_ASSERT(!m_Contour1to4->RemoveVertexAt(2)); + CPPUNIT_ASSERT(m_Contour1to4->GetSize() == 2); + + mitk::Point3D search = GeneratePoint(6.05); + + CPPUNIT_ASSERT(!m_Contour1to4->RemoveVertexAt(search,0.1)); + CPPUNIT_ASSERT(m_Contour1to4->GetSize() == 2); + + CPPUNIT_ASSERT(m_Contour5to6->RemoveVertexAt(search, 0.1)); + CPPUNIT_ASSERT(m_Contour5to6->GetSize() == 1); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(0)->Coordinates == m_p5); + CPPUNIT_ASSERT(m_Contour5to6->GetVertexAt(0)->IsControlPoint == false); + } + + void Clear() + { + m_Contour1to4->Clear(); + CPPUNIT_ASSERT(m_Contour1to4->IsEmpty()); + m_Contour_empty->Clear(); + CPPUNIT_ASSERT(m_Contour_empty->IsEmpty()); + } + + void GetControlVertices() + { + auto controlVs = m_Contour1to4->GetControlVertices(); + CPPUNIT_ASSERT(controlVs.size() == 2); + CPPUNIT_ASSERT(controlVs[0] == m_Contour1to4->GetVertexAt(0)); + CPPUNIT_ASSERT(controlVs[1] == m_Contour1to4->GetVertexAt(2)); + + controlVs = m_Contour_empty->GetControlVertices(); + CPPUNIT_ASSERT(controlVs.empty()); + } + + void RedistributeControlVertices() + { + //just adding more nodes to better check the redistribution + m_Contour1to4->Concatenate(m_Contour1to4, false); + m_Contour1to4->Concatenate(m_Contour1to4, false); + + m_Contour1to4->RedistributeControlVertices(nullptr, 3); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(0)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(1)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(2)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(3)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(4)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(5)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(6)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(7)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(8)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(9)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(10)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(11)->IsControlPoint == false); + + m_Contour1to4->RedistributeControlVertices(m_Contour1to4->GetVertexAt(4), 4); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(0)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(1)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(2)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(3)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(4)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(5)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(6)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(7)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(8)->IsControlPoint == true); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(9)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(10)->IsControlPoint == false); + CPPUNIT_ASSERT(m_Contour1to4->GetVertexAt(11)->IsControlPoint == false); + } + + void Others() + { + CPPUNIT_ASSERT(m_Contour1to4->GetSize() == 4); + CPPUNIT_ASSERT(m_Contour_empty->GetSize() == 0); + CPPUNIT_ASSERT(!m_Contour1to4->IsEmpty()); + CPPUNIT_ASSERT(m_Contour_empty->IsEmpty()); + + mitk::ContourElement::Pointer copyConstructed = m_Contour5to6->Clone(); + CPPUNIT_ASSERT(*(m_Contour5to6->GetVertexAt(0)) == *(copyConstructed->GetVertexAt(0))); + CPPUNIT_ASSERT(*(m_Contour5to6->GetVertexAt(1)) == *(copyConstructed->GetVertexAt(1))); + CPPUNIT_ASSERT(m_Contour5to6->GetSize() == copyConstructed->GetSize()); + } + +}; + +MITK_TEST_SUITE_REGISTRATION(mitkContourElement) diff --git a/Modules/ContourModel/Testing/mitkContourModelTest.cpp b/Modules/ContourModel/Testing/mitkContourModelTest.cpp index 7f2e643776..9464bdcb83 100644 --- a/Modules/ContourModel/Testing/mitkContourModelTest.cpp +++ b/Modules/ContourModel/Testing/mitkContourModelTest.cpp @@ -1,419 +1,483 @@ /*============================================================================ 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() > 0, "Add a Vertex, size increased"); + 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."); + + contour->Expand(3); + + mitk::Point3D p2; + p2[0] = p2[1] = p2[2] = 2; + mitk::Point3D p3; + p3[0] = p3[1] = p3[2] = 3; + + contour->AddVertex(p2, mitk::TimeStepType(1)); + contour->AddVertex(mitk::ContourModel::VertexType(p3), mitk::TimeStepType(1)); + + MITK_TEST_CONDITION(!contour->IsEmptyTimeStep(1), "Add a vertex to an unsupported time step has not added an contour element."); + MITK_TEST_CONDITION(contour->GetVertexAt(0,1)->Coordinates == p2, "Add a vertex to the 2nd time step (as Point)."); + MITK_TEST_CONDITION(contour->GetVertexAt(1,1)->Coordinates == p3, "Add a vertex to the 2nd time step via overload (as vertex type)."); + MITK_TEST_CONDITION(contour->GetNumberOfVertices(1) == 2, "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); - int invalidTimeStep = 42; + 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"); + + MITK_TEST_CONDITION(contour->GetVertexAt(0, 5) == nullptr, "Access a vertex on an invalid time step."); + MITK_TEST_CONDITION(contour->GetVertexAt(10, 0) == nullptr, "Access a vertex on an invalid index."); } 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)); + 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)); + 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"); + + 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/Core/include/mitkBaseRenderer.h b/Modules/Core/include/mitkBaseRenderer.h index b50a10e5c1..b4015ffd7a 100644 --- a/Modules/Core/include/mitkBaseRenderer.h +++ b/Modules/Core/include/mitkBaseRenderer.h @@ -1,532 +1,532 @@ /*============================================================================ 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 BASERENDERER_H_HEADER_INCLUDED_C1CCA0F4 #define BASERENDERER_H_HEADER_INCLUDED_C1CCA0F4 #include "mitkCameraRotationController.h" #include "mitkDataStorage.h" #include "mitkPlaneGeometry.h" #include "mitkPlaneGeometryData.h" #include "mitkSliceNavigationController.h" #include "mitkTimeGeometry.h" #include "mitkBindDispatcherInteractor.h" #include "mitkDispatcher.h" #include #include #include #include // DEPRECATED #include namespace mitk { class NavigationController; class SliceNavigationController; class CameraRotationController; class CameraController; class DataStorage; class Mapper; class BaseLocalStorageHandler; class KeyEvent; //##Documentation //## @brief Organizes the rendering process //## //## Organizes the rendering process. A Renderer contains a reference to a //## DataStorage and asks the mappers of the data objects to render //## the data into the renderwindow it is associated to. //## //## \#Render() checks if rendering is currently allowed by calling //## RenderWindow::PrepareRendering(). Initialization of a rendering context //## can also be performed in this method. //## //## The actual rendering code has been moved to \#Repaint() //## Both \#Repaint() and \#Update() are declared protected now. //## //## Note: Separation of the Repaint and Update processes (rendering vs //## creating a vtk prop tree) still needs to be worked on. The whole //## rendering process also should be reworked to use VTK based classes for //## both 2D and 3D rendering. //## @ingroup Renderer class MITKCORE_EXPORT BaseRenderer : public itk::Object { public: typedef std::map BaseRendererMapType; static BaseRendererMapType baseRendererMap; static BaseRenderer *GetInstance(vtkRenderWindow *renWin); static void AddInstance(vtkRenderWindow *renWin, BaseRenderer *baseRenderer); static void RemoveInstance(vtkRenderWindow *renWin); static BaseRenderer *GetByName(const std::string &name); static vtkRenderWindow *GetRenderWindowByName(const std::string &name); #pragma GCC visibility push(default) itkEventMacro(RendererResetEvent, itk::AnyEvent); #pragma GCC visibility pop /** Standard class typedefs. */ mitkClassMacroItkParent(BaseRenderer, itk::Object); BaseRenderer(const char *name = nullptr, vtkRenderWindow *renWin = nullptr); //##Documentation //## @brief MapperSlotId defines which kind of mapper (e.g. 2D or 3D) should be used. typedef int MapperSlotId; enum StandardMapperSlot { Standard2D = 1, Standard3D = 2 }; //##Documentation //## @brief Possible view directions for render windows. enum class ViewDirection { AXIAL = 0, SAGITTAL, CORONAL, THREE_D }; virtual void SetDataStorage(DataStorage *storage); ///< set the datastorage that will be used for rendering //##Documentation //## return the DataStorage that is used for rendering virtual DataStorage::Pointer GetDataStorage() const { return m_DataStorage.GetPointer(); } //##Documentation //## @brief Access the RenderWindow into which this renderer renders. vtkRenderWindow *GetRenderWindow() const { return m_RenderWindow; } vtkRenderer *GetVtkRenderer() const { return m_VtkRenderer; } //##Documentation //## @brief Returns the Dispatcher which handles Events for this BaseRenderer Dispatcher::Pointer GetDispatcher() const; //##Documentation //## @brief Default mapper id to use. static const MapperSlotId defaultMapper; //##Documentation //## @brief Do the rendering and flush the result. virtual void Paint(); //##Documentation //## @brief Initialize the RenderWindow. Should only be called from RenderWindow. virtual void Initialize(); //##Documentation //## @brief Called to inform the renderer that the RenderWindow has been resized. virtual void Resize(int w, int h); //##Documentation //## @brief Initialize the renderer with a RenderWindow (@a renderwindow). virtual void InitRenderer(vtkRenderWindow *renderwindow); //##Documentation //## @brief Set the initial size. Called by RenderWindow after it has become //## visible for the first time. virtual void InitSize(int w, int h); //##Documentation //## @brief Draws a point on the widget. //## Should be used during conferences to show the position of the remote mouse virtual void DrawOverlayMouse(Point2D &p2d); //##Documentation //## @brief Set/Get the WorldGeometry (m_WorldGeometry) for 3D and 2D rendering, that describing the //## (maximal) area to be rendered. //## //## Depending of the type of the passed BaseGeometry more or less information can be extracted: //## \li if it is a PlaneGeometry (which is a sub-class of BaseGeometry), m_CurrentWorldPlaneGeometry is //## also set to point to it. m_WorldTimeGeometry is set to nullptr. //## \li if it is a TimeGeometry, m_WorldTimeGeometry is also set to point to it. //## If m_WorldTimeGeometry contains instances of SlicedGeometry3D, m_CurrentWorldPlaneGeometry is set to //## one of geometries stored in the SlicedGeometry3D according to the value of m_Slice; otherwise //## a PlaneGeometry describing the top of the bounding-box of the BaseGeometry is set as the //## m_CurrentWorldPlaneGeometry. //## \li otherwise a PlaneGeometry describing the top of the bounding-box of the BaseGeometry //## is set as the m_CurrentWorldPlaneGeometry. m_WorldTimeGeometry is set to nullptr. //## @todo add calculation of PlaneGeometry describing the top of the bounding-box of the BaseGeometry //## when the passed BaseGeometry is not sliced. //## \sa m_WorldGeometry //## \sa m_WorldTimeGeometry //## \sa m_CurrentWorldPlaneGeometry virtual void SetWorldGeometry3D(const BaseGeometry *geometry); virtual void SetWorldTimeGeometry(const mitk::TimeGeometry *geometry); /** * \deprecatedSince{2013_09} Please use TimeGeometry instead of TimeSlicedGeometry. For more information see * http://www.mitk.org/Development/Refactoring%20of%20the%20Geometry%20Classes%20-%20Part%201 */ DEPRECATED(void SetWorldGeometry3D(TimeSlicedGeometry *geometry)); itkGetConstObjectMacro(WorldTimeGeometry, TimeGeometry); //##Documentation //## @brief Get the current 3D-worldgeometry (m_CurrentWorldGeometry) used for 3D-rendering itkGetConstObjectMacro(CurrentWorldGeometry, BaseGeometry); //##Documentation //## @brief Get the current 2D-worldgeometry (m_CurrentWorldPlaneGeometry) used for 2D-rendering itkGetConstObjectMacro(CurrentWorldPlaneGeometry, PlaneGeometry) /** * \deprecatedSince{2014_10} Please use GetCurrentWorldPlaneGeometry */ DEPRECATED(const PlaneGeometry *GetCurrentWorldGeometry2D()) { return GetCurrentWorldPlaneGeometry(); }; //##Documentation //## Calculates the bounds of the DataStorage (if it contains any valid data), //## creates a geometry from these bounds and sets it as world geometry of the renderer. //## //## Call this method to re-initialize the renderer to the current DataStorage //## (e.g. after loading an additional dataset), to ensure that the view is //## aligned correctly. //## \warning This is not implemented yet. virtual bool SetWorldGeometryToDataStorageBounds() { return false; } //##Documentation //## @brief Set/Get m_Slice which defines together with m_TimeStep the 2D geometry //## stored in m_WorldTimeGeometry used as m_CurrentWorldPlaneGeometry //## //## \sa m_Slice virtual void SetSlice(unsigned int slice); itkGetConstMacro(Slice, unsigned int); //##Documentation //## @brief Set/Get m_TimeStep which defines together with m_Slice the 2D geometry //## stored in m_WorldTimeGeometry used as m_CurrentWorldPlaneGeometry //## //## \sa m_TimeStep virtual void SetTimeStep(unsigned int timeStep); itkGetConstMacro(TimeStep, unsigned int); //##Documentation //## @brief Get the time-step of a BaseData object which //## exists at the time of the currently displayed content //## //## Returns -1 or mitk::BaseData::m_TimeSteps if there //## is no data at the current time. //## \sa GetTimeStep, m_TimeStep - int GetTimeStep(const BaseData *data) const; + TimeStepType GetTimeStep(const BaseData *data) const; //##Documentation //## @brief Get the time in ms of the currently displayed content //## //## \sa GetTimeStep, m_TimeStep ScalarType GetTime() const; //##Documentation //## @brief SetWorldGeometry is called according to the geometrySliceEvent, //## which is supposed to be a SliceNavigationController::GeometrySendEvent virtual void SetGeometry(const itk::EventObject &geometrySliceEvent); //##Documentation //## @brief UpdateWorldGeometry is called to re-read the 2D geometry from the //## slice navigation controller virtual void UpdateGeometry(const itk::EventObject &geometrySliceEvent); //##Documentation //## @brief SetSlice is called according to the geometrySliceEvent, //## which is supposed to be a SliceNavigationController::GeometrySliceEvent virtual void SetGeometrySlice(const itk::EventObject &geometrySliceEvent); //##Documentation //## @brief SetTimeStep is called according to the geometrySliceEvent, //## which is supposed to be a SliceNavigationController::GeometryTimeEvent virtual void SetGeometryTime(const itk::EventObject &geometryTimeEvent); //##Documentation //## @brief Get a DataNode pointing to a data object containing the current 2D-worldgeometry // m_CurrentWorldPlaneGeometry (for 2D rendering) itkGetObjectMacro(CurrentWorldPlaneGeometryNode, DataNode) /** * \deprecatedSince{2014_10} Please use GetCurrentWorldPlaneGeometryNode */ DEPRECATED(DataNode *GetCurrentWorldGeometry2DNode()) { return GetCurrentWorldPlaneGeometryNode(); }; //##Documentation //## @brief Sets timestamp of CurrentWorldPlaneGeometry and forces so reslicing in that renderwindow void SendUpdateSlice(); //##Documentation //## @brief Get timestamp of last call of SetCurrentWorldPlaneGeometry unsigned long GetCurrentWorldPlaneGeometryUpdateTime() { return m_CurrentWorldPlaneGeometryUpdateTime; } /** * \deprecatedSince{2014_10} Please use GetCurrentWorldPlaneGeometryUpdateTime */ DEPRECATED(unsigned long GetCurrentWorldGeometry2DUpdateTime()) { return GetCurrentWorldPlaneGeometryUpdateTime(); }; //##Documentation //## @brief Get timestamp of last change of current TimeStep unsigned long GetTimeStepUpdateTime() { return m_TimeStepUpdateTime; } //##Documentation //## @brief Perform a picking: find the x,y,z world coordinate of a //## display x,y coordinate. //## @warning Has to be overwritten in subclasses for the 3D-case. //## //## Implemented here only for 2D-rendering virtual void PickWorldPoint(const Point2D &diplayPosition, Point3D &worldPosition) const = 0; /** \brief Determines the object (mitk::DataNode) closest to the current * position by means of picking * * \warning Implementation currently empty for 2D rendering; intended to be * implemented for 3D renderers */ virtual DataNode *PickObject(const Point2D & /*displayPosition*/, Point3D & /*worldPosition*/) const { return nullptr; } //##Documentation //## @brief Get the MapperSlotId to use. itkGetMacro(MapperID, MapperSlotId); itkGetConstMacro(MapperID, MapperSlotId); //##Documentation //## @brief Set the MapperSlotId to use. virtual void SetMapperID(MapperSlotId id); virtual int *GetSize() const; virtual int *GetViewportSize() const; void SetSliceNavigationController(SliceNavigationController *SlicenavigationController); itkGetObjectMacro(CameraController, CameraController); itkGetObjectMacro(SliceNavigationController, SliceNavigationController); itkGetObjectMacro(CameraRotationController, CameraRotationController); itkGetMacro(EmptyWorldGeometry, bool); //##Documentation //## @brief Tells if the displayed region is shifted and rescaled if the render window is resized. itkGetMacro(KeepDisplayedRegion, bool) //##Documentation //## @brief Tells if the displayed region should be shifted and rescaled if the render window is resized. itkSetMacro(KeepDisplayedRegion, bool); //##Documentation //## @brief get the name of the Renderer //## @note const char *GetName() const { return m_Name.c_str(); } //##Documentation //## @brief get the x_size of the RendererWindow //## @note int GetSizeX() const { return GetSize()[0]; } //##Documentation //## @brief get the y_size of the RendererWindow //## @note int GetSizeY() const { return GetSize()[1]; } const double *GetBounds() const; void RequestUpdate(); void ForceImmediateUpdate(); /** Returns number of mappers which are visible and have level-of-detail * rendering enabled */ unsigned int GetNumberOfVisibleLODEnabledMappers() const; //##Documentation //## @brief This method converts a display point to the 3D world index //## using the geometry of the renderWindow. void DisplayToWorld(const Point2D &displayPoint, Point3D &worldIndex) const; //##Documentation //## @brief This method converts a display point to the 2D world index, mapped onto the display plane //## using the geometry of the renderWindow. void DisplayToPlane(const Point2D &displayPoint, Point2D &planePointInMM) const; //##Documentation //## @brief This method converts a 3D world index to the display point //## using the geometry of the renderWindow. void WorldToDisplay(const Point3D &worldIndex, Point2D &displayPoint) const; //##Documentation //## @brief This method converts a 3D world index to the point on the viewport //## using the geometry of the renderWindow. void WorldToView(const Point3D &worldIndex, Point2D &viewPoint) const; //##Documentation //## @brief This method converts a 2D plane coordinate to the display point //## using the geometry of the renderWindow. void PlaneToDisplay(const Point2D &planePointInMM, Point2D &displayPoint) const; //##Documentation //## @brief This method converts a 2D plane coordinate to the point on the viewport //## using the geometry of the renderWindow. void PlaneToView(const Point2D &planePointInMM, Point2D &viewPoint) const; double GetScaleFactorMMPerDisplayUnit() const; Point2D GetDisplaySizeInMM() const; Point2D GetViewportSizeInMM() const; Point2D GetOriginInMM() const; itkGetConstMacro(ConstrainZoomingAndPanning, bool) virtual void SetConstrainZoomingAndPanning(bool constrain); /** * \brief Provides (1) world coordinates for a given mouse position and (2) * translates mousePosition to Display coordinates * \deprecated Map2DRendererPositionTo3DWorldPosition is deprecated. Please use DisplayToWorld instead. */ DEPRECATED(virtual Point3D Map2DRendererPositionTo3DWorldPosition(const Point2D &mousePosition) const); protected: ~BaseRenderer() override; //##Documentation //## @brief Call update of all mappers. To be implemented in subclasses. virtual void Update() = 0; vtkRenderWindow *m_RenderWindow; vtkRenderer *m_VtkRenderer; //##Documentation //## @brief MapperSlotId to use. Defines which kind of mapper (e.g., 2D or 3D) shoud be used. MapperSlotId m_MapperID; //##Documentation //## @brief The DataStorage that is used for rendering. DataStorage::Pointer m_DataStorage; //##Documentation //## @brief Timestamp of last call of Update(). unsigned long m_LastUpdateTime; //##Documentation //## @brief CameraController for 3D rendering //## @note preliminary. itk::SmartPointer m_CameraController; SliceNavigationController::Pointer m_SliceNavigationController; CameraRotationController::Pointer m_CameraRotationController; //##Documentation //## @brief Sets m_CurrentWorldPlaneGeometry virtual void SetCurrentWorldPlaneGeometry(const PlaneGeometry *geometry2d); /** * \deprecatedSince{2014_10} Please use SetCurrentWorldPlaneGeometry */ DEPRECATED(void SetCurrentWorldGeometry2D(PlaneGeometry *geometry2d)) { SetCurrentWorldPlaneGeometry(geometry2d); }; //##Documentation //## @brief Sets m_CurrentWorldGeometry virtual void SetCurrentWorldGeometry(const BaseGeometry *geometry); private: //##Documentation //## m_WorldTimeGeometry is set by SetWorldGeometry if the passed BaseGeometry is a //## TimeGeometry (or a sub-class of it). If it contains instances of SlicedGeometry3D, //## m_Slice and m_TimeStep (set via SetSlice and SetTimeStep, respectively) define //## which 2D geometry stored in m_WorldTimeGeometry (if available) //## is used as m_CurrentWorldPlaneGeometry. //## \sa m_CurrentWorldPlaneGeometry TimeGeometry::ConstPointer m_WorldTimeGeometry; //##Documentation //## Pointer to the current 3D-worldgeometry. BaseGeometry::ConstPointer m_CurrentWorldGeometry; //##Documentation //## Pointer to the current 2D-worldgeometry. The 2D-worldgeometry //## describes the maximal area (2D manifold) to be rendered in case we //## are doing 2D-rendering. //## It is const, since we are not allowed to change it (it may be taken //## directly from the geometry of an image-slice and thus it would be //## very strange when suddenly the image-slice changes its geometry). PlaneGeometry::Pointer m_CurrentWorldPlaneGeometry; //##Documentation //## Defines together with m_Slice which 2D geometry stored in m_WorldTimeGeometry //## is used as m_CurrentWorldPlaneGeometry: m_WorldTimeGeometry->GetPlaneGeometry(m_Slice, m_TimeStep). //## \sa m_WorldTimeGeometry unsigned int m_Slice; //##Documentation //## Defines together with m_TimeStep which 2D geometry stored in m_WorldTimeGeometry //## is used as m_CurrentWorldPlaneGeometry: m_WorldTimeGeometry->GetPlaneGeometry(m_Slice, m_TimeStep). //## \sa m_WorldTimeGeometry unsigned int m_TimeStep; //##Documentation //## @brief timestamp of last call of SetWorldGeometry itk::TimeStamp m_CurrentWorldPlaneGeometryUpdateTime; //##Documentation //## @brief timestamp of last change of the current time step itk::TimeStamp m_TimeStepUpdateTime; //##Documentation //## @brief Helper class which establishes connection between Interactors and Dispatcher via a common DataStorage. BindDispatcherInteractor *m_BindDispatcherInteractor; //##Documentation //## @brief Tells if the displayed region should be shifted or rescaled if the render window is resized. bool m_KeepDisplayedRegion; protected: void PrintSelf(std::ostream &os, itk::Indent indent) const override; //##Documentation //## Data object containing the m_CurrentWorldPlaneGeometry defined above. PlaneGeometryData::Pointer m_CurrentWorldPlaneGeometryData; //##Documentation //## DataNode objects containing the m_CurrentWorldPlaneGeometryData defined above. DataNode::Pointer m_CurrentWorldPlaneGeometryNode; //##Documentation //## @brief test only unsigned long m_CurrentWorldPlaneGeometryTransformTime; std::string m_Name; double m_Bounds[6]; bool m_EmptyWorldGeometry; typedef std::set LODEnabledMappersType; /** Number of mappers which are visible and have level-of-detail * rendering enabled */ unsigned int m_NumberOfVisibleLODEnabledMappers; // Local Storage Handling for mappers protected: std::list m_RegisteredLocalStorageHandlers; bool m_ConstrainZoomingAndPanning; public: void RemoveAllLocalStorages(); void RegisterLocalStorageHandler(mitk::BaseLocalStorageHandler *lsh); void UnregisterLocalStorageHandler(mitk::BaseLocalStorageHandler *lsh); }; } // namespace mitk #endif /* BASERENDERER_H_HEADER_INCLUDED_C1CCA0F4 */ diff --git a/Modules/Core/include/mitkMessage.h b/Modules/Core/include/mitkMessage.h index f1c4b48617..47cf00cac8 100644 --- a/Modules/Core/include/mitkMessage.h +++ b/Modules/Core/include/mitkMessage.h @@ -1,697 +1,697 @@ /*============================================================================ 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 mitkMessageHIncluded #define mitkMessageHIncluded #include #include #include /** * Adds a Message<> variable and methods to add/remove message delegates to/from * this variable. */ #define mitkNewMessageMacro(msgHandleObject) \ private: \ ::mitk::Message<> m_##msgHandleObject##Message; \ \ public: \ inline void Add##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate<> &delegate) \ { \ m_##msgHandleObject##Message += delegate; \ } \ inline void Remove##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate<> &delegate) \ { \ m_##msgHandleObject##Message -= delegate; \ } #define mitkNewMessageWithReturnMacro(msgHandleObject, returnType) \ private: \ ::mitk::Message m_##msgHandleObject##Message; \ \ public: \ inline void Add##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate &delegate) \ { \ m_##msgHandleObject##Message += delegate; \ } \ inline void Remove##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate &delegate) \ { \ m_##msgHandleObject##Message -= delegate; \ } #define mitkNewMessage1Macro(msgHandleObject, type1) \ private: \ ::mitk::Message1 m_##msgHandleObject##Message; \ \ public: \ void Add##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate1 &delegate) \ { \ m_##msgHandleObject##Message += delegate; \ } \ void Remove##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate1 &delegate) \ { \ m_##msgHandleObject##Message -= delegate; \ } #define mitkNewMessage2Macro(msgHandleObject, type1, type2) \ private: \ ::mitk::Message2 m_##msgHandleObject##Message; \ \ public: \ void Add##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate2 &delegate) \ { \ m_##msgHandleObject##Message += delegate; \ } \ void Remove##msgHandleObject##Listener(const ::mitk::MessageAbstractDelegate2 &delegate) \ { \ m_##msgHandleObject##Message -= delegate; \ } namespace mitk { template class MessageAbstractDelegate { public: virtual ~MessageAbstractDelegate() {} virtual A Execute() const = 0; virtual bool operator==(const MessageAbstractDelegate *cmd) const = 0; virtual MessageAbstractDelegate *Clone() const = 0; }; template class MessageAbstractDelegate1 { public: virtual ~MessageAbstractDelegate1() {} virtual A Execute(T t) const = 0; virtual bool operator==(const MessageAbstractDelegate1 *cmd) const = 0; virtual MessageAbstractDelegate1 *Clone() const = 0; }; template class MessageAbstractDelegate2 { public: virtual ~MessageAbstractDelegate2() {} virtual A Execute(T t, U u) const = 0; virtual bool operator==(const MessageAbstractDelegate2 *cmd) const = 0; virtual MessageAbstractDelegate2 *Clone() const = 0; }; template class MessageAbstractDelegate3 { public: virtual ~MessageAbstractDelegate3() {} virtual A Execute(T t, U u, V v) const = 0; virtual bool operator==(const MessageAbstractDelegate3 *cmd) const = 0; virtual MessageAbstractDelegate3 *Clone() const = 0; }; template class MessageAbstractDelegate4 { public: virtual ~MessageAbstractDelegate4() {} virtual A Execute(T t, U u, V v, W w) const = 0; virtual bool operator==(const MessageAbstractDelegate4 *cmd) const = 0; virtual MessageAbstractDelegate4 *Clone() const = 0; }; /** * This class essentially wraps a function pointer with signature * A(R::*function)(). A is the return type of your callback function * and R the type of the class implementing the function. * * Use this class to add a callback function to * messages without parameters. */ template class MessageDelegate : public MessageAbstractDelegate { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate(R *object, A (R::*memberFunctionPointer)()) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } ~MessageDelegate() override {} // override function "Call" A Execute() const override { return (m_Object->*m_MemberFunctionPointer)(); // execute member function } bool operator==(const MessageAbstractDelegate *c) const override { const MessageDelegate *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate *Clone() const override { return new MessageDelegate(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(); // pointer to member function }; /** * This class essentially wraps a function pointer with signature * A(R::*function)(T). A is the return type of your callback function, * R the type of the class implementing the function and T the type * of the argument. * * Use this class to add a callback function to * messages with one parameter. * * If you need more parameters, use MessageDelegate2 etc. */ template class MessageDelegate1 : public MessageAbstractDelegate1 { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate1(R *object, A (R::*memberFunctionPointer)(T)) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } ~MessageDelegate1() override {} // override function "Call" A Execute(T t) const override { return (m_Object->*m_MemberFunctionPointer)(t); // execute member function } bool operator==(const MessageAbstractDelegate1 *c) const override { const MessageDelegate1 *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate1 *Clone() const override { return new MessageDelegate1(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(T); // pointer to member function }; template class MessageDelegate2 : public MessageAbstractDelegate2 { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate2(R *object, A (R::*memberFunctionPointer)(T, U)) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } ~MessageDelegate2() override {} // override function "Call" A Execute(T t, U u) const override { return (m_Object->*m_MemberFunctionPointer)(t, u); // execute member function } bool operator==(const MessageAbstractDelegate2 *c) const override { const MessageDelegate2 *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate2 *Clone() const override { return new MessageDelegate2(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(T, U); // pointer to member function }; template class MessageDelegate3 : public MessageAbstractDelegate3 { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate3(R *object, A (R::*memberFunctionPointer)(T, U, V)) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } ~MessageDelegate3() override {} // override function "Call" A Execute(T t, U u, V v) const override { return (m_Object->*m_MemberFunctionPointer)(t, u, v); // execute member function } bool operator==(const MessageAbstractDelegate3 *c) const override { const MessageDelegate3 *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate3 *Clone() const override { return new MessageDelegate3(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(T, U, V); // pointer to member function }; template class MessageDelegate4 : public MessageAbstractDelegate4 { public: // constructor - takes pointer to an object and pointer to a member and stores // them in two private variables MessageDelegate4(R *object, A (R::*memberFunctionPointer)(T, U, V, W)) : m_Object(object), m_MemberFunctionPointer(memberFunctionPointer) { } virtual ~MessageDelegate4() {} // override function "Call" virtual A Execute(T t, U u, V v, W w) const { return (m_Object->*m_MemberFunctionPointer)(t, u, v, w); // execute member function } bool operator==(const MessageAbstractDelegate4 *c) const { const MessageDelegate4 *cmd = dynamic_cast *>(c); if (!cmd) return false; if ((void *)this->m_Object != (void *)cmd->m_Object) return false; if (this->m_MemberFunctionPointer != cmd->m_MemberFunctionPointer) return false; return true; } MessageAbstractDelegate4 *Clone() const { return new MessageDelegate4(m_Object, m_MemberFunctionPointer); } private: R *m_Object; // pointer to object A (R::*m_MemberFunctionPointer)(T, U, V, W); // pointer to member function }; template class MessageBase { public: typedef std::vector ListenerList; virtual ~MessageBase() { for (auto iter = m_Listeners.begin(); iter != m_Listeners.end(); ++iter) { delete *iter; } } MessageBase() {} MessageBase(const MessageBase &o) { for (typename ListenerList::iterator iter = o.m_Listeners.begin(); iter != o.m_Listeners.end(); ++iter) { m_Listeners.push_back((*iter)->Clone()); } } MessageBase &operator=(const MessageBase &o) { MessageBase tmp(o); std::swap(tmp.m_Listeners, this->m_Listeners); return *this; } void AddListener(const AbstractDelegate &delegate) const { AbstractDelegate *msgCmd = delegate.Clone(); m_Mutex.Lock(); for (auto iter = m_Listeners.begin(); iter != m_Listeners.end(); ++iter) { if ((*iter)->operator==(msgCmd)) { delete msgCmd; m_Mutex.Unlock(); return; } } m_Listeners.push_back(msgCmd); m_Mutex.Unlock(); } void operator+=(const AbstractDelegate &delegate) const { this->AddListener(delegate); } void RemoveListener(const AbstractDelegate &delegate) const { m_Mutex.Lock(); for (auto iter = m_Listeners.begin(); iter != m_Listeners.end(); ++iter) { if ((*iter)->operator==(&delegate)) { delete *iter; m_Listeners.erase(iter); m_Mutex.Unlock(); return; } } m_Mutex.Unlock(); } void operator-=(const AbstractDelegate &delegate) const { this->RemoveListener(delegate); } const ListenerList &GetListeners() const { return m_Listeners; } bool HasListeners() const { return !m_Listeners.empty(); } bool IsEmpty() const { return m_Listeners.empty(); } protected: /** * \brief List of listeners. * * This is declared mutable for a reason: Imagine an object that sends out notifications and * someone gets a const Database object, because he/she should not write to the * database. He/she should anyway be able to register for notifications about changes in the database * -- this is why AddListener and RemoveListener are declared const. m_Listeners must be * mutable so that AddListener and RemoveListener can modify it regardless of the object's constness. */ mutable ListenerList m_Listeners; mutable itk::SimpleFastMutexLock m_Mutex; }; /** * \brief Event/message/notification class. * * \sa mitk::BinaryThresholdTool * \sa QmitkBinaryThresholdToolGUI * * This totally ITK, Qt, VTK, whatever toolkit independent class * allows one class to send out messages and another class to * receive these message. This class is templated over the * return type (A) of the callback functions. * There are variations of this class * (Message1, Message2, etc.) for sending * one, two or more parameters along with the messages. * * This is an implementation of the Observer pattern. * * \li There is no guarantee about the order of which observer is notified first. At the moment the observers which * register first will be notified first. * \li Notifications are synchronous, by direct method calls. There is no support for asynchronous messages. * * To conveniently add methods for registering/unregistering observers * to Message variables of your class, you can use the mitkNewMessageMacro * macros. */ template class Message : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; - void Send() + void Send() const { ListenerList listeners; { this->m_Mutex.Lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.Unlock(); } for (auto iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(); } } - void operator()() { this->Send(); } + void operator()() const { this->Send(); } }; // message with 1 parameter and return type template class Message1 : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; - void Send(T t) + void Send(T t) const { ListenerList listeners; { this->m_Mutex.Lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.Unlock(); } for (auto iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(t); } } - void operator()(T t) { this->Send(t); } + void operator() (T t) const { this->Send(t); } }; // message with 2 parameters and return type template class Message2 : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; - void Send(T t, U u) + void Send(T t, U u) const { ListenerList listeners; { this->m_Mutex.Lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.Unlock(); } for (auto iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(t, u); } } - void operator()(T t, U u) { this->Send(t, u); } + void operator()(T t, U u) const { this->Send(t, u); } }; // message with 3 parameters and return type template class Message3 : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; - void Send(T t, U u, V v) + void Send(T t, U u, V v) const { ListenerList listeners; { this->m_Mutex.Lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.Unlock(); } for (typename ListenerList::iterator iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(t, u, v); } } - void operator()(T t, U u, V v) { this->Send(t, u, v); } + void operator()(T t, U u, V v) const { this->Send(t, u, v); } }; // message with 4 parameters and return type template class Message4 : public MessageBase> { public: typedef MessageBase> Super; typedef typename Super::ListenerList ListenerList; - void Send(T t, U u, V v, W w) + void Send(T t, U u, V v, W w) const { ListenerList listeners; { this->m_Mutex.Lock(); listeners.assign(this->m_Listeners.begin(), this->m_Listeners.end()); this->m_Mutex.Unlock(); } for (typename ListenerList::iterator iter = listeners.begin(); iter != listeners.end(); ++iter) { // notify each listener (*iter)->Execute(t, u, v, w); } } - void operator()(T t, U u, V v, W w) { this->Send(t, u, v, w); } + void operator()(T t, U u, V v, W w) const { this->Send(t, u, v, w); } }; /* Here is an example how to use the macros and templates: * * // An object to be send around * class Law * { * private: * std::string m_Description; * * public: * * Law(const std::string law) : m_Description(law) * { } * * std::string GetDescription() const * { * return m_Description; * } * }; * * // The NewtonMachine will issue specific events * class NewtonMachine * { * mitkNewMessageMacro(AnalysisStarted); * mitkNewMessage1Macro(AnalysisStopped, bool); * mitkNewMessage1Macro(LawDiscovered, const Law&); * * public: * * void StartAnalysis() * { * // send the "started" signal * m_AnalysisStartedMessage(); * * // we found a new law of nature by creating one :-) * Law massLaw("F=ma"); * m_LawDiscoveredMessage(massLaw); * } * * void StopAnalysis() * { * // send the "stop" message with false, indicating * // that no error occured * m_AnalysisStoppedMessage(false); * } * }; * * class Observer * { * private: * * NewtonMachine* m_Machine; * * public: * * Observer(NewtonMachine* machine) : m_Machine(machine) * { * // Add "observers", i.e. function pointers to the machine * m_Machine->AddAnalysisStartedListener( * ::mitk::MessageDelegate(this, &Observer::MachineStarted)); * m_Machine->AddAnalysisStoppedListener( * ::mitk::MessageDelegate1(this, &Observer::MachineStopped)); * m_Machine->AddLawDiscoveredListener( * ::mitk::MessageDelegate1(this, &Observer::LawDiscovered)); * } * * ~Observer() * { * // Always remove your observers when finished * m_Machine->RemoveAnalysisStartedListener( * ::mitk::MessagDelegate(this, &Observer::MachineStarted)); * m_Machine->RemoveAnalysisStoppedListener( * ::mitk::MessageDelegate1(this, &Observer::MachineStopped)); * m_Machine->RemoveLawDiscoveredListener( * ::mitk::MessageDelegate1(this, &Observer::LawDiscovered)); * } * * void MachineStarted() * { * std::cout << "Observed machine has started" << std::endl; * } * * void MachineStopped(bool error) * { * std::cout << "Observed machine stopped " << (error ? "with an error" : "") << std::endl; * } * * void LawDiscovered(const Law& law) * { * std::cout << "New law of nature discovered: " << law.GetDescription() << std::endl; * } * }; * * NewtonMachine newtonMachine; * Observer observer(&newtonMachine); * * // This will send two events to registered observers * newtonMachine.StartAnalysis(); * // This will send one event to registered observers * newtonMachine.StopAnalysis(); * * Another example of how to use these message classes can be * found in the directory Testing, file mitkMessageTest.cpp * */ } // namespace #endif diff --git a/Modules/Core/include/mitkPlaneGeometry.h b/Modules/Core/include/mitkPlaneGeometry.h index f06e23e30d..783a6a4d5e 100644 --- a/Modules/Core/include/mitkPlaneGeometry.h +++ b/Modules/Core/include/mitkPlaneGeometry.h @@ -1,614 +1,614 @@ /*============================================================================ 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. ============================================================================*/ /** * \brief Describes the geometry of a plane object * * Describes a two-dimensional manifold, i.e., to put it simply, * an object that can be described using a 2D coordinate-system. * * PlaneGeometry can map points between 3D world coordinates * (in mm) and the described 2D coordinate-system (in mm) by first projecting * the 3D point onto the 2D manifold and then calculating the 2D-coordinates * (in mm). These 2D-mm-coordinates can be further converted into * 2D-unit-coordinates (e.g., pixels), giving a parameter representation of * the object with parameter values inside a rectangle * (e.g., [0,0]..[width, height]), which is the bounding box (bounding range * in z-direction always [0]..[1]). * * A PlaneGeometry describes the 2D representation within a 3D object (derived from BaseGeometry). For example, * a single CT-image (slice) is 2D in the sense that you can access the * pixels using 2D-coordinates, but is also 3D, as the pixels are really * voxels, thus have an extension (thickness) in the 3rd dimension. * * * Optionally, a reference BaseGeometry can be specified, which usually would * be the geometry associated with the underlying dataset. This is currently * used for calculating the intersection of inclined / rotated planes * (represented as PlaneGeometry) with the bounding box of the associated * BaseGeometry. * * \warning The PlaneGeometry are not necessarily up-to-date and not even * initialized. As described in the previous paragraph, one of the * Generate-/Copy-/UpdateOutputInformation methods have to initialize it. * mitk::BaseData::GetPlaneGeometry() makes sure, that the PlaneGeometry is * up-to-date before returning it (by setting the update extent appropriately * and calling UpdateOutputInformation). * * Rule: everything is in mm (or ms for temporal information) if not * stated otherwise. * \ingroup Geometry */ #ifndef PLANEGEOMETRY_H_HEADER_INCLUDED_C1C68A2C #define PLANEGEOMETRY_H_HEADER_INCLUDED_C1C68A2C #include "mitkBaseGeometry.h" #include "mitkRestorePlanePositionOperation.h" #include #include namespace mitk { template class Line; typedef Line Line3D; class PlaneGeometry; /** \deprecatedSince{2014_10} This class is deprecated. Please use PlaneGeometry instead. */ DEPRECATED(typedef PlaneGeometry Geometry2D); /** * \brief Describes a two-dimensional, rectangular plane * * \ingroup Geometry */ class MITKCORE_EXPORT PlaneGeometry : public BaseGeometry { public: mitkClassMacro(PlaneGeometry, BaseGeometry); /** Method for creation through the object factory. */ itkFactorylessNewMacro(Self); itkCloneMacro(Self); enum PlaneOrientation { Axial, Sagittal, Frontal, // also known as "Coronal" in mitk. None // This defines the PlaneGeometry for the 3D renderWindow which // curiously also needs a PlaneGeometry. This should be reconsidered some time. }; virtual void IndexToWorld(const Point2D &pt_units, Point2D &pt_mm) const; virtual void WorldToIndex(const Point2D &pt_mm, Point2D &pt_units) const; //##Documentation //## @brief Convert (continuous or discrete) index coordinates of a \em vector //## \a vec_units to world coordinates (in mm) //## @deprecated First parameter (Point2D) is not used. If possible, please use void IndexToWorld(const // mitk::Vector2D& vec_units, mitk::Vector2D& vec_mm) const. //## For further information about coordinates types, please see the Geometry documentation virtual void IndexToWorld(const mitk::Point2D &atPt2d_untis, const mitk::Vector2D &vec_units, mitk::Vector2D &vec_mm) const; //##Documentation //## @brief Convert (continuous or discrete) index coordinates of a \em vector //## \a vec_units to world coordinates (in mm) //## For further information about coordinates types, please see the Geometry documentation virtual void IndexToWorld(const mitk::Vector2D &vec_units, mitk::Vector2D &vec_mm) const; //##Documentation //## @brief Convert world coordinates (in mm) of a \em vector //## \a vec_mm to (continuous!) index coordinates. //## @deprecated First parameter (Point2D) is not used. If possible, please use void WorldToIndex(const // mitk::Vector2D& vec_mm, mitk::Vector2D& vec_units) const. //## For further information about coordinates types, please see the Geometry documentation virtual void WorldToIndex(const mitk::Point2D &atPt2d_mm, const mitk::Vector2D &vec_mm, mitk::Vector2D &vec_units) const; //##Documentation //## @brief Convert world coordinates (in mm) of a \em vector //## \a vec_mm to (continuous!) index coordinates. //## For further information about coordinates types, please see the Geometry documentation virtual void WorldToIndex(const mitk::Vector2D &vec_mm, mitk::Vector2D &vec_units) const; /** * \brief Initialize a plane with orientation \a planeorientation * (default: axial) with respect to \a BaseGeometry (default: identity). * Spacing also taken from \a BaseGeometry. * * \warning A former version of this method created a geometry with unit * spacing. For unit spacing use * * \code * // for in-plane unit spacing: * thisgeometry->SetSizeInUnits(thisgeometry->GetExtentInMM(0), * thisgeometry->GetExtentInMM(1)); * // additionally, for unit spacing in normal direction (former version * // did not do this): * thisgeometry->SetExtentInMM(2, 1.0); * \endcode */ virtual void InitializeStandardPlane(const BaseGeometry *geometry3D, PlaneOrientation planeorientation = Axial, ScalarType zPosition = 0, bool frontside = true, bool rotated = false, bool top = true); /** * \brief Initialize a plane with orientation \a planeorientation * (default: axial) with respect to \a BaseGeometry (default: identity). * Spacing also taken from \a BaseGeometry. * * \param geometry3D * \param top if \a true, create plane at top, otherwise at bottom * (for PlaneOrientation Axial, for other plane locations respectively) * \param planeorientation * \param frontside * \param rotated */ virtual void InitializeStandardPlane(const BaseGeometry *geometry3D, bool top, PlaneOrientation planeorientation = Axial, bool frontside = true, bool rotated = false); /** * \brief Initialize a plane with orientation \a planeorientation * (default: axial) with respect to \a transform (default: identity) * given width and height in units. * * \a Rotated means rotated by 180 degrees (1/2 rotation) within the plane. * Rotation by 90 degrees (1/4 rotation) is not implemented as of now. * * \a Frontside/Backside: * Viewed from below = frontside in the axial case; * (radiologist's view versus neuro-surgeon's view, see: * http://www.itk.org/Wiki/images/e/ed/DICOM-OrientationDiagram-Radiologist-vs-NeuroSurgeon.png ) * Viewed from front = frontside in the coronal case; * Viewed from left = frontside in the sagittal case. * * \a Cave/Caution: Currently only RPI, LAI, LPS and RAS in the three standard planes are covered, * i.e. 12 cases of 144: 3 standard planes * 48 coordinate orientations = 144 cases. */ virtual void InitializeStandardPlane(ScalarType width, ScalarType height, const AffineTransform3D *transform = nullptr, PlaneOrientation planeorientation = Axial, ScalarType zPosition = 0, bool frontside = true, bool rotated = false, bool top = true); /** * \brief Initialize plane with orientation \a planeorientation * (default: axial) given width, height and spacing. * */ virtual void InitializeStandardPlane(ScalarType width, ScalarType height, const Vector3D &spacing, PlaneOrientation planeorientation = Axial, ScalarType zPosition = 0, bool frontside = true, bool rotated = false, bool top = true); /** * \brief Initialize plane by width and height in pixels, right-/down-vector * (itk) to describe orientation in world-space (vectors will be normalized) * and spacing (default: 1.0 mm in all directions). * * The vectors are normalized and multiplied by the respective spacing before * they are set in the matrix. * * This overloaded version of InitializeStandardPlane() creates only righthanded * coordinate orientations, unless spacing contains 1 or 3 negative entries. * */ virtual void InitializeStandardPlane(ScalarType width, ScalarType height, const Vector3D &rightVector, const Vector3D &downVector, const Vector3D *spacing = nullptr); /** * \brief Initialize plane by width and height in pixels, * right-/down-vector (vnl) to describe orientation in world-space (vectors * will be normalized) and spacing (default: 1.0 mm in all directions). * * The vectors are normalized and multiplied by the respective spacing * before they are set in the matrix. * * This overloaded version of InitializeStandardPlane() creates only righthanded * coordinate orientations, unless spacing contains 1 or 3 negative entries. * */ virtual void InitializeStandardPlane(ScalarType width, ScalarType height, const VnlVector &rightVector, const VnlVector &downVector, const Vector3D *spacing = nullptr); /** * \brief Initialize plane by right-/down-vector (itk) and spacing * (default: 1.0 mm in all directions). * * The length of the right-/-down-vector is used as width/height in units, * respectively. Then, the vectors are normalized and multiplied by the * respective spacing before they are set in the matrix. */ virtual void InitializeStandardPlane(const Vector3D &rightVector, const Vector3D &downVector, const Vector3D *spacing = nullptr); /** * \brief Initialize plane by right-/down-vector (vnl) and spacing * (default: 1.0 mm in all directions). * * The length of the right-/-down-vector is used as width/height in units, * respectively. Then, the vectors are normalized and multiplied by the * respective spacing before they are set in the matrix. */ virtual void InitializeStandardPlane(const VnlVector &rightVector, const VnlVector &downVector, const Vector3D *spacing = nullptr); /** * \brief Initialize plane by origin and normal (size is 1.0 mm in * all directions, direction of right-/down-vector valid but * undefined). * \warning This function can only produce righthanded coordinate orientation, not lefthanded. */ virtual void InitializePlane(const Point3D &origin, const Vector3D &normal); /** * \brief Initialize plane by right-/down-vector. * * \warning The vectors are set into the matrix as they are, * \em without normalization! * This function creates a righthanded IndexToWorldTransform, * only a negative thickness could still make it lefthanded. */ void SetMatrixByVectors(const VnlVector &rightVector, const VnlVector &downVector, ScalarType thickness = 1.0); /** * \brief Check if matrix is a rotation matrix: * - determinant is 1? * - R*R^T is ID? * Output warning otherwise. */ static bool CheckRotationMatrix(AffineTransform3D *transform, double epsilon=mitk::eps); /** * \brief Normal of the plane * */ Vector3D GetNormal() const; /** * \brief Normal of the plane as VnlVector * */ VnlVector GetNormalVnl() const; virtual ScalarType SignedDistance(const Point3D &pt3d_mm) const; /** * \brief Calculates, whether a point is below or above the plane. There are two different *calculation methods, with or without consideration of the bounding box. */ virtual bool IsAbove(const Point3D &pt3d_mm, bool considerBoundingBox = false) const; /** * \brief Distance of the point from the plane * (bounding-box \em not considered) * */ ScalarType DistanceFromPlane(const Point3D &pt3d_mm) const; /** * \brief Signed distance of the point from the plane * (bounding-box \em not considered) * * > 0 : point is in the direction of the direction vector. */ inline ScalarType SignedDistanceFromPlane(const Point3D &pt3d_mm) const { ScalarType len = GetNormalVnl().two_norm(); if (len == 0) return 0; return (pt3d_mm - GetOrigin()) * GetNormal() / len; } /** * \brief Distance of the plane from another plane * (bounding-box \em not considered) * * Result is 0 if planes are not parallel. */ ScalarType DistanceFromPlane(const PlaneGeometry *plane) const { return fabs(SignedDistanceFromPlane(plane)); } /** * \brief Signed distance of the plane from another plane * (bounding-box \em not considered) * * Result is 0 if planes are not parallel. */ inline ScalarType SignedDistanceFromPlane(const PlaneGeometry *plane) const { if (IsParallel(plane)) { return SignedDistance(plane->GetOrigin()); } return 0; } /** * \brief Calculate the intersecting line of two planes * * \return \a true planes are intersecting * \return \a false planes do not intersect */ bool IntersectionLine(const PlaneGeometry *plane, Line3D &crossline) const; /** * \brief Calculate two points where another plane intersects the border of this plane * * \return number of intersection points (0..2). First interection point (if existing) * is returned in \a lineFrom, second in \a lineTo. */ unsigned int IntersectWithPlane2D(const PlaneGeometry *plane, Point2D &lineFrom, Point2D &lineTo) const; /** * \brief Calculate the angle between two planes * * \return angle in radiants */ double Angle(const PlaneGeometry *plane) const; /** * \brief Calculate the angle between the plane and a line * * \return angle in radiants */ double Angle(const Line3D &line) const; /** * \brief Calculate intersection point between the plane and a line * * \param line * \param intersectionPoint intersection point * \return \a true if \em unique intersection exists, i.e., if line * is \em not on or parallel to the plane */ bool IntersectionPoint(const Line3D &line, Point3D &intersectionPoint) const; /** * \brief Calculate line parameter of intersection point between the * plane and a line * * \param line * \param t parameter of line: intersection point is * line.GetPoint()+t*line.GetDirection() * \return \a true if \em unique intersection exists, i.e., if line * is \em not on or parallel to the plane */ bool IntersectionPointParam(const Line3D &line, double &t) const; /** * \brief Returns whether the plane is parallel to another plane * * @return true iff the normal vectors both point to the same or exactly oposit direction */ bool IsParallel(const PlaneGeometry *plane) const; /** * \brief Returns whether the point is on the plane * (bounding-box \em not considered) */ bool IsOnPlane(const Point3D &point) const; /** * \brief Returns whether the line is on the plane * (bounding-box \em not considered) */ bool IsOnPlane(const Line3D &line) const; /** * \brief Returns whether the plane is on the plane * (bounding-box \em not considered) * - * @return true iff the normal vector of the planes point to the same or the exactly oposit direction and + * @return true if the normal vector of the planes point to the same or the exactly oposit direction and * the distance of the planes is < eps * */ bool IsOnPlane(const PlaneGeometry *plane) const; /** * \brief Returns the lot from the point to the plane */ Point3D ProjectPointOntoPlane(const Point3D &pt) const; itk::LightObject::Pointer InternalClone() const override; /** Implements operation to re-orient the plane */ void ExecuteOperation(Operation *operation) override; /** * \brief Project a 3D point given in mm (\a pt3d_mm) onto the 2D * geometry. The result is a 2D point in mm (\a pt2d_mm). * * The result is a 2D point in mm (\a pt2d_mm) relative to the upper-left * corner of the geometry. To convert this point into units (e.g., pixels * in case of an image), use WorldToIndex. * \return true projection was possible * \sa Project(const mitk::Point3D &pt3d_mm, mitk::Point3D * &projectedPt3d_mm) */ virtual bool Map(const mitk::Point3D &pt3d_mm, mitk::Point2D &pt2d_mm) const; /** * \brief Converts a 2D point given in mm (\a pt2d_mm) relative to the * upper-left corner of the geometry into the corresponding * world-coordinate (a 3D point in mm, \a pt3d_mm). * * To convert a 2D point given in units (e.g., pixels in case of an * image) into a 2D point given in mm (as required by this method), use * IndexToWorld. */ virtual void Map(const mitk::Point2D &pt2d_mm, mitk::Point3D &pt3d_mm) const; /** * \brief Set the width and height of this 2D-geometry in units by calling * SetBounds. This does \a not change the extent in mm! * * For an image, this is the number of pixels in x-/y-direction. * \note In contrast to calling SetBounds directly, this does \a not change * the extent in mm! */ virtual void SetSizeInUnits(mitk::ScalarType width, mitk::ScalarType height); /** * \brief Project a 3D point given in mm (\a pt3d_mm) onto the 2D * geometry. The result is a 3D point in mm (\a projectedPt3d_mm). * * \return true projection was possible */ virtual bool Project(const mitk::Point3D &pt3d_mm, mitk::Point3D &projectedPt3d_mm) const; /** * \brief Project a 3D vector given in mm (\a vec3d_mm) onto the 2D * geometry. The result is a 2D vector in mm (\a vec2d_mm). * * The result is a 2D vector in mm (\a vec2d_mm) relative to the * upper-left * corner of the geometry. To convert this point into units (e.g., pixels * in case of an image), use WorldToIndex. * \return true projection was possible * \sa Project(const mitk::Vector3D &vec3d_mm, mitk::Vector3D * &projectedVec3d_mm) */ virtual bool Map(const mitk::Point3D &atPt3d_mm, const mitk::Vector3D &vec3d_mm, mitk::Vector2D &vec2d_mm) const; /** * \brief Converts a 2D vector given in mm (\a vec2d_mm) relative to the * upper-left corner of the geometry into the corresponding * world-coordinate (a 3D vector in mm, \a vec3d_mm). * * To convert a 2D vector given in units (e.g., pixels in case of an * image) into a 2D vector given in mm (as required by this method), use * IndexToWorld. */ virtual void Map(const mitk::Point2D &atPt2d_mm, const mitk::Vector2D &vec2d_mm, mitk::Vector3D &vec3d_mm) const; /** * \brief Project a 3D vector given in mm (\a vec3d_mm) onto the 2D * geometry. The result is a 3D vector in mm (\a projectedVec3d_mm). * * DEPRECATED. Use Project(vector,vector) instead * * \return true projection was possible */ virtual bool Project(const mitk::Point3D &atPt3d_mm, const mitk::Vector3D &vec3d_mm, mitk::Vector3D &projectedVec3d_mm) const; /** * \brief Project a 3D vector given in mm (\a vec3d_mm) onto the 2D * geometry. The result is a 3D vector in mm (\a projectedVec3d_mm). * * \return true projection was possible */ virtual bool Project(const mitk::Vector3D &vec3d_mm, mitk::Vector3D &projectedVec3d_mm) const; /** * \brief Distance of the point from the geometry * (bounding-box \em not considered) * */ inline ScalarType Distance(const Point3D &pt3d_mm) const { return fabs(SignedDistance(pt3d_mm)); } /** * \brief Set the geometrical frame of reference in which this PlaneGeometry * is placed. * * This would usually be the BaseGeometry of the underlying dataset, but * setting it is optional. */ void SetReferenceGeometry(const mitk::BaseGeometry *geometry); /** * \brief Get the geometrical frame of reference for this PlaneGeometry. */ const BaseGeometry *GetReferenceGeometry() const; bool HasReferenceGeometry() const; static std::vector< int > CalculateDominantAxes(mitk::AffineTransform3D::MatrixType::InternalMatrixType& rotation_matrix); protected: PlaneGeometry(); PlaneGeometry(const PlaneGeometry &other); ~PlaneGeometry() override; void PrintSelf(std::ostream &os, itk::Indent indent) const override; const mitk::BaseGeometry *m_ReferenceGeometry; //##Documentation //## @brief PreSetSpacing //## //## These virtual function allows a different beahiour in subclasses. //## Do implement them in every subclass of BaseGeometry. If not needed, use //## {Superclass::PreSetSpacing();}; void PreSetSpacing(const mitk::Vector3D &aSpacing) override { Superclass::PreSetSpacing(aSpacing); }; //##Documentation //## @brief CheckBounds //## //## This function is called in SetBounds. Assertions can be implemented in this function (see PlaneGeometry.cpp). //## If you implement this function in a subclass, make sure, that all classes were your class inherits from //## have an implementation of CheckBounds //## (e.g. inheritance BaseGeometry <- A <- B. Implementation of CheckBounds in class B needs implementation in A as // well!) void CheckBounds(const BoundsArrayType &bounds) override; //##Documentation //## @brief CheckIndexToWorldTransform //## //## This function is called in SetIndexToWorldTransform. Assertions can be implemented in this function (see // PlaneGeometry.cpp). //## In Subclasses of BaseGeometry, implement own conditions or call Superclass::CheckBounds(bounds);. void CheckIndexToWorldTransform(mitk::AffineTransform3D *transform) override; private: /** * \brief Compares plane with another plane: \a true if IsOnPlane * (bounding-box \em not considered) */ virtual bool operator==(const PlaneGeometry *) const { return false; }; /** * \brief Compares plane with another plane: \a false if IsOnPlane * (bounding-box \em not considered) */ virtual bool operator!=(const PlaneGeometry *) const { return false; }; }; } // namespace mitk #endif /* PLANEGEOMETRY_H_HEADER_INCLUDED_C1C68A2C */ diff --git a/Modules/Core/include/mitkPoint.h b/Modules/Core/include/mitkPoint.h index a0f0cc3b85..779deb73e8 100644 --- a/Modules/Core/include/mitkPoint.h +++ b/Modules/Core/include/mitkPoint.h @@ -1,135 +1,135 @@ /*============================================================================ 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 MITKPOINT_H_ #define MITKPOINT_H_ #include #include "mitkArray.h" #include "mitkEqual.h" #include "mitkNumericConstants.h" namespace mitk { //##Documentation //##@brief enumeration of the type a point can be enum PointSpecificationType { PTUNDEFINED = 0, PTSTART, PTCORNER, PTEDGE, PTEND }; template class Point : public itk::Point { public: /** Default constructor has nothing to do. */ explicit Point() : itk::Point() {} /** Pass-through constructors for the Array base class. */ template explicit Point(const Point &r) : itk::Point(r) { } template explicit Point(const TPointValueType r[NPointDimension]) : itk::Point(r) { } template - Point(const TPointValueType &v) : itk::Point(v) + explicit Point(const TPointValueType &v) : itk::Point(v) { } Point(const mitk::Point &r) : itk::Point(r) { } Point(const TCoordRep r[NPointDimension]) : itk::Point(r) {} Point(const TCoordRep &v) : itk::Point(v) {} Point(const itk::Point &p) : itk::Point(p) { } /** * Copies the elements from array array to this. * Note that this method will assign doubles to floats without complaining! * * @param array the array whose values shall be copied. Must overload [] operator. */ template void FillPoint(const ArrayType &array) { itk::FixedArray *thisP = dynamic_cast *>(this); mitk::FillArray(*thisP, array); } /** * Copies the values stored in this point into the array array. * * @param array the array which should store the values of this. */ template void ToArray(ArrayType array) const { mitk::ToArray(array, *this); } }; typedef Point Point2D; typedef Point Point3D; typedef Point Point4D; typedef Point Point2I; typedef Point Point3I; typedef Point Point4I; /** * @ingroup MITKTestingAPI * * @param point1 Point to compare. * @param point2 Point to compare. * @param eps Tolerance for floating point comparison. * @param verbose Flag indicating detailed console output. * @return True if points are equal. */ template inline bool Equal(const itk::Point &point1, const itk::Point &point2, TCoordRep eps = mitk::eps, bool verbose = false) { bool isEqual = true; typename itk::Point::VectorType diff = point1 - point2; for (unsigned int i = 0; i < NPointDimension; i++) { if (DifferenceBiggerOrEqualEps(diff[i], eps)) { isEqual = false; break; } } ConditionalOutputOfDifference(point1, point2, eps, verbose, isEqual); return isEqual; } } // namespace mitk #endif /* MITKPOINT_H_ */ diff --git a/Modules/Core/src/DataManagement/mitkBaseGeometry.cpp b/Modules/Core/src/DataManagement/mitkBaseGeometry.cpp index 038edbc41b..ba87e198ea 100644 --- a/Modules/Core/src/DataManagement/mitkBaseGeometry.cpp +++ b/Modules/Core/src/DataManagement/mitkBaseGeometry.cpp @@ -1,1096 +1,1096 @@ /*============================================================================ 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 "mitkApplyTransformMatrixOperation.h" #include "mitkBaseGeometry.h" #include "mitkGeometryTransformHolder.h" #include "mitkInteractionConst.h" #include "mitkMatrixConvert.h" #include "mitkModifiedLock.h" #include "mitkPointOperation.h" #include "mitkRestorePlanePositionOperation.h" #include "mitkRotationOperation.h" #include "mitkScaleOperation.h" #include "mitkVector.h" #include "mitkMatrix.h" mitk::BaseGeometry::BaseGeometry() : Superclass(), mitk::OperationActor(), m_FrameOfReferenceID(0), m_IndexToWorldTransformLastModified(0), m_ImageGeometry(false), m_ModifiedLockFlag(false), m_ModifiedCalledFlag(false) { m_GeometryTransform = new GeometryTransformHolder(); Initialize(); } mitk::BaseGeometry::BaseGeometry(const BaseGeometry &other) : Superclass(), mitk::OperationActor(), m_FrameOfReferenceID(other.m_FrameOfReferenceID), m_IndexToWorldTransformLastModified(other.m_IndexToWorldTransformLastModified), m_ImageGeometry(other.m_ImageGeometry), m_ModifiedLockFlag(false), m_ModifiedCalledFlag(false) { m_GeometryTransform = new GeometryTransformHolder(*other.GetGeometryTransformHolder()); other.InitializeGeometry(this); } mitk::BaseGeometry::~BaseGeometry() { delete m_GeometryTransform; } void mitk::BaseGeometry::SetVtkMatrixDeepCopy(vtkTransform *vtktransform) { m_GeometryTransform->SetVtkMatrixDeepCopy(vtktransform); } const mitk::Point3D mitk::BaseGeometry::GetOrigin() const { return m_GeometryTransform->GetOrigin(); } void mitk::BaseGeometry::SetOrigin(const Point3D &origin) { mitk::ModifiedLock lock(this); if (origin != GetOrigin()) { m_GeometryTransform->SetOrigin(origin); Modified(); } } const mitk::Vector3D mitk::BaseGeometry::GetSpacing() const { return m_GeometryTransform->GetSpacing(); } void mitk::BaseGeometry::Initialize() { float b[6] = {0, 1, 0, 1, 0, 1}; SetFloatBounds(b); m_GeometryTransform->Initialize(); m_FrameOfReferenceID = 0; m_ImageGeometry = false; } void mitk::BaseGeometry::SetFloatBounds(const float bounds[6]) { mitk::BoundingBox::BoundsArrayType b; const float *input = bounds; int i = 0; for (mitk::BoundingBox::BoundsArrayType::Iterator it = b.Begin(); i < 6; ++i) *it++ = (mitk::ScalarType)*input++; SetBounds(b); } void mitk::BaseGeometry::SetFloatBounds(const double bounds[6]) { mitk::BoundingBox::BoundsArrayType b; const double *input = bounds; int i = 0; for (mitk::BoundingBox::BoundsArrayType::Iterator it = b.Begin(); i < 6; ++i) *it++ = (mitk::ScalarType)*input++; SetBounds(b); } /** Initialize the geometry */ void mitk::BaseGeometry::InitializeGeometry(BaseGeometry *newGeometry) const { newGeometry->SetBounds(m_BoundingBox->GetBounds()); newGeometry->SetFrameOfReferenceID(GetFrameOfReferenceID()); newGeometry->InitializeGeometryTransformHolder(this); newGeometry->m_ImageGeometry = m_ImageGeometry; } void mitk::BaseGeometry::InitializeGeometryTransformHolder(const BaseGeometry *otherGeometry) { this->m_GeometryTransform->Initialize(otherGeometry->GetGeometryTransformHolder()); } /** Set the bounds */ void mitk::BaseGeometry::SetBounds(const BoundsArrayType &bounds) { mitk::ModifiedLock lock(this); this->CheckBounds(bounds); m_BoundingBox = BoundingBoxType::New(); BoundingBoxType::PointsContainer::Pointer pointscontainer = BoundingBoxType::PointsContainer::New(); BoundingBoxType::PointType p; BoundingBoxType::PointIdentifier pointid; for (pointid = 0; pointid < 2; ++pointid) { unsigned int i; for (i = 0; i < m_NDimensions; ++i) { p[i] = bounds[2 * i + pointid]; } pointscontainer->InsertElement(pointid, p); } m_BoundingBox->SetPoints(pointscontainer); m_BoundingBox->ComputeBoundingBox(); this->Modified(); } void mitk::BaseGeometry::SetIndexToWorldTransform(mitk::AffineTransform3D *transform) { mitk::ModifiedLock lock(this); CheckIndexToWorldTransform(transform); m_GeometryTransform->SetIndexToWorldTransform(transform); Modified(); } void mitk::BaseGeometry::SetIndexToWorldTransformWithoutChangingSpacing(mitk::AffineTransform3D *transform) { // security check mitk::Vector3D originalSpacing = this->GetSpacing(); mitk::ModifiedLock lock(this); CheckIndexToWorldTransform(transform); m_GeometryTransform->SetIndexToWorldTransformWithoutChangingSpacing(transform); Modified(); // Security check. Spacig must not have changed if (!mitk::Equal(originalSpacing, this->GetSpacing())) { MITK_WARN << "Spacing has changed in a method, where the spacing must not change."; assert(false); } } const mitk::BaseGeometry::BoundsArrayType mitk::BaseGeometry::GetBounds() const { assert(m_BoundingBox.IsNotNull()); return m_BoundingBox->GetBounds(); } bool mitk::BaseGeometry::IsValid() const { return true; } void mitk::BaseGeometry::SetSpacing(const mitk::Vector3D &aSpacing, bool enforceSetSpacing) { PreSetSpacing(aSpacing); _SetSpacing(aSpacing, enforceSetSpacing); } void mitk::BaseGeometry::_SetSpacing(const mitk::Vector3D &aSpacing, bool enforceSetSpacing) { m_GeometryTransform->SetSpacing(aSpacing, enforceSetSpacing); } mitk::Vector3D mitk::BaseGeometry::GetAxisVector(unsigned int direction) const { Vector3D frontToBack; frontToBack.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(direction)); frontToBack *= GetExtent(direction); return frontToBack; } mitk::ScalarType mitk::BaseGeometry::GetExtent(unsigned int direction) const { assert(m_BoundingBox.IsNotNull()); if (direction >= m_NDimensions) mitkThrow() << "Direction is too big. This geometry is for 3D Data"; BoundsArrayType bounds = m_BoundingBox->GetBounds(); return bounds[direction * 2 + 1] - bounds[direction * 2]; } bool mitk::BaseGeometry::Is2DConvertable() { bool isConvertableWithoutLoss = true; do { if (this->GetSpacing()[2] != 1) { isConvertableWithoutLoss = false; break; } if (this->GetOrigin()[2] != 0) { isConvertableWithoutLoss = false; break; } mitk::Vector3D col0, col1, col2; col0.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(0)); col1.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(1)); col2.SetVnlVector(this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(2)); if ((col0[2] != 0) || (col1[2] != 0) || (col2[0] != 0) || (col2[1] != 0) || (col2[2] != 1)) { isConvertableWithoutLoss = false; break; } } while (false); return isConvertableWithoutLoss; } mitk::Point3D mitk::BaseGeometry::GetCenter() const { assert(m_BoundingBox.IsNotNull()); Point3D c = m_BoundingBox->GetCenter(); if (m_ImageGeometry) { // Get Center returns the middel of min and max pixel index. In corner based images, this is the right position. // In center based images (imageGeometry == true), the index needs to be shifted back. c[0] -= 0.5; c[1] -= 0.5; c[2] -= 0.5; } this->IndexToWorld(c, c); return c; } double mitk::BaseGeometry::GetDiagonalLength2() const { Vector3D diagonalvector = GetCornerPoint() - GetCornerPoint(false, false, false); return diagonalvector.GetSquaredNorm(); } double mitk::BaseGeometry::GetDiagonalLength() const { return sqrt(GetDiagonalLength2()); } mitk::Point3D mitk::BaseGeometry::GetCornerPoint(int id) const { assert(id >= 0); assert(this->IsBoundingBoxNull() == false); BoundingBox::BoundsArrayType bounds = this->GetBoundingBox()->GetBounds(); Point3D cornerpoint; switch (id) { case 0: FillVector3D(cornerpoint, bounds[0], bounds[2], bounds[4]); break; case 1: FillVector3D(cornerpoint, bounds[0], bounds[2], bounds[5]); break; case 2: FillVector3D(cornerpoint, bounds[0], bounds[3], bounds[4]); break; case 3: FillVector3D(cornerpoint, bounds[0], bounds[3], bounds[5]); break; case 4: FillVector3D(cornerpoint, bounds[1], bounds[2], bounds[4]); break; case 5: FillVector3D(cornerpoint, bounds[1], bounds[2], bounds[5]); break; case 6: FillVector3D(cornerpoint, bounds[1], bounds[3], bounds[4]); break; case 7: FillVector3D(cornerpoint, bounds[1], bounds[3], bounds[5]); break; default: { itkExceptionMacro(<< "A cube only has 8 corners. These are labeled 0-7."); } } if (m_ImageGeometry) { // Here i have to adjust the 0.5 offset manually, because the cornerpoint is the corner of the // bounding box. The bounding box itself is no image, so it is corner-based FillVector3D(cornerpoint, cornerpoint[0] - 0.5, cornerpoint[1] - 0.5, cornerpoint[2] - 0.5); } return this->GetIndexToWorldTransform()->TransformPoint(cornerpoint); } mitk::Point3D mitk::BaseGeometry::GetCornerPoint(bool xFront, bool yFront, bool zFront) const { assert(this->IsBoundingBoxNull() == false); BoundingBox::BoundsArrayType bounds = this->GetBoundingBox()->GetBounds(); Point3D cornerpoint; cornerpoint[0] = (xFront ? bounds[0] : bounds[1]); cornerpoint[1] = (yFront ? bounds[2] : bounds[3]); cornerpoint[2] = (zFront ? bounds[4] : bounds[5]); if (m_ImageGeometry) { // Here i have to adjust the 0.5 offset manually, because the cornerpoint is the corner of the // bounding box. The bounding box itself is no image, so it is corner-based FillVector3D(cornerpoint, cornerpoint[0] - 0.5, cornerpoint[1] - 0.5, cornerpoint[2] - 0.5); } return this->GetIndexToWorldTransform()->TransformPoint(cornerpoint); } mitk::ScalarType mitk::BaseGeometry::GetExtentInMM(int direction) const { return this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(direction).magnitude() * GetExtent(direction); } void mitk::BaseGeometry::SetExtentInMM(int direction, ScalarType extentInMM) { mitk::ModifiedLock lock(this); ScalarType len = GetExtentInMM(direction); if (fabs(len - extentInMM) >= mitk::eps) { AffineTransform3D::MatrixType::InternalMatrixType vnlmatrix; vnlmatrix = m_GeometryTransform->GetVnlMatrix(); if (len > extentInMM) vnlmatrix.set_column(direction, vnlmatrix.get_column(direction) / len * extentInMM); else vnlmatrix.set_column(direction, vnlmatrix.get_column(direction) * extentInMM / len); Matrix3D matrix; matrix = vnlmatrix; m_GeometryTransform->SetMatrix(matrix); Modified(); } } bool mitk::BaseGeometry::IsInside(const mitk::Point3D &p) const { mitk::Point3D index; WorldToIndex(p, index); return IsIndexInside(index); } bool mitk::BaseGeometry::IsIndexInside(const mitk::Point3D &index) const { bool inside = false; // if it is an image geometry, we need to convert the index to discrete values // this is done by applying the rounding function also used in WorldToIndex (see line 323) if (m_ImageGeometry) { mitk::Point3D discretIndex; discretIndex[0] = itk::Math::RoundHalfIntegerUp(index[0]); discretIndex[1] = itk::Math::RoundHalfIntegerUp(index[1]); discretIndex[2] = itk::Math::RoundHalfIntegerUp(index[2]); inside = this->GetBoundingBox()->IsInside(discretIndex); // we have to check if the index is at the upper border of each dimension, // because the boundingbox is not centerbased if (inside) { const BoundingBox::BoundsArrayType &bounds = this->GetBoundingBox()->GetBounds(); if ((discretIndex[0] == bounds[1]) || (discretIndex[1] == bounds[3]) || (discretIndex[2] == bounds[5])) inside = false; } } else inside = this->GetBoundingBox()->IsInside(index); return inside; } void mitk::BaseGeometry::WorldToIndex(const mitk::Point3D &pt_mm, mitk::Point3D &pt_units) const { mitk::Vector3D tempIn, tempOut; const TransformType::OffsetType &offset = this->GetIndexToWorldTransform()->GetOffset(); tempIn = pt_mm.GetVectorFromOrigin() - offset; WorldToIndex(tempIn, tempOut); - pt_units = tempOut; + pt_units = Point3D(tempOut); } void mitk::BaseGeometry::WorldToIndex(const mitk::Vector3D &vec_mm, mitk::Vector3D &vec_units) const { // Get WorldToIndex transform if (m_IndexToWorldTransformLastModified != this->GetIndexToWorldTransform()->GetMTime()) { if (!m_InvertedTransform) { m_InvertedTransform = TransformType::New(); } if (!this->GetIndexToWorldTransform()->GetInverse(m_InvertedTransform.GetPointer())) { itkExceptionMacro("Internal ITK matrix inversion error, cannot proceed."); } m_IndexToWorldTransformLastModified = this->GetIndexToWorldTransform()->GetMTime(); } // Check for valid matrix inversion const TransformType::MatrixType &inverse = m_InvertedTransform->GetMatrix(); if (inverse.GetVnlMatrix().has_nans()) { itkExceptionMacro("Internal ITK matrix inversion error, cannot proceed. Matrix was: " << std::endl << this->GetIndexToWorldTransform()->GetMatrix() << "Suggested inverted matrix is:" << std::endl << inverse); } vec_units = inverse * vec_mm; } void mitk::BaseGeometry::WorldToIndex(const mitk::Point3D & /*atPt3d_mm*/, const mitk::Vector3D &vec_mm, mitk::Vector3D &vec_units) const { MITK_WARN << "Warning! Call of the deprecated function BaseGeometry::WorldToIndex(point, vec, vec). Use " "BaseGeometry::WorldToIndex(vec, vec) instead!"; this->WorldToIndex(vec_mm, vec_units); } mitk::VnlVector mitk::BaseGeometry::GetOriginVnl() const { return GetOrigin().GetVnlVector(); } vtkLinearTransform *mitk::BaseGeometry::GetVtkTransform() const { return m_GeometryTransform->GetVtkTransform(); } void mitk::BaseGeometry::SetIdentity() { mitk::ModifiedLock lock(this); m_GeometryTransform->SetIdentity(); Modified(); } void mitk::BaseGeometry::Compose(const mitk::BaseGeometry::TransformType *other, bool pre) { mitk::ModifiedLock lock(this); m_GeometryTransform->Compose(other, pre); Modified(); } void mitk::BaseGeometry::Compose(const vtkMatrix4x4 *vtkmatrix, bool pre) { mitk::BaseGeometry::TransformType::Pointer itkTransform = mitk::BaseGeometry::TransformType::New(); TransferVtkMatrixToItkTransform(vtkmatrix, itkTransform.GetPointer()); Compose(itkTransform, pre); } void mitk::BaseGeometry::Translate(const Vector3D &vector) { if ((vector[0] != 0) || (vector[1] != 0) || (vector[2] != 0)) { this->SetOrigin(this->GetOrigin() + vector); } } void mitk::BaseGeometry::IndexToWorld(const mitk::Point3D &pt_units, mitk::Point3D &pt_mm) const { pt_mm = this->GetIndexToWorldTransform()->TransformPoint(pt_units); } void mitk::BaseGeometry::IndexToWorld(const mitk::Vector3D &vec_units, mitk::Vector3D &vec_mm) const { vec_mm = this->GetIndexToWorldTransform()->TransformVector(vec_units); } void mitk::BaseGeometry::ExecuteOperation(Operation *operation) { mitk::ModifiedLock lock(this); vtkTransform *vtktransform = vtkTransform::New(); vtktransform->SetMatrix(this->GetVtkMatrix()); switch (operation->GetOperationType()) { case OpNOTHING: break; case OpMOVE: { auto *pointOp = dynamic_cast(operation); if (pointOp == nullptr) { MITK_ERROR << "Point move operation is null!"; return; } mitk::Point3D newPos = pointOp->GetPoint(); ScalarType data[3]; vtktransform->GetPosition(data); vtktransform->PostMultiply(); vtktransform->Translate(newPos[0], newPos[1], newPos[2]); vtktransform->PreMultiply(); break; } case OpSCALE: { auto *scaleOp = dynamic_cast(operation); if (scaleOp == nullptr) { MITK_ERROR << "Scale operation is null!"; return; } mitk::Point3D newScale = scaleOp->GetScaleFactor(); ScalarType scalefactor[3]; scalefactor[0] = 1 + (newScale[0] / GetMatrixColumn(0).magnitude()); scalefactor[1] = 1 + (newScale[1] / GetMatrixColumn(1).magnitude()); scalefactor[2] = 1 + (newScale[2] / GetMatrixColumn(2).magnitude()); mitk::Point3D anchor = scaleOp->GetScaleAnchorPoint(); vtktransform->PostMultiply(); vtktransform->Translate(-anchor[0], -anchor[1], -anchor[2]); vtktransform->Scale(scalefactor[0], scalefactor[1], scalefactor[2]); vtktransform->Translate(anchor[0], anchor[1], anchor[2]); break; } case OpROTATE: { auto *rotateOp = dynamic_cast(operation); if (rotateOp == nullptr) { MITK_ERROR << "Rotation operation is null!"; return; } Vector3D rotationVector = rotateOp->GetVectorOfRotation(); Point3D center = rotateOp->GetCenterOfRotation(); ScalarType angle = rotateOp->GetAngleOfRotation(); vtktransform->PostMultiply(); vtktransform->Translate(-center[0], -center[1], -center[2]); vtktransform->RotateWXYZ(angle, rotationVector[0], rotationVector[1], rotationVector[2]); vtktransform->Translate(center[0], center[1], center[2]); vtktransform->PreMultiply(); break; } case OpRESTOREPLANEPOSITION: { // Copy necessary to avoid vtk warning vtkMatrix4x4 *matrix = vtkMatrix4x4::New(); TransferItkTransformToVtkMatrix( dynamic_cast(operation)->GetTransform().GetPointer(), matrix); vtktransform->SetMatrix(matrix); matrix->Delete(); break; } case OpAPPLYTRANSFORMMATRIX: { auto *applyMatrixOp = dynamic_cast(operation); vtktransform->SetMatrix(applyMatrixOp->GetMatrix()); break; } default: vtktransform->Delete(); return; } this->SetVtkMatrixDeepCopy(vtktransform); Modified(); vtktransform->Delete(); } mitk::VnlVector mitk::BaseGeometry::GetMatrixColumn(unsigned int direction) const { return this->GetIndexToWorldTransform()->GetMatrix().GetVnlMatrix().get_column(direction); } mitk::BoundingBox::Pointer mitk::BaseGeometry::CalculateBoundingBoxRelativeToTransform( const mitk::AffineTransform3D *transform) const { mitk::BoundingBox::PointsContainer::Pointer pointscontainer = mitk::BoundingBox::PointsContainer::New(); mitk::BoundingBox::PointIdentifier pointid = 0; unsigned char i; if (transform != nullptr) { mitk::AffineTransform3D::Pointer inverse = mitk::AffineTransform3D::New(); transform->GetInverse(inverse); for (i = 0; i < 8; ++i) pointscontainer->InsertElement(pointid++, inverse->TransformPoint(GetCornerPoint(i))); } else { for (i = 0; i < 8; ++i) pointscontainer->InsertElement(pointid++, GetCornerPoint(i)); } mitk::BoundingBox::Pointer result = mitk::BoundingBox::New(); result->SetPoints(pointscontainer); result->ComputeBoundingBox(); return result; } const std::string mitk::BaseGeometry::GetTransformAsString(TransformType *transformType) { std::ostringstream out; out << '['; for (int i = 0; i < 3; ++i) { out << '['; for (int j = 0; j < 3; ++j) out << transformType->GetMatrix().GetVnlMatrix().get(i, j) << ' '; out << ']'; } out << "]["; for (int i = 0; i < 3; ++i) out << transformType->GetOffset()[i] << ' '; out << "]\0"; return out.str(); } void mitk::BaseGeometry::SetIndexToWorldTransformByVtkMatrix(vtkMatrix4x4 *vtkmatrix) { m_GeometryTransform->SetIndexToWorldTransformByVtkMatrix(vtkmatrix); } void mitk::BaseGeometry::SetIndexToWorldTransformByVtkMatrixWithoutChangingSpacing(vtkMatrix4x4 *vtkmatrix) { m_GeometryTransform->SetIndexToWorldTransformByVtkMatrixWithoutChangingSpacing(vtkmatrix); } void mitk::BaseGeometry::IndexToWorld(const mitk::Point3D & /*atPt3d_units*/, const mitk::Vector3D &vec_units, mitk::Vector3D &vec_mm) const { MITK_WARN << "Warning! Call of the deprecated function BaseGeometry::IndexToWorld(point, vec, vec). Use " "BaseGeometry::IndexToWorld(vec, vec) instead!"; // vec_mm = m_IndexToWorldTransform->TransformVector(vec_units); this->IndexToWorld(vec_units, vec_mm); } vtkMatrix4x4 *mitk::BaseGeometry::GetVtkMatrix() { return m_GeometryTransform->GetVtkMatrix(); } bool mitk::BaseGeometry::IsBoundingBoxNull() const { return m_BoundingBox.IsNull(); } bool mitk::BaseGeometry::IsIndexToWorldTransformNull() const { return m_GeometryTransform->IsIndexToWorldTransformNull(); } void mitk::BaseGeometry::ChangeImageGeometryConsideringOriginOffset(const bool isAnImageGeometry) { // If Geometry is switched to ImageGeometry, you have to put an offset to the origin, because // imageGeometries origins are pixel-center-based // ... and remove the offset, if you switch an imageGeometry back to a normal geometry // For more information please see the Geometry documentation page if (m_ImageGeometry == isAnImageGeometry) return; const BoundingBox::BoundsArrayType &boundsarray = this->GetBoundingBox()->GetBounds(); Point3D originIndex; FillVector3D(originIndex, boundsarray[0], boundsarray[2], boundsarray[4]); if (isAnImageGeometry == true) FillVector3D(originIndex, originIndex[0] + 0.5, originIndex[1] + 0.5, originIndex[2] + 0.5); else FillVector3D(originIndex, originIndex[0] - 0.5, originIndex[1] - 0.5, originIndex[2] - 0.5); Point3D originWorld; originWorld = GetIndexToWorldTransform()->TransformPoint(originIndex); // instead could as well call IndexToWorld(originIndex,originWorld); SetOrigin(originWorld); this->SetImageGeometry(isAnImageGeometry); } void mitk::BaseGeometry::PrintSelf(std::ostream &os, itk::Indent indent) const { os << indent << " IndexToWorldTransform: "; if (this->IsIndexToWorldTransformNull()) os << "nullptr" << std::endl; else { // from itk::MatrixOffsetTransformBase unsigned int i, j; os << std::endl; os << indent << "Matrix: " << std::endl; for (i = 0; i < 3; i++) { os << indent.GetNextIndent(); for (j = 0; j < 3; j++) { os << this->GetIndexToWorldTransform()->GetMatrix()[i][j] << " "; } os << std::endl; } os << indent << "Offset: " << this->GetIndexToWorldTransform()->GetOffset() << std::endl; os << indent << "Center: " << this->GetIndexToWorldTransform()->GetCenter() << std::endl; os << indent << "Translation: " << this->GetIndexToWorldTransform()->GetTranslation() << std::endl; os << indent << "Inverse: " << std::endl; for (i = 0; i < 3; i++) { os << indent.GetNextIndent(); for (j = 0; j < 3; j++) { os << this->GetIndexToWorldTransform()->GetInverseMatrix()[i][j] << " "; } os << std::endl; } // from itk::ScalableAffineTransform os << indent << "Scale : "; for (i = 0; i < 3; i++) { os << this->GetIndexToWorldTransform()->GetScale()[i] << " "; } os << std::endl; } os << indent << " BoundingBox: "; if (this->IsBoundingBoxNull()) os << "nullptr" << std::endl; else { os << indent << "( "; for (unsigned int i = 0; i < 3; i++) { os << this->GetBoundingBox()->GetBounds()[2 * i] << "," << this->GetBoundingBox()->GetBounds()[2 * i + 1] << " "; } os << " )" << std::endl; } os << indent << " Origin: " << this->GetOrigin() << std::endl; os << indent << " ImageGeometry: " << this->GetImageGeometry() << std::endl; os << indent << " Spacing: " << this->GetSpacing() << std::endl; } void mitk::BaseGeometry::Modified() const { if (!m_ModifiedLockFlag) Superclass::Modified(); else m_ModifiedCalledFlag = true; } mitk::AffineTransform3D *mitk::BaseGeometry::GetIndexToWorldTransform() { return m_GeometryTransform->GetIndexToWorldTransform(); } const mitk::AffineTransform3D *mitk::BaseGeometry::GetIndexToWorldTransform() const { return m_GeometryTransform->GetIndexToWorldTransform(); } const mitk::GeometryTransformHolder *mitk::BaseGeometry::GetGeometryTransformHolder() const { return m_GeometryTransform; } bool mitk::Equal(const mitk::BaseGeometry::BoundingBoxType &leftHandSide, const mitk::BaseGeometry::BoundingBoxType &rightHandSide, ScalarType eps, bool verbose) { bool result = true; BaseGeometry::BoundsArrayType rightBounds = rightHandSide.GetBounds(); BaseGeometry::BoundsArrayType leftBounds = leftHandSide.GetBounds(); BaseGeometry::BoundsArrayType::Iterator itLeft = leftBounds.Begin(); for (BaseGeometry::BoundsArrayType::Iterator itRight = rightBounds.Begin(); itRight != rightBounds.End(); ++itRight) { if ((!mitk::Equal(*itLeft, *itRight, eps))) { if (verbose) { MITK_INFO << "[( Geometry3D::BoundingBoxType )] bounds are not equal."; MITK_INFO << "rightHandSide is " << setprecision(12) << *itRight << " : leftHandSide is " << *itLeft << " and tolerance is " << eps; } result = false; } itLeft++; } return result; } bool mitk::Equal(const mitk::BaseGeometry &leftHandSide, const mitk::BaseGeometry &rightHandSide, ScalarType coordinateEps, ScalarType directionEps, bool verbose) { bool result = true; // Compare spacings if (!mitk::Equal(leftHandSide.GetSpacing(), rightHandSide.GetSpacing(), coordinateEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Spacing differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetSpacing() << " : leftHandSide is " << leftHandSide.GetSpacing() << " and tolerance is " << coordinateEps; } result = false; } // Compare Origins if (!mitk::Equal(leftHandSide.GetOrigin(), rightHandSide.GetOrigin(), coordinateEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Origin differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetOrigin() << " : leftHandSide is " << leftHandSide.GetOrigin() << " and tolerance is " << coordinateEps; } result = false; } // Compare Axis and Extents for (unsigned int i = 0; i < 3; ++i) { if (!mitk::Equal(leftHandSide.GetAxisVector(i), rightHandSide.GetAxisVector(i), directionEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] AxisVector #" << i << " differ"; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetAxisVector(i) << " : leftHandSide is " << leftHandSide.GetAxisVector(i) << " and tolerance is " << directionEps; } result = false; } if (!mitk::Equal(leftHandSide.GetExtent(i), rightHandSide.GetExtent(i), coordinateEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Extent #" << i << " differ"; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetExtent(i) << " : leftHandSide is " << leftHandSide.GetExtent(i) << " and tolerance is " << coordinateEps; } result = false; } } // Compare ImageGeometry Flag if (rightHandSide.GetImageGeometry() != leftHandSide.GetImageGeometry()) { if (verbose) { MITK_INFO << "[( Geometry3D )] GetImageGeometry is different."; MITK_INFO << "rightHandSide is " << rightHandSide.GetImageGeometry() << " : leftHandSide is " << leftHandSide.GetImageGeometry(); } result = false; } // Compare FrameOfReference ID if (rightHandSide.GetFrameOfReferenceID() != leftHandSide.GetFrameOfReferenceID()) { if (verbose) { MITK_INFO << "[( Geometry3D )] GetFrameOfReferenceID is different."; MITK_INFO << "rightHandSide is " << rightHandSide.GetFrameOfReferenceID() << " : leftHandSide is " << leftHandSide.GetFrameOfReferenceID(); } result = false; } // Compare BoundingBoxes if (!mitk::Equal(*leftHandSide.GetBoundingBox(), *rightHandSide.GetBoundingBox(), coordinateEps, verbose)) { result = false; } // Compare IndexToWorldTransform Matrix if (!mitk::Equal(*leftHandSide.GetIndexToWorldTransform(), *rightHandSide.GetIndexToWorldTransform(), directionEps, verbose)) { result = false; } return result; } bool mitk::Equal(const mitk::BaseGeometry& leftHandSide, const mitk::BaseGeometry& rightHandSide, ScalarType eps, bool verbose) { return Equal(leftHandSide, rightHandSide, eps, eps, verbose); } bool mitk::Equal(const mitk::BaseGeometry::TransformType &leftHandSide, const mitk::BaseGeometry::TransformType &rightHandSide, ScalarType eps, bool verbose) { // Compare IndexToWorldTransform Matrix if (!mitk::MatrixEqualElementWise(leftHandSide.GetMatrix(), rightHandSide.GetMatrix(), eps)) { if (verbose) { MITK_INFO << "[( Geometry3D::TransformType )] Index to World Transformation matrix differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetMatrix() << " : leftHandSide is " << leftHandSide.GetMatrix() << " and tolerance is " << eps; } return false; } return true; } bool mitk::IsSubGeometry(const mitk::BaseGeometry& testGeo, const mitk::BaseGeometry& referenceGeo, ScalarType coordinateEps, ScalarType directionEps, bool verbose) { bool result = true; // Compare spacings (must be equal) const auto testedSpacing = testGeo.GetSpacing(); if (!mitk::Equal(testedSpacing, referenceGeo.GetSpacing(), coordinateEps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Spacing differs."; MITK_INFO << "testedGeometry is " << setprecision(12) << testedSpacing << " : referenceGeometry is " << referenceGeo.GetSpacing() << " and tolerance is " << coordinateEps; } result = false; } // Compare ImageGeometry Flag (must be equal) if (referenceGeo.GetImageGeometry() != testGeo.GetImageGeometry()) { if (verbose) { MITK_INFO << "[( Geometry3D )] GetImageGeometry is different."; MITK_INFO << "referenceGeo is " << referenceGeo.GetImageGeometry() << " : testGeo is " << testGeo.GetImageGeometry(); } result = false; } // Compare IndexToWorldTransform Matrix (must be equal -> same axis directions) if (!Equal(*(testGeo.GetIndexToWorldTransform()), *(referenceGeo.GetIndexToWorldTransform()), directionEps, verbose)) { result = false; } for (int i = 0; i<8; ++i) { auto testCorner = testGeo.GetCornerPoint(i); bool isInside = false; mitk::Point3D testCornerIndex; referenceGeo.WorldToIndex(testCorner, testCornerIndex); std::bitset bs(i); //To regard the directionEps, we substract or add it to the index elments //depending on wether it was constructed by a lower or an upper bound value //(see implementation of BaseGeometry::GetCorner()). if (bs.test(0)) { testCornerIndex[2] -= directionEps; } else { testCornerIndex[2] += directionEps; } if (bs.test(1)) { testCornerIndex[1] -= directionEps; } else { testCornerIndex[1] += directionEps; } if (bs.test(2)) { testCornerIndex[0] -= directionEps; } else { testCornerIndex[0] += directionEps; } isInside = referenceGeo.IsIndexInside(testCornerIndex); if (!isInside) { if (verbose) { MITK_INFO << "[( Geometry3D )] corner point is not inside. "; MITK_INFO << "referenceGeo is " << setprecision(12) << referenceGeo << " : tested corner is " << testGeo.GetCornerPoint(i); } result = false; } } // check grid of test geometry is on the grid of the reference geometry. This is important as the // boundingbox is only checked for containing the tested geometry, but if a corner (one is enough // as we know that axis and spacing are equal, due to equal transfor (see above)) of the tested geometry // is on the grid it is really a sub geometry (as they have the same spacing and axis). auto cornerOffset = testGeo.GetCornerPoint(0) - referenceGeo.GetCornerPoint(0); mitk::Vector3D cornerIndexOffset; referenceGeo.WorldToIndex(cornerOffset, cornerIndexOffset); for (unsigned int i = 0; i < 3; ++i) { auto pixelCountContinous = cornerIndexOffset[i]; auto pixelCount = std::round(pixelCountContinous); if (std::abs(pixelCount - pixelCountContinous) > coordinateEps) { if (verbose) { MITK_INFO << "[( Geometry3D )] Tested geometry is not on the grid of the reference geometry. "; MITK_INFO << "referenceGeo is " << setprecision(15) << referenceGeo << " : tested corner offset in pixels is " << pixelCountContinous << " for axis "< #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace mitk { void GeometryTransformHolder::CopySpacingFromTransform(const mitk::AffineTransform3D *transform, mitk::Vector3D &spacing) { mitk::AffineTransform3D::MatrixType::InternalMatrixType vnlmatrix; vnlmatrix = transform->GetMatrix().GetVnlMatrix(); spacing[0] = vnlmatrix.get_column(0).magnitude(); spacing[1] = vnlmatrix.get_column(1).magnitude(); spacing[2] = vnlmatrix.get_column(2).magnitude(); } GeometryTransformHolder::GeometryTransformHolder() { m_VtkMatrix = vtkMatrix4x4::New(); m_VtkIndexToWorldTransform = vtkMatrixToLinearTransform::New(); m_VtkIndexToWorldTransform->SetInput(m_VtkMatrix); this->Initialize(); } GeometryTransformHolder::GeometryTransformHolder(const GeometryTransformHolder &other) { m_VtkMatrix = vtkMatrix4x4::New(); m_VtkIndexToWorldTransform = vtkMatrixToLinearTransform::New(); m_VtkIndexToWorldTransform->SetInput(m_VtkMatrix); this->Initialize(&other); } GeometryTransformHolder::~GeometryTransformHolder() { m_VtkMatrix->Delete(); m_VtkIndexToWorldTransform->Delete(); } void GeometryTransformHolder::Initialize() { if (m_IndexToWorldTransform.IsNull()) m_IndexToWorldTransform = TransformType::New(); else m_IndexToWorldTransform->SetIdentity(); m_VtkMatrix->Identity(); } void GeometryTransformHolder::Initialize(const GeometryTransformHolder *other) { Initialize(); if (other->GetIndexToWorldTransform()) { TransformType::Pointer indexToWorldTransform = other->GetIndexToWorldTransform()->Clone(); this->SetIndexToWorldTransform(indexToWorldTransform); } } //##Documentation //## @brief Copy the ITK transform //## (m_IndexToWorldTransform) to the VTK transform //## \sa SetIndexToWorldTransform void GeometryTransformHolder::TransferItkToVtkTransform() { TransferItkTransformToVtkMatrix(m_IndexToWorldTransform.GetPointer(), m_VtkMatrix); } //##Documentation //## @brief Copy the VTK transform //## to the ITK transform (m_IndexToWorldTransform) //## \sa SetIndexToWorldTransform void GeometryTransformHolder::TransferVtkToItkTransform() { TransferVtkMatrixToItkTransform(m_VtkMatrix, m_IndexToWorldTransform.GetPointer()); } //##Documentation //## @brief Get the origin, e.g. the upper-left corner of the plane const Point3D GeometryTransformHolder::GetOrigin() const { - mitk::Point3D origin = this->GetIndexToWorldTransform()->GetOffset(); + Point3D origin = Point3D(this->GetIndexToWorldTransform()->GetOffset()); return origin; } //##Documentation //## @brief Set the origin, i.e. the upper-left corner of the plane //## void GeometryTransformHolder::SetOrigin(const Point3D &origin) { m_IndexToWorldTransform->SetOffset(origin.GetVectorFromOrigin()); TransferItkToVtkTransform(); } //##Documentation //## @brief Get the spacing (size of a pixel). //## const mitk::Vector3D GeometryTransformHolder::GetSpacing() const { mitk::Vector3D spacing; CopySpacingFromTransform(this->GetIndexToWorldTransform(), spacing); return spacing; } //##Documentation //## @brief Set the spacing. //## //##The spacing is also changed in the IndexToWorldTransform. void GeometryTransformHolder::SetSpacing(const mitk::Vector3D &aSpacing, bool enforceSetSpacing) { if (mitk::Equal(this->GetSpacing(), aSpacing) == false || enforceSetSpacing) { assert(aSpacing[0] > 0 && aSpacing[1] > 0 && aSpacing[2] > 0); AffineTransform3D::MatrixType::InternalMatrixType vnlmatrix; vnlmatrix = m_IndexToWorldTransform->GetMatrix().GetVnlMatrix(); mitk::VnlVector col; col = vnlmatrix.get_column(0); col.normalize(); col *= aSpacing[0]; vnlmatrix.set_column(0, col); col = vnlmatrix.get_column(1); col.normalize(); col *= aSpacing[1]; vnlmatrix.set_column(1, col); col = vnlmatrix.get_column(2); col.normalize(); col *= aSpacing[2]; vnlmatrix.set_column(2, col); Matrix3D matrix; matrix = vnlmatrix; AffineTransform3D::Pointer transform = AffineTransform3D::New(); transform->SetMatrix(matrix); transform->SetOffset(m_IndexToWorldTransform->GetOffset()); SetIndexToWorldTransform(transform.GetPointer()); } } //##Documentation //## @brief Get the transformation used to convert from index //## to world coordinates mitk::AffineTransform3D *GeometryTransformHolder::GetIndexToWorldTransform() { return m_IndexToWorldTransform; } //##Documentation //## @brief Get the transformation used to convert from index //## to world coordinates const mitk::AffineTransform3D *GeometryTransformHolder::GetIndexToWorldTransform() const { return m_IndexToWorldTransform; } //## @brief Set the transformation used to convert from index //## to world coordinates. The spacing of the new transform is //## copied to m_spacing. void GeometryTransformHolder::SetIndexToWorldTransform(mitk::AffineTransform3D *transform) { m_IndexToWorldTransform = transform; TransferItkToVtkTransform(); } //##Documentation //## @brief Convenience method for setting the ITK transform //## (m_IndexToWorldTransform) via an vtkMatrix4x4.The spacing of //## the new transform is copied to m_spacing. //## \sa SetIndexToWorldTransform void GeometryTransformHolder::SetIndexToWorldTransformByVtkMatrix(vtkMatrix4x4 *vtkmatrix) { m_VtkMatrix->DeepCopy(vtkmatrix); TransferVtkToItkTransform(); } //## @brief Set the transformation used to convert from index //## to world coordinates.This function keeps the original spacing. void GeometryTransformHolder::SetIndexToWorldTransformWithoutChangingSpacing(mitk::AffineTransform3D *transform) { mitk::Vector3D originalSpacing = this->GetSpacing(); this->SetIndexToWorldTransform(transform); this->SetSpacing(originalSpacing); } //##Documentation //## @brief Convenience method for setting the ITK transform //## (m_IndexToWorldTransform) via an vtkMatrix4x4. This function keeps the original spacing. //## \sa SetIndexToWorldTransform void GeometryTransformHolder::SetIndexToWorldTransformByVtkMatrixWithoutChangingSpacing(vtkMatrix4x4 *vtkmatrix) { mitk::Vector3D originalSpacing = this->GetSpacing(); this->SetIndexToWorldTransformByVtkMatrix(vtkmatrix); this->SetSpacing(originalSpacing); } //## Get the Vtk Matrix which describes the transform. vtkMatrix4x4 *GeometryTransformHolder::GetVtkMatrix() { return m_VtkMatrix; } //## Get the Vtk Matrix which describes the transform. const vtkMatrix4x4 *GeometryTransformHolder::GetVtkMatrix() const { return m_VtkMatrix; } //##Documentation //## @brief Get the m_IndexToWorldTransform as a vtkLinearTransform vtkLinearTransform *GeometryTransformHolder::GetVtkTransform() const { return (vtkLinearTransform *)m_VtkIndexToWorldTransform; } void GeometryTransformHolder::SetMatrix(Matrix3D &matrix) { m_IndexToWorldTransform->SetMatrix(matrix); TransferItkToVtkTransform(); } void GeometryTransformHolder::SetIdentity() { m_IndexToWorldTransform->SetIdentity(); TransferItkToVtkTransform(); } void GeometryTransformHolder::Compose(const TransformType *other, bool pre) { m_IndexToWorldTransform->Compose(other, pre); TransferItkToVtkTransform(); } void GeometryTransformHolder::SetVtkMatrixDeepCopy(vtkTransform *vtktransform) { m_VtkMatrix->DeepCopy(vtktransform->GetMatrix()); TransferVtkToItkTransform(); } bool GeometryTransformHolder::IsIndexToWorldTransformNull() { return m_IndexToWorldTransform.IsNull(); } AffineTransform3D::MatrixType::InternalMatrixType GeometryTransformHolder::GetVnlMatrix() { return m_IndexToWorldTransform->GetMatrix().GetVnlMatrix(); } } bool mitk::Equal(const mitk::GeometryTransformHolder *leftHandSide, const mitk::GeometryTransformHolder *rightHandSide, ScalarType eps, bool verbose) { if ((leftHandSide == nullptr) || (rightHandSide == nullptr)) { MITK_ERROR << "mitk::Equal(const mitk::Geometry3D *leftHandSide, const mitk::Geometry3D *rightHandSide, ScalarType " "eps, bool verbose) does not with nullptr pointer input."; return false; } return Equal(*leftHandSide, *rightHandSide, eps, verbose); } bool mitk::Equal(const mitk::GeometryTransformHolder &leftHandSide, const mitk::GeometryTransformHolder &rightHandSide, ScalarType eps, bool verbose) { bool result = true; // Compare spacings if (!mitk::Equal(leftHandSide.GetSpacing(), rightHandSide.GetSpacing(), eps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Spacing differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetSpacing() << " : leftHandSide is " << leftHandSide.GetSpacing() << " and tolerance is " << eps; } result = false; } // Compare Origins if (!mitk::Equal(leftHandSide.GetOrigin(), rightHandSide.GetOrigin(), eps)) { if (verbose) { MITK_INFO << "[( Geometry3D )] Origin differs."; MITK_INFO << "rightHandSide is " << setprecision(12) << rightHandSide.GetOrigin() << " : leftHandSide is " << leftHandSide.GetOrigin() << " and tolerance is " << eps; } result = false; } // Compare IndexToWorldTransform Matrix if (!mitk::Equal(*leftHandSide.GetIndexToWorldTransform(), *rightHandSide.GetIndexToWorldTransform(), eps, verbose)) { result = false; } // Compare vtk Matrix for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { if (leftHandSide.GetVtkMatrix()->GetElement(i, j) != rightHandSide.GetVtkMatrix()->GetElement(i, j)) { result = false; } } } return result; } diff --git a/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp b/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp index ac2d921e9a..cfecd85744 100644 --- a/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp +++ b/Modules/Core/src/Interactions/mitkPointSetDataInteractor.cpp @@ -1,621 +1,621 @@ /*============================================================================ 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 "mitkPointSetDataInteractor.h" #include "mitkMouseMoveEvent.h" #include "mitkInteractionConst.h" // TODO: refactor file #include "mitkInternalEvent.h" #include "mitkOperationEvent.h" #include "mitkRenderingManager.h" #include // #include "mitkBaseRenderer.h" #include "mitkDispatcher.h" #include "mitkUndoController.h" void mitk::PointSetDataInteractor::ConnectActionsAndFunctions() { // Condition which is evaluated before transition is taken // following actions in the statemachine are only executed if it returns TRUE CONNECT_CONDITION("isoverpoint", CheckSelection); CONNECT_FUNCTION("addpoint", AddPoint); CONNECT_FUNCTION("selectpoint", SelectPoint); CONNECT_FUNCTION("unselect", UnSelectPointAtPosition); CONNECT_FUNCTION("unselectAll", UnSelectAll); CONNECT_FUNCTION("initMove", InitMove); CONNECT_FUNCTION("movePoint", MovePoint); CONNECT_FUNCTION("finishMovement", FinishMove); CONNECT_FUNCTION("removePoint", RemovePoint); } void mitk::PointSetDataInteractor::AddPoint(StateMachineAction *stateMachineAction, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); // disallow adding of new points if maximum number of points is reached if (m_MaxNumberOfPoints > 1 && m_PointSet->GetSize(timeStep) >= m_MaxNumberOfPoints) { return; } // To add a point the minimal information is the position, this method accepts all InteractionsPositionEvents auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { mitk::Point3D itkPoint = positionEvent->GetPositionInWorld(); this->UnselectAll(timeStep, timeInMs); int lastPosition = 0; mitk::PointSet::PointsIterator it, end; it = m_PointSet->Begin(timeStep); end = m_PointSet->End(timeStep); while (it != end) { if (!m_PointSet->IndexExists(lastPosition, timeStep)) break; ++it; ++lastPosition; } // Insert a Point to the PointSet // 2) Create the Operation inserting the point if (m_PointSet->IsEmpty()) { lastPosition = 0; } auto *doOp = new mitk::PointOperation(OpINSERT, timeInMs, itkPoint, lastPosition); // 3) If Undo is enabled, also create the inverse Operation if (m_UndoEnabled) { auto *undoOp = new mitk::PointOperation(OpREMOVE, timeInMs, itkPoint, lastPosition); // 4) Do and Undo Operations are combined in an Operation event which also contains the target of the operations // (here m_PointSet) OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Add point"); // 5) Store the Operation in the UndoController OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); } // 6) Execute the Operation performs the actual insertion of the point into the PointSet m_PointSet->ExecuteOperation(doOp); // 7) If Undo is not enabled the Do-Operation is to be dropped to prevent memory leaks. if (!m_UndoEnabled) delete doOp; RenderingManager::GetInstance()->RequestUpdateAll(); // Check if points form a closed contour now, if so fire an InternalEvent IsClosedContour(stateMachineAction, interactionEvent); if (m_MaxNumberOfPoints > 0 && m_PointSet->GetSize(timeStep) >= m_MaxNumberOfPoints) { // Signal that DataNode is fully filled this->NotifyResultReady(); // Send internal event that can be used by StateMachines to switch in a different state InternalEvent::Pointer event = InternalEvent::New(nullptr, this, "MaximalNumberOfPoints"); positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer()); } } } void mitk::PointSetDataInteractor::SelectPoint(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected int index = GetPointIndexByPosition(point, timeStep); if (index != -1) { // first deselect the other points // undoable deselect of all points in the DataList this->UnselectAll(timeStep, timeInMs); auto *doOp = new mitk::PointOperation(OpSELECTPOINT, timeInMs, point, index); /*if (m_UndoEnabled) { PointOperation* undoOp = new mitk::PointOperation(OpDESELECTPOINT,timeInMs,point, index); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Select Point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); }*/ // execute the Operation m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; RenderingManager::GetInstance()->RequestUpdateAll(); } } } mitk::PointSetDataInteractor::PointSetDataInteractor() : m_MaxNumberOfPoints(0), m_SelectionAccuracy(3.5) { } mitk::PointSetDataInteractor::~PointSetDataInteractor() { } void mitk::PointSetDataInteractor::RemovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { mitk::Point3D itkPoint = positionEvent->GetPositionInWorld(); // search the point in the list int position = m_PointSet->SearchPoint(itkPoint, m_SelectionAccuracy, timeStep); if (position >= 0) // found a point { PointSet::PointType pt = m_PointSet->GetPoint(position, timeStep); itkPoint[0] = pt[0]; itkPoint[1] = pt[1]; itkPoint[2] = pt[2]; auto *doOp = new mitk::PointOperation(OpREMOVE, timeInMs, itkPoint, position); if (m_UndoEnabled) // write to UndoMechanism { auto *undoOp = new mitk::PointOperation(OpINSERT, timeInMs, itkPoint, position); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Remove point"); mitk::OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); } // execute the Operation m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; /*now select the point "position-1", and if it is the first in list, then continue at the last in list*/ // only then a select of a point is possible! if (m_PointSet->GetSize(timeStep) > 0) { this->SelectPoint(m_PointSet->Begin(timeStep)->Index(), timeStep, timeInMs); } } RenderingManager::GetInstance()->RequestUpdateAll(); } } void mitk::PointSetDataInteractor::IsClosedContour(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected if (GetPointIndexByPosition(point, timeStep) != -1 && m_PointSet->GetSize(timeStep) >= 3) { InternalEvent::Pointer event = InternalEvent::New(nullptr, this, "ClosedContour"); positionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer()); } } } void mitk::PointSetDataInteractor::MovePoint(StateMachineAction *stateMachineAction, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { IsClosedContour(stateMachineAction, interactionEvent); mitk::Point3D newPoint, resultPoint; newPoint = positionEvent->GetPositionInWorld(); // search the elements in the list that are selected then calculate the // vector, because only with the vector we can move several elements in // the same direction // newPoint - lastPoint = vector // then move all selected and set the lastPoint = newPoint. // then add all vectors to a summeryVector (to be able to calculate the // startpoint for undoOperation) mitk::Vector3D dirVector = newPoint - m_LastPoint; // sum up all Movement for Undo in FinishMovement m_SumVec = m_SumVec + dirVector; mitk::PointSet::PointsIterator it, end; it = m_PointSet->Begin(timeStep); end = m_PointSet->End(timeStep); while (it != end) { int position = it->Index(); if (m_PointSet->GetSelectInfo(position, timeStep)) // if selected { PointSet::PointType pt = m_PointSet->GetPoint(position, timeStep); mitk::Point3D sumVec; sumVec[0] = pt[0]; sumVec[1] = pt[1]; sumVec[2] = pt[2]; resultPoint = sumVec + dirVector; auto *doOp = new mitk::PointOperation(OpMOVE, timeInMs, resultPoint, position); // execute the Operation // here no undo is stored, because the movement-steps aren't interesting. // only the start and the end is interisting to store for undo. m_PointSet->ExecuteOperation(doOp); delete doOp; } ++it; } m_LastPoint = newPoint; // for calculation of the direction vector // Update the display RenderingManager::GetInstance()->RequestUpdateAll(); IsClosedContour(stateMachineAction, interactionEvent); } } void mitk::PointSetDataInteractor::UnSelectPointAtPosition(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected int index = GetPointIndexByPosition(point, timeStep); // here it is ensured that we don't switch from one point being selected to another one being selected, // without accepting the unselect of the current point if (index != -1) { auto *doOp = new mitk::PointOperation(OpDESELECTPOINT, timeInMs, point, index); /*if (m_UndoEnabled) { PointOperation* undoOp = new mitk::PointOperation(OpSELECTPOINT,timeInMs, point, index); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Unselect Point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); }*/ m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; RenderingManager::GetInstance()->RequestUpdateAll(); } } } void mitk::PointSetDataInteractor::UnSelectAll(mitk::StateMachineAction *, mitk::InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { Point3D positioninWorld = positionEvent->GetPositionInWorld(); PointSet::PointsContainer::Iterator it, end; PointSet::DataType *itkPointSet = m_PointSet->GetPointSet(timeStep); end = itkPointSet->GetPoints()->End(); for (it = itkPointSet->GetPoints()->Begin(); it != end; it++) { int position = it->Index(); // then declare an operation which unselects this point; // UndoOperation as well! if (m_PointSet->GetSelectInfo(position, timeStep)) { float distance = sqrt(positioninWorld.SquaredEuclideanDistanceTo(m_PointSet->GetPoint(position, timeStep))); if (distance > m_SelectionAccuracy) { mitk::Point3D noPoint; noPoint.Fill(0); auto *doOp = new mitk::PointOperation(OpDESELECTPOINT, timeInMs, noPoint, position); /*if ( m_UndoEnabled ) { mitk::PointOperation* undoOp = new mitk::PointOperation(OpSELECTPOINT, timeInMs, noPoint, position); OperationEvent *operationEvent = new OperationEvent( m_PointSet, doOp, undoOp, "Unselect Point" ); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent( operationEvent ); }*/ m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } } } } else { this->UnselectAll(timeStep, timeInMs); } RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PointSetDataInteractor::UpdatePointSet(mitk::StateMachineAction *, mitk::InteractionEvent *) { auto *pointSet = dynamic_cast(this->GetDataNode()->GetData()); if (pointSet == nullptr) { MITK_ERROR << "PointSetDataInteractor:: No valid point set ."; return; } m_PointSet = pointSet; } void mitk::PointSetDataInteractor::Abort(StateMachineAction *, InteractionEvent *interactionEvent) { InternalEvent::Pointer event = InternalEvent::New(nullptr, this, IntDeactivateMe); interactionEvent->GetSender()->GetDispatcher()->QueueEvent(event.GetPointer()); } /* * Check whether the DataNode contains a pointset, if not create one and add it. */ void mitk::PointSetDataInteractor::DataNodeChanged() { if (GetDataNode() != nullptr) { auto *points = dynamic_cast(GetDataNode()->GetData()); if (points == nullptr) { m_PointSet = PointSet::New(); GetDataNode()->SetData(m_PointSet); } else { m_PointSet = points; } // load config file parameter: maximal number of points mitk::PropertyList::Pointer properties = GetAttributes(); std::string strNumber; if (properties->GetStringProperty("MaxPoints", strNumber)) { m_MaxNumberOfPoints = atoi(strNumber.c_str()); } } } void mitk::PointSetDataInteractor::InitMove(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) return; // start of the Movement is stored to calculate the undoKoordinate // in FinishMovement m_LastPoint = positionEvent->GetPositionInWorld(); // initialize a value to calculate the movement through all // MouseMoveEvents from MouseClick to MouseRelease m_SumVec.Fill(0); GetDataNode()->SetProperty("contourcolor", ColorProperty::New(1.0, 1.0, 1.0)); } void mitk::PointSetDataInteractor::FinishMove(StateMachineAction *, InteractionEvent *interactionEvent) { unsigned int timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); ScalarType timeInMs = interactionEvent->GetSender()->GetTime(); auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { // finish the movement: // the final point is m_LastPoint // m_SumVec stores the movement in a vector // the operation would not be necessary, but we need it for the undo Operation. // m_LastPoint is for the Operation // the point for undoOperation calculates from all selected // elements (point) - m_SumVec // search all selected elements and move them with undo-functionality. mitk::PointSet::PointsIterator it, end; it = m_PointSet->Begin(timeStep); end = m_PointSet->End(timeStep); while (it != end) { int position = it->Index(); if (m_PointSet->GetSelectInfo(position, timeStep)) // if selected { PointSet::PointType pt = m_PointSet->GetPoint(position, timeStep); Point3D itkPoint; itkPoint[0] = pt[0]; itkPoint[1] = pt[1]; itkPoint[2] = pt[2]; auto *doOp = new mitk::PointOperation(OpMOVE, timeInMs, itkPoint, position); if (m_UndoEnabled) //&& (posEvent->GetType() == mitk::Type_MouseButtonRelease) { // set the undo-operation, so the final position is undo-able // calculate the old Position from the already moved position - m_SumVec mitk::Point3D undoPoint = (itkPoint - m_SumVec); auto *undoOp = new mitk::PointOperation(OpMOVE, timeInMs, undoPoint, position); OperationEvent *operationEvent = new OperationEvent(m_PointSet, doOp, undoOp, "Move point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); } // execute the Operation m_PointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } ++it; } // Update the display RenderingManager::GetInstance()->RequestUpdateAll(); } else { return; } this->NotifyResultReady(); } void mitk::PointSetDataInteractor::SetAccuracy(float accuracy) { m_SelectionAccuracy = accuracy; } void mitk::PointSetDataInteractor::SetMaxPoints(unsigned int maxNumber) { m_MaxNumberOfPoints = maxNumber; } int mitk::PointSetDataInteractor::GetPointIndexByPosition(Point3D position, unsigned int time, float accuracy) { // iterate over point set and check if it contains a point close enough to the pointer to be selected auto *points = dynamic_cast(GetDataNode()->GetData()); int index = -1; if (points == nullptr) { return index; } if (points->GetPointSet(time) == nullptr) return -1; PointSet::PointsContainer *pointsContainer = points->GetPointSet(time)->GetPoints(); float minDistance = m_SelectionAccuracy; if (accuracy != -1) minDistance = accuracy; for (PointSet::PointsIterator it = pointsContainer->Begin(); it != pointsContainer->End(); it++) { float distance = sqrt(position.SquaredEuclideanDistanceTo(points->GetPoint(it->Index(), time))); if (distance < minDistance) // if several points fall within the margin, choose the one with minimal distance to position { index = it->Index(); } } return index; } bool mitk::PointSetDataInteractor::CheckSelection(const mitk::InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent != nullptr) { - int timeStep = positionEvent->GetSender()->GetTimeStep(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); Point3D point = positionEvent->GetPositionInWorld(); // iterate over point set and check if it contains a point close enough to the pointer to be selected int index = GetPointIndexByPosition(point, timeStep); if (index != -1) return true; } return false; } void mitk::PointSetDataInteractor::UnselectAll(unsigned int timeStep, ScalarType timeInMs) { auto *pointSet = dynamic_cast(GetDataNode()->GetData()); if (pointSet == nullptr) { return; } mitk::PointSet::DataType *itkPointSet = pointSet->GetPointSet(timeStep); if (itkPointSet == nullptr) { return; } mitk::PointSet::PointsContainer::Iterator it, end; end = itkPointSet->GetPoints()->End(); for (it = itkPointSet->GetPoints()->Begin(); it != end; it++) { int position = it->Index(); PointSet::PointDataType pointData = {0, false, PTUNDEFINED}; itkPointSet->GetPointData(position, &pointData); // then declare an operation which unselects this point; // UndoOperation as well! if (pointData.selected) { mitk::Point3D noPoint; noPoint.Fill(0); auto *doOp = new mitk::PointOperation(OpDESELECTPOINT, timeInMs, noPoint, position); /*if ( m_UndoEnabled ) { mitk::PointOperation *undoOp = new mitk::PointOperation(OpSELECTPOINT, timeInMs, noPoint, position); OperationEvent *operationEvent = new OperationEvent( pointSet, doOp, undoOp, "Unselect Point" ); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent( operationEvent ); }*/ pointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } } } void mitk::PointSetDataInteractor::SelectPoint(int position, unsigned int timeStep, ScalarType timeInMS) { auto *pointSet = dynamic_cast(this->GetDataNode()->GetData()); // if List is empty, then no selection of a point can be done! if ((pointSet == nullptr) || (pointSet->GetSize(timeStep) <= 0)) { return; } // dummyPoint... not needed anyway mitk::Point3D noPoint; noPoint.Fill(0); auto *doOp = new mitk::PointOperation(OpSELECTPOINT, timeInMS, noPoint, position); /*if ( m_UndoEnabled ) { mitk::PointOperation* undoOp = new mitk::PointOperation(OpDESELECTPOINT,timeInMS, noPoint, position); OperationEvent *operationEvent = new OperationEvent(pointSet, doOp, undoOp, "Select Point"); OperationEvent::IncCurrObjectEventId(); m_UndoController->SetOperationEvent(operationEvent); }*/ pointSet->ExecuteOperation(doOp); if (!m_UndoEnabled) delete doOp; } diff --git a/Modules/Core/src/Rendering/mitkBaseRenderer.cpp b/Modules/Core/src/Rendering/mitkBaseRenderer.cpp index ee62b659cd..58f2831295 100644 --- a/Modules/Core/src/Rendering/mitkBaseRenderer.cpp +++ b/Modules/Core/src/Rendering/mitkBaseRenderer.cpp @@ -1,784 +1,784 @@ /*============================================================================ 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 "mitkBaseRenderer.h" #include "mitkMapper.h" #include "mitkResliceMethodProperty.h" // Geometries #include "mitkPlaneGeometry.h" #include "mitkSlicedGeometry3D.h" // Controllers #include "mitkCameraController.h" #include "mitkCameraRotationController.h" #include "mitkSliceNavigationController.h" #include "mitkVtkLayerController.h" #include "mitkInteractionConst.h" #include "mitkProperties.h" #include "mitkWeakPointerProperty.h" // VTK #include #include #include #include #include #include #include mitk::BaseRenderer::BaseRendererMapType mitk::BaseRenderer::baseRendererMap; mitk::BaseRenderer *mitk::BaseRenderer::GetInstance(vtkRenderWindow *renWin) { for (auto mapit = baseRendererMap.begin(); mapit != baseRendererMap.end(); ++mapit) { if ((*mapit).first == renWin) return (*mapit).second; } return nullptr; } void mitk::BaseRenderer::AddInstance(vtkRenderWindow *renWin, BaseRenderer *baseRenderer) { if (renWin == nullptr || baseRenderer == nullptr) return; // ensure that no BaseRenderer is managed twice mitk::BaseRenderer::RemoveInstance(renWin); baseRendererMap.insert(BaseRendererMapType::value_type(renWin, baseRenderer)); } void mitk::BaseRenderer::RemoveInstance(vtkRenderWindow *renWin) { auto mapit = baseRendererMap.find(renWin); if (mapit != baseRendererMap.end()) baseRendererMap.erase(mapit); } mitk::BaseRenderer *mitk::BaseRenderer::GetByName(const std::string &name) { for (auto mapit = baseRendererMap.begin(); mapit != baseRendererMap.end(); ++mapit) { if ((*mapit).second->m_Name == name) return (*mapit).second; } return nullptr; } vtkRenderWindow *mitk::BaseRenderer::GetRenderWindowByName(const std::string &name) { for (auto mapit = baseRendererMap.begin(); mapit != baseRendererMap.end(); ++mapit) { if ((*mapit).second->m_Name == name) return (*mapit).first; } return nullptr; } mitk::BaseRenderer::BaseRenderer(const char *name, vtkRenderWindow *renWin) : m_RenderWindow(nullptr), m_VtkRenderer(nullptr), m_MapperID(defaultMapper), m_DataStorage(nullptr), m_LastUpdateTime(0), m_CameraController(nullptr), m_SliceNavigationController(nullptr), m_CameraRotationController(nullptr), m_WorldTimeGeometry(nullptr), m_CurrentWorldGeometry(nullptr), m_CurrentWorldPlaneGeometry(nullptr), m_Slice(0), m_TimeStep(), m_CurrentWorldPlaneGeometryUpdateTime(), m_TimeStepUpdateTime(), m_KeepDisplayedRegion(true), m_CurrentWorldPlaneGeometryData(nullptr), m_CurrentWorldPlaneGeometryNode(nullptr), m_CurrentWorldPlaneGeometryTransformTime(0), m_Name(name), m_EmptyWorldGeometry(true), m_NumberOfVisibleLODEnabledMappers(0) { m_Bounds[0] = 0; m_Bounds[1] = 0; m_Bounds[2] = 0; m_Bounds[3] = 0; m_Bounds[4] = 0; m_Bounds[5] = 0; if (name != nullptr) { m_Name = name; } else { m_Name = "unnamed renderer"; itkWarningMacro(<< "Created unnamed renderer. Bad for serialization. Please choose a name."); } if (renWin != nullptr) { m_RenderWindow = renWin; m_RenderWindow->Register(nullptr); } else { itkWarningMacro(<< "Created mitkBaseRenderer without vtkRenderWindow present."); } // instances.insert( this ); // adding this BaseRenderer to the List of all BaseRenderer m_BindDispatcherInteractor = new mitk::BindDispatcherInteractor(GetName()); WeakPointerProperty::Pointer rendererProp = WeakPointerProperty::New((itk::Object *)this); m_CurrentWorldPlaneGeometry = mitk::PlaneGeometry::New(); m_CurrentWorldPlaneGeometryData = mitk::PlaneGeometryData::New(); m_CurrentWorldPlaneGeometryData->SetPlaneGeometry(m_CurrentWorldPlaneGeometry); m_CurrentWorldPlaneGeometryNode = mitk::DataNode::New(); m_CurrentWorldPlaneGeometryNode->SetData(m_CurrentWorldPlaneGeometryData); m_CurrentWorldPlaneGeometryNode->GetPropertyList()->SetProperty("renderer", rendererProp); m_CurrentWorldPlaneGeometryNode->GetPropertyList()->SetProperty("layer", IntProperty::New(1000)); m_CurrentWorldPlaneGeometryNode->SetProperty("reslice.thickslices", mitk::ResliceMethodProperty::New()); m_CurrentWorldPlaneGeometryNode->SetProperty("reslice.thickslices.num", mitk::IntProperty::New(1)); m_CurrentWorldPlaneGeometryTransformTime = m_CurrentWorldPlaneGeometryNode->GetVtkTransform()->GetMTime(); mitk::SliceNavigationController::Pointer sliceNavigationController = mitk::SliceNavigationController::New(); sliceNavigationController->SetRenderer(this); sliceNavigationController->ConnectGeometrySliceEvent(this); sliceNavigationController->ConnectGeometryUpdateEvent(this); sliceNavigationController->ConnectGeometryTimeEvent(this, false); m_SliceNavigationController = sliceNavigationController; m_CameraRotationController = mitk::CameraRotationController::New(); m_CameraRotationController->SetRenderWindow(m_RenderWindow); m_CameraRotationController->AcquireCamera(); m_CameraController = mitk::CameraController::New(); m_CameraController->SetRenderer(this); m_VtkRenderer = vtkRenderer::New(); m_VtkRenderer->SetMaximumNumberOfPeels(16); if (AntiAliasing::FastApproximate == RenderingManager::GetInstance()->GetAntiAliasing()) m_VtkRenderer->UseFXAAOn(); if (nullptr == mitk::VtkLayerController::GetInstance(m_RenderWindow)) mitk::VtkLayerController::AddInstance(m_RenderWindow, m_VtkRenderer); mitk::VtkLayerController::GetInstance(m_RenderWindow)->InsertSceneRenderer(m_VtkRenderer); } mitk::BaseRenderer::~BaseRenderer() { if (m_VtkRenderer != nullptr) { m_VtkRenderer->Delete(); m_VtkRenderer = nullptr; } if (m_CameraController.IsNotNull()) m_CameraController->SetRenderer(nullptr); mitk::VtkLayerController::RemoveInstance(m_RenderWindow); RemoveAllLocalStorages(); m_DataStorage = nullptr; if (m_BindDispatcherInteractor != nullptr) { delete m_BindDispatcherInteractor; } if (m_RenderWindow != nullptr) { m_RenderWindow->Delete(); m_RenderWindow = nullptr; } } void mitk::BaseRenderer::SetMapperID(MapperSlotId id) { if (m_MapperID != id) { bool useDepthPeeling = Standard3D == id; m_VtkRenderer->SetUseDepthPeeling(useDepthPeeling); m_VtkRenderer->SetUseDepthPeelingForVolumes(useDepthPeeling); m_MapperID = id; this->Modified(); } } void mitk::BaseRenderer::RemoveAllLocalStorages() { this->InvokeEvent(mitk::BaseRenderer::RendererResetEvent()); std::list::iterator it; for (it = m_RegisteredLocalStorageHandlers.begin(); it != m_RegisteredLocalStorageHandlers.end(); ++it) (*it)->ClearLocalStorage(this, false); m_RegisteredLocalStorageHandlers.clear(); } void mitk::BaseRenderer::RegisterLocalStorageHandler(mitk::BaseLocalStorageHandler *lsh) { m_RegisteredLocalStorageHandlers.push_back(lsh); } mitk::Dispatcher::Pointer mitk::BaseRenderer::GetDispatcher() const { return m_BindDispatcherInteractor->GetDispatcher(); } void mitk::BaseRenderer::UnregisterLocalStorageHandler(mitk::BaseLocalStorageHandler *lsh) { m_RegisteredLocalStorageHandlers.remove(lsh); } void mitk::BaseRenderer::SetDataStorage(DataStorage *storage) { if (storage != m_DataStorage && storage != nullptr) { m_DataStorage = storage; m_BindDispatcherInteractor->SetDataStorage(m_DataStorage); this->Modified(); } } const mitk::BaseRenderer::MapperSlotId mitk::BaseRenderer::defaultMapper = 1; void mitk::BaseRenderer::Paint() { } void mitk::BaseRenderer::Initialize() { } void mitk::BaseRenderer::Resize(int w, int h) { this->m_RenderWindow->SetSize(w, h); } void mitk::BaseRenderer::InitRenderer(vtkRenderWindow *renderwindow) { if (m_RenderWindow != renderwindow) { if (m_RenderWindow != nullptr) { m_RenderWindow->Delete(); } m_RenderWindow = renderwindow; if (m_RenderWindow != nullptr) { m_RenderWindow->Register(nullptr); } } RemoveAllLocalStorages(); if (m_CameraController.IsNotNull()) { m_CameraController->SetRenderer(this); } } void mitk::BaseRenderer::InitSize(int w, int h) { this->m_RenderWindow->SetSize(w, h); } void mitk::BaseRenderer::SetSlice(unsigned int slice) { if (m_Slice != slice) { m_Slice = slice; if (m_WorldTimeGeometry.IsNotNull()) { // get world geometry which may be rotated, for the current time step SlicedGeometry3D *slicedWorldGeometry = dynamic_cast(m_WorldTimeGeometry->GetGeometryForTimeStep(m_TimeStep).GetPointer()); if (slicedWorldGeometry != nullptr) { // if slice position is part of the world geometry... if (m_Slice >= slicedWorldGeometry->GetSlices()) // set the current worldplanegeomety as the selected 2D slice of the world geometry m_Slice = slicedWorldGeometry->GetSlices() - 1; SetCurrentWorldPlaneGeometry(slicedWorldGeometry->GetPlaneGeometry(m_Slice)); SetCurrentWorldGeometry(slicedWorldGeometry); } } else Modified(); } } void mitk::BaseRenderer::SetTimeStep(unsigned int timeStep) { if (m_TimeStep != timeStep) { m_TimeStep = timeStep; m_TimeStepUpdateTime.Modified(); if (m_WorldTimeGeometry.IsNotNull()) { if (m_TimeStep >= m_WorldTimeGeometry->CountTimeSteps()) m_TimeStep = m_WorldTimeGeometry->CountTimeSteps() - 1; SlicedGeometry3D *slicedWorldGeometry = dynamic_cast(m_WorldTimeGeometry->GetGeometryForTimeStep(m_TimeStep).GetPointer()); if (slicedWorldGeometry != nullptr) { SetCurrentWorldPlaneGeometry(slicedWorldGeometry->GetPlaneGeometry(m_Slice)); SetCurrentWorldGeometry(slicedWorldGeometry); } } else Modified(); } } -int mitk::BaseRenderer::GetTimeStep(const mitk::BaseData *data) const +mitk::TimeStepType mitk::BaseRenderer::GetTimeStep(const mitk::BaseData *data) const { if ((data == nullptr) || (data->IsInitialized() == false)) { return -1; } return data->GetTimeGeometry()->TimePointToTimeStep(GetTime()); } mitk::ScalarType mitk::BaseRenderer::GetTime() const { if (m_WorldTimeGeometry.IsNull()) { return 0; } else { ScalarType timeInMS = m_WorldTimeGeometry->TimeStepToTimePoint(GetTimeStep()); if (timeInMS == itk::NumericTraits::NonpositiveMin()) return 0; else return timeInMS; } } void mitk::BaseRenderer::SetWorldTimeGeometry(const mitk::TimeGeometry *geometry) { assert(geometry != nullptr); itkDebugMacro("setting WorldTimeGeometry to " << geometry); if (m_WorldTimeGeometry != geometry) { if (geometry->GetBoundingBoxInWorld()->GetDiagonalLength2() == 0) return; m_WorldTimeGeometry = geometry; itkDebugMacro("setting WorldTimeGeometry to " << m_WorldTimeGeometry); if (m_TimeStep >= m_WorldTimeGeometry->CountTimeSteps()) m_TimeStep = m_WorldTimeGeometry->CountTimeSteps() - 1; BaseGeometry *geometry3d; geometry3d = m_WorldTimeGeometry->GetGeometryForTimeStep(m_TimeStep); SetWorldGeometry3D(geometry3d); } } void mitk::BaseRenderer::SetWorldGeometry3D(const mitk::BaseGeometry *geometry) { itkDebugMacro("setting WorldGeometry3D to " << geometry); if (geometry->GetBoundingBox()->GetDiagonalLength2() == 0) return; const SlicedGeometry3D *slicedWorldGeometry; slicedWorldGeometry = dynamic_cast(geometry); PlaneGeometry::ConstPointer geometry2d; if (slicedWorldGeometry != nullptr) { if (m_Slice >= slicedWorldGeometry->GetSlices() && (m_Slice != 0)) m_Slice = slicedWorldGeometry->GetSlices() - 1; geometry2d = slicedWorldGeometry->GetPlaneGeometry(m_Slice); if (geometry2d.IsNull()) { PlaneGeometry::Pointer plane = mitk::PlaneGeometry::New(); plane->InitializeStandardPlane(slicedWorldGeometry); geometry2d = plane; } SetCurrentWorldGeometry(slicedWorldGeometry); } else { geometry2d = dynamic_cast(geometry); if (geometry2d.IsNull()) { PlaneGeometry::Pointer plane = PlaneGeometry::New(); plane->InitializeStandardPlane(geometry); geometry2d = plane; } SetCurrentWorldGeometry(geometry); } SetCurrentWorldPlaneGeometry(geometry2d); // calls Modified() if (m_CurrentWorldPlaneGeometry.IsNull()) itkWarningMacro("m_CurrentWorldPlaneGeometry is nullptr"); } void mitk::BaseRenderer::SetCurrentWorldPlaneGeometry(const mitk::PlaneGeometry *geometry2d) { if (m_CurrentWorldPlaneGeometry != geometry2d) { m_CurrentWorldPlaneGeometry = geometry2d->Clone(); m_CurrentWorldPlaneGeometryData->SetPlaneGeometry(m_CurrentWorldPlaneGeometry); m_CurrentWorldPlaneGeometryUpdateTime.Modified(); Modified(); } } void mitk::BaseRenderer::SendUpdateSlice() { m_CurrentWorldPlaneGeometryUpdateTime.Modified(); } int *mitk::BaseRenderer::GetSize() const { return this->m_RenderWindow->GetSize(); } int *mitk::BaseRenderer::GetViewportSize() const { return this->m_VtkRenderer->GetSize(); } void mitk::BaseRenderer::SetCurrentWorldGeometry(const mitk::BaseGeometry *geometry) { m_CurrentWorldGeometry = geometry; if (geometry == nullptr) { m_Bounds[0] = 0; m_Bounds[1] = 0; m_Bounds[2] = 0; m_Bounds[3] = 0; m_Bounds[4] = 0; m_Bounds[5] = 0; m_EmptyWorldGeometry = true; return; } BoundingBox::Pointer boundingBox = m_CurrentWorldGeometry->CalculateBoundingBoxRelativeToTransform(nullptr); const BoundingBox::BoundsArrayType &worldBounds = boundingBox->GetBounds(); m_Bounds[0] = worldBounds[0]; m_Bounds[1] = worldBounds[1]; m_Bounds[2] = worldBounds[2]; m_Bounds[3] = worldBounds[3]; m_Bounds[4] = worldBounds[4]; m_Bounds[5] = worldBounds[5]; if (boundingBox->GetDiagonalLength2() <= mitk::eps) m_EmptyWorldGeometry = true; else m_EmptyWorldGeometry = false; } void mitk::BaseRenderer::SetGeometry(const itk::EventObject &geometrySendEvent) { const auto *sendEvent = dynamic_cast(&geometrySendEvent); assert(sendEvent != nullptr); SetWorldTimeGeometry(sendEvent->GetTimeGeometry()); } void mitk::BaseRenderer::UpdateGeometry(const itk::EventObject &geometryUpdateEvent) { const auto *updateEvent = dynamic_cast(&geometryUpdateEvent); if (updateEvent == nullptr) return; if (m_CurrentWorldGeometry.IsNotNull()) { auto *slicedWorldGeometry = dynamic_cast(m_CurrentWorldGeometry.GetPointer()); if (slicedWorldGeometry) { PlaneGeometry *geometry2D = slicedWorldGeometry->GetPlaneGeometry(m_Slice); SetCurrentWorldPlaneGeometry(geometry2D); // calls Modified() } } } void mitk::BaseRenderer::SetGeometrySlice(const itk::EventObject &geometrySliceEvent) { const auto *sliceEvent = dynamic_cast(&geometrySliceEvent); assert(sliceEvent != nullptr); SetSlice(sliceEvent->GetPos()); } void mitk::BaseRenderer::SetGeometryTime(const itk::EventObject &geometryTimeEvent) { const auto *timeEvent = dynamic_cast(&geometryTimeEvent); assert(timeEvent != nullptr); SetTimeStep(timeEvent->GetPos()); } const double *mitk::BaseRenderer::GetBounds() const { return m_Bounds; } void mitk::BaseRenderer::DrawOverlayMouse(mitk::Point2D &itkNotUsed(p2d)) { MITK_INFO << "BaseRenderer::DrawOverlayMouse()- should be inconcret implementation OpenGLRenderer." << std::endl; } void mitk::BaseRenderer::RequestUpdate() { SetConstrainZoomingAndPanning(true); RenderingManager::GetInstance()->RequestUpdate(this->m_RenderWindow); } void mitk::BaseRenderer::ForceImmediateUpdate() { RenderingManager::GetInstance()->ForceImmediateUpdate(this->m_RenderWindow); } unsigned int mitk::BaseRenderer::GetNumberOfVisibleLODEnabledMappers() const { return m_NumberOfVisibleLODEnabledMappers; } /*! Sets the new Navigation controller */ void mitk::BaseRenderer::SetSliceNavigationController(mitk::SliceNavigationController *SlicenavigationController) { if (SlicenavigationController == nullptr) return; // copy worldgeometry SlicenavigationController->SetInputWorldTimeGeometry(SlicenavigationController->GetCreatedWorldGeometry()); SlicenavigationController->Update(); // set new m_SliceNavigationController = SlicenavigationController; m_SliceNavigationController->SetRenderer(this); if (m_SliceNavigationController.IsNotNull()) { m_SliceNavigationController->ConnectGeometrySliceEvent(this); m_SliceNavigationController->ConnectGeometryUpdateEvent(this); m_SliceNavigationController->ConnectGeometryTimeEvent(this, false); } } void mitk::BaseRenderer::DisplayToWorld(const Point2D &displayPoint, Point3D &worldIndex) const { if (m_MapperID == BaseRenderer::Standard2D) { double display[3], *world; // For the rigth z-position in display coordinates, take the focal point, convert it to display and use it for // correct depth. double *displayCoord; double cameraFP[4]; // Get camera focal point and position. Convert to display (screen) // coordinates. We need a depth value for z-buffer. this->GetVtkRenderer()->GetActiveCamera()->GetFocalPoint(cameraFP); cameraFP[3] = 0.0; this->GetVtkRenderer()->SetWorldPoint(cameraFP[0], cameraFP[1], cameraFP[2], cameraFP[3]); this->GetVtkRenderer()->WorldToDisplay(); displayCoord = this->GetVtkRenderer()->GetDisplayPoint(); // now convert the display point to world coordinates display[0] = displayPoint[0]; display[1] = displayPoint[1]; display[2] = displayCoord[2]; this->GetVtkRenderer()->SetDisplayPoint(display); this->GetVtkRenderer()->DisplayToWorld(); world = this->GetVtkRenderer()->GetWorldPoint(); for (int i = 0; i < 3; i++) { worldIndex[i] = world[i] / world[3]; } } else if (m_MapperID == BaseRenderer::Standard3D) { PickWorldPoint( displayPoint, worldIndex); // Seems to be the same code as above, but subclasses may contain different implementations. } return; } void mitk::BaseRenderer::DisplayToPlane(const Point2D &displayPoint, Point2D &planePointInMM) const { if (m_MapperID == BaseRenderer::Standard2D) { Point3D worldPoint; this->DisplayToWorld(displayPoint, worldPoint); this->m_CurrentWorldPlaneGeometry->Map(worldPoint, planePointInMM); } else if (m_MapperID == BaseRenderer::Standard3D) { MITK_WARN << "No conversion possible with 3D mapper."; return; } return; } void mitk::BaseRenderer::WorldToDisplay(const Point3D &worldIndex, Point2D &displayPoint) const { double world[4], *display; world[0] = worldIndex[0]; world[1] = worldIndex[1]; world[2] = worldIndex[2]; world[3] = 1.0; this->GetVtkRenderer()->SetWorldPoint(world); this->GetVtkRenderer()->WorldToDisplay(); display = this->GetVtkRenderer()->GetDisplayPoint(); displayPoint[0] = display[0]; displayPoint[1] = display[1]; return; } void mitk::BaseRenderer::WorldToView(const mitk::Point3D &worldIndex, mitk::Point2D &viewPoint) const { double world[4], *view; world[0] = worldIndex[0]; world[1] = worldIndex[1]; world[2] = worldIndex[2]; world[3] = 1.0; this->GetVtkRenderer()->SetWorldPoint(world); this->GetVtkRenderer()->WorldToView(); view = this->GetVtkRenderer()->GetViewPoint(); this->GetVtkRenderer()->ViewToNormalizedViewport(view[0], view[1], view[2]); viewPoint[0] = view[0] * this->GetViewportSize()[0]; viewPoint[1] = view[1] * this->GetViewportSize()[1]; return; } void mitk::BaseRenderer::PlaneToDisplay(const Point2D &planePointInMM, Point2D &displayPoint) const { Point3D worldPoint; this->m_CurrentWorldPlaneGeometry->Map(planePointInMM, worldPoint); this->WorldToDisplay(worldPoint, displayPoint); return; } void mitk::BaseRenderer::PlaneToView(const Point2D &planePointInMM, Point2D &viewPoint) const { Point3D worldPoint; this->m_CurrentWorldPlaneGeometry->Map(planePointInMM, worldPoint); this->WorldToView(worldPoint,viewPoint); return; } double mitk::BaseRenderer::GetScaleFactorMMPerDisplayUnit() const { if (this->GetMapperID() == BaseRenderer::Standard2D) { // GetParallelScale returns half of the height of the render window in mm. // Divided by the half size of the Display size in pixel givest the mm per pixel. return this->GetVtkRenderer()->GetActiveCamera()->GetParallelScale() * 2.0 / GetViewportSize()[1]; } else return 1.0; } mitk::Point2D mitk::BaseRenderer::GetDisplaySizeInMM() const { Point2D dispSizeInMM; dispSizeInMM[0] = GetSizeX() * GetScaleFactorMMPerDisplayUnit(); dispSizeInMM[1] = GetSizeY() * GetScaleFactorMMPerDisplayUnit(); return dispSizeInMM; } mitk::Point2D mitk::BaseRenderer::GetViewportSizeInMM() const { Point2D dispSizeInMM; dispSizeInMM[0] = GetViewportSize()[0] * GetScaleFactorMMPerDisplayUnit(); dispSizeInMM[1] = GetViewportSize()[1] * GetScaleFactorMMPerDisplayUnit(); return dispSizeInMM; } mitk::Point2D mitk::BaseRenderer::GetOriginInMM() const { Point2D originPx; originPx[0] = m_VtkRenderer->GetOrigin()[0]; originPx[1] = m_VtkRenderer->GetOrigin()[1]; Point2D displayGeometryOriginInMM; DisplayToPlane(originPx, displayGeometryOriginInMM); // top left of the render window (Origin) return displayGeometryOriginInMM; } void mitk::BaseRenderer::SetConstrainZoomingAndPanning(bool constrain) { m_ConstrainZoomingAndPanning = constrain; if (m_ConstrainZoomingAndPanning) { this->GetCameraController()->AdjustCameraToPlane(); } } mitk::Point3D mitk::BaseRenderer::Map2DRendererPositionTo3DWorldPosition(const Point2D &mousePosition) const { // DEPRECATED: Map2DRendererPositionTo3DWorldPosition is deprecated. use DisplayToWorldInstead Point3D position; DisplayToWorld(mousePosition, position); return position; } void mitk::BaseRenderer::PrintSelf(std::ostream &os, itk::Indent indent) const { os << indent << " MapperID: " << m_MapperID << std::endl; os << indent << " Slice: " << m_Slice << std::endl; os << indent << " TimeStep: " << m_TimeStep << std::endl; os << indent << " CurrentWorldPlaneGeometry: "; if (m_CurrentWorldPlaneGeometry.IsNull()) os << "nullptr" << std::endl; else m_CurrentWorldPlaneGeometry->Print(os, indent); os << indent << " CurrentWorldPlaneGeometryUpdateTime: " << m_CurrentWorldPlaneGeometryUpdateTime << std::endl; os << indent << " CurrentWorldPlaneGeometryTransformTime: " << m_CurrentWorldPlaneGeometryTransformTime << std::endl; Superclass::PrintSelf(os, indent); } diff --git a/Modules/DataTypesExt/include/mitkCompressedImageContainer.h b/Modules/DataTypesExt/include/mitkCompressedImageContainer.h index 71d5d9878e..32ead859c3 100644 --- a/Modules/DataTypesExt/include/mitkCompressedImageContainer.h +++ b/Modules/DataTypesExt/include/mitkCompressedImageContainer.h @@ -1,80 +1,80 @@ /*============================================================================ 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 mitkCompressedImageContainer_h_Included #define mitkCompressedImageContainer_h_Included #include "MitkDataTypesExtExports.h" #include "mitkCommon.h" #include "mitkGeometry3D.h" #include "mitkImage.h" #include "mitkImageDataItem.h" #include #include namespace mitk { /** \brief Holds one (compressed) mitk::Image Uses zlib to compress the data of an mitk::Image. $Author$ */ class MITKDATATYPESEXT_EXPORT CompressedImageContainer : public itk::Object { public: mitkClassMacroItkParent(CompressedImageContainer, itk::Object); itkFactorylessNewMacro(Self); itkCloneMacro(Self); /** * \brief Creates a compressed version of the image. * * Will not hold any further SmartPointers to the image. * */ - void SetImage(Image *); + void SetImage(const Image *); /** * \brief Creates a full mitk::Image from its compressed version. * * This Method hold no buffer, so the uncompression algorithm will be * executed every time you call this method. Don't overdo it. * */ - Image::Pointer GetImage(); + Image::Pointer GetImage() const; protected: CompressedImageContainer(); // purposely hidden ~CompressedImageContainer() override; PixelType *m_PixelType; unsigned int m_ImageDimension; std::vector m_ImageDimensions; unsigned long m_OneTimeStepImageSizeInBytes; unsigned int m_NumberOfTimeSteps; /// one for each timestep. first = pointer to compressed data; second = size of buffer in bytes std::vector> m_ByteBuffers; BaseGeometry::Pointer m_ImageGeometry; }; } // namespace #endif diff --git a/Modules/DataTypesExt/src/mitkCompressedImageContainer.cpp b/Modules/DataTypesExt/src/mitkCompressedImageContainer.cpp index bfad62d7ef..7d11a9e9a9 100644 --- a/Modules/DataTypesExt/src/mitkCompressedImageContainer.cpp +++ b/Modules/DataTypesExt/src/mitkCompressedImageContainer.cpp @@ -1,178 +1,178 @@ /*============================================================================ 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 "mitkCompressedImageContainer.h" #include "mitkImageReadAccessor.h" #include "itk_zlib.h" #include mitk::CompressedImageContainer::CompressedImageContainer() : m_PixelType(nullptr), m_ImageGeometry(nullptr) { } mitk::CompressedImageContainer::~CompressedImageContainer() { for (auto iter = m_ByteBuffers.begin(); iter != m_ByteBuffers.end(); ++iter) { free(iter->first); } delete m_PixelType; } -void mitk::CompressedImageContainer::SetImage(Image *image) +void mitk::CompressedImageContainer::SetImage(const Image *image) { for (auto iter = m_ByteBuffers.begin(); iter != m_ByteBuffers.end(); ++iter) { free(iter->first); } m_ByteBuffers.clear(); // Compress diff image using zlib (will be restored on demand) // determine memory size occupied by voxel data m_ImageDimension = image->GetDimension(); m_ImageDimensions.clear(); m_PixelType = new mitk::PixelType(image->GetPixelType()); m_OneTimeStepImageSizeInBytes = m_PixelType->GetSize(); // bits per element divided by 8 for (unsigned int i = 0; i < m_ImageDimension; ++i) { unsigned int currentImageDimension = image->GetDimension(i); m_ImageDimensions.push_back(currentImageDimension); if (i < 3) { m_OneTimeStepImageSizeInBytes *= currentImageDimension; // only the 3D memory size } } m_ImageGeometry = image->GetGeometry(); m_NumberOfTimeSteps = 1; if (m_ImageDimension > 3) { m_NumberOfTimeSteps = image->GetDimension(3); } for (unsigned int timestep = 0; timestep < m_NumberOfTimeSteps; ++timestep) { // allocate a buffer as specified by zlib unsigned long bufferSize = m_OneTimeStepImageSizeInBytes + static_cast(m_OneTimeStepImageSizeInBytes * 0.2) + 12; auto *byteBuffer = (unsigned char *)malloc(bufferSize); if (itk::Object::GetDebug()) { // compress image here into a buffer MITK_INFO << "Using ZLib version: '" << zlibVersion() << "'" << std::endl << "Attempting to compress " << m_OneTimeStepImageSizeInBytes << " image bytes into a buffer of size " << bufferSize << std::endl; } ImageReadAccessor imgAcc(image, image->GetVolumeData(timestep)); ::Bytef *dest(byteBuffer); ::uLongf destLen(bufferSize); auto *source((unsigned char *)imgAcc.GetData()); ::uLongf sourceLen(m_OneTimeStepImageSizeInBytes); int zlibRetVal = ::compress(dest, &destLen, source, sourceLen); if (itk::Object::GetDebug()) { if (zlibRetVal == Z_OK) { MITK_INFO << "Success, using " << destLen << " bytes of the buffer (ratio " << ((double)destLen / (double)sourceLen) << ")" << std::endl; } else { switch (zlibRetVal) { case Z_MEM_ERROR: MITK_ERROR << "not enough memory" << std::endl; break; case Z_BUF_ERROR: MITK_ERROR << "output buffer too small" << std::endl; break; default: MITK_ERROR << "other, unspecified error" << std::endl; break; } } } // only use the neccessary amount of memory, realloc the buffer! byteBuffer = (unsigned char *)realloc(byteBuffer, destLen); bufferSize = destLen; // MITK_INFO << "Using " << bufferSize << " bytes to store compressed image (" << destLen << " needed)" << // std::endl; m_ByteBuffers.push_back(std::pair(byteBuffer, bufferSize)); } } -mitk::Image::Pointer mitk::CompressedImageContainer::GetImage() +mitk::Image::Pointer mitk::CompressedImageContainer::GetImage() const { if (m_ByteBuffers.empty()) return nullptr; // uncompress image data, create an Image Image::Pointer image = Image::New(); unsigned int dims[20]; // more than 20 dimensions and bang for (unsigned int dim = 0; dim < m_ImageDimension; ++dim) dims[dim] = m_ImageDimensions[dim]; image->Initialize(*m_PixelType, m_ImageDimension, dims); // this IS needed, right ?? But it does allocate memory -> // does create one big lump of memory (also in windows) unsigned int timeStep(0); for (auto iter = m_ByteBuffers.begin(); iter != m_ByteBuffers.end(); ++iter, ++timeStep) { ImageReadAccessor imgAcc(image, image->GetVolumeData(timeStep)); auto *dest((unsigned char *)imgAcc.GetData()); ::uLongf destLen(m_OneTimeStepImageSizeInBytes); ::Bytef *source(iter->first); ::uLongf sourceLen(iter->second); int zlibRetVal = ::uncompress(dest, &destLen, source, sourceLen); if (itk::Object::GetDebug()) { if (zlibRetVal == Z_OK) { MITK_INFO << "Success, destLen now " << destLen << " bytes" << std::endl; } else { switch (zlibRetVal) { case Z_DATA_ERROR: MITK_ERROR << "compressed data corrupted" << std::endl; break; case Z_MEM_ERROR: MITK_ERROR << "not enough memory" << std::endl; break; case Z_BUF_ERROR: MITK_ERROR << "output buffer too small" << std::endl; break; default: MITK_ERROR << "other, unspecified error" << std::endl; break; } } } } image->SetGeometry(m_ImageGeometry); image->Modified(); return image; } diff --git a/Modules/Gizmo/src/mitkGizmoInteractor.cpp b/Modules/Gizmo/src/mitkGizmoInteractor.cpp index a682f70a24..7749ad82de 100644 --- a/Modules/Gizmo/src/mitkGizmoInteractor.cpp +++ b/Modules/Gizmo/src/mitkGizmoInteractor.cpp @@ -1,465 +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 "mitkGizmoInteractor.h" #include "mitkGizmoMapper2D.h" // MITK includes #include #include #include #include #include #include #include #include #include #include // VTK includes #include #include #include #include #include #include #include #include #include mitk::GizmoInteractor::GizmoInteractor() { m_ColorForHighlight[0] = 1.0; m_ColorForHighlight[1] = 0.5; m_ColorForHighlight[2] = 0.0; m_ColorForHighlight[3] = 1.0; // TODO if we want to get this configurable, the this is the recipe: // - make the 2D mapper add corresponding properties to control "enabled" and "color" // - make the interactor evaluate those properties // - in an ideal world, modify the state machine on the fly and skip mouse move handling } mitk::GizmoInteractor::~GizmoInteractor() { } void mitk::GizmoInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("PickedHandle", HasPickedHandle); CONNECT_FUNCTION("DecideInteraction", DecideInteraction); CONNECT_FUNCTION("MoveAlongAxis", MoveAlongAxis); CONNECT_FUNCTION("RotateAroundAxis", RotateAroundAxis); CONNECT_FUNCTION("MoveFreely", MoveFreely); CONNECT_FUNCTION("ScaleEqually", ScaleEqually); CONNECT_FUNCTION("FeedUndoStack", FeedUndoStack); } void mitk::GizmoInteractor::SetGizmoNode(DataNode *node) { DataInteractor::SetDataNode(node); m_Gizmo = dynamic_cast(node->GetData()); // setup picking from just this object m_Picker.clear(); } void mitk::GizmoInteractor::SetManipulatedObjectNode(DataNode *node) { if (node && node->GetData()) { m_ManipulatedObjectGeometry = node->GetData()->GetGeometry(); } } bool mitk::GizmoInteractor::HasPickedHandle(const InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr || m_Gizmo.IsNull() || m_ManipulatedObjectGeometry.IsNull() || interactionEvent->GetSender()->GetRenderWindow()->GetNeverRendered()) { return false; } if (interactionEvent->GetSender()->GetMapperID() == BaseRenderer::Standard2D) { m_PickedHandle = PickFrom2D(positionEvent); } else { m_PickedHandle = PickFrom3D(positionEvent); } UpdateHandleHighlight(); return m_PickedHandle != Gizmo::NoHandle; } void mitk::GizmoInteractor::DecideInteraction(StateMachineAction *, InteractionEvent *interactionEvent) { assert(m_PickedHandle != Gizmo::NoHandle); auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) { return; } // if something relevant was picked, we calculate a number of // important points and axes for the upcoming geometry manipulations // note initial state m_InitialClickPosition2D = positionEvent->GetPointerPositionOnScreen(); m_InitialClickPosition3D = positionEvent->GetPositionInWorld(); auto renderer = positionEvent->GetSender()->GetVtkRenderer(); renderer->SetWorldPoint(m_InitialClickPosition3D[0], m_InitialClickPosition3D[1], m_InitialClickPosition3D[2], 0); renderer->WorldToDisplay(); m_InitialClickPosition2DZ = renderer->GetDisplayPoint()[2]; m_InitialGizmoCenter3D = m_Gizmo->GetCenter(); positionEvent->GetSender()->WorldToDisplay(m_InitialGizmoCenter3D, m_InitialGizmoCenter2D); m_InitialManipulatedObjectGeometry = m_ManipulatedObjectGeometry->Clone(); switch ( m_PickedHandle ) { case Gizmo::MoveAlongAxisX: case Gizmo::RotateAroundAxisX: case Gizmo::ScaleX: m_AxisOfMovement = m_InitialManipulatedObjectGeometry->GetAxisVector(0); break; case Gizmo::MoveAlongAxisY: case Gizmo::RotateAroundAxisY: case Gizmo::ScaleY: m_AxisOfMovement = m_InitialManipulatedObjectGeometry->GetAxisVector(1); break; case Gizmo::MoveAlongAxisZ: case Gizmo::RotateAroundAxisZ: case Gizmo::ScaleZ: m_AxisOfMovement = m_InitialManipulatedObjectGeometry->GetAxisVector(2); break; default: break; } m_AxisOfMovement.Normalize(); m_AxisOfRotation = m_AxisOfMovement; // for translation: test whether the user clicked into the "object's real" axis direction // or into the other one Vector3D intendedAxis = m_InitialClickPosition3D - m_InitialGizmoCenter3D; if ( intendedAxis * m_AxisOfMovement < 0 ) { m_AxisOfMovement *= -1.0; } // for rotation: test whether the axis of rotation is more looking in the direction // of the camera or in the opposite vtkCamera *camera = renderer->GetActiveCamera(); vtkVector3d cameraDirection(camera->GetDirectionOfProjection()); double angle_rad = vtkMath::AngleBetweenVectors(cameraDirection.GetData(), m_AxisOfRotation.GetDataPointer()); if ( angle_rad < vtkMath::Pi() / 2.0 ) { m_AxisOfRotation *= -1.0; } InternalEvent::Pointer decision; switch (m_PickedHandle) { case Gizmo::MoveAlongAxisX: case Gizmo::MoveAlongAxisY: case Gizmo::MoveAlongAxisZ: decision = InternalEvent::New(interactionEvent->GetSender(), this, "StartTranslationAlongAxis"); break; case Gizmo::RotateAroundAxisX: case Gizmo::RotateAroundAxisY: case Gizmo::RotateAroundAxisZ: decision = InternalEvent::New(interactionEvent->GetSender(), this, "StartRotationAroundAxis"); break; case Gizmo::MoveFreely: decision = InternalEvent::New(interactionEvent->GetSender(), this, "MoveFreely"); break; case Gizmo::ScaleX: case Gizmo::ScaleY: case Gizmo::ScaleZ: // Implementation note: Why didn't we implement per-axis scaling yet? // When this was implemented, the mitk::ScaleOperation was able to only describe // uniform scaling around a central point. Since we use operations for all modifications // in order to have undo/redo working, any axis-specific scaling should also // use operations. // Consequence: enhance ScaleOperation when there is need to have scaling per axis. decision = InternalEvent::New(interactionEvent->GetSender(), this, "ScaleEqually"); break; default: break; } interactionEvent->GetSender()->GetDispatcher()->QueueEvent(decision); } void mitk::GizmoInteractor::MoveAlongAxis(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) { return; } Point2D currentPosition2D = positionEvent->GetPointerPositionOnScreen(); // re-use the initial z value to calculate movements parallel to the camera plane auto renderer = positionEvent->GetSender()->GetVtkRenderer(); renderer->SetDisplayPoint(currentPosition2D[0], currentPosition2D[1], m_InitialClickPosition2DZ); renderer->DisplayToWorld(); vtkVector3d worldPointVTK(renderer->GetWorldPoint()); Point3D worldPointITK(worldPointVTK.GetData()); Vector3D freeMouseMovement3D(worldPointITK - m_InitialClickPosition3D); double projectedMouseMovement3D = freeMouseMovement3D * m_AxisOfMovement; Vector3D appliedMovement3D = projectedMouseMovement3D * m_AxisOfMovement; ApplyTranslationToManipulatedObject(appliedMovement3D); RenderingManager::GetInstance()->ForceImmediateUpdateAll(); } void mitk::GizmoInteractor::RotateAroundAxis(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) { return; } Vector2D originalVector = m_InitialClickPosition2D - m_InitialGizmoCenter2D; Vector2D currentVector = positionEvent->GetPointerPositionOnScreen() - m_InitialGizmoCenter2D; originalVector.Normalize(); currentVector.Normalize(); double angle_rad = std::atan2(currentVector[1], currentVector[0]) - std::atan2(originalVector[1], originalVector[0]); ApplyRotationToManipulatedObject(vtkMath::DegreesFromRadians(angle_rad)); RenderingManager::GetInstance()->ForceImmediateUpdateAll(); } void mitk::GizmoInteractor::MoveFreely(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) { return; } Point2D currentPosition2D = positionEvent->GetPointerPositionOnScreen(); // re-use the initial z value to really move parallel to the camera plane auto renderer = positionEvent->GetSender()->GetVtkRenderer(); renderer->SetDisplayPoint(currentPosition2D[0], currentPosition2D[1], m_InitialClickPosition2DZ); renderer->DisplayToWorld(); vtkVector3d worldPointVTK(renderer->GetWorldPoint()); Point3D worldPointITK(worldPointVTK.GetData()); Vector3D movementITK(worldPointITK - m_InitialClickPosition3D); ApplyTranslationToManipulatedObject(movementITK); RenderingManager::GetInstance()->ForceImmediateUpdateAll(); } void mitk::GizmoInteractor::ScaleEqually(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (positionEvent == nullptr) { return; } Point2D currentPosition2D = positionEvent->GetPointerPositionOnScreen(); double relativeSize = (currentPosition2D - m_InitialGizmoCenter2D).GetNorm() / (m_InitialClickPosition2D - m_InitialGizmoCenter2D).GetNorm(); ApplyEqualScalingToManipulatedObject(relativeSize); RenderingManager::GetInstance()->ForceImmediateUpdateAll(); } void mitk::GizmoInteractor::ApplyTranslationToManipulatedObject(const Vector3D &translation) { assert(m_ManipulatedObjectGeometry.IsNotNull()); auto manipulatedGeometry = m_InitialManipulatedObjectGeometry->Clone(); - m_FinalDoOperation.reset(new PointOperation(OpMOVE, translation)); + m_FinalDoOperation.reset(new PointOperation(OpMOVE, Point3D(translation))); if (m_UndoEnabled) { - m_FinalUndoOperation.reset(new PointOperation(OpMOVE, -translation)); + m_FinalUndoOperation.reset(new PointOperation(OpMOVE, Point3D(-translation))); } manipulatedGeometry->ExecuteOperation(m_FinalDoOperation.get()); m_ManipulatedObjectGeometry->SetIndexToWorldTransform(manipulatedGeometry->GetIndexToWorldTransform()); } void mitk::GizmoInteractor::ApplyEqualScalingToManipulatedObject(double scalingFactor) { assert(m_ManipulatedObjectGeometry.IsNotNull()); auto manipulatedGeometry = m_InitialManipulatedObjectGeometry->Clone(); m_FinalDoOperation.reset(new ScaleOperation(OpSCALE, scalingFactor - 1.0, m_InitialGizmoCenter3D)); if (m_UndoEnabled) { m_FinalUndoOperation.reset(new ScaleOperation(OpSCALE, -(scalingFactor - 1.0), m_InitialGizmoCenter3D)); } manipulatedGeometry->ExecuteOperation(m_FinalDoOperation.get()); m_ManipulatedObjectGeometry->SetIndexToWorldTransform(manipulatedGeometry->GetIndexToWorldTransform()); } void mitk::GizmoInteractor::ApplyRotationToManipulatedObject(double angle_deg) { assert(m_ManipulatedObjectGeometry.IsNotNull()); auto manipulatedGeometry = m_InitialManipulatedObjectGeometry->Clone(); m_FinalDoOperation.reset(new RotationOperation(OpROTATE, m_InitialGizmoCenter3D, m_AxisOfRotation, angle_deg)); if (m_UndoEnabled) { m_FinalUndoOperation.reset(new RotationOperation(OpROTATE, m_InitialGizmoCenter3D, m_AxisOfRotation, -angle_deg)); } manipulatedGeometry->ExecuteOperation(m_FinalDoOperation.get()); m_ManipulatedObjectGeometry->SetIndexToWorldTransform(manipulatedGeometry->GetIndexToWorldTransform()); } void mitk::GizmoInteractor::FeedUndoStack(StateMachineAction *, InteractionEvent *) { if (m_UndoEnabled) { OperationEvent *operationEvent = new OperationEvent(m_ManipulatedObjectGeometry, // OperationEvent will destroy operations! // --> release() and not get() m_FinalDoOperation.release(), m_FinalUndoOperation.release(), "Direct geometry manipulation"); mitk::OperationEvent::IncCurrObjectEventId(); // save each modification individually m_UndoController->SetOperationEvent(operationEvent); } } mitk::Gizmo::HandleType mitk::GizmoInteractor::PickFrom2D(const InteractionPositionEvent *positionEvent) { BaseRenderer *renderer = positionEvent->GetSender(); auto mapper = GetDataNode()->GetMapper(BaseRenderer::Standard2D); auto gizmo_mapper = dynamic_cast(mapper); auto &picker = m_Picker[renderer]; if (picker == nullptr) { picker = vtkSmartPointer::New(); picker->SetTolerance(0.005); if (gizmo_mapper) { // doing this each time is bizarre picker->AddPickList(gizmo_mapper->GetVtkProp(renderer)); picker->PickFromListOn(); } } auto displayPosition = positionEvent->GetPointerPositionOnScreen(); picker->Pick(displayPosition[0], displayPosition[1], 0, positionEvent->GetSender()->GetVtkRenderer()); vtkIdType pickedPointID = picker->GetPointId(); if (pickedPointID == -1) { return Gizmo::NoHandle; } vtkPolyData *polydata = gizmo_mapper->GetVtkPolyData(renderer); if (polydata && polydata->GetPointData() && polydata->GetPointData()->GetScalars()) { double dataValue = polydata->GetPointData()->GetScalars()->GetTuple1(pickedPointID); return m_Gizmo->GetHandleFromPointDataValue(dataValue); } return Gizmo::NoHandle; } mitk::Gizmo::HandleType mitk::GizmoInteractor::PickFrom3D(const InteractionPositionEvent *positionEvent) { BaseRenderer *renderer = positionEvent->GetSender(); auto &picker = m_Picker[renderer]; if (picker == nullptr) { picker = vtkSmartPointer::New(); picker->SetTolerance(0.005); auto mapper = GetDataNode()->GetMapper(BaseRenderer::Standard3D); auto vtk_mapper = dynamic_cast(mapper); if (vtk_mapper) { // doing this each time is bizarre picker->AddPickList(vtk_mapper->GetVtkProp(renderer)); picker->PickFromListOn(); } } auto displayPosition = positionEvent->GetPointerPositionOnScreen(); picker->Pick(displayPosition[0], displayPosition[1], 0, positionEvent->GetSender()->GetVtkRenderer()); vtkIdType pickedPointID = picker->GetPointId(); if (pickedPointID == -1) { return Gizmo::NoHandle; } // _something_ picked return m_Gizmo->GetHandleFromPointID(pickedPointID); } void mitk::GizmoInteractor::UpdateHandleHighlight() { if (m_HighlightedHandle != m_PickedHandle) { auto node = GetDataNode(); if (node == nullptr) return; auto base_prop = node->GetProperty("LookupTable"); if (base_prop == nullptr) return; auto lut_prop = dynamic_cast(base_prop); if (lut_prop == nullptr) return; auto lut = lut_prop->GetLookupTable(); if (lut == nullptr) return; // Table size is expected to constructed as one entry per gizmo-part enum value assert(lut->GetVtkLookupTable()->GetNumberOfTableValues() > std::max(m_PickedHandle, m_HighlightedHandle)); // Reset previously overwritten color if (m_HighlightedHandle != Gizmo::NoHandle) { lut->SetTableValue(m_HighlightedHandle, m_ColorReplacedByHighlight); } // Overwrite currently highlighted color if (m_PickedHandle != Gizmo::NoHandle) { lut->GetTableValue(m_PickedHandle, m_ColorReplacedByHighlight); lut->SetTableValue(m_PickedHandle, m_ColorForHighlight); } // Mark node modified to allow repaint node->Modified(); RenderingManager::GetInstance()->RequestUpdateAll(RenderingManager::REQUEST_UPDATE_ALL); m_HighlightedHandle = m_PickedHandle; } } diff --git a/Modules/IGT/Algorithms/mitkNavigationDataToIGTLMessageFilter.cpp b/Modules/IGT/Algorithms/mitkNavigationDataToIGTLMessageFilter.cpp index 300ed54246..186d9fc2fc 100644 --- a/Modules/IGT/Algorithms/mitkNavigationDataToIGTLMessageFilter.cpp +++ b/Modules/IGT/Algorithms/mitkNavigationDataToIGTLMessageFilter.cpp @@ -1,319 +1,319 @@ /*============================================================================ 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 "mitkNavigationDataToIGTLMessageFilter.h" #include "igtlQuaternionTrackingDataMessage.h" #include "igtlTrackingDataMessage.h" #include "igtlTransformMessage.h" #include "igtlPositionMessage.h" #include #include mitk::NavigationDataToIGTLMessageFilter::NavigationDataToIGTLMessageFilter() { mitk::IGTLMessage::Pointer output = mitk::IGTLMessage::New(); this->SetNumberOfRequiredOutputs(1); this->SetNthOutput(0, output.GetPointer()); this->SetNumberOfRequiredInputs(1); // m_OperationMode = Mode3D; m_CurrentTimeStep = 0; // m_RingBufferSize = 50; //the default ring buffer size // m_NumberForMean = 100; } mitk::NavigationDataToIGTLMessageFilter::~NavigationDataToIGTLMessageFilter() { } void mitk::NavigationDataToIGTLMessageFilter::GenerateData() { switch (m_OperationMode) { case ModeSendQTDataMsg: this->GenerateDataModeSendQTDataMsg(); break; case ModeSendTDataMsg: this->GenerateDataModeSendTDataMsg(); break; case ModeSendQTransMsg: this->GenerateDataModeSendQTransMsg(); break; case ModeSendTransMsg: this->GenerateDataModeSendTransMsg(); break; default: break; } } void mitk::NavigationDataToIGTLMessageFilter::SetInput(const NavigationData* nd) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput(0, const_cast(nd)); this->CreateOutputsForAllInputs(); } void mitk::NavigationDataToIGTLMessageFilter::SetInput(unsigned int idx, const NavigationData* nd) { // Process object is not const-correct so the const_cast is required here this->ProcessObject::SetNthInput(idx, const_cast(nd)); this->CreateOutputsForAllInputs(); } const mitk::NavigationData* mitk::NavigationDataToIGTLMessageFilter::GetInput(void) { if (this->GetNumberOfInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(0)); } const mitk::NavigationData* mitk::NavigationDataToIGTLMessageFilter::GetInput(unsigned int idx) { if (this->GetNumberOfInputs() < 1) return nullptr; return static_cast(this->ProcessObject::GetInput(idx)); } void mitk::NavigationDataToIGTLMessageFilter::CreateOutputsForAllInputs() { switch (m_OperationMode) { case ModeSendQTDataMsg: // create one message output for all navigation data inputs this->SetNumberOfIndexedOutputs(1); // set the type for this filter this->SetType("QTDATA"); break; case ModeSendTDataMsg: // create one message output for all navigation data inputs this->SetNumberOfIndexedOutputs(1); // set the type for this filter this->SetType("TDATA"); break; case ModeSendQTransMsg: // create one message output for all navigation data input together this->SetNumberOfIndexedOutputs(1); // set the type for this filter this->SetType("POSITION"); break; case ModeSendTransMsg: // create one message output for each navigation data input this->SetNumberOfIndexedOutputs(this->GetNumberOfIndexedInputs()); // set the type for this filter this->SetType("TRANS"); break; default: break; } for (unsigned int idx = 0; idx < this->GetNumberOfIndexedOutputs(); ++idx) { if (this->GetOutput(idx) == nullptr) { DataObjectPointer newOutput = this->MakeOutput(idx); this->SetNthOutput(idx, newOutput); } this->Modified(); } } void ConvertAffineTransformationIntoIGTLMatrix(mitk::AffineTransform3D* trans, igtl::Matrix4x4 igtlTransform) { const mitk::AffineTransform3D::MatrixType& matrix = trans->GetMatrix(); mitk::Vector3D position = trans->GetOffset(); //copy the data into a matrix type that igtl understands for (unsigned int r = 0; r < 3; r++) { for (unsigned int c = 0; c < 3; c++) { igtlTransform[r][c] = matrix(r, c); } igtlTransform[r][3] = position[r]; } for (unsigned int c = 0; c < 3; c++) { igtlTransform[3][c] = 0.0; } igtlTransform[3][3] = 1.0; } void mitk::NavigationDataToIGTLMessageFilter::GenerateDataModeSendQTransMsg() { // for each output message for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); ++i) { mitk::IGTLMessage* output = this->GetOutput(i); assert(output); const mitk::NavigationData* input = this->GetInput(i); assert(input); // do not add navigation data to message if input is invalid if (input->IsDataValid() == false) continue; //get the navigation data components mitk::NavigationData::PositionType pos = input->GetPosition(); mitk::NavigationData::OrientationType ori = input->GetOrientation(); //insert this information into the message igtl::PositionMessage::Pointer posMsg = igtl::PositionMessage::New(); posMsg->SetPosition(pos[0], pos[1], pos[2]); posMsg->SetQuaternion(ori[0], ori[1], ori[2], ori[3]); igtl::TimeStamp::Pointer timestamp = ConvertToIGTLTimeStamp(input->GetIGTTimeStamp()); posMsg->SetTimeStamp(timestamp); posMsg->SetDeviceName(input->GetName()); posMsg->Pack(); //add the igtl message to the mitk::IGTLMessage output->SetMessage(posMsg.GetPointer()); } } void mitk::NavigationDataToIGTLMessageFilter::GenerateDataModeSendTransMsg() { // for each output message for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); ++i) { mitk::IGTLMessage* output = this->GetOutput(i); assert(output); const mitk::NavigationData* input = this->GetInput(i); assert(input); // do not add navigation data to message if input is invalid if (input->IsDataValid() == false) continue; //get the navigation data components mitk::AffineTransform3D::Pointer transform = input->GetAffineTransform3D(); - mitk::NavigationData::PositionType position = transform->GetOffset(); + mitk::NavigationData::PositionType position = Point3D(transform->GetOffset()); //convert the transform into a igtl type igtl::Matrix4x4 igtlTransform; ConvertAffineTransformationIntoIGTLMatrix(transform, igtlTransform); //insert this information into the message igtl::TransformMessage::Pointer transMsg = igtl::TransformMessage::New(); transMsg->SetMatrix(igtlTransform); transMsg->SetPosition(position[0], position[1], position[2]); igtl::TimeStamp::Pointer timestamp = ConvertToIGTLTimeStamp(input->GetIGTTimeStamp()); transMsg->SetTimeStamp(timestamp); transMsg->SetDeviceName(input->GetName()); transMsg->Pack(); //add the igtl message to the mitk::IGTLMessage output->SetMessage(transMsg.GetPointer()); } } igtl::TimeStamp::Pointer mitk::NavigationDataToIGTLMessageFilter::ConvertToIGTLTimeStamp(double IGTTimeStamp) { igtl::TimeStamp::Pointer timestamp = igtl::TimeStamp::New(); timestamp->SetTime(IGTTimeStamp / 1000, (int)(IGTTimeStamp) % 1000); return timestamp; } void mitk::NavigationDataToIGTLMessageFilter::GenerateDataModeSendQTDataMsg() { mitk::IGTLMessage* output = this->GetOutput(); assert(output); //create a output igtl message igtl::QuaternionTrackingDataMessage::Pointer qtdMsg = igtl::QuaternionTrackingDataMessage::New(); mitk::NavigationData::PositionType pos; mitk::NavigationData::OrientationType ori; for (unsigned int index = 0; index < this->GetNumberOfIndexedInputs(); index++) { const mitk::NavigationData* nd = GetInput(index); assert(nd); //get the navigation data components pos = nd->GetPosition(); ori = nd->GetOrientation(); //insert the information into the tracking element igtl::QuaternionTrackingDataElement::Pointer tde = igtl::QuaternionTrackingDataElement::New(); tde->SetPosition(pos[0], pos[1], pos[2]); tde->SetQuaternion(ori[0], ori[1], ori[2], ori[3]); tde->SetName(nd->GetName()); //insert this element into the tracking data message qtdMsg->AddQuaternionTrackingDataElement(tde); MITK_INFO << ConvertToIGTLTimeStamp(nd->GetIGTTimeStamp()); } qtdMsg->Pack(); //add the igtl message to the mitk::IGTLMessage output->SetMessage(qtdMsg.GetPointer()); } void mitk::NavigationDataToIGTLMessageFilter::GenerateDataModeSendTDataMsg() { igtl::TrackingDataMessage::Pointer tdMsg = igtl::TrackingDataMessage::New(); mitk::IGTLMessage* output = this->GetOutput(0); assert(output); // for each output message for (unsigned int i = 0; i < this->GetNumberOfIndexedInputs(); ++i) { const mitk::NavigationData* input = this->GetInput(i); assert(input); // do not add navigation data to message if input is invalid if (input->IsDataValid() == false) continue; //get the navigation data components mitk::AffineTransform3D::Pointer transform = input->GetAffineTransform3D(); - mitk::NavigationData::PositionType position = transform->GetOffset(); + mitk::NavigationData::PositionType position = Point3D(transform->GetOffset()); //convert the transform into a igtl type igtl::Matrix4x4 igtlTransform; ConvertAffineTransformationIntoIGTLMatrix(transform, igtlTransform); //insert this information into the message igtl::TrackingDataElement::Pointer tde = igtl::TrackingDataElement::New(); tde->SetMatrix(igtlTransform); tde->SetPosition(position[0], position[1], position[2]); tde->SetName(input->GetName()); tde->SetType(igtl::TrackingDataElement::TYPE_6D); tdMsg->AddTrackingDataElement(tde); } //use time stamp from first input igtl::TimeStamp::Pointer timestamp = ConvertToIGTLTimeStamp(this->GetInput(0)->GetIGTTimeStamp()); tdMsg->SetTimeStamp(timestamp); //tdMsg->SetDeviceName("MITK OpenIGTLink Connection"); tdMsg->Pack(); tdMsg->SetDeviceName("MITK OpenIGTLink Source"); output->SetMessage(tdMsg.GetPointer()); } void mitk::NavigationDataToIGTLMessageFilter::SetOperationMode(OperationMode mode) { m_OperationMode = mode; this->Modified(); } void mitk::NavigationDataToIGTLMessageFilter::ConnectTo( mitk::NavigationDataSource* UpstreamFilter) { for (DataObjectPointerArraySizeType i = 0; i < UpstreamFilter->GetNumberOfOutputs(); i++) { this->SetInput(i, UpstreamFilter->GetOutput(i)); } } diff --git a/Modules/IGT/Testing/mitkNavigationDataReferenceTransformFilterTest.cpp b/Modules/IGT/Testing/mitkNavigationDataReferenceTransformFilterTest.cpp index f76ed9bae0..3c557e6186 100644 --- a/Modules/IGT/Testing/mitkNavigationDataReferenceTransformFilterTest.cpp +++ b/Modules/IGT/Testing/mitkNavigationDataReferenceTransformFilterTest.cpp @@ -1,223 +1,223 @@ /*============================================================================ 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 "mitkNavigationDataReferenceTransformFilter.h" #include "mitkNavigationData.h" #include "mitkTestingMacros.h" #include #include /**Documentation * test for the class "NavigationDataReferenceTransformFilter". */ int mitkNavigationDataReferenceTransformFilterTest(int /* argc */, char* /*argv*/[]) { MITK_TEST_BEGIN("NavigationDataReferenceTransformFilter") // let's create an object of our class mitk::NavigationDataReferenceTransformFilter::Pointer myFilter = mitk::NavigationDataReferenceTransformFilter::New(); // first test: did this work? // using MITK_TEST_CONDITION_REQUIRED makes the test stop after failure, since // it makes no sense to continue without an object. MITK_TEST_CONDITION_REQUIRED(myFilter.IsNotNull(),"Testing instantiation"); /*create helper objects: positions of the ND sources*/ mitk::Point3D sourcePos1,sourcePos2, sourcePos3, targetPos1, targetPos2, targetPos3; mitk::FillVector3D(sourcePos1, 11.1, 11.1, 11.1); mitk::FillVector3D(sourcePos2, 22.2, 22.2, 22.2); mitk::FillVector3D(sourcePos3, 33.3, 33.3, 33.3); mitk::FillVector3D(targetPos1, -1.1, -2.2, -3.3); mitk::FillVector3D(targetPos2, -4.4, -5.5, -6.6); mitk::FillVector3D(targetPos3, -7.7, -8.8, -9.9); /*create helper objects: orientations of the ND sources*/ mitk::NavigationData::OrientationType sourceOri1(0.1, 0.1, 0.1, 0.1); mitk::NavigationData::OrientationType sourceOri2(0.2, 0.2, 0.2, 0.2); mitk::NavigationData::OrientationType sourceOri3(0.3, 0.3, 0.3, 0.3); mitk::NavigationData::OrientationType targetOri1(0.4, 0.4, 0.4, 0.4); mitk::NavigationData::OrientationType targetOri2(0.5, 0.5, 0.5, 0.5); mitk::NavigationData::OrientationType targetOri3(0.6, 0.6, 0.6, 0.6); /*create helper objects: ND position accurancy and validity bool*/ mitk::ScalarType initialError(0.0); bool initialValid(true); /*create helper objects: NDs for the source and target NDs*/ mitk::NavigationData::Pointer snd1 = mitk::NavigationData::New(); snd1->SetPosition(sourcePos1); snd1->SetOrientation(sourceOri1); snd1->SetPositionAccuracy(initialError); snd1->SetDataValid(initialValid); mitk::NavigationData::Pointer snd2 = mitk::NavigationData::New(); snd2->SetPosition(sourcePos2); snd2->SetOrientation(sourceOri2); snd2->SetPositionAccuracy(initialError); snd2->SetDataValid(initialValid); mitk::NavigationData::Pointer snd3 = mitk::NavigationData::New(); snd3->SetPosition(sourcePos3); snd3->SetOrientation(sourceOri3); snd3->SetPositionAccuracy(initialError); snd3->SetDataValid(initialValid); mitk::NavigationData::Pointer tnd1 = mitk::NavigationData::New(); tnd1->SetPosition(targetPos1); tnd1->SetOrientation(targetOri1); tnd1->SetPositionAccuracy(initialError); tnd1->SetDataValid(initialValid); mitk::NavigationData::Pointer tnd2 = mitk::NavigationData::New(); tnd2->SetPosition(targetPos2); tnd2->SetOrientation(targetOri2); tnd2->SetPositionAccuracy(initialError); tnd2->SetDataValid(initialValid); mitk::NavigationData::Pointer tnd3 = mitk::NavigationData::New(); tnd3->SetPosition(targetPos3); tnd3->SetOrientation(targetOri3); tnd3->SetPositionAccuracy(initialError); tnd3->SetDataValid(initialValid); std::vector emptySourceNDs; std::vector oneSourceNDs; oneSourceNDs.push_back(snd1); std::vector twoSourceNDs; twoSourceNDs.push_back(snd1); twoSourceNDs.push_back(snd2); std::vector threeSourceNDs; threeSourceNDs.push_back(snd1); threeSourceNDs.push_back(snd2); threeSourceNDs.push_back(snd3); std::vector emptyTargetNDs; std::vector oneTargetNDs; oneTargetNDs.push_back(tnd1); std::vector twoTargetNDs; twoTargetNDs.push_back(tnd1); twoTargetNDs.push_back(tnd2); std::vector threeTargetNDs; threeTargetNDs.push_back(tnd1); threeTargetNDs.push_back(tnd2); threeTargetNDs.push_back(tnd3); // ------------------ setting no NDs ------------------ myFilter->SetSourceNavigationDatas(emptySourceNDs); MITK_TEST_CONDITION_REQUIRED(myFilter->GetSourceLandmarks()->IsEmpty() == true, "Testing behaviour if setting no source NDs"); myFilter->SetSourceNavigationDatas(emptyTargetNDs); MITK_TEST_CONDITION_REQUIRED(myFilter->GetTargetLandmarks()->IsEmpty() == true, "Testing behaviour if setting no target NDs"); // ------------------ setting one ND ------------------ myFilter->SetSourceNavigationDatas(oneSourceNDs); MITK_TEST_CONDITION_REQUIRED(myFilter->GetSourceLandmarks()->GetSize() == 3, "Testing if 3 source points are generated from one source ND"); myFilter->SetTargetNavigationDatas(oneTargetNDs); MITK_TEST_CONDITION_REQUIRED(myFilter->GetTargetLandmarks()->GetSize() == 3, "Testing if 3 target points are generated from one target ND"); // ------------------ setting two NDs ------------------ myFilter->SetSourceNavigationDatas(twoSourceNDs); MITK_TEST_CONDITION_REQUIRED(myFilter->GetSourceLandmarks()->GetSize() == 6, "Testing if 6 source points are generated from two source NDs"); myFilter->SetTargetNavigationDatas(twoTargetNDs); MITK_TEST_CONDITION_REQUIRED(myFilter->GetTargetLandmarks()->GetSize() == 6, "Testing if 6 target points are generated from two target NDs"); // ------------------ setting three NDs ------------------ myFilter->SetSourceNavigationDatas(threeSourceNDs); MITK_TEST_CONDITION_REQUIRED(myFilter->GetSourceLandmarks()->GetSize() == 3, "Testing if 3 source NDs are passed to 3 source points"); myFilter->SetTargetNavigationDatas(threeTargetNDs); MITK_TEST_CONDITION_REQUIRED(myFilter->GetTargetLandmarks()->GetSize() == 3, "Testing if 3 target NDs are passed to 3 target points"); // ------------------ setting different number of NDs for source and target ------------------ bool firstInitialize = myFilter->InitializeTransform(); myFilter->SetTargetNavigationDatas(twoTargetNDs); MITK_TEST_CONDITION_REQUIRED((firstInitialize == true && myFilter->InitializeTransform() == false), "Testing if initialization is denied, if different number of source and target NDs are set"); // ------------------ reinit of this filter ------------------ bool sourcePointsSet = myFilter->GetSourceLandmarks()->GetSize() > 0; bool targetPointsSet = myFilter->GetTargetLandmarks()->GetSize() > 0; MITK_TEST_CONDITION_REQUIRED(sourcePointsSet && targetPointsSet, "Testing if there are source and target landmarks set in the superclass"); myFilter->ReinitFilter(); bool sourcePointsCleared = myFilter->GetSourceLandmarks()->GetSize() == 0; bool targetPointsCleared = myFilter->GetTargetLandmarks()->GetSize() == 0; MITK_TEST_CONDITION_REQUIRED(sourcePointsCleared && targetPointsCleared, "Testing if reinit of filter was successful"); // ------------------ testing the point generation ------------------ myFilter->SetSourceNavigationDatas(oneSourceNDs); // set the ND with sourcePos1 and sourceOri1 for that the points will be generated itk::QuaternionRigidTransform::Pointer quaternionTransform = itk::QuaternionRigidTransform::New(); vnl_quaternion const vnlQuatIn(sourceOri1.x(), sourceOri1.y(), sourceOri1.z(), sourceOri1.r()); quaternionTransform->SetRotation(vnlQuatIn); mitk::Point3D pointA; mitk::Point3D pointB; mitk::Point3D pointC; //initializing three points with position(0|0|0) pointA.Fill(0); pointB.Fill(0); pointC.Fill(0); // changing position off all points in order to make them orthogonal pointA[0] = 1; pointB[1] = 1; pointC[2] = 1; // quaternion transform the points pointA = quaternionTransform->GetMatrix() * pointA; pointB = quaternionTransform->GetMatrix() * pointB; pointC = quaternionTransform->GetMatrix() * pointC; // now subtract them from the filter landmarks and compare them to the source pos - pointA = myFilter->GetSourceLandmarks()->GetPoint(0)-pointA; - pointB = myFilter->GetSourceLandmarks()->GetPoint(1)-pointB; - pointC = myFilter->GetSourceLandmarks()->GetPoint(2)-pointC; + pointA = mitk::Point3D(myFilter->GetSourceLandmarks()->GetPoint(0)-pointA); + pointB = mitk::Point3D(myFilter->GetSourceLandmarks()->GetPoint(1)-pointB); + pointC = mitk::Point3D(myFilter->GetSourceLandmarks()->GetPoint(2)-pointC); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(sourcePos1,pointA,mitk::eps,true), "Testing if point generation of first point is correct"); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(sourcePos1,pointB,mitk::eps,true), "Testing if point generation of second point is correct"); MITK_TEST_CONDITION_REQUIRED(mitk::Equal(sourcePos1,pointC,mitk::eps,true), "Testing if point generation of third point is correct"); // deleting helper objects myFilter = nullptr; quaternionTransform = nullptr; snd1 = nullptr; snd2 = nullptr; snd3 = nullptr; tnd1 = nullptr; tnd2 = nullptr; tnd3 = nullptr; // always end with this! MITK_TEST_END(); } diff --git a/Modules/IGTUI/Qmitk/QmitkNavigationToolCreationWidget.cpp b/Modules/IGTUI/Qmitk/QmitkNavigationToolCreationWidget.cpp index 60cb9e101c..11fa90a23f 100644 --- a/Modules/IGTUI/Qmitk/QmitkNavigationToolCreationWidget.cpp +++ b/Modules/IGTUI/Qmitk/QmitkNavigationToolCreationWidget.cpp @@ -1,393 +1,393 @@ /*============================================================================ 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 "QmitkNavigationToolCreationWidget.h" //mitk headers #include #include #include #include #include "mitkTrackingDeviceTypeCollection.h" //qt headers #include #include #include #include #include //poco headers #include const std::string QmitkNavigationToolCreationWidget::VIEW_ID = "org.mitk.views.navigationtoolcreationwizardwidget"; QmitkNavigationToolCreationWidget::QmitkNavigationToolCreationWidget(QWidget* parent, Qt::WindowFlags f) : QWidget(parent, f) { m_Controls = nullptr; m_ToolToBeEdited = mitk::NavigationTool::New(); m_FinalTool = mitk::NavigationTool::New(); m_ToolTransformationWidget = new QmitkInteractiveTransformationWidget(); m_Controls = nullptr; CreateQtPartControl(this); CreateConnections(); this->InitializeUIToolLandmarkLists(); Initialize(nullptr, ""); //Default values, which are not stored in tool m_Controls->m_CalibrationFileName->setText("none"); m_Controls->m_Surface_Use_Sphere->setChecked(true); m_Controls->m_CalibrationLandmarksList->EnableEditButton(false); m_Controls->m_RegistrationLandmarksList->EnableEditButton(false); RefreshTrackingDeviceCollection(); OnSurfaceUseToggled(); } QmitkNavigationToolCreationWidget::~QmitkNavigationToolCreationWidget() { m_Controls->m_CalibrationLandmarksList->SetPointSetNode(nullptr); m_Controls->m_RegistrationLandmarksList->SetPointSetNode(nullptr); delete m_ToolTransformationWidget; } void QmitkNavigationToolCreationWidget::CreateQtPartControl(QWidget *parent) { if (!m_Controls) { // create GUI widgets m_Controls = new Ui::QmitkNavigationToolCreationWidgetControls; m_Controls->setupUi(parent); } } void QmitkNavigationToolCreationWidget::CreateConnections() { if (m_Controls) { connect((QObject*)(m_Controls->m_TrackingDeviceTypeChooser), SIGNAL(currentIndexChanged(int)), this, SLOT(GetValuesFromGuiElements())); connect((QObject*)(m_Controls->m_ToolNameEdit), SIGNAL(textChanged(const QString)), this, SLOT(GetValuesFromGuiElements())); connect((QObject*)(m_Controls->m_ToolTypeChooser), SIGNAL(currentIndexChanged(int)), this, SLOT(GetValuesFromGuiElements())); connect((QObject*)(m_Controls->m_IdentifierEdit), SIGNAL(textChanged(const QString)), this, SLOT(GetValuesFromGuiElements())); connect((QObject*)(m_Controls->m_SerialNumberEdit), SIGNAL(textChanged(const QString)), this, SLOT(GetValuesFromGuiElements())); connect((QObject*)(m_Controls->m_ToolAxisX), SIGNAL(valueChanged(int)), this, SLOT(GetValuesFromGuiElements())); connect((QObject*)(m_Controls->m_ToolAxisY), SIGNAL(valueChanged(int)), this, SLOT(GetValuesFromGuiElements())); connect((QObject*)(m_Controls->m_ToolAxisZ), SIGNAL(valueChanged(int)), this, SLOT(GetValuesFromGuiElements())); //Buttons connect((QObject*)(m_Controls->m_LoadCalibrationFile), SIGNAL(clicked()), this, SLOT(OnLoadCalibrationFile())); connect(m_Controls->m_Surface_Use_Other, SIGNAL(toggled(bool)), this, SLOT(OnSurfaceUseToggled())); connect(m_Controls->m_Surface_Load_File, SIGNAL(toggled(bool)), this, SLOT(OnSurfaceUseToggled())); connect((QObject*)(m_Controls->m_LoadSurface), SIGNAL(clicked()), this, SLOT(OnLoadSurface())); connect((QObject*)(m_Controls->m_EditToolTip), SIGNAL(clicked()), this, SLOT(OnEditToolTip())); connect((QObject*)(m_ToolTransformationWidget), SIGNAL(EditToolTipFinished(mitk::AffineTransform3D::Pointer)), this, SLOT(OnEditToolTipFinished(mitk::AffineTransform3D::Pointer))); connect((QObject*)(m_Controls->m_cancel), SIGNAL(clicked()), this, SLOT(OnCancel())); connect((QObject*)(m_Controls->m_finished), SIGNAL(clicked()), this, SLOT(OnFinished())); } } void QmitkNavigationToolCreationWidget::Initialize(mitk::DataStorage* dataStorage, const std::string& supposedIdentifier, const std::string& supposedName) { m_DataStorage = dataStorage; //initialize UI components m_Controls->m_SurfaceChooser->SetDataStorage(m_DataStorage); m_Controls->m_SurfaceChooser->SetAutoSelectNewItems(true); m_Controls->m_SurfaceChooser->SetPredicate(mitk::NodePredicateDataType::New("Surface")); //Create new tool, which should be edited/created m_ToolToBeEdited = nullptr;//Reset m_ToolToBeEdited = mitk::NavigationTool::New();//Reinitialize m_ToolToBeEdited->SetIdentifier(supposedIdentifier); m_ToolToBeEdited->GetDataNode()->SetName(supposedName); this->SetDefaultData(m_ToolToBeEdited); } void QmitkNavigationToolCreationWidget::ShowToolPreview(std::string _name) { m_DataStorage->Add(m_ToolToBeEdited->GetDataNode()); m_ToolToBeEdited->GetDataNode()->SetName(_name); //change color to blue m_ToolToBeEdited->GetDataNode()->SetProperty("color", mitk::ColorProperty::New(0, 0, 1)); //Global Reinit to show new tool mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); } void QmitkNavigationToolCreationWidget::SetDefaultData(mitk::NavigationTool::Pointer DefaultTool) { //Set Members. This can either be the new initialized tool from call of Initialize() or a tool which already exists in the toolStorage m_ToolToBeEdited = DefaultTool->Clone(); //Set all gui variables SetGuiElements(); } void QmitkNavigationToolCreationWidget::SetGuiElements() { //Block signals, so that we don't call SetGuiElements again. This is undone at the end of this function! m_Controls->m_TrackingDeviceTypeChooser->blockSignals(true); m_Controls->m_ToolNameEdit->blockSignals(true); m_Controls->m_ToolTypeChooser->blockSignals(true); m_Controls->m_IdentifierEdit->blockSignals(true); m_Controls->m_SerialNumberEdit->blockSignals(true); m_Controls->m_ToolAxisX->blockSignals(true); m_Controls->m_ToolAxisY->blockSignals(true); m_Controls->m_ToolAxisZ->blockSignals(true); //DeviceType int index = m_Controls->m_TrackingDeviceTypeChooser->findText(QString::fromStdString(m_ToolToBeEdited->GetTrackingDeviceType())); if (index >= 0) { m_Controls->m_TrackingDeviceTypeChooser->setCurrentIndex(index); } m_Controls->m_ToolNameEdit->setText(QString(m_ToolToBeEdited->GetToolName().c_str())); m_Controls->m_CalibrationFileName->setText(QString(m_ToolToBeEdited->GetCalibrationFile().c_str())); FillUIToolLandmarkLists(m_ToolToBeEdited->GetToolControlPoints(), m_ToolToBeEdited->GetToolLandmarks()); switch (m_ToolToBeEdited->GetType()) { case mitk::NavigationTool::Instrument: m_Controls->m_ToolTypeChooser->setCurrentIndex(0); break; case mitk::NavigationTool::Fiducial: m_Controls->m_ToolTypeChooser->setCurrentIndex(1); break; case mitk::NavigationTool::Skinmarker: m_Controls->m_ToolTypeChooser->setCurrentIndex(2); break; case mitk::NavigationTool::Unknown: m_Controls->m_ToolTypeChooser->setCurrentIndex(3); break; } m_Controls->m_IdentifierEdit->setText(QString(m_ToolToBeEdited->GetIdentifier().c_str())); m_Controls->m_SerialNumberEdit->setText(QString(m_ToolToBeEdited->GetSerialNumber().c_str())); QString _label = "(" + QString::number(m_ToolToBeEdited->GetToolTipPosition()[0], 'f', 1) + ", " + QString::number(m_ToolToBeEdited->GetToolTipPosition()[1], 'f', 1) + ", " + QString::number(m_ToolToBeEdited->GetToolTipPosition()[2], 'f', 1) + "), quat: [" + QString::number(m_ToolToBeEdited->GetToolAxisOrientation()[0], 'f', 2) + ", " + QString::number(m_ToolToBeEdited->GetToolAxisOrientation()[1], 'f', 2) + ", " + QString::number(m_ToolToBeEdited->GetToolAxisOrientation()[2], 'f', 2) + ", " + QString::number(m_ToolToBeEdited->GetToolAxisOrientation()[3], 'f', 2) + "]"; m_Controls->m_ToolTipLabel->setText(_label); //Undo block signals. Don't remove it, if signals are still blocked at the beginning of this function! m_Controls->m_TrackingDeviceTypeChooser->blockSignals(false); m_Controls->m_ToolNameEdit->blockSignals(false); m_Controls->m_ToolTypeChooser->blockSignals(false); m_Controls->m_IdentifierEdit->blockSignals(false); m_Controls->m_SerialNumberEdit->blockSignals(false); m_Controls->m_ToolAxisX->blockSignals(false); m_Controls->m_ToolAxisY->blockSignals(false); m_Controls->m_ToolAxisZ->blockSignals(false); } void QmitkNavigationToolCreationWidget::OnSurfaceUseToggled() { if (m_Controls->m_Surface_Use_Sphere->isChecked()) m_ToolToBeEdited->SetDefaultSurface(); m_Controls->m_SurfaceChooser->setEnabled(m_Controls->m_Surface_Use_Other->isChecked()); m_Controls->m_LoadSurface->setEnabled(m_Controls->m_Surface_Load_File->isChecked()); //Global Reinit to show tool surface preview mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); } void QmitkNavigationToolCreationWidget::OnLoadSurface() { std::string filename = QFileDialog::getOpenFileName(nullptr, tr("Open Surface"), QmitkIGTCommonHelper::GetLastFileLoadPath(), tr("STL (*.stl)")).toLatin1().data(); QmitkIGTCommonHelper::SetLastFileLoadPathByFileName(QString::fromStdString(filename)); mitk::Surface::Pointer surface; try { surface = mitk::IOUtil::Load(filename.c_str()); } catch (mitk::Exception &e) { MITK_ERROR << "Exception occured: " << e.what(); return; } m_ToolToBeEdited->GetDataNode()->SetData(surface); //Global Reinit to show tool surface or preview mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(m_DataStorage); } void QmitkNavigationToolCreationWidget::OnLoadCalibrationFile() { QString fileName = QFileDialog::getOpenFileName(nullptr, tr("Open Calibration File"), QmitkIGTCommonHelper::GetLastFileLoadPath(), "*.*"); QmitkIGTCommonHelper::SetLastFileLoadPathByFileName(fileName); m_Controls->m_CalibrationFileName->setText(fileName); m_ToolToBeEdited->SetCalibrationFile(fileName.toStdString()); } void QmitkNavigationToolCreationWidget::GetValuesFromGuiElements() { //Tracking Device m_ToolToBeEdited->SetTrackingDeviceType(m_Controls->m_TrackingDeviceTypeChooser->currentText().toStdString()); //m_ToolToBeEdited->GetDataNode()->SetName(m_Controls->m_ToolNameEdit->text().toStdString()); //Tool Landmarks mitk::PointSet::Pointer toolCalLandmarks, toolRegLandmarks; GetUIToolLandmarksLists(toolCalLandmarks, toolRegLandmarks); m_ToolToBeEdited->SetToolControlPoints(toolCalLandmarks); m_ToolToBeEdited->SetToolLandmarks(toolRegLandmarks); //Advanced if (m_Controls->m_ToolTypeChooser->currentText() == "Instrument") m_ToolToBeEdited->SetType(mitk::NavigationTool::Instrument); else if (m_Controls->m_ToolTypeChooser->currentText() == "Fiducial") m_ToolToBeEdited->SetType(mitk::NavigationTool::Fiducial); else if (m_Controls->m_ToolTypeChooser->currentText() == "Skinmarker") m_ToolToBeEdited->SetType(mitk::NavigationTool::Skinmarker); else m_FinalTool->SetType(mitk::NavigationTool::Unknown); m_ToolToBeEdited->SetIdentifier(m_Controls->m_IdentifierEdit->text().toLatin1().data()); m_ToolToBeEdited->SetSerialNumber(m_Controls->m_SerialNumberEdit->text().toLatin1().data()); ////Tool Axis //mitk::Point3D toolAxis; //toolAxis.SetElement(0, (m_Controls->m_ToolAxisX->value())); //toolAxis.SetElement(1, (m_Controls->m_ToolAxisY->value())); //toolAxis.SetElement(2, (m_Controls->m_ToolAxisZ->value())); //m_ToolToBeEdited->SetToolAxis(toolAxis); } mitk::NavigationTool::Pointer QmitkNavigationToolCreationWidget::GetCreatedTool() { return m_FinalTool; } void QmitkNavigationToolCreationWidget::OnFinished() { if (m_Controls->m_Surface_Use_Other->isChecked()) m_ToolToBeEdited->GetDataNode()->SetData(m_Controls->m_SurfaceChooser->GetSelectedNode()->GetData()); //here we create a new tool m_FinalTool = m_ToolToBeEdited->Clone(); //Set the correct name of data node, cause the m_ToolToBeEdited was called "Tool preview" m_FinalTool->GetDataNode()->SetName(m_Controls->m_ToolNameEdit->text().toStdString()); emit NavigationToolFinished(); } void QmitkNavigationToolCreationWidget::OnCancel() { Initialize(nullptr, "");//Reset everything to a fresh tool, like it was done in the constructor emit Canceled(); } void QmitkNavigationToolCreationWidget::SetTrackingDeviceType(mitk::TrackingDeviceType type, bool changeable /*= true*/) { //Adapt Gui int index = m_Controls->m_TrackingDeviceTypeChooser->findText(QString::fromStdString(type)); if (index >= 0) { m_Controls->m_TrackingDeviceTypeChooser->setCurrentIndex(index); } m_Controls->m_TrackingDeviceTypeChooser->setEditable(changeable); //Set data to member m_ToolToBeEdited->SetTrackingDeviceType(type); } //################################################################################## //############################## internal help methods ############################# //################################################################################## void QmitkNavigationToolCreationWidget::MessageBox(std::string s) { QMessageBox msgBox; msgBox.setText(s.c_str()); msgBox.exec(); } void QmitkNavigationToolCreationWidget::OnEditToolTip() { m_ToolTransformationWidget->SetToolToEdit(m_ToolToBeEdited); m_ToolTransformationWidget->SetDefaultRotation(m_ToolToBeEdited->GetToolAxisOrientation()); m_ToolTransformationWidget->SetDefaultOffset(m_ToolToBeEdited->GetToolTipPosition()); m_ToolTransformationWidget->open(); } void QmitkNavigationToolCreationWidget::OnEditToolTipFinished(mitk::AffineTransform3D::Pointer toolTip) { //if user pressed cancle, nullptr is returned. Do nothing. Else, set values. if (toolTip) { - m_ToolToBeEdited->SetToolTipPosition(toolTip->GetOffset()); + m_ToolToBeEdited->SetToolTipPosition(mitk::Point3D(toolTip->GetOffset())); mitk::NavigationData::Pointer tempND = mitk::NavigationData::New(toolTip);//Convert to Navigation data for simple transversion to quaternion m_ToolToBeEdited->SetToolAxisOrientation(tempND->GetOrientation()); //Update Label QString _label = "(" + QString::number(m_ToolToBeEdited->GetToolTipPosition()[0], 'f', 1) + ", " + QString::number(m_ToolToBeEdited->GetToolTipPosition()[1], 'f', 1) + ", " + QString::number(m_ToolToBeEdited->GetToolTipPosition()[2], 'f', 1) + "), quat: [" + QString::number(m_ToolToBeEdited->GetToolAxisOrientation()[0], 'f', 2) + ", " + QString::number(m_ToolToBeEdited->GetToolAxisOrientation()[1], 'f', 2) + ", " + QString::number(m_ToolToBeEdited->GetToolAxisOrientation()[2], 'f', 2) + ", " + QString::number(m_ToolToBeEdited->GetToolAxisOrientation()[3], 'f', 2) + "]"; m_Controls->m_ToolTipLabel->setText(_label); } } void QmitkNavigationToolCreationWidget::FillUIToolLandmarkLists(mitk::PointSet::Pointer calLandmarks, mitk::PointSet::Pointer regLandmarks) { m_calLandmarkNode->SetData(calLandmarks); m_regLandmarkNode->SetData(regLandmarks); m_Controls->m_CalibrationLandmarksList->SetPointSetNode(m_calLandmarkNode); m_Controls->m_RegistrationLandmarksList->SetPointSetNode(m_regLandmarkNode); } void QmitkNavigationToolCreationWidget::GetUIToolLandmarksLists(mitk::PointSet::Pointer& calLandmarks, mitk::PointSet::Pointer& regLandmarks) { calLandmarks = dynamic_cast(m_calLandmarkNode->GetData()); regLandmarks = dynamic_cast(m_regLandmarkNode->GetData()); } void QmitkNavigationToolCreationWidget::InitializeUIToolLandmarkLists() { m_calLandmarkNode = mitk::DataNode::New(); m_regLandmarkNode = mitk::DataNode::New(); FillUIToolLandmarkLists(mitk::PointSet::New(), mitk::PointSet::New()); } void QmitkNavigationToolCreationWidget::RefreshTrackingDeviceCollection() { us::ModuleContext* context = us::GetModuleContext(); std::vector > refs = context->GetServiceReferences(); if (refs.empty()) { MITK_WARN << "No tracking device service found!"; return; } mitk::TrackingDeviceTypeCollection* _DeviceTypeCollection = context->GetService(refs.front()); for (auto name : _DeviceTypeCollection->GetTrackingDeviceTypeNames()) { //if the device is not included yet, add name to comboBox and widget to stackedWidget if (m_Controls->m_TrackingDeviceTypeChooser->findText(QString::fromStdString(name)) == -1) { m_Controls->m_TrackingDeviceTypeChooser->addItem(QString::fromStdString(name)); } } } diff --git a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp index 8637823ce0..97516291b0 100644 --- a/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp +++ b/Modules/Multilabel/Testing/mitkLabelSetImageTest.cpp @@ -1,435 +1,442 @@ /*============================================================================ 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 class mitkLabelSetImageTestSuite : public mitk::TestFixture { CPPUNIT_TEST_SUITE(mitkLabelSetImageTestSuite); MITK_TEST(TestInitialize); MITK_TEST(TestAddLayer); MITK_TEST(TestGetActiveLabelSet); MITK_TEST(TestGetActiveLabel); MITK_TEST(TestInitializeByLabeledImage); MITK_TEST(TestGetLabelSet); MITK_TEST(TestGetLabel); MITK_TEST(TestSetExteriorLabel); MITK_TEST(TestGetTotalNumberOfLabels); MITK_TEST(TestExistsLabel); MITK_TEST(TestExistsLabelSet); MITK_TEST(TestSetActiveLayer); MITK_TEST(TestRemoveLayer); MITK_TEST(TestRemoveLabels); MITK_TEST(TestMergeLabel); CPPUNIT_TEST_SUITE_END(); private: mitk::LabelSetImage::Pointer m_LabelSetImage; public: void setUp() override { // Create a new labelset image m_LabelSetImage = mitk::LabelSetImage::New(); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 96, 128, 52 }; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); m_LabelSetImage->Initialize(regularImage); } void tearDown() override { // Delete LabelSetImage m_LabelSetImage = nullptr; } void TestInitialize() { // LabelSet image should always has the pixel type mitk::Label::PixelType CPPUNIT_ASSERT_MESSAGE("LabelSetImage has wrong pixel type", m_LabelSetImage->GetPixelType() == mitk::MakeScalarPixelType()); mitk::Image::Pointer regularImage = mitk::Image::New(); unsigned int dimensions[3] = { 96, 128, 52 }; regularImage->Initialize(mitk::MakeScalarPixelType(), 3, dimensions); mitk::BaseGeometry::Pointer regularImageGeo = regularImage->GetGeometry(); mitk::BaseGeometry::Pointer labelImageGeo = m_LabelSetImage->GetGeometry(); MITK_ASSERT_EQUAL(labelImageGeo, regularImageGeo, "LabelSetImage has wrong geometry"); // By default one layer containing the exterior label should be added CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - number of layers is not one", m_LabelSetImage->GetNumberOfLayers() == 1); CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == 0); CPPUNIT_ASSERT_MESSAGE("Image was not correctly initialized - active label is not the exterior label", m_LabelSetImage->GetActiveLabel()->GetValue() == 0); } void TestAddLayer() { CPPUNIT_ASSERT_MESSAGE("Number of layers is not zero", m_LabelSetImage->GetNumberOfLayers() == 1); m_LabelSetImage->AddLayer(); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - number of layers is not one", m_LabelSetImage->GetNumberOfLayers() == 2); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == 1); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is not the exterior label", m_LabelSetImage->GetActiveLabel()->GetValue() == 0); mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - number of layers is not two", m_LabelSetImage->GetNumberOfLayers() == 3); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active layer has wrong ID", m_LabelSetImage->GetActiveLayer() == layerID); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel(layerID)->GetValue() == 200); } void TestGetActiveLabelSet() { mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); mitk::LabelSet::Pointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); CPPUNIT_ASSERT_MESSAGE("Wrong layer ID was returned", layerID == 1); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *activeLayer, 0.00001, true)); + + mitk::LabelSet::ConstPointer constActiveLayer = const_cast(m_LabelSetImage.GetPointer())->GetActiveLabelSet(); + CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *constActiveLayer, 0.00001, true)); } void TestGetActiveLabel() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); mitk::Label::PixelType value1 = 1; label1->SetValue(value1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); mitk::Label::PixelType value2 = 200; label2->SetValue(value2); m_LabelSetImage->GetActiveLabelSet()->AddLabel(label1); m_LabelSetImage->GetActiveLabelSet()->AddLabel(label2); m_LabelSetImage->GetActiveLabelSet()->SetActiveLabel(1); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel()->GetValue() == value1); m_LabelSetImage->GetActiveLabelSet()->SetActiveLabel(value2); CPPUNIT_ASSERT_MESSAGE("Layer was not added correctly to image - active label is wrong", m_LabelSetImage->GetActiveLabel()->GetValue() == value2); + + CPPUNIT_ASSERT_MESSAGE("Active Label was not correctly retreived with const getter", + const_cast(m_LabelSetImage.GetPointer())->GetActiveLabel()->GetValue() == value2); + } void TestInitializeByLabeledImage() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage->InitializeByLabeledImage(image); CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 6); } void TestGetLabelSet() { // Test get non existing lset mitk::LabelSet::ConstPointer lset = m_LabelSetImage->GetLabelSet(10000); CPPUNIT_ASSERT_MESSAGE("Non existing labelset is not nullptr", lset.IsNull()); lset = m_LabelSetImage->GetLabelSet(0); CPPUNIT_ASSERT_MESSAGE("Existing labelset is nullptr", lset.IsNotNull()); } void TestGetLabel() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); mitk::Label::PixelType value1 = 1; label1->SetValue(value1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); mitk::Label::PixelType value2 = 200; label2->SetValue(value2); m_LabelSetImage->GetActiveLabelSet()->AddLabel(label1); m_LabelSetImage->AddLayer(); m_LabelSetImage->GetLabelSet(1)->AddLabel(label2); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for active layer", mitk::Equal(*m_LabelSetImage->GetLabel(1), *label1, 0.0001, true)); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for layer 1", mitk::Equal(*m_LabelSetImage->GetLabel(200, 1), *label2, 0.0001, true)); // Try to get a non existing label mitk::Label *label3 = m_LabelSetImage->GetLabel(1000); CPPUNIT_ASSERT_MESSAGE("Non existing label should be nullptr", label3 == nullptr); // Try to get a label from a non existing layer label3 = m_LabelSetImage->GetLabel(200, 1000); CPPUNIT_ASSERT_MESSAGE("Label from non existing layer should be nullptr", label3 == nullptr); } void TestSetExteriorLabel() { mitk::Label::Pointer exteriorLabel = mitk::Label::New(); exteriorLabel->SetName("MyExteriorSpecialLabel"); mitk::Label::PixelType value1 = 10000; exteriorLabel->SetValue(value1); m_LabelSetImage->SetExteriorLabel(exteriorLabel); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for layer 1", mitk::Equal(*m_LabelSetImage->GetExteriorLabel(), *exteriorLabel, 0.0001, true)); // Exterior label should be set automatically for each new layer m_LabelSetImage->AddLayer(); CPPUNIT_ASSERT_MESSAGE("Wrong label retrieved for layer 1", mitk::Equal(*m_LabelSetImage->GetLabel(10000, 1), *exteriorLabel, 0.0001, true)); } void TestGetTotalNumberOfLabels() { mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); mitk::Label::PixelType value1 = 1; label1->SetValue(value1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); mitk::Label::PixelType value2 = 200; label2->SetValue(value2); m_LabelSetImage->GetActiveLabelSet()->AddLabel(label1); m_LabelSetImage->AddLayer(); m_LabelSetImage->GetLabelSet(1)->AddLabel(label2); CPPUNIT_ASSERT_MESSAGE( "Wrong total number of labels", m_LabelSetImage->GetTotalNumberOfLabels() == 4); // added 2 labels + 2 exterior default labels } void TestExistsLabel() { mitk::Label::Pointer label = mitk::Label::New(); label->SetName("Label2"); mitk::Label::PixelType value = 200; label->SetValue(value); m_LabelSetImage->AddLayer(); m_LabelSetImage->GetLabelSet(1)->AddLabel(label); m_LabelSetImage->SetActiveLayer(0); CPPUNIT_ASSERT_MESSAGE("Existing label was not found", m_LabelSetImage->ExistLabel(value) == true); CPPUNIT_ASSERT_MESSAGE("Non existing label was found", m_LabelSetImage->ExistLabel(10000) == false); } void TestExistsLabelSet() { // Cache active layer mitk::LabelSet::ConstPointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); // Add new layer mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); m_LabelSetImage->AddLayer(newlayer); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(20) == false); } void TestSetActiveLayer() { // Cache active layer mitk::LabelSet::ConstPointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); // Add new layer mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); unsigned int layerID = m_LabelSetImage->AddLayer(newlayer); // Set initial layer as active layer m_LabelSetImage->SetActiveLayer(0); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*activeLayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); // Set previously added layer as active layer m_LabelSetImage->SetActiveLayer(layerID); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); // Set a non existing layer as active layer - nothing should change m_LabelSetImage->SetActiveLayer(10000); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); } void TestRemoveLayer() { // Cache active layer mitk::LabelSet::ConstPointer activeLayer = m_LabelSetImage->GetActiveLabelSet(); // Add new layers m_LabelSetImage->AddLayer(); mitk::LabelSet::Pointer newlayer = mitk::LabelSet::New(); mitk::Label::Pointer label1 = mitk::Label::New(); label1->SetName("Label1"); label1->SetValue(1); mitk::Label::Pointer label2 = mitk::Label::New(); label2->SetName("Label2"); label2->SetValue(200); newlayer->AddLabel(label1); newlayer->AddLabel(label2); newlayer->SetActiveLabel(200); m_LabelSetImage->AddLayer(newlayer); CPPUNIT_ASSERT_MESSAGE("Wrong active labelset returned", mitk::Equal(*newlayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); m_LabelSetImage->RemoveLayer(); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 2); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(2) == false); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == true); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == true); m_LabelSetImage->RemoveLayer(); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 1); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(1) == false); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == true); CPPUNIT_ASSERT_MESSAGE("Wrong active layer", mitk::Equal(*activeLayer, *m_LabelSetImage->GetActiveLabelSet(), 0.00001, true)); m_LabelSetImage->RemoveLayer(); CPPUNIT_ASSERT_MESSAGE("Wrong number of layers, after a layer was removed", m_LabelSetImage->GetNumberOfLayers() == 0); CPPUNIT_ASSERT_MESSAGE("Check for existing layer failed", m_LabelSetImage->ExistLabelSet(0) == false); CPPUNIT_ASSERT_MESSAGE("Active layers is not nullptr although all layer have been removed", m_LabelSetImage->GetActiveLabelSet() == nullptr); } void TestRemoveLabels() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage->InitializeByLabeledImage(image); CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 6); // 2ndMin because of the exterior label = 0 CPPUNIT_ASSERT_MESSAGE("Labels with value 1 and 3 was not remove from the image", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 1); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not remove from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); CPPUNIT_ASSERT_MESSAGE("Label with ID 3 does not exists after initialization", m_LabelSetImage->ExistLabel(3) == true); CPPUNIT_ASSERT_MESSAGE("Label with ID 7 does not exists after initialization", m_LabelSetImage->ExistLabel(7) == true); std::vector labelsToBeRemoved; labelsToBeRemoved.push_back(1); labelsToBeRemoved.push_back(3); labelsToBeRemoved.push_back(7); m_LabelSetImage->RemoveLabels(labelsToBeRemoved); CPPUNIT_ASSERT_MESSAGE("Wrong number of labels after some have been removed", m_LabelSetImage->GetNumberOfLabels() == 3); // Values within the image are 0, 1, 3, 5, 6, 7 - New Min/Max value should be 5 / 6 // 2ndMin because of the exterior label = 0 CPPUNIT_ASSERT_MESSAGE("Labels with value 1 and 3 was not remove from the image", m_LabelSetImage->GetStatistics()->GetScalarValue2ndMin() == 5); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not remove from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); } void TestMergeLabel() { mitk::Image::Pointer image = mitk::IOUtil::Load(GetTestDataFilePath("Multilabel/LabelSetTestInitializeImage.nrrd")); m_LabelSetImage = nullptr; m_LabelSetImage = mitk::LabelSetImage::New(); m_LabelSetImage->InitializeByLabeledImage(image); CPPUNIT_ASSERT_MESSAGE("Image - number of labels is not 6", m_LabelSetImage->GetNumberOfLabels() == 6); // 2ndMin because of the exterior label = 0 CPPUNIT_ASSERT_MESSAGE("Wrong MIN value", m_LabelSetImage->GetStatistics()->GetScalarValueMin() == 0); CPPUNIT_ASSERT_MESSAGE("Wrong MAX value", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 7); m_LabelSetImage->GetActiveLabelSet()->SetActiveLabel(6); // Merge label 7 with label 0. Result should be that label 7 is not present any more m_LabelSetImage->MergeLabel(6, 7); CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not remove from the image", m_LabelSetImage->GetStatistics()->GetScalarValueMax() == 6); m_LabelSetImage->GetStatistics()->GetScalarValue2ndMax(); // Count all pixels with value 7 = 823 // Count all pixels with value 6 = 507 // Check if merge label has 507 + 823 = 1330 pixels CPPUNIT_ASSERT_MESSAGE("Label with value 7 was not remove from the image", m_LabelSetImage->GetStatistics()->GetCountOfMaxValuedVoxels() == 1330); } }; MITK_TEST_SUITE_REGISTRATION(mitkLabelSetImage) diff --git a/Modules/Multilabel/mitkLabelSetImage.cpp b/Modules/Multilabel/mitkLabelSetImage.cpp index 71a099210e..507157853d 100644 --- a/Modules/Multilabel/mitkLabelSetImage.cpp +++ b/Modules/Multilabel/mitkLabelSetImage.cpp @@ -1,989 +1,1005 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkLabelSetImage.h" #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImagePixelReadAccessor.h" #include "mitkImagePixelWriteAccessor.h" #include "mitkInteractionConst.h" #include "mitkLookupTableProperty.h" #include "mitkPadImageFilter.h" #include "mitkRenderingManager.h" #include "mitkDICOMSegmentationPropertyHelper.h" #include "mitkDICOMQIPropertyHelper.h" #include #include #include #include #include #include //#include #include template void SetToZero(itk::Image *source) { source->FillBuffer(0); } template void CreateLabelMaskProcessing(mitk::Image *layerImage, mitk::Image *mask, mitk::LabelSet::PixelType index) { mitk::ImagePixelReadAccessor readAccessor(layerImage); mitk::ImagePixelWriteAccessor writeAccessor(mask); std::size_t numberOfPixels = 1; for (int dim = 0; dim < static_cast(VImageDimension); ++dim) numberOfPixels *= static_cast(readAccessor.GetDimension(dim)); auto src = readAccessor.GetData(); auto dest = writeAccessor.GetData(); for (std::size_t i = 0; i < numberOfPixels; ++i) { if (index == *(src + i)) *(dest + i) = 1; } } mitk::LabelSetImage::LabelSetImage() : mitk::Image(), m_ActiveLayer(0), m_activeLayerInvalid(false), m_ExteriorLabel(nullptr) { // Iniitlaize Background Label mitk::Color color; color.Set(0, 0, 0); m_ExteriorLabel = mitk::Label::New(); m_ExteriorLabel->SetColor(color); m_ExteriorLabel->SetName("Exterior"); m_ExteriorLabel->SetOpacity(0.0); m_ExteriorLabel->SetLocked(false); m_ExteriorLabel->SetValue(0); // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } mitk::LabelSetImage::LabelSetImage(const mitk::LabelSetImage &other) : Image(other), m_ActiveLayer(other.GetActiveLayer()), m_activeLayerInvalid(false), m_ExteriorLabel(other.GetExteriorLabel()->Clone()) { for (unsigned int i = 0; i < other.GetNumberOfLayers(); i++) { // Clone LabelSet data mitk::LabelSet::Pointer lsClone = other.GetLabelSet(i)->Clone(); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); lsClone->AddObserver(itk::ModifiedEvent(), command); m_LabelSetContainer.push_back(lsClone); // clone layer Image data mitk::Image::Pointer liClone = other.GetLayerImage(i)->Clone(); m_LayerContainer.push_back(liClone); } // Add some DICOM Tags as properties to segmentation image DICOMSegmentationPropertyHelper::DeriveDICOMSegmentationProperties(this); } void mitk::LabelSetImage::OnLabelSetModified() { Superclass::Modified(); } void mitk::LabelSetImage::SetExteriorLabel(mitk::Label *label) { m_ExteriorLabel = label; } mitk::Label *mitk::LabelSetImage::GetExteriorLabel() { return m_ExteriorLabel; } const mitk::Label *mitk::LabelSetImage::GetExteriorLabel() const { return m_ExteriorLabel; } void mitk::LabelSetImage::Initialize(const mitk::Image *other) { mitk::PixelType pixelType(mitk::MakeScalarPixelType()); if (other->GetDimension() == 2) { const unsigned int dimensions[] = {other->GetDimension(0), other->GetDimension(1), 1}; Superclass::Initialize(pixelType, 3, dimensions); } else { Superclass::Initialize(pixelType, other->GetDimension(), other->GetDimensions()); } auto originalGeometry = other->GetTimeGeometry()->Clone(); this->SetTimeGeometry(originalGeometry); // initialize image memory to zero if (4 == this->GetDimension()) { AccessFixedDimensionByItk(this, SetToZero, 4); } else { AccessByItk(this, SetToZero); } // Transfer some general DICOM properties from the source image to derived image (e.g. Patient information,...) DICOMQIPropertyHelper::DeriveDICOMSourceProperties(other, this); // Add a inital LabelSet ans corresponding image data to the stack AddLayer(); } mitk::LabelSetImage::~LabelSetImage() { m_LabelSetContainer.clear(); } mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) { return m_LayerContainer[layer]; } const mitk::Image *mitk::LabelSetImage::GetLayerImage(unsigned int layer) const { return m_LayerContainer[layer]; } unsigned int mitk::LabelSetImage::GetActiveLayer() const { return m_ActiveLayer; } unsigned int mitk::LabelSetImage::GetNumberOfLayers() const { return m_LabelSetContainer.size(); } void mitk::LabelSetImage::RemoveLayer() { int layerToDelete = GetActiveLayer(); // remove all observers from active label set GetLabelSet(layerToDelete)->RemoveAllObservers(); // set the active layer to one below, if exists. if (layerToDelete != 0) { SetActiveLayer(layerToDelete - 1); } else { // we are deleting layer zero, it should not be copied back into the vector m_activeLayerInvalid = true; } // remove labelset and image data m_LabelSetContainer.erase(m_LabelSetContainer.begin() + layerToDelete); m_LayerContainer.erase(m_LayerContainer.begin() + layerToDelete); if (layerToDelete == 0) { this->SetActiveLayer(layerToDelete); } this->Modified(); } unsigned int mitk::LabelSetImage::AddLayer(mitk::LabelSet::Pointer lset) { mitk::Image::Pointer newImage = mitk::Image::New(); newImage->Initialize(this->GetPixelType(), this->GetDimension(), this->GetDimensions(), this->GetImageDescriptor()->GetNumberOfChannels()); newImage->SetTimeGeometry(this->GetTimeGeometry()->Clone()); if (newImage->GetDimension() < 4) { AccessByItk(newImage, SetToZero); } else { AccessFixedDimensionByItk(newImage, SetToZero, 4); } unsigned int newLabelSetId = this->AddLayer(newImage, lset); return newLabelSetId; } unsigned int mitk::LabelSetImage::AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer lset) { unsigned int newLabelSetId = m_LayerContainer.size(); // Add labelset to layer mitk::LabelSet::Pointer ls; if (lset.IsNotNull()) { ls = lset; } else { ls = mitk::LabelSet::New(); ls->AddLabel(GetExteriorLabel()); ls->SetActiveLabel(0 /*Exterior Label*/); } ls->SetLayer(newLabelSetId); // Add exterior Label to label set // mitk::Label::Pointer exteriorLabel = CreateExteriorLabel(); // push a new working image for the new layer m_LayerContainer.push_back(layerImage); // push a new labelset for the new layer m_LabelSetContainer.push_back(ls); // add modified event listener to LabelSet (listen to LabelSet changes) itk::SimpleMemberCommand::Pointer command = itk::SimpleMemberCommand::New(); command->SetCallbackFunction(this, &mitk::LabelSetImage::OnLabelSetModified); ls->AddObserver(itk::ModifiedEvent(), command); SetActiveLayer(newLabelSetId); // MITK_INFO << GetActiveLayer(); this->Modified(); return newLabelSetId; } void mitk::LabelSetImage::AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet::Pointer labelSet) { if (m_LayerContainer.size() <= layerIdx) { mitkThrow() << "Trying to add labelSet to non-existing layer."; } if (layerIdx < m_LabelSetContainer.size()) { m_LabelSetContainer[layerIdx] = labelSet; } else { while (layerIdx >= m_LabelSetContainer.size()) { mitk::LabelSet::Pointer defaultLabelSet = mitk::LabelSet::New(); defaultLabelSet->AddLabel(GetExteriorLabel()); defaultLabelSet->SetActiveLabel(0 /*Exterior Label*/); defaultLabelSet->SetLayer(m_LabelSetContainer.size()); m_LabelSetContainer.push_back(defaultLabelSet); } m_LabelSetContainer.push_back(labelSet); } } void mitk::LabelSetImage::SetActiveLayer(unsigned int layer) { try { if (4 == this->GetDimension()) { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessFixedDimensionByItk_n(this, ImageToLayerContainerProcessing, 4, (GetActiveLayer())); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessFixedDimensionByItk_n(this, LayerContainerToImageProcessing, 4, (GetActiveLayer())); AfterChangeLayerEvent.Send(); } } else { if ((layer != GetActiveLayer() || m_activeLayerInvalid) && (layer < this->GetNumberOfLayers())) { BeforeChangeLayerEvent.Send(); if (m_activeLayerInvalid) { // We should not write the invalid layer back to the vector m_activeLayerInvalid = false; } else { AccessByItk_1(this, ImageToLayerContainerProcessing, GetActiveLayer()); } m_ActiveLayer = layer; // only at this place m_ActiveLayer should be manipulated!!! Use Getter and Setter AccessByItk_1(this, LayerContainerToImageProcessing, GetActiveLayer()); AfterChangeLayerEvent.Send(); } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::Concatenate(mitk::LabelSetImage *other) { const unsigned int *otherDims = other->GetDimensions(); const unsigned int *thisDims = this->GetDimensions(); if ((otherDims[0] != thisDims[0]) || (otherDims[1] != thisDims[1]) || (otherDims[2] != thisDims[2])) mitkThrow() << "Dimensions do not match."; try { int numberOfLayers = other->GetNumberOfLayers(); for (int layer = 0; layer < numberOfLayers; ++layer) { this->SetActiveLayer(layer); AccessByItk_1(this, ConcatenateProcessing, other); mitk::LabelSet *ls = other->GetLabelSet(layer); auto it = ls->IteratorConstBegin(); auto end = ls->IteratorConstEnd(); it++; // skip exterior while (it != end) { GetLabelSet()->AddLabel((it->second)); // AddLabelEvent.Send(); it++; } } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } this->Modified(); } void mitk::LabelSetImage::ClearBuffer() { try { AccessByItk(this, ClearBufferProcessing); this->Modified(); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue) const { bool exist = false; for (unsigned int lidx = 0; lidx < GetNumberOfLayers(); lidx++) exist |= m_LabelSetContainer[lidx]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabel(PixelType pixelValue, unsigned int layer) const { bool exist = m_LabelSetContainer[layer]->ExistLabel(pixelValue); return exist; } bool mitk::LabelSetImage::ExistLabelSet(unsigned int layer) const { return layer < m_LabelSetContainer.size(); } void mitk::LabelSetImage::MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer) { try { AccessByItk_2(this, MergeLabelProcessing, pixelValue, sourcePixelValue); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::MergeLabels(PixelType pixelValue, std::vector& vectorOfSourcePixelValues, unsigned int layer) { try { for (unsigned int idx = 0; idx < vectorOfSourcePixelValues.size(); idx++) { AccessByItk_2(this, MergeLabelProcessing, pixelValue, vectorOfSourcePixelValues[idx]); } } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } GetLabelSet(layer)->SetActiveLabel(pixelValue); Modified(); } void mitk::LabelSetImage::RemoveLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer) { for (unsigned int idx = 0; idx < VectorOfLabelPixelValues.size(); idx++) { GetLabelSet(layer)->RemoveLabel(VectorOfLabelPixelValues[idx]); EraseLabel(VectorOfLabelPixelValues[idx], layer); } } void mitk::LabelSetImage::EraseLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer) { for (unsigned int i = 0; i < VectorOfLabelPixelValues.size(); i++) { this->EraseLabel(VectorOfLabelPixelValues[i], layer); } } void mitk::LabelSetImage::EraseLabel(PixelType pixelValue, unsigned int layer) { try { AccessByItk_2(this, EraseLabelProcessing, pixelValue, layer); } catch (itk::ExceptionObject &e) { mitkThrow() << e.GetDescription(); } Modified(); } mitk::Label *mitk::LabelSetImage::GetActiveLabel(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else - return m_LabelSetContainer[layer]->GetActiveLabel();; + return m_LabelSetContainer[layer]->GetActiveLabel(); +} + +const mitk::Label* mitk::LabelSetImage::GetActiveLabel(unsigned int layer) const +{ + if (m_LabelSetContainer.size() <= layer) + return nullptr; + else + return m_LabelSetContainer[layer]->GetActiveLabel(); } mitk::Label *mitk::LabelSetImage::GetLabel(PixelType pixelValue, unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer]->GetLabel(pixelValue); } mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } const mitk::LabelSet *mitk::LabelSetImage::GetLabelSet(unsigned int layer) const { if (m_LabelSetContainer.size() <= layer) return nullptr; else return m_LabelSetContainer[layer].GetPointer(); } mitk::LabelSet *mitk::LabelSetImage::GetActiveLabelSet() { if (m_LabelSetContainer.size() == 0) return nullptr; else return m_LabelSetContainer[GetActiveLayer()].GetPointer(); } +const mitk::LabelSet* mitk::LabelSetImage::GetActiveLabelSet() const +{ + if (m_LabelSetContainer.size() == 0) + return nullptr; + else + return m_LabelSetContainer[GetActiveLayer()].GetPointer(); +} + void mitk::LabelSetImage::UpdateCenterOfMass(PixelType pixelValue, unsigned int layer) { if (4 == this->GetDimension()) { AccessFixedDimensionByItk_2(this, CalculateCenterOfMassProcessing, 4, pixelValue, layer); } else { AccessByItk_2(this, CalculateCenterOfMassProcessing, pixelValue, layer); } } unsigned int mitk::LabelSetImage::GetNumberOfLabels(unsigned int layer) const { return m_LabelSetContainer[layer]->GetNumberOfLabels(); } unsigned int mitk::LabelSetImage::GetTotalNumberOfLabels() const { unsigned int totalLabels(0); auto layerIter = m_LabelSetContainer.begin(); for (; layerIter != m_LabelSetContainer.end(); ++layerIter) totalLabels += (*layerIter)->GetNumberOfLabels(); return totalLabels; } void mitk::LabelSetImage::MaskStamp(mitk::Image *mask, bool forceOverwrite) { try { mitk::PadImageFilter::Pointer padImageFilter = mitk::PadImageFilter::New(); padImageFilter->SetInput(0, mask); padImageFilter->SetInput(1, this); padImageFilter->SetPadConstant(0); padImageFilter->SetBinaryFilter(false); padImageFilter->SetLowerThreshold(0); padImageFilter->SetUpperThreshold(1); padImageFilter->Update(); mitk::Image::Pointer paddedMask = padImageFilter->GetOutput(); if (paddedMask.IsNull()) return; AccessByItk_2(this, MaskStampProcessing, paddedMask, forceOverwrite); } catch (...) { mitkThrow() << "Could not stamp the provided mask on the selected label."; } } mitk::Image::Pointer mitk::LabelSetImage::CreateLabelMask(PixelType index, bool useActiveLayer, unsigned int layer) { auto previousActiveLayer = this->GetActiveLayer(); auto mask = mitk::Image::New(); try { mask->Initialize(this); auto byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < mask->GetDimension(); ++dim) byteSize *= mask->GetDimension(dim); { ImageWriteAccessor accessor(mask); memset(accessor.GetData(), 0, byteSize); } if (!useActiveLayer) this->SetActiveLayer(layer); if (4 == this->GetDimension()) { ::CreateLabelMaskProcessing<4>(this, mask, index); } else if (3 == this->GetDimension()) { ::CreateLabelMaskProcessing(this, mask, index); } else { mitkThrow(); } } catch (...) { if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); mitkThrow() << "Could not create a mask out of the selected label."; } if (!useActiveLayer) this->SetActiveLayer(previousActiveLayer); return mask; } void mitk::LabelSetImage::InitializeByLabeledImage(mitk::Image::Pointer image) { if (image.IsNull() || image->IsEmpty() || !image->IsInitialized()) mitkThrow() << "Invalid labeled image."; try { this->Initialize(image); unsigned int byteSize = sizeof(LabelSetImage::PixelType); for (unsigned int dim = 0; dim < image->GetDimension(); ++dim) { byteSize *= image->GetDimension(dim); } mitk::ImageWriteAccessor *accessor = new mitk::ImageWriteAccessor(static_cast(this)); memset(accessor->GetData(), 0, byteSize); delete accessor; auto geometry = image->GetTimeGeometry()->Clone(); this->SetTimeGeometry(geometry); if (image->GetDimension() == 3) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 3); } else if (image->GetDimension() == 4) { AccessTwoImagesFixedDimensionByItk(this, image, InitializeByLabeledImageProcessing, 4); } else { mitkThrow() << image->GetDimension() << "-dimensional label set images not yet supported"; } } catch (...) { mitkThrow() << "Could not intialize by provided labeled image."; } this->Modified(); } template void mitk::LabelSetImage::InitializeByLabeledImageProcessing(LabelSetImageType *labelSetImage, ImageType *image) { typedef itk::ImageRegionConstIteratorWithIndex SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; TargetIteratorType targetIter(labelSetImage, labelSetImage->GetRequestedRegion()); targetIter.GoToBegin(); SourceIteratorType sourceIter(image, image->GetRequestedRegion()); sourceIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { auto sourceValue = static_cast(sourceIter.Get()); targetIter.Set(sourceValue); if (!this->ExistLabel(sourceValue)) { std::stringstream name; name << "object-" << sourceValue; double rgba[4]; m_LabelSetContainer[this->GetActiveLayer()]->GetLookupTable()->GetTableValue(sourceValue, rgba); mitk::Color color; color.SetRed(rgba[0]); color.SetGreen(rgba[1]); color.SetBlue(rgba[2]); auto label = mitk::Label::New(); label->SetName(name.str().c_str()); label->SetColor(color); label->SetOpacity(rgba[3]); label->SetValue(sourceValue); this->GetLabelSet()->AddLabel(label); if (GetActiveLabelSet()->GetNumberOfLabels() >= mitk::Label::MAX_LABEL_VALUE || sourceValue >= mitk::Label::MAX_LABEL_VALUE) this->AddLayer(); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::MaskStampProcessing(ImageType *itkImage, mitk::Image *mask, bool forceOverwrite) { typename ImageType::Pointer itkMask; mitk::CastToItkImage(mask, itkMask); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkMask, itkMask->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkImage, itkImage->GetLargestPossibleRegion()); targetIter.GoToBegin(); int activeLabel = this->GetActiveLabel(GetActiveLayer())->GetValue(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != 0) && (forceOverwrite || !this->GetLabel(targetValue)->GetLocked())) // skip exterior and locked labels { targetIter.Set(activeLabel); } ++sourceIter; ++targetIter; } this->Modified(); } template void mitk::LabelSetImage::CalculateCenterOfMassProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int layer) { // for now, we just retrieve the voxel in the middle typedef itk::ImageRegionConstIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); std::vector indexVector; while (!iter.IsAtEnd()) { // TODO fix comparison warning more effective if (iter.Get() == pixelValue) { indexVector.push_back(iter.GetIndex()); } ++iter; } mitk::Point3D pos; pos.Fill(0.0); if (!indexVector.empty()) { typename itk::ImageRegionConstIteratorWithIndex::IndexType centerIndex; centerIndex = indexVector.at(indexVector.size() / 2); if (centerIndex.GetIndexDimension() == 3) { pos[0] = centerIndex[0]; pos[1] = centerIndex[1]; pos[2] = centerIndex[2]; } else return; } GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassIndex(pos); this->GetSlicedGeometry()->IndexToWorld(pos, pos); // TODO: TimeGeometry? GetLabelSet(layer)->GetLabel(pixelValue)->SetCenterOfMassCoordinates(pos); } template void mitk::LabelSetImage::ClearBufferProcessing(ImageType *itkImage) { itkImage->FillBuffer(0); } // todo: concatenate all layers and not just the active one template void mitk::LabelSetImage::ConcatenateProcessing(ImageType *itkTarget, mitk::LabelSetImage *other) { typename ImageType::Pointer itkSource = ImageType::New(); mitk::CastToItkImage(other, itkSource); typedef itk::ImageRegionConstIterator ConstIteratorType; typedef itk::ImageRegionIterator IteratorType; ConstIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); IteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); int numberOfTargetLabels = this->GetNumberOfLabels(GetActiveLayer()) - 1; // skip exterior sourceIter.GoToBegin(); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { PixelType sourceValue = sourceIter.Get(); PixelType targetValue = targetIter.Get(); if ((sourceValue != 0) && !this->GetLabel(targetValue)->GetLocked()) // skip exterior and locked labels { targetIter.Set(sourceValue + numberOfTargetLabels); } ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::LayerContainerToImageProcessing(itk::Image *target, unsigned int layer) { typedef itk::Image ImageType; typename ImageType::Pointer itkSource; // mitk::CastToItkImage(m_LayerContainer[layer], itkSource); itkSource = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(itkSource, itkSource->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(target, target->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const { typedef itk::Image ImageType; typename ImageType::Pointer itkTarget; // mitk::CastToItkImage(m_LayerContainer[layer], itkTarget); itkTarget = ImageToItkImage(m_LayerContainer[layer]); typedef itk::ImageRegionConstIterator SourceIteratorType; typedef itk::ImageRegionIterator TargetIteratorType; SourceIteratorType sourceIter(source, source->GetLargestPossibleRegion()); sourceIter.GoToBegin(); TargetIteratorType targetIter(itkTarget, itkTarget->GetLargestPossibleRegion()); targetIter.GoToBegin(); while (!sourceIter.IsAtEnd()) { targetIter.Set(sourceIter.Get()); ++sourceIter; ++targetIter; } } template void mitk::LabelSetImage::EraseLabelProcessing(ImageType *itkImage, PixelType pixelValue, unsigned int /*layer*/) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { PixelType value = iter.Get(); if (value == pixelValue) { iter.Set(0); } ++iter; } } template void mitk::LabelSetImage::MergeLabelProcessing(ImageType *itkImage, PixelType pixelValue, PixelType index) { typedef itk::ImageRegionIterator IteratorType; IteratorType iter(itkImage, itkImage->GetLargestPossibleRegion()); iter.GoToBegin(); while (!iter.IsAtEnd()) { if (iter.Get() == index) { iter.Set(pixelValue); } ++iter; } } bool mitk::Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose) { bool returnValue = true; /* LabelSetImage members */ MITK_INFO(verbose) << "--- LabelSetImage Equal ---"; // number layers returnValue = leftHandSide.GetNumberOfLayers() == rightHandSide.GetNumberOfLayers(); if (!returnValue) { MITK_INFO(verbose) << "Number of layers not equal."; return false; } // total number labels returnValue = leftHandSide.GetTotalNumberOfLabels() == rightHandSide.GetTotalNumberOfLabels(); if (!returnValue) { MITK_INFO(verbose) << "Total number of labels not equal."; return false; } // active layer returnValue = leftHandSide.GetActiveLayer() == rightHandSide.GetActiveLayer(); if (!returnValue) { MITK_INFO(verbose) << "Active layer not equal."; return false; } if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // working image data returnValue = mitk::Equal((const mitk::Image &)leftHandSide, (const mitk::Image &)rightHandSide, eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Working image data not equal."; return false; } } for (unsigned int layerIndex = 0; layerIndex < leftHandSide.GetNumberOfLayers(); layerIndex++) { if (4 == leftHandSide.GetDimension()) { MITK_INFO(verbose) << "Can not compare image data for 4D images - skipping check."; } else { // layer image data returnValue = mitk::Equal(*leftHandSide.GetLayerImage(layerIndex), *rightHandSide.GetLayerImage(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer image data not equal."; return false; } } // layer labelset data returnValue = mitk::Equal(*leftHandSide.GetLabelSet(layerIndex), *rightHandSide.GetLabelSet(layerIndex), eps, verbose); if (!returnValue) { MITK_INFO(verbose) << "Layer labelset data not equal."; return false; } } return returnValue; } diff --git a/Modules/Multilabel/mitkLabelSetImage.h b/Modules/Multilabel/mitkLabelSetImage.h index e65e6752e8..2d02b05287 100644 --- a/Modules/Multilabel/mitkLabelSetImage.h +++ b/Modules/Multilabel/mitkLabelSetImage.h @@ -1,340 +1,342 @@ /*============================================================================ 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 __mitkLabelSetImage_H_ #define __mitkLabelSetImage_H_ #include #include #include namespace mitk { //##Documentation //## @brief LabelSetImage class for handling labels and layers in a segmentation session. //## //## Handles operations for adding, removing, erasing and editing labels and layers. //## @ingroup Data class MITKMULTILABEL_EXPORT LabelSetImage : public Image { public: mitkClassMacro(LabelSetImage, Image); itkNewMacro(Self); typedef mitk::Label::PixelType PixelType; /** * \brief BeforeChangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset should be changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> BeforeChangeLayerEvent; /** * \brief AfterchangeLayerEvent (e.g. used for GUI integration) * As soon as active labelset was changed, the signal emits. * Emitted by SetActiveLayer(int layer); */ Message<> AfterChangeLayerEvent; /** * @brief Initialize an empty mitk::LabelSetImage using the information * of an mitk::Image * @param image the image which is used for initializing the mitk::LabelSetImage */ using mitk::Image::Initialize; void Initialize(const mitk::Image *image) override; /** * \brief */ void Concatenate(mitk::LabelSetImage *image); /** * \brief */ void ClearBuffer(); /** * @brief Merges the mitk::Label with a given target value with the active label * * @param pixelValue the value of the label that should be the new merged label * @param sourcePixelValue the value of the label that should be merged into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabel(PixelType pixelValue, PixelType sourcePixelValue, unsigned int layer = 0); /** * @brief Merges a list of mitk::Labels with the mitk::Label that has a specific value * * @param pixelValue the value of the label that should be the new merged label * @param vectorOfSourcePixelValues the list of label values that should be merge into the specified one * @param layer the layer in which the merge should be performed */ void MergeLabels(PixelType pixelValue, std::vector& vectorOfSourcePixelValues, unsigned int layer = 0); /** * \brief */ void UpdateCenterOfMass(PixelType pixelValue, unsigned int layer = 0); /** * @brief Removes labels from the mitk::LabelSet of given layer. * Calls mitk::LabelSetImage::EraseLabels() which also removes the labels from within the image. * @param VectorOfLabelPixelValues a list of labels to be removed * @param layer the layer in which the labels should be removed */ void RemoveLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer = 0); /** * @brief Erases the label with the given value in the given layer from the underlying image. * The label itself will not be erased from the respective mitk::LabelSet. In order to * remove the label itself use mitk::LabelSetImage::RemoveLabels() * @param pixelValue the label which will be remove from the image * @param layer the layer in which the label should be removed */ void EraseLabel(PixelType pixelValue, unsigned int layer = 0); /** * @brief Similar to mitk::LabelSetImage::EraseLabel() this funtion erase a list of labels from the image * @param VectorOfLabelPixelValues the list of labels that should be remove * @param layer the layer for which the labels should be removed */ void EraseLabels(std::vector &VectorOfLabelPixelValues, unsigned int layer = 0); /** * \brief Returns true if the value exists in one of the labelsets*/ bool ExistLabel(PixelType pixelValue) const; /** * @brief Checks if a label exists in a certain layer * @param pixelValue the label value * @param layer the layer in which should be searched for the label * @return true if the label exists otherwise false */ bool ExistLabel(PixelType pixelValue, unsigned int layer) const; /** * \brief Returns true if the labelset exists*/ bool ExistLabelSet(unsigned int layer) const; /** * @brief Returns the active label of a specific layer * @param layer the layer ID for which the active label should be returned * @return the active label of the specified layer */ mitk::Label *GetActiveLabel(unsigned int layer = 0); + const mitk::Label* GetActiveLabel(unsigned int layer = 0) const; /** * @brief Returns the mitk::Label with the given pixelValue and for the given layer * @param pixelValue the pixel value of the label * @param layer the layer in which the labels should be located * @return the mitk::Label if available otherwise nullptr */ mitk::Label *GetLabel(PixelType pixelValue, unsigned int layer = 0) const; /** * @brief Returns the currently active mitk::LabelSet * @return the mitk::LabelSet of the active layer or nullptr if non is present */ mitk::LabelSet *GetActiveLabelSet(); + const mitk::LabelSet* GetActiveLabelSet() const; /** * @brief Gets the mitk::LabelSet for the given layer * @param layer the layer for which the mitk::LabelSet should be retrieved * @return the respective mitk::LabelSet or nullptr if non exists for the given layer */ mitk::LabelSet *GetLabelSet(unsigned int layer = 0); const mitk::LabelSet *GetLabelSet(unsigned int layer = 0) const; /** * @brief Gets the ID of the currently active layer * @return the ID of the active layer */ unsigned int GetActiveLayer() const; /** * @brief Get the number of all existing mitk::Labels for a given layer * @param layer the layer ID for which the active mitk::Labels should be retrieved * @return the number of all existing mitk::Labels for the given layer */ unsigned int GetNumberOfLabels(unsigned int layer = 0) const; /** * @brief Returns the number of all labels summed up across all layers * @return the overall number of labels across all layers */ unsigned int GetTotalNumberOfLabels() const; // This function will need to be ported to an external class // it requires knowledge of pixeltype and dimension and includes // too much algorithm to be sensibly part of a data class ///** // * \brief */ // void SurfaceStamp(mitk::Surface* surface, bool forceOverwrite); /** * \brief */ mitk::Image::Pointer CreateLabelMask(PixelType index, bool useActiveLayer = true, unsigned int layer = 0); /** * @brief Initialize a new mitk::LabelSetImage by an given image. * For all distinct pixel values of the parameter image new labels will * be created. If the number of distinct pixel values exceeds mitk::Label::MAX_LABEL_VALUE * a new layer will be created * @param image the image which is used for initialization */ void InitializeByLabeledImage(mitk::Image::Pointer image); /** * \brief */ void MaskStamp(mitk::Image *mask, bool forceOverwrite); /** * \brief */ void SetActiveLayer(unsigned int layer); /** * \brief */ unsigned int GetNumberOfLayers() const; /** * @brief Adds a new layer to the LabelSetImage. The new layer will be set as the active one * @param layer a mitk::LabelSet which will be set as new layer. * @return the layer ID of the new layer */ unsigned int AddLayer(mitk::LabelSet::Pointer layer = nullptr); /** * \brief Add a layer based on a provided mitk::Image * \param layerImage is added to the vector of label images * \param lset a label set that will be added to the new layer if provided *\return the layer ID of the new layer */ unsigned int AddLayer(mitk::Image::Pointer layerImage, mitk::LabelSet::Pointer lset = nullptr); /** * \brief Add a LabelSet to an existing layer * * This will replace an existing labelSet if one exists. Throws an exceptions if you are trying * to add a labelSet to a non-existing layer. * * If there are no labelSets for layers with an id less than layerIdx default ones will be added * for them. * * \param layerIdx The index of the layer the LabelSet should be added to * \param labelSet The LabelSet that should be added */ void AddLabelSetToLayer(const unsigned int layerIdx, const mitk::LabelSet::Pointer labelSet); /** * @brief Removes the active layer and the respective mitk::LabelSet and image information. * The new active layer is the one below, if exists */ void RemoveLayer(); /** * \brief */ mitk::Image *GetLayerImage(unsigned int layer); const mitk::Image *GetLayerImage(unsigned int layer) const; void OnLabelSetModified(); /** * @brief Sets the label which is used as default exterior label when creating a new layer * @param label the label which will be used as new exterior label */ void SetExteriorLabel(mitk::Label *label); /** * @brief Gets the mitk::Label which is used as default exterior label * @return the exterior mitk::Label */ mitk::Label *GetExteriorLabel(); const mitk::Label *GetExteriorLabel() const; protected: mitkCloneMacro(Self); LabelSetImage(); LabelSetImage(const LabelSetImage &other); ~LabelSetImage() override; template void ChangeLayerProcessing(ImageType1 *source, ImageType2 *target); template void LayerContainerToImageProcessing(itk::Image *source, unsigned int layer); template void ImageToLayerContainerProcessing(itk::Image *source, unsigned int layer) const; template void CalculateCenterOfMassProcessing(ImageType *input, PixelType index, unsigned int layer); template void ClearBufferProcessing(ImageType *input); template void EraseLabelProcessing(ImageType *input, PixelType index, unsigned int layer); // template < typename ImageType > // void ReorderLabelProcessing( ImageType* input, int index, int layer); template void MergeLabelProcessing(ImageType *input, PixelType pixelValue, PixelType index); template void ConcatenateProcessing(ImageType *input, mitk::LabelSetImage *other); template void MaskStampProcessing(ImageType *input, mitk::Image *mask, bool forceOverwrite); template void InitializeByLabeledImageProcessing(LabelSetImageType *input, ImageType *other); std::vector m_LabelSetContainer; std::vector m_LayerContainer; int m_ActiveLayer; bool m_activeLayerInvalid; mitk::Label::Pointer m_ExteriorLabel; }; /** * @brief Equal A function comparing two label set images for beeing equal in meta- and imagedata * * @ingroup MITKTestingAPI * * Following aspects are tested for equality: * - LabelSetImage members * - working image data * - layer image data * - labels in label set * * @param rightHandSide An image to be compared * @param leftHandSide An image to be compared * @param eps Tolerance for comparison. You can use mitk::eps in most cases. * @param verbose Flag indicating if the user wants detailed console output or not. * @return true, if all subsequent comparisons are true, false otherwise */ MITKMULTILABEL_EXPORT bool Equal(const mitk::LabelSetImage &leftHandSide, const mitk::LabelSetImage &rightHandSide, ScalarType eps, bool verbose); } // namespace mitk #endif // __mitkLabelSetImage_H_ diff --git a/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp b/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.cpp index 1b0c4598da..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(mitk::Image *imageVolume, - Image *slice, - SlicedGeometry3D *sliceGeometry, - unsigned int timestep, - BaseGeometry *currentWorldGeometry) +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/Algorithms/mitkDiffSliceOperation.h b/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.h index 1ed4d8aa0c..9647976b40 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.h +++ b/Modules/Segmentation/Algorithms/mitkDiffSliceOperation.h @@ -1,109 +1,101 @@ /*============================================================================ 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 mitkDiffSliceOperation_h_Included #define mitkDiffSliceOperation_h_Included #include "mitkCompressedImageContainer.h" #include #include #include namespace mitk { class Image; /** \brief An Operation for applying an edited slice to the volume. \sa DiffSliceOperationApplier The information for the operation is specified by properties: imageVolume the volume where the slice was extracted from. slice the slice to be applied. timestep the timestep in an 4D image. currentWorldGeometry specifies the axis where the slice has to be applied in the volume. This Operation can be used to realize undo-redo functionality for e.g. segmentation purposes. */ class MITKSEGMENTATION_EXPORT DiffSliceOperation : public Operation { public: mitkClassMacro(DiffSliceOperation, OperationActor); // itkFactorylessNewMacro(Self) // itkCloneMacro(Self) // mitkNewMacro4Param(DiffSliceOperation,mitk::Image,mitk::Image,unsigned int, mitk::PlaneGeometry); /** \brief Creates an empty instance. Note that it is not valid yet. The properties of the object have to be set. */ DiffSliceOperation(); /** \brief */ DiffSliceOperation(mitk::Image *imageVolume, - mitk::Image *slice, - SlicedGeometry3D *sliceGeometry, - unsigned int timestep, - BaseGeometry *currentWorldGeometry); + const mitk::Image *slice, + const SlicedGeometry3D *sliceGeometry, + const TimeStepType timestep, + const BaseGeometry *currentWorldGeometry); /** \brief Check if it is a valid operation.*/ bool IsValid(); - /** \brief Set the image volume.*/ - void SetImage(mitk::Image *image) { this->m_Image = image; } /** \brief Get th image volume.*/ mitk::Image *GetImage() { return this->m_Image; } - /** \brief Set thee slice to be applied.*/ - void SetImage(vtkImageData *slice) { this->m_Slice = slice; } + const mitk::Image* GetImage() const { return this->m_Image; } + /** \brief Get the slice that is applied in the operation.*/ Image::Pointer GetSlice(); - /** \brief Get timeStep.*/ - void SetTimeStep(unsigned int timestep) { this->m_TimeStep = timestep; } /** \brief Set timeStep*/ - unsigned int GetTimeStep() { return this->m_TimeStep; } - /** \brief Set the axis where the slice has to be applied in the volume.*/ - void SetSliceGeometry(SlicedGeometry3D *sliceGeometry) { this->m_SliceGeometry = sliceGeometry; } + TimeStepType GetTimeStep() const { return this->m_TimeStep; } /** \brief Get the axis where the slice has to be applied in the volume.*/ - SlicedGeometry3D *GetSliceGeometry() { return this->m_SliceGeometry; } - /** \brief Set the axis where the slice has to be applied in the volume.*/ - void SetCurrentWorldGeometry(BaseGeometry *worldGeometry) { this->m_WorldGeometry = worldGeometry; } + const SlicedGeometry3D *GetSliceGeometry() const { return this->m_SliceGeometry; } /** \brief Get the axis where the slice has to be applied in the volume.*/ - BaseGeometry *GetWorldGeometry() { return this->m_WorldGeometry; } + const BaseGeometry *GetWorldGeometry() const { return this->m_WorldGeometry; } protected: ~DiffSliceOperation() override; /** \brief Callback for image observer.*/ void OnImageDeleted(); CompressedImageContainer::Pointer m_zlibSliceContainer; mitk::Image *m_Image; vtkSmartPointer m_Slice; - SlicedGeometry3D::Pointer m_SliceGeometry; + SlicedGeometry3D::ConstPointer m_SliceGeometry; - unsigned int m_TimeStep; + TimeStepType m_TimeStep; - BaseGeometry::Pointer m_WorldGeometry; + BaseGeometry::ConstPointer m_WorldGeometry; bool m_ImageIsValid; unsigned long m_DeleteObserverTag; mitk::BaseGeometry::ConstPointer m_GuardReferenceGeometry; }; } #endif diff --git a/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp b/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp index b34a65bc09..6c666c1b36 100644 --- a/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp +++ b/Modules/Segmentation/Algorithms/mitkDiffSliceOperationApplier.cpp @@ -1,89 +1,89 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkDiffSliceOperationApplier.h" #include "mitkDiffSliceOperation.h" #include "mitkRenderingManager.h" #include "mitkSegTool2D.h" #include #include // VTK #include mitk::DiffSliceOperationApplier::DiffSliceOperationApplier() { } mitk::DiffSliceOperationApplier::~DiffSliceOperationApplier() { } void mitk::DiffSliceOperationApplier::ExecuteOperation(Operation *operation) { auto *imageOperation = dynamic_cast(operation); // as we only support DiffSliceOperation return if operation is not type of DiffSliceOperation if (!imageOperation) return; // chak if the operation is valid if (imageOperation->IsValid()) { // the actual overwrite filter (vtk) vtkSmartPointer reslice = vtkSmartPointer::New(); mitk::Image::Pointer slice = imageOperation->GetSlice(); // Set the slice as 'input' reslice->SetInputSlice(slice->GetVtkImageData()); // set overwrite mode to true to write back to the image volume reslice->SetOverwriteMode(true); reslice->Modified(); // a wrapper for vtkImageOverwrite mitk::ExtractSliceFilter::Pointer extractor = mitk::ExtractSliceFilter::New(reslice); extractor->SetInput(imageOperation->GetImage()); extractor->SetTimeStep(imageOperation->GetTimeStep()); - extractor->SetWorldGeometry(dynamic_cast(imageOperation->GetWorldGeometry())); + extractor->SetWorldGeometry(dynamic_cast(imageOperation->GetWorldGeometry())); extractor->SetVtkOutputRequest(true); extractor->SetResliceTransformByGeometry(imageOperation->GetImage()->GetGeometry(imageOperation->GetTimeStep())); extractor->Modified(); extractor->Update(); // make sure the modification is rendered RenderingManager::GetInstance()->RequestUpdateAll(); imageOperation->GetImage()->Modified(); mitk::ExtractSliceFilter::Pointer extractor2 = mitk::ExtractSliceFilter::New(); extractor2->SetInput(imageOperation->GetImage()); extractor2->SetTimeStep(imageOperation->GetTimeStep()); - extractor2->SetWorldGeometry(dynamic_cast(imageOperation->GetWorldGeometry())); + extractor2->SetWorldGeometry(dynamic_cast(imageOperation->GetWorldGeometry())); extractor2->SetResliceTransformByGeometry(imageOperation->GetImage()->GetGeometry(imageOperation->GetTimeStep())); extractor2->Modified(); extractor2->Update(); // TODO Move this code to SurfaceInterpolationController! mitk::Image::Pointer slice2 = extractor2->GetOutput(); - mitk::PlaneGeometry::Pointer plane = dynamic_cast(imageOperation->GetWorldGeometry()); + mitk::PlaneGeometry::ConstPointer plane = dynamic_cast(imageOperation->GetWorldGeometry()); slice2->DisconnectPipeline(); mitk::SegTool2D::UpdateSurfaceInterpolation(slice2, imageOperation->GetImage(), plane, true); } } mitk::DiffSliceOperationApplier *mitk::DiffSliceOperationApplier::GetInstance() { static auto *s_Instance = new DiffSliceOperationApplier(); return s_Instance; } diff --git a/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h index 1b923b58b2..b3c2f482d5 100644 --- a/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h +++ b/Modules/Segmentation/Algorithms/mitkImageLiveWireContourModelFilter.h @@ -1,162 +1,156 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef _mitkImageLiveWireContourModelFilter_h__ #define _mitkImageLiveWireContourModelFilter_h__ #include "mitkCommon.h" #include "mitkContourModel.h" #include "mitkContourModelSource.h" #include #include #include #include #include #include namespace mitk { /** \brief Calculates a LiveWire contour between two points in an image. For defining costs between two pixels specific features are extraced from the image and tranformed into a single cost value. \sa ShortestPathCostFunctionLiveWire The filter is able to create dynamic cost tranfer map and thus use on the fly training. \note On the fly training will only be used for next update. The computation uses the last calculated segment to map cost according to features in the area of the segment. - For time resolved purposes use ImageLiveWireContourModelFilter::SetTimestep( unsigned int ) to create the LiveWire - contour - at a specific timestep. + Caution: time support currently not available. Filter will always work on the first + timestep in its current implementation. \ingroup ContourModelFilters \ingroup Process */ class MITKSEGMENTATION_EXPORT ImageLiveWireContourModelFilter : public ContourModelSource { public: mitkClassMacro(ImageLiveWireContourModelFilter, ContourModelSource); itkFactorylessNewMacro(Self); itkCloneMacro(Self); typedef ContourModel OutputType; typedef OutputType::Pointer OutputTypePointer; typedef mitk::Image InputType; typedef itk::Image InternalImageType; typedef itk::ShortestPathImageFilter ShortestPathImageFilterType; typedef itk::ShortestPathCostFunctionLiveWire CostFunctionType; typedef std::vector> ShortestPathType; /** \brief start point in world coordinates*/ itkSetMacro(StartPoint, mitk::Point3D); itkGetMacro(StartPoint, mitk::Point3D); /** \brief end point in woorld coordinates*/ itkSetMacro(EndPoint, mitk::Point3D); itkGetMacro(EndPoint, mitk::Point3D); /** \brief Create dynamic cost tranfer map - use on the fly training. \note On the fly training will be used for next update only. The computation uses the last calculated segment to map cost according to features in the area of the segment. */ itkSetMacro(UseDynamicCostMap, bool); itkGetMacro(UseDynamicCostMap, bool); - /** \brief Actual time step - */ - itkSetMacro(TimeStep, unsigned int); - itkGetMacro(TimeStep, unsigned int); - /** \brief Clear all repulsive points used in the cost function */ void ClearRepulsivePoints(); /** \brief Set a vector with repulsive points to use in the cost function */ void SetRepulsivePoints(const ShortestPathType &points); /** \brief Add a single repulsive point to the cost function */ void AddRepulsivePoint(const itk::Index<2> &idx); /** \brief Remove a single repulsive point from the cost function */ void RemoveRepulsivePoint(const itk::Index<2> &idx); virtual void SetInput(const InputType *input); using Superclass::SetInput; virtual void SetInput(unsigned int idx, const InputType *input); const InputType *GetInput(void); const InputType *GetInput(unsigned int idx); virtual OutputType *GetOutput(); virtual void DumpMaskImage(); /** \brief Create dynamic cost tranfer map - on the fly training*/ bool CreateDynamicCostMap(mitk::ContourModel *path = nullptr); protected: ImageLiveWireContourModelFilter(); ~ImageLiveWireContourModelFilter() override; void GenerateOutputInformation() override{}; void GenerateData() override; void UpdateLiveWire(); /** \brief start point in worldcoordinates*/ mitk::Point3D m_StartPoint; /** \brief end point in woorldcoordinates*/ mitk::Point3D m_EndPoint; /** \brief Start point in index*/ mitk::Point3D m_StartPointInIndex; /** \brief End point in index*/ mitk::Point3D m_EndPointInIndex; /** \brief The cost function to compute costs between two pixels*/ CostFunctionType::Pointer m_CostFunction; /** \brief Shortest path filter according to cost function m_CostFunction*/ ShortestPathImageFilterType::Pointer m_ShortestPathFilter; /** \brief Flag to use a dynmic cost map or not*/ bool m_UseDynamicCostMap; unsigned int m_TimeStep; template void ItkPreProcessImage(const itk::Image *inputImage); template void CreateDynamicCostMapByITK(const itk::Image *inputImage, mitk::ContourModel *path = nullptr); InternalImageType::Pointer m_InternalImage; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp index 96ba360d0f..647fa8b80d 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.cpp @@ -1,97 +1,130 @@ /*============================================================================ 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 "mitkAutoSegmentationTool.h" #include "mitkImage.h" #include "mitkToolManager.h" #include mitk::AutoSegmentationTool::AutoSegmentationTool() : Tool("dummy"), m_OverwriteExistingSegmentation(false) { } +mitk::AutoSegmentationTool::AutoSegmentationTool(const char* interactorType, const us::Module* interactorModule) : Tool(interactorType, interactorModule), m_OverwriteExistingSegmentation(false) +{ +} + mitk::AutoSegmentationTool::~AutoSegmentationTool() { } +void mitk::AutoSegmentationTool::Activated() +{ + Superclass::Activated(); + + m_NoneOverwriteTargetSegmentationNode = nullptr; +} + +void mitk::AutoSegmentationTool::Deactivated() +{ + m_NoneOverwriteTargetSegmentationNode = nullptr; + + Superclass::Deactivated(); +} + const char *mitk::AutoSegmentationTool::GetGroup() const { return "autoSegmentation"; } mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimeStep(const mitk::Image* image, unsigned int timestep) { if (nullptr == image) return image; if (image->GetDimension() != 4) return image; mitk::ImageTimeSelector::Pointer imageTimeSelector = mitk::ImageTimeSelector::New(); imageTimeSelector->SetInput(image); imageTimeSelector->SetTimeNr(static_cast(timestep)); imageTimeSelector->UpdateLargestPossibleRegion(); return imageTimeSelector->GetOutput(); } mitk::Image::ConstPointer mitk::AutoSegmentationTool::GetImageByTimePoint(const mitk::Image* image, TimePointType timePoint) { if (nullptr == image) return image; if (!image->GetTimeGeometry()->IsValidTimePoint(timePoint)) return nullptr; return AutoSegmentationTool::GetImageByTimeStep(image, image->GetTimeGeometry()->TimePointToTimeStep(timePoint)); } void mitk::AutoSegmentationTool::SetOverwriteExistingSegmentation(bool overwrite) { - m_OverwriteExistingSegmentation = overwrite; + if (m_OverwriteExistingSegmentation != overwrite) + { + m_OverwriteExistingSegmentation = overwrite; + m_NoneOverwriteTargetSegmentationNode = nullptr; + } } std::string mitk::AutoSegmentationTool::GetCurrentSegmentationName() { if (m_ToolManager->GetWorkingData(0)) return m_ToolManager->GetWorkingData(0)->GetName(); else return ""; } -mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() +mitk::DataNode *mitk::AutoSegmentationTool::GetTargetSegmentationNode() const { mitk::DataNode::Pointer segmentationNode = m_ToolManager->GetWorkingData(0); if (!m_OverwriteExistingSegmentation) { - mitk::DataNode::Pointer refNode = m_ToolManager->GetReferenceData(0); - if (refNode.IsNull()) + if (m_NoneOverwriteTargetSegmentationNode.IsNull()) { - // TODO create and use segmentation exceptions instead!! - MITK_ERROR << "No valid reference data!"; - return nullptr; + mitk::DataNode::Pointer refNode = m_ToolManager->GetReferenceData(0); + if (refNode.IsNull()) + { + // TODO create and use segmentation exceptions instead!! + MITK_ERROR << "No valid reference data!"; + return nullptr; + } + + std::string nodename = refNode->GetName() + "_" + this->GetName(); + mitk::Color color; + color.SetRed(1); + color.SetBlue(0); + color.SetGreen(0); + //create a new segmentation node based on the current segmentation as template + m_NoneOverwriteTargetSegmentationNode = CreateEmptySegmentationNode(dynamic_cast(segmentationNode->GetData()), nodename, color); } - - std::string nodename = refNode->GetName() + "_" + this->GetName(); - mitk::Color color; - color.SetRed(1); - color.SetBlue(0); - color.SetGreen(0); - //create a new segmentation node based on the current segmentation as template - segmentationNode = CreateEmptySegmentationNode(dynamic_cast(segmentationNode->GetData()), nodename, color); - - m_ToolManager->GetDataStorage()->Add(segmentationNode, refNode); + segmentationNode = m_NoneOverwriteTargetSegmentationNode; } return segmentationNode; } + +void mitk::AutoSegmentationTool::EnsureTargetSegmentationNodeInDataStorage() const +{ + auto targetNode = this->GetTargetSegmentationNode(); + if (!m_ToolManager->GetDataStorage()->Exists(targetNode)) + { + m_ToolManager->GetDataStorage()->Add(targetNode, m_ToolManager->GetReferenceData(0)); + } +} diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h index dfb36b908c..69624986c8 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationTool.h @@ -1,71 +1,84 @@ /*============================================================================ 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 mitkAutoSegmentationTool_h_Included #define mitkAutoSegmentationTool_h_Included #include "mitkCommon.h" #include "mitkTool.h" #include namespace mitk { class Image; /** \brief Superclass for tool that create a new segmentation without user interaction in render windows This class is undocumented. Ask the creator ($Author$) to supply useful comments. */ class MITKSEGMENTATION_EXPORT AutoSegmentationTool : public Tool { public: mitkClassMacro(AutoSegmentationTool, Tool); + void Activated() override; + void Deactivated() override; + /** This function controls wether a confirmed segmentation should replace the old * segmentation/working node (true) or if it should be stored as new and additional * node (false). */ void SetOverwriteExistingSegmentation(bool overwrite); /** * @brief Gets the name of the currently selected segmentation node * @return the name of the segmentation node or an empty string if * none is selected */ std::string GetCurrentSegmentationName(); /** - * @brief Depending on the selected mode either returns the currently selected segmentation - * or creates a new one from the selected reference data and adds the new segmentation - * to the datastorage + * @brief Depending on the selected mode either returns the currently selected segmentation node + * or (if overwrite mode is false) creates a new one from the selected reference data. + * @remark Please keep in mind that new created nodes are not automatically added to the data storage. + * Derived tools can call EnsureTargetSegmentationNodeInDataStorage to ensure it as soon as it is clear + * that the target segmentation node will be/is confirmed. * @return a mitk::DataNode which contains a segmentation image */ - virtual mitk::DataNode *GetTargetSegmentationNode(); + virtual DataNode *GetTargetSegmentationNode() const; protected: AutoSegmentationTool(); // purposely hidden + AutoSegmentationTool(const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden ~AutoSegmentationTool() override; const char *GetGroup() const override; /** Helper that extracts the image for the passed timestep, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimeStep(const Image* image, unsigned int timestep); /** Helper that extracts the image for the passed time point, if the image has multiple time steps.*/ static Image::ConstPointer GetImageByTimePoint(const Image* image, TimePointType timePoint); + void EnsureTargetSegmentationNodeInDataStorage() const; + bool m_OverwriteExistingSegmentation; + + private: + /**Contains the node returned by GetTargetSementationNode if m_OverwriteExistingSegmentation == false. Then + * GetTargetSegmentation generates a new target segmentation node.*/ + mutable DataNode::Pointer m_NoneOverwriteTargetSegmentationNode; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp index e8554c2228..1c52e44aad 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.cpp @@ -1,478 +1,523 @@ /*============================================================================ 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 "mitkAutoSegmentationWithPreviewTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkLevelWindowProperty.h" #include "mitkProperties.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" #include #include "mitkImageAccessByItk.h" #include "mitkImageCast.h" #include "mitkImageStatisticsHolder.h" #include "mitkImageTimeSelector.h" #include "mitkLabelSetImage.h" #include "mitkMaskAndCutRoiImageFilter.h" #include "mitkPadImageFilter.h" #include "mitkNodePredicateGeometry.h" +#include "mitkSegTool2D.h" mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews): m_LazyDynamicPreviews(lazyDynamicPreviews) { m_ProgressCommand = mitk::ToolCommand::New(); } +mitk::AutoSegmentationWithPreviewTool::AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule) : AutoSegmentationTool(interactorType, interactorModule), m_LazyDynamicPreviews(lazyDynamicPreviews) +{ + m_ProgressCommand = mitk::ToolCommand::New(); +} + + mitk::AutoSegmentationWithPreviewTool::~AutoSegmentationWithPreviewTool() { } bool mitk::AutoSegmentationWithPreviewTool::CanHandle(const BaseData* referenceData, const BaseData* workingData) const { if (!Superclass::CanHandle(referenceData, workingData)) return false; if (workingData == nullptr) return true; auto* labelSet = dynamic_cast(workingData); if (labelSet != nullptr) return true; auto* image = dynamic_cast(workingData); if (image == nullptr) return false; //if it is a normal image and not a label set image is used as working data //it must have the same pixel type as a label set. return MakeScalarPixelType< DefaultSegmentationDataType >() == image->GetPixelType(); } void mitk::AutoSegmentationWithPreviewTool::Activated() { Superclass::Activated(); m_ToolManager->RoiDataChanged += mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); m_ToolManager->SelectedTimePointChanged += mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); m_ReferenceDataNode = m_ToolManager->GetReferenceData(0); m_SegmentationInputNode = m_ReferenceDataNode; - m_LastTimePointOfUpdate = 0; + m_LastTimePointOfUpdate = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); if (m_PreviewSegmentationNode.IsNull()) { m_PreviewSegmentationNode = DataNode::New(); m_PreviewSegmentationNode->SetProperty("color", ColorProperty::New(0.0, 1.0, 0.0)); m_PreviewSegmentationNode->SetProperty("name", StringProperty::New(std::string(this->GetName())+" preview")); m_PreviewSegmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); m_PreviewSegmentationNode->SetProperty("binary", BoolProperty::New(true)); m_PreviewSegmentationNode->SetProperty("helper object", BoolProperty::New(true)); } if (m_SegmentationInputNode.IsNotNull()) { this->ResetPreviewNode(); this->InitiateToolByInput(); } else { m_ToolManager->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::Deactivated() { m_ToolManager->RoiDataChanged -= mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged); m_ToolManager->SelectedTimePointChanged -= mitk::MessageDelegate(this, &mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged); m_SegmentationInputNode = nullptr; m_ReferenceDataNode = nullptr; + m_WorkingPlaneGeometry = nullptr; try { if (DataStorage *storage = m_ToolManager->GetDataStorage()) { storage->Remove(m_PreviewSegmentationNode); RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (...) { // don't care } if (m_PreviewSegmentationNode.IsNotNull()) { m_PreviewSegmentationNode->SetData(nullptr); } Superclass::Deactivated(); } void mitk::AutoSegmentationWithPreviewTool::ConfirmSegmentation() { if (m_LazyDynamicPreviews && m_CreateAllTimeSteps) { // The tool should create all time steps but is currently in lazy mode, // thus ensure that a preview for all time steps is available. this->UpdatePreview(true); } CreateResultSegmentationFromPreview(); RenderingManager::GetInstance()->RequestUpdateAll(); if (!m_KeepActiveAfterAccept) { m_ToolManager->ActivateTool(-1); } } void mitk::AutoSegmentationWithPreviewTool::InitiateToolByInput() { //default implementation does nothing. //implement in derived classes to change behavior } mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentation() { if (m_PreviewSegmentationNode.IsNull()) { return nullptr; } return dynamic_cast(m_PreviewSegmentationNode->GetData()); } mitk::DataNode* mitk::AutoSegmentationWithPreviewTool::GetPreviewSegmentationNode() { return m_PreviewSegmentationNode; } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetSegmentationInput() const { if (m_SegmentationInputNode.IsNull()) { return nullptr; } return dynamic_cast(m_SegmentationInputNode->GetData()); } const mitk::Image* mitk::AutoSegmentationWithPreviewTool::GetReferenceData() const { if (m_ReferenceDataNode.IsNull()) { return nullptr; } return dynamic_cast(m_ReferenceDataNode->GetData()); } void mitk::AutoSegmentationWithPreviewTool::ResetPreviewNode() { itk::RGBPixel previewColor; previewColor[0] = 0.0f; previewColor[1] = 1.0f; previewColor[2] = 0.0f; const auto image = this->GetSegmentationInput(); if (nullptr != image) { mitk::LabelSetImage::ConstPointer workingImage = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (workingImage.IsNotNull()) { auto newPreviewImage = workingImage->Clone(); if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage); // Let's paint the feedback node green... newPreviewImage->GetActiveLabel()->SetColor(previewColor); newPreviewImage->GetActiveLabelSet()->UpdateLookupTable(newPreviewImage->GetActiveLabel()->GetValue()); } else { mitk::Image::ConstPointer workingImageBin = dynamic_cast(m_ToolManager->GetWorkingData(0)->GetData()); if (workingImageBin.IsNotNull()) { auto newPreviewImage = workingImageBin->Clone(); if (newPreviewImage.IsNull()) { MITK_ERROR << "Cannot create preview helper objects. Unable to clone working image"; return; } m_PreviewSegmentationNode->SetData(newPreviewImage->Clone()); } else { mitkThrow() << "Tool is an invalid state. Cannot setup preview node. Working data is an unsupported class and should have not been accepted by CanHandle()."; } } m_PreviewSegmentationNode->SetColor(previewColor); m_PreviewSegmentationNode->SetOpacity(0.5); int layer(50); m_ReferenceDataNode->GetIntProperty("layer", layer); m_PreviewSegmentationNode->SetIntProperty("layer", layer + 1); if (DataStorage *ds = m_ToolManager->GetDataStorage()) { if (!ds->Exists(m_PreviewSegmentationNode)) ds->Add(m_PreviewSegmentationNode, m_ReferenceDataNode); } } } template static void ITKSetVolume(const itk::Image *originalImage, mitk::Image *segmentation, unsigned int timeStep) { auto constPixelContainer = originalImage->GetPixelContainer(); //have to make a const cast because itk::PixelContainer does not provide a const correct access :( auto pixelContainer = const_cast::PixelContainer*>(constPixelContainer); segmentation->SetVolume((void *)pixelContainer->GetBufferPointer(), timeStep); } void mitk::AutoSegmentationWithPreviewTool::TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep) { try { - Image::ConstPointer image3D = this->GetImageByTimeStep(sourceImage, timeStep); + Image::ConstPointer sourceImageAtTimeStep = this->GetImageByTimeStep(sourceImage, timeStep); - if (image3D->GetPixelType() != destinationImage->GetPixelType()) + if (sourceImageAtTimeStep->GetPixelType() != destinationImage->GetPixelType()) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same pixel type. " << "Source pixel type: " << sourceImage->GetPixelType().GetTypeAsString() << "; destination pixel type: " << destinationImage->GetPixelType().GetTypeAsString(); } if (!Equal(*(sourceImage->GetGeometry(timeStep)), *(destinationImage->GetGeometry(timeStep)), NODE_PREDICATE_GEOMETRY_DEFAULT_CHECK_PRECISION, false)) { mitkThrow() << "Cannot transfer images. Tool is in an invalid state, source image and destination image do not have the same geometry."; } - if (image3D->GetDimension() == 2) + if (nullptr != this->GetWorkingPlaneGeometry()) { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 2, destinationImage, timeStep); + auto sourceSlice = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), sourceImage, timeStep); + SegTool2D::WriteBackSegmentationResult(this->GetTargetSegmentationNode(), m_WorkingPlaneGeometry, sourceSlice, timeStep); } else - { - AccessFixedDimensionByItk_2( - image3D, ITKSetVolume, 3, destinationImage, timeStep); + { //take care of the full segmentation volume + if (sourceImageAtTimeStep->GetDimension() == 2) + { + AccessFixedDimensionByItk_2( + sourceImageAtTimeStep, ITKSetVolume, 2, destinationImage, timeStep); + } + else + { + AccessFixedDimensionByItk_2( + sourceImageAtTimeStep, ITKSetVolume, 3, destinationImage, timeStep); + } } } catch (...) { Tool::ErrorMessage("Error accessing single time steps of the original image. Cannot create segmentation."); throw; } } void mitk::AutoSegmentationWithPreviewTool::CreateResultSegmentationFromPreview() { const auto segInput = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); if (nullptr != segInput && nullptr != previewImage) { DataNode::Pointer resultSegmentationNode = GetTargetSegmentationNode(); if (resultSegmentationNode.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); auto resultSegmentation = dynamic_cast(resultSegmentationNode->GetData()); // REMARK: the following code in this scope assumes that previewImage and resultSegmentation // are clones of the working image (segmentation provided to the tool). Therefore they have // the same time geometry. if (previewImage->GetTimeSteps() != resultSegmentation->GetTimeSteps()) { mitkThrow() << "Cannot perform threshold. Internal tool state is invalid." << " Preview segmentation and segmentation result image have different time geometries."; } if (m_CreateAllTimeSteps) { for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { - TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } } else { const auto timeStep = resultSegmentation->GetTimeGeometry()->TimePointToTimeStep(timePoint); - TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); + this->TransferImageAtTimeStep(previewImage, resultSegmentation, timeStep); } // since we are maybe working on a smaller image, pad it to the size of the original image if (m_ReferenceDataNode.GetPointer() != m_SegmentationInputNode.GetPointer()) { mitk::PadImageFilter::Pointer padFilter = mitk::PadImageFilter::New(); padFilter->SetInput(0, resultSegmentation); padFilter->SetInput(1, dynamic_cast(m_ReferenceDataNode->GetData())); padFilter->SetBinaryFilter(true); padFilter->SetUpperThreshold(1); padFilter->SetLowerThreshold(1); padFilter->Update(); resultSegmentationNode->SetData(padFilter->GetOutput()); } m_ToolManager->SetWorkingData(resultSegmentationNode); m_ToolManager->GetWorkingData(0)->Modified(); + this->EnsureTargetSegmentationNodeInDataStorage(); } } } void mitk::AutoSegmentationWithPreviewTool::OnRoiDataChanged() { mitk::DataNode::ConstPointer node = m_ToolManager->GetRoiData(0); if (node.IsNotNull()) { mitk::MaskAndCutRoiImageFilter::Pointer roiFilter = mitk::MaskAndCutRoiImageFilter::New(); mitk::Image::Pointer image = dynamic_cast(m_SegmentationInputNode->GetData()); if (image.IsNull()) return; roiFilter->SetInput(image); roiFilter->SetRegionOfInterest(node->GetData()); roiFilter->Update(); mitk::DataNode::Pointer tmpNode = mitk::DataNode::New(); tmpNode->SetData(roiFilter->GetOutput()); m_SegmentationInputNode = tmpNode; } else m_SegmentationInputNode = m_ReferenceDataNode; this->ResetPreviewNode(); this->InitiateToolByInput(); this->UpdatePreview(); } void mitk::AutoSegmentationWithPreviewTool::OnTimePointChanged() { if (m_IsTimePointChangeAware && m_PreviewSegmentationNode.IsNotNull() && m_SegmentationInputNode.IsNotNull()) { const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); const bool isStaticSegOnDynamicImage = m_PreviewSegmentationNode->GetData()->GetTimeSteps() == 1 && m_SegmentationInputNode->GetData()->GetTimeSteps() > 1; if (timePoint!=m_LastTimePointOfUpdate && (isStaticSegOnDynamicImage || m_LazyDynamicPreviews)) { //we only need to update either because we are lazzy //or because we have a static segmentation with a dynamic image this->UpdatePreview(); } } } void mitk::AutoSegmentationWithPreviewTool::UpdatePreview(bool ignoreLazyPreviewSetting) { const auto inputImage = this->GetSegmentationInput(); auto previewImage = this->GetPreviewSegmentation(); int progress_steps = 200; this->CurrentlyBusy.Send(true); + m_IsUpdating = true; this->UpdatePrepare(); const auto timePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); try { if (nullptr != inputImage && nullptr != previewImage) { m_ProgressCommand->AddStepsToDo(progress_steps); if (previewImage->GetTimeSteps() > 1 && (ignoreLazyPreviewSetting || !m_LazyDynamicPreviews)) { - for (unsigned int timeStep = 0; timeStep < inputImage->GetTimeSteps(); ++timeStep) + for (unsigned int timeStep = 0; timeStep < previewImage->GetTimeSteps(); ++timeStep) { - auto feedBackImage3D = this->GetImageByTimeStep(inputImage, timeStep); - - this->DoUpdatePreview(feedBackImage3D, previewImage, timeStep); + Image::ConstPointer feedBackImage; + auto previewTimePoint = previewImage->GetTimeGeometry()->TimeStepToTimePoint(timeStep); + auto inputTimeStep = inputImage->GetTimeGeometry()->TimePointToTimeStep(previewTimePoint); + + if (nullptr != this->GetWorkingPlaneGeometry()) + { //only extract a specific slice defined by the working plane as feedback image. + feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImage(this->GetWorkingPlaneGeometry(), inputImage, inputTimeStep); + } + else + { //work on the whole feedback image + feedBackImage = this->GetImageByTimeStep(inputImage, inputTimeStep); + } + + this->DoUpdatePreview(feedBackImage, previewImage, timeStep); } } else { - auto feedBackImage3D = this->GetImageByTimePoint(inputImage, timePoint); + Image::ConstPointer feedBackImage; + if (nullptr != this->GetWorkingPlaneGeometry()) + { + feedBackImage = SegTool2D::GetAffectedImageSliceAs2DImageByTimePoint(this->GetWorkingPlaneGeometry(), inputImage, timePoint); + } + else + { + feedBackImage = this->GetImageByTimePoint(inputImage, timePoint); + } + auto timeStep = previewImage->GetTimeGeometry()->TimePointToTimeStep(timePoint); - this->DoUpdatePreview(feedBackImage3D, previewImage, timeStep); + this->DoUpdatePreview(feedBackImage, previewImage, timeStep); } RenderingManager::GetInstance()->RequestUpdateAll(); } } catch (itk::ExceptionObject & excep) { MITK_ERROR << "Exception caught: " << excep.GetDescription(); m_ProgressCommand->SetProgress(progress_steps); std::string msg = excep.GetDescription(); ErrorMessage.Send(msg); } catch (...) { m_ProgressCommand->SetProgress(progress_steps); + m_IsUpdating = false; CurrentlyBusy.Send(false); throw; } this->UpdateCleanUp(); m_LastTimePointOfUpdate = timePoint; m_ProgressCommand->SetProgress(progress_steps); + m_IsUpdating = false; CurrentlyBusy.Send(false); } +bool mitk::AutoSegmentationWithPreviewTool::IsUpdating() const +{ + return m_IsUpdating; +} + void mitk::AutoSegmentationWithPreviewTool::UpdatePrepare() { // default implementation does nothing //reimplement in derived classes for special behavior } void mitk::AutoSegmentationWithPreviewTool::UpdateCleanUp() { // default implementation does nothing //reimplement in derived classes for special behavior } mitk::TimePointType mitk::AutoSegmentationWithPreviewTool::GetLastTimePointOfUpdate() const { return m_LastTimePointOfUpdate; } diff --git a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h index 848ff0c005..b3cad84572 100644 --- a/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h +++ b/Modules/Segmentation/Interactions/mitkAutoSegmentationWithPreviewTool.h @@ -1,152 +1,169 @@ /*============================================================================ 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 mitkAutoSegmentationWithPreviewTool_h_Included #define mitkAutoSegmentationWithPreviewTool_h_Included #include "mitkAutoSegmentationTool.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkToolCommand.h" #include namespace mitk { /** \brief Base class for any auto segmentation tool that provides a preview of the new segmentation. This tool class implements a lot basic logic to handle auto segmentation tools with preview, Time point and ROI support. Derived classes will ask to update the segmentation preview if needed (e.g. because the ROI or the current time point has changed) or because derived tools indicated the need to update themselves. This class also takes care to properly transfer a confirmed preview into the segementation result. \ingroup ToolManagerEtAl \sa mitk::Tool \sa QmitkInteractiveSegmentation */ class MITKSEGMENTATION_EXPORT AutoSegmentationWithPreviewTool : public AutoSegmentationTool { public: mitkClassMacro(AutoSegmentationWithPreviewTool, AutoSegmentationTool); void Activated() override; void Deactivated() override; void ConfirmSegmentation(); itkSetMacro(CreateAllTimeSteps, bool); itkGetMacro(CreateAllTimeSteps, bool); itkBooleanMacro(CreateAllTimeSteps); itkSetMacro(KeepActiveAfterAccept, bool); itkGetMacro(KeepActiveAfterAccept, bool); itkBooleanMacro(KeepActiveAfterAccept); itkSetMacro(IsTimePointChangeAware, bool); itkGetMacro(IsTimePointChangeAware, bool); itkBooleanMacro(IsTimePointChangeAware); bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /** Triggers the actualization of the preview * @param ignoreLazyPreviewSetting If set true UpdatePreview will always * generate the preview for all time steps. If set to false, UpdatePreview * will regard the setting specified by the constructor. * To define the update generation for time steps implement DoUpdatePreview. * To alter what should be done directly before or after the update of the preview, * reimplement UpdatePrepare() or UpdateCleanUp().*/ void UpdatePreview(bool ignoreLazyPreviewSetting = false); + /** Indicate if currently UpdatePreview is triggered (true) or not (false).*/ + bool IsUpdating() const; + protected: mitk::ToolCommand::Pointer m_ProgressCommand; /** Member is always called if GetSegmentationInput() has changed * (e.g. because a new ROI was defined, or on activation) to give derived * classes the posibility to initiate their state accordingly. * Reimplement this function to implement special behavior. */ virtual void InitiateToolByInput(); virtual void UpdatePrepare(); virtual void UpdateCleanUp(); /** This function does the real work. Here the preview for a given * input image should be computed and stored in the also passed * preview image at the passed time step. */ virtual void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) = 0; - AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews = false); + AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews = false); // purposely hidden + AutoSegmentationWithPreviewTool(bool lazyDynamicPreviews, const char* interactorType, const us::Module* interactorModule = nullptr); // purposely hidden + ~AutoSegmentationWithPreviewTool() override; /** Returns the image that contains the preview of the current segmentation. * Returns null if the node is not set or does not contain an image.*/ Image* GetPreviewSegmentation(); DataNode* GetPreviewSegmentationNode(); /** Returns the input that should be used for any segmentation/preview or tool update. * It is either the data of ReferenceDataNode itself or a part of it defined by a ROI mask * provided by the tool manager. Derived classes should regard this as the relevant * input data for any processing. * Returns null if the node is not set or does not contain an image.*/ const Image* GetSegmentationInput() const; /** Returns the image that is provided by the ReferenceDataNode. * Returns null if the node is not set or does not contain an image.*/ const Image* GetReferenceData() const; /** Resets the preview node so it is empty and ready to be filled by the tool*/ void ResetPreviewNode(); TimePointType GetLastTimePointOfUpdate() const; + itkSetObjectMacro(WorkingPlaneGeometry, PlaneGeometry); + itkGetConstObjectMacro(WorkingPlaneGeometry, PlaneGeometry); + private: void TransferImageAtTimeStep(const Image* sourceImage, Image* destinationImage, const TimeStepType timeStep); void CreateResultSegmentationFromPreview(); void OnRoiDataChanged(); void OnTimePointChanged(); /** Node that containes the preview data generated and managed by this class or derived ones.*/ DataNode::Pointer m_PreviewSegmentationNode; /** The reference data recieved from ToolManager::GetReferenceData when tool was activated.*/ DataNode::Pointer m_ReferenceDataNode; /** Node that containes the data that should be used as input for any auto segmentation. It might * be the same like m_ReferenceDataNode (if no ROI is set) or a sub region (if ROI is set).*/ DataNode::Pointer m_SegmentationInputNode; /** Indicates if Accepting the threshold should transfer/create the segmentations of all time steps (true) or only of the currently selected timepoint (false).*/ bool m_CreateAllTimeSteps = false; /** Indicates if the tool should kept active after accepting the segmentation or not.*/ bool m_KeepActiveAfterAccept = false; /** Relevant if the working data / preview image has multiple time steps (dynamic segmentations). * This flag has to be set by derived classes accordingly to there way to generate dynamic previews. * If LazyDynamicPreview is true, the tool generates only the preview for the current time step. * Therefore it always has to update the preview if current time point has changed and it has to (re)compute * all timeframes if ConfirmSegmentation() is called.*/ bool m_LazyDynamicPreviews = false; bool m_IsTimePointChangeAware = true; TimePointType m_LastTimePointOfUpdate = 0.; + + bool m_IsUpdating = false; + + /** This variable indicates if for the tool a working plane geometry is defined. + * If a working plane is defined the tool will only work an the slice of the input + * and the segmentation. Thus only the relevant input slice will be passed to + * DoUpdatePreview(...) and only the relevant slice of the preview will be transfered when + * ConfirmSegmentation() is called.*/ + PlaneGeometry::Pointer m_WorkingPlaneGeometry; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp b/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp index 92dba4c9a5..e608aaec13 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkContourModelInteractor.cpp @@ -1,164 +1,164 @@ /*============================================================================ 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 "mitkContourModelInteractor.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include #include mitk::ContourModelInteractor::ContourModelInteractor() { } void mitk::ContourModelInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("checkisOverPoint", OnCheckPointClick); CONNECT_CONDITION("mouseMove", IsHovering); CONNECT_FUNCTION("movePoints", OnMovePoint); CONNECT_FUNCTION("deletePoint", OnDeletePoint); CONNECT_FUNCTION("finish", OnFinishEditing); } mitk::ContourModelInteractor::~ContourModelInteractor() { } bool mitk::ContourModelInteractor::OnCheckPointClick(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; - int timestep = positionEvent->GetSender()->GetTimeStep(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); contour->Deselect(); mitk::Point3D click = positionEvent->GetPositionInWorld(); - if (contour->SelectVertexAt(click, 1.5, timestep)) + if (contour->SelectVertexAt(click, 1.5, timeStep)) { contour->SetSelectedVertexAsControlPoint(); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); m_lastMousePosition = click; - auto *contourGeometry = dynamic_cast(contour->GetGeometry(timestep)); + auto *contourGeometry = dynamic_cast(contour->GetGeometry(timeStep)); if (contourGeometry->IsInside(click)) { m_lastMousePosition = click; return true; } else return false; } else { return false; } return true; } void mitk::ContourModelInteractor::OnDeletePoint(StateMachineAction *, InteractionEvent *) { auto *contour = dynamic_cast(this->GetDataNode()->GetData()); contour->RemoveVertex(contour->GetSelectedVertex()); } bool mitk::ContourModelInteractor::IsHovering(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; - int timestep = positionEvent->GetSender()->GetTimeStep(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); bool isHover = false; this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()); - if (contour->IsNearContour(currentPosition, 1.5, timestep)) + if (contour->IsNearContour(currentPosition, 1.5, timeStep)) { if (isHover == false) { this->GetDataNode()->SetBoolProperty("contour.hovering", true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } else { if (isHover == true) { this->GetDataNode()->SetBoolProperty("contour.hovering", false); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } return false; } void mitk::ContourModelInteractor::OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; auto *contour = dynamic_cast(this->GetDataNode()->GetData()); mitk::Vector3D translation; mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); translation[0] = currentPosition[0] - this->m_lastMousePosition[0]; translation[1] = currentPosition[1] - this->m_lastMousePosition[1]; translation[2] = currentPosition[2] - this->m_lastMousePosition[2]; contour->ShiftSelectedVertex(translation); this->m_lastMousePosition = positionEvent->GetPositionInWorld(); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::ContourModelInteractor::OnMoveContour(StateMachineAction *, InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; - int timestep = positionEvent->GetSender()->GetTimeStep(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); mitk::Vector3D translation; mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); translation[0] = currentPosition[0] - this->m_lastMousePosition[0]; translation[1] = currentPosition[1] - this->m_lastMousePosition[1]; translation[2] = currentPosition[2] - this->m_lastMousePosition[2]; - contour->ShiftContour(translation, timestep); + contour->ShiftContour(translation, timeStep); this->m_lastMousePosition = positionEvent->GetPositionInWorld(); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::ContourModelInteractor::OnFinishEditing(StateMachineAction *, InteractionEvent *interactionEvent) { auto *contour = dynamic_cast(this->GetDataNode()->GetData()); contour->Deselect(); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } diff --git a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp index 695b2969ef..e2e0e95b68 100644 --- a/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkContourModelLiveWireInteractor.cpp @@ -1,482 +1,485 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkContourModelLiveWireInteractor.h" #include "mitkInteractionPositionEvent.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" #include "mitkRenderingManager.h" #include #include "mitkIOUtil.h" mitk::ContourModelLiveWireInteractor::ContourModelLiveWireInteractor() : ContourModelInteractor() { m_LiveWireFilter = mitk::ImageLiveWireContourModelFilter::New(); m_NextActiveVertexDown.Fill(0); m_NextActiveVertexUp.Fill(0); } mitk::ContourModelLiveWireInteractor::~ContourModelLiveWireInteractor() { } void mitk::ContourModelLiveWireInteractor::ConnectActionsAndFunctions() { CONNECT_CONDITION("checkisOverPoint", OnCheckPointClick); CONNECT_CONDITION("mouseMove", IsHovering); CONNECT_FUNCTION("movePoint", OnMovePoint); CONNECT_FUNCTION("deletePoint", OnDeletePoint); CONNECT_FUNCTION("finish", OnFinishEditing); } bool mitk::ContourModelLiveWireInteractor::OnCheckPointClick(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; - int timestep = positionEvent->GetSender()->GetTimeStep(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "Invalid Contour"; return false; } contour->Deselect(); // Check distance to any vertex. // Transition YES if click close to a vertex mitk::Point3D click = positionEvent->GetPositionInWorld(); - if (contour->SelectVertexAt(click, 1.5, timestep)) + if (contour->SelectVertexAt(click, 1.5, timeStep)) { contour->SetSelectedVertexAsControlPoint(false); m_ContourLeft = mitk::ContourModel::New(); // get coordinates of next active vertex downwards from selected vertex - int downIndex = this->SplitContourFromSelectedVertex(contour, m_ContourLeft, false, timestep); + int downIndex = this->SplitContourFromSelectedVertex(contour, m_ContourLeft, false, timeStep); m_NextActiveVertexDownIter = contour->IteratorBegin() + downIndex; m_NextActiveVertexDown = (*m_NextActiveVertexDownIter)->Coordinates; m_ContourRight = mitk::ContourModel::New(); // get coordinates of next active vertex upwards from selected vertex - int upIndex = this->SplitContourFromSelectedVertex(contour, m_ContourRight, true, timestep); + int upIndex = this->SplitContourFromSelectedVertex(contour, m_ContourRight, true, timeStep); m_NextActiveVertexUpIter = contour->IteratorBegin() + upIndex; m_NextActiveVertexUp = (*m_NextActiveVertexUpIter)->Coordinates; // clear previous void positions this->m_LiveWireFilter->ClearRepulsivePoints(); // set the current contour as void positions in the cost map // start with down side - auto iter = contour->IteratorBegin(timestep); + auto iter = contour->IteratorBegin(timeStep); for (; iter != m_NextActiveVertexDownIter; iter++) { itk::Index<2> idx; this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); } // continue with upper side iter = m_NextActiveVertexUpIter + 1; - for (; iter != contour->IteratorEnd(timestep); iter++) + for (; iter != contour->IteratorEnd(timeStep); iter++) { itk::Index<2> idx; this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); } // clear container with void points between neighboring control points m_ContourBeingModified.clear(); // let us have the selected point as a control point contour->SetSelectedVertexAsControlPoint(true); // finally, return true to pass this condition return true; } else { // do not pass condition return false; } mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); return true; } void mitk::ContourModelLiveWireInteractor::SetEditingContourModelNode(mitk::DataNode *_arg) { if (this->m_EditingContourNode != _arg) { this->m_EditingContourNode = _arg; } } void mitk::ContourModelLiveWireInteractor::SetWorkingImage(mitk::Image *_arg) { if (this->m_WorkingSlice != _arg) { this->m_WorkingSlice = _arg; this->m_LiveWireFilter->SetInput(this->m_WorkingSlice); } } void mitk::ContourModelLiveWireInteractor::OnDeletePoint(StateMachineAction *, InteractionEvent *interactionEvent) { - int timestep = interactionEvent->GetSender()->GetTimeStep(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "Invalid Contour!"; return; } if (contour->GetSelectedVertex()) { mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); + newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); - newContour->Concatenate(m_ContourLeft, timestep); + newContour->Concatenate(m_ContourLeft, timeStep); // recompute contour between neighbored two active control points this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(this->m_NextActiveVertexUp); // this->m_LiveWireFilter->ClearRepulsivePoints(); this->m_LiveWireFilter->Update(); mitk::ContourModel *liveWireContour = this->m_LiveWireFilter->GetOutput(); assert(liveWireContour); - if (liveWireContour->IsEmpty(timestep)) + if (liveWireContour->IsEmpty(timeStep)) return; - liveWireContour->RemoveVertexAt(0, timestep); - liveWireContour->RemoveVertexAt(liveWireContour->GetNumberOfVertices(timestep) - 1, timestep); + liveWireContour->RemoveVertexAt(0, timeStep); + liveWireContour->RemoveVertexAt(liveWireContour->GetNumberOfVertices(timeStep) - 1, timeStep); // insert new live wire computed points - newContour->Concatenate(liveWireContour, timestep); + newContour->Concatenate(liveWireContour, timeStep); // insert right side of original contour - newContour->Concatenate(this->m_ContourRight, timestep); + newContour->Concatenate(this->m_ContourRight, timeStep); - newContour->SetClosed(contour->IsClosed(timestep), timestep); + newContour->SetClosed(contour->IsClosed(timeStep), timeStep); // instead of leaving a single point, delete all points - if (newContour->GetNumberOfVertices(timestep) <= 2) + if (newContour->GetNumberOfVertices(timeStep) <= 2) { - newContour->Clear(timestep); + newContour->Clear(timeStep); } this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } void mitk::ContourModelLiveWireInteractor::OnMovePoint(StateMachineAction *, InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; - int timestep = positionEvent->GetSender()->GetTimeStep(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); if (contour == nullptr) { MITK_ERROR << "invalid contour"; return; } mitk::ContourModel::Pointer editingContour = mitk::ContourModel::New(); editingContour->Expand(contour->GetTimeSteps()); + editingContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // recompute left live wire, i.e. the contour between previous active vertex and selected vertex this->m_LiveWireFilter->SetStartPoint(this->m_NextActiveVertexDown); this->m_LiveWireFilter->SetEndPoint(currentPosition); // remove void positions between previous active vertex and next active vertex. if (!m_ContourBeingModified.empty()) { std::vector>::const_iterator iter = m_ContourBeingModified.begin(); for (; iter != m_ContourBeingModified.end(); iter++) { this->m_LiveWireFilter->RemoveRepulsivePoint((*iter)); } } // update to get the left livewire. Remember that the points in the rest of the contour are already // set as void positions in the filter this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer leftLiveWire = this->m_LiveWireFilter->GetOutput(); assert(leftLiveWire); - if (!leftLiveWire->IsEmpty(timestep)) - leftLiveWire->RemoveVertexAt(0, timestep); + if (!leftLiveWire->IsEmpty(timeStep)) + leftLiveWire->RemoveVertexAt(0, timeStep); - editingContour->Concatenate(leftLiveWire, timestep); + editingContour->Concatenate(leftLiveWire, timeStep); // the new index of the selected vertex unsigned int selectedVertexIndex = - this->m_ContourLeft->GetNumberOfVertices(timestep) + leftLiveWire->GetNumberOfVertices(timestep) - 1; + this->m_ContourLeft->GetNumberOfVertices(timeStep) + leftLiveWire->GetNumberOfVertices(timeStep) - 1; // at this point the container has to be empty m_ContourBeingModified.clear(); // add points from left live wire contour - auto iter = leftLiveWire->IteratorBegin(timestep); - for (; iter != leftLiveWire->IteratorEnd(timestep); iter++) + auto iter = leftLiveWire->IteratorBegin(timeStep); + for (; iter != leftLiveWire->IteratorEnd(timeStep); iter++) { itk::Index<2> idx; this->m_WorkingSlice->GetGeometry()->WorldToIndex((*iter)->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); // add indices m_ContourBeingModified.push_back(idx); } // recompute right live wire, i.e. the contour between selected vertex and next active vertex this->m_LiveWireFilter->SetStartPoint(currentPosition); this->m_LiveWireFilter->SetEndPoint(m_NextActiveVertexUp); // update filter with all contour points set as void but the right live wire portion to be calculated now this->m_LiveWireFilter->Update(); mitk::ContourModel::Pointer rightLiveWire = this->m_LiveWireFilter->GetOutput(); assert(rightLiveWire); // reject strange paths - if (abs(rightLiveWire->GetNumberOfVertices(timestep) - leftLiveWire->GetNumberOfVertices(timestep)) > 50) + if (abs(rightLiveWire->GetNumberOfVertices(timeStep) - leftLiveWire->GetNumberOfVertices(timeStep)) > 50) { return; } - if (!leftLiveWire->IsEmpty(timestep)) - leftLiveWire->SetControlVertexAt(leftLiveWire->GetNumberOfVertices() - 1, timestep); + if (!leftLiveWire->IsEmpty(timeStep)) + leftLiveWire->SetControlVertexAt(leftLiveWire->GetNumberOfVertices() - 1, timeStep); - if (!rightLiveWire->IsEmpty(timestep)) - rightLiveWire->RemoveVertexAt(0, timestep); + if (!rightLiveWire->IsEmpty(timeStep)) + rightLiveWire->RemoveVertexAt(0, timeStep); - editingContour->Concatenate(rightLiveWire, timestep); + editingContour->Concatenate(rightLiveWire, timeStep); m_EditingContourNode->SetData(editingContour); mitk::ContourModel::Pointer newContour = mitk::ContourModel::New(); newContour->Expand(contour->GetTimeSteps()); + newContour->SetTimeGeometry(contour->GetTimeGeometry()->Clone()); // concatenate left original contour - newContour->Concatenate(this->m_ContourLeft, timestep); + newContour->Concatenate(this->m_ContourLeft, timeStep); - newContour->Concatenate(editingContour, timestep, true); + newContour->Concatenate(editingContour, timeStep, true); // set last inserted vertex as selected - newContour->SelectVertexAt(selectedVertexIndex, timestep); + newContour->SelectVertexAt(selectedVertexIndex, timeStep); // set as control point newContour->SetSelectedVertexAsControlPoint(true); // concatenate right original contour - newContour->Concatenate(this->m_ContourRight, timestep); + newContour->Concatenate(this->m_ContourRight, timeStep); - newContour->SetClosed(contour->IsClosed(timestep), timestep); + newContour->SetClosed(contour->IsClosed(timeStep), timeStep); this->GetDataNode()->SetData(newContour); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } bool mitk::ContourModelLiveWireInteractor::IsHovering(const InteractionEvent *interactionEvent) { const auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return false; - int timestep = positionEvent->GetSender()->GetTimeStep(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *contour = dynamic_cast(this->GetDataNode()->GetData()); mitk::Point3D currentPosition = positionEvent->GetPositionInWorld(); bool isHover = false; this->GetDataNode()->GetBoolProperty("contour.hovering", isHover, positionEvent->GetSender()); - if (contour->IsNearContour(currentPosition, 1.5, timestep)) + if (contour->IsNearContour(currentPosition, 1.5, timeStep)) { if (isHover == false) { this->GetDataNode()->SetBoolProperty("contour.hovering", true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } else { if (isHover == true) { this->GetDataNode()->SetBoolProperty("contour.hovering", false); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } } return false; } int mitk::ContourModelLiveWireInteractor::SplitContourFromSelectedVertex(mitk::ContourModel *srcContour, mitk::ContourModel *destContour, bool fromSelectedUpwards, int timestep) { auto end = srcContour->IteratorEnd(); auto begin = srcContour->IteratorBegin(); // search next active control point to left and rigth and set as start and end point for filter auto itSelected = begin; // move iterator to position while ((*itSelected) != srcContour->GetSelectedVertex()) { itSelected++; } // CASE search upwards for next control point if (fromSelectedUpwards) { auto itUp = itSelected; if (itUp != end) { itUp++; // step once up otherwise the loop breaks immediately } while (itUp != end && !((*itUp)->IsControlPoint)) { itUp++; } auto it = itUp; if (itSelected != begin) { // copy the rest of the original contour while (it != end) { destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); it++; } } // else do not copy the contour // return the offset of iterator at one before next-vertex-upwards if (itUp != begin) { return std::distance(begin, itUp) - 1; } else { return std::distance(begin, itUp); } } else // CASE search downwards for next control point { auto itDown = itSelected; auto it = srcContour->IteratorBegin(); if (itSelected != begin) { if (itDown != begin) { itDown--; // step once down otherwise the the loop breaks immediately } while (itDown != begin && !((*itDown)->IsControlPoint)) { itDown--; } if (it != end) // if not empty { // always add the first vertex destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); it++; } // copy from begin to itDown while (it <= itDown) { destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); it++; } } else { // if selected vertex is the first element search from end of contour downwards itDown = end; itDown--; while (!((*itDown)->IsControlPoint) && itDown != begin) { itDown--; } // move one forward as we don't want the first control point it++; // move iterator to second control point while ((it != end) && !((*it)->IsControlPoint)) { it++; } // copy from begin to itDown while (it <= itDown) { // copy the contour from second control point to itDown destContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, timestep); it++; } } /* //add vertex at itDown - it's not considered during while loop if( it != begin && it != end) { //destContour->AddVertex( (*it)->Coordinates, (*it)->IsControlPoint, timestep); } */ // return the offset of iterator at one after next-vertex-downwards if (itDown != end) { return std::distance(begin, itDown); // + 1;//index of next vertex } else { return std::distance(begin, itDown) - 1; } } } void mitk::ContourModelLiveWireInteractor::OnFinishEditing(StateMachineAction *, InteractionEvent *interactionEvent) { - int timestep = interactionEvent->GetSender()->GetTimeStep(); + const auto timeStep = interactionEvent->GetSender()->GetTimeStep(GetDataNode()->GetData()); auto *editingContour = dynamic_cast(this->m_EditingContourNode->GetData()); - editingContour->Clear(timestep); + editingContour->Clear(timeStep); mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } diff --git a/Modules/Segmentation/Interactions/mitkContourTool.cpp b/Modules/Segmentation/Interactions/mitkContourTool.cpp index 3cc6e941fe..41ec690074 100644 --- a/Modules/Segmentation/Interactions/mitkContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkContourTool.cpp @@ -1,180 +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" -#include "mitkAbstractTransformGeometry.h" -#include "mitkOverwriteDirectedPlaneImageFilter.h" -#include "mitkOverwriteSliceImageFilter.h" -#include "mitkToolManager.h" - -#include "mitkBaseRenderer.h" -#include "mitkRenderingManager.h" - -#include "mitkInteractionEvent.h" -#include "mitkStateMachineAction.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(); - int timestep = positionEvent->GetSender()->GetTimeStep(); - - ContourModel *contour = FeedbackContourTool::GetFeedbackContour(); - // Clear feedback contour - contour->Initialize(); - // expand time bounds because our contour was initialized - contour->Expand(timestep + 1); - // draw as a closed contour - contour->SetClosed(true, timestep); - // add first mouse position + this->ClearsCurrentFeedbackContour(true); mitk::Point3D point = positionEvent->GetPositionInWorld(); - contour->AddVertex(point, timestep); + 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; - int timestep = positionEvent->GetSender()->GetTimeStep(); - - ContourModel *contour = FeedbackContourTool::GetFeedbackContour(); mitk::Point3D point = positionEvent->GetPositionInWorld(); - contour->AddVertex(point, timestep); + 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) { - // 1. Hide the feedback contour, find out which slice the user clicked, find out which slice of the toolmanager's - // working image corresponds to that - FeedbackContourTool::SetFeedbackContourVisible(false); - auto *positionEvent = dynamic_cast(interactionEvent); - // const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return; assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); - if (!workingNode) - return; - - auto workingImage = dynamic_cast(workingNode->GetData()); - const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); - if (!workingImage || !planeGeometry) - return; - - const auto *abstractTransformGeometry( - dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); - if (!workingImage || abstractTransformGeometry) - return; - - // 2. Slice is known, now we try to get it as a 2D image and project the contour into index coordinates of this slice - Image::Pointer slice = SegTool2D::GetAffectedImageSliceAs2DImage(positionEvent, workingImage); - - if (slice.IsNull()) - { - MITK_ERROR << "Unable to extract slice." << std::endl; - return; - } - - ContourModel *feedbackContour = FeedbackContourTool::GetFeedbackContour(); - ContourModel::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( - slice, feedbackContour, true, false); // true: actually no idea why this is neccessary, but it works :-( - - if (projectedContour.IsNull()) - return; - - int timestep = positionEvent->GetSender()->GetTimeStep(); - int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); - - // m_PaintingPixelValue only decides whether to paint or erase - mitk::ContourModelUtils::FillContourInSlice( - projectedContour, timestep, slice, workingImage, m_PaintingPixelValue * activePixelValue); - - // this->WriteBackSegmentationResult(positionEvent, slice); - SegTool2D::WriteBackSegmentationResult(positionEvent, slice); - - // 4. Make sure the result is drawn again --> is visible then. - assert(positionEvent->GetSender()->GetRenderWindow()); + this->WriteBackFeedbackContourAsSegmentationResult(positionEvent, m_PaintingPixelValue); } /** 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/mitkCorrectorTool2D.cpp b/Modules/Segmentation/Interactions/mitkCorrectorTool2D.cpp index 0e928ed279..4b761a8556 100644 --- a/Modules/Segmentation/Interactions/mitkCorrectorTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkCorrectorTool2D.cpp @@ -1,204 +1,181 @@ /*============================================================================ 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 "mitkCorrectorTool2D.h" #include "mitkCorrectorAlgorithm.h" #include "mitkAbstractTransformGeometry.h" -#include "mitkBaseRenderer.h" #include "mitkImageReadAccessor.h" -#include "mitkLabelSetImage.h" -#include "mitkRenderingManager.h" #include "mitkToolManager.h" #include "mitkCorrectorTool2D.xpm" -#include "mitkLabelSetImage.h" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, CorrectorTool2D, "Correction tool"); } mitk::CorrectorTool2D::CorrectorTool2D(int paintingPixelValue) : FeedbackContourTool("PressMoveRelease"), m_PaintingPixelValue(paintingPixelValue) { - GetFeedbackContour()->SetClosed(false); // don't close the contour to a polygon } mitk::CorrectorTool2D::~CorrectorTool2D() { } void mitk::CorrectorTool2D::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); } const char **mitk::CorrectorTool2D::GetXPM() const { return mitkCorrectorTool2D_xpm; } us::ModuleResource mitk::CorrectorTool2D::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Correction_48x48.png"); return resource; } us::ModuleResource mitk::CorrectorTool2D::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Correction_Cursor_32x32.png"); return resource; } const char *mitk::CorrectorTool2D::GetName() const { return "Correction"; } void mitk::CorrectorTool2D::Activated() { Superclass::Activated(); } void mitk::CorrectorTool2D::Deactivated() { Superclass::Deactivated(); } void mitk::CorrectorTool2D::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); - int timestep = positionEvent->GetSender()->GetTimeStep(); - ContourModel *contour = FeedbackContourTool::GetFeedbackContour(); - contour->Initialize(); - contour->Expand(timestep + 1); - contour->SetClosed(false, timestep); + this->ClearsCurrentFeedbackContour(false); mitk::Point3D point = positionEvent->GetPositionInWorld(); - contour->AddVertex(point, timestep); + this->AddVertexToCurrentFeedbackContour(point); FeedbackContourTool::SetFeedbackContourVisible(true); } void mitk::CorrectorTool2D::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; - int timestep = positionEvent->GetSender()->GetTimeStep(); - ContourModel *contour = FeedbackContourTool::GetFeedbackContour(); mitk::Point3D point = positionEvent->GetPositionInWorld(); - contour->AddVertex(point, timestep); + this->AddVertexToCurrentFeedbackContour(point); assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::CorrectorTool2D::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { // 1. Hide the feedback contour, find out which slice the user clicked, find out which slice of the toolmanager's // working image corresponds to that FeedbackContourTool::SetFeedbackContourVisible(false); auto *positionEvent = dynamic_cast(interactionEvent); // const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return; assert(positionEvent->GetSender()->GetRenderWindow()); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); DataNode *workingNode(m_ToolManager->GetWorkingData(0)); if (!workingNode) return; auto *workingImage = dynamic_cast(workingNode->GetData()); const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (!workingImage || !planeGeometry) return; const auto *abstractTransformGeometry( dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); if (!workingImage || abstractTransformGeometry) return; // 2. Slice is known, now we try to get it as a 2D image and project the contour into index coordinates of this slice m_WorkingSlice = FeedbackContourTool::GetAffectedImageSliceAs2DImage(positionEvent, workingImage); if (m_WorkingSlice.IsNull()) { MITK_ERROR << "Unable to extract slice." << std::endl; return; } - int timestep = positionEvent->GetSender()->GetTimeStep(); + auto feedbackContour = this->GetFeedbackContour(); + auto timestep = positionEvent->GetSender()->GetTimeStep(feedbackContour); mitk::ContourModel::Pointer singleTimestepContour = mitk::ContourModel::New(); - auto it = FeedbackContourTool::GetFeedbackContour()->Begin(timestep); - auto end = FeedbackContourTool::GetFeedbackContour()->End(timestep); + auto it = feedbackContour->Begin(timestep); + auto end = feedbackContour->End(timestep); while (it != end) { singleTimestepContour->AddVertex((*it)->Coordinates); it++; } CorrectorAlgorithm::Pointer algorithm = CorrectorAlgorithm::New(); algorithm->SetInput(m_WorkingSlice); algorithm->SetContour(singleTimestepContour); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); algorithm->SetFillColor(activePixelValue); try { algorithm->UpdateLargestPossibleRegion(); } catch (std::exception &e) { MITK_ERROR << "Caught exception '" << e.what() << "'" << std::endl; } - mitk::Image::Pointer resultSlice = mitk::Image::New(); - resultSlice->Initialize(algorithm->GetOutput()); - - auto* labelSetImage = dynamic_cast(workingImage); - if (nullptr != labelSetImage) - { - mitk::Image::Pointer erg1 = FeedbackContourTool::GetAffectedImageSliceAs2DImage(positionEvent, workingImage); - SegTool2D::WritePreviewOnWorkingImage(erg1, algorithm->GetOutput(), workingImage, activePixelValue, 0); - SegTool2D::WriteBackSegmentationResult(positionEvent, erg1); - } - else - { - mitk::ImageReadAccessor imAccess(algorithm->GetOutput()); - resultSlice->SetVolume(imAccess.GetData()); - this->WriteBackSegmentationResult(positionEvent, resultSlice); - } + mitk::Image::Pointer resultSlice = FeedbackContourTool::GetAffectedImageSliceAs2DImage(positionEvent, workingImage); + SegTool2D::WritePreviewOnWorkingImage(resultSlice, algorithm->GetOutput(), workingImage, activePixelValue); + SegTool2D::WriteBackSegmentationResult(positionEvent, resultSlice); } diff --git a/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.cpp b/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.cpp index 9f15b3765d..644e2de507 100644 --- a/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.cpp +++ b/Modules/Segmentation/Interactions/mitkDrawPaintbrushTool.cpp @@ -1,58 +1,59 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkDrawPaintbrushTool.h" #include "mitkDrawPaintbrushTool.xpm" // us #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, DrawPaintbrushTool, "Paintbrush drawing tool"); } mitk::DrawPaintbrushTool::DrawPaintbrushTool() : PaintbrushTool(1) { + FeedbackContourTool::SetFeedbackContourColorDefault(); } mitk::DrawPaintbrushTool::~DrawPaintbrushTool() { } const char **mitk::DrawPaintbrushTool::GetXPM() const { return mitkDrawPaintbrushTool_xpm; } us::ModuleResource mitk::DrawPaintbrushTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Paint_48x48.png"); return resource; } us::ModuleResource mitk::DrawPaintbrushTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("Paint_Cursor_32x32.png"); return resource; } const char *mitk::DrawPaintbrushTool::GetName() const { return "Paint"; } diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp new file mode 100644 index 0000000000..1ded558ddc --- /dev/null +++ b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.cpp @@ -0,0 +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 workingPlaneGeometry = positionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); + + // 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(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) + { + 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, + 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/mitkFastMarchingTool3D.h b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h similarity index 67% copy from Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h copy to Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h index 652e0d550b..ebec9fd0b2 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingBaseTool.h @@ -1,124 +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 mitkFastMarchingTool3D_h_Included -#define mitkFastMarchingTool3D_h_Included +#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. + \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 FastMarchingTool3D : public AutoSegmentationWithPreviewTool + class MITKSEGMENTATION_EXPORT FastMarchingBaseTool : public AutoSegmentationWithPreviewTool { public: - mitkClassMacro(FastMarchingTool3D, AutoSegmentationWithPreviewTool); - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); + mitkClassMacro(FastMarchingBaseTool, AutoSegmentationWithPreviewTool); bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; /* icon stuff */ const char **GetXPM() const override; - const char *GetName() 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: - FastMarchingTool3D(); - ~FastMarchingTool3D() override; + FastMarchingBaseTool(unsigned int toolDim); + ~FastMarchingBaseTool() override; + + void ConnectActionsAndFunctions() override; /// \brief Add point action of StateMachine pattern - virtual void OnAddPoint(); + virtual void OnAddPoint(StateMachineAction*, InteractionEvent* interactionEvent); /// \brief Delete action of StateMachine pattern - virtual void OnDelete(); + virtual void OnDelete(StateMachineAction*, InteractionEvent* interactionEvent); - void UpdatePrepare() override; - void UpdateCleanUp() override; void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; template void DoITKFastMarching(const itk::Image* inputImage, - mitk::Image* segmentation, unsigned int timeStep); + 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 - typedef float InternalPixelType; - typedef itk::Image InternalImageType; - typedef itk::FastMarchingImageFilter FastMarchingFilterType; - typedef FastMarchingFilterType::NodeContainer NodeContainer; - typedef FastMarchingFilterType::NodeType NodeType; + DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points + PointSet::Pointer m_SeedsAsPointSet; - NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching + 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; - mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points - mitk::PointSet::Pointer m_SeedsAsPointSet; - mitk::PointSetDataInteractor::Pointer m_SeedPointInteractor; - unsigned int m_PointSetAddObserverTag; - unsigned int m_PointSetRemoveObserverTag; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingTool.cpp index b4220d6acb..95eb57c850 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool.cpp @@ -1,483 +1,34 @@ /*============================================================================ 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 "mitkFastMarchingTool.h" -#include "mitkToolManager.h" -#include "mitkBaseRenderer.h" -#include "mitkInteractionConst.h" -#include "mitkRenderingManager.h" - -#include "itkOrImageFilter.h" -#include "mitkImageTimeSelector.h" - -// us -#include -#include -#include -#include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, FastMarchingTool, "FastMarching2D tool"); } mitk::FastMarchingTool::FastMarchingTool() - : FeedbackContourTool("FastMarchingTool"), - m_NeedUpdate(true), - m_CurrentTimeStep(0), - m_PositionEvent(nullptr), - m_LowerThreshold(0), - m_UpperThreshold(200), - m_StoppingValue(100), - m_Sigma(1.0), - m_Alpha(-0.5), - m_Beta(3.0) + : FastMarchingBaseTool(2) { } mitk::FastMarchingTool::~FastMarchingTool() { - if (this->m_SmoothFilter.IsNotNull()) - this->m_SmoothFilter->RemoveAllObservers(); - - if (this->m_SigmoidFilter.IsNotNull()) - this->m_SigmoidFilter->RemoveAllObservers(); - - if (this->m_GradientMagnitudeFilter.IsNotNull()) - this->m_GradientMagnitudeFilter->RemoveAllObservers(); - - if (this->m_FastMarchingFilter.IsNotNull()) - this->m_FastMarchingFilter->RemoveAllObservers(); -} - -void mitk::FastMarchingTool::ConnectActionsAndFunctions() -{ - CONNECT_FUNCTION("ShiftSecondaryButtonPressed", OnAddPoint); - CONNECT_FUNCTION("ShiftPrimaryButtonPressed", OnAddPoint); - CONNECT_FUNCTION("DeletePoint", OnDelete); -} - -const char **mitk::FastMarchingTool::GetXPM() const -{ - return nullptr; // mitkFastMarchingTool_xpm; -} - -us::ModuleResource mitk::FastMarchingTool::GetIconResource() const -{ - us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); - return resource; -} - -us::ModuleResource mitk::FastMarchingTool::GetCursorIconResource() const -{ - us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("FastMarching_Cursor_32x32.png"); - return resource; } const char *mitk::FastMarchingTool::GetName() const { return "2D Fast Marching"; } -void mitk::FastMarchingTool::BuildITKPipeline() -{ - m_ReferenceImageSliceAsITK = InternalImageType::New(); - - m_ReferenceImageSlice = GetAffectedReferenceSlice(m_PositionEvent); - CastToItkImage(m_ReferenceImageSlice, m_ReferenceImageSliceAsITK); - - m_ProgressCommand = mitk::ToolCommand::New(); - - m_SmoothFilter = SmoothingFilterType::New(); - m_SmoothFilter->SetInput(m_ReferenceImageSliceAsITK); - m_SmoothFilter->SetTimeStep(0.05); - m_SmoothFilter->SetNumberOfIterations(2); - m_SmoothFilter->SetConductanceParameter(9.0); - - m_GradientMagnitudeFilter = GradientFilterType::New(); - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - - m_SigmoidFilter = SigmoidFilterType::New(); - m_SigmoidFilter->SetAlpha(m_Alpha); - m_SigmoidFilter->SetBeta(m_Beta); - m_SigmoidFilter->SetOutputMinimum(0.0); - m_SigmoidFilter->SetOutputMaximum(1.0); - - m_FastMarchingFilter = FastMarchingFilterType::New(); - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); - - m_ThresholdFilter = ThresholdingFilterType::New(); - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_ThresholdFilter->SetOutsideValue(0); - m_ThresholdFilter->SetInsideValue(1.0); - - m_SeedContainer = NodeContainer::New(); - m_SeedContainer->Initialize(); - m_FastMarchingFilter->SetTrialPoints(m_SeedContainer); - - if (this->m_SmoothFilter.IsNotNull()) - this->m_SmoothFilter->RemoveAllObservers(); - - if (this->m_SigmoidFilter.IsNotNull()) - this->m_SigmoidFilter->RemoveAllObservers(); - - if (this->m_GradientMagnitudeFilter.IsNotNull()) - this->m_GradientMagnitudeFilter->RemoveAllObservers(); - - if (this->m_FastMarchingFilter.IsNotNull()) - this->m_FastMarchingFilter->RemoveAllObservers(); - - m_SmoothFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_GradientMagnitudeFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_SigmoidFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - m_FastMarchingFilter->AddObserver(itk::ProgressEvent(), m_ProgressCommand); - - m_SmoothFilter->SetInput(m_ReferenceImageSliceAsITK); - m_GradientMagnitudeFilter->SetInput(m_SmoothFilter->GetOutput()); - m_SigmoidFilter->SetInput(m_GradientMagnitudeFilter->GetOutput()); - m_FastMarchingFilter->SetInput(m_SigmoidFilter->GetOutput()); - m_ThresholdFilter->SetInput(m_FastMarchingFilter->GetOutput()); - m_ReferenceImageSliceAsITK = InternalImageType::New(); -} - -void mitk::FastMarchingTool::SetUpperThreshold(double value) -{ - if (m_UpperThreshold != value) - { - m_UpperThreshold = value / 10.0; - m_ThresholdFilter->SetUpperThreshold(m_UpperThreshold); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetLowerThreshold(double value) -{ - if (m_LowerThreshold != value) - { - m_LowerThreshold = value / 10.0; - m_ThresholdFilter->SetLowerThreshold(m_LowerThreshold); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetBeta(double value) -{ - if (m_Beta != value) - { - m_Beta = value; - m_SigmoidFilter->SetBeta(m_Beta); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetSigma(double value) -{ - if (m_Sigma != value) - { - m_Sigma = value; - m_GradientMagnitudeFilter->SetSigma(m_Sigma); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetAlpha(double value) -{ - if (m_Alpha != value) - { - m_Alpha = value; - m_SigmoidFilter->SetAlpha(m_Alpha); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::SetStoppingValue(double value) -{ - if (m_StoppingValue != value) - { - m_StoppingValue = value; - m_FastMarchingFilter->SetStoppingValue(m_StoppingValue); - m_NeedUpdate = true; - } -} - -void mitk::FastMarchingTool::Activated() -{ - Superclass::Activated(); - - m_ResultImageNode = mitk::DataNode::New(); - m_ResultImageNode->SetName("FastMarching_Preview"); - m_ResultImageNode->SetBoolProperty("helper object", true); - m_ResultImageNode->SetColor(0.0, 1.0, 0.0); - m_ResultImageNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_ResultImageNode, m_ToolManager->GetReferenceData(0)); - - m_SeedsAsPointSet = mitk::PointSet::New(); - m_SeedsAsPointSetNode = mitk::DataNode::New(); - m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); - m_SeedsAsPointSetNode->SetName("Seeds_Preview"); - m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); - m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); - m_SeedsAsPointSetNode->SetVisibility(true); - m_ToolManager->GetDataStorage()->Add(this->m_SeedsAsPointSetNode, m_ToolManager->GetReferenceData(0)); - - this->Initialize(); -} - -void mitk::FastMarchingTool::Deactivated() -{ - m_ToolManager->GetDataStorage()->Remove(this->m_ResultImageNode); - m_ToolManager->GetDataStorage()->Remove(this->m_SeedsAsPointSetNode); - this->ClearSeeds(); - m_ResultImageNode = nullptr; - m_SeedsAsPointSetNode = nullptr; - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - Superclass::Deactivated(); -} - -void mitk::FastMarchingTool::Initialize() -{ - m_ReferenceImage = dynamic_cast(m_ToolManager->GetReferenceData(0)->GetData()); - if (m_ReferenceImage->GetTimeGeometry()->CountTimeSteps() > 1) - { - mitk::ImageTimeSelector::Pointer timeSelector = ImageTimeSelector::New(); - timeSelector->SetInput(m_ReferenceImage); - timeSelector->SetTimeNr(m_CurrentTimeStep); - timeSelector->UpdateLargestPossibleRegion(); - m_ReferenceImage = timeSelector->GetOutput(); - } - m_NeedUpdate = true; -} - -void mitk::FastMarchingTool::ConfirmSegmentation() -{ - // combine preview image with current working segmentation - if (dynamic_cast(m_ResultImageNode->GetData())) - { - // logical or combination of preview and segmentation slice - - Image::Pointer workingImageSlice; - - Image::Pointer workingImage = dynamic_cast(this->m_ToolManager->GetWorkingData(0)->GetData()); - TimePointType referenceImageTimePoint = m_ReferenceImage->GetTimeGeometry()->TimeStepToTimePoint(m_CurrentTimeStep); - TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(referenceImageTimePoint); - workingImageSlice = GetAffectedImageSliceAs2DImage(m_WorkingPlane, workingImage, workingImageTimeStep); - - Image::Pointer segmentationResult = Image::New(); - - bool isDeprecatedUnsignedCharSegmentation = - (workingImage->GetPixelType().GetComponentType() == itk::ImageIOBase::UCHAR); - - if (isDeprecatedUnsignedCharSegmentation) - { - typedef itk::Image OutputUCharImageType; - OutputUCharImageType::Pointer workingImageSliceInITK = OutputUCharImageType::New(); - - CastToItkImage(workingImageSlice, workingImageSliceInITK); - - typedef itk::OrImageFilter OrImageFilterType; - OrImageFilterType::Pointer orFilter = OrImageFilterType::New(); - - orFilter->SetInput1(m_ThresholdFilter->GetOutput()); - orFilter->SetInput2(workingImageSliceInITK); - orFilter->Update(); - - mitk::CastToMitkImage(orFilter->GetOutput(), segmentationResult); - } - else - { - OutputImageType::Pointer workingImageSliceInITK = OutputImageType::New(); - - CastToItkImage(workingImageSlice, workingImageSliceInITK); - - typedef itk::OrImageFilter OrImageFilterType; - OrImageFilterType::Pointer orFilter = OrImageFilterType::New(); - - orFilter->SetInput(0, m_ThresholdFilter->GetOutput()); - orFilter->SetInput(1, workingImageSliceInITK); - orFilter->Update(); - - mitk::CastToMitkImage(orFilter->GetOutput(), segmentationResult); - } - - segmentationResult->GetGeometry()->SetOrigin(workingImageSlice->GetGeometry()->GetOrigin()); - segmentationResult->GetGeometry()->SetIndexToWorldTransform( - workingImageSlice->GetGeometry()->GetIndexToWorldTransform()); - - // write to segmentation volume and hide preview image - // again, current time step is not considered - this->WriteBackSegmentationResult(m_WorkingPlane, segmentationResult, workingImageTimeStep); - this->m_ResultImageNode->SetVisibility(false); - - this->ClearSeeds(); - } - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - m_ToolManager->ActivateTool(-1); -} - -void mitk::FastMarchingTool::OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) -{ - // Add a new seed point for FastMarching algorithm - auto *positionEvent = dynamic_cast(interactionEvent); - // const PositionEvent* p = dynamic_cast(stateEvent->GetEvent()); - if (positionEvent == nullptr) - return; - - if (m_PositionEvent.IsNotNull()) - m_PositionEvent = nullptr; - - m_PositionEvent = - InteractionPositionEvent::New(positionEvent->GetSender(), positionEvent->GetPointerPositionOnScreen()); - - // if click was on another renderwindow or slice then reset pipeline and preview - if ((m_LastEventSender != m_PositionEvent->GetSender()) || - (m_LastEventSlice != m_PositionEvent->GetSender()->GetSlice())) - { - this->BuildITKPipeline(); - this->ClearSeeds(); - } - - m_LastEventSender = m_PositionEvent->GetSender(); - m_LastEventSlice = m_LastEventSender->GetSlice(); - m_WorkingPlane = positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone(); - - mitk::Point3D clickInIndex; - - m_ReferenceImageSlice->GetGeometry()->WorldToIndex(m_PositionEvent->GetPositionInWorld(), clickInIndex); - itk::Index<2> seedPosition; - seedPosition[0] = clickInIndex[0]; - seedPosition[1] = clickInIndex[1]; - - NodeType node; - const double seedValue = 0.0; - node.SetValue(seedValue); - node.SetIndex(seedPosition); - this->m_SeedContainer->InsertElement(this->m_SeedContainer->Size(), node); - m_FastMarchingFilter->Modified(); - - m_SeedsAsPointSet->InsertPoint(m_SeedsAsPointSet->GetSize(), m_PositionEvent->GetPositionInWorld()); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - m_NeedUpdate = true; - - this->Update(); - - m_ReadyMessage.Send(); -} - -void mitk::FastMarchingTool::OnDelete(StateMachineAction *, InteractionEvent *) -{ - // delete last seed point - if (!(this->m_SeedContainer->empty())) - { - // delete last element of seeds container - this->m_SeedContainer->pop_back(); - m_FastMarchingFilter->Modified(); - - // delete last point in pointset - somehow ugly - m_SeedsAsPointSet->GetPointSet()->GetPoints()->DeleteIndex(m_SeedsAsPointSet->GetSize() - 1); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - m_NeedUpdate = true; - - this->Update(); - } -} - -void mitk::FastMarchingTool::Update() -{ - const unsigned int progress_steps = 20; - - // update FastMarching pipeline and show result - if (m_NeedUpdate) - { - m_ProgressCommand->AddStepsToDo(progress_steps); - CurrentlyBusy.Send(true); - try - { - m_ThresholdFilter->Update(); - } - catch (itk::ExceptionObject &excep) - { - MITK_ERROR << "Exception caught: " << excep.GetDescription(); - - // progress by max step count, will force - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - std::string msg = excep.GetDescription(); - ErrorMessage.Send(msg); - - return; - } - m_ProgressCommand->SetProgress(progress_steps); - CurrentlyBusy.Send(false); - - // make output visible - mitk::Image::Pointer result = mitk::Image::New(); - CastToMitkImage(m_ThresholdFilter->GetOutput(), result); - result->GetGeometry()->SetOrigin(m_ReferenceImageSlice->GetGeometry()->GetOrigin()); - result->GetGeometry()->SetIndexToWorldTransform(m_ReferenceImageSlice->GetGeometry()->GetIndexToWorldTransform()); - m_ResultImageNode->SetData(result); - m_ResultImageNode->SetVisibility(true); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - } -} - -void mitk::FastMarchingTool::ClearSeeds() -{ - // clear seeds for FastMarching as well as the PointSet for visualization - if (this->m_SeedContainer.IsNotNull()) - this->m_SeedContainer->Initialize(); - - if (this->m_SeedsAsPointSet.IsNotNull()) - { - this->m_SeedsAsPointSet = mitk::PointSet::New(); - this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); - m_SeedsAsPointSetNode->SetName("Seeds_Preview"); - m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); - m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); - m_SeedsAsPointSetNode->SetVisibility(true); - } - - if (this->m_FastMarchingFilter.IsNotNull()) - m_FastMarchingFilter->Modified(); - - this->m_NeedUpdate = true; -} - -void mitk::FastMarchingTool::Reset() -{ - // clear all seeds and preview empty result - this->ClearSeeds(); - - m_ResultImageNode->SetVisibility(false); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); -} - -void mitk::FastMarchingTool::SetCurrentTimeStep(int t) -{ - if (m_CurrentTimeStep != t) - { - m_CurrentTimeStep = t; - - this->Initialize(); - } -} diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool.h b/Modules/Segmentation/Interactions/mitkFastMarchingTool.h index e98b2fba71..9aa06c0b1a 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool.h @@ -1,175 +1,48 @@ /*============================================================================ 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 mitkFastMarchingTool_h_Included #define mitkFastMarchingTool_h_Included -#include "mitkDataNode.h" -#include "mitkFeedbackContourTool.h" -#include "mitkPointSet.h" -#include "mitkToolCommand.h" -#include - -#include "mitkMessage.h" - -#include "itkImage.h" +#include "mitkFastMarchingBaseTool.h" -// itk filter -#include "itkBinaryThresholdImageFilter.h" -#include "itkCurvatureAnisotropicDiffusionImageFilter.h" -#include "itkFastMarchingImageFilter.h" -#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" -#include "itkSigmoidImageFilter.h" - -namespace us -{ - class ModuleResource; -} +#include namespace mitk { - class StateMachineAction; - class InteractionEvent; - /** \brief FastMarching semgentation tool. 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 FastMarchingTool : public FeedbackContourTool + class MITKSEGMENTATION_EXPORT FastMarchingTool : public FastMarchingBaseTool { - mitkNewMessageMacro(Ready); - public: - mitkClassMacro(FastMarchingTool, FeedbackContourTool); + mitkClassMacro(FastMarchingTool, FastMarchingBaseTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - /* typedefs for itk pipeline */ - typedef float InternalPixelType; - typedef itk::Image InternalImageType; - typedef mitk::Tool::DefaultSegmentationDataType OutputPixelType; - typedef itk::Image OutputImageType; - - typedef itk::BinaryThresholdImageFilter ThresholdingFilterType; - typedef itk::CurvatureAnisotropicDiffusionImageFilter SmoothingFilterType; - typedef itk::GradientMagnitudeRecursiveGaussianImageFilter GradientFilterType; - typedef itk::SigmoidImageFilter SigmoidFilterType; - typedef itk::FastMarchingImageFilter FastMarchingFilterType; - typedef FastMarchingFilterType::NodeContainer NodeContainer; - typedef FastMarchingFilterType::NodeType NodeType; - - /* icon stuff */ - const char **GetXPM() const override; const char *GetName() const override; - us::ModuleResource GetCursorIconResource() const override; - us::ModuleResource GetIconResource() const 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 Adds the feedback image to the current working image. - virtual void ConfirmSegmentation(); - - /// \brief Set the working time step. - virtual void SetCurrentTimeStep(int t); - - /// \brief Clear all seed points. - void ClearSeeds(); - - /// \brief Updates the itk pipeline and shows the result of FastMarching. - void Update(); - protected: FastMarchingTool(); ~FastMarchingTool() override; - - void ConnectActionsAndFunctions() override; - - // virtual float CanHandleEvent( StateEvent const *stateEvent) const; - - void Activated() override; - void Deactivated() override; - virtual void Initialize(); - - virtual void BuildITKPipeline(); - - /// \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); - - /// \brief Reset all relevant inputs of the itk pipeline. - void Reset(); - - mitk::ToolCommand::Pointer m_ProgressCommand; - - Image::Pointer m_ReferenceImage; - Image::Pointer m_ReferenceImageSlice; - - bool m_NeedUpdate; - - int m_CurrentTimeStep; - - mitk::InteractionPositionEvent::Pointer m_PositionEvent; - - 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 - - NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching - - InternalImageType::Pointer m_ReferenceImageSliceAsITK; // the reference image as itk::Image - - mitk::DataNode::Pointer m_ResultImageNode; // holds the result as a preview image - - mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points - mitk::PointSet::Pointer m_SeedsAsPointSet; - - ThresholdingFilterType::Pointer m_ThresholdFilter; - SmoothingFilterType::Pointer m_SmoothFilter; - GradientFilterType::Pointer m_GradientMagnitudeFilter; - SigmoidFilterType::Pointer m_SigmoidFilter; - FastMarchingFilterType::Pointer m_FastMarchingFilter; - - private: - PlaneGeometry::Pointer m_WorkingPlane; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp index 8ada9faf2b..0de2aacf30 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.cpp @@ -1,334 +1,32 @@ /*============================================================================ 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 "mitkFastMarchingTool3D.h" -#include "mitkToolManager.h" - -#include "mitkBaseRenderer.h" -#include "mitkInteractionConst.h" -#include "mitkRenderingManager.h" - -#include "mitkImageAccessByItk.h" - -// itk filter -#include "itkBinaryThresholdImageFilter.h" -#include "itkCurvatureAnisotropicDiffusionImageFilter.h" -#include "itkGradientMagnitudeRecursiveGaussianImageFilter.h" -#include "itkSigmoidImageFilter.h" - -// us -#include -#include -#include -#include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, FastMarchingTool3D, "FastMarching3D tool"); } mitk::FastMarchingTool3D::FastMarchingTool3D() - : AutoSegmentationWithPreviewTool(), - m_LowerThreshold(0), - m_UpperThreshold(200), - m_StoppingValue(100), - m_Sigma(1.0), - m_Alpha(-0.5), - m_Beta(3.0), - m_PointSetAddObserverTag(0), - m_PointSetRemoveObserverTag(0) + : FastMarchingBaseTool(3) { } mitk::FastMarchingTool3D::~FastMarchingTool3D() { } -bool mitk::FastMarchingTool3D::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::FastMarchingTool3D::GetXPM() const -{ - return nullptr; // mitkFastMarchingTool3D_xpm; -} - -us::ModuleResource mitk::FastMarchingTool3D::GetIconResource() const -{ - us::Module *module = us::GetModuleContext()->GetModule(); - us::ModuleResource resource = module->GetResource("FastMarching_48x48.png"); - return resource; -} - const char *mitk::FastMarchingTool3D::GetName() const { return "Fast Marching 3D"; } - -void mitk::FastMarchingTool3D::SetUpperThreshold(double value) -{ - m_UpperThreshold = value / 10.0; -} - -void mitk::FastMarchingTool3D::SetLowerThreshold(double value) -{ - m_LowerThreshold = value / 10.0; -} - -void mitk::FastMarchingTool3D::SetBeta(double value) -{ - if (m_Beta != value) - { - m_Beta = value; - } -} - -void mitk::FastMarchingTool3D::SetSigma(double value) -{ - if (m_Sigma != value) - { - if (value > 0.0) - { - m_Sigma = value; - } - } -} - -void mitk::FastMarchingTool3D::SetAlpha(double value) -{ - if (m_Alpha != value) - { - m_Alpha = value; - } -} - -void mitk::FastMarchingTool3D::SetStoppingValue(double value) -{ - if (m_StoppingValue != value) - { - m_StoppingValue = value; - } -} - -void mitk::FastMarchingTool3D::Activated() -{ - Superclass::Activated(); - - m_SeedsAsPointSet = mitk::PointSet::New(); - m_SeedsAsPointSetNode = mitk::DataNode::New(); - m_SeedsAsPointSetNode->SetData(m_SeedsAsPointSet); - m_SeedsAsPointSetNode->SetName("3D_FastMarching_PointSet"); - m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); - m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); - m_SeedsAsPointSetNode->SetVisibility(true); - - // Create PointSetData Interactor - m_SeedPointInteractor = mitk::PointSetDataInteractor::New(); - // Load the according state machine for regular point set interaction - m_SeedPointInteractor->LoadStateMachine("PointSet.xml"); - // Set the configuration file that defines the triggers for the transitions - m_SeedPointInteractor->SetEventConfig("PointSetConfig.xml"); - // set the DataNode (which already is added to the DataStorage - m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); - - m_ToolManager->GetDataStorage()->Add(m_SeedsAsPointSetNode, m_ToolManager->GetWorkingData(0)); - - m_SeedContainer = NodeContainer::New(); - m_SeedContainer->Initialize(); - - itk::SimpleMemberCommand::Pointer pointAddedCommand = - itk::SimpleMemberCommand::New(); - pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); - m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); - - itk::SimpleMemberCommand::Pointer pointRemovedCommand = - itk::SimpleMemberCommand::New(); - pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); - m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); -} - -void mitk::FastMarchingTool3D::Deactivated() -{ - this->ClearSeeds(); - - // Deactivate Interaction - m_SeedPointInteractor->SetDataNode(nullptr); - m_ToolManager->GetDataStorage()->Remove(m_SeedsAsPointSetNode); - m_SeedsAsPointSetNode = nullptr; - m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); - m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); - m_SeedsAsPointSet = nullptr; - - Superclass::Deactivated(); -} - -void mitk::FastMarchingTool3D::OnAddPoint() -{ - // Add a new seed point for FastMarching algorithm - mitk::Point3D clickInIndex; - - this->GetReferenceData()->GetGeometry()->WorldToIndex(m_SeedsAsPointSet->GetPoint(m_SeedsAsPointSet->GetSize() - 1), - clickInIndex); - itk::Index<3> seedPosition; - seedPosition[0] = clickInIndex[0]; - seedPosition[1] = clickInIndex[1]; - seedPosition[2] = clickInIndex[2]; - - NodeType node; - const double seedValue = 0.0; - node.SetValue(seedValue); - node.SetIndex(seedPosition); - this->m_SeedContainer->InsertElement(this->m_SeedContainer->Size(), node); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - this->UpdatePreview(); -} - -void mitk::FastMarchingTool3D::OnDelete() -{ - // delete last seed point - if (!(this->m_SeedContainer->empty())) - { - // delete last element of seeds container - this->m_SeedContainer->pop_back(); - - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); - - this->UpdatePreview(); - } -} - -void mitk::FastMarchingTool3D::ClearSeeds() -{ - // clear seeds for FastMarching as well as the PointSet for visualization - if (this->m_SeedContainer.IsNotNull()) - this->m_SeedContainer->Initialize(); - - if (this->m_SeedsAsPointSet.IsNotNull()) - { - // remove observers from current pointset - m_SeedsAsPointSet->RemoveObserver(m_PointSetAddObserverTag); - m_SeedsAsPointSet->RemoveObserver(m_PointSetRemoveObserverTag); - - // renew pointset - this->m_SeedsAsPointSet = mitk::PointSet::New(); - this->m_SeedsAsPointSetNode->SetData(this->m_SeedsAsPointSet); - m_SeedsAsPointSetNode->SetName("Seeds_Preview"); - m_SeedsAsPointSetNode->SetBoolProperty("helper object", true); - m_SeedsAsPointSetNode->SetColor(0.0, 1.0, 0.0); - m_SeedsAsPointSetNode->SetVisibility(true); - - // add callback function for adding and removing points - itk::SimpleMemberCommand::Pointer pointAddedCommand = - itk::SimpleMemberCommand::New(); - pointAddedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnAddPoint); - m_PointSetAddObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetAddEvent(), pointAddedCommand); - - itk::SimpleMemberCommand::Pointer pointRemovedCommand = - itk::SimpleMemberCommand::New(); - pointRemovedCommand->SetCallbackFunction(this, &mitk::FastMarchingTool3D::OnDelete); - m_PointSetRemoveObserverTag = m_SeedsAsPointSet->AddObserver(mitk::PointSetRemoveEvent(), pointRemovedCommand); - } -} - -template -void mitk::FastMarchingTool3D::DoITKFastMarching(const itk::Image* inputImage, - mitk::Image* segmentation, unsigned int timeStep) -{ - typedef itk::Image InputImageType; - - /* typedefs for itk pipeline */ - - 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; - - 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(m_SeedContainer); - - 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(); - - segmentation->SetVolume((void*)(thresholdFilter->GetOutput()->GetPixelContainer()->GetBufferPointer()), timeStep); -} - -void mitk::FastMarchingTool3D::UpdatePrepare() -{ - // remove interaction with poinset while updating - if (m_SeedPointInteractor.IsNotNull()) - m_SeedPointInteractor->SetDataNode(nullptr); -} - -void mitk::FastMarchingTool3D::UpdateCleanUp() -{ - // add interaction with poinset again - if (m_SeedPointInteractor.IsNotNull()) - m_SeedPointInteractor->SetDataNode(m_SeedsAsPointSetNode); -} - -void mitk::FastMarchingTool3D::DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) -{ - if (nullptr != inputAtTimeStep && nullptr != previewImage && m_SeedContainer.IsNotNull()) - { - AccessFixedDimensionByItk_n(inputAtTimeStep, DoITKFastMarching, 3, (previewImage, timeStep)); - } -} diff --git a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h index 652e0d550b..88b82a6038 100644 --- a/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h +++ b/Modules/Segmentation/Interactions/mitkFastMarchingTool3D.h @@ -1,124 +1,49 @@ /*============================================================================ 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 mitkFastMarchingTool3D_h_Included #define mitkFastMarchingTool3D_h_Included -#include "mitkAutoSegmentationWithPreviewTool.h" -#include "mitkDataNode.h" -#include "mitkPointSet.h" -#include "mitkPointSetDataInteractor.h" -#include "mitkToolCommand.h" - -#include "itkImage.h" -#include "itkFastMarchingImageFilter.h" +#include "mitkFastMarchingBaseTool.h" #include -namespace us -{ - class ModuleResource; -} - namespace mitk { /** \brief FastMarching semgentation tool. 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 FastMarchingTool3D : public AutoSegmentationWithPreviewTool + class MITKSEGMENTATION_EXPORT FastMarchingTool3D : public FastMarchingBaseTool { public: - mitkClassMacro(FastMarchingTool3D, AutoSegmentationWithPreviewTool); + mitkClassMacro(FastMarchingTool3D, FastMarchingBaseTool); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - bool CanHandle(const BaseData* referenceData, const BaseData* workingData) const override; - /* icon stuff */ - const char **GetXPM() const override; const char *GetName() 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: FastMarchingTool3D(); ~FastMarchingTool3D() override; - - /// \brief Add point action of StateMachine pattern - virtual void OnAddPoint(); - - /// \brief Delete action of StateMachine pattern - virtual void OnDelete(); - - void UpdatePrepare() override; - void UpdateCleanUp() override; - void DoUpdatePreview(const Image* inputAtTimeStep, Image* previewImage, TimeStepType timeStep) override; - - template - void DoITKFastMarching(const itk::Image* inputImage, - mitk::Image* segmentation, unsigned int timeStep); - - 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 - - typedef float InternalPixelType; - typedef itk::Image InternalImageType; - typedef itk::FastMarchingImageFilter FastMarchingFilterType; - typedef FastMarchingFilterType::NodeContainer NodeContainer; - typedef FastMarchingFilterType::NodeType NodeType; - - NodeContainer::Pointer m_SeedContainer; // seed points for FastMarching - - mitk::DataNode::Pointer m_SeedsAsPointSetNode; // used to visualize the seed points - mitk::PointSet::Pointer m_SeedsAsPointSet; - mitk::PointSetDataInteractor::Pointer m_SeedPointInteractor; - unsigned int m_PointSetAddObserverTag; - unsigned int m_PointSetRemoveObserverTag; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp index 4cea251184..c6eb82b51a 100644 --- a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp +++ b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.cpp @@ -1,140 +1,278 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkFeedbackContourTool.h" #include "mitkToolManager.h" #include "mitkColorProperty.h" #include "mitkProperties.h" #include "mitkStringProperty.h" #include "mitkBaseRenderer.h" #include "mitkDataStorage.h" #include "mitkRenderingManager.h" +#include "mitkAbstractTransformGeometry.h" + mitk::FeedbackContourTool::FeedbackContourTool(const char *type) : SegTool2D(type), m_FeedbackContourVisible(false) { - m_FeedbackContour = ContourModel::New(); - m_FeedbackContour->SetClosed(true); m_FeedbackContourNode = DataNode::New(); - m_FeedbackContourNode->SetData(m_FeedbackContour); m_FeedbackContourNode->SetProperty("name", StringProperty::New("One of FeedbackContourTool's feedback nodes")); m_FeedbackContourNode->SetProperty("visible", BoolProperty::New(true)); m_FeedbackContourNode->SetProperty("helper object", BoolProperty::New(true)); m_FeedbackContourNode->SetProperty("layer", IntProperty::New(1000)); m_FeedbackContourNode->SetProperty("contour.project-onto-plane", BoolProperty::New(false)); m_FeedbackContourNode->SetProperty("contour.width", FloatProperty::New(1.0)); // set to max short, max int doesn't work, max value somewhere hardcoded? m_FeedbackContourNode->SetProperty("layer", IntProperty::New(std::numeric_limits::max())); m_FeedbackContourNode->SetProperty("fixedLayer", BoolProperty::New(true)); - + this->InitializeFeedbackContour(true); SetFeedbackContourColorDefault(); } mitk::FeedbackContourTool::~FeedbackContourTool() { } void mitk::FeedbackContourTool::SetFeedbackContourColor(float r, float g, float b) { m_FeedbackContourNode->SetProperty("contour.color", ColorProperty::New(r, g, b)); } void mitk::FeedbackContourTool::SetFeedbackContourColorDefault() { m_FeedbackContourNode->SetProperty("contour.color", ColorProperty::New(0.0 / 255.0, 255.0 / 255.0, 0.0 / 255.0)); } void mitk::FeedbackContourTool::Deactivated() { Superclass::Deactivated(); DataStorage *storage = m_ToolManager->GetDataStorage(); if (storage && m_FeedbackContourNode.IsNotNull()) { storage->Remove(m_FeedbackContourNode); m_FeedbackContour->Clear(); SetFeedbackContourVisible(false); } } void mitk::FeedbackContourTool::Activated() { Superclass::Activated(); - - SetFeedbackContourVisible(true); + this->InitializeFeedbackContour(true); + this->SetFeedbackContourVisible(true); } -mitk::ContourModel *mitk::FeedbackContourTool::GetFeedbackContour() +const mitk::ContourModel *mitk::FeedbackContourTool::GetFeedbackContour() const { return m_FeedbackContour; } -void mitk::FeedbackContourTool::SetFeedbackContour(ContourModel::Pointer contour) +void mitk::FeedbackContourTool::InitializeFeedbackContour(bool isClosed) { - m_FeedbackContour = contour; + m_FeedbackContour = ContourModel::New(); + m_FeedbackContour->SetClosed(isClosed); + + auto workingImage = this->GetWorkingData(); + + if (nullptr != workingImage) + { + m_FeedbackContour->Expand(workingImage->GetTimeSteps()); + + auto contourTimeGeometry = workingImage->GetTimeGeometry()->Clone(); + contourTimeGeometry->ReplaceTimeStepGeometries(m_FeedbackContour->GetGeometry()); + m_FeedbackContour->SetTimeGeometry(contourTimeGeometry); + + for (unsigned int t = 0; t < m_FeedbackContour->GetTimeSteps(); ++t) + { + m_FeedbackContour->SetClosed(isClosed, t); + } + } + m_FeedbackContourNode->SetData(m_FeedbackContour); } +void mitk::FeedbackContourTool::ClearsCurrentFeedbackContour(bool isClosed) +{ + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) + { + MITK_WARN << "Cannot clear feedback contour at current time step. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: " << this->GetLastTimePointTriggered(); + return; + } + + auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); + m_FeedbackContour->Clear(feedbackTimeStep); + m_FeedbackContour->SetClosed(isClosed, feedbackTimeStep); +} + +void mitk::FeedbackContourTool::UpdateCurrentFeedbackContour(const ContourModel* sourceModel, TimeStepType sourceTimeStep) +{ + if (nullptr == sourceModel) + return; + + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) + { + MITK_WARN << "Cannot update feedback contour. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: "<GetLastTimePointTriggered(); + return; + } + + auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); + this->UpdateFeedbackContour(sourceModel, feedbackTimeStep, sourceTimeStep); +} + +void mitk::FeedbackContourTool::UpdateFeedbackContour(const ContourModel* sourceModel, TimeStepType feedbackTimeStep, TimeStepType sourceTimeStep) +{ + if (nullptr == sourceModel) + return; + + if (!sourceModel->GetTimeGeometry()->IsValidTimeStep(sourceTimeStep)) + { + MITK_WARN << "Cannot update feedback contour. Source contour time geometry does not support passed time step. Invalid time step: " << sourceTimeStep; + return; + } + + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimeStep(feedbackTimeStep)) + { + MITK_WARN << "Cannot update feedback contour. Feedback contour time geometry does not support passed time step. Invalid time step: "<UpdateContour(sourceModel, feedbackTimeStep, sourceTimeStep); +} + +void mitk::FeedbackContourTool::AddVertexToCurrentFeedbackContour(const Point3D& point) +{ + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimePoint(this->GetLastTimePointTriggered())) + { + MITK_WARN << "Cannot add vertex to feedback contour. Feedback contour is in invalid state as its time geometry does not support current selected time point. Invalid time point: " << this->GetLastTimePointTriggered(); + return; + } + + auto feedbackTimeStep = m_FeedbackContour->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); + this->AddVertexToFeedbackContour(point, feedbackTimeStep); +}; + +/** Adds a vertex to the feedback contour for the passed time step. If time step is invalid, nothing will be added.*/ +void mitk::FeedbackContourTool::AddVertexToFeedbackContour(const Point3D& point, TimeStepType feedbackTimeStep) +{ + if (!m_FeedbackContour->GetTimeGeometry()->IsValidTimeStep(feedbackTimeStep)) + { + MITK_WARN << "Cannot add vertex to feedback contour. Feedback contour time geometry does not support passed time step. Invalid time step: " << feedbackTimeStep; + return; + } + + m_FeedbackContour->AddVertex(point, feedbackTimeStep); +} + + void mitk::FeedbackContourTool::SetFeedbackContourVisible(bool visible) { if (m_FeedbackContourVisible == visible) return; // nothing changed if (DataStorage *storage = m_ToolManager->GetDataStorage()) { if (visible) { // Add the feedback contour node as a derived node of the first working data. // If there is no working data, the node is added at the top level. - storage->Add(m_FeedbackContourNode, m_ToolManager->GetWorkingData(0)); + storage->Add(m_FeedbackContourNode, this->GetWorkingDataNode()); } else { storage->Remove(m_FeedbackContourNode); } } m_FeedbackContourVisible = visible; } -mitk::ContourModel::Pointer mitk::FeedbackContourTool::ProjectContourTo2DSlice(Image *slice, - ContourModel *contourIn3D, +mitk::ContourModel::Pointer mitk::FeedbackContourTool::ProjectContourTo2DSlice(const Image *slice, + const ContourModel *contourIn3D, bool correctionForIpSegmentation, bool constrainToInside) { return mitk::ContourModelUtils::ProjectContourTo2DSlice( slice, contourIn3D, correctionForIpSegmentation, constrainToInside); } mitk::ContourModel::Pointer mitk::FeedbackContourTool::BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, - ContourModel *contourIn2D, + const ContourModel *contourIn2D, bool correctionForIpSegmentation) { return mitk::ContourModelUtils::BackProjectContourFrom2DSlice( sliceGeometry, contourIn2D, correctionForIpSegmentation); } +void mitk::FeedbackContourTool::WriteBackFeedbackContourAsSegmentationResult(const InteractionPositionEvent* positionEvent, int paintingPixelValue, bool setInvisibleAfterSuccess) +{ + if (!positionEvent) + return; + + auto workingImage = this->GetWorkingData(); + const auto planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); + + if (!workingImage || !planeGeometry) + return; + + const auto* abstractTransformGeometry( + dynamic_cast(positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); + if (nullptr != abstractTransformGeometry) + return; + + Image::Pointer slice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); + + if (slice.IsNull()) + { + MITK_ERROR << "Unable to extract slice." << std::endl; + return; + } + + const auto feedbackContour = FeedbackContourTool::GetFeedbackContour(); + auto contourTimeStep = positionEvent->GetSender()->GetTimeStep(feedbackContour); + + ContourModel::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( + slice, feedbackContour, false, false); // false: don't add 0.5 (done by FillContourInSlice) + // false: don't constrain the contour to the image's inside + + if (projectedContour.IsNull()) + return; + + auto activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); + + mitk::ContourModelUtils::FillContourInSlice( + projectedContour, contourTimeStep, slice, workingImage, paintingPixelValue * activePixelValue); + + this->WriteBackSegmentationResult(positionEvent, slice); + + if (setInvisibleAfterSuccess) + { + this->SetFeedbackContourVisible(false); + } +} + + void mitk::FeedbackContourTool::FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, int paintingPixelValue) { this->FillContourInSlice(projectedContour, 0, sliceImage, paintingPixelValue); } void mitk::FeedbackContourTool::FillContourInSlice(ContourModel *projectedContour, unsigned int timeStep, Image *sliceImage, int paintingPixelValue) { mitk::ContourModelUtils::FillContourInSlice(projectedContour, timeStep, sliceImage, sliceImage, paintingPixelValue); } diff --git a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h index 945c4911fd..60c6b60f34 100644 --- a/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h +++ b/Modules/Segmentation/Interactions/mitkFeedbackContourTool.h @@ -1,118 +1,149 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkFeedbackContourTool_h_Included #define mitkFeedbackContourTool_h_Included #include "mitkCommon.h" #include "mitkContourModelUtils.h" #include "mitkContourUtils.h" //TODO remove legacy support #include "mitkImage.h" #include "mitkSegTool2D.h" #include #include "mitkDataNode.h" #include "mitkImageCast.h" namespace mitk { /** \brief Base class for tools that use a contour for feedback \sa Tool \sa ContourModel \ingroup Interaction \ingroup ToolManagerEtAl Implements helper methods, that might be of use to all kind of 2D segmentation tools that use a contour for user feedback. - Providing a feedback contour that might be added or removed from the visible scene (SetFeedbackContourVisible). - Filling of a contour into a 2D slice These helper methods are actually implemented in ContourUtils now. FeedbackContourTool only forwards such requests. \warning Only to be instantiated by mitk::ToolManager. $Author: nolden $ */ class MITKSEGMENTATION_EXPORT FeedbackContourTool : public SegTool2D { public: mitkClassMacro(FeedbackContourTool, SegTool2D); protected: FeedbackContourTool(); // purposely hidden FeedbackContourTool(const char *); // purposely hidden ~FeedbackContourTool() override; - ContourModel *GetFeedbackContour(); - void SetFeedbackContour(ContourModel::Pointer); + const ContourModel *GetFeedbackContour() const; + + /** (Re)initialize the feesback contour by creating a new instance. + * It is assured that the new instance as the same time geometry than + * the working image.*/ + void InitializeFeedbackContour(bool isClosed); + + /** Clears the current time step of the feedback contour and resets its closed state.*/ + void ClearsCurrentFeedbackContour(bool isClosed); + + /** Updates the feedback contour of the currently selected time point. The update will be done + * by clearing all existings vertices at the current time point and copying the vertics of the + * source model at the specified source time step.*/ + void UpdateCurrentFeedbackContour(const ContourModel* sourceModel, TimeStepType sourceTimeStep = 0); + /** Updates the feedback contour at the time step specified by feedbackTimeStep. The update will be done + * by clearing all existings vertices at feedbackTimeStep and copying the vertics of the + * source model at the specified source time step.*/ + void UpdateFeedbackContour(const ContourModel* sourceModel, TimeStepType feedbackTimeStep, TimeStepType sourceTimeStep = 0); + + /** Adds a vertex to the feedback contour for the current time point. */ + void AddVertexToCurrentFeedbackContour(const Point3D& point); + + /** Adds a vertex to the feedback contour for the passed time step. If time step is invalid, nothing will be added.*/ + void AddVertexToFeedbackContour(const Point3D& point, TimeStepType feedbackTimeStep); void SetFeedbackContourVisible(bool); /// Provide values from 0.0 (black) to 1.0 (full color) void SetFeedbackContourColor(float r, float g, float b); void SetFeedbackContourColorDefault(); void Deactivated() override; void Activated() override; /** \brief Projects a contour onto an image point by point. Converts from world to index coordinates. \param slice \param contourIn3D \param correctionForIpSegmentation adds 0.5 to x and y index coordinates (difference between ipSegmentation and MITK contours) \param constrainToInside */ - ContourModel::Pointer ProjectContourTo2DSlice(Image *slice, - ContourModel *contourIn3D, + ContourModel::Pointer ProjectContourTo2DSlice(const Image *slice, + const ContourModel *contourIn3D, bool correctionForIpSegmentation = false, bool constrainToInside = true); /** \brief Projects a slice index coordinates of a contour back into world coordinates. \param sliceGeometry \param contourIn2D \param correctionForIpSegmentation subtracts 0.5 to x and y index coordinates (difference between ipSegmentation and MITK contours) */ ContourModel::Pointer BackProjectContourFrom2DSlice(const BaseGeometry *sliceGeometry, - ContourModel *contourIn2D, + const ContourModel *contourIn2D, bool correctionForIpSegmentation = false); + /** Helper methods that checks all precondition and if they are fullfilled does the following: + * 1. Gets the contour of the time point specified by positionEvent. + * 2. Gets the affacted working slice of the time point specified by positionEvent. + * 3. projects the contour onto the working slice and then fills it with the passed paintingPixelValue (adjusted by the current active lable value) + * to the slice. + * 4. writes the slice back into the working image using SegTool2D::WriteBackSegmentationResult().*/ + void WriteBackFeedbackContourAsSegmentationResult(const InteractionPositionEvent* positionEvent, int paintingPixelValue, bool setInvisibleAfterSuccess = true); + /** \brief Fill a contour in a 2D slice with a specified pixel value. */ void FillContourInSlice(ContourModel *projectedContour, Image *sliceImage, int paintingPixelValue = 1); /** \brief Fill a contour in a 2D slice with a specified pixel value at a given time step. */ void FillContourInSlice(ContourModel *projectedContour, unsigned int timeStep, Image *sliceImage, int paintingPixelValue = 1); + private: ContourModel::Pointer m_FeedbackContour; DataNode::Pointer m_FeedbackContourNode; bool m_FeedbackContourVisible; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp index a75e5bd1ce..05a5088e3e 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.cpp @@ -1,593 +1,627 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include #include #include #include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, LiveWireTool2D, "LiveWire tool"); } mitk::LiveWireTool2D::LiveWireTool2D() : SegTool2D("LiveWireTool"), m_CreateAndUseDynamicCosts(false) { } mitk::LiveWireTool2D::~LiveWireTool2D() { this->ClearSegmentation(); } void mitk::LiveWireTool2D::RemoveHelperObjects() { auto dataStorage = m_ToolManager->GetDataStorage(); if (nullptr == dataStorage) return; for (const auto &editingContour : m_EditingContours) dataStorage->Remove(editingContour.first); for (const auto &workingContour : m_WorkingContours) dataStorage->Remove(workingContour.first); if (m_EditingContourNode.IsNotNull()) dataStorage->Remove(m_EditingContourNode); if (m_LiveWireContourNode.IsNotNull()) dataStorage->Remove(m_LiveWireContourNode); if (m_ContourNode.IsNotNull()) dataStorage->Remove(m_ContourNode); mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::LiveWireTool2D::ReleaseHelperObjects() { this->RemoveHelperObjects(); m_EditingContours.clear(); m_WorkingContours.clear(); m_EditingContourNode = nullptr; m_EditingContour = nullptr; m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; m_ContourNode = nullptr; m_Contour = nullptr; } void mitk::LiveWireTool2D::ReleaseInteractors() { this->EnableContourLiveWireInteraction(false); m_LiveWireInteractors.clear(); } void mitk::LiveWireTool2D::ConnectActionsAndFunctions() { CONNECT_CONDITION("CheckContourClosed", OnCheckPoint); CONNECT_FUNCTION("InitObject", OnInitLiveWire); CONNECT_FUNCTION("AddPoint", OnAddPoint); CONNECT_FUNCTION("CtrlAddPoint", OnAddPoint); CONNECT_FUNCTION("MovePoint", OnMouseMoveNoDynamicCosts); CONNECT_FUNCTION("FinishContour", OnFinish); CONNECT_FUNCTION("DeletePoint", OnLastSegmentDelete); CONNECT_FUNCTION("CtrlMovePoint", OnMouseMoved); } const char **mitk::LiveWireTool2D::GetXPM() const { return mitkLiveWireTool2D_xpm; } us::ModuleResource mitk::LiveWireTool2D::GetIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_48x48.png"); } us::ModuleResource mitk::LiveWireTool2D::GetCursorIconResource() const { return us::GetModuleContext()->GetModule()->GetResource("LiveWire_Cursor_32x32.png"); } const char *mitk::LiveWireTool2D::GetName() const { return "Live Wire"; } void mitk::LiveWireTool2D::Activated() { Superclass::Activated(); this->ResetToStartState(); this->EnableContourLiveWireInteraction(true); } void mitk::LiveWireTool2D::Deactivated() { this->ConfirmSegmentation(); Superclass::Deactivated(); } +void mitk::LiveWireTool2D::UpdateLiveWireContour() +{ + if (m_Contour.IsNotNull()) + { + auto timeGeometry = m_Contour->GetTimeGeometry()->Clone(); + m_LiveWireContour = this->m_LiveWireFilter->GetOutput(); + m_LiveWireContour->SetTimeGeometry(timeGeometry); //needed because the results of the filter are always from 0 ms to 1 ms and the filter also resets its outputs. + m_LiveWireContourNode->SetData(this->m_LiveWireContour); + } +} + +void mitk::LiveWireTool2D::OnTimePointChanged() +{ + auto reference = this->GetReferenceData(); + if (nullptr == reference || m_PlaneGeometry.IsNull() || m_LiveWireFilter.IsNull() || m_LiveWireContourNode.IsNull()) + return; + + auto timeStep = reference->GetTimeGeometry()->TimePointToTimeStep(this->GetLastTimePointTriggered()); + + m_ReferenceDataSlice = GetAffectedImageSliceAs2DImageByTimePoint(m_PlaneGeometry, reference, timeStep); + m_LiveWireFilter->SetInput(m_ReferenceDataSlice); + + m_LiveWireFilter->Update(); + + this->UpdateLiveWireContour(); + + RenderingManager::GetInstance()->RequestUpdateAll(); +}; + + void mitk::LiveWireTool2D::EnableContourLiveWireInteraction(bool on) { for (const auto &interactor : m_LiveWireInteractors) interactor->EnableInteraction(on); } void mitk::LiveWireTool2D::ConfirmSegmentation() { - auto referenceNode = m_ToolManager->GetReferenceData(0); - auto workingNode = m_ToolManager->GetWorkingData(0); - - if (nullptr == referenceNode || nullptr == workingNode) - return; - - auto referenceImage = dynamic_cast(referenceNode->GetData()); - auto workingImage = dynamic_cast(workingNode->GetData()); + auto referenceImage = this->GetReferenceData(); + auto workingImage = this->GetWorkingData(); if (nullptr == referenceImage || nullptr == workingImage) return; std::vector sliceInfos; sliceInfos.reserve(m_WorkingContours.size()); + const auto currentTimePoint = mitk::RenderingManager::GetInstance()->GetTimeNavigationController()->GetSelectedTimePoint(); + TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(currentTimePoint); + for (const auto &workingContour : m_WorkingContours) { auto contour = dynamic_cast(workingContour.first->GetData()); - if (nullptr == contour) + if (nullptr == contour || contour->IsEmpty()) continue; - const auto numberOfTimeSteps = contour->GetTimeSteps(); + auto sameSlicePredicate = [&workingContour, workingImageTimeStep](const SliceInformation& si) { return workingContour.second->IsOnPlane(si.plane) && workingImageTimeStep == si.timestep; }; - for (std::remove_const_t t = 0; t < numberOfTimeSteps; ++t) + auto finding = std::find_if(sliceInfos.begin(), sliceInfos.end(), sameSlicePredicate); + if (finding == sliceInfos.end()) { - if (contour->IsEmptyTimeStep(t)) - continue; - - TimePointType referenceImageTimePoint = referenceImage->GetTimeGeometry()->TimeStepToTimePoint(t); - TimeStepType workingImageTimeStep = workingImage->GetTimeGeometry()->TimePointToTimeStep(referenceImageTimePoint); + auto workingSlice = this->GetAffectedImageSliceAs2DImage(workingContour.second, workingImage, workingImageTimeStep)->Clone(); + sliceInfos.emplace_back(workingSlice, workingContour.second, workingImageTimeStep); + finding = std::prev(sliceInfos.end()); + } - auto workingSlice = this->GetAffectedImageSliceAs2DImage(workingContour.second, workingImage, workingImageTimeStep); - auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour, true, false); - int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); + //cast const away is OK in this case, because these are all slices created and manipulated + //localy in this function call. And we want to keep the high constness of SliceInformation for + //public interfaces. + auto workingSlice = const_cast(finding->slice.GetPointer()); - ContourModelUtils::FillContourInSlice( - projectedContour, referenceImageTimePoint, workingSlice, workingImage, activePixelValue); + auto projectedContour = ContourModelUtils::ProjectContourTo2DSlice(workingSlice, contour, true, false); + int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); - sliceInfos.emplace_back(workingSlice, workingContour.second, referenceImageTimePoint); - this->WriteSliceToVolume(sliceInfos.back()); - } + ContourModelUtils::FillContourInSlice( + projectedContour, workingSlice, workingImage, activePixelValue); } - this->WriteBackSegmentationResult(sliceInfos, false); + this->WriteBackSegmentationResults(sliceInfos); this->ClearSegmentation(); } void mitk::LiveWireTool2D::ClearSegmentation() { this->ReleaseHelperObjects(); this->ReleaseInteractors(); this->ResetToStartState(); } bool mitk::LiveWireTool2D::IsPositionEventInsideImageRegion(mitk::InteractionPositionEvent *positionEvent, mitk::BaseData *data) { bool isPositionEventInsideImageRegion = nullptr != data && data->GetGeometry()->IsInside(positionEvent->GetPositionInWorld()); if (!isPositionEventInsideImageRegion) MITK_WARN("LiveWireTool2D") << "PositionEvent is outside ImageRegion!"; return isPositionEventInsideImageRegion; } +mitk::ContourModel::Pointer mitk::LiveWireTool2D::CreateNewContour() const +{ + auto workingData = this->GetWorkingData(); + if (nullptr == workingData) + { + this->InteractiveSegmentationBugMessage("Cannot create new contour. No valid working data is set. Application is in invalid state."); + mitkThrow() << "Cannot create new contour. No valid working data is set. Application is in invalid state."; + } + + const auto minTime = workingData->GetTimeGeometry()->GetMinimumTimePoint(); + auto duration = workingData->GetTimeGeometry()->GetMaximumTimePoint() - minTime; + + if (duration <= 0) + { + duration = 1.; + }; + + auto contour = ContourModel::New(); + + //generate a time geometry that is always visible as the working contour should always be. + auto contourTimeGeometry = ProportionalTimeGeometry::New(); + contourTimeGeometry->SetStepDuration(std::numeric_limits::max()); + contourTimeGeometry->SetTimeStepGeometry(contour->GetTimeGeometry()->GetGeometryForTimeStep(0)->Clone(), 0); + contour->SetTimeGeometry(contourTimeGeometry); + + return contour; +} + void mitk::LiveWireTool2D::OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent) { auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; - auto workingDataNode = m_ToolManager->GetWorkingData(0); + auto workingDataNode = this->GetWorkingDataNode(); if (!IsPositionEventInsideImageRegion(positionEvent, workingDataNode->GetData())) { this->ResetToStartState(); return; } m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); - auto t = positionEvent->GetSender()->GetTimeStep(); - - m_Contour = mitk::ContourModel::New(); - m_Contour->Expand(t + 1); + m_Contour = this->CreateNewContour(); m_ContourNode = mitk::DataNode::New(); m_ContourNode->SetData(m_Contour); m_ContourNode->SetName("working contour node"); m_ContourNode->SetProperty("layer", IntProperty::New(100)); m_ContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_ContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_ContourNode->AddProperty("contour.color", ColorProperty::New(1.0f, 1.0f, 0.0f), nullptr, true); m_ContourNode->AddProperty("contour.points.color", ColorProperty::New(1.0f, 0.0f, 0.1f), nullptr, true); m_ContourNode->AddProperty("contour.controlpoints.show", BoolProperty::New(true), nullptr, true); - m_LiveWireContour = mitk::ContourModel::New(); - m_LiveWireContour->Expand(t + 1); + m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode = mitk::DataNode::New(); m_LiveWireContourNode->SetData(m_LiveWireContour); m_LiveWireContourNode->SetName("active livewire node"); m_LiveWireContourNode->SetProperty("layer", IntProperty::New(101)); m_LiveWireContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_LiveWireContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_LiveWireContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_LiveWireContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); - m_EditingContour = mitk::ContourModel::New(); - m_EditingContour->Expand(t + 1); + m_EditingContour = this->CreateNewContour(); m_EditingContourNode = mitk::DataNode::New(); m_EditingContourNode->SetData(m_EditingContour); m_EditingContourNode->SetName("editing node"); m_EditingContourNode->SetProperty("layer", IntProperty::New(102)); m_EditingContourNode->AddProperty("fixedLayer", BoolProperty::New(true)); m_EditingContourNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_EditingContourNode->AddProperty("contour.color", ColorProperty::New(0.1f, 1.0f, 0.1f), nullptr, true); m_EditingContourNode->AddProperty("contour.points.color", ColorProperty::New(0.0f, 0.0f, 1.0f), nullptr, true); m_EditingContourNode->AddProperty("contour.width", mitk::FloatProperty::New(4.0f), nullptr, true); auto dataStorage = m_ToolManager->GetDataStorage(); dataStorage->Add(m_ContourNode, workingDataNode); dataStorage->Add(m_LiveWireContourNode, workingDataNode); dataStorage->Add(m_EditingContourNode, workingDataNode); // Set current slice as input for ImageToLiveWireContourFilter - m_WorkingSlice = this->GetAffectedReferenceSlice(positionEvent); + m_ReferenceDataSlice = this->GetAffectedReferenceSlice(positionEvent); - auto origin = m_WorkingSlice->GetSlicedGeometry()->GetOrigin(); - m_WorkingSlice->GetSlicedGeometry()->WorldToIndex(origin, origin); - m_WorkingSlice->GetSlicedGeometry()->IndexToWorld(origin, origin); - m_WorkingSlice->GetSlicedGeometry()->SetOrigin(origin); + auto origin = m_ReferenceDataSlice->GetSlicedGeometry()->GetOrigin(); + m_ReferenceDataSlice->GetSlicedGeometry()->WorldToIndex(origin, origin); + m_ReferenceDataSlice->GetSlicedGeometry()->IndexToWorld(origin, origin); + m_ReferenceDataSlice->GetSlicedGeometry()->SetOrigin(origin); m_LiveWireFilter = ImageLiveWireContourModelFilter::New(); - m_LiveWireFilter->SetInput(m_WorkingSlice); + m_LiveWireFilter->SetInput(m_ReferenceDataSlice); // Map click to pixel coordinates auto click = positionEvent->GetPositionInWorld(); itk::Index<3> idx; - m_WorkingSlice->GetGeometry()->WorldToIndex(click, idx); + m_ReferenceDataSlice->GetGeometry()->WorldToIndex(click, idx); // Get the pixel with the highest gradient in a 7x7 region itk::Index<3> indexWithHighestGradient; - AccessFixedDimensionByItk_2(m_WorkingSlice, FindHighestGradientMagnitudeByITK, 2, idx, indexWithHighestGradient); + AccessFixedDimensionByItk_2(m_ReferenceDataSlice, FindHighestGradientMagnitudeByITK, 2, idx, indexWithHighestGradient); click[0] = indexWithHighestGradient[0]; click[1] = indexWithHighestGradient[1]; click[2] = indexWithHighestGradient[2]; - m_WorkingSlice->GetGeometry()->IndexToWorld(click, click); + m_ReferenceDataSlice->GetGeometry()->IndexToWorld(click, click); // Set initial start point - m_Contour->AddVertex(click, true, t); + m_Contour->AddVertex(click, true); m_LiveWireFilter->SetStartPoint(click); // Remember PlaneGeometry to determine if events were triggered in the same plane m_PlaneGeometry = interactionEvent->GetSender()->GetCurrentWorldPlaneGeometry(); m_CreateAndUseDynamicCosts = true; mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent) { // Complete LiveWire interaction for the last segment. Add current LiveWire contour to // the finished contour and reset to start a new segment and computation. auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; if (m_PlaneGeometry.IsNotNull()) { // Check if the point is in the correct slice if (m_PlaneGeometry->DistanceFromPlane(positionEvent->GetPositionInWorld()) > mitk::sqrteps) return; } - auto t = static_cast(positionEvent->GetSender()->GetTimeStep()); - // Add repulsive points to avoid getting the same path again std::for_each(m_LiveWireContour->IteratorBegin(), m_LiveWireContour->IteratorEnd(), [this](ContourElement::VertexType *vertex) { ImageLiveWireContourModelFilter::InternalImageType::IndexType idx; - this->m_WorkingSlice->GetGeometry()->WorldToIndex(vertex->Coordinates, idx); + this->m_ReferenceDataSlice->GetGeometry()->WorldToIndex(vertex->Coordinates, idx); this->m_LiveWireFilter->AddRepulsivePoint(idx); }); // Remove duplicate first vertex, it's already contained in m_Contour - m_LiveWireContour->RemoveVertexAt(0, t); + m_LiveWireContour->RemoveVertexAt(0); // Set last point as control point - m_LiveWireContour->SetControlVertexAt(m_LiveWireContour->GetNumberOfVertices(t) - 1, t); + m_LiveWireContour->SetControlVertexAt(m_LiveWireContour->GetNumberOfVertices() - 1); // Merge contours - m_Contour->Concatenate(m_LiveWireContour, t); + m_Contour->Concatenate(m_LiveWireContour); // Clear the LiveWire contour and reset the corresponding DataNode - m_LiveWireContour->Clear(t); + m_LiveWireContour->Clear(); // Set new start point m_LiveWireFilter->SetStartPoint(positionEvent->GetPositionInWorld()); if (m_CreateAndUseDynamicCosts) { // Use dynamic cost map for next update m_LiveWireFilter->CreateDynamicCostMap(m_Contour); m_LiveWireFilter->SetUseDynamicCostMap(true); } mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { // Compute LiveWire segment from last control point to current mouse position auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; - auto t = positionEvent->GetSender()->GetTimeStep(); - m_LiveWireFilter->SetEndPoint(positionEvent->GetPositionInWorld()); - m_LiveWireFilter->SetTimeStep(t); m_LiveWireFilter->Update(); - m_LiveWireContour = this->m_LiveWireFilter->GetOutput(); - m_LiveWireContourNode->SetData(this->m_LiveWireContour); + this->UpdateLiveWireContour(); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::LiveWireTool2D::OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent) { m_LiveWireFilter->SetUseDynamicCostMap(false); this->OnMouseMoved(nullptr, interactionEvent); m_LiveWireFilter->SetUseDynamicCostMap(true); } bool mitk::LiveWireTool2D::OnCheckPoint(const InteractionEvent *interactionEvent) { // Check double click on first control point to finish the LiveWire tool auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return false; - auto t = static_cast(positionEvent->GetSender()->GetTimeStep()); - mitk::Point3D click = positionEvent->GetPositionInWorld(); - mitk::Point3D first = this->m_Contour->GetVertexAt(0, t)->Coordinates; + mitk::Point3D first = this->m_Contour->GetVertexAt(0)->Coordinates; return first.EuclideanDistanceTo(click) < 4.5; } void mitk::LiveWireTool2D::OnFinish(StateMachineAction *, InteractionEvent *interactionEvent) { // Finish LiveWire tool interaction auto positionEvent = dynamic_cast(interactionEvent); if (nullptr == positionEvent) return; - // Have to do that here so that the m_LastEventSender is set correctly - mitk::SegTool2D::AddContourmarker(); - - auto t = static_cast(positionEvent->GetSender()->GetTimeStep()); - // Remove last control point added by double click - m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices(t) - 1, t); + m_Contour->RemoveVertexAt(m_Contour->GetNumberOfVertices() - 1); // Save contour and corresponding plane geometry to list this->m_WorkingContours.emplace_back(std::make_pair(m_ContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); this->m_EditingContours.emplace_back(std::make_pair(m_EditingContourNode, positionEvent->GetSender()->GetCurrentWorldPlaneGeometry()->Clone())); m_LiveWireFilter->SetUseDynamicCostMap(false); this->FinishTool(); } void mitk::LiveWireTool2D::FinishTool() { - auto numberOfTimesteps = static_cast(m_Contour->GetTimeGeometry()->CountTimeSteps()); + auto numberOfTimesteps = static_cast(m_Contour->GetTimeSteps()); for (int i = 0; i <= numberOfTimesteps; ++i) m_Contour->Close(i); m_ToolManager->GetDataStorage()->Remove(m_LiveWireContourNode); m_LiveWireContourNode = nullptr; m_LiveWireContour = nullptr; m_ContourInteractor = mitk::ContourModelLiveWireInteractor::New(); m_ContourInteractor->SetDataNode(m_ContourNode); m_ContourInteractor->LoadStateMachine("ContourModelModificationInteractor.xml", us::GetModuleContext()->GetModule()); m_ContourInteractor->SetEventConfig("ContourModelModificationConfig.xml", us::GetModuleContext()->GetModule()); - m_ContourInteractor->SetWorkingImage(this->m_WorkingSlice); + m_ContourInteractor->SetWorkingImage(this->m_ReferenceDataSlice); m_ContourInteractor->SetEditingContourModelNode(this->m_EditingContourNode); m_ContourNode->SetDataInteractor(m_ContourInteractor.GetPointer()); this->m_LiveWireInteractors.push_back(m_ContourInteractor); } void mitk::LiveWireTool2D::OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent) { - int t = static_cast(interactionEvent->GetSender()->GetTimeStep()); - // If last point of current contour will be removed go to start state and remove nodes - if (m_Contour->GetNumberOfVertices(t) <= 1) + if (m_Contour->GetNumberOfVertices() <= 1) { auto dataStorage = m_ToolManager->GetDataStorage(); dataStorage->Remove(m_LiveWireContourNode); dataStorage->Remove(m_ContourNode); dataStorage->Remove(m_EditingContourNode); - m_LiveWireContour = mitk::ContourModel::New(); + m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); - m_Contour = mitk::ContourModel::New(); + m_Contour = this->CreateNewContour(); m_ContourNode->SetData(m_Contour); this->ResetToStartState(); } else // Remove last segment from contour and reset LiveWire contour { - m_LiveWireContour = mitk::ContourModel::New(); + m_LiveWireContour = this->CreateNewContour(); m_LiveWireContourNode->SetData(m_LiveWireContour); - auto newContour = mitk::ContourModel::New(); - newContour->Expand(m_Contour->GetTimeSteps()); + auto newContour = this->CreateNewContour(); auto begin = m_Contour->IteratorBegin(); // Iterate from last point to next active point auto newLast = m_Contour->IteratorBegin() + (m_Contour->GetNumberOfVertices() - 1); // Go at least one down if (newLast != begin) --newLast; // Search next active control point while (newLast != begin && !((*newLast)->IsControlPoint)) --newLast; // Set position of start point for LiveWire filter to coordinates of the new last point m_LiveWireFilter->SetStartPoint((*newLast)->Coordinates); auto it = m_Contour->IteratorBegin(); // Fll new Contour while (it <= newLast) { - newContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint, t); + newContour->AddVertex((*it)->Coordinates, (*it)->IsControlPoint); ++it; } newContour->SetClosed(m_Contour->IsClosed()); m_ContourNode->SetData(newContour); m_Contour = newContour; mitk::RenderingManager::GetInstance()->RequestUpdate(interactionEvent->GetSender()->GetRenderWindow()); } } template void mitk::LiveWireTool2D::FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex) { typedef itk::Image InputImageType; typedef typename InputImageType::IndexType IndexType; const auto MAX_X = inputImage->GetLargestPossibleRegion().GetSize()[0]; const auto MAX_Y = inputImage->GetLargestPossibleRegion().GetSize()[1]; returnIndex[0] = index[0]; returnIndex[1] = index[1]; returnIndex[2] = 0.0; double gradientMagnitude = 0.0; double maxGradientMagnitude = 0.0; // The size and thus the region of 7x7 is only used to calculate the gradient magnitude in that region, // not for searching the maximum value. // Maximum value in each direction for size typename InputImageType::SizeType size; size[0] = 7; size[1] = 7; // Minimum value in each direction for startRegion IndexType startRegion; startRegion[0] = index[0] - 3; startRegion[1] = index[1] - 3; if (startRegion[0] < 0) startRegion[0] = 0; if (startRegion[1] < 0) startRegion[1] = 0; if (MAX_X - index[0] < 7) startRegion[0] = MAX_X - 7; if (MAX_Y - index[1] < 7) startRegion[1] = MAX_Y - 7; index[0] = startRegion[0] + 3; index[1] = startRegion[1] + 3; typename InputImageType::RegionType region; region.SetSize(size); region.SetIndex(startRegion); typedef typename itk::GradientMagnitudeImageFilter GradientMagnitudeFilterType; typename GradientMagnitudeFilterType::Pointer gradientFilter = GradientMagnitudeFilterType::New(); gradientFilter->SetInput(inputImage); gradientFilter->GetOutput()->SetRequestedRegion(region); gradientFilter->Update(); typename InputImageType::Pointer gradientMagnitudeImage; gradientMagnitudeImage = gradientFilter->GetOutput(); IndexType currentIndex; currentIndex[0] = 0; currentIndex[1] = 0; // Search max (approximate) gradient magnitude for (int x = -1; x <= 1; ++x) { currentIndex[0] = index[0] + x; for (int y = -1; y <= 1; ++y) { currentIndex[1] = index[1] + y; gradientMagnitude = gradientMagnitudeImage->GetPixel(currentIndex); // Check for new max if (maxGradientMagnitude < gradientMagnitude) { maxGradientMagnitude = gradientMagnitude; returnIndex[0] = currentIndex[0]; returnIndex[1] = currentIndex[1]; returnIndex[2] = 0.0; } } currentIndex[1] = index[1]; } } diff --git a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h index 45ebf5f865..dff07f7a0b 100644 --- a/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h +++ b/Modules/Segmentation/Interactions/mitkLiveWireTool2D.h @@ -1,125 +1,139 @@ /*============================================================================ 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 mitkLiveWireTool2D_h #define mitkLiveWireTool2D_h #include #include namespace mitk { /** \brief A 2D segmentation tool based on a LiveWire approach. The contour between the last point and the current mouse position is computed by searching the shortest path according to specific features of the image. The contour thus tends to snap to the boundary of objects. + The tool always assumes that unconfirmed contours are always defined for the + current time point. So the time step in which the contours will be stored as + segmentations will be determined when the contours got confirmed. Then they + will be transfered to the slices of the currently selected time step. + Changing the time point/time step while tool is active will updated the working + slice the live wire filter. So the behavior of the active live wire contour is + always WYSIWYG (What you see is what you get). + \sa SegTool2D \sa ImageLiveWireContourModelFilter \ingroup Interaction \ingroup ToolManagerEtAl \warning Only to be instantiated by mitk::ToolManager. */ class MITKSEGMENTATION_EXPORT LiveWireTool2D : public SegTool2D { public: mitkClassMacro(LiveWireTool2D, SegTool2D); itkFactorylessNewMacro(Self); us::ModuleResource GetCursorIconResource() const override; us::ModuleResource GetIconResource() const override; const char *GetName() const override; const char **GetXPM() const override; /// \brief Convert all current contours to binary segmentations. void ConfirmSegmentation(); /// \brief Delete all current contours. void ClearSegmentation(); protected: LiveWireTool2D(); ~LiveWireTool2D() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; + void UpdateLiveWireContour(); + void OnTimePointChanged() override; private: /// \brief Initialize tool. void OnInitLiveWire(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Add a control point and finish current segment. void OnAddPoint(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Actual LiveWire computation. void OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Check double click on first control point to finish the LiveWire tool. bool OnCheckPoint(const InteractionEvent *interactionEvent); /// \brief Finish LiveWire tool. void OnFinish(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Close the contour. void OnLastSegmentDelete(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Don't use dynamic cost map for LiveWire calculation. void OnMouseMoveNoDynamicCosts(StateMachineAction *, InteractionEvent *interactionEvent); /// \brief Finish contour interaction. void FinishTool(); void EnableContourLiveWireInteraction(bool on); bool IsPositionEventInsideImageRegion(InteractionPositionEvent *positionEvent, BaseData *data); void ReleaseInteractors(); void ReleaseHelperObjects(); void RemoveHelperObjects(); template void FindHighestGradientMagnitudeByITK(itk::Image *inputImage, itk::Index<3> &index, itk::Index<3> &returnIndex); + ContourModel::Pointer CreateNewContour() const; + mitk::ContourModel::Pointer m_Contour; mitk::DataNode::Pointer m_ContourNode; mitk::ContourModel::Pointer m_LiveWireContour; mitk::DataNode::Pointer m_LiveWireContourNode; mitk::ContourModel::Pointer m_EditingContour; mitk::DataNode::Pointer m_EditingContourNode; mitk::ContourModelLiveWireInteractor::Pointer m_ContourInteractor; - mitk::Image::Pointer m_WorkingSlice; + /** Slice of the reference data the tool is currently actively working on to + define contours.*/ + mitk::Image::Pointer m_ReferenceDataSlice; mitk::ImageLiveWireContourModelFilter::Pointer m_LiveWireFilter; bool m_CreateAndUseDynamicCosts; std::vector> m_WorkingContours; std::vector> m_EditingContours; std::vector m_LiveWireInteractors; PlaneGeometry::ConstPointer m_PlaneGeometry; }; } #endif diff --git a/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp b/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp index de9837ba8d..54231db23f 100644 --- a/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp +++ b/Modules/Segmentation/Interactions/mitkPaintbrushTool.cpp @@ -1,587 +1,566 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkPaintbrushTool.h" -#include "ipSegmentation.h" #include "mitkAbstractTransformGeometry.h" #include "mitkBaseRenderer.h" -#include "mitkImageDataItem.h" -#include "mitkOverwriteSliceImageFilter.h" #include "mitkToolManager.h" #include "mitkContourModelUtils.h" #include "mitkLevelWindowProperty.h" int mitk::PaintbrushTool::m_Size = 1; mitk::PaintbrushTool::PaintbrushTool(int paintingPixelValue) : FeedbackContourTool("PressMoveReleaseWithCTRLInversionAllMouseMoves"), m_PaintingPixelValue(paintingPixelValue), m_LastContourSize(0) // other than initial mitk::PaintbrushTool::m_Size (around l. 28) { m_MasterContour = ContourModel::New(); m_MasterContour->Initialize(); m_CurrentPlane = nullptr; m_WorkingNode = DataNode::New(); m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true)); } mitk::PaintbrushTool::~PaintbrushTool() { } void mitk::PaintbrushTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnPrimaryButtonPressedMoved); CONNECT_FUNCTION("MouseMove", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); CONNECT_FUNCTION("InvertLogic", OnInvertLogic); } void mitk::PaintbrushTool::Activated() { Superclass::Activated(); FeedbackContourTool::SetFeedbackContourVisible(true); SizeChanged.Send(m_Size); m_ToolManager->WorkingDataChanged += mitk::MessageDelegate(this, &mitk::PaintbrushTool::OnToolManagerWorkingDataModified); } void mitk::PaintbrushTool::Deactivated() { FeedbackContourTool::SetFeedbackContourVisible(false); if (m_ToolManager->GetDataStorage()->Exists(m_WorkingNode)) m_ToolManager->GetDataStorage()->Remove(m_WorkingNode); m_WorkingSlice = nullptr; m_CurrentPlane = nullptr; m_ToolManager->WorkingDataChanged -= mitk::MessageDelegate(this, &mitk::PaintbrushTool::OnToolManagerWorkingDataModified); Superclass::Deactivated(); } void mitk::PaintbrushTool::SetSize(int value) { m_Size = value; } mitk::Point2D mitk::PaintbrushTool::upperLeft(mitk::Point2D p) { p[0] -= 0.5; p[1] += 0.5; return p; } void mitk::PaintbrushTool::UpdateContour(const InteractionPositionEvent *positionEvent) { // MITK_INFO<<"Update..."; // examine stateEvent and create a contour that matches the pixel mask that we are going to draw // mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent ); // const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); if (!positionEvent) return; // Get Spacing of current Slice // mitk::Vector3D vSpacing = m_WorkingSlice->GetSlicedGeometry()->GetPlaneGeometry(0)->GetSpacing(); // // Draw a contour in Square according to selected brush size // int radius = (m_Size) / 2; float fradius = static_cast(m_Size) / 2.0f; ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New(); // estimate center point of the brush ( relative to the pixel the mouse points on ) // -- left upper corner for even sizes, // -- midpoint for uneven sizes mitk::Point2D centerCorrection; centerCorrection.Fill(0); // even --> correction of [+0.5, +0.5] bool evenSize = ((m_Size % 2) == 0); if (evenSize) { centerCorrection[0] += 0.5; centerCorrection[1] += 0.5; } // we will compute the control points for the upper left quarter part of a circle contour std::vector quarterCycleUpperRight; std::vector quarterCycleLowerRight; std::vector quarterCycleLowerLeft; std::vector quarterCycleUpperLeft; mitk::Point2D curPoint; bool curPointIsInside = true; curPoint[0] = 0; curPoint[1] = radius; quarterCycleUpperRight.push_back(upperLeft(curPoint)); // to estimate if a pixel is inside the circle, we need to compare against the 'outer radius' // i.e. the distance from the midpoint [0,0] to the border of the pixel [0,radius] // const float outer_radius = static_cast(radius) + 0.5; while (curPoint[1] > 0) { // Move right until pixel is outside circle float curPointX_squared = 0.0f; float curPointY_squared = (curPoint[1] - centerCorrection[1]) * (curPoint[1] - centerCorrection[1]); while (curPointIsInside) { // increment posX and chec curPoint[0]++; curPointX_squared = (curPoint[0] - centerCorrection[0]) * (curPoint[0] - centerCorrection[0]); const float len = sqrt(curPointX_squared + curPointY_squared); if (len > fradius) { // found first Pixel in this horizontal line, that is outside the circle curPointIsInside = false; } } quarterCycleUpperRight.push_back(upperLeft(curPoint)); // Move down until pixel is inside circle while (!curPointIsInside) { // increment posX and chec curPoint[1]--; curPointY_squared = (curPoint[1] - centerCorrection[1]) * (curPoint[1] - centerCorrection[1]); const float len = sqrt(curPointX_squared + curPointY_squared); if (len <= fradius) { // found first Pixel in this horizontal line, that is outside the circle curPointIsInside = true; quarterCycleUpperRight.push_back(upperLeft(curPoint)); } // Quarter cycle is full, when curPoint y position is 0 if (curPoint[1] <= 0) break; } } // QuarterCycle is full! Now copy quarter cycle to other quarters. if (!evenSize) { std::vector::const_iterator it = quarterCycleUpperRight.begin(); while (it != quarterCycleUpperRight.end()) { mitk::Point2D p; p = *it; // the contour points in the lower right corner have same position but with negative y values p[1] *= -1; quarterCycleLowerRight.push_back(p); // the contour points in the lower left corner have same position // but with both x,y negative p[0] *= -1; quarterCycleLowerLeft.push_back(p); // the contour points in the upper left corner have same position // but with x negative p[1] *= -1; quarterCycleUpperLeft.push_back(p); it++; } } else { std::vector::const_iterator it = quarterCycleUpperRight.begin(); while (it != quarterCycleUpperRight.end()) { mitk::Point2D p, q; p = *it; q = p; // the contour points in the lower right corner have same position but with negative y values q[1] *= -1; // correct for moved offset if size even = the midpoint is not the midpoint of the current pixel // but its upper rigt corner q[1] += 1; quarterCycleLowerRight.push_back(q); q = p; // the contour points in the lower left corner have same position // but with both x,y negative q[1] = -1.0f * q[1] + 1; q[0] = -1.0f * q[0] + 1; quarterCycleLowerLeft.push_back(q); // the contour points in the upper left corner have same position // but with x negative q = p; q[0] *= -1; q[0] += 1; quarterCycleUpperLeft.push_back(q); it++; } } // fill contour with poins in right ordering, starting with the upperRight block mitk::Point3D tempPoint; for (unsigned int i = 0; i < quarterCycleUpperRight.size(); i++) { tempPoint[0] = quarterCycleUpperRight[i][0]; tempPoint[1] = quarterCycleUpperRight[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } // the lower right has to be parsed in reverse order for (int i = quarterCycleLowerRight.size() - 1; i >= 0; i--) { tempPoint[0] = quarterCycleLowerRight[i][0]; tempPoint[1] = quarterCycleLowerRight[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } for (unsigned int i = 0; i < quarterCycleLowerLeft.size(); i++) { tempPoint[0] = quarterCycleLowerLeft[i][0]; tempPoint[1] = quarterCycleLowerLeft[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } // the upper left also has to be parsed in reverse order for (int i = quarterCycleUpperLeft.size() - 1; i >= 0; i--) { tempPoint[0] = quarterCycleUpperLeft[i][0]; tempPoint[1] = quarterCycleUpperLeft[i][1]; tempPoint[2] = 0; contourInImageIndexCoordinates->AddVertex(tempPoint); } m_MasterContour = contourInImageIndexCoordinates; } /** Just show the contour, get one point as the central point and add surrounding points to the contour. */ void mitk::PaintbrushTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { if (m_WorkingSlice.IsNull()) return; auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_WorkingSlice->GetGeometry()->WorldToIndex(positionEvent->GetPositionInWorld(), m_LastPosition); // create new working node // a fresh node is needed to only display the actual drawing process for // the undo function if (m_ToolManager->GetDataStorage()->Exists(m_WorkingNode)) m_ToolManager->GetDataStorage()->Remove(m_WorkingNode); m_WorkingSlice = nullptr; m_CurrentPlane = nullptr; m_WorkingNode = DataNode::New(); m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true)); this->m_WorkingNode->SetVisibility(true); m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); m_MasterContour->SetClosed(true); this->MouseMoved(interactionEvent, true); } void mitk::PaintbrushTool::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { MouseMoved(interactionEvent, false); } void mitk::PaintbrushTool::OnPrimaryButtonPressedMoved(StateMachineAction *, InteractionEvent *interactionEvent) { MouseMoved(interactionEvent, true); } /** Insert the point to the feedback contour,finish to build the contour and at the same time the painting function */ void mitk::PaintbrushTool::MouseMoved(mitk::InteractionEvent *interactionEvent, bool leftMouseButtonPressed) { auto *positionEvent = dynamic_cast(interactionEvent); CheckIfCurrentSliceHasChanged(positionEvent); if (m_LastContourSize != m_Size) { UpdateContour(positionEvent); m_LastContourSize = m_Size; } Point3D worldCoordinates = positionEvent->GetPositionInWorld(); Point3D indexCoordinates; m_WorkingSlice->GetGeometry()->WorldToIndex(worldCoordinates, indexCoordinates); // round to nearest voxel center (abort if this hasn't changed) if (m_Size % 2 == 0) // even { indexCoordinates[0] = std::round(indexCoordinates[0]); indexCoordinates[1] = std::round(indexCoordinates[1]); } else // odd { indexCoordinates[0] = std::round(indexCoordinates[0]); indexCoordinates[1] = std::round(indexCoordinates[1]); } static Point3D lastPos; // uninitialized: if somebody finds out how this can be initialized in a one-liner, tell me if (fabs(indexCoordinates[0] - lastPos[0]) > mitk::eps || fabs(indexCoordinates[1] - lastPos[1]) > mitk::eps || fabs(indexCoordinates[2] - lastPos[2]) > mitk::eps || leftMouseButtonPressed) { lastPos = indexCoordinates; } else { return; } - int t = positionEvent->GetSender()->GetTimeStep(); - auto contour = ContourModel::New(); contour->SetClosed(true); auto it = m_MasterContour->Begin(); auto end = m_MasterContour->End(); while (it != end) { auto point = (*it)->Coordinates; point[0] += indexCoordinates[0]; point[1] += indexCoordinates[1]; contour->AddVertex(point); ++it; } if (leftMouseButtonPressed) { const double dist = indexCoordinates.EuclideanDistanceTo(m_LastPosition); const double radius = static_cast(m_Size) / 2.0; DataNode *workingNode(m_ToolManager->GetWorkingData(0)); auto workingImage = dynamic_cast(workingNode->GetData()); int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); // m_PaintingPixelValue only decides whether to paint or erase mitk::ContourModelUtils::FillContourInSlice( contour, m_WorkingSlice, workingImage, m_PaintingPixelValue * activePixelValue); m_WorkingNode->SetData(m_WorkingSlice); m_WorkingNode->Modified(); // if points are >= radius away draw rectangle to fill empty holes // in between the 2 points if (dist > radius) { const mitk::Point3D ¤tPos = indexCoordinates; mitk::Point3D direction; mitk::Point3D vertex; mitk::Point3D normal; direction[0] = indexCoordinates[0] - m_LastPosition[0]; direction[1] = indexCoordinates[1] - m_LastPosition[1]; direction[2] = indexCoordinates[2] - m_LastPosition[2]; direction[0] = direction.GetVnlVector().normalize()[0]; direction[1] = direction.GetVnlVector().normalize()[1]; direction[2] = direction.GetVnlVector().normalize()[2]; // 90 degrees rotation of direction normal[0] = -1.0 * direction[1]; normal[1] = direction[0]; contour->Clear(); // upper left corner vertex[0] = m_LastPosition[0] + (normal[0] * radius); vertex[1] = m_LastPosition[1] + (normal[1] * radius); contour->AddVertex(vertex); // upper right corner vertex[0] = currentPos[0] + (normal[0] * radius); vertex[1] = currentPos[1] + (normal[1] * radius); contour->AddVertex(vertex); // lower right corner vertex[0] = currentPos[0] - (normal[0] * radius); vertex[1] = currentPos[1] - (normal[1] * radius); contour->AddVertex(vertex); // lower left corner vertex[0] = m_LastPosition[0] - (normal[0] * radius); vertex[1] = m_LastPosition[1] - (normal[1] * radius); contour->AddVertex(vertex); mitk::ContourModelUtils::FillContourInSlice(contour, m_WorkingSlice, workingImage, m_PaintingPixelValue * activePixelValue); m_WorkingNode->SetData(m_WorkingSlice); m_WorkingNode->Modified(); } } else { // switched from different renderwindow // no activate hover highlighting. Otherwise undo / redo wont work this->m_WorkingNode->SetVisibility(false); } m_LastPosition = indexCoordinates; // visualize contour - ContourModel::Pointer displayContour = FeedbackContourTool::GetFeedbackContour(); - displayContour->Clear(); - ContourModel::Pointer tmp = FeedbackContourTool::BackProjectContourFrom2DSlice(m_WorkingSlice->GetGeometry(), contour); - // copy transformed contour into display contour - it = tmp->Begin(); - end = tmp->End(); - - while (it != end) - { - Point3D point = (*it)->Coordinates; - - displayContour->AddVertex(point, t); - it++; - } - - m_FeedbackContourNode->GetData()->Modified(); + this->UpdateCurrentFeedbackContour(tmp); assert(positionEvent->GetSender()->GetRenderWindow()); - RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::PaintbrushTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { // When mouse is released write segmentationresult back into image auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice->Clone()); // deactivate visibility of helper node m_WorkingNode->SetVisibility(false); RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } /** Called when the CTRL key is pressed. Will change the painting pixel value from 0 to 1 or from 1 to 0. */ void mitk::PaintbrushTool::OnInvertLogic(StateMachineAction *, InteractionEvent *) { // Inversion only for 0 and 1 as painting values if (m_PaintingPixelValue == 1) { m_PaintingPixelValue = 0; FeedbackContourTool::SetFeedbackContourColor(1.0, 0.0, 0.0); } else if (m_PaintingPixelValue == 0) { m_PaintingPixelValue = 1; FeedbackContourTool::SetFeedbackContourColorDefault(); } mitk::RenderingManager::GetInstance()->RequestUpdateAll(); } void mitk::PaintbrushTool::CheckIfCurrentSliceHasChanged(const InteractionPositionEvent *event) { const PlaneGeometry *planeGeometry((event->GetSender()->GetCurrentWorldPlaneGeometry())); const auto *abstractTransformGeometry( dynamic_cast(event->GetSender()->GetCurrentWorldPlaneGeometry())); DataNode *workingNode(m_ToolManager->GetWorkingData(0)); if (!workingNode) return; Image::Pointer image = dynamic_cast(workingNode->GetData()); if (!image || !planeGeometry || abstractTransformGeometry) return; if (m_CurrentPlane.IsNull() || m_WorkingSlice.IsNull()) { m_CurrentPlane = planeGeometry; m_WorkingSlice = SegTool2D::GetAffectedImageSliceAs2DImage(event, image)->Clone(); m_WorkingNode->ReplaceProperty("color", workingNode->GetProperty("color")); m_WorkingNode->SetData(m_WorkingSlice); } else { bool isSameSlice(false); isSameSlice = mitk::MatrixEqualElementWise(planeGeometry->GetIndexToWorldTransform()->GetMatrix(), m_CurrentPlane->GetIndexToWorldTransform()->GetMatrix()); isSameSlice = mitk::Equal(planeGeometry->GetIndexToWorldTransform()->GetOffset(), m_CurrentPlane->GetIndexToWorldTransform()->GetOffset()); if (!isSameSlice) { m_ToolManager->GetDataStorage()->Remove(m_WorkingNode); m_CurrentPlane = nullptr; m_WorkingSlice = nullptr; m_WorkingNode = nullptr; m_CurrentPlane = planeGeometry; m_WorkingSlice = SegTool2D::GetAffectedImageSliceAs2DImage(event, image)->Clone(); m_WorkingNode = mitk::DataNode::New(); m_WorkingNode->SetProperty("levelwindow", mitk::LevelWindowProperty::New(mitk::LevelWindow(0, 1))); m_WorkingNode->SetProperty("binary", mitk::BoolProperty::New(true)); m_WorkingNode->SetData(m_WorkingSlice); // So that the paintbrush contour vanished in the previous render window RenderingManager::GetInstance()->RequestUpdateAll(); } } if (!m_ToolManager->GetDataStorage()->Exists(m_WorkingNode)) { m_WorkingNode->SetProperty("outline binary", mitk::BoolProperty::New(true)); m_WorkingNode->SetProperty("color", workingNode->GetProperty("color")); m_WorkingNode->SetProperty("name", mitk::StringProperty::New("Paintbrush_Node")); m_WorkingNode->SetProperty("helper object", mitk::BoolProperty::New(true)); m_WorkingNode->SetProperty("opacity", mitk::FloatProperty::New(0.8)); m_WorkingNode->SetProperty("includeInBoundingBox", mitk::BoolProperty::New(false)); m_WorkingNode->SetVisibility( false, mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget3"))); m_ToolManager->GetDataStorage()->Add(m_WorkingNode); } } void mitk::PaintbrushTool::OnToolManagerWorkingDataModified() { // Here we simply set the current working slice to null. The next time the mouse is moved // within a renderwindow a new slice will be extracted from the new working data m_WorkingSlice = nullptr; } diff --git a/Modules/Segmentation/Interactions/mitkPaintbrushTool.h b/Modules/Segmentation/Interactions/mitkPaintbrushTool.h index b63fdfc0a5..b8c4074376 100644 --- a/Modules/Segmentation/Interactions/mitkPaintbrushTool.h +++ b/Modules/Segmentation/Interactions/mitkPaintbrushTool.h @@ -1,103 +1,101 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkPaintbrushTool_h_Included #define mitkPaintbrushTool_h_Included #include "mitkCommon.h" #include "mitkFeedbackContourTool.h" -#include "mitkPointOperation.h" -#include "mitkPointSet.h" #include namespace mitk { class StateMachineAction; class InteractionEvent; class InteractionPositionEvent; /** \brief Paintbrush tool for InteractiveSegmentation \sa FeedbackContourTool \sa ExtractImageFilter \sa OverwriteSliceImageFilter \ingroup Interaction \ingroup ToolManagerEtAl Simple paintbrush drawing tool. Right now there are only circular pens of varying size. \warning Only to be instantiated by mitk::ToolManager. $Author: maleike $ */ class MITKSEGMENTATION_EXPORT PaintbrushTool : public FeedbackContourTool { public: // sent when the pen size is changed or should be updated in a GUI. Message1 SizeChanged; mitkClassMacro(PaintbrushTool, FeedbackContourTool); void SetSize(int value); protected: PaintbrushTool(int paintingPixelValue = 1); // purposely hidden ~PaintbrushTool() override; void ConnectActionsAndFunctions() override; void Activated() override; void Deactivated() override; virtual void OnMousePressed(StateMachineAction *, InteractionEvent *); virtual void OnMouseMoved(StateMachineAction *, InteractionEvent *); virtual void OnPrimaryButtonPressedMoved(StateMachineAction *, InteractionEvent *); virtual void MouseMoved(mitk::InteractionEvent *interactionEvent, bool leftMouseButtonPressed); virtual void OnMouseReleased(StateMachineAction *, InteractionEvent *); virtual void OnInvertLogic(StateMachineAction *, InteractionEvent *); /** * \todo This is a possible place where to introduce * different types of pens */ void UpdateContour(const InteractionPositionEvent *); /** * Little helper function. Returns the upper left corner of the given pixel. */ mitk::Point2D upperLeft(mitk::Point2D p); /** * Checks if the current slice has changed */ void CheckIfCurrentSliceHasChanged(const InteractionPositionEvent *event); void OnToolManagerWorkingDataModified(); int m_PaintingPixelValue; static int m_Size; ContourModel::Pointer m_MasterContour; int m_LastContourSize; Image::Pointer m_WorkingSlice; PlaneGeometry::ConstPointer m_CurrentPlane; DataNode::Pointer m_WorkingNode; mitk::Point3D m_LastPosition; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp index 7a46716569..3e2617f963 100644 --- a/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp +++ b/Modules/Segmentation/Interactions/mitkRegionGrowingTool.cpp @@ -1,650 +1,573 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkRegionGrowingTool.h" -#include "mitkApplicationCursor.h" #include "mitkBaseRenderer.h" -#include "mitkImageDataItem.h" #include "mitkImageToContourModelFilter.h" -#include "mitkOverwriteSliceImageFilter.h" #include "mitkRegionGrowingTool.xpm" #include "mitkRenderingManager.h" #include "mitkToolManager.h" -#include "mitkExtractDirectedPlaneImageFilterNew.h" -#include "mitkLabelSetImage.h" -#include "mitkOverwriteDirectedPlaneImageFilter.h" - // us #include #include #include #include // ITK #include "mitkITKImageImport.h" #include "mitkImageAccessByItk.h" #include #include -#include #include #include #include namespace mitk { MITK_TOOL_MACRO(MITKSEGMENTATION_EXPORT, RegionGrowingTool, "Region growing tool"); } #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) mitk::RegionGrowingTool::RegionGrowingTool() : FeedbackContourTool("PressMoveRelease"), m_SeedValue(0), m_ScreenYDifference(0), m_ScreenXDifference(0), m_MouseDistanceScaleFactor(0.5), m_PaintingPixelValue(0), m_FillFeedbackContour(true), m_ConnectedComponentValue(1) { } mitk::RegionGrowingTool::~RegionGrowingTool() { } void mitk::RegionGrowingTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Move", OnMouseMoved); CONNECT_FUNCTION("Release", OnMouseReleased); } const char **mitk::RegionGrowingTool::GetXPM() const { return mitkRegionGrowingTool_xpm; } us::ModuleResource mitk::RegionGrowingTool::GetIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_48x48.png"); return resource; } us::ModuleResource mitk::RegionGrowingTool::GetCursorIconResource() const { us::Module *module = us::GetModuleContext()->GetModule(); us::ModuleResource resource = module->GetResource("RegionGrowing_Cursor_32x32.png"); return resource; } const char *mitk::RegionGrowingTool::GetName() const { return "Region Growing"; } void mitk::RegionGrowingTool::Activated() { Superclass::Activated(); } void mitk::RegionGrowingTool::Deactivated() { Superclass::Deactivated(); } // Get the average pixel value of square/cube with radius=neighborhood around index template void mitk::RegionGrowingTool::GetNeighborhoodAverage(const itk::Image *itkImage, const itk::Index& index, ScalarType *result, unsigned int neighborhood) { // maybe assert that image dimension is only 2 or 3? auto neighborhoodInt = (int)neighborhood; TPixel averageValue(0); unsigned int numberOfPixels = (2 * neighborhood + 1) * (2 * neighborhood + 1); if (imageDimension == 3) { numberOfPixels *= (2 * neighborhood + 1); } MITK_DEBUG << "Getting neighborhood of " << numberOfPixels << " pixels around " << index; itk::Index currentIndex; for (int i = (0 - neighborhoodInt); i <= neighborhoodInt; ++i) { currentIndex[0] = index[0] + i; for (int j = (0 - neighborhoodInt); j <= neighborhoodInt; ++j) { currentIndex[1] = index[1] + j; if (imageDimension == 3) { for (int k = (0 - neighborhoodInt); k <= neighborhoodInt; ++k) { currentIndex[2] = index[2] + k; if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } else { if (itkImage->GetLargestPossibleRegion().IsInside(currentIndex)) { averageValue += itkImage->GetPixel(currentIndex); } else { numberOfPixels -= 1; } } } } *result = (ScalarType)averageValue; *result /= numberOfPixels; } // Check whether index lies inside a segmentation template void mitk::RegionGrowingTool::IsInsideSegmentation(const itk::Image *itkImage, const itk::Index& index, bool *result) { if (itkImage->GetPixel(index) > 0) { *result = true; } else { *result = false; } } // Do the region growing (i.e. call an ITK filter that does it) template void mitk::RegionGrowingTool::StartRegionGrowing(const itk::Image *inputImage, const itk::Index& seedIndex, const std::array& thresholds, mitk::Image::Pointer &outputImage) { MITK_DEBUG << "Starting region growing at index " << seedIndex << " with lower threshold " << thresholds[0] << " and upper threshold " << thresholds[1]; typedef itk::Image InputImageType; typedef itk::Image OutputImageType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; typename RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); // perform region growing in desired segmented region regionGrower->SetInput(inputImage); regionGrower->SetSeed(seedIndex); regionGrower->SetLower(thresholds[0]); regionGrower->SetUpper(thresholds[1]); try { regionGrower->Update(); } catch (...) { return; // Should we do something? } typename OutputImageType::Pointer resultImage = regionGrower->GetOutput(); // Smooth result: Every pixel is replaced by the majority of the neighborhood typedef itk::NeighborhoodIterator NeighborhoodIteratorType; typedef itk::ImageRegionIterator ImageIteratorType; typename NeighborhoodIteratorType::RadiusType radius; radius.Fill(2); // for now, maybe make this something the user can adjust in the preferences? typedef itk::ImageDuplicator< OutputImageType > DuplicatorType; typename DuplicatorType::Pointer duplicator = DuplicatorType::New(); duplicator->SetInputImage(resultImage); duplicator->Update(); typename OutputImageType::Pointer resultDup = duplicator->GetOutput(); NeighborhoodIteratorType neighborhoodIterator(radius, resultDup, resultDup->GetRequestedRegion()); ImageIteratorType imageIterator(resultImage, resultImage->GetRequestedRegion()); for (neighborhoodIterator.GoToBegin(), imageIterator.GoToBegin(); !neighborhoodIterator.IsAtEnd(); ++neighborhoodIterator, ++imageIterator) { DefaultSegmentationDataType voteYes(0); DefaultSegmentationDataType voteNo(0); for (unsigned int i = 0; i < neighborhoodIterator.Size(); ++i) { if (neighborhoodIterator.GetPixel(i) > 0) { voteYes += 1; } else { voteNo += 1; } } if (voteYes > voteNo) { imageIterator.Set(1); } else { imageIterator.Set(0); } } if (resultImage.IsNull()) { MITK_DEBUG << "Region growing result is empty."; } // Can potentially have multiple regions, use connected component image filter to label disjunct regions typedef itk::ConnectedComponentImageFilter ConnectedComponentImageFilterType; typename ConnectedComponentImageFilterType::Pointer connectedComponentFilter = ConnectedComponentImageFilterType::New(); connectedComponentFilter->SetInput(resultImage); connectedComponentFilter->Update(); typename OutputImageType::Pointer resultImageCC = connectedComponentFilter->GetOutput(); m_ConnectedComponentValue = resultImageCC->GetPixel(seedIndex); outputImage = mitk::GrabItkImageMemory(resultImageCC); } template void mitk::RegionGrowingTool::CalculateInitialThresholds(const itk::Image*) { LevelWindow levelWindow; m_ToolManager->GetReferenceData(0)->GetLevelWindow(levelWindow); m_ThresholdExtrema[0] = static_cast(std::numeric_limits::lowest()); m_ThresholdExtrema[1] = static_cast(std::numeric_limits::max()); const ScalarType lowerWindowBound = std::max(m_ThresholdExtrema[0], levelWindow.GetLowerWindowBound()); const ScalarType upperWindowBound = std::min(m_ThresholdExtrema[1], levelWindow.GetUpperWindowBound()); if (m_SeedValue < lowerWindowBound) { m_InitialThresholds = { m_ThresholdExtrema[0], lowerWindowBound }; } else if (m_SeedValue > upperWindowBound) { m_InitialThresholds = { upperWindowBound, m_ThresholdExtrema[1] }; } else { const ScalarType range = 0.1 * (upperWindowBound - lowerWindowBound); // 10% of the visible window m_InitialThresholds[0] = std::min(std::max(lowerWindowBound, m_SeedValue - 0.5 * range), upperWindowBound - range); m_InitialThresholds[1] = m_InitialThresholds[0] + range; } } void mitk::RegionGrowingTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); - m_LastScreenPosition = positionEvent->GetPointerPositionOnScreen(); + m_LastScreenPosition = Point2I(positionEvent->GetPointerPositionOnScreen()); // ReferenceSlice is from the underlying image, WorkingSlice from the active segmentation (can be empty) m_ReferenceSlice = FeedbackContourTool::GetAffectedReferenceSlice(positionEvent); m_WorkingSlice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); if (m_WorkingSlice.IsNotNull()) // can't do anything without a working slice (i.e. a possibly empty segmentation) { // 2. Determine if the user clicked inside or outside of the segmentation/working slice (i.e. the whole volume) mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); workingSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), m_SeedPoint); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; if (workingSliceGeometry->IsIndexInside(m_SeedPoint)) { MITK_DEBUG << "OnMousePressed: point " << positionEvent->GetPositionInWorld() << " (index coordinates " << m_SeedPoint << ") is inside working slice"; // 3. determine the pixel value under the last click to determine what to do bool inside(true); AccessFixedDimensionByItk_2(m_WorkingSlice, IsInsideSegmentation, 2, indexInWorkingSlice2D, &inside); m_PaintingPixelValue = inside ? 0 : 1; if (inside) { MITK_DEBUG << "Clicked inside segmentation"; // For now, we're doing nothing when the user clicks inside the segmentation. Behaviour can be implemented via // OnMousePressedInside() // When you do, be sure to remove the m_PaintingPixelValue check in OnMouseMoved() and OnMouseReleased() return; } else { MITK_DEBUG << "Clicked outside of segmentation"; OnMousePressedOutside(nullptr, interactionEvent); } } } } // Use this to implement a behaviour for when the user clicks inside a segmentation (for example remove something) // Old IpPic code is kept as comment for reference void mitk::RegionGrowingTool::OnMousePressedInside() { // mitk::InteractionPositionEvent* positionEvent = dynamic_cast( interactionEvent // ); // //const PositionEvent* positionEvent = dynamic_cast(stateEvent->GetEvent()); // checked in // OnMousePressed // // 3.1.1. Create a skeletonization of the segmentation and try to find a nice cut // // apply the skeletonization-and-cut algorithm // // generate contour to remove // // set m_ReferenceSlice = nullptr so nothing will happen during mouse move // // remember to fill the contour with 0 in mouserelease // mitkIpPicDescriptor* segmentationHistory = ipMITKSegmentationCreateGrowerHistory( workingPicSlice, // m_LastWorkingSeed, nullptr ); // free again // if (segmentationHistory) // { // tCutResult cutContour = ipMITKSegmentationGetCutPoints( workingPicSlice, segmentationHistory, // initialWorkingOffset ); // tCutResult is a ipSegmentation type // mitkIpPicFree( segmentationHistory ); // if (cutContour.cutIt) // { // int timestep = positionEvent->GetSender()->GetTimeStep(); // // 3.1.2 copy point from float* to mitk::Contour // ContourModel::Pointer contourInImageIndexCoordinates = ContourModel::New(); // contourInImageIndexCoordinates->Expand(timestep + 1); // contourInImageIndexCoordinates->SetClosed(true, timestep); // Point3D newPoint; // for (int index = 0; index < cutContour.deleteSize; ++index) // { // newPoint[0] = cutContour.deleteCurve[ 2 * index + 0 ] - 0.5;//correction is needed because the // output of the algorithm is center based // newPoint[1] = cutContour.deleteCurve[ 2 * index + 1 ] - 0.5;//and we want our contour displayed // corner based. // newPoint[2] = 0.0; // contourInImageIndexCoordinates->AddVertex( newPoint, timestep ); // } // free(cutContour.traceline); // free(cutContour.deleteCurve); // perhaps visualize this for fun? // free(cutContour.onGradient); // ContourModel::Pointer contourInWorldCoordinates = FeedbackContourTool::BackProjectContourFrom2DSlice( // m_WorkingSlice->GetGeometry(), contourInImageIndexCoordinates, true ); // true: sub 0.5 for // ipSegmentation correction // FeedbackContourTool::SetFeedbackContour( contourInWorldCoordinates ); // FeedbackContourTool::SetFeedbackContourVisible(true); // mitk::RenderingManager::GetInstance()->RequestUpdate( positionEvent->GetSender()->GetRenderWindow() ); // m_FillFeedbackContour = true; // } // else // { // m_FillFeedbackContour = false; // } // } // else // { // m_FillFeedbackContour = false; // } // m_ReferenceSlice = nullptr; // return true; } void mitk::RegionGrowingTool::OnMousePressedOutside(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (positionEvent) { // Get geometry and indices mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; mitk::BaseGeometry::Pointer referenceSliceGeometry; referenceSliceGeometry = m_ReferenceSlice->GetGeometry(); itk::Index<3> indexInReferenceSlice; itk::Index<2> indexInReferenceSlice2D; referenceSliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), indexInReferenceSlice); indexInReferenceSlice2D[0] = indexInReferenceSlice[0]; indexInReferenceSlice2D[1] = indexInReferenceSlice[1]; // Get seed neighborhood ScalarType averageValue(0); AccessFixedDimensionByItk_3(m_ReferenceSlice, GetNeighborhoodAverage, 2, indexInReferenceSlice2D, &averageValue, 1); m_SeedValue = averageValue; MITK_DEBUG << "Seed value is " << m_SeedValue; // Calculate initial thresholds AccessFixedDimensionByItk(m_ReferenceSlice, CalculateInitialThresholds, 2); m_Thresholds[0] = m_InitialThresholds[0]; m_Thresholds[1] = m_InitialThresholds[1]; // Perform region growing mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Extract contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); - // this is not a beautiful solution, just one that works, check T22412 for details - auto t = positionEvent->GetSender()->GetTimeStep(); - - FeedbackContourTool::SetFeedbackContour(0 != t - ? ContourModelUtils::MoveZerothContourTimeStep(resultContourWorld, t) - : resultContourWorld); + FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(m_LastEventSender->GetRenderWindow()); } } } } void mitk::RegionGrowingTool::OnMouseMoved(StateMachineAction *, InteractionEvent *interactionEvent) { // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, // i.e. when the user clicked inside the segmentation if (m_PaintingPixelValue == 0) { return; } auto *positionEvent = dynamic_cast(interactionEvent); if (m_ReferenceSlice.IsNotNull() && positionEvent) { // Get geometry and indices mitk::BaseGeometry::Pointer workingSliceGeometry; workingSliceGeometry = m_WorkingSlice->GetGeometry(); itk::Index<2> indexInWorkingSlice2D; indexInWorkingSlice2D[0] = m_SeedPoint[0]; indexInWorkingSlice2D[1] = m_SeedPoint[1]; m_ScreenYDifference += positionEvent->GetPointerPositionOnScreen()[1] - m_LastScreenPosition[1]; m_ScreenXDifference += positionEvent->GetPointerPositionOnScreen()[0] - m_LastScreenPosition[0]; - m_LastScreenPosition = positionEvent->GetPointerPositionOnScreen(); + m_LastScreenPosition = Point2I(positionEvent->GetPointerPositionOnScreen()); // Moving the mouse up and down adjusts the width of the threshold window, // moving it left and right shifts the threshold window m_Thresholds[0] = std::min(m_SeedValue, m_InitialThresholds[0] - (m_ScreenYDifference - m_ScreenXDifference) * m_MouseDistanceScaleFactor); m_Thresholds[1] = std::max(m_SeedValue, m_InitialThresholds[1] + (m_ScreenYDifference + m_ScreenXDifference) * m_MouseDistanceScaleFactor); // Do not exceed the pixel type extrema of the reference slice, though m_Thresholds[0] = std::max(m_ThresholdExtrema[0], m_Thresholds[0]); m_Thresholds[1] = std::min(m_ThresholdExtrema[1], m_Thresholds[1]); // Perform region growing again and show the result mitk::Image::Pointer resultImage = mitk::Image::New(); AccessFixedDimensionByItk_3( m_ReferenceSlice, StartRegionGrowing, 2, indexInWorkingSlice2D, m_Thresholds, resultImage); resultImage->SetGeometry(workingSliceGeometry); // Update the contour if (resultImage.IsNotNull() && m_ConnectedComponentValue >= 1) { float isoOffset = 0.33; mitk::ImageToContourModelFilter::Pointer contourExtractor = mitk::ImageToContourModelFilter::New(); contourExtractor->SetInput(resultImage); contourExtractor->SetContourValue(m_ConnectedComponentValue - isoOffset); contourExtractor->Update(); ContourModel::Pointer resultContour = ContourModel::New(); resultContour = contourExtractor->GetOutput(); // Show contour if (resultContour.IsNotNull()) { ContourModel::Pointer resultContourWorld = FeedbackContourTool::BackProjectContourFrom2DSlice( workingSliceGeometry, FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, resultContour)); - // this is not a beautiful solution, just one that works, check T22412 for details - int timestep = positionEvent->GetSender()->GetTimeStep(); - if (0 != timestep) - { - int size = resultContourWorld->GetNumberOfVertices(0); - auto resultContourTimeWorld = mitk::ContourModel::New(); - resultContourTimeWorld->Expand(timestep + 1); - for (int loop = 0; loop < size; ++loop) - { - resultContourTimeWorld->AddVertex(resultContourWorld->GetVertexAt(loop, 0), timestep); - } - FeedbackContourTool::SetFeedbackContour(resultContourTimeWorld); - } - else - { - FeedbackContourTool::SetFeedbackContour(resultContourWorld); - } + FeedbackContourTool::UpdateCurrentFeedbackContour(resultContourWorld); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->ForceImmediateUpdate(positionEvent->GetSender()->GetRenderWindow()); } } } } void mitk::RegionGrowingTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { // Until OnMousePressedInside() implements a behaviour, we're just returning here whenever m_PaintingPixelValue is 0, // i.e. when the user clicked inside the segmentation if (m_PaintingPixelValue == 0) { return; } auto *positionEvent = dynamic_cast(interactionEvent); if (m_WorkingSlice.IsNotNull() && m_FillFeedbackContour && positionEvent) { - // Project contour into working slice - ContourModel *feedbackContour(FeedbackContourTool::GetFeedbackContour()); - - ContourModel::Pointer projectedContour; - - // this is not a beautiful solution, just one that works, check T22412 for details - int timestep = positionEvent->GetSender()->GetTimeStep(); - if (0 != timestep) - { - int size = feedbackContour->GetNumberOfVertices(timestep); - auto feedbackContourTime = mitk::ContourModel::New(); - feedbackContourTime->Expand(timestep + 1); - for (int loop = 0; loop < size; ++loop) - { - feedbackContourTime->AddVertex(feedbackContour->GetVertexAt(loop, timestep), 0); - } - - projectedContour = - FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, feedbackContourTime, false, false); - } - else - { - projectedContour = - FeedbackContourTool::ProjectContourTo2DSlice(m_WorkingSlice, feedbackContour, false, false); - } - - // If there is a projected contour, fill it - if (projectedContour.IsNotNull()) - { - mitk::DataNode *workingNode(m_ToolManager->GetWorkingData(0)); - if (nullptr == workingNode) - { - return; - } - - auto workingImage = dynamic_cast(workingNode->GetData()); - if (nullptr == workingImage) - { - return; - } - - // m_PaintingPixelValue only decides whether to paint or erase - int activePixelValue = ContourModelUtils::GetActivePixelValue(workingImage); - mitk::ContourModelUtils::FillContourInSlice( - projectedContour, 0, m_WorkingSlice, workingImage, m_PaintingPixelValue * activePixelValue); - - this->WriteBackSegmentationResult(positionEvent, m_WorkingSlice); - FeedbackContourTool::SetFeedbackContourVisible(false); - } + this->WriteBackFeedbackContourAsSegmentationResult(positionEvent, m_PaintingPixelValue); m_ScreenYDifference = 0; m_ScreenXDifference = 0; } } diff --git a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp index cd83b0f699..1258f041f5 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.cpp +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.cpp @@ -1,629 +1,818 @@ /*============================================================================ 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 "mitkExtractDirectedPlaneImageFilter.h" -#include "mitkExtractImageFilter.h" - // Include of the new ImageExtractor -#include "mitkExtractDirectedPlaneImageFilterNew.h" #include "mitkMorphologicalOperations.h" -#include "mitkOverwriteDirectedPlaneImageFilter.h" -#include "mitkOverwriteSliceImageFilter.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 "mitkImageAccessByItk.h" -#include "mitkImageCast.h" -#include "mitkImageToItk.h" #include "mitkLabelSetImage.h" +#include "mitkContourModelUtils.h" + +#include "itkImageRegionIterator.h" + #define ROUND(a) ((a) > 0 ? (int)((a) + 0.5) : -(int)(0.5 - (a))) bool mitk::SegTool2D::m_SurfaceInterpolationEnabled = true; +mitk::SegTool2D::SliceInformation::SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep) : + slice(aSlice), plane(aPlane), timestep(aTimestep) +{ +} + mitk::SegTool2D::SegTool2D(const char *type, const us::Module *interactorModule) - : Tool(type, interactorModule), m_LastEventSender(nullptr), m_LastEventSlice(0), m_Contourmarkername("Position"), m_ShowMarkerNodes(false) + : 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)}; + Self::UpdateSurfaceInterpolation(slices, workingImage, detectIntersection); +} + +void mitk::SegTool2D::RemoveContourFromInterpolator(const SliceInformation& sliceInfo) +{ + mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; + contourInfo.contourNormal = sliceInfo.plane->GetNormal(); + contourInfo.contourPoint = sliceInfo.plane->GetOrigin(); + mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); +} + +void mitk::SegTool2D::UpdateSurfaceInterpolation(const std::vector& sliceInfos, + const Image* workingImage, + bool detectIntersection) { if (!m_SurfaceInterpolationEnabled) return; + //Remark: the ImageTimeSelector is just needed to extract a timestep/channel of + //the image in order to get the image dimension (time dimension and channel dimension + //stripped away). Therfore it is OK to always use time step 0 and channel 0 + mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); + timeSelector->SetInput(workingImage); + timeSelector->SetTimeNr(0); + timeSelector->SetChannelNr(0); + timeSelector->Update(); + const auto dimRefImg = timeSelector->GetOutput()->GetDimension(); + + if (dimRefImg != 3) + return; + + std::vector contourList; + contourList.reserve(sliceInfos.size()); + ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); - mitk::Surface::Pointer contour; + + std::vector relevantSlices = sliceInfos; if (detectIntersection) { - // Test whether there is something to extract or whether the slice just contains intersections of others - mitk::Image::Pointer slice2 = slice->Clone(); - mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); - - contourExtractor->SetInput(slice2); - contourExtractor->Update(); - contour = contourExtractor->GetOutput(); + relevantSlices.clear(); - if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) + for (const auto& sliceInfo : sliceInfos) { - // Remove contour! - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; - contourInfo.contourNormal = plane->GetNormal(); - contourInfo.contourPoint = plane->GetOrigin(); - mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); - return; + // Test whether there is something to extract or whether the slice just contains intersections of others + mitk::Image::Pointer slice2 = sliceInfo.slice->Clone(); + mitk::MorphologicalOperations::Erode(slice2, 2, mitk::MorphologicalOperations::Ball); + + contourExtractor->SetInput(slice2); + contourExtractor->Update(); + mitk::Surface::Pointer contour = contourExtractor->GetOutput(); + + if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) + { + Self::RemoveContourFromInterpolator(sliceInfo); + } + else + { + relevantSlices.push_back(sliceInfo); + } } } - contourExtractor->SetInput(slice); - contourExtractor->Update(); - contour = contourExtractor->GetOutput(); - - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(workingImage); - timeSelector->SetTimeNr(0); - timeSelector->SetChannelNr(0); - timeSelector->Update(); - Image::Pointer dimRefImg = timeSelector->GetOutput(); + if (relevantSlices.empty()) + return; - if (contour->GetVtkPolyData()->GetNumberOfPoints() != 0 && dimRefImg->GetDimension() == 3) - { - mitk::SurfaceInterpolationController::GetInstance()->AddNewContour(contour); - contour->DisconnectPipeline(); - } - else + for (const auto& sliceInfo : relevantSlices) { - // Remove contour! - mitk::SurfaceInterpolationController::ContourPositionInformation contourInfo; - contourInfo.contourNormal = plane->GetNormal(); - contourInfo.contourPoint = plane->GetOrigin(); - mitk::SurfaceInterpolationController::GetInstance()->RemoveContour(contourInfo); + + contourExtractor->SetInput(sliceInfo.slice); + contourExtractor->Update(); + mitk::Surface::Pointer contour = contourExtractor->GetOutput(); + + if (contour->GetVtkPolyData()->GetNumberOfPoints() == 0) + { + Self::RemoveContourFromInterpolator(sliceInfo); + } + else + { + contour->DisconnectPipeline(); + contourList.push_back(contour); + } } + mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList); } + + mitk::Image::Pointer mitk::SegTool2D::GetAffectedImageSliceAs2DImage(const InteractionPositionEvent *positionEvent, const Image *image, unsigned int component /*= 0*/) { if (!positionEvent) { return nullptr; } assert(positionEvent->GetSender()); // sure, right? - unsigned int timeStep = positionEvent->GetSender()->GetTimeStep(image); // get the timestep of the visible part (time-wise) of the image + 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::GetAffectedImageSliceAs2DImage(const PlaneGeometry *planeGeometry, const Image *image, unsigned int timeStep, unsigned int component /*= 0*/) +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) +mitk::Image::Pointer mitk::SegTool2D::GetAffectedWorkingSlice(const InteractionPositionEvent *positionEvent) const { - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); + const auto workingNode = this->GetWorkingDataNode(); if (!workingNode) { return nullptr; } - auto *workingImage = dynamic_cast(workingNode->GetData()); + const auto *workingImage = dynamic_cast(workingNode->GetData()); if (!workingImage) { return nullptr; } return GetAffectedImageSliceAs2DImage(positionEvent, workingImage); } -mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const InteractionPositionEvent *positionEvent) +mitk::Image::Pointer mitk::SegTool2D::GetAffectedReferenceSlice(const InteractionPositionEvent *positionEvent) const { - DataNode *referenceNode(m_ToolManager->GetReferenceData(0)); + 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); } } -void mitk::SegTool2D::WriteBackSegmentationResult(const InteractionPositionEvent *positionEvent, Image *slice) +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 && slice && !abstractTransformGeometry) + if (planeGeometry && segmentationResult && !abstractTransformGeometry) { - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); + const auto workingNode = this->GetWorkingDataNode(); auto *image = dynamic_cast(workingNode->GetData()); - unsigned int timeStep = positionEvent->GetSender()->GetTimeStep(image); - this->WriteBackSegmentationResult(planeGeometry, slice, timeStep); + const auto timeStep = positionEvent->GetSender()->GetTimeStep(image); + this->WriteBackSegmentationResult(planeGeometry, segmentationResult, timeStep); } } +void mitk::SegTool2D::WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep) +{ + if (!planeGeometry || !segmentationResult) + return; + + SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); + Self::WriteBackSegmentationResults(workingNode, { sliceInfo }, true); +} + void mitk::SegTool2D::WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, - Image *slice, - unsigned int timeStep) + const Image * segmentationResult, + TimeStepType timeStep) { - if (!planeGeometry || !slice) + if (!planeGeometry || !segmentationResult) return; - SliceInformation sliceInfo(slice, const_cast(planeGeometry), timeStep); - this->WriteSliceToVolume(sliceInfo); - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); - auto *image = dynamic_cast(workingNode->GetData()); + SliceInformation sliceInfo(segmentationResult, const_cast(planeGeometry), timeStep); + WriteBackSegmentationResults({ sliceInfo }, true); +} - this->UpdateSurfaceInterpolation(slice, image, planeGeometry, false); +void mitk::SegTool2D::WriteBackSegmentationResults(const std::vector &sliceList, + bool writeSliceToVolume) +{ + const auto workingNode = this->GetWorkingDataNode(); - if (m_SurfaceInterpolationEnabled) - this->AddContourmarker(); + mitk::SegTool2D::WriteBackSegmentationResults(workingNode, sliceList, writeSliceToVolume); - mitk::RenderingManager::GetInstance()->RequestUpdateAll(); + // 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::WriteBackSegmentationResult(const std::vector &sliceList, - bool writeSliceToVolume) +void mitk::SegTool2D::WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume) { - std::vector contourList; - contourList.reserve(sliceList.size()); - ImageToContourFilter::Pointer contourExtractor = ImageToContourFilter::New(); + if (nullptr == workingNode) + { + mitkThrow() << "Cannot write slice to working node. Working node is invalid."; + } - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); - auto *image = dynamic_cast(workingNode->GetData()); + auto* image = dynamic_cast(workingNode->GetData()); - mitk::ImageTimeSelector::Pointer timeSelector = mitk::ImageTimeSelector::New(); - timeSelector->SetInput(image); - timeSelector->SetTimeNr(0); - timeSelector->SetChannelNr(0); - timeSelector->Update(); - Image::Pointer dimRefImg = timeSelector->GetOutput(); + if (nullptr == image) + { + mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; + } - for (unsigned int i = 0; i < sliceList.size(); ++i) + for (const auto& sliceInfo : sliceList) { - SliceInformation currentSliceInfo = sliceList.at(i); - if (writeSliceToVolume) - this->WriteSliceToVolume(currentSliceInfo); - if (m_SurfaceInterpolationEnabled && dimRefImg->GetDimension() == 3) + if (writeSliceToVolume && nullptr != sliceInfo.plane && sliceInfo.slice.IsNotNull()) { - currentSliceInfo.slice->DisconnectPipeline(); - contourExtractor->SetInput(currentSliceInfo.slice); - contourExtractor->Update(); - mitk::Surface::Pointer contour = contourExtractor->GetOutput(); - contour->DisconnectPipeline(); - - contourList.push_back(contour); + mitk::SegTool2D::WriteSliceToVolume(image, sliceInfo, true); } } - mitk::SurfaceInterpolationController::GetInstance()->AddNewContours(contourList); + + 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(const mitk::SegTool2D::SliceInformation &sliceInfo) +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) { - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); - auto *image = dynamic_cast(workingNode->GetData()); + if (nullptr == workingImage) + { + mitkThrow() << "Cannot write slice to working node. Working node does not contain an image."; + } + + DiffSliceOperation* undoOperation = nullptr; - /*============= BEGIN undo/redo feature block ========================*/ - // Create undo operation by caching the not yet modified slices - mitk::Image::Pointer originalSlice = GetAffectedImageSliceAs2DImage(sliceInfo.plane, image, sliceInfo.timestep); - auto *undoOperation = - new DiffSliceOperation(image, - originalSlice, - dynamic_cast(originalSlice->GetGeometry()), - sliceInfo.timestep, - sliceInfo.plane); - /*============= END undo/redo feature block ========================*/ + 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' - reslice->SetInputSlice(sliceInfo.slice->GetVtkImageData()); + // 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(image); + extractor->SetInput(workingImage); extractor->SetTimeStep(sliceInfo.timestep); extractor->SetWorldGeometry(sliceInfo.plane); extractor->SetVtkOutputRequest(false); - extractor->SetResliceTransformByGeometry(image->GetGeometry(sliceInfo.timestep)); + extractor->SetResliceTransformByGeometry(workingImage->GetGeometry(sliceInfo.timestep)); extractor->Modified(); extractor->Update(); // the image was modified within the pipeline, but not marked so - image->Modified(); - image->GetVtkImageData()->Modified(); + workingImage->Modified(); + workingImage->GetVtkImageData()->Modified(); - // also mark its node as modified (T27308). Can be removed if T27307 - // is properly solved - if (workingNode != nullptr) workingNode->Modified(); - - /*============= BEGIN undo/redo feature block ========================*/ - // specify the undo operation with the edited slice - auto *doOperation = - new DiffSliceOperation(image, - 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); + 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 ========================*/ + } - // clear the pointers as the operation are stored in the undocontroller and also deleted from there - undoOperation = nullptr; - doOperation = nullptr; - /*============= 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() + +int mitk::SegTool2D::AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex) { - if (m_LastEventSender == nullptr) + if (planeGeometry == nullptr) return -1; us::ServiceReference serviceRef = us::GetModuleContext()->GetServiceReference(); PlanePositionManagerService *service = us::GetModuleContext()->GetService(serviceRef); - unsigned int slicePosition = m_LastEventSender->GetSliceNavigationController()->GetSlice()->GetPos(); - - // the first geometry is needed otherwise restoring the position is not working - const auto *plane = - dynamic_cast(dynamic_cast( - m_LastEventSender->GetSliceNavigationController()->GetCurrentGeometry3D()) - ->GetPlaneGeometry(0)); - unsigned int size = service->GetNumberOfPlanePositions(); - unsigned int id = service->AddNewPlanePosition(plane, slicePosition); + unsigned int id = service->AddNewPlanePosition(planeGeometry, sliceIndex); mitk::PlanarCircle::Pointer contourMarker = mitk::PlanarCircle::New(); mitk::Point2D p1; - plane->Map(plane->GetCenter(), p1); + planeGeometry->Map(planeGeometry->GetCenter(), p1); mitk::Point2D p2 = p1; - p2[0] -= plane->GetSpacing()[0]; - p2[1] -= plane->GetSpacing()[1]; + p2[0] -= planeGeometry->GetSpacing()[0]; + p2[1] -= planeGeometry->GetSpacing()[1]; contourMarker->PlaceFigure(p1); contourMarker->SetCurrentControlPoint(p1); - contourMarker->SetPlaneGeometry(const_cast(plane)); + contourMarker->SetPlaneGeometry(planeGeometry->Clone()); std::stringstream markerStream; - mitk::DataNode *workingNode(m_ToolManager->GetWorkingData(0)); + 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 (plane) + 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) +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, Image *sourceSlice, mitk::Image *workingImage, int paintingPixelValue, int) + Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue) { - if ((!targetSlice) || (!sourceSlice)) - return; - AccessFixedDimensionByItk_3( - targetSlice, InternalWritePreviewOnWorkingImage, 2, sourceSlice, workingImage, 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 474b90fae7..a95831ba38 100644 --- a/Modules/Segmentation/Interactions/mitkSegTool2D.h +++ b/Modules/Segmentation/Interactions/mitkSegTool2D.h @@ -1,199 +1,293 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkSegTool2D_h_Included #define mitkSegTool2D_h_Included #include "mitkCommon.h" #include "mitkImage.h" #include "mitkTool.h" #include #include "mitkInteractionPositionEvent.h" #include "mitkInteractionConst.h" #include "mitkPlanePositionManager.h" #include "mitkRestorePlanePositionOperation.h" #include namespace mitk { class BaseRenderer; /** \brief Abstract base class for segmentation tools. \sa Tool \ingroup Interaction \ingroup ToolManagerEtAl Implements 2D segmentation specific helper methods, that might be of use to all kind of 2D segmentation tools. At the moment these are: - Determination of the slice where the user paints upon (DetermineAffectedImageSlice) - Projection of a 3D contour onto a 2D plane/slice SegTool2D tries to structure the interaction a bit. If you pass "PressMoveRelease" as the interaction type of your derived tool, you might implement the methods OnMousePressed, OnMouseMoved, and OnMouseReleased. Yes, your guess about when they are called is correct. \warning Only to be instantiated by mitk::ToolManager. $Author$ */ class MITKSEGMENTATION_EXPORT SegTool2D : public Tool { public: mitkClassMacro(SegTool2D, Tool); /** \brief Calculates for a given Image and PlaneGeometry, which slice of the image (in index corrdinates) is meant by the plane. \return false, if no slice direction seems right (e.g. rotated planes) \param image \param plane \param affectedDimension The image dimension, which is constant for all points in the plane, e.g. Axial --> 2 \param affectedSlice The index of the image slice */ static bool DetermineAffectedImageSlice(const Image *image, const PlaneGeometry *plane, int &affectedDimension, int &affectedSlice); /** * @brief Updates the surface interpolation by extracting the contour form the given slice. * @param slice the slice from which the contour should be extracted * @param workingImage the segmentation image * @param plane the plane in which the slice lies * @param detectIntersection if true the slice is eroded before contour extraction. If the slice is empty after the * erosion it is most * likely an intersecting contour an will not be added to the SurfaceInterpolationController */ static void UpdateSurfaceInterpolation(const Image *slice, const Image *workingImage, const PlaneGeometry *plane, bool detectIntersection); + /** + * \brief Extract the slice of an image that the user just scribbles on. The given component denotes the vector component of an vector image. + * + * \param positionEvent Event that specifies the plane that should be used to slice + * \param image Image that should be sliced + * \param 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 vector image. + * + * \param planeGeometry Geometry defining the slice that should be cut out. + * \param image Image that should be sliced + * \param timeStep TimeStep of the image that shold be sliced + * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. + * + * \return 'nullptr' if SegTool2D is either unable to determine which slice was affected, or if there was some problem + * getting the image data at that position. + */ + static Image::Pointer GetAffectedImageSliceAs2DImage(const PlaneGeometry* planeGeometry, + const Image* image, + TimeStepType timeStep, + unsigned int component = 0); + static Image::Pointer GetAffectedImageSliceAs2DImageByTimePoint(const PlaneGeometry* planeGeometry, + const Image* image, + TimePointType timePoint, + unsigned int component = 0); + + /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. + * Calls static WriteBackSegmentationResults*/ + static void WriteBackSegmentationResult(const DataNode* workingNode, const PlaneGeometry* planeGeometry, const Image* segmentationResult, TimeStepType timeStep); + + /** Convenience overloaded version that can be called for a given planeGeometry, slice image and time step. + * For more details see protected WriteSliceToVolume version.*/ + static void WriteSliceToVolume(Image* workingImage, const PlaneGeometry* planeGeometry, const Image* slice, TimeStepType timeStep, bool allowUndo); + void SetShowMarkerNodes(bool); /** * \brief Enables or disables the 3D interpolation after writing back the 2D segmentation result, and defaults to * true. */ void SetEnable3DInterpolation(bool); + void Activated() override; + void Deactivated() override; + + itkSetMacro(IsTimePointChangeAware, bool); + itkGetMacro(IsTimePointChangeAware, bool); + itkBooleanMacro(IsTimePointChangeAware); + protected: SegTool2D(); // purposely hidden SegTool2D(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~SegTool2D() override; + /** + * @brief returns the segmentation node that should be modified by the tool. + */ + DataNode* GetWorkingDataNode() const; + Image* GetWorkingData() const; + + DataNode* GetReferenceDataNode() const; + Image* GetReferenceData() const; + + /** + * This function can be reimplemented by derived classes to react on changes of the current + * time point. Default implementation does nothing.*/ + virtual void OnTimePointChanged(); + struct SliceInformation { - mitk::Image::Pointer slice; - mitk::PlaneGeometry *plane; - unsigned int timestep; - - SliceInformation() {} - SliceInformation(mitk::Image *slice, mitk::PlaneGeometry *plane, unsigned int timestep) - { - this->slice = slice; - this->plane = plane; - this->timestep = timestep; - } + mitk::Image::ConstPointer slice; + const mitk::PlaneGeometry *plane = nullptr; + mitk::TimeStepType timestep = 0; + + SliceInformation() = default; + SliceInformation(const mitk::Image* aSlice, const mitk::PlaneGeometry* aPlane, mitk::TimeStepType aTimestep); }; /** - * \brief Filters events that cannot be handle by 2D segmentation tools - * - * Current 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 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 Extract the slice of an image that the user just scribbles on. The given component denotes the vector component of a dwi-image. - * - * \param positionEvent - * \param image - * \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. - */ - 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. - * - * \param planeGeometry - * \param image - * \param timeStep - * \param component The component to be extracted of a given multi-component image. -1 is the default parameter to denote an invalid component. + * \brief Filters events that cannot be handled by 2D segmentation tools * - * \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. + * Currently an event is discarded if it was not sent by a 2D renderwindow and if it is + * not of type InteractionPositionEvent */ - Image::Pointer GetAffectedImageSliceAs2DImage(const PlaneGeometry *planeGeometry, - const Image *image, - unsigned int timeStep, - unsigned int component = 0); + 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 *); + 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 *); - - void WriteBackSegmentationResult(const InteractionPositionEvent *, Image *); - - void WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, Image *, unsigned int timeStep); + Image::Pointer GetAffectedReferenceSlice(const InteractionPositionEvent *) const; + /** Overload version that gets the reference slice passed on the passed plane geometry and timestep.*/ + Image::Pointer GetAffectedReferenceSlice(const PlaneGeometry* planeGeometry, TimeStepType timeStep) const; + + /** Convenience version that can be called for a given event (which is used to deduce timepoint and plane) and a slice image. + * Calls non static WriteBackSegmentationResults*/ + void WriteBackSegmentationResult(const InteractionPositionEvent *, const Image* segmentationResult); + + /** Convenience version that can be called for a given planeGeometry, slice image and time step. + * Calls non static WriteBackSegmentationResults*/ + void WriteBackSegmentationResult(const PlaneGeometry *planeGeometry, const Image* segmentationResult, TimeStepType timeStep); + + /** Overloaded version that calls the static version and also adds the contour markers.*/ + 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 false the write operation (WriteSliceToVolume will be skipped) + * and only the surface interpolation will be updated. + * @pre workingNode must point to a valid instance and contain an image instance as data.*/ + static void WriteBackSegmentationResults(const DataNode* workingNode, const std::vector& sliceList, bool writeSliceToVolume = true); + + /** Writes the provided source slice into the target slice with the given pixel value. + * If passed workingImage is a LabelSetImage the label set rules will be applied when + * writing all non zero source pixels into the target slice (e.g. locked lables will not be touched) + * with the given paintingPixelValue. + * @param targetSlice Pointer to the slice that should be filled with the content of the sourceSlice. + * @param sourceSlice Pointer to the slice that is the source/preview every pixel will be (tried to be) transfered . + * @param workingImage Will be used to check if LabeSetImageRules have to be applied and the label set state. + * @param paintingPixelValue Value that will be used to paint onto target slice. + * @pre targetSlice must point to a valid instance. + * @pre sourceSlice must point to a valid instance. + * @pre workingImage must point to a valid instance.*/ + static void WritePreviewOnWorkingImage( + Image *targetSlice, const Image *sourceSlice, const Image *workingImage, int paintingPixelValue); + + /** Writes a provided slice into the passed working image. The content of working image that is covered + * by the slice will be completly overwritten. If asked for it also generates the needed + * undo/redo steps. + * @param workingImage Pointer to the image that is the target of the write operation. + * @param sliceInfo SliceInfo instance that containes the slice image, the defining plane geometry and time step. + * @param allowUndo Indicates if undo/redo operations should be registered for the write operation + * performed by this call. true: undo/redo will be generated; false: no undo/redo will be generated, so + * this operation cannot be revoked by the user. + * @pre workingImage must point to a valid instance.*/ + static void WriteSliceToVolume(Image* workingImage, const SliceInformation &sliceInfo, bool allowUndo); - void WriteBackSegmentationResult(const std::vector &sliceList, bool writeSliceToVolume = true); - - void WritePreviewOnWorkingImage( - Image *targetSlice, Image *sourceSlice, Image *workingImage, int paintingPixelValue, int timestep); - - void WriteSliceToVolume(const SliceInformation &sliceInfo); /** \brief Adds a new node called Contourmarker to the datastorage which holds a mitk::PlanarFigure. - By selecting this node the slicestack will be reoriented according to the PlanarFigure's Geometry + By selecting this node the slicestack will be reoriented according to the passed + PlanarFigure's Geometry */ - int AddContourmarker(); + int AddContourmarker(const PlaneGeometry* planeGeometry, unsigned int sliceIndex); - void InteractiveSegmentationBugMessage(const std::string &message); + void InteractiveSegmentationBugMessage(const std::string &message) const; - BaseRenderer *m_LastEventSender; - unsigned int m_LastEventSlice; + 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; + bool m_ShowMarkerNodes = false; static bool m_SurfaceInterpolationEnabled; + + bool m_IsTimePointChangeAware = true; + + TimePointType m_LastTimePointTriggered = 0.; }; } // namespace #endif diff --git a/Modules/Segmentation/Interactions/mitkSegmentationInteractor.cpp b/Modules/Segmentation/Interactions/mitkSegmentationInteractor.cpp index 4aec82aba5..1b67d32f1f 100644 --- a/Modules/Segmentation/Interactions/mitkSegmentationInteractor.cpp +++ b/Modules/Segmentation/Interactions/mitkSegmentationInteractor.cpp @@ -1,63 +1,63 @@ /*============================================================================ 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 "mitkSegmentationInteractor.h" #include "mitkInteractionPositionEvent.h" #include "mitkLabelSetImage.h" #include "mitkToolManager.h" #include "mitkToolManagerProvider.h" #include #include void mitk::SegmentationInteractor::ConnectActionsAndFunctions() { Superclass::ConnectActionsAndFunctions(); // CONNECT_FUNCTION("change_active_label", ChangeActiveLabel); } bool mitk::SegmentationInteractor::ChangeActiveLabel(StateMachineAction *, InteractionEvent *interactionEvent) { BaseRenderer::Pointer sender = interactionEvent->GetSender(); auto *positionEvent = static_cast(interactionEvent); // MLI TODO // m_LastDisplayCoordinate = m_CurrentDisplayCoordinate; // m_CurrentDisplayCoordinate = positionEvent->GetPointerPositionOnScreen(); auto *toolManager = mitk::ToolManagerProvider::GetInstance()->GetToolManager( mitk::ToolManagerProvider::MULTILABEL_SEGMENTATION); assert(toolManager); DataNode *workingNode(toolManager->GetWorkingData(0)); if (workingNode) { auto *workingImage = dynamic_cast(workingNode->GetData()); assert(workingImage); - int timestep = positionEvent->GetSender()->GetTimeStep(); + const auto timestep = positionEvent->GetSender()->GetTimeStep(workingImage); int pixelValue = static_cast(workingImage->GetPixelValueByWorldCoordinate(positionEvent->GetPositionInWorld(), timestep)); workingImage->GetActiveLabelSet()->SetActiveLabel(pixelValue); // can be the background // Call Events // workingImage->ActiveLabelEvent.Send(pixelValue); // MLI TODO // toolManager->WorkingDataModified.Send(); } RenderingManager::GetInstance()->RequestUpdateAll(); return true; } diff --git a/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp b/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp index bf0bbfe459..da8a94ed20 100644 --- a/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp +++ b/Modules/Segmentation/Interactions/mitkSetRegionTool.cpp @@ -1,176 +1,138 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #include "mitkSetRegionTool.h" #include "mitkToolManager.h" #include "mitkBaseRenderer.h" -#include "mitkImageDataItem.h" -#include "mitkLabelSetImage.h" #include -#include #include #include #include mitk::SetRegionTool::SetRegionTool(int paintingPixelValue) : FeedbackContourTool("PressMoveRelease"), m_PaintingPixelValue(paintingPixelValue) { } mitk::SetRegionTool::~SetRegionTool() { } void mitk::SetRegionTool::ConnectActionsAndFunctions() { CONNECT_FUNCTION("PrimaryButtonPressed", OnMousePressed); CONNECT_FUNCTION("Release", OnMouseReleased); CONNECT_FUNCTION("Move", OnMouseMoved); } void mitk::SetRegionTool::Activated() { Superclass::Activated(); } void mitk::SetRegionTool::Deactivated() { Superclass::Deactivated(); } void mitk::SetRegionTool::OnMousePressed(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; m_LastEventSender = positionEvent->GetSender(); m_LastEventSlice = m_LastEventSender->GetSlice(); // 1. Get the working image Image::Pointer workingSlice = FeedbackContourTool::GetAffectedWorkingSlice(positionEvent); if (workingSlice.IsNull()) return; // can't do anything without the segmentation // if click was outside the image, don't continue const BaseGeometry *sliceGeometry = workingSlice->GetGeometry(); itk::Index<3> projectedPointIn2D; sliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), projectedPointIn2D); if (!sliceGeometry->IsIndexInside(projectedPointIn2D)) { MITK_WARN << "Point outside of segmentation slice." << std::endl; return; // can't use that as a seed point } typedef itk::Image InputImageType; typedef InputImageType::IndexType IndexType; typedef itk::ConnectedThresholdImageFilter RegionGrowingFilterType; RegionGrowingFilterType::Pointer regionGrower = RegionGrowingFilterType::New(); // convert world coordinates to image indices IndexType seedIndex; sliceGeometry->WorldToIndex(positionEvent->GetPositionInWorld(), seedIndex); // perform region growing in desired segmented region InputImageType::Pointer itkImage = InputImageType::New(); CastToItkImage(workingSlice, itkImage); regionGrower->SetInput(itkImage); regionGrower->AddSeed(seedIndex); InputImageType::PixelType bound = itkImage->GetPixel(seedIndex); regionGrower->SetLower(bound); regionGrower->SetUpper(bound); regionGrower->SetReplaceValue(1); itk::BinaryFillholeImageFilter::Pointer fillHolesFilter = itk::BinaryFillholeImageFilter::New(); fillHolesFilter->SetInput(regionGrower->GetOutput()); fillHolesFilter->SetForegroundValue(1); // Store result and preview mitk::Image::Pointer resultImage = mitk::GrabItkImageMemory(fillHolesFilter->GetOutput()); resultImage->SetGeometry(workingSlice->GetGeometry()); // Get the current working color DataNode *workingNode(m_ToolManager->GetWorkingData(0)); if (!workingNode) return; mitk::ImageToContourModelFilter::Pointer contourextractor = mitk::ImageToContourModelFilter::New(); contourextractor->SetInput(resultImage); contourextractor->Update(); mitk::ContourModel::Pointer awesomeContour = contourextractor->GetOutput(); - auto t = positionEvent->GetSender()->GetTimeStep(); - FeedbackContourTool::SetFeedbackContour(0 != t - ? ContourModelUtils::MoveZerothContourTimeStep(awesomeContour, t) - : awesomeContour); + this->UpdateCurrentFeedbackContour(awesomeContour); FeedbackContourTool::SetFeedbackContourVisible(true); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); } void mitk::SetRegionTool::OnMouseReleased(StateMachineAction *, InteractionEvent *interactionEvent) { auto *positionEvent = dynamic_cast(interactionEvent); if (!positionEvent) return; assert(positionEvent->GetSender()->GetRenderWindow()); // 1. Hide the feedback contour, find out which slice the user clicked, find out which slice of the toolmanager's // working image corresponds to that - FeedbackContourTool::SetFeedbackContourVisible(false); mitk::RenderingManager::GetInstance()->RequestUpdate(positionEvent->GetSender()->GetRenderWindow()); - int timeStep = positionEvent->GetSender()->GetTimeStep(); - - DataNode *workingNode(m_ToolManager->GetWorkingData(0)); - if (!workingNode) - return; - - auto *image = dynamic_cast(workingNode->GetData()); - const PlaneGeometry *planeGeometry((positionEvent->GetSender()->GetCurrentWorldPlaneGeometry())); - if (!image || !planeGeometry) - return; - - Image::Pointer slice = FeedbackContourTool::GetAffectedImageSliceAs2DImage(positionEvent, image); - - if (slice.IsNull()) - { - MITK_ERROR << "Unable to extract slice." << std::endl; - return; - } - - ContourModel *feedbackContour(FeedbackContourTool::GetFeedbackContour()); - ContourModel::Pointer projectedContour = FeedbackContourTool::ProjectContourTo2DSlice( - slice, feedbackContour, false, false); // false: don't add 0.5 (done by FillContourInSlice) - // false: don't constrain the contour to the image's inside - if (projectedContour.IsNull()) - return; - - int activePixelValue = ContourModelUtils::GetActivePixelValue(image); - - mitk::ContourModelUtils::FillContourInSlice( - projectedContour, timeStep, slice, image, m_PaintingPixelValue * activePixelValue); - - this->WriteBackSegmentationResult(positionEvent, slice); + this->WriteBackFeedbackContourAsSegmentationResult(positionEvent, m_PaintingPixelValue); } void mitk::SetRegionTool::OnMouseMoved(mitk::StateMachineAction *, mitk::InteractionEvent *) { } diff --git a/Modules/Segmentation/Interactions/mitkTool.cpp b/Modules/Segmentation/Interactions/mitkTool.cpp index 2c2f32e1e5..5f15bc5dbf 100644 --- a/Modules/Segmentation/Interactions/mitkTool.cpp +++ b/Modules/Segmentation/Interactions/mitkTool.cpp @@ -1,333 +1,333 @@ /*============================================================================ 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 "mitkTool.h" #include #include "mitkDisplayInteractor.h" #include "mitkDisplayActionEventBroadcast.h" #include "mitkImageReadAccessor.h" #include "mitkImageWriteAccessor.h" #include "mitkLevelWindowProperty.h" #include "mitkLookupTableProperty.h" #include "mitkProperties.h" #include "mitkVtkResliceInterpolationProperty.h" #include // us #include #include // itk #include mitk::Tool::Tool(const char *type, const us::Module *interactorModule) : m_EventConfig("DisplayConfigMITK.xml"), m_ToolManager(nullptr), m_PredicateImages(NodePredicateDataType::New("Image")), // for reference images m_PredicateDim3(NodePredicateDimension::New(3, 1)), m_PredicateDim4(NodePredicateDimension::New(4, 1)), m_PredicateDimension(mitk::NodePredicateOr::New(m_PredicateDim3, m_PredicateDim4)), m_PredicateImage3D(NodePredicateAnd::New(m_PredicateImages, m_PredicateDimension)), m_PredicateBinary(NodePredicateProperty::New("binary", BoolProperty::New(true))), m_PredicateNotBinary(NodePredicateNot::New(m_PredicateBinary)), m_PredicateSegmentation(NodePredicateProperty::New("segmentation", BoolProperty::New(true))), m_PredicateNotSegmentation(NodePredicateNot::New(m_PredicateSegmentation)), m_PredicateHelper(NodePredicateProperty::New("helper object", BoolProperty::New(true))), m_PredicateNotHelper(NodePredicateNot::New(m_PredicateHelper)), m_PredicateImageColorful(NodePredicateAnd::New(m_PredicateNotBinary, m_PredicateNotSegmentation)), m_PredicateImageColorfulNotHelper(NodePredicateAnd::New(m_PredicateImageColorful, m_PredicateNotHelper)), m_PredicateReference(NodePredicateAnd::New(m_PredicateImage3D, m_PredicateImageColorfulNotHelper)), m_IsSegmentationPredicate( NodePredicateAnd::New(NodePredicateOr::New(m_PredicateBinary, m_PredicateSegmentation), m_PredicateNotHelper)), m_InteractorType(type), m_DisplayInteractorConfigs(), m_InteractorModule(interactorModule) { } mitk::Tool::~Tool() { } bool mitk::Tool::CanHandle(const BaseData* referenceData, const BaseData* /*workingData*/) const { if (referenceData == nullptr) return false; return true; } void mitk::Tool::InitializeStateMachine() { if (m_InteractorType.empty()) return; try { auto isThisModule = nullptr == m_InteractorModule; auto module = isThisModule ? us::GetModuleContext()->GetModule() : m_InteractorModule; LoadStateMachine(m_InteractorType + ".xml", module); SetEventConfig(isThisModule ? "SegmentationToolsConfig.xml" : m_InteractorType + "Config.xml", module); } catch (const std::exception &e) { MITK_ERROR << "Could not load statemachine pattern " << m_InteractorType << ".xml with exception: " << e.what(); } } void mitk::Tool::Notify(InteractionEvent *interactionEvent, bool isHandled) { // to use the state machine pattern, // the event is passed to the state machine interface to be handled if (!isHandled) { this->HandleEvent(interactionEvent, nullptr); } } void mitk::Tool::ConnectActionsAndFunctions() { } bool mitk::Tool::FilterEvents(InteractionEvent *, DataNode *) { return true; } const char *mitk::Tool::GetGroup() const { return "default"; } void mitk::Tool::SetToolManager(ToolManager *manager) { m_ToolManager = manager; } void mitk::Tool::Activated() { // As a legacy solution the display interaction of the new interaction framework is disabled here to avoid conflicts // with tools // Note: this only affects InteractionEventObservers (formerly known as Listeners) all DataNode specific interaction // will still be enabled m_DisplayInteractorConfigs.clear(); std::vector> listEventObserver = us::GetModuleContext()->GetServiceReferences(); for (auto it = listEventObserver.begin(); it != listEventObserver.end(); ++it) { auto displayInteractor = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayInteractor != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayInteractor->GetEventConfig())); // here the alternative configuration is loaded displayInteractor->SetEventConfig(m_EventConfig.c_str()); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(*it)); if (displayActionEventBroadcast != nullptr) { // remember the original configuration m_DisplayInteractorConfigs.insert(std::make_pair(*it, displayActionEventBroadcast->GetEventConfig())); // here the alternative configuration is loaded displayActionEventBroadcast->SetEventConfig(m_EventConfig.c_str()); } } } void mitk::Tool::Deactivated() { // Re-enabling InteractionEventObservers that have been previously disabled for legacy handling of Tools // in new interaction framework for (auto it = m_DisplayInteractorConfigs.begin(); it != m_DisplayInteractorConfigs.end(); ++it) { if (it->first) { auto displayInteractor = static_cast(us::GetModuleContext()->GetService(it->first)); if (displayInteractor != nullptr) { // here the regular configuration is loaded again displayInteractor->SetEventConfig(it->second); } auto displayActionEventBroadcast = dynamic_cast(us::GetModuleContext()->GetService(it->first)); if (displayActionEventBroadcast != nullptr) { // here the regular configuration is loaded again displayActionEventBroadcast->SetEventConfig(it->second); } } } m_DisplayInteractorConfigs.clear(); } itk::Object::Pointer mitk::Tool::GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix) { itk::Object::Pointer object; std::string classname = this->GetNameOfClass(); std::string guiClassname = toolkitPrefix + classname + toolkitPostfix; std::list allGUIs = itk::ObjectFactoryBase::CreateAllInstance(guiClassname.c_str()); for (auto iter = allGUIs.begin(); iter != allGUIs.end(); ++iter) { if (object.IsNull()) { object = dynamic_cast(iter->GetPointer()); } else { MITK_ERROR << "There is more than one GUI for " << classname << " (several factories claim ability to produce a " << guiClassname << " ) " << std::endl; return nullptr; // people should see and fix this error } } return object; } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetReferenceDataPreference() const { return m_PredicateReference.GetPointer(); } mitk::NodePredicateBase::ConstPointer mitk::Tool::GetWorkingDataPreference() const { return m_IsSegmentationPredicate.GetPointer(); } mitk::DataNode::Pointer mitk::Tool::CreateEmptySegmentationNode(const Image *original, const std::string &organName, - const mitk::Color &color) + const mitk::Color &color) const { // we NEED a reference image for size etc. if (!original) return nullptr; // actually create a new empty segmentation PixelType pixelType(mitk::MakeScalarPixelType()); LabelSetImage::Pointer segmentation = LabelSetImage::New(); if (original->GetDimension() == 2) { const unsigned int dimensions[] = {original->GetDimension(0), original->GetDimension(1), 1}; segmentation->Initialize(pixelType, 3, dimensions); segmentation->AddLayer(); } else { segmentation->Initialize(original); } mitk::Label::Pointer label = mitk::Label::New(); label->SetName(organName); label->SetColor(color); label->SetValue(1); segmentation->GetActiveLabelSet()->AddLabel(label); segmentation->GetActiveLabelSet()->SetActiveLabel(1); unsigned int byteSize = sizeof(mitk::Label::PixelType); if (segmentation->GetDimension() < 4) { for (unsigned int dim = 0; dim < segmentation->GetDimension(); ++dim) { byteSize *= segmentation->GetDimension(dim); } mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(0)); memset(writeAccess.GetData(), 0, byteSize); } else { // if we have a time-resolved image we need to set memory to 0 for each time step for (unsigned int dim = 0; dim < 3; ++dim) { byteSize *= segmentation->GetDimension(dim); } for (unsigned int volumeNumber = 0; volumeNumber < segmentation->GetDimension(3); volumeNumber++) { mitk::ImageWriteAccessor writeAccess(segmentation.GetPointer(), segmentation->GetVolumeData(volumeNumber)); memset(writeAccess.GetData(), 0, byteSize); } } if (original->GetTimeGeometry()) { TimeGeometry::Pointer originalGeometry = original->GetTimeGeometry()->Clone(); segmentation->SetTimeGeometry(originalGeometry); } else { Tool::ErrorMessage("Original image does not have a 'Time sliced geometry'! Cannot create a segmentation."); return nullptr; } return CreateSegmentationNode(segmentation, organName, color); } mitk::DataNode::Pointer mitk::Tool::CreateSegmentationNode(Image *image, const std::string &organName, - const mitk::Color &color) + const mitk::Color &color) const { if (!image) return nullptr; // decorate the datatreenode with some properties DataNode::Pointer segmentationNode = DataNode::New(); segmentationNode->SetData(image); // name segmentationNode->SetProperty("name", StringProperty::New(organName)); // visualization properties segmentationNode->SetProperty("binary", BoolProperty::New(true)); segmentationNode->SetProperty("color", ColorProperty::New(color)); mitk::LookupTable::Pointer lut = mitk::LookupTable::New(); lut->SetType(mitk::LookupTable::MULTILABEL); mitk::LookupTableProperty::Pointer lutProp = mitk::LookupTableProperty::New(); lutProp->SetLookupTable(lut); segmentationNode->SetProperty("LookupTable", lutProp); segmentationNode->SetProperty("texture interpolation", BoolProperty::New(false)); segmentationNode->SetProperty("layer", IntProperty::New(10)); segmentationNode->SetProperty("levelwindow", LevelWindowProperty::New(LevelWindow(0.5, 1))); segmentationNode->SetProperty("opacity", FloatProperty::New(0.3)); segmentationNode->SetProperty("segmentation", BoolProperty::New(true)); segmentationNode->SetProperty("reslice interpolation", VtkResliceInterpolationProperty::New()); // otherwise -> segmentation appears in 2 // slices sometimes (only visual effect, not // different data) // For MITK-3M3 release, the volume of all segmentations should be shown segmentationNode->SetProperty("showVolume", BoolProperty::New(true)); return segmentationNode; } us::ModuleResource mitk::Tool::GetIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } us::ModuleResource mitk::Tool::GetCursorIconResource() const { // Each specific tool should load its own resource. This one will be invalid return us::ModuleResource(); } diff --git a/Modules/Segmentation/Interactions/mitkTool.h b/Modules/Segmentation/Interactions/mitkTool.h index 16b2cf15a9..ddc998b8d0 100644 --- a/Modules/Segmentation/Interactions/mitkTool.h +++ b/Modules/Segmentation/Interactions/mitkTool.h @@ -1,270 +1,269 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef mitkTool_h_Included #define mitkTool_h_Included #include "itkObjectFactoryBase.h" #include "itkVersion.h" #include "mitkCommon.h" #include "mitkDataNode.h" #include "mitkEventStateMachine.h" #include "mitkInteractionEventObserver.h" #include "mitkLabelSetImage.h" #include "mitkMessage.h" #include "mitkNodePredicateAnd.h" #include "mitkNodePredicateDataType.h" #include "mitkNodePredicateDimension.h" #include "mitkNodePredicateNot.h" #include "mitkNodePredicateOr.h" #include "mitkNodePredicateProperty.h" #include "mitkToolEvents.h" #include "mitkToolFactoryMacro.h" #include #include #include #include #include #include #include "usServiceRegistration.h" namespace us { class ModuleResource; } namespace mitk { class ToolManager; /** \brief Base class of all tools used by mitk::ToolManager. \sa ToolManager \sa SegTool2D \ingroup Interaction \ingroup ToolManagerEtAl Every tool is a mitk::StateMachine, which can follow any transition pattern that it likes. One important thing to know is, that every derived tool should always call SuperClass::Deactivated() at the end of its own implementation of Deactivated, because mitk::Tool resets the StateMachine in this method. Only if you are very sure that you covered all possible things that might happen to your own tool, you should consider not to reset the StateMachine from time to time. To learn about the MITK implementation of state machines in general, have a look at \ref InteractionPage. To derive a non-abstract tool, you inherit from mitk::Tool (or some other base class further down the inheritance tree), and in your own parameterless constructor (that is called from the itkFactorylessNewMacro that you use) you pass a StateMachine pattern name to the superclass. Names for valid patterns can be found in StateMachine.xml (which might be enhanced by you). You have to implement at least GetXPM() and GetName() to provide some identification. Each Tool knows its ToolManager, which can provide the data that the tool should work on. \warning Only to be instantiated by mitk::ToolManager (because SetToolManager has to be called). All other uses are unsupported. $Author$ */ class MITKSEGMENTATION_EXPORT Tool : public EventStateMachine, public InteractionEventObserver { public: typedef mitk::Label::PixelType DefaultSegmentationDataType; /** * \brief To let GUI process new events (e.g. qApp->processEvents() ) */ Message<> GUIProcessEventsMessage; /** * \brief To send error messages (to be shown by some GUI) */ Message1 ErrorMessage; /** * \brief To send whether the tool is busy (to be shown by some GUI) */ Message1 CurrentlyBusy; /** * \brief To send general messages (to be shown by some GUI) */ Message1 GeneralMessage; mitkClassMacro(Tool, EventStateMachine); // no New(), there should only be subclasses /** \brief Returns an icon in the XPM format. This icon has to fit into some kind of button in most applications, so make it smaller than 25x25 pixels. XPM is e.g. supported by The Gimp. But if you open any XPM file in your text editor, you will see that you could also "draw" it with an editor. */ virtual const char **GetXPM() const = 0; /** * \brief Returns the path of an icon. * * This icon is preferred to the XPM icon. */ virtual std::string GetIconPath() const { return ""; } /** * \brief Returns the path of a cursor icon. * */ virtual us::ModuleResource GetCursorIconResource() const; /** * @brief Returns the tool button icon of the tool wrapped by a usModuleResource * @return a valid ModuleResource or an invalid if this function * is not reimplemented */ virtual us::ModuleResource GetIconResource() const; /** \brief Returns the name of this tool. Make it short! This name has to fit into some kind of button in most applications, so take some time to think of a good name! */ virtual const char *GetName() const = 0; /** \brief Name of a group. You can group several tools by assigning a group name. Graphical tool selectors might use this information to group tools. (What other reason could there be?) */ virtual const char *GetGroup() const; virtual void InitializeStateMachine(); /** * \brief Interface for GUI creation. * * This is the basic interface for creation of a GUI object belonging to one tool. * * Tools that support a GUI (e.g. for display/editing of parameters) should follow some rules: * * - A Tool and its GUI are two separate classes * - There may be several instances of a GUI at the same time. * - mitk::Tool is toolkit (Qt, wxWidgets, etc.) independent, the GUI part is of course dependent * - The GUI part inherits both from itk::Object and some GUI toolkit class * - The GUI class name HAS to be constructed like "toolkitPrefix" tool->GetClassName() + "toolkitPostfix", e.g. * MyTool -> wxMyToolGUI * - For each supported toolkit there is a base class for tool GUIs, which contains some convenience methods * - Tools notify the GUI about changes using ITK events. The GUI must observe interesting events. * - The GUI base class may convert all ITK events to the GUI toolkit's favoured messaging system (Qt -> signals) * - Calling methods of a tool by its GUI is done directly. * In some cases GUIs don't want to be notified by the tool when they cause a change in a tool. * There is a macro CALL_WITHOUT_NOTICE(method()), which will temporarily disable all notifications during a * method call. */ virtual itk::Object::Pointer GetGUI(const std::string &toolkitPrefix, const std::string &toolkitPostfix); virtual NodePredicateBase::ConstPointer GetReferenceDataPreference() const; virtual NodePredicateBase::ConstPointer GetWorkingDataPreference() const; DataNode::Pointer CreateEmptySegmentationNode(const Image *original, const std::string &organName, - const mitk::Color &color); - DataNode::Pointer CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color); + const mitk::Color &color) const; + DataNode::Pointer CreateSegmentationNode(Image *image, const std::string &organName, const mitk::Color &color) const; /** Function used to check if a tool can handle the referenceData and (if specified) the working data. @pre referenceData must be a valid pointer @param referenceData Pointer to the data that should be checked as valid reference for the tool. @param workingData Pointer to the data that should be checked as valid working data for this tool. This parameter can be null if no working data is specified so far.*/ virtual bool CanHandle(const BaseData *referenceData, const BaseData *workingData) const; protected: friend class ToolManager; virtual void SetToolManager(ToolManager *); void ConnectActionsAndFunctions() override; /** \brief Called when the tool gets activated. Derived tools should call their parents implementation at the beginning of the overriding function. */ virtual void Activated(); /** \brief Called when the tool gets deactivated. Derived tools should call their parents implementation at the end of the overriding function. */ virtual void Deactivated(); /** \brief Let subclasses change their event configuration. */ std::string m_EventConfig; - Tool(); // purposely hidden Tool(const char *, const us::Module *interactorModule = nullptr); // purposely hidden ~Tool() override; void Notify(InteractionEvent *interactionEvent, bool isHandled) override; bool FilterEvents(InteractionEvent *, DataNode *) override; ToolManager *m_ToolManager; private: // for reference data NodePredicateDataType::Pointer m_PredicateImages; NodePredicateDimension::Pointer m_PredicateDim3; NodePredicateDimension::Pointer m_PredicateDim4; NodePredicateOr::Pointer m_PredicateDimension; NodePredicateAnd::Pointer m_PredicateImage3D; NodePredicateProperty::Pointer m_PredicateBinary; NodePredicateNot::Pointer m_PredicateNotBinary; NodePredicateProperty::Pointer m_PredicateSegmentation; NodePredicateNot::Pointer m_PredicateNotSegmentation; NodePredicateProperty::Pointer m_PredicateHelper; NodePredicateNot::Pointer m_PredicateNotHelper; NodePredicateAnd::Pointer m_PredicateImageColorful; NodePredicateAnd::Pointer m_PredicateImageColorfulNotHelper; NodePredicateAnd::Pointer m_PredicateReference; // for working data NodePredicateAnd::Pointer m_IsSegmentationPredicate; std::string m_InteractorType; std::map m_DisplayInteractorConfigs; const us::Module *m_InteractorModule; }; } // namespace #endif diff --git a/Modules/Segmentation/files.cmake b/Modules/Segmentation/files.cmake index 3bce31c9b5..e54cc10e02 100644 --- a/Modules/Segmentation/files.cmake +++ b/Modules/Segmentation/files.cmake @@ -1,118 +1,119 @@ set(CPP_FILES Algorithms/mitkCalculateSegmentationVolume.cpp Algorithms/mitkContourModelSetToImageFilter.cpp Algorithms/mitkContourSetToPointSetFilter.cpp Algorithms/mitkContourUtils.cpp Algorithms/mitkCorrectorAlgorithm.cpp Algorithms/mitkDiffImageApplier.cpp Algorithms/mitkDiffSliceOperation.cpp Algorithms/mitkDiffSliceOperationApplier.cpp Algorithms/mitkFeatureBasedEdgeDetectionFilter.cpp Algorithms/mitkImageLiveWireContourModelFilter.cpp Algorithms/mitkImageToContourFilter.cpp #Algorithms/mitkImageToContourModelFilter.cpp Algorithms/mitkImageToLiveWireContourFilter.cpp Algorithms/mitkManualSegmentationToSurfaceFilter.cpp Algorithms/mitkOtsuSegmentationFilter.cpp Algorithms/mitkOverwriteDirectedPlaneImageFilter.cpp Algorithms/mitkOverwriteSliceImageFilter.cpp Algorithms/mitkSegmentationObjectFactory.cpp Algorithms/mitkShapeBasedInterpolationAlgorithm.cpp Algorithms/mitkShowSegmentationAsSmoothedSurface.cpp Algorithms/mitkShowSegmentationAsSurface.cpp Algorithms/mitkVtkImageOverwrite.cpp Controllers/mitkSegmentationInterpolationController.cpp Controllers/mitkToolManager.cpp Controllers/mitkSegmentationModuleActivator.cpp Controllers/mitkToolManagerProvider.cpp DataManagement/mitkContour.cpp DataManagement/mitkContourSet.cpp DataManagement/mitkExtrudedContour.cpp Interactions/mitkAdaptiveRegionGrowingTool.cpp Interactions/mitkAddContourTool.cpp Interactions/mitkAutoCropTool.cpp Interactions/mitkAutoSegmentationTool.cpp Interactions/mitkAutoSegmentationWithPreviewTool.cpp Interactions/mitkAutoMLSegmentationWithPreviewTool.cpp Interactions/mitkBinaryThresholdBaseTool.cpp Interactions/mitkBinaryThresholdTool.cpp Interactions/mitkBinaryThresholdULTool.cpp Interactions/mitkCalculateGrayValueStatisticsTool.cpp Interactions/mitkCalculateVolumetryTool.cpp Interactions/mitkContourModelInteractor.cpp Interactions/mitkContourModelLiveWireInteractor.cpp Interactions/mitkLiveWireTool2D.cpp Interactions/mitkContourTool.cpp Interactions/mitkCorrectorTool2D.cpp Interactions/mitkCreateSurfaceTool.cpp Interactions/mitkDrawPaintbrushTool.cpp Interactions/mitkErasePaintbrushTool.cpp Interactions/mitkEraseRegionTool.cpp + Interactions/mitkFastMarchingBaseTool.cpp Interactions/mitkFastMarchingTool.cpp Interactions/mitkFastMarchingTool3D.cpp Interactions/mitkFeedbackContourTool.cpp Interactions/mitkFillRegionTool.cpp Interactions/mitkOtsuTool3D.cpp Interactions/mitkPaintbrushTool.cpp Interactions/mitkPixelManipulationTool.cpp Interactions/mitkRegionGrowingTool.cpp Interactions/mitkSegmentationsProcessingTool.cpp Interactions/mitkSetRegionTool.cpp Interactions/mitkSegTool2D.cpp Interactions/mitkSubtractContourTool.cpp Interactions/mitkTool.cpp Interactions/mitkToolCommand.cpp Interactions/mitkWatershedTool.cpp Interactions/mitkPickingTool.cpp Interactions/mitkSegmentationInteractor.cpp #SO Rendering/mitkContourMapper2D.cpp Rendering/mitkContourSetMapper2D.cpp Rendering/mitkContourSetVtkMapper3D.cpp Rendering/mitkContourVtkMapper3D.cpp SegmentationUtilities/BooleanOperations/mitkBooleanOperation.cpp SegmentationUtilities/MorphologicalOperations/mitkMorphologicalOperations.cpp #Added from ML Controllers/mitkSliceBasedInterpolationController.cpp Algorithms/mitkSurfaceStampImageFilter.cpp ) set(RESOURCE_FILES Add_48x48.png Add_Cursor_32x32.png Correction_48x48.png Correction_Cursor_32x32.png Erase_48x48.png Erase_Cursor_32x32.png FastMarching_48x48.png FastMarching_Cursor_32x32.png Fill_48x48.png Fill_Cursor_32x32.png LiveWire_48x48.png LiveWire_Cursor_32x32.png Otsu_48x48.png Paint_48x48.png Paint_Cursor_32x32.png Pick_48x48.png RegionGrowing_48x48.png RegionGrowing_Cursor_32x32.png Subtract_48x48.png Subtract_Cursor_32x32.png Threshold_48x48.png TwoThresholds_48x48.png Watershed_48x48.png Watershed_Cursor_32x32.png Wipe_48x48.png Wipe_Cursor_32x32.png Interactions/dummy.xml Interactions/LiveWireTool.xml Interactions/FastMarchingTool.xml Interactions/PressMoveRelease.xml Interactions/PressMoveReleaseAndPointSetting.xml Interactions/PressMoveReleaseWithCTRLInversion.xml Interactions/PressMoveReleaseWithCTRLInversionAllMouseMoves.xml Interactions/SegmentationToolsConfig.xml Interactions/ContourModelModificationConfig.xml Interactions/ContourModelModificationInteractor.xml ) diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp new file mode 100644 index 0000000000..b332866f6f --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp @@ -0,0 +1,95 @@ +/*============================================================================ + +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 "QmitkAutoMLSegmentationToolGUIBase.h" +#include "mitkAutoMLSegmentationWithPreviewTool.h" + +#include + +QmitkAutoMLSegmentationToolGUIBase::QmitkAutoMLSegmentationToolGUIBase() : QmitkAutoSegmentationToolGUIBase(false) +{ + auto enableOtsuDelegate = [this](bool enabled) + { + bool result = enabled; + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + result = !tool->GetSelectedLabels().empty() && enabled; + } + else + { + result = false; + } + + return result; + }; + + m_EnableConfirmSegBtnFnc = enableOtsuDelegate; +} + +void QmitkAutoMLSegmentationToolGUIBase::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) +{ + Superclass::ConnectNewTool(newTool); + this->EnableWidgets(true); +} + +void QmitkAutoMLSegmentationToolGUIBase::InitializeUI(QBoxLayout* mainLayout) +{ + m_LabelSelectionList = new QmitkSimpleLabelSetListWidget(this); + m_LabelSelectionList->setObjectName(QString::fromUtf8("m_LabelSelectionList")); + QSizePolicy sizePolicy2(QSizePolicy::Minimum, QSizePolicy::MinimumExpanding); + sizePolicy2.setHorizontalStretch(0); + sizePolicy2.setVerticalStretch(0); + sizePolicy2.setHeightForWidth(m_LabelSelectionList->sizePolicy().hasHeightForWidth()); + m_LabelSelectionList->setSizePolicy(sizePolicy2); + m_LabelSelectionList->setMaximumSize(QSize(10000000, 10000000)); + + mainLayout->addWidget(m_LabelSelectionList); + + connect(m_LabelSelectionList, &QmitkSimpleLabelSetListWidget::SelectedLabelsChanged, this, &QmitkAutoMLSegmentationToolGUIBase::OnLabelSelectionChanged); + + Superclass::InitializeUI(mainLayout); +} + +void QmitkAutoMLSegmentationToolGUIBase::OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels) +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType labelIDs; + for (const auto& label : selectedLabels) + { + labelIDs.push_back(label->GetValue()); + } + + tool->SetSelectedLabels(labelIDs); + tool->UpdatePreview(); + this->EnableWidgets(true); //used to actualize the ConfirmSeg btn via the delegate; + } +} + +void QmitkAutoMLSegmentationToolGUIBase::EnableWidgets(bool enabled) +{ + Superclass::EnableWidgets(enabled); + if (nullptr != m_LabelSelectionList) + { + m_LabelSelectionList->setEnabled(enabled); + } +} + +void QmitkAutoMLSegmentationToolGUIBase::SetLabelSetPreview(const mitk::LabelSetImage* preview) +{ + if (nullptr != m_LabelSelectionList) + { + m_LabelSelectionList->SetLabelSetImage(preview); + } +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h new file mode 100644 index 0000000000..76bf69cea8 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoMLSegmentationToolGUIBase.h @@ -0,0 +1,53 @@ +/*============================================================================ + +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 QmitkAutoMLSegmentationToolGUIBase_h_Included +#define QmitkAutoMLSegmentationToolGUIBase_h_Included + +#include "QmitkAutoSegmentationToolGUIBase.h" + +#include "QmitkSimpleLabelSetListWidget.h" + +#include + +/** + \ingroup org_mitk_gui_qt_interactivesegmentation_internal + \brief GUI for tools based on mitk::AutoMLSegmentationWithPreviewTool. + + This GUI offers an additional list to select the label that should be confirmed. +*/ +class MITKSEGMENTATIONUI_EXPORT QmitkAutoMLSegmentationToolGUIBase : public QmitkAutoSegmentationToolGUIBase +{ + Q_OBJECT + +public: + mitkClassMacro(QmitkAutoMLSegmentationToolGUIBase, QmitkAutoSegmentationToolGUIBase); + +protected slots : + + void OnLabelSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels); + +protected: + QmitkAutoMLSegmentationToolGUIBase(); + ~QmitkAutoMLSegmentationToolGUIBase() = default; + + void InitializeUI(QBoxLayout* mainLayout) override; + void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; + + void EnableWidgets(bool enabled) override; + void SetLabelSetPreview(const mitk::LabelSetImage* preview); + +private: + QmitkSimpleLabelSetListWidget* m_LabelSelectionList = nullptr; +}; + +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp new file mode 100644 index 0000000000..19e7da115e --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.cpp @@ -0,0 +1,155 @@ +/*============================================================================ + +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 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. As soon as it is sorted out we can remove that "feature switch" + //or the comment. + + 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. As soon as it is sorted out we can remove that "feature switch" + //or the comment. + + 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/QmitkAutoSegmentationToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.h new file mode 100644 index 0000000000..6501a2e1b5 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkAutoSegmentationToolGUIBase.h @@ -0,0 +1,81 @@ +/*============================================================================ + +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 QmitkAutoSegmentationToolGUIBase_h_Included +#define QmitkAutoSegmentationToolGUIBase_h_Included + +#include "QmitkToolGUI.h" + +#include "mitkAutoSegmentationWithPreviewTool.h" + +#include + +class QCheckBox; +class QPushButton; +class QBoxLayout; + +/** + \ingroup org_mitk_gui_qt_interactivesegmentation_internal + \brief GUI base clase for tools derived from mitk::AutoSegmentationTool. +*/ +class MITKSEGMENTATIONUI_EXPORT QmitkAutoSegmentationToolGUIBase : public QmitkToolGUI +{ + Q_OBJECT + +public: + mitkClassMacro(QmitkAutoSegmentationToolGUIBase, QmitkToolGUI); + itkCloneMacro(Self); + + itkGetConstMacro(Mode2D, bool); + +protected slots: + + void OnNewToolAssociated(mitk::Tool *); + + void OnAcceptPreview(); + +protected: + QmitkAutoSegmentationToolGUIBase(bool mode2D); + ~QmitkAutoSegmentationToolGUIBase() override; + + virtual void DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool); + virtual void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool); + virtual void InitializeUI(QBoxLayout* mainLayout); + + void BusyStateChanged(bool isBusy) override; + + using EnableConfirmSegBtnFunctionType = std::function; + EnableConfirmSegBtnFunctionType m_EnableConfirmSegBtnFnc; + + virtual void EnableWidgets(bool enabled); + + template + TTool* GetConnectedToolAs() + { + return dynamic_cast(m_Tool.GetPointer()); + }; + + + +private: + QCheckBox* m_CheckProcessAll = nullptr; + QCheckBox* m_CheckCreateNew = nullptr; + QPushButton* m_ConfirmSegBtn = nullptr; + QBoxLayout* m_MainLayout = nullptr; + + /**Indicates if the tool is in 2D or 3D mode.*/ + bool m_Mode2D; + + mitk::AutoSegmentationWithPreviewTool::Pointer m_Tool; +}; + +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp index 7c720d4a58..fc1cdf2592 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.cpp @@ -1,168 +1,31 @@ /*============================================================================ 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 "QmitkBinaryThresholdToolGUI.h" #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkBinaryThresholdToolGUI, "") QmitkBinaryThresholdToolGUI::QmitkBinaryThresholdToolGUI() - : QmitkToolGUI() + : QmitkBinaryThresholdToolGUIBase(false) { - // create the visible widgets - QBoxLayout *mainLayout = new QVBoxLayout(this); - - QLabel *label = new QLabel("Threshold :", this); - QFont f = label->font(); - f.setBold(false); - label->setFont(f); - mainLayout->addWidget(label); - - QBoxLayout *layout = new QHBoxLayout(); - - m_ThresholdSlider = new ctkSliderWidget(); - connect( - m_ThresholdSlider, SIGNAL(valueChanged(double)), this, SLOT(OnSliderValueChanged(double))); - layout->addWidget(m_ThresholdSlider); - mainLayout->addLayout(layout); - m_ThresholdSlider->setSingleStep(0.01); - - QPushButton *okButton = new QPushButton("Confirm Segmentation", this); - connect(okButton, SIGNAL(clicked()), this, SLOT(OnAcceptThresholdPreview())); - okButton->setFont(f); - mainLayout->addWidget(okButton); - - 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."); - - mainLayout->addWidget(m_CheckProcessAll); - - 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."); - mainLayout->addWidget(m_CheckCreateNew); - - connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkBinaryThresholdToolGUI::~QmitkBinaryThresholdToolGUI() { - if (m_BinaryThresholdTool.IsNotNull()) - { - m_BinaryThresholdTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); - m_BinaryThresholdTool->IntervalBordersChanged -= - mitk::MessageDelegate3( - this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); - m_BinaryThresholdTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( - this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); - } } -void QmitkBinaryThresholdToolGUI::OnNewToolAssociated(mitk::Tool *tool) -{ - if (m_BinaryThresholdTool.IsNotNull()) - { - m_BinaryThresholdTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); - m_BinaryThresholdTool->IntervalBordersChanged -= - mitk::MessageDelegate3( - this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); - m_BinaryThresholdTool->ThresholdingValuesChanged -= mitk::MessageDelegate2( - this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); - } - - m_BinaryThresholdTool = dynamic_cast(tool); - - if (m_BinaryThresholdTool.IsNotNull()) - { - m_BinaryThresholdTool->CurrentlyBusy += - mitk::MessageDelegate1(this, &QmitkBinaryThresholdToolGUI::BusyStateChanged); - m_BinaryThresholdTool->IntervalBordersChanged += - mitk::MessageDelegate3( - this, &QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged); - m_BinaryThresholdTool->ThresholdingValuesChanged += mitk::MessageDelegate2( - this, &QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged); - - m_BinaryThresholdTool->SetOverwriteExistingSegmentation(true); - m_CheckProcessAll->setVisible(m_BinaryThresholdTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps()>1); - } -} - -void QmitkBinaryThresholdToolGUI::OnAcceptThresholdPreview() -{ - if (m_BinaryThresholdTool.IsNotNull()) - { - if (m_CheckCreateNew->isChecked()) - { - m_BinaryThresholdTool->SetOverwriteExistingSegmentation(false); - } - else - { - m_BinaryThresholdTool->SetOverwriteExistingSegmentation(true); - } - - m_BinaryThresholdTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); - - this->thresholdAccepted(); - m_BinaryThresholdTool->ConfirmSegmentation(); - } -} - -void QmitkBinaryThresholdToolGUI::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) -{ - m_InternalUpdate = true; - if (!isFloat) - { - m_ThresholdSlider->setRange(int(lower), int(upper)); - m_ThresholdSlider->setSingleStep(1); - m_ThresholdSlider->setDecimals(0); - } - else - { - m_ThresholdSlider->setRange(lower, upper); - } - m_InternalUpdate = false; -} - -void QmitkBinaryThresholdToolGUI::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType /*upper*/) -{ - m_ThresholdSlider->setValue(lower); -} - -void QmitkBinaryThresholdToolGUI::OnSliderValueChanged(double value) -{ - if (m_BinaryThresholdTool.IsNotNull() && !m_InternalUpdate) - { - m_BinaryThresholdTool->SetThresholdValue(value); - } -} - -void QmitkBinaryThresholdToolGUI::BusyStateChanged(bool value) -{ - if (value) - { - QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - } - else - { - QApplication::restoreOverrideCursor(); - } - - m_ThresholdSlider->setEnabled(!value); -} diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h index 5696c1ff98..e62d8fc8fc 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUI.h @@ -1,80 +1,50 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ #ifndef QmitkBinaryThresholdToolGUI_h_Included #define QmitkBinaryThresholdToolGUI_h_Included -#include "QmitkToolGUI.h" +#include "QmitkBinaryThresholdToolGUIBase.h" #include "mitkBinaryThresholdTool.h" #include #include "ctkSliderWidget.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::BinaryThresholdTool. This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. There is only a slider for INT values in QT. So, if the working image has a float/double pixeltype, we need to convert the original float intensity into a respective int value for the slider. The slider range is then between 0 and 99. If the pixeltype is INT, then we do not need any conversion. Last contributor: $Author$ */ -class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdToolGUI : public QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdToolGUI : public QmitkBinaryThresholdToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkBinaryThresholdToolGUI, QmitkToolGUI); + mitkClassMacro(QmitkBinaryThresholdToolGUI, QmitkBinaryThresholdToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); - void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); - -signals: - - /// \brief Emitted when threshold Accepted - void thresholdAccepted(); - - /// \brief Emitted when threshold Canceled - void thresholdCanceled(); - -public slots: - -protected slots: - - void OnNewToolAssociated(mitk::Tool *); - void OnAcceptThresholdPreview(); - - void OnSliderValueChanged(double value); - protected: QmitkBinaryThresholdToolGUI(); ~QmitkBinaryThresholdToolGUI() override; - - void BusyStateChanged(bool) override; - - ctkSliderWidget* m_ThresholdSlider = nullptr; - QCheckBox* m_CheckProcessAll = nullptr; - QCheckBox* m_CheckCreateNew = nullptr; - - bool m_InternalUpdate = false; - - mitk::BinaryThresholdTool::Pointer m_BinaryThresholdTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.cpp new file mode 100644 index 0000000000..930eacadb7 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.cpp @@ -0,0 +1,187 @@ +/*============================================================================ + +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 "QmitkBinaryThresholdToolGUIBase.h" + +#include "mitkBinaryThresholdBaseTool.h" +#include "mitkBinaryThresholdTool.h" + +#include +#include +#include +#include + +QmitkBinaryThresholdToolGUIBase::QmitkBinaryThresholdToolGUIBase(bool ulMode) : QmitkAutoSegmentationToolGUIBase(false), m_ULMode(ulMode) +{ +} + +QmitkBinaryThresholdToolGUIBase::~QmitkBinaryThresholdToolGUIBase() +{ + auto tool = this->GetConnectedToolAs(); + + if (nullptr != tool) + { + tool->IntervalBordersChanged -= + mitk::MessageDelegate3( + this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingIntervalBordersChanged); + tool->ThresholdingValuesChanged -= + mitk::MessageDelegate2( + this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingValuesChanged); + } +} + +void QmitkBinaryThresholdToolGUIBase::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) +{ + m_InternalUpdate = true; + + if (m_ULMode) + { + if (!isFloat) + { + m_ThresholdRange->setRange(int(lower), int(upper)); + m_ThresholdRange->setSingleStep(1); + m_ThresholdRange->setDecimals(0); + } + else + { + m_ThresholdRange->setRange(lower, upper); + } + } + else + { + if (!isFloat) + { + m_ThresholdSlider->setRange(int(lower), int(upper)); + m_ThresholdSlider->setSingleStep(1); + m_ThresholdSlider->setDecimals(0); + } + else + { + m_ThresholdSlider->setRange(lower, upper); + } + } + + m_InternalUpdate = false; +} + +void QmitkBinaryThresholdToolGUIBase::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper) +{ + if (m_ULMode) + { + m_ThresholdRange->setValues(lower, upper); + } + else + { + m_ThresholdSlider->setValue(lower); + } +} + +void QmitkBinaryThresholdToolGUIBase::OnThresholdRangeChanged(double min, double max) +{ + auto tool = this->GetConnectedToolAs(); + + if (nullptr != tool && !m_InternalUpdate) + { + tool->SetThresholdValues(min, max); + } +} + +void QmitkBinaryThresholdToolGUIBase::OnThresholdSliderChanged(double value) +{ + auto tool = this->GetConnectedToolAs(); + + if (nullptr != tool && !m_InternalUpdate) + { + tool->SetThresholdValue(value); + } +} + +void QmitkBinaryThresholdToolGUIBase::DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool) +{ + Superclass::DisconnectOldTool(oldTool); + + auto tool = dynamic_cast(oldTool); + + if (nullptr != tool) + { + tool->IntervalBordersChanged -= + mitk::MessageDelegate3( + this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingIntervalBordersChanged); + tool->ThresholdingValuesChanged -= + mitk::MessageDelegate2( + this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingValuesChanged); + } +} + +void QmitkBinaryThresholdToolGUIBase::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) +{ + Superclass::ConnectNewTool(newTool); + + auto tool = dynamic_cast(newTool); + + if (nullptr != tool) + { + tool->IntervalBordersChanged += + mitk::MessageDelegate3( + this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingIntervalBordersChanged); + tool->ThresholdingValuesChanged += + mitk::MessageDelegate2( + this, &QmitkBinaryThresholdToolGUIBase::OnThresholdingValuesChanged); + } +} + +void QmitkBinaryThresholdToolGUIBase::InitializeUI(QBoxLayout* mainLayout) +{ + QLabel* label = new QLabel("Threshold :", this); + QFont f = label->font(); + f.setBold(false); + label->setFont(f); + mainLayout->addWidget(label); + + QBoxLayout* layout = new QHBoxLayout(); + + if (m_ULMode) + { + m_ThresholdRange = new ctkRangeWidget(); + connect( + m_ThresholdRange, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdRangeChanged(double, double))); + layout->addWidget(m_ThresholdRange); + m_ThresholdRange->setSingleStep(0.01); + } + else + { + m_ThresholdSlider = new ctkSliderWidget(); + connect( + m_ThresholdSlider, SIGNAL(valueChanged(double)), this, SLOT(OnThresholdSliderChanged(double))); + layout->addWidget(m_ThresholdSlider); + m_ThresholdSlider->setSingleStep(0.01); + } + + mainLayout->addLayout(layout); + + Superclass::InitializeUI(mainLayout); +} + +void QmitkBinaryThresholdToolGUIBase::BusyStateChanged(bool value) +{ + Superclass::BusyStateChanged(value); + + if (m_ThresholdRange) + { + m_ThresholdRange->setEnabled(!value); + } + + if (m_ThresholdSlider) + { + m_ThresholdSlider->setEnabled(!value); + } +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.h new file mode 100644 index 0000000000..47f25dd544 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdToolGUIBase.h @@ -0,0 +1,63 @@ +/*============================================================================ + +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 QmitkBinaryThresholdToolGUIBase_h_Included +#define QmitkBinaryThresholdToolGUIBase_h_Included + +#include "QmitkAutoSegmentationToolGUIBase.h" +#include "ctkRangeWidget.h" +#include "ctkSliderWidget.h" + +#include + +/** + \ingroup org_mitk_gui_qt_interactivesegmentation_internal + \brief Base GUI for mitk::BinaryThresholdTool. + + This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. +*/ +class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdToolGUIBase : public QmitkAutoSegmentationToolGUIBase +{ + Q_OBJECT + +public: + mitkClassMacro(QmitkBinaryThresholdToolGUIBase, QmitkAutoSegmentationToolGUIBase); + + void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); + void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); + +protected slots: + + void OnThresholdRangeChanged(double min, double max); + void OnThresholdSliderChanged(double value); + +protected: + QmitkBinaryThresholdToolGUIBase(bool ulMode); + ~QmitkBinaryThresholdToolGUIBase() override; + + void DisconnectOldTool(mitk::AutoSegmentationWithPreviewTool* oldTool) override; + void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; + void InitializeUI(QBoxLayout* mainLayout) override; + + void BusyStateChanged(bool) override; + + ctkRangeWidget* m_ThresholdRange = nullptr; + ctkSliderWidget* m_ThresholdSlider = nullptr; + + /** Indicates if the tool UI is used for a tool with upper an lower threshold (true) + ore only with one threshold (false)*/ + bool m_ULMode; + + bool m_InternalUpdate = false; +}; + +#endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp index 5954acff4c..1eab27a6fb 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.cpp @@ -1,163 +1,30 @@ /*============================================================================ 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 "QmitkBinaryThresholdULToolGUI.h" #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkBinaryThresholdULToolGUI, "") -QmitkBinaryThresholdULToolGUI::QmitkBinaryThresholdULToolGUI() : QmitkToolGUI() +QmitkBinaryThresholdULToolGUI::QmitkBinaryThresholdULToolGUI() : QmitkBinaryThresholdToolGUIBase(true) { - // create the visible widgets - QBoxLayout *mainLayout = new QVBoxLayout(this); - - QLabel *label = new QLabel("Threshold :", this); - QFont f = label->font(); - f.setBold(false); - label->setFont(f); - mainLayout->addWidget(label); - - QBoxLayout *layout = new QHBoxLayout(); - - m_DoubleThresholdSlider = new ctkRangeWidget(); - connect( - m_DoubleThresholdSlider, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdsChanged(double, double))); - layout->addWidget(m_DoubleThresholdSlider); - mainLayout->addLayout(layout); - m_DoubleThresholdSlider->setSingleStep(0.01); - - QPushButton *okButton = new QPushButton("Confirm Segmentation", this); - connect(okButton, SIGNAL(clicked()), this, SLOT(OnAcceptThresholdPreview())); - okButton->setFont(f); - mainLayout->addWidget(okButton); - - 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."); - mainLayout->addWidget(m_CheckProcessAll); - - 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."); - mainLayout->addWidget(m_CheckCreateNew); - - connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); } QmitkBinaryThresholdULToolGUI::~QmitkBinaryThresholdULToolGUI() { - if (m_BinaryThresholdULTool.IsNotNull()) - { - m_BinaryThresholdULTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); - m_BinaryThresholdULTool->IntervalBordersChanged -= - mitk::MessageDelegate3( - this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); - m_BinaryThresholdULTool->ThresholdingValuesChanged -= - mitk::MessageDelegate2( - this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); - } -} - -void QmitkBinaryThresholdULToolGUI::OnNewToolAssociated(mitk::Tool *tool) -{ - if (m_BinaryThresholdULTool.IsNotNull()) - { - m_BinaryThresholdULTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); - m_BinaryThresholdULTool->IntervalBordersChanged -= - mitk::MessageDelegate3( - this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); - m_BinaryThresholdULTool->ThresholdingValuesChanged -= - mitk::MessageDelegate2( - this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); - } - - m_BinaryThresholdULTool = dynamic_cast(tool); - - if (m_BinaryThresholdULTool.IsNotNull()) - { - m_BinaryThresholdULTool->CurrentlyBusy += - mitk::MessageDelegate1(this, &QmitkBinaryThresholdULToolGUI::BusyStateChanged); - m_BinaryThresholdULTool->IntervalBordersChanged += - mitk::MessageDelegate3( - this, &QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged); - m_BinaryThresholdULTool->ThresholdingValuesChanged += - mitk::MessageDelegate2( - this, &QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged); - - m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(true); - m_CheckProcessAll->setVisible(m_BinaryThresholdULTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); - } -} - -void QmitkBinaryThresholdULToolGUI::OnAcceptThresholdPreview() -{ - if (m_BinaryThresholdULTool.IsNotNull()) - { - if (m_CheckCreateNew->isChecked()) - { - m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(false); - } - else - { - m_BinaryThresholdULTool->SetOverwriteExistingSegmentation(true); - } - - m_BinaryThresholdULTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); - - m_BinaryThresholdULTool->ConfirmSegmentation(); - } -} - -void QmitkBinaryThresholdULToolGUI::OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat) -{ - if (!isFloat) - { - m_DoubleThresholdSlider->setRange(int(lower), int(upper)); - m_DoubleThresholdSlider->setSingleStep(1); - m_DoubleThresholdSlider->setDecimals(0); - } - else - { - m_DoubleThresholdSlider->setRange(lower, upper); - } } -void QmitkBinaryThresholdULToolGUI::OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper) -{ - m_DoubleThresholdSlider->setValues(lower, upper); -} - -void QmitkBinaryThresholdULToolGUI::OnThresholdsChanged(double min, double max) -{ - m_BinaryThresholdULTool->SetThresholdValues(min, max); -} - -void QmitkBinaryThresholdULToolGUI::BusyStateChanged(bool value) -{ - if (value) - { - QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - } - else - { - QApplication::restoreOverrideCursor(); - } - - m_DoubleThresholdSlider->setEnabled(!value); -} diff --git a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h index ec0c130821..b903df1e58 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkBinaryThresholdULToolGUI.h @@ -1,68 +1,42 @@ /*============================================================================ 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 QmitkBinaryThresholdULToolGUI_h_Included #define QmitkBinaryThresholdULToolGUI_h_Included -#include "QmitkToolGUI.h" -#include "ctkRangeWidget.h" -#include -#include "mitkBinaryThresholdULTool.h" +#include "QmitkBinaryThresholdToolGUIBase.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::BinaryThresholdTool. This GUI shows a slider to change the tool's threshold and an OK button to accept a preview for actual thresholding. Last contributor: $Author$ */ -class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdULToolGUI : public QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkBinaryThresholdULToolGUI : public QmitkBinaryThresholdToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkBinaryThresholdULToolGUI, QmitkToolGUI); + mitkClassMacro(QmitkBinaryThresholdULToolGUI, QmitkBinaryThresholdToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - void OnThresholdingIntervalBordersChanged(double lower, double upper, bool isFloat); - void OnThresholdingValuesChanged(mitk::ScalarType lower, mitk::ScalarType upper); - -signals: - -public slots: - -protected slots: - - void OnNewToolAssociated(mitk::Tool *); - - void OnAcceptThresholdPreview(); - - void OnThresholdsChanged(double min, double max); - protected: QmitkBinaryThresholdULToolGUI(); ~QmitkBinaryThresholdULToolGUI() override; - - void BusyStateChanged(bool) override; - - ctkRangeWidget *m_DoubleThresholdSlider; - QCheckBox* m_CheckProcessAll = nullptr; - QCheckBox* m_CheckCreateNew = nullptr; - - mitk::BinaryThresholdULTool::Pointer m_BinaryThresholdULTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp index b83f50a5eb..165cd8bde0 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.cpp @@ -1,361 +1,20 @@ /*============================================================================ 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 "QmitkFastMarchingTool3DGUI.h" -#include "QmitkConfirmSegmentationDialog.h" - -#include "mitkBaseRenderer.h" -#include "mitkStepper.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkFastMarchingTool3DGUI, "") -QmitkFastMarchingTool3DGUI::QmitkFastMarchingTool3DGUI() : QmitkToolGUI() -{ - this->setContentsMargins(0, 0, 0, 0); - - // create the visible widgets - QVBoxLayout *widgetLayout = new QVBoxLayout(this); - widgetLayout->setContentsMargins(0, 0, 0, 0); - - QFont fntHelp; - fntHelp.setBold(true); - - QLabel *lblHelp = new QLabel(this); - lblHelp->setText("Press shift-click to add seeds repeatedly."); - lblHelp->setFont(fntHelp); - - widgetLayout->addWidget(lblHelp); - - // Sigma controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Sigma: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addItem(hlayout); - } - - m_slSigma = new ctkSliderWidget(this); - m_slSigma->setMinimum(0.1); - m_slSigma->setMaximum(5.0); - m_slSigma->setPageStep(0.1); - m_slSigma->setSingleStep(0.01); - m_slSigma->setValue(1.0); - m_slSigma->setTracking(false); - m_slSigma->setToolTip("The \"sigma\" parameter in the Gradient Magnitude filter."); - connect(m_slSigma, SIGNAL(valueChanged(double)), this, SLOT(OnSigmaChanged(double))); - widgetLayout->addWidget(m_slSigma); - - // Alpha controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Alpha: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addItem(hlayout); - } - - m_slAlpha = new ctkSliderWidget(this); - m_slAlpha->setMinimum(-10); - m_slAlpha->setMaximum(0); - m_slAlpha->setPageStep(0.1); - m_slAlpha->setSingleStep(0.01); - m_slAlpha->setValue(-2.5); - m_slAlpha->setTracking(false); - m_slAlpha->setToolTip("The \"alpha\" parameter in the Sigmoid mapping filter."); - connect(m_slAlpha, SIGNAL(valueChanged(double)), this, SLOT(OnAlphaChanged(double))); - widgetLayout->addWidget(m_slAlpha); - - // Beta controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Beta: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addLayout(hlayout); - } - - m_slBeta = new ctkSliderWidget(this); - m_slBeta->setMinimum(0); - m_slBeta->setMaximum(100); - m_slBeta->setPageStep(0.1); - m_slBeta->setSingleStep(0.01); - m_slBeta->setValue(3.5); - m_slBeta->setTracking(false); - m_slBeta->setToolTip("The \"beta\" parameter in the Sigmoid mapping filter."); - connect(m_slBeta, SIGNAL(valueChanged(double)), this, SLOT(OnBetaChanged(double))); - widgetLayout->addWidget(m_slBeta); - - // stopping value controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Stopping value: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addLayout(hlayout); - } - - m_slStoppingValue = new ctkSliderWidget(this); - m_slStoppingValue->setMinimum(0); - m_slStoppingValue->setMaximum(10000); - m_slStoppingValue->setPageStep(10); - m_slStoppingValue->setSingleStep(1); - m_slStoppingValue->setValue(2000); - m_slStoppingValue->setDecimals(0); - m_slStoppingValue->setTracking(false); - m_slStoppingValue->setToolTip("The \"stopping value\" parameter in the fast marching 3D algorithm"); - connect(m_slStoppingValue, SIGNAL(valueChanged(double)), this, SLOT(OnStoppingValueChanged(double))); - widgetLayout->addWidget(m_slStoppingValue); - - // threshold controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Threshold: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addLayout(hlayout); - } - - m_slwThreshold = new ctkRangeWidget(this); - m_slwThreshold->setMinimum(-100); - m_slwThreshold->setMaximum(5000); - m_slwThreshold->setMinimumValue(-100); - m_slwThreshold->setMaximumValue(2000); - m_slwThreshold->setDecimals(0); - m_slwThreshold->setTracking(false); - m_slwThreshold->setToolTip("The lower and upper thresholds for the final thresholding"); - connect(m_slwThreshold, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdChanged(double, double))); - widgetLayout->addWidget(m_slwThreshold); - - m_btClearSeeds = new QPushButton("Clear"); - m_btClearSeeds->setToolTip("Clear current result and start over again"); - m_btClearSeeds->setEnabled(false); - widgetLayout->addWidget(m_btClearSeeds); - connect(m_btClearSeeds, SIGNAL(clicked()), this, SLOT(OnClearSeeds())); - - m_btConfirm = new QPushButton("Confirm Segmentation"); - m_btConfirm->setToolTip("Incorporate current result in your working session."); - m_btConfirm->setEnabled(false); - widgetLayout->addWidget(m_btConfirm); - connect(m_btConfirm, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); - - 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."); - widgetLayout->addWidget(m_CheckProcessAll); - - 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."); - widgetLayout->addWidget(m_CheckCreateNew); - - connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); - - m_slSigma->setDecimals(2); - m_slBeta->setDecimals(2); - m_slAlpha->setDecimals(2); - - this->EnableWidgets(false); -} - -QmitkFastMarchingTool3DGUI::~QmitkFastMarchingTool3DGUI() -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - } -} - -void QmitkFastMarchingTool3DGUI::OnNewToolAssociated(mitk::Tool *tool) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - } - - m_FastMarchingTool = dynamic_cast(tool); - - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->CurrentlyBusy += - mitk::MessageDelegate1(this, &QmitkFastMarchingTool3DGUI::BusyStateChanged); - - m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); - m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); - m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); - m_FastMarchingTool->SetSigma(m_slSigma->value()); - m_FastMarchingTool->SetAlpha(m_slAlpha->value()); - m_FastMarchingTool->SetBeta(m_slBeta->value()); - m_FastMarchingTool->SetOverwriteExistingSegmentation(true); - m_FastMarchingTool->ClearSeeds(); - m_CheckProcessAll->setVisible(m_FastMarchingTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); - } -} - -void QmitkFastMarchingTool3DGUI::Update() -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetLowerThreshold(m_slwThreshold->minimumValue()); - m_FastMarchingTool->SetUpperThreshold(m_slwThreshold->maximumValue()); - m_FastMarchingTool->SetStoppingValue(m_slStoppingValue->value()); - m_FastMarchingTool->SetSigma(m_slSigma->value()); - m_FastMarchingTool->SetAlpha(m_slAlpha->value()); - m_FastMarchingTool->SetBeta(m_slBeta->value()); - m_FastMarchingTool->UpdatePreview(); - } -} - -void QmitkFastMarchingTool3DGUI::OnThresholdChanged(double lower, double upper) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetLowerThreshold(lower); - m_FastMarchingTool->SetUpperThreshold(upper); - this->Update(); - } -} - -void QmitkFastMarchingTool3DGUI::OnBetaChanged(double value) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetBeta(value); - this->Update(); - } -} - -void QmitkFastMarchingTool3DGUI::OnSigmaChanged(double value) +QmitkFastMarchingTool3DGUI::QmitkFastMarchingTool3DGUI() : QmitkFastMarchingToolGUIBase(false) { - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetSigma(value); - this->Update(); - } } -void QmitkFastMarchingTool3DGUI::OnAlphaChanged(double value) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetAlpha(value); - this->Update(); - } -} - -void QmitkFastMarchingTool3DGUI::OnStoppingValueChanged(double value) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetStoppingValue(value); - this->Update(); - } -} - -void QmitkFastMarchingTool3DGUI::OnConfirmSegmentation() -{ - if (m_FastMarchingTool.IsNotNull()) - { - if (m_CheckCreateNew->isChecked()) - { - m_FastMarchingTool->SetOverwriteExistingSegmentation(false); - } - else - { - m_FastMarchingTool->SetOverwriteExistingSegmentation(true); - } - - m_FastMarchingTool->SetCreateAllTimeSteps(m_CheckProcessAll->isChecked()); - - m_btConfirm->setEnabled(false); - m_FastMarchingTool->ConfirmSegmentation(); - } -} - -void QmitkFastMarchingTool3DGUI::OnClearSeeds() -{ - // event from image navigator recieved - timestep has changed - m_FastMarchingTool->ClearSeeds(); - m_btClearSeeds->setEnabled(false); - m_btConfirm->setEnabled(false); - this->EnableWidgets(false); - this->Update(); -} - -void QmitkFastMarchingTool3DGUI::BusyStateChanged(bool value) -{ - if (value) - { - QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - this->EnableWidgets(false); - } - else - { - QApplication::restoreOverrideCursor(); - this->EnableWidgets(true); - } -} - -void QmitkFastMarchingTool3DGUI::EnableWidgets(bool enable) -{ - m_slSigma->setEnabled(enable); - m_slAlpha->setEnabled(enable); - m_slBeta->setEnabled(enable); - m_slStoppingValue->setEnabled(enable); - m_slwThreshold->setEnabled(enable); - m_btClearSeeds->setEnabled(enable); - m_btConfirm->setEnabled(enable); - m_CheckCreateNew->setEnabled(enable); - m_CheckProcessAll->setEnabled(enable); -} diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h index a02b18de12..42f2c60ecb 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h @@ -1,79 +1,38 @@ /*============================================================================ 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 QmitkFastMarchingTool3DGUI_h_Included #define QmitkFastMarchingTool3DGUI_h_Included -#include "QmitkToolGUI.h" -#include "mitkFastMarchingTool3D.h" +#include "QmitkFastMarchingToolGUIBase.h" #include -class ctkSliderWidget; -class ctkRangeWidget; -class QPushButton; -class QCheckBox; - /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal -\brief GUI for mitk::FastMarchingTool. +\brief GUI for mitk::FastMarchingTool3D. \sa mitk::FastMarchingTool */ -class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingTool3DGUI : public QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingTool3DGUI : public QmitkFastMarchingToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkFastMarchingTool3DGUI, QmitkToolGUI); + mitkClassMacro(QmitkFastMarchingTool3DGUI, QmitkFastMarchingToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - void OnThresholdChanged(int current); - -protected slots: - - void OnNewToolAssociated(mitk::Tool *); - - void OnThresholdChanged(double, double); - void OnAlphaChanged(double); - void OnBetaChanged(double); - void OnSigmaChanged(double); - void OnStoppingValueChanged(double); - void OnConfirmSegmentation(); - void OnClearSeeds(); - protected: QmitkFastMarchingTool3DGUI(); - ~QmitkFastMarchingTool3DGUI() override; - - void BusyStateChanged(bool) override; - - void Update(); - - ctkRangeWidget *m_slwThreshold; - ctkSliderWidget *m_slStoppingValue; - ctkSliderWidget *m_slSigma; - ctkSliderWidget *m_slAlpha; - ctkSliderWidget *m_slBeta; - - QPushButton *m_btConfirm; - QPushButton *m_btClearSeeds; - - QCheckBox* m_CheckProcessAll = nullptr; - QCheckBox* m_CheckCreateNew = nullptr; - - mitk::FastMarchingTool3D::Pointer m_FastMarchingTool; - -private: - void EnableWidgets(bool); + ~QmitkFastMarchingTool3DGUI() = default; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp index f02ff0523b..08670906f7 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.cpp @@ -1,350 +1,31 @@ /*============================================================================ 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 "QmitkFastMarchingToolGUI.h" #include "QmitkNewSegmentationDialog.h" #include "mitkBaseRenderer.h" #include "mitkStepper.h" #include #include #include #include #include #include #include #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkFastMarchingToolGUI, "") -QmitkFastMarchingToolGUI::QmitkFastMarchingToolGUI() : QmitkToolGUI(), m_TimeIsConnected(false) -{ - this->setContentsMargins(0, 0, 0, 0); - - // create the visible widgets - QVBoxLayout *widgetLayout = new QVBoxLayout(this); - widgetLayout->setContentsMargins(0, 0, 0, 0); - - QFont fntHelp; - fntHelp.setBold(true); - - QLabel *lblHelp = new QLabel(this); - lblHelp->setText("Press shift-click to add seeds repeatedly."); - lblHelp->setFont(fntHelp); - - widgetLayout->addWidget(lblHelp); - - // Sigma controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Sigma: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addItem(hlayout); - } - - m_slSigma = new ctkSliderWidget(this); - m_slSigma->setMinimum(0.1); - m_slSigma->setMaximum(5.0); - m_slSigma->setPageStep(0.1); - m_slSigma->setSingleStep(0.01); - m_slSigma->setValue(1.0); - m_slSigma->setTracking(false); - m_slSigma->setToolTip("The \"sigma\" parameter in the Gradient Magnitude filter."); - connect(m_slSigma, SIGNAL(valueChanged(double)), this, SLOT(OnSigmaChanged(double))); - widgetLayout->addWidget(m_slSigma); - - // Alpha controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Alpha: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addItem(hlayout); - } - - m_slAlpha = new ctkSliderWidget(this); - m_slAlpha->setMinimum(-10); - m_slAlpha->setMaximum(0); - m_slAlpha->setPageStep(0.1); - m_slAlpha->setSingleStep(0.01); - m_slAlpha->setValue(-2.5); - m_slAlpha->setTracking(false); - m_slAlpha->setToolTip("The \"alpha\" parameter in the Sigmoid mapping filter."); - connect(m_slAlpha, SIGNAL(valueChanged(double)), this, SLOT(OnAlphaChanged(double))); - widgetLayout->addWidget(m_slAlpha); - - // Beta controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Beta: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addLayout(hlayout); - } - - m_slBeta = new ctkSliderWidget(this); - m_slBeta->setMinimum(0); - m_slBeta->setMaximum(100); - m_slBeta->setPageStep(0.1); - m_slBeta->setSingleStep(0.01); - m_slBeta->setValue(3.5); - m_slBeta->setTracking(false); - m_slBeta->setToolTip("The \"beta\" parameter in the Sigmoid mapping filter."); - connect(m_slBeta, SIGNAL(valueChanged(double)), this, SLOT(OnBetaChanged(double))); - widgetLayout->addWidget(m_slBeta); - - // stopping value controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Stopping value: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addLayout(hlayout); - } - - m_slStoppingValue = new ctkSliderWidget(this); - m_slStoppingValue->setMinimum(0); - m_slStoppingValue->setMaximum(10000); - m_slStoppingValue->setPageStep(10); - m_slStoppingValue->setSingleStep(1); - m_slStoppingValue->setValue(2000); - m_slStoppingValue->setDecimals(0); - m_slStoppingValue->setTracking(false); - m_slStoppingValue->setToolTip("The \"stopping value\" parameter in the fast marching 3D algorithm"); - connect(m_slStoppingValue, SIGNAL(valueChanged(double)), this, SLOT(OnStoppingValueChanged(double))); - widgetLayout->addWidget(m_slStoppingValue); - - // threshold controls - { - QHBoxLayout *hlayout = new QHBoxLayout(); - hlayout->setSpacing(2); - - QLabel *lbl = new QLabel(this); - lbl->setText("Threshold: "); - hlayout->addWidget(lbl); - - QSpacerItem *sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); - hlayout->addItem(sp2); - - widgetLayout->addLayout(hlayout); - } - - m_slwThreshold = new ctkRangeWidget(this); - m_slwThreshold->setMinimum(-100); - m_slwThreshold->setMaximum(5000); - m_slwThreshold->setMinimumValue(-100); - m_slwThreshold->setMaximumValue(2000); - m_slwThreshold->setDecimals(0); - m_slwThreshold->setTracking(false); - m_slwThreshold->setToolTip("The lower and upper thresholds for the final thresholding"); - connect(m_slwThreshold, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdChanged(double, double))); - widgetLayout->addWidget(m_slwThreshold); - - m_btClearSeeds = new QPushButton("Clear"); - m_btClearSeeds->setToolTip("Clear current result and start over again"); - m_btClearSeeds->setEnabled(false); - widgetLayout->addWidget(m_btClearSeeds); - connect(m_btClearSeeds, SIGNAL(clicked()), this, SLOT(OnClearSeeds())); - - m_btConfirm = new QPushButton("Confirm Segmentation"); - m_btConfirm->setToolTip("Incorporate current result in your working session."); - m_btConfirm->setEnabled(false); - widgetLayout->addWidget(m_btConfirm); - connect(m_btConfirm, SIGNAL(clicked()), this, SLOT(OnConfirmSegmentation())); - - connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); - - m_slAlpha->setDecimals(2); - m_slSigma->setDecimals(2); - m_slBeta->setDecimals(2); - - this->EnableWidgets(false); -} - -QmitkFastMarchingToolGUI::~QmitkFastMarchingToolGUI() -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkFastMarchingToolGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingToolGUI::OnFastMarchingToolReady)); - } -} - -void QmitkFastMarchingToolGUI::OnNewToolAssociated(mitk::Tool *tool) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkFastMarchingToolGUI::BusyStateChanged); - m_FastMarchingTool->RemoveReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingToolGUI::OnFastMarchingToolReady)); - } - - m_FastMarchingTool = dynamic_cast(tool); - - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->CurrentlyBusy += - mitk::MessageDelegate1(this, &QmitkFastMarchingToolGUI::BusyStateChanged); - m_FastMarchingTool->AddReadyListener( - mitk::MessageDelegate(this, &QmitkFastMarchingToolGUI::OnFastMarchingToolReady)); - - // listen to timestep change events - mitk::BaseRenderer::Pointer renderer; - renderer = mitk::BaseRenderer::GetInstance(mitk::BaseRenderer::GetRenderWindowByName("stdmulti.widget0")); - if (renderer.IsNotNull() && !m_TimeIsConnected) - { - new QmitkStepperAdapter(this, renderer->GetSliceNavigationController()->GetTime(), "stepper"); - // connect(m_TimeStepper, SIGNAL(Refetch()), this, SLOT(Refetch())); - m_TimeIsConnected = true; - } - } -} - -void QmitkFastMarchingToolGUI::Update() -{ - m_FastMarchingTool->SetLowerThreshold(this->m_slwThreshold->minimumValue()); - m_FastMarchingTool->SetUpperThreshold(this->m_slwThreshold->maximumValue()); - m_FastMarchingTool->SetStoppingValue(this->m_slStoppingValue->value()); - m_FastMarchingTool->SetSigma(this->m_slSigma->value()); - m_FastMarchingTool->SetAlpha(this->m_slAlpha->value()); - m_FastMarchingTool->SetBeta(this->m_slBeta->value()); - m_FastMarchingTool->Update(); -} - -void QmitkFastMarchingToolGUI::OnThresholdChanged(double lower, double upper) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetLowerThreshold(lower); - m_FastMarchingTool->SetUpperThreshold(upper); - this->Update(); - } -} - -void QmitkFastMarchingToolGUI::OnBetaChanged(double value) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetBeta(value); - this->Update(); - } -} - -void QmitkFastMarchingToolGUI::OnSigmaChanged(double value) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetSigma(value); - this->Update(); - } -} - -void QmitkFastMarchingToolGUI::OnAlphaChanged(double value) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetAlpha(value); - this->Update(); - } -} - -void QmitkFastMarchingToolGUI::OnStoppingValueChanged(double value) -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_FastMarchingTool->SetStoppingValue(value); - this->Update(); - } -} - -void QmitkFastMarchingToolGUI::OnConfirmSegmentation() -{ - if (m_FastMarchingTool.IsNotNull()) - { - m_btConfirm->setEnabled(false); - m_FastMarchingTool->ConfirmSegmentation(); - } -} - -void QmitkFastMarchingToolGUI::SetStepper(mitk::Stepper *stepper) -{ - this->m_TimeStepper = stepper; -} - -void QmitkFastMarchingToolGUI::Refetch() -{ - // event from image navigator recieved - timestep has changed - m_FastMarchingTool->SetCurrentTimeStep(m_TimeStepper->GetPos()); -} - -void QmitkFastMarchingToolGUI::OnClearSeeds() -{ - // event from image navigator recieved - timestep has changed - m_FastMarchingTool->ClearSeeds(); - m_btClearSeeds->setEnabled(false); - m_btConfirm->setEnabled(false); - this->EnableWidgets(false); - this->Update(); -} - -void QmitkFastMarchingToolGUI::BusyStateChanged(bool value) -{ - if (value) - QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - else - QApplication::restoreOverrideCursor(); -} - -void QmitkFastMarchingToolGUI::OnFastMarchingToolReady() -{ - this->EnableWidgets(true); - this->m_btClearSeeds->setEnabled(true); - this->m_btConfirm->setEnabled(true); -} - -void QmitkFastMarchingToolGUI::EnableWidgets(bool enable) -{ - m_slSigma->setEnabled(enable); - m_slAlpha->setEnabled(enable); - m_slBeta->setEnabled(enable); - m_slStoppingValue->setEnabled(enable); - m_slwThreshold->setEnabled(enable); -} +QmitkFastMarchingToolGUI::QmitkFastMarchingToolGUI() : QmitkFastMarchingToolGUIBase(true) +{} diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.h index 6d1ede7b3d..72340f6114 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUI.h @@ -1,84 +1,38 @@ /*============================================================================ 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 QmitkFastMarchingToolGUI_h_Included #define QmitkFastMarchingToolGUI_h_Included -#include "QmitkToolGUI.h" -#include "mitkFastMarchingTool.h" +#include "QmitkFastMarchingToolGUIBase.h" #include -class ctkSliderWidget; -class ctkRangeWidget; -class QPushButton; - -#include "QmitkStepperAdapter.h" - /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal -\brief GUI for mitk::FastMarchingTool. +\brief GUI for mitk::FastMarchingTool in 2D. \sa mitk::FastMarchingTool */ -class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingToolGUI : public QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingToolGUI : public QmitkFastMarchingToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkFastMarchingToolGUI, QmitkToolGUI); + mitkClassMacro(QmitkFastMarchingToolGUI, QmitkFastMarchingToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); - void OnThresholdChanged(int current); - -protected slots: - - void OnNewToolAssociated(mitk::Tool *); - - void OnThresholdChanged(double, double); - void OnAlphaChanged(double); - void OnBetaChanged(double); - void OnSigmaChanged(double); - void OnStoppingValueChanged(double); - void OnConfirmSegmentation(); - void Refetch(); - void SetStepper(mitk::Stepper *); - void OnClearSeeds(); - protected: QmitkFastMarchingToolGUI(); - ~QmitkFastMarchingToolGUI() override; - - void Update(); - - void BusyStateChanged(bool) override; - - ctkRangeWidget *m_slwThreshold; - ctkSliderWidget *m_slStoppingValue; - ctkSliderWidget *m_slSigma; - ctkSliderWidget *m_slAlpha; - ctkSliderWidget *m_slBeta; - - QPushButton *m_btConfirm; - QPushButton *m_btClearSeeds; - - mitk::FastMarchingTool::Pointer m_FastMarchingTool; - - bool m_TimeIsConnected; - mitk::Stepper::Pointer m_TimeStepper; - - void OnFastMarchingToolReady(); - -private: - void EnableWidgets(bool); + ~QmitkFastMarchingToolGUI() = default; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUIBase.cpp b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUIBase.cpp new file mode 100644 index 0000000000..1bd272c0f5 --- /dev/null +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUIBase.cpp @@ -0,0 +1,296 @@ +/*============================================================================ + +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 "QmitkFastMarchingToolGUIBase.h" + +#include "mitkBaseRenderer.h" +#include "mitkStepper.h" +#include +#include +#include +#include +#include +#include +#include +#include + +QmitkFastMarchingToolGUIBase::QmitkFastMarchingToolGUIBase(bool mode2D) : QmitkAutoSegmentationToolGUIBase(mode2D) +{ +} + +QmitkFastMarchingToolGUIBase::~QmitkFastMarchingToolGUIBase() +{ +} + +void QmitkFastMarchingToolGUIBase::Update() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->SetLowerThreshold(m_slwThreshold->minimumValue()); + tool->SetUpperThreshold(m_slwThreshold->maximumValue()); + tool->SetStoppingValue(m_slStoppingValue->value()); + tool->SetSigma(m_slSigma->value()); + tool->SetAlpha(m_slAlpha->value()); + tool->SetBeta(m_slBeta->value()); + tool->UpdatePreview(); + } +} + +void QmitkFastMarchingToolGUIBase::OnThresholdChanged(double lower, double upper) +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->SetLowerThreshold(lower); + tool->SetUpperThreshold(upper); + this->Update(); + } +} + +void QmitkFastMarchingToolGUIBase::OnBetaChanged(double value) +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->SetBeta(value); + this->Update(); + } +} + +void QmitkFastMarchingToolGUIBase::OnSigmaChanged(double value) +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->SetSigma(value); + this->Update(); + } +} + +void QmitkFastMarchingToolGUIBase::OnAlphaChanged(double value) +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->SetAlpha(value); + this->Update(); + } +} + +void QmitkFastMarchingToolGUIBase::OnStoppingValueChanged(double value) +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->SetStoppingValue(value); + this->Update(); + } +} + +void QmitkFastMarchingToolGUIBase::OnClearSeeds() +{ + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) + { + tool->ClearSeeds(); + this->EnableWidgets(false); + this->Update(); + } +} + +void QmitkFastMarchingToolGUIBase::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) +{ + Superclass::ConnectNewTool(newTool); + + auto tool = dynamic_cast(newTool); + if (nullptr != tool) + { + tool->SetLowerThreshold(m_slwThreshold->minimumValue()); + tool->SetUpperThreshold(m_slwThreshold->maximumValue()); + tool->SetStoppingValue(m_slStoppingValue->value()); + tool->SetSigma(m_slSigma->value()); + tool->SetAlpha(m_slAlpha->value()); + tool->SetBeta(m_slBeta->value()); + tool->ClearSeeds(); + } +} + +void QmitkFastMarchingToolGUIBase::InitializeUI(QBoxLayout* mainLayout) +{ + mainLayout->setContentsMargins(0, 0, 0, 0); + + QFont fntHelp; + fntHelp.setBold(true); + + QLabel* lblHelp = new QLabel(this); + lblHelp->setText("Press shift-click to add seeds repeatedly."); + lblHelp->setFont(fntHelp); + + mainLayout->addWidget(lblHelp); + + // Sigma controls + { + QHBoxLayout* hlayout = new QHBoxLayout(); + hlayout->setSpacing(2); + + QLabel* lbl = new QLabel(this); + lbl->setText("Sigma: "); + hlayout->addWidget(lbl); + + QSpacerItem* sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + hlayout->addItem(sp2); + + mainLayout->addItem(hlayout); + } + + m_slSigma = new ctkSliderWidget(this); + m_slSigma->setMinimum(0.1); + m_slSigma->setMaximum(5.0); + m_slSigma->setPageStep(0.1); + m_slSigma->setSingleStep(0.01); + m_slSigma->setValue(1.0); + m_slSigma->setTracking(false); + m_slSigma->setToolTip("The \"sigma\" parameter in the Gradient Magnitude filter."); + connect(m_slSigma, SIGNAL(valueChanged(double)), this, SLOT(OnSigmaChanged(double))); + mainLayout->addWidget(m_slSigma); + + // Alpha controls + { + QHBoxLayout* hlayout = new QHBoxLayout(); + hlayout->setSpacing(2); + + QLabel* lbl = new QLabel(this); + lbl->setText("Alpha: "); + hlayout->addWidget(lbl); + + QSpacerItem* sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + hlayout->addItem(sp2); + + mainLayout->addItem(hlayout); + } + + m_slAlpha = new ctkSliderWidget(this); + m_slAlpha->setMinimum(-10); + m_slAlpha->setMaximum(0); + m_slAlpha->setPageStep(0.1); + m_slAlpha->setSingleStep(0.01); + m_slAlpha->setValue(-2.5); + m_slAlpha->setTracking(false); + m_slAlpha->setToolTip("The \"alpha\" parameter in the Sigmoid mapping filter."); + connect(m_slAlpha, SIGNAL(valueChanged(double)), this, SLOT(OnAlphaChanged(double))); + mainLayout->addWidget(m_slAlpha); + + // Beta controls + { + QHBoxLayout* hlayout = new QHBoxLayout(); + hlayout->setSpacing(2); + + QLabel* lbl = new QLabel(this); + lbl->setText("Beta: "); + hlayout->addWidget(lbl); + + QSpacerItem* sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + hlayout->addItem(sp2); + + mainLayout->addLayout(hlayout); + } + + m_slBeta = new ctkSliderWidget(this); + m_slBeta->setMinimum(0); + m_slBeta->setMaximum(100); + m_slBeta->setPageStep(0.1); + m_slBeta->setSingleStep(0.01); + m_slBeta->setValue(3.5); + m_slBeta->setTracking(false); + m_slBeta->setToolTip("The \"beta\" parameter in the Sigmoid mapping filter."); + connect(m_slBeta, SIGNAL(valueChanged(double)), this, SLOT(OnBetaChanged(double))); + mainLayout->addWidget(m_slBeta); + + // stopping value controls + { + QHBoxLayout* hlayout = new QHBoxLayout(); + hlayout->setSpacing(2); + + QLabel* lbl = new QLabel(this); + lbl->setText("Stopping value: "); + hlayout->addWidget(lbl); + + QSpacerItem* sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + hlayout->addItem(sp2); + + mainLayout->addLayout(hlayout); + } + + m_slStoppingValue = new ctkSliderWidget(this); + m_slStoppingValue->setMinimum(0); + m_slStoppingValue->setMaximum(10000); + m_slStoppingValue->setPageStep(10); + m_slStoppingValue->setSingleStep(1); + m_slStoppingValue->setValue(2000); + m_slStoppingValue->setDecimals(0); + m_slStoppingValue->setTracking(false); + m_slStoppingValue->setToolTip("The \"stopping value\" parameter in the fast marching 3D algorithm"); + connect(m_slStoppingValue, SIGNAL(valueChanged(double)), this, SLOT(OnStoppingValueChanged(double))); + mainLayout->addWidget(m_slStoppingValue); + + // threshold controls + { + QHBoxLayout* hlayout = new QHBoxLayout(); + hlayout->setSpacing(2); + + QLabel* lbl = new QLabel(this); + lbl->setText("Threshold: "); + hlayout->addWidget(lbl); + + QSpacerItem* sp2 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum); + hlayout->addItem(sp2); + + mainLayout->addLayout(hlayout); + } + + m_slwThreshold = new ctkRangeWidget(this); + m_slwThreshold->setMinimum(-100); + m_slwThreshold->setMaximum(5000); + m_slwThreshold->setMinimumValue(-100); + m_slwThreshold->setMaximumValue(2000); + m_slwThreshold->setDecimals(0); + m_slwThreshold->setTracking(false); + m_slwThreshold->setToolTip("The lower and upper thresholds for the final thresholding"); + connect(m_slwThreshold, SIGNAL(valuesChanged(double, double)), this, SLOT(OnThresholdChanged(double, double))); + mainLayout->addWidget(m_slwThreshold); + + m_btClearSeeds = new QPushButton("Clear"); + m_btClearSeeds->setToolTip("Clear current result and start over again"); + m_btClearSeeds->setEnabled(false); + mainLayout->addWidget(m_btClearSeeds); + connect(m_btClearSeeds, SIGNAL(clicked()), this, SLOT(OnClearSeeds())); + + m_slSigma->setDecimals(2); + m_slBeta->setDecimals(2); + m_slAlpha->setDecimals(2); + + this->EnableWidgets(false); + + Superclass::InitializeUI(mainLayout); +} + +void QmitkFastMarchingToolGUIBase::EnableWidgets(bool enable) +{ + Superclass::EnableWidgets(enable); + m_slSigma->setEnabled(enable); + m_slAlpha->setEnabled(enable); + m_slBeta->setEnabled(enable); + m_slStoppingValue->setEnabled(enable); + m_slwThreshold->setEnabled(enable); + m_btClearSeeds->setEnabled(enable); +} diff --git a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUIBase.h similarity index 58% copy from Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h copy to Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUIBase.h index a02b18de12..669ef27155 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkFastMarchingTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkFastMarchingToolGUIBase.h @@ -1,79 +1,67 @@ /*============================================================================ The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center (DKFZ) All rights reserved. Use of this source code is governed by a 3-clause BSD license that can be found in the LICENSE file. ============================================================================*/ -#ifndef QmitkFastMarchingTool3DGUI_h_Included -#define QmitkFastMarchingTool3DGUI_h_Included +#ifndef QmitkFastMarchingToolGUIBase_h_Included +#define QmitkFastMarchingToolGUIBase_h_Included -#include "QmitkToolGUI.h" +#include "QmitkAutoSegmentationToolGUIBase.h" #include "mitkFastMarchingTool3D.h" #include class ctkSliderWidget; class ctkRangeWidget; class QPushButton; -class QCheckBox; /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal -\brief GUI for mitk::FastMarchingTool. +\brief Base GUI for mitk::FastMarchingTool (2D and 3D). \sa mitk::FastMarchingTool */ -class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingTool3DGUI : public QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkFastMarchingToolGUIBase : public QmitkAutoSegmentationToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkFastMarchingTool3DGUI, QmitkToolGUI); - itkFactorylessNewMacro(Self); - itkCloneMacro(Self); - - void OnThresholdChanged(int current); + mitkClassMacro(QmitkFastMarchingToolGUIBase, QmitkAutoSegmentationToolGUIBase); protected slots: - void OnNewToolAssociated(mitk::Tool *); - void OnThresholdChanged(double, double); void OnAlphaChanged(double); void OnBetaChanged(double); void OnSigmaChanged(double); void OnStoppingValueChanged(double); - void OnConfirmSegmentation(); void OnClearSeeds(); protected: - QmitkFastMarchingTool3DGUI(); - ~QmitkFastMarchingTool3DGUI() override; + QmitkFastMarchingToolGUIBase(bool mode2D); + ~QmitkFastMarchingToolGUIBase() override; + + void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; + void InitializeUI(QBoxLayout* mainLayout) override; - void BusyStateChanged(bool) override; + void EnableWidgets(bool) override; void Update(); ctkRangeWidget *m_slwThreshold; ctkSliderWidget *m_slStoppingValue; ctkSliderWidget *m_slSigma; ctkSliderWidget *m_slAlpha; ctkSliderWidget *m_slBeta; - QPushButton *m_btConfirm; QPushButton *m_btClearSeeds; - QCheckBox* m_CheckProcessAll = nullptr; - QCheckBox* m_CheckCreateNew = nullptr; - - mitk::FastMarchingTool3D::Pointer m_FastMarchingTool; - private: - void EnableWidgets(bool); }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp index 9e925b66ba..a752782207 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.cpp @@ -1,194 +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() : QmitkToolGUI(), m_NumberOfRegions(0) +QmitkOtsuTool3DGUI::QmitkOtsuTool3DGUI() : QmitkAutoMLSegmentationToolGUIBase() +{ +} + +void QmitkOtsuTool3DGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) +{ + Superclass::ConnectNewTool(newTool); + + newTool->IsTimePointChangeAwareOff(); + m_FirstPreviewComputation = 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_selectionListWidget, &QmitkSimpleLabelSetListWidget::SelectedLabelsChanged, this, &QmitkOtsuTool3DGUI::OnRegionSelectionChanged); + connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnPreviewBtnClicked())); connect(m_Controls.m_Spinbox, SIGNAL(valueChanged(int)), this, SLOT(OnRegionSpinboxChanged(int))); - connect(m_Controls.m_ConfSegButton, SIGNAL(clicked()), this, SLOT(OnSegmentationRegionAccept())); - connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); connect(m_Controls.advancedSettingsButton, SIGNAL(toggled(bool)), this, SLOT(OnAdvancedSettingsButtonToggled(bool))); this->OnAdvancedSettingsButtonToggled(false); -} -QmitkOtsuTool3DGUI::~QmitkOtsuTool3DGUI() -{ - if (m_OtsuTool3DTool.IsNotNull()) - { - m_OtsuTool3DTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); - } + 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::OnRegionSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels) -{ - if (m_OtsuTool3DTool.IsNotNull()) - { - mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType labelIDs; - for (const auto& label : selectedLabels) - { - labelIDs.push_back(label->GetValue()); - } - - m_OtsuTool3DTool->SetSelectedLabels(labelIDs); - m_OtsuTool3DTool->UpdatePreview(); - - m_Controls.m_ConfSegButton->setEnabled(!labelIDs.empty()); - } -} - void QmitkOtsuTool3DGUI::OnAdvancedSettingsButtonToggled(bool toggled) { m_Controls.m_ValleyCheckbox->setVisible(toggled); m_Controls.binLabel->setVisible(toggled); m_Controls.m_BinsSpinBox->setVisible(toggled); - if (toggled) + auto tool = this->GetConnectedToolAs(); + if (toggled && nullptr != tool) { - int max = m_OtsuTool3DTool->GetMaxNumberOfBins(); + int max = tool->GetMaxNumberOfBins(); if (max >= m_Controls.m_BinsSpinBox->minimum()) { m_Controls.m_BinsSpinBox->setMaximum(max); } } } -void QmitkOtsuTool3DGUI::OnNewToolAssociated(mitk::Tool *tool) +void QmitkOtsuTool3DGUI::OnPreviewBtnClicked() { - if (m_OtsuTool3DTool.IsNotNull()) - { - m_OtsuTool3DTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); - } - - m_OtsuTool3DTool = dynamic_cast(tool); - - if (m_OtsuTool3DTool.IsNotNull()) + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) { - m_OtsuTool3DTool->CurrentlyBusy += - mitk::MessageDelegate1(this, &QmitkOtsuTool3DGUI::BusyStateChanged); - - m_OtsuTool3DTool->SetOverwriteExistingSegmentation(true); - m_OtsuTool3DTool->IsTimePointChangeAwareOff(); - m_Controls.m_CheckProcessAll->setVisible(m_OtsuTool3DTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); - } -} - -void QmitkOtsuTool3DGUI::OnSegmentationRegionAccept() -{ - QString segName = QString::fromStdString(m_OtsuTool3DTool->GetCurrentSegmentationName()); - - if (m_OtsuTool3DTool.IsNotNull()) - { - if (this->m_Controls.m_CheckCreateNew->isChecked()) - { - m_OtsuTool3DTool->SetOverwriteExistingSegmentation(false); - } - else - { - m_OtsuTool3DTool->SetOverwriteExistingSegmentation(true); - } - - m_OtsuTool3DTool->SetCreateAllTimeSteps(this->m_Controls.m_CheckProcessAll->isChecked()); - - this->m_Controls.m_ConfSegButton->setEnabled(false); - m_OtsuTool3DTool->ConfirmSegmentation(); - } -} + if (!m_FirstPreviewComputation || + (tool->GetNumberOfRegions() == static_cast(m_Controls.m_Spinbox->value()) && + tool->GetUseValley() == m_Controls.m_ValleyCheckbox->isChecked() && + tool->GetNumberOfBins() == static_cast(m_Controls.m_BinsSpinBox->value()))) + return; -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; + m_FirstPreviewComputation = false; - if (m_OtsuTool3DTool.IsNotNull()) - { 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(); - m_OtsuTool3DTool->SetNumberOfRegions(m_NumberOfRegions); - m_OtsuTool3DTool->SetUseValley(m_UseValleyEmphasis); - m_OtsuTool3DTool->SetNumberOfBins(m_NumberOfBins); + tool->SetNumberOfRegions(static_cast(m_Controls.m_Spinbox->value())); + tool->SetUseValley(m_Controls.m_ValleyCheckbox->isChecked()); + tool->SetNumberOfBins(static_cast(m_Controls.m_BinsSpinBox->value())); - m_OtsuTool3DTool->UpdatePreview(); + 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; } - m_Controls.m_selectionListWidget->SetLabelSetImage(m_OtsuTool3DTool->GetMLPreview()); - m_OtsuTool3DTool->IsTimePointChangeAwareOn(); + this->SetLabelSetPreview(tool->GetMLPreview()); + tool->IsTimePointChangeAwareOn(); } } -void QmitkOtsuTool3DGUI::BusyStateChanged(bool value) +void QmitkOtsuTool3DGUI::EnableWidgets(bool enabled) { - if (value) - { - QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - } - else - { - QApplication::restoreOverrideCursor(); - } - - m_Controls.m_ValleyCheckbox->setEnabled(!value); - m_Controls.binLabel->setEnabled(!value); - m_Controls.m_BinsSpinBox->setEnabled(!value); - m_Controls.m_ConfSegButton->setEnabled(!m_OtsuTool3DTool->GetSelectedLabels().empty() && !value); - m_Controls.m_CheckProcessAll->setEnabled(!value); - m_Controls.m_CheckCreateNew->setEnabled(!value); - m_Controls.previewButton->setEnabled(!value); + 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 e7a3762c43..f9d87fd615 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuTool3DGUI.h @@ -1,75 +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 "QmitkToolGUI.h" -#include "mitkOtsuTool3D.h" +#include "QmitkAutoMLSegmentationToolGUIBase.h" #include "ui_QmitkOtsuToolWidgetControls.h" #include -#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 QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkOtsuTool3DGUI : public QmitkAutoMLSegmentationToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkOtsuTool3DGUI, QmitkToolGUI); + mitkClassMacro(QmitkOtsuTool3DGUI, QmitkAutoMLSegmentationToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots : - void OnNewToolAssociated(mitk::Tool *); - - void OnSpinboxValueAccept(); - - void OnSegmentationRegionAccept(); - - void OnRegionSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels); + void OnPreviewBtnClicked(); void OnRegionSpinboxChanged(int); private slots: void OnAdvancedSettingsButtonToggled(bool toggled); protected: QmitkOtsuTool3DGUI(); - ~QmitkOtsuTool3DGUI() override; + ~QmitkOtsuTool3DGUI() = default; - void BusyStateChanged(bool value) override; + void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; + void InitializeUI(QBoxLayout* mainLayout) override; - mitk::OtsuTool3D::Pointer m_OtsuTool3DTool; + void EnableWidgets(bool enabled) override; Ui_QmitkOtsuToolWidgetControls m_Controls; - int m_NumberOfRegions; - - bool m_UseValleyEmphasis; - - int m_NumberOfBins; + bool m_FirstPreviewComputation = true; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui b/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui index d01d26178d..8137fb2d3f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkOtsuToolWidgetControls.ui @@ -1,253 +1,205 @@ QmitkOtsuToolWidgetControls 0 0 699 352 0 0 100 0 100000 100000 QmitkOtsuToolWidget + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + QLayout::SetNoConstraint 0 0 Number of Regions: 0 0 40 16777215 2 32 Qt::Horizontal 40 20 0 0 0 32 Advanced settings Qt::ToolButtonTextBesideIcon true 2 4096 128 Use Valley Emphasis Number of Histogram Bins: Qt::Horizontal 40 20 - - - - - 0 - 0 - - - - - 10000000 - 10000000 - - - - 0 0 100000 16777215 Preview - - - - false - - - - 0 - 0 - - - - - 100000 - 16777215 - - - - Confirm Segmentation - - - - - - - Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step. - - - Process all time steps - - - - - - - Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected. - - - Create as new segmentation - - - ctkExpandButton QToolButton
ctkExpandButton.h
- - QmitkSimpleLabelSetListWidget - QWidget -
QmitkSimpleLabelSetListWidget.h
-
diff --git a/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUI.cpp b/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUI.cpp index 41c3151292..fda699f8d4 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUI.cpp +++ b/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUI.cpp @@ -1,199 +1,139 @@ /*============================================================================ 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 "QmitkWatershedToolGUI.h" +#include "mitkWatershedTool.h" #include MITK_TOOL_GUI_MACRO(MITKSEGMENTATIONUI_EXPORT, QmitkWatershedToolGUI, "") -QmitkWatershedToolGUI::QmitkWatershedToolGUI() : QmitkToolGUI() +QmitkWatershedToolGUI::QmitkWatershedToolGUI() : QmitkAutoMLSegmentationToolGUIBase() +{ +} + +void QmitkWatershedToolGUI::ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) +{ + Superclass::ConnectNewTool(newTool); + + auto tool = dynamic_cast(newTool); + if (nullptr != tool) + { + tool->SetLevel(m_Level); + tool->SetThreshold(m_Threshold); + } + + newTool->IsTimePointChangeAwareOff(); +} + +void QmitkWatershedToolGUI::InitializeUI(QBoxLayout* mainLayout) { m_Controls.setupUi(this); m_Controls.thresholdSlider->setMinimum(0); //We set the threshold maximum to 0.5 to avoid crashes in the watershed filter //see T27703 for more details. m_Controls.thresholdSlider->setMaximum(0.5); m_Controls.thresholdSlider->setValue(m_Threshold); m_Controls.thresholdSlider->setPageStep(0.01); m_Controls.thresholdSlider->setSingleStep(0.001); m_Controls.thresholdSlider->setDecimals(4); m_Controls.levelSlider->setMinimum(0); m_Controls.levelSlider->setMaximum(1); m_Controls.levelSlider->setValue(m_Level); m_Controls.levelSlider->setPageStep(0.1); m_Controls.levelSlider->setSingleStep(0.01); connect(m_Controls.previewButton, SIGNAL(clicked()), this, SLOT(OnSettingsAccept())); - connect(m_Controls.m_selectionListWidget, &QmitkSimpleLabelSetListWidget::SelectedLabelsChanged, this, &QmitkWatershedToolGUI::OnRegionSelectionChanged); connect(m_Controls.levelSlider, SIGNAL(valueChanged(double)), this, SLOT(OnLevelChanged(double))); connect(m_Controls.thresholdSlider, SIGNAL(valueChanged(double)), this, SLOT(OnThresholdChanged(double))); - connect(m_Controls.m_ConfSegButton, SIGNAL(clicked()), this, SLOT(OnSegmentationRegionAccept())); - connect(this, SIGNAL(NewToolAssociated(mitk::Tool *)), this, SLOT(OnNewToolAssociated(mitk::Tool *))); -} - -QmitkWatershedToolGUI::~QmitkWatershedToolGUI() -{ - if (m_WatershedTool.IsNotNull()) - { - m_WatershedTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkWatershedToolGUI::BusyStateChanged); - } -} - -void QmitkWatershedToolGUI::OnRegionSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels) -{ - if (m_WatershedTool.IsNotNull()) - { - mitk::AutoMLSegmentationWithPreviewTool::SelectedLabelVectorType labelIDs; - for (const auto& label : selectedLabels) - { - labelIDs.push_back(label->GetValue()); - } - - m_WatershedTool->SetSelectedLabels(labelIDs); - m_WatershedTool->UpdatePreview(); - - m_Controls.m_ConfSegButton->setEnabled(!labelIDs.empty()); - } -} -void QmitkWatershedToolGUI::OnNewToolAssociated(mitk::Tool *tool) -{ - if (m_WatershedTool.IsNotNull()) - { - m_WatershedTool->CurrentlyBusy -= - mitk::MessageDelegate1(this, &QmitkWatershedToolGUI::BusyStateChanged); - } - - m_WatershedTool = dynamic_cast(tool); - - if (m_WatershedTool.IsNotNull()) - { - m_WatershedTool->CurrentlyBusy += - mitk::MessageDelegate1(this, &QmitkWatershedToolGUI::BusyStateChanged); - - m_WatershedTool->SetLevel(m_Level); - m_WatershedTool->SetThreshold(m_Threshold); + mainLayout->addLayout(m_Controls.verticalLayout); - m_WatershedTool->SetOverwriteExistingSegmentation(true); - m_WatershedTool->IsTimePointChangeAwareOff(); - m_Controls.m_CheckProcessAll->setVisible(m_WatershedTool->GetTargetSegmentationNode()->GetData()->GetTimeSteps() > 1); - } -} - -void QmitkWatershedToolGUI::OnSegmentationRegionAccept() -{ - QString segName = QString::fromStdString(m_WatershedTool->GetCurrentSegmentationName()); - - if (m_WatershedTool.IsNotNull()) - { - if (this->m_Controls.m_CheckCreateNew->isChecked()) - { - m_WatershedTool->SetOverwriteExistingSegmentation(false); - } - else - { - m_WatershedTool->SetOverwriteExistingSegmentation(true); - } - - m_WatershedTool->SetCreateAllTimeSteps(this->m_Controls.m_CheckProcessAll->isChecked()); - - this->m_Controls.m_ConfSegButton->setEnabled(false); - m_WatershedTool->ConfirmSegmentation(); - } + Superclass::InitializeUI(mainLayout); } void QmitkWatershedToolGUI::OnSettingsAccept() { - if (m_WatershedTool.IsNotNull()) + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) { try { m_Threshold = m_Controls.thresholdSlider->value(); m_Level = m_Controls.levelSlider->value(); - m_WatershedTool->SetThreshold(m_Threshold); - m_WatershedTool->SetLevel(m_Level); + tool->SetThreshold(m_Threshold); + tool->SetLevel(m_Level); - m_WatershedTool->UpdatePreview(); + tool->UpdatePreview(); } catch (const std::exception& e) { this->setCursor(Qt::ArrowCursor); std::stringstream stream; stream << "Error while generation watershed segmentation. Reason: " << e.what(); QMessageBox* messageBox = new QMessageBox(QMessageBox::Critical, nullptr, stream.str().c_str()); messageBox->exec(); delete messageBox; MITK_ERROR << stream.str(); return; } catch (...) { this->setCursor(Qt::ArrowCursor); std::stringstream stream; stream << "Unkown error occured while generation watershed segmentation."; QMessageBox* messageBox = new QMessageBox(QMessageBox::Critical, nullptr, stream.str().c_str()); messageBox->exec(); delete messageBox; MITK_ERROR << stream.str(); return; } - m_Controls.m_selectionListWidget->SetLabelSetImage(m_WatershedTool->GetMLPreview()); - m_WatershedTool->IsTimePointChangeAwareOn(); + this->SetLabelSetPreview(tool->GetMLPreview()); + tool->IsTimePointChangeAwareOn(); } } -void QmitkWatershedToolGUI::BusyStateChanged(bool value) +void QmitkWatershedToolGUI::EnableWidgets(bool enabled) { - if (value) - { - QApplication::setOverrideCursor(QCursor(Qt::BusyCursor)); - } - else - { - QApplication::restoreOverrideCursor(); - } - - m_Controls.levelSlider->setEnabled(!value); - m_Controls.thresholdSlider->setEnabled(!value); - m_Controls.m_ConfSegButton->setEnabled(!m_WatershedTool->GetSelectedLabels().empty() && !value); - m_Controls.m_CheckProcessAll->setEnabled(!value); - m_Controls.m_CheckCreateNew->setEnabled(!value); - m_Controls.previewButton->setEnabled(!value); + Superclass::EnableWidgets(enabled); + m_Controls.levelSlider->setEnabled(enabled); + m_Controls.thresholdSlider->setEnabled(enabled); + m_Controls.previewButton->setEnabled(enabled); } void QmitkWatershedToolGUI::OnLevelChanged(double value) { - if (m_WatershedTool.IsNotNull()) + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) { - m_WatershedTool->SetLevel(value); + tool->SetLevel(value); } } void QmitkWatershedToolGUI::OnThresholdChanged(double value) { - if (m_WatershedTool.IsNotNull()) + auto tool = this->GetConnectedToolAs(); + if (nullptr != tool) { - m_WatershedTool->SetThreshold(value); + tool->SetThreshold(value); } } diff --git a/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUI.h b/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUI.h index c5b8bbe5f1..193257e01f 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUI.h +++ b/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUI.h @@ -1,71 +1,66 @@ /*============================================================================ 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 QmitkWatershedToolGUI_h_Included #define QmitkWatershedToolGUI_h_Included -#include "QmitkToolGUI.h" -#include "mitkWatershedTool.h" +#include "QmitkAutoMLSegmentationToolGUIBase.h" #include "ui_QmitkWatershedToolGUIControls.h" #include /** \ingroup org_mitk_gui_qt_interactivesegmentation_internal \brief GUI for mitk::WatershedTool. \sa mitk::WatershedTool This GUI shows two sliders to change the watershed parameters. It executes the watershed algorithm by clicking on the button. */ -class MITKSEGMENTATIONUI_EXPORT QmitkWatershedToolGUI : public QmitkToolGUI +class MITKSEGMENTATIONUI_EXPORT QmitkWatershedToolGUI : public QmitkAutoMLSegmentationToolGUIBase { Q_OBJECT public: - mitkClassMacro(QmitkWatershedToolGUI, QmitkToolGUI); + mitkClassMacro(QmitkWatershedToolGUI, QmitkAutoMLSegmentationToolGUIBase); itkFactorylessNewMacro(Self); itkCloneMacro(Self); protected slots : - void OnNewToolAssociated(mitk::Tool *); - void OnSettingsAccept(); - void OnSegmentationRegionAccept(); - - void OnRegionSelectionChanged(const QmitkSimpleLabelSetListWidget::LabelVectorType& selectedLabels); - void OnLevelChanged(double value); void OnThresholdChanged(double value); protected: QmitkWatershedToolGUI(); - ~QmitkWatershedToolGUI() override; + ~QmitkWatershedToolGUI() = default; + + void ConnectNewTool(mitk::AutoSegmentationWithPreviewTool* newTool) override; + void InitializeUI(QBoxLayout* mainLayout) override; - void BusyStateChanged(bool value) override; + void EnableWidgets(bool enabled) override; //Recommendation from ITK is to have a threshold:level ration around 1:100 //we set Level a bit higher. This provokes more oversegmentation, //but produces less objects in the first run and profits form the fact that //decreasing level is quite fast in the filter. double m_Level = 0.6; double m_Threshold = 0.004; Ui_QmitkWatershedToolGUIControls m_Controls; - mitk::WatershedTool::Pointer m_WatershedTool; }; #endif diff --git a/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUIControls.ui b/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUIControls.ui index 4c4931c08a..4ce970d215 100644 --- a/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUIControls.ui +++ b/Modules/SegmentationUI/Qmitk/QmitkWatershedToolGUIControls.ui @@ -1,160 +1,109 @@ QmitkWatershedToolGUIControls 0 0 192 352 0 0 100 0 100000 100000 QmitkOtsuToolWidget + + 0 + + + 0 + + + 0 + + + 0 + Level: 0 0 Threshold: - - - - - 0 - 0 - - - - - 10000000 - 10000000 - - - - 0 0 100000 16777215 Preview - - - - false - - - - 0 - 0 - - - - - 100000 - 16777215 - - - - Confirm Segmentation - - - - - - - Process/overwrite all time steps of the dynamic segmentation and not just the currently visible time step. - - - Process all time steps - - - - - - - Add the confirmed segmentation as a new segmentation instead of overwriting the currently selected. - - - Create as new segmentation - - - - - QmitkSimpleLabelSetListWidget - QWidget -
QmitkSimpleLabelSetListWidget.h
-
ctkSliderWidget QWidget
ctkSliderWidget.h
1
diff --git a/Modules/SegmentationUI/files.cmake b/Modules/SegmentationUI/files.cmake index d84231e8a4..fb35d7707a 100644 --- a/Modules/SegmentationUI/files.cmake +++ b/Modules/SegmentationUI/files.cmake @@ -1,84 +1,91 @@ set( CPP_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.cpp +Qmitk/QmitkAutoSegmentationToolGUIBase.cpp +Qmitk/QmitkAutoMLSegmentationToolGUIBase.cpp +Qmitk/QmitkBinaryThresholdToolGUIBase.cpp Qmitk/QmitkBinaryThresholdToolGUI.cpp Qmitk/QmitkBinaryThresholdULToolGUI.cpp Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.cpp Qmitk/QmitkConfirmSegmentationDialog.cpp Qmitk/QmitkCopyToClipBoardDialog.cpp Qmitk/QmitkDrawPaintbrushToolGUI.cpp Qmitk/QmitkErasePaintbrushToolGUI.cpp +Qmitk/QmitkFastMarchingToolGUIBase.cpp Qmitk/QmitkFastMarchingTool3DGUI.cpp Qmitk/QmitkFastMarchingToolGUI.cpp - Qmitk/QmitkLiveWireTool2DGUI.cpp Qmitk/QmitkNewSegmentationDialog.cpp Qmitk/QmitkOtsuTool3DGUI.cpp Qmitk/QmitkPaintbrushToolGUI.cpp Qmitk/QmitkPickingToolGUI.cpp Qmitk/QmitkPixelManipulationToolGUI.cpp Qmitk/QmitkSlicesInterpolator.cpp Qmitk/QmitkToolGUI.cpp Qmitk/QmitkToolGUIArea.cpp Qmitk/QmitkToolSelectionBox.cpp Qmitk/QmitkWatershedToolGUI.cpp #Added from ML Qmitk/QmitkLabelSetWidget.cpp Qmitk/QmitkSurfaceStampWidget.cpp Qmitk/QmitkMaskStampWidget.cpp Qmitk/QmitkSliceBasedInterpolatorWidget.cpp Qmitk/QmitkSurfaceBasedInterpolatorWidget.cpp Qmitk/QmitkSearchLabelDialog.cpp Qmitk/QmitkSimpleLabelSetListWidget.cpp ) set(MOC_H_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUI.h +Qmitk/QmitkAutoSegmentationToolGUIBase.h +Qmitk/QmitkAutoMLSegmentationToolGUIBase.h +Qmitk/QmitkBinaryThresholdToolGUIBase.h Qmitk/QmitkBinaryThresholdToolGUI.h Qmitk/QmitkBinaryThresholdULToolGUI.h Qmitk/QmitkCalculateGrayValueStatisticsToolGUI.h Qmitk/QmitkConfirmSegmentationDialog.h Qmitk/QmitkCopyToClipBoardDialog.h Qmitk/QmitkDrawPaintbrushToolGUI.h Qmitk/QmitkErasePaintbrushToolGUI.h +Qmitk/QmitkFastMarchingToolGUIBase.h Qmitk/QmitkFastMarchingTool3DGUI.h Qmitk/QmitkFastMarchingToolGUI.h Qmitk/QmitkLiveWireTool2DGUI.h Qmitk/QmitkNewSegmentationDialog.h Qmitk/QmitkOtsuTool3DGUI.h Qmitk/QmitkPaintbrushToolGUI.h Qmitk/QmitkPickingToolGUI.h Qmitk/QmitkPixelManipulationToolGUI.h Qmitk/QmitkSlicesInterpolator.h Qmitk/QmitkToolGUI.h Qmitk/QmitkToolGUIArea.h Qmitk/QmitkToolSelectionBox.h Qmitk/QmitkWatershedToolGUI.h #Added from ML Qmitk/QmitkLabelSetWidget.h Qmitk/QmitkSurfaceStampWidget.h Qmitk/QmitkMaskStampWidget.h Qmitk/QmitkSliceBasedInterpolatorWidget.h Qmitk/QmitkSurfaceBasedInterpolatorWidget.h Qmitk/QmitkSearchLabelDialog.h Qmitk/QmitkSimpleLabelSetListWidget.h ) set(UI_FILES Qmitk/QmitkAdaptiveRegionGrowingToolGUIControls.ui Qmitk/QmitkConfirmSegmentationDialog.ui Qmitk/QmitkOtsuToolWidgetControls.ui Qmitk/QmitkPickingToolGUIControls.ui Qmitk/QmitkLiveWireTool2DGUIControls.ui Qmitk/QmitkWatershedToolGUIControls.ui #Added from ML Qmitk/QmitkLabelSetWidgetControls.ui Qmitk/QmitkSurfaceStampWidgetGUIControls.ui Qmitk/QmitkMaskStampWidgetGUIControls.ui Qmitk/QmitkSliceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSurfaceBasedInterpolatorWidgetGUIControls.ui Qmitk/QmitkSearchLabelDialogGUI.ui ) set(QRC_FILES resources/SegmentationUI.qrc ) diff --git a/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/NavigationStepWidgets/QmitkUSNavigationStepCtUsRegistration.cpp b/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/NavigationStepWidgets/QmitkUSNavigationStepCtUsRegistration.cpp index 456093d420..16b5a9a3b8 100644 --- a/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/NavigationStepWidgets/QmitkUSNavigationStepCtUsRegistration.cpp +++ b/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/NavigationStepWidgets/QmitkUSNavigationStepCtUsRegistration.cpp @@ -1,2014 +1,2014 @@ /*============================================================================ 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 "QmitkUSNavigationStepCtUsRegistration.h" #include "ui_QmitkUSNavigationStepCtUsRegistration.h" #include #include "mitkNodeDisplacementFilter.h" #include "../Filter/mitkFloatingImageToUltrasoundRegistrationFilter.h" #include "../QmitkUSNavigationMarkerPlacement.h" #include #include #include #include "mitkProperties.h" #include #include #include #include #include #include #include #include #include #include #include #include static const int NUMBER_FIDUCIALS_NEEDED = 8; QmitkUSNavigationStepCtUsRegistration::QmitkUSNavigationStepCtUsRegistration(QWidget *parent) : QmitkUSAbstractNavigationStep(parent), ui(new Ui::QmitkUSNavigationStepCtUsRegistration), m_PerformingGroundTruthProtocolEvaluation(false), m_FloatingImageToUltrasoundRegistrationFilter(nullptr), m_FreezeCombinedModality(false) { this->UnsetFloatingImageGeometry(); this->DefineDataStorageImageFilter(); this->CreateQtPartControl(this); } QmitkUSNavigationStepCtUsRegistration::~QmitkUSNavigationStepCtUsRegistration() { delete ui; } bool QmitkUSNavigationStepCtUsRegistration::OnStartStep() { MITK_INFO << "OnStartStep()"; return true; } bool QmitkUSNavigationStepCtUsRegistration::OnStopStep() { MITK_INFO << "OnStopStep()"; return true; } bool QmitkUSNavigationStepCtUsRegistration::OnFinishStep() { MITK_INFO << "OnFinishStep()"; return true; } bool QmitkUSNavigationStepCtUsRegistration::OnActivateStep() { MITK_INFO << "OnActivateStep()"; ui->floatingImageComboBox->SetDataStorage(this->GetDataStorage()); ui->ctImagesToChooseComboBox->SetDataStorage(this->GetDataStorage()); ui->segmentationComboBox->SetDataStorage(this->GetDataStorage()); ui->selectedSurfaceComboBox->SetDataStorage(this->GetDataStorage()); ui->pointSetComboBox->SetDataStorage(this->GetDataStorage()); m_FloatingImageToUltrasoundRegistrationFilter = mitk::FloatingImageToUltrasoundRegistrationFilter::New(); return true; } bool QmitkUSNavigationStepCtUsRegistration::OnDeactivateStep() { MITK_INFO << "OnDeactivateStep()"; return true; } void QmitkUSNavigationStepCtUsRegistration::OnUpdate() { if (m_NavigationDataSource.IsNull()) { return; } m_NavigationDataSource->Update(); m_FloatingImageToUltrasoundRegistrationFilter->Update(); } void QmitkUSNavigationStepCtUsRegistration::OnSettingsChanged(const itk::SmartPointer settingsNode) { Q_UNUSED(settingsNode); } QString QmitkUSNavigationStepCtUsRegistration::GetTitle() { return "CT-to-US registration"; } QmitkUSAbstractNavigationStep::FilterVector QmitkUSNavigationStepCtUsRegistration::GetFilter() { return FilterVector(); } void QmitkUSNavigationStepCtUsRegistration::OnSetCombinedModality() { mitk::AbstractUltrasoundTrackerDevice::Pointer combinedModality = this->GetCombinedModality(false); if (combinedModality.IsNotNull()) { m_NavigationDataSource = combinedModality->GetNavigationDataSource(); } } void QmitkUSNavigationStepCtUsRegistration::UnsetFloatingImageGeometry() { m_ImageDimension[0] = 0; m_ImageDimension[1] = 0; m_ImageDimension[2] = 0; m_ImageSpacing[0] = 1; m_ImageSpacing[1] = 1; m_ImageSpacing[2] = 1; } void QmitkUSNavigationStepCtUsRegistration::SetFloatingImageGeometryInformation(mitk::Image * image) { m_ImageDimension[0] = image->GetDimension(0); m_ImageDimension[1] = image->GetDimension(1); m_ImageDimension[2] = image->GetDimension(2); m_ImageSpacing[0] = image->GetGeometry()->GetSpacing()[0]; m_ImageSpacing[1] = image->GetGeometry()->GetSpacing()[1]; m_ImageSpacing[2] = image->GetGeometry()->GetSpacing()[2]; } double QmitkUSNavigationStepCtUsRegistration::GetVoxelVolume() { if (m_FloatingImage.IsNull()) { return 0.0; } MITK_INFO << "ImageSpacing = " << m_ImageSpacing; return m_ImageSpacing[0] * m_ImageSpacing[1] * m_ImageSpacing[2]; } double QmitkUSNavigationStepCtUsRegistration::GetFiducialVolume(double radius) { return 1.333333333 * 3.141592 * (radius * radius * radius); } bool QmitkUSNavigationStepCtUsRegistration::FilterFloatingImage() { if (m_FloatingImage.IsNull()) { return false; } ImageType::Pointer itkImage1 = ImageType::New(); mitk::CastToItkImage(m_FloatingImage, itkImage1); this->InitializeImageFilters(); m_ThresholdFilter->SetInput(itkImage1); m_LaplacianFilter1->SetInput(m_ThresholdFilter->GetOutput()); m_LaplacianFilter2->SetInput(m_LaplacianFilter1->GetOutput()); m_BinaryThresholdFilter->SetInput(m_LaplacianFilter2->GetOutput()); m_HoleFillingFilter->SetInput(m_BinaryThresholdFilter->GetOutput()); m_BinaryImageToShapeLabelMapFilter->SetInput(m_HoleFillingFilter->GetOutput()); m_BinaryImageToShapeLabelMapFilter->Update(); ImageType::Pointer binaryImage = ImageType::New(); binaryImage = m_HoleFillingFilter->GetOutput(); this->EliminateTooSmallLabeledObjects(binaryImage); //mitk::CastToMitkImage(binaryImage, m_FloatingImage); return true; } void QmitkUSNavigationStepCtUsRegistration::InitializeImageFilters() { //Initialize threshold filters m_ThresholdFilter = itk::ThresholdImageFilter::New(); m_ThresholdFilter->SetOutsideValue(0); m_ThresholdFilter->SetLower(500); m_ThresholdFilter->SetUpper(3200); //Initialize binary threshold filter 1 m_BinaryThresholdFilter = BinaryThresholdImageFilterType::New(); m_BinaryThresholdFilter->SetOutsideValue(0); m_BinaryThresholdFilter->SetInsideValue(1); m_BinaryThresholdFilter->SetLowerThreshold(350); m_BinaryThresholdFilter->SetUpperThreshold(10000); //Initialize laplacian recursive gaussian image filter m_LaplacianFilter1 = LaplacianRecursiveGaussianImageFilterType::New(); m_LaplacianFilter2 = LaplacianRecursiveGaussianImageFilterType::New(); //Initialize binary hole filling filter m_HoleFillingFilter = VotingBinaryIterativeHoleFillingImageFilterType::New(); VotingBinaryIterativeHoleFillingImageFilterType::InputSizeType radius; radius.Fill(1); m_HoleFillingFilter->SetRadius(radius); m_HoleFillingFilter->SetBackgroundValue(0); m_HoleFillingFilter->SetForegroundValue(1); m_HoleFillingFilter->SetMaximumNumberOfIterations(5); //Initialize binary image to shape label map filter m_BinaryImageToShapeLabelMapFilter = BinaryImageToShapeLabelMapFilterType::New(); m_BinaryImageToShapeLabelMapFilter->SetInputForegroundValue(1); } double QmitkUSNavigationStepCtUsRegistration::GetCharacteristicDistanceAWithUpperMargin() { switch (ui->fiducialMarkerConfigurationComboBox->currentIndex()) { // case 0 is equal to fiducial marker configuration A (10mm distance) case 0: return 12.07; // case 1 is equal to fiducial marker configuration B (15mm distance) case 1: return 18.105; // case 2 is equal to fiducial marker configuration C (20mm distance) case 2: return 24.14; } return 0.0; } double QmitkUSNavigationStepCtUsRegistration::GetCharacteristicDistanceBWithLowerMargin() { switch (ui->fiducialMarkerConfigurationComboBox->currentIndex()) { // case 0 is equal to fiducial marker configuration A (10mm distance) case 0: return 12.07; // case 1 is equal to fiducial marker configuration B (15mm distance) case 1: return 18.105; // case 2 is equal to fiducial marker configuration C (20mm distance) case 2: return 24.14; } return 0.0; } double QmitkUSNavigationStepCtUsRegistration::GetCharacteristicDistanceBWithUpperMargin() { switch (ui->fiducialMarkerConfigurationComboBox->currentIndex()) { // case 0 is equal to fiducial marker configuration A (10mm distance) case 0: return 15.73; // case 1 is equal to fiducial marker configuration B (15mm distance) case 1: return 23.595; // case 2 is equal to fiducial marker configuration C (20mm distance) case 2: return 31.46; } return 0.0; } double QmitkUSNavigationStepCtUsRegistration::GetMinimalFiducialConfigurationDistance() { switch (ui->fiducialMarkerConfigurationComboBox->currentIndex()) { // case 0 is equal to fiducial marker configuration A (10mm distance) case 0: return 10.0; // case 1 is equal to fiducial marker configuration B (15mm distance) case 1: return 15.0; // case 2 is equal to fiducial marker configuration C (20mm distance) case 2: return 20.0; } return 0.0; } void QmitkUSNavigationStepCtUsRegistration::CreateMarkerModelCoordinateSystemPointSet() { if (m_MarkerModelCoordinateSystemPointSet.IsNull()) { m_MarkerModelCoordinateSystemPointSet = mitk::PointSet::New(); } else { m_MarkerModelCoordinateSystemPointSet->Clear(); } mitk::Point3D fiducial1; mitk::Point3D fiducial2; mitk::Point3D fiducial3; mitk::Point3D fiducial4; mitk::Point3D fiducial5; mitk::Point3D fiducial6; mitk::Point3D fiducial7; mitk::Point3D fiducial8; switch (ui->fiducialMarkerConfigurationComboBox->currentIndex()) { // case 0 is equal to fiducial marker configuration A (10mm distance) case 0: fiducial1[0] = 0; fiducial1[1] = 0; fiducial1[2] = 0; fiducial2[0] = 0; fiducial2[1] = 10; fiducial2[2] = 0; fiducial3[0] = 10; fiducial3[1] = 0; fiducial3[2] = 0; fiducial4[0] = 20; fiducial4[1] = 20; fiducial4[2] = 0; fiducial5[0] = 0; fiducial5[1] = 20; fiducial5[2] = 10; fiducial6[0] = 10; fiducial6[1] = 20; fiducial6[2] = 10; fiducial7[0] = 20; fiducial7[1] = 10; fiducial7[2] = 10; fiducial8[0] = 20; fiducial8[1] = 0; fiducial8[2] = 10; break; // case 1 is equal to fiducial marker configuration B (15mm distance) case 1: fiducial1[0] = 0; fiducial1[1] = 0; fiducial1[2] = 0; fiducial2[0] = 0; fiducial2[1] = 15; fiducial2[2] = 0; fiducial3[0] = 15; fiducial3[1] = 0; fiducial3[2] = 0; fiducial4[0] = 30; fiducial4[1] = 30; fiducial4[2] = 0; fiducial5[0] = 0; fiducial5[1] = 30; fiducial5[2] = 15; fiducial6[0] = 15; fiducial6[1] = 30; fiducial6[2] = 15; fiducial7[0] = 30; fiducial7[1] = 15; fiducial7[2] = 15; fiducial8[0] = 30; fiducial8[1] = 0; fiducial8[2] = 15; break; // case 2 is equal to fiducial marker configuration C (20mm distance) case 2: fiducial1[0] = 0; fiducial1[1] = 0; fiducial1[2] = 0; fiducial2[0] = 0; fiducial2[1] = 20; fiducial2[2] = 0; fiducial3[0] = 20; fiducial3[1] = 0; fiducial3[2] = 0; fiducial4[0] = 40; fiducial4[1] = 40; fiducial4[2] = 0; fiducial5[0] = 0; fiducial5[1] = 40; fiducial5[2] = 20; fiducial6[0] = 20; fiducial6[1] = 40; fiducial6[2] = 20; fiducial7[0] = 40; fiducial7[1] = 20; fiducial7[2] = 20; fiducial8[0] = 40; fiducial8[1] = 0; fiducial8[2] = 20; break; } m_MarkerModelCoordinateSystemPointSet->InsertPoint(0, fiducial1); m_MarkerModelCoordinateSystemPointSet->InsertPoint(1, fiducial2); m_MarkerModelCoordinateSystemPointSet->InsertPoint(2, fiducial3); m_MarkerModelCoordinateSystemPointSet->InsertPoint(3, fiducial4); m_MarkerModelCoordinateSystemPointSet->InsertPoint(4, fiducial5); m_MarkerModelCoordinateSystemPointSet->InsertPoint(5, fiducial6); m_MarkerModelCoordinateSystemPointSet->InsertPoint(6, fiducial7); m_MarkerModelCoordinateSystemPointSet->InsertPoint(7, fiducial8); /*mitk::DataNode::Pointer node = this->GetDataStorage()->GetNamedNode("Marker Model Coordinate System Point Set"); if (node == nullptr) { node = mitk::DataNode::New(); node->SetName("Marker Model Coordinate System Point Set"); node->SetData(m_MarkerModelCoordinateSystemPointSet); this->GetDataStorage()->Add(node); } else { node->SetData(m_MarkerModelCoordinateSystemPointSet); this->GetDataStorage()->Modified(); }*/ } void QmitkUSNavigationStepCtUsRegistration::InitializePointsToTransformForGroundTruthProtocol() { m_PointsToTransformGroundTruthProtocol.clear(); mitk::Point3D point0mm; mitk::Point3D point20mm; mitk::Point3D point40mm; mitk::Point3D point60mm; mitk::Point3D point80mm; mitk::Point3D point100mm; point0mm[0] = 0.0; point0mm[1] = 0.0; point0mm[2] = 0.0; point20mm[0] = 0.0; point20mm[1] = 0.0; point20mm[2] = 0.0; point40mm[0] = 0.0; point40mm[1] = 0.0; point40mm[2] = 0.0; point60mm[0] = 0.0; point60mm[1] = 0.0; point60mm[2] = 0.0; point80mm[0] = 0.0; point80mm[1] = 0.0; point80mm[2] = 0.0; point100mm[0] = 0.0; point100mm[1] = 0.0; point100mm[2] = 0.0; m_PointsToTransformGroundTruthProtocol.insert(std::pair(0, point0mm)); m_PointsToTransformGroundTruthProtocol.insert(std::pair(20, point20mm)); m_PointsToTransformGroundTruthProtocol.insert(std::pair(40, point40mm)); m_PointsToTransformGroundTruthProtocol.insert(std::pair(60, point60mm)); m_PointsToTransformGroundTruthProtocol.insert(std::pair(80, point80mm)); m_PointsToTransformGroundTruthProtocol.insert(std::pair(100, point100mm)); } void QmitkUSNavigationStepCtUsRegistration::CreatePointsToTransformForGroundTruthProtocol() { this->InitializePointsToTransformForGroundTruthProtocol(); switch (ui->fiducialMarkerConfigurationComboBox->currentIndex()) { // case 0 is equal to fiducial marker configuration A (10mm distance) case 0: MITK_WARN << "For this marker configuration (10mm) there does not exist a point to transform."; break; // case 1 is equal to fiducial marker configuration B (15mm distance) case 1: m_PointsToTransformGroundTruthProtocol.at(0)[0] = 130; // = 30mm to end of clipping plate + 100 mm to middle axis of measurement plate m_PointsToTransformGroundTruthProtocol.at(0)[1] = 15; m_PointsToTransformGroundTruthProtocol.at(0)[2] = -7; // = 5mm distance to clipping plate + 2mm to base m_PointsToTransformGroundTruthProtocol.at(20)[0] = 130; m_PointsToTransformGroundTruthProtocol.at(20)[1] = 15; m_PointsToTransformGroundTruthProtocol.at(20)[2] = -27; // = 5mm distance to clipping plate + 2mm to base + 20mm depth m_PointsToTransformGroundTruthProtocol.at(40)[0] = 130; m_PointsToTransformGroundTruthProtocol.at(40)[1] = 15; m_PointsToTransformGroundTruthProtocol.at(40)[2] = -47; // = 5mm distance to clipping plate + 2mm to base + 40mm depth m_PointsToTransformGroundTruthProtocol.at(60)[0] = 130; m_PointsToTransformGroundTruthProtocol.at(60)[1] = 15; m_PointsToTransformGroundTruthProtocol.at(60)[2] = -67; // = 5mm distance to clipping plate + 2mm to base + 60mm depth m_PointsToTransformGroundTruthProtocol.at(80)[0] = 130; m_PointsToTransformGroundTruthProtocol.at(80)[1] = 15; m_PointsToTransformGroundTruthProtocol.at(80)[2] = -87; // = 5mm distance to clipping plate + 2mm to base + 80mm depth m_PointsToTransformGroundTruthProtocol.at(100)[0] = 130; m_PointsToTransformGroundTruthProtocol.at(100)[1] = 15; m_PointsToTransformGroundTruthProtocol.at(100)[2] = -107; // = 5mm distance to clipping plate + 2mm to base + 100mm depth break; // case 2 is equal to fiducial marker configuration C (20mm distance) case 2: m_PointsToTransformGroundTruthProtocol.at(0)[0] = 135; // = 20 + 15mm to end of clipping plate + 100 mm to middle axis of measurement plate m_PointsToTransformGroundTruthProtocol.at(0)[1] = 20; m_PointsToTransformGroundTruthProtocol.at(0)[2] = -9; // = 7mm distance to clipping plate + 2mm to base m_PointsToTransformGroundTruthProtocol.at(20)[0] = 135; m_PointsToTransformGroundTruthProtocol.at(20)[1] = 20; m_PointsToTransformGroundTruthProtocol.at(20)[2] = -29; // = 7mm distance to clipping plate + 2mm to base + 20mm depth m_PointsToTransformGroundTruthProtocol.at(40)[0] = 135; m_PointsToTransformGroundTruthProtocol.at(40)[1] = 20; m_PointsToTransformGroundTruthProtocol.at(40)[2] = -49; // = 7mm distance to clipping plate + 2mm to base + 40mm depth m_PointsToTransformGroundTruthProtocol.at(60)[0] = 135; m_PointsToTransformGroundTruthProtocol.at(60)[1] = 20; m_PointsToTransformGroundTruthProtocol.at(60)[2] = -69; // = 7mm distance to clipping plate + 2mm to base + 60mm depth m_PointsToTransformGroundTruthProtocol.at(80)[0] = 135; m_PointsToTransformGroundTruthProtocol.at(80)[1] = 20; m_PointsToTransformGroundTruthProtocol.at(80)[2] = -89; // = 7mm distance to clipping plate + 2mm to base + 80mm depth m_PointsToTransformGroundTruthProtocol.at(100)[0] = 135; m_PointsToTransformGroundTruthProtocol.at(100)[1] = 20; m_PointsToTransformGroundTruthProtocol.at(100)[2] = -109; // = 7mm distance to clipping plate + 2mm to base + 100mm depth break; } } void QmitkUSNavigationStepCtUsRegistration::TransformPointsGroundTruthProtocol() { if (m_GroundTruthProtocolTransformedPoints.find(0) == m_GroundTruthProtocolTransformedPoints.end()) { mitk::PointSet::Pointer pointSet = mitk::PointSet::New(); pointSet->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(0))); m_GroundTruthProtocolTransformedPoints.insert(std::pair(0, pointSet)); } else { m_GroundTruthProtocolTransformedPoints.at(0)->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(0))); } if (m_GroundTruthProtocolTransformedPoints.find(20) == m_GroundTruthProtocolTransformedPoints.end()) { mitk::PointSet::Pointer pointSet = mitk::PointSet::New(); pointSet->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(20))); m_GroundTruthProtocolTransformedPoints.insert(std::pair(20, pointSet)); } else { m_GroundTruthProtocolTransformedPoints.at(20)->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(20))); } if (m_GroundTruthProtocolTransformedPoints.find(40) == m_GroundTruthProtocolTransformedPoints.end()) { mitk::PointSet::Pointer pointSet = mitk::PointSet::New(); pointSet->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(40))); m_GroundTruthProtocolTransformedPoints.insert(std::pair(40, pointSet)); } else { m_GroundTruthProtocolTransformedPoints.at(40)->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(40))); } if (m_GroundTruthProtocolTransformedPoints.find(60) == m_GroundTruthProtocolTransformedPoints.end()) { mitk::PointSet::Pointer pointSet = mitk::PointSet::New(); pointSet->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(60))); m_GroundTruthProtocolTransformedPoints.insert(std::pair(60, pointSet)); } else { m_GroundTruthProtocolTransformedPoints.at(60)->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(60))); } if (m_GroundTruthProtocolTransformedPoints.find(80) == m_GroundTruthProtocolTransformedPoints.end()) { mitk::PointSet::Pointer pointSet = mitk::PointSet::New(); pointSet->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(80))); m_GroundTruthProtocolTransformedPoints.insert(std::pair(80, pointSet)); } else { m_GroundTruthProtocolTransformedPoints.at(80)->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(80))); } if (m_GroundTruthProtocolTransformedPoints.find(100) == m_GroundTruthProtocolTransformedPoints.end()) { mitk::PointSet::Pointer pointSet = mitk::PointSet::New(); pointSet->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(100))); m_GroundTruthProtocolTransformedPoints.insert(std::pair(100, pointSet)); } else { m_GroundTruthProtocolTransformedPoints.at(100)->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(m_PointsToTransformGroundTruthProtocol.at(100))); } } void QmitkUSNavigationStepCtUsRegistration::AddTransformedPointsToDataStorage() { if (m_GroundTruthProtocolTransformedPoints.find(0) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(20) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(40) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(60) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(80) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(100) == m_GroundTruthProtocolTransformedPoints.end()) { QMessageBox msgBox; msgBox.setText("Cannot add transformed Points to DataStorage because they do not exist.\ Stopping evaluation the protocol."); msgBox.exec(); return; } std::string nameNode0mm = "GroundTruthProt-Depth0mm"; std::string nameNode20mm = "GroundTruthProt-Depth20mm"; std::string nameNode40mm = "GroundTruthProt-Depth40mm"; std::string nameNode60mm = "GroundTruthProt-Depth60mm"; std::string nameNode80mm = "GroundTruthProt-Depth80mm"; std::string nameNode100mm = "GroundTruthProt-Depth100mm"; //Add transformed points of depth 0mm to the data storage mitk::DataNode::Pointer node0mm = this->GetDataStorage()->GetNamedNode(nameNode0mm); if (node0mm.IsNull()) { node0mm = mitk::DataNode::New(); node0mm->SetName(nameNode0mm); node0mm->SetData(m_GroundTruthProtocolTransformedPoints.at(0)); this->GetDataStorage()->Add(node0mm); } else { node0mm->SetData(m_GroundTruthProtocolTransformedPoints.at(0)); this->GetDataStorage()->Modified(); } if(ui->protocolEvaluationTypeComboBox->currentText().compare("PLANE") == 0 ) { //Add transformed points of depth 20mm to the data storage mitk::DataNode::Pointer node20mm = this->GetDataStorage()->GetNamedNode(nameNode20mm); if (node20mm.IsNull()) { node20mm = mitk::DataNode::New(); node20mm->SetName(nameNode20mm); node20mm->SetData(m_GroundTruthProtocolTransformedPoints.at(20)); this->GetDataStorage()->Add(node20mm); } else { node20mm->SetData(m_GroundTruthProtocolTransformedPoints.at(20)); this->GetDataStorage()->Modified(); } //Add transformed points of depth 40mm to the data storage mitk::DataNode::Pointer node40mm = this->GetDataStorage()->GetNamedNode(nameNode40mm); if (node40mm.IsNull()) { node40mm = mitk::DataNode::New(); node40mm->SetName(nameNode40mm); node40mm->SetData(m_GroundTruthProtocolTransformedPoints.at(40)); this->GetDataStorage()->Add(node40mm); } else { node40mm->SetData(m_GroundTruthProtocolTransformedPoints.at(40)); this->GetDataStorage()->Modified(); } //Add transformed points of depth 60mm to the data storage mitk::DataNode::Pointer node60mm = this->GetDataStorage()->GetNamedNode(nameNode60mm); if (node60mm.IsNull()) { node60mm = mitk::DataNode::New(); node60mm->SetName(nameNode60mm); node60mm->SetData(m_GroundTruthProtocolTransformedPoints.at(60)); this->GetDataStorage()->Add(node60mm); } else { node60mm->SetData(m_GroundTruthProtocolTransformedPoints.at(60)); this->GetDataStorage()->Modified(); } //Add transformed points of depth 80mm to the data storage mitk::DataNode::Pointer node80mm = this->GetDataStorage()->GetNamedNode(nameNode80mm); if (node80mm.IsNull()) { node80mm = mitk::DataNode::New(); node80mm->SetName(nameNode80mm); node80mm->SetData(m_GroundTruthProtocolTransformedPoints.at(80)); this->GetDataStorage()->Add(node80mm); } else { node80mm->SetData(m_GroundTruthProtocolTransformedPoints.at(80)); this->GetDataStorage()->Modified(); } //Add transformed points of depth 100mm to the data storage mitk::DataNode::Pointer node100mm = this->GetDataStorage()->GetNamedNode(nameNode100mm); if (node100mm.IsNull()) { node100mm = mitk::DataNode::New(); node100mm->SetName(nameNode100mm); node100mm->SetData(m_GroundTruthProtocolTransformedPoints.at(100)); this->GetDataStorage()->Add(node100mm); } else { node100mm->SetData(m_GroundTruthProtocolTransformedPoints.at(100)); this->GetDataStorage()->Modified(); } } //Do a global reinit mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(this->GetDataStorage()); } double QmitkUSNavigationStepCtUsRegistration::CalculateMeanFRE() { double meanFRE = 0.0; for (unsigned int counter = 0; counter < m_GroundTruthProtocolFRE.size(); ++counter) { meanFRE += m_GroundTruthProtocolFRE[counter]; } return meanFRE / m_GroundTruthProtocolFRE.size(); } double QmitkUSNavigationStepCtUsRegistration::CalculateStandardDeviationOfFRE(double meanFRE) { double variance = 0.0; for (unsigned int counter = 0; counter < m_GroundTruthProtocolFRE.size(); ++counter) { variance += ((meanFRE - m_GroundTruthProtocolFRE[counter]) * (meanFRE - m_GroundTruthProtocolFRE[counter])); } variance /= m_GroundTruthProtocolFRE.size(); // calculate the empirical variance (n) and not the sampling variance (n-1) return sqrt(variance); } void QmitkUSNavigationStepCtUsRegistration::CalculateGroundTruthProtocolTRE() { if (m_GroundTruthProtocolTransformedPoints.find(0) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(20) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(40) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(60) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(80) == m_GroundTruthProtocolTransformedPoints.end() || m_GroundTruthProtocolTransformedPoints.find(100) == m_GroundTruthProtocolTransformedPoints.end()) { QMessageBox msgBox; msgBox.setText("Cannot calculate TRE of Ground-Truth-Protocol because points were not transformed."); msgBox.exec(); return; } // clear the std::map containing possibly data of earlier TRE calculations m_GroundTruthProtocolTRE.clear(); // loop through all existing point sets containing the transformed points for (int counter = 0; m_GroundTruthProtocolTransformedPoints.find(counter) != m_GroundTruthProtocolTransformedPoints.end(); counter += 20) { //calculate the middle point of the point set mitk::PointSet::Pointer pointSet = m_GroundTruthProtocolTransformedPoints.at(counter); mitk::Point3D middlePoint; middlePoint[0] = 0.0; middlePoint[1] = 0.0; middlePoint[2] = 0.0; for (int position = 0; position < pointSet->GetSize(); ++position) { middlePoint[0] += pointSet->GetPoint(position)[0]; middlePoint[1] += pointSet->GetPoint(position)[1]; middlePoint[2] += pointSet->GetPoint(position)[2]; } middlePoint[0] /= pointSet->GetSize(); middlePoint[1] /= pointSet->GetSize(); middlePoint[2] /= pointSet->GetSize(); MITK_INFO << "Calculated MiddlePoint: " << middlePoint; //sum up the euclidean distances between the middle point and each transformed point double meanDistance = 0.0; for (int position = 0; position < pointSet->GetSize(); ++position) { meanDistance += middlePoint.SquaredEuclideanDistanceTo(pointSet->GetPoint(position)); MITK_INFO << "SquaredEuclideanDistance: " << middlePoint.SquaredEuclideanDistanceTo(pointSet->GetPoint(position)); } meanDistance /= pointSet->GetSize(); // this can be interpreted as empirical variance // the root of the empirical variance can be interpreted as the protocols registration TRE m_GroundTruthProtocolTRE.insert(std::pair(counter, sqrt(meanDistance))); MITK_INFO << "Ground-Truth-Protocol TRE: " << sqrt(meanDistance); } } void QmitkUSNavigationStepCtUsRegistration::EliminateTooSmallLabeledObjects( ImageType::Pointer binaryImage) { BinaryImageToShapeLabelMapFilterType::OutputImageType::Pointer labelMap = m_BinaryImageToShapeLabelMapFilter->GetOutput(); double voxelVolume = this->GetVoxelVolume(); double fiducialVolume; unsigned int numberOfPixels; if (ui->fiducialDiameter3mmRadioButton->isChecked()) { fiducialVolume = this->GetFiducialVolume(1.5); numberOfPixels = ceil(fiducialVolume / voxelVolume); } else { fiducialVolume = this->GetFiducialVolume(2.5); numberOfPixels = ceil(fiducialVolume / voxelVolume); } MITK_INFO << "Voxel Volume = " << voxelVolume << "; Fiducial Volume = " << fiducialVolume; MITK_INFO << "Number of pixels = " << numberOfPixels; labelMap = m_BinaryImageToShapeLabelMapFilter->GetOutput(); // The output of this filter is an itk::LabelMap, which contains itk::LabelObject's MITK_INFO << "There are " << labelMap->GetNumberOfLabelObjects() << " objects."; // Loop over each region for (int i = labelMap->GetNumberOfLabelObjects() - 1; i >= 0; --i) { // Get the ith region BinaryImageToShapeLabelMapFilterType::OutputImageType::LabelObjectType* labelObject = labelMap->GetNthLabelObject(i); MITK_INFO << "Object " << i << " contains " << labelObject->Size() << " pixel"; //TODO: Threshold-Wert evtl. experimentell besser abstimmen, // um zu verhindern, dass durch Threshold wahre Fiducial-Kandidaten elimiert werden. if (labelObject->Size() < numberOfPixels * 0.8) { for (unsigned int pixelId = 0; pixelId < labelObject->Size(); pixelId++) { binaryImage->SetPixel(labelObject->GetIndex(pixelId), 0); } labelMap->RemoveLabelObject(labelObject); } } } bool QmitkUSNavigationStepCtUsRegistration::EliminateFiducialCandidatesByEuclideanDistances() { if (m_CentroidsOfFiducialCandidates.size() < NUMBER_FIDUCIALS_NEEDED) { return false; } for (unsigned int counter = 0; counter < m_CentroidsOfFiducialCandidates.size(); ++counter) { int amountOfAcceptedFiducials = 0; mitk::Point3D fiducialCentroid(m_CentroidsOfFiducialCandidates.at(counter)); //Loop through all fiducial candidates and calculate the distance between the chosen fiducial // candidate and the other candidates. For each candidate with a right distance between // Configuration A: 7.93mm and 31.0mm (10 mm distance between fiducial centers) or // Configuration B: 11.895mm and 45.0mm (15 mm distance between fiducial centers) or // Configuration C: 15.86mm and 59.0mm (20 mm distance between fiducial centers) // // increase the amountOfAcceptedFiducials. for (unsigned int position = 0; position < m_CentroidsOfFiducialCandidates.size(); ++position) { if (position == counter) { continue; } mitk::Point3D otherCentroid(m_CentroidsOfFiducialCandidates.at(position)); double distance = fiducialCentroid.EuclideanDistanceTo(otherCentroid); switch (ui->fiducialMarkerConfigurationComboBox->currentIndex()) { // case 0 is equal to fiducial marker configuration A (10mm distance) case 0: if (distance > 7.93 && distance < 31.0) { ++amountOfAcceptedFiducials; } break; // case 1 is equal to fiducial marker configuration B (15mm distance) case 1: if (distance > 11.895 && distance < 45.0) { ++amountOfAcceptedFiducials; } break; // case 2 is equal to fiducial marker configuration C (20mm distance) case 2: if (distance > 15.86 && distance < 59.0) { ++amountOfAcceptedFiducials; } break; } } //The amountOfAcceptedFiducials must be at least 7. Otherwise delete the fiducial candidate // from the list of candidates. if (amountOfAcceptedFiducials < NUMBER_FIDUCIALS_NEEDED - 1) { MITK_INFO << "Deleting fiducial candidate at position: " << m_CentroidsOfFiducialCandidates.at(counter); m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + counter); if (m_CentroidsOfFiducialCandidates.size() < NUMBER_FIDUCIALS_NEEDED ) { return false; } counter = -1; } } //Classify the rested fiducial candidates by its characteristic Euclidean distances // between the canidates and remove all candidates with a false distance configuration: this->ClassifyFiducialCandidates(); return true; } void QmitkUSNavigationStepCtUsRegistration::ClassifyFiducialCandidates() { MITK_INFO << "ClassifyFiducialCandidates()"; std::vector fiducialCandidatesToBeRemoved; std::vector> distanceVectorsFiducials; this->CalculateDistancesBetweenFiducials(distanceVectorsFiducials); for (unsigned int counter = 0; counter < distanceVectorsFiducials.size(); ++counter) { int distanceA = 0; // => 10,00mm distance int distanceB = 0; // => 14,14mm distance int distanceC = 0; // => 17,32mm distance int distanceD = 0; // => 22,36mm distance int distanceE = 0; // => 24,49mm distance int distanceF = 0; // => 28,28mm distance std::vector &distances = distanceVectorsFiducials.at(counter); for (unsigned int number = 0; number < distances.size(); ++number) { double &distance = distances.at(number); switch (ui->fiducialMarkerConfigurationComboBox->currentIndex()) { // case 0 is equal to fiducial marker configuration A (10mm distance) case 0: if (distance > 7.93 && distance <= 12.07) { ++distanceA; } else if (distance > 12.07 && distance <= 15.73) { ++distanceB; } else if (distance > 15.73 && distance <= 19.84) { ++distanceC; } else if (distance > 19.84 && distance <= 23.425) { ++distanceD; } else if (distance > 23.425 && distance <= 26.385) { ++distanceE; } else if (distance > 26.385 && distance <= 31.00) { ++distanceF; } break; // case 1 is equal to fiducial marker configuration B (15mm distance) case 1: if (distance > 11.895 && distance <= 18.105) { ++distanceA; } else if (distance > 18.105 && distance <= 23.595) { ++distanceB; } else if (distance > 23.595 && distance <= 29.76) { ++distanceC; } else if (distance > 29.76 && distance <= 35.1375) { ++distanceD; if (distance > 33.54) { ++distanceE; } } else if (distance > 35.1375 && distance <= 39.5775) { ++distanceE; if (distance < 36.735) { ++distanceD; } } else if (distance > 39.5775 && distance <= 45.00) { ++distanceF; } break; // case 2 is equal to fiducial marker configuration C (20mm distance) case 2: if (distance > 15.86 && distance <= 24.14) { ++distanceA; } else if (distance > 24.14 && distance <= 31.46) { ++distanceB; } else if (distance > 31.46 && distance <= 39.68) { ++distanceC; } else if (distance > 39.68 && distance <= 46.85) { ++distanceD; } else if (distance > 46.85 && distance <= 52.77) { ++distanceE; } else if (distance > 52.77 && distance <= 59.00) { ++distanceF; } break; } }// End for-loop distances-vector //Now, having looped through all distances of one fiducial candidate, check // if the combination of different distances is known. The >= is due to the // possible occurrence of other fiducial candidates that have an distance equal to // one of the distances A - E. However, false fiducial candidates outside // the fiducial marker does not have the right distance configuration: if (((distanceA >= 2 && distanceD >= 2 && distanceE >= 2 && distanceF >= 1) || (distanceA >= 1 && distanceB >= 2 && distanceC >= 1 && distanceD >= 2 && distanceE >= 1) || (distanceB >= 2 && distanceD >= 4 && distanceF >= 1) || (distanceA >= 1 && distanceB >= 1 && distanceD >= 3 && distanceE >= 1 && distanceF >= 1)) == false) { MITK_INFO << "Detected fiducial candidate with unknown distance configuration."; fiducialCandidatesToBeRemoved.push_back(counter); } } for (int count = fiducialCandidatesToBeRemoved.size() - 1; count >= 0; --count) { MITK_INFO << "Removing fiducial candidate " << fiducialCandidatesToBeRemoved.at(count); m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + fiducialCandidatesToBeRemoved.at(count)); } } void QmitkUSNavigationStepCtUsRegistration::GetCentroidsOfLabeledObjects() { MITK_INFO << "GetCentroidsOfLabeledObjects()"; BinaryImageToShapeLabelMapFilterType::OutputImageType::Pointer labelMap = m_BinaryImageToShapeLabelMapFilter->GetOutput(); for (int i = labelMap->GetNumberOfLabelObjects() - 1; i >= 0; --i) { // Get the ith region BinaryImageToShapeLabelMapFilterType::OutputImageType::LabelObjectType* labelObject = labelMap->GetNthLabelObject(i); MITK_INFO << "Object " << i << " contains " << labelObject->Size() << " pixel"; mitk::Vector3D centroid; centroid[0] = labelObject->GetCentroid()[0]; centroid[1] = labelObject->GetCentroid()[1]; centroid[2] = labelObject->GetCentroid()[2]; m_CentroidsOfFiducialCandidates.push_back(centroid); } //evtl. for later: itk::LabelMapOverlayImageFilter } void QmitkUSNavigationStepCtUsRegistration::NumerateFiducialMarks() { MITK_INFO << "NumerateFiducialMarks()"; bool successFiducialNo1; bool successFiducialNo4; bool successFiducialNo2And3; bool successFiducialNo5; bool successFiducialNo8; bool successFiducialNo6; bool successFiducialNo7; std::vector> distanceVectorsFiducials; this->CalculateDistancesBetweenFiducials(distanceVectorsFiducials); successFiducialNo1 = this->FindFiducialNo1(distanceVectorsFiducials); successFiducialNo4 = this->FindFiducialNo4(distanceVectorsFiducials); successFiducialNo2And3 = this->FindFiducialNo2And3(); successFiducialNo5 = this->FindFiducialNo5(); successFiducialNo8 = this->FindFiducialNo8(); successFiducialNo6 = this->FindFiducialNo6(); successFiducialNo7 = this->FindFiducialNo7(); if (!successFiducialNo1 || !successFiducialNo4 || !successFiducialNo2And3 || !successFiducialNo5 || !successFiducialNo8 || !successFiducialNo6 || !successFiducialNo7) { QMessageBox msgBox; msgBox.setText("Cannot numerate/localize all fiducials successfully."); msgBox.exec(); return; } if (m_MarkerFloatingImageCoordinateSystemPointSet.IsNull()) { m_MarkerFloatingImageCoordinateSystemPointSet = mitk::PointSet::New(); } else if (m_MarkerFloatingImageCoordinateSystemPointSet->GetSize() != 0) { m_MarkerFloatingImageCoordinateSystemPointSet->Clear(); } for (unsigned int counter = 1; counter <= m_FiducialMarkerCentroids.size(); ++counter) { - m_MarkerFloatingImageCoordinateSystemPointSet->InsertPoint(counter - 1, m_FiducialMarkerCentroids.at(counter)); + m_MarkerFloatingImageCoordinateSystemPointSet->InsertPoint(counter - 1, mitk::Point3D(m_FiducialMarkerCentroids.at(counter))); } if( !m_PerformingGroundTruthProtocolEvaluation ) { mitk::DataNode::Pointer node = mitk::DataNode::New(); node->SetData(m_MarkerFloatingImageCoordinateSystemPointSet); node->SetName("MarkerFloatingImageCSPointSet"); //node->SetFloatProperty("pointsize", 5.0); this->GetDataStorage()->Add(node); } } void QmitkUSNavigationStepCtUsRegistration::CalculateDistancesBetweenFiducials(std::vector>& distanceVectorsFiducials) { std::vector distancesBetweenFiducials; for (unsigned int i = 0; i < m_CentroidsOfFiducialCandidates.size(); ++i) { distancesBetweenFiducials.clear(); mitk::Point3D fiducialCentroid(m_CentroidsOfFiducialCandidates.at(i)); for (unsigned int n = 0; n < m_CentroidsOfFiducialCandidates.size(); ++n) { mitk::Point3D otherCentroid(m_CentroidsOfFiducialCandidates.at(n)); distancesBetweenFiducials.push_back(fiducialCentroid.EuclideanDistanceTo(otherCentroid)); } //Sort the distances from low to big numbers std::sort(distancesBetweenFiducials.begin(), distancesBetweenFiducials.end()); //First entry of the distance vector must be 0, so erase it if (distancesBetweenFiducials.at(0) == 0.0) { distancesBetweenFiducials.erase(distancesBetweenFiducials.begin()); } //Add the distance vector to the collecting distances vector distanceVectorsFiducials.push_back(distancesBetweenFiducials); } for (unsigned int i = 0; i < distanceVectorsFiducials.size(); ++i) { MITK_INFO << "Vector " << i << ":"; for (unsigned int k = 0; k < distanceVectorsFiducials.at(i).size(); ++k) { MITK_INFO << distanceVectorsFiducials.at(i).at(k); } } } bool QmitkUSNavigationStepCtUsRegistration::FindFiducialNo1(std::vector>& distanceVectorsFiducials) { for (unsigned int i = 0; i < distanceVectorsFiducials.size(); ++i) { std::vector &distances = distanceVectorsFiducials.at(i); if (distances.size() < NUMBER_FIDUCIALS_NEEDED - 1 ) { MITK_WARN << "Cannot find fiducial 1, there aren't found enough fiducial candidates."; return false; } double characteristicDistanceAWithUpperMargin = this->GetCharacteristicDistanceAWithUpperMargin(); if (distances.at(0) <= characteristicDistanceAWithUpperMargin && distances.at(1) <= characteristicDistanceAWithUpperMargin) { MITK_INFO << "Found Fiducial 1 (PointSet number " << i << ")"; m_FiducialMarkerCentroids.insert( std::pair(1, m_CentroidsOfFiducialCandidates.at(i))); distanceVectorsFiducials.erase(distanceVectorsFiducials.begin() + i); m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + i); return true; } } return false; } bool QmitkUSNavigationStepCtUsRegistration::FindFiducialNo2And3() { if (m_FiducialMarkerCentroids.find(1) == m_FiducialMarkerCentroids.end() ) { MITK_WARN << "Cannot find fiducial No 2 and 3. Before must be found fiducial No 1."; return false; } mitk::Point3D fiducialNo1(m_FiducialMarkerCentroids.at(1)); mitk::Vector3D fiducialVectorA; mitk::Vector3D fiducialVectorB; mitk::Point3D fiducialPointA; mitk::Point3D fiducialPointB; bool foundFiducialA = false; bool foundFiducialB = false; mitk::Vector3D vectorFiducial1ToFiducialA; mitk::Vector3D vectorFiducial1ToFiducialB; for (unsigned int i = 0; i < m_CentroidsOfFiducialCandidates.size(); ++i) { mitk::Point3D fiducialCentroid(m_CentroidsOfFiducialCandidates.at(i)); double distance = fiducialNo1.EuclideanDistanceTo(fiducialCentroid); if (distance <= this->GetCharacteristicDistanceAWithUpperMargin()) { fiducialVectorA = m_CentroidsOfFiducialCandidates.at(i); fiducialPointA = fiducialCentroid; m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + i); foundFiducialA = true; break; } } for (unsigned int i = 0; i < m_CentroidsOfFiducialCandidates.size(); ++i) { mitk::Point3D fiducialCentroid(m_CentroidsOfFiducialCandidates.at(i)); double distance = fiducialNo1.EuclideanDistanceTo(fiducialCentroid); if (distance <= this->GetCharacteristicDistanceAWithUpperMargin()) { fiducialVectorB = m_CentroidsOfFiducialCandidates.at(i); fiducialPointB = fiducialCentroid; m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + i); foundFiducialB = true; break; } } if (!foundFiducialA || !foundFiducialB) { MITK_WARN << "Cannot identify fiducial candidates 2 and 3"; return false; } else if (m_CentroidsOfFiducialCandidates.size() == 0) { MITK_WARN << "Too less fiducials detected. Cannot identify fiducial candidates 2 and 3"; return false; } vectorFiducial1ToFiducialA = fiducialVectorA - m_FiducialMarkerCentroids.at(1); vectorFiducial1ToFiducialB = fiducialVectorB - m_FiducialMarkerCentroids.at(1); vnl_vector crossProductVnl = vnl_cross_3d(vectorFiducial1ToFiducialA.GetVnlVector(), vectorFiducial1ToFiducialB.GetVnlVector()); mitk::Vector3D crossProduct; crossProduct.SetVnlVector(crossProductVnl); mitk::Vector3D vectorFiducial1ToRandomLeftFiducial = m_CentroidsOfFiducialCandidates.at(0) - m_FiducialMarkerCentroids.at(1); double scalarProduct = (crossProduct * vectorFiducial1ToRandomLeftFiducial) / (crossProduct.GetNorm() * vectorFiducial1ToRandomLeftFiducial.GetNorm()); double alpha = acos(scalarProduct) * 57.29578; //Transform into degree MITK_INFO << "Scalar Product = " << alpha; if (alpha <= 90) { m_FiducialMarkerCentroids[3] = fiducialVectorA; m_FiducialMarkerCentroids[2] = fiducialVectorB; } else { m_FiducialMarkerCentroids[2] = fiducialVectorA; m_FiducialMarkerCentroids[3] = fiducialVectorB; } MITK_INFO << "Found Fiducial 2, PointSet: " << m_FiducialMarkerCentroids.at(2); MITK_INFO << "Found Fiducial 3, PointSet: " << m_FiducialMarkerCentroids.at(3); return true; } bool QmitkUSNavigationStepCtUsRegistration::FindFiducialNo4(std::vector>& distanceVectorsFiducials) { double characteristicDistanceBWithLowerMargin = this->GetCharacteristicDistanceBWithLowerMargin(); double characteristicDistanceBWithUpperMargin = this->GetCharacteristicDistanceBWithUpperMargin(); for (unsigned int i = 0; i < distanceVectorsFiducials.size(); ++i) { std::vector &distances = distanceVectorsFiducials.at(i); if (distances.size() < NUMBER_FIDUCIALS_NEEDED - 1) { MITK_WARN << "Cannot find fiducial 4, there aren't found enough fiducial candidates."; return false; } if (distances.at(0) > characteristicDistanceBWithLowerMargin && distances.at(0) <= characteristicDistanceBWithUpperMargin && distances.at(1) > characteristicDistanceBWithLowerMargin && distances.at(1) <= characteristicDistanceBWithUpperMargin) { MITK_INFO << "Found Fiducial 4 (PointSet number " << i << ")"; m_FiducialMarkerCentroids.insert(std::pair(4, m_CentroidsOfFiducialCandidates.at(i))); distanceVectorsFiducials.erase(distanceVectorsFiducials.begin() + i); m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + i); return true; } } return false; } bool QmitkUSNavigationStepCtUsRegistration::FindFiducialNo5() { if (m_FiducialMarkerCentroids.find(2) == m_FiducialMarkerCentroids.end()) { MITK_WARN << "To find fiducial No 5, fiducial No 2 has to be found before."; return false; } double characteristicDistanceBWithUpperMargin = this->GetCharacteristicDistanceBWithUpperMargin(); mitk::Point3D fiducialNo2(m_FiducialMarkerCentroids.at(2)); for (unsigned int counter = 0; counter < m_CentroidsOfFiducialCandidates.size(); ++counter) { mitk::Point3D fiducialCentroid(m_CentroidsOfFiducialCandidates.at(counter)); double distance = fiducialNo2.EuclideanDistanceTo(fiducialCentroid); if (distance <= characteristicDistanceBWithUpperMargin) { m_FiducialMarkerCentroids[5] = m_CentroidsOfFiducialCandidates.at(counter); m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + counter); MITK_INFO << "Found Fiducial No 5, PointSet: " << m_FiducialMarkerCentroids[5]; return true; } } MITK_WARN << "Cannot find fiducial No 5."; return false; } bool QmitkUSNavigationStepCtUsRegistration::FindFiducialNo6() { if (m_FiducialMarkerCentroids.find(5) == m_FiducialMarkerCentroids.end()) { MITK_WARN << "To find fiducial No 6, fiducial No 5 has to be found before."; return false; } double characteristicDistanceAWithUpperMargin = this->GetCharacteristicDistanceAWithUpperMargin(); mitk::Point3D fiducialNo5(m_FiducialMarkerCentroids.at(5)); for (unsigned int counter = 0; counter < m_CentroidsOfFiducialCandidates.size(); ++counter) { mitk::Point3D fiducialCentroid(m_CentroidsOfFiducialCandidates.at(counter)); double distance = fiducialNo5.EuclideanDistanceTo(fiducialCentroid); if (distance <= characteristicDistanceAWithUpperMargin) { m_FiducialMarkerCentroids[6] = m_CentroidsOfFiducialCandidates.at(counter); m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + counter); MITK_INFO << "Found Fiducial No 6, PointSet: " << m_FiducialMarkerCentroids[6]; return true; } } MITK_WARN << "Cannot find fiducial No 6."; return false; } bool QmitkUSNavigationStepCtUsRegistration::FindFiducialNo7() { if (m_FiducialMarkerCentroids.find(8) == m_FiducialMarkerCentroids.end()) { MITK_WARN << "To find fiducial No 7, fiducial No 8 has to be found before."; return false; } double characteristicDistanceAWithUpperMargin = this->GetCharacteristicDistanceAWithUpperMargin(); mitk::Point3D fiducialNo8(m_FiducialMarkerCentroids.at(8)); for (unsigned int counter = 0; counter < m_CentroidsOfFiducialCandidates.size(); ++counter) { mitk::Point3D fiducialCentroid(m_CentroidsOfFiducialCandidates.at(counter)); double distance = fiducialNo8.EuclideanDistanceTo(fiducialCentroid); if (distance <= characteristicDistanceAWithUpperMargin) { m_FiducialMarkerCentroids[7] = m_CentroidsOfFiducialCandidates.at(counter); m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + counter); MITK_INFO << "Found Fiducial No 7, PointSet: " << m_FiducialMarkerCentroids[7]; return true; } } MITK_WARN << "Cannot find fiducial No 7."; return false; } bool QmitkUSNavigationStepCtUsRegistration::FindFiducialNo8() { if (m_FiducialMarkerCentroids.find(3) == m_FiducialMarkerCentroids.end()) { MITK_WARN << "To find fiducial No 8, fiducial No 3 has to be found before."; return false; } double characteristicDistanceBWithUpperMargin = this->GetCharacteristicDistanceBWithUpperMargin(); mitk::Point3D fiducialNo3(m_FiducialMarkerCentroids.at(3)); for (unsigned int counter = 0; counter < m_CentroidsOfFiducialCandidates.size(); ++counter) { mitk::Point3D fiducialCentroid(m_CentroidsOfFiducialCandidates.at(counter)); double distance = fiducialNo3.EuclideanDistanceTo(fiducialCentroid); if (distance <= characteristicDistanceBWithUpperMargin) { m_FiducialMarkerCentroids[8] = m_CentroidsOfFiducialCandidates.at(counter); m_CentroidsOfFiducialCandidates.erase(m_CentroidsOfFiducialCandidates.begin() + counter); MITK_INFO << "Found Fiducial No 8, PointSet: " << m_FiducialMarkerCentroids[8]; return true; } } MITK_WARN << "Cannot find fiducial No 8."; return false; } void QmitkUSNavigationStepCtUsRegistration::DefineDataStorageImageFilter() { m_IsAPointSetPredicate = mitk::TNodePredicateDataType::New(); mitk::TNodePredicateDataType::Pointer isImage = mitk::TNodePredicateDataType::New(); auto isSegmentation = mitk::NodePredicateDataType::New("Segment"); m_IsASurfacePredicate = mitk::NodePredicateDataType::New("Surface"); mitk::NodePredicateOr::Pointer validImages = mitk::NodePredicateOr::New(); validImages->AddPredicate(mitk::NodePredicateAnd::New(isImage, mitk::NodePredicateNot::New(isSegmentation))); mitk::NodePredicateNot::Pointer isNotAHelperObject = mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object", mitk::BoolProperty::New(true))); m_IsOfTypeImagePredicate = mitk::NodePredicateAnd::New(validImages, isNotAHelperObject); mitk::NodePredicateProperty::Pointer isBinaryPredicate = mitk::NodePredicateProperty::New("binary", mitk::BoolProperty::New(true)); mitk::NodePredicateNot::Pointer isNotBinaryPredicate = mitk::NodePredicateNot::New(isBinaryPredicate); mitk::NodePredicateAnd::Pointer isABinaryImagePredicate = mitk::NodePredicateAnd::New(m_IsOfTypeImagePredicate, isBinaryPredicate); mitk::NodePredicateAnd::Pointer isNotABinaryImagePredicate = mitk::NodePredicateAnd::New(m_IsOfTypeImagePredicate, isNotBinaryPredicate); m_IsASegmentationImagePredicate = mitk::NodePredicateOr::New(isABinaryImagePredicate, mitk::TNodePredicateDataType::New()); m_IsAPatientImagePredicate = mitk::NodePredicateAnd::New(isNotABinaryImagePredicate, mitk::NodePredicateNot::New(mitk::TNodePredicateDataType::New())); } void QmitkUSNavigationStepCtUsRegistration::CreateQtPartControl(QWidget *parent) { ui->setupUi(parent); ui->floatingImageComboBox->SetPredicate(m_IsAPatientImagePredicate); ui->ctImagesToChooseComboBox->SetPredicate(m_IsAPatientImagePredicate); ui->segmentationComboBox->SetPredicate(m_IsASegmentationImagePredicate); ui->selectedSurfaceComboBox->SetPredicate(m_IsASurfacePredicate); ui->pointSetComboBox->SetPredicate(m_IsAPointSetPredicate); // create signal/slot connections connect(ui->floatingImageComboBox, SIGNAL(OnSelectionChanged(const mitk::DataNode*)), this, SLOT(OnFloatingImageComboBoxSelectionChanged(const mitk::DataNode*))); connect(ui->doRegistrationMarkerToImagePushButton, SIGNAL(clicked()), this, SLOT(OnRegisterMarkerToFloatingImageCS())); connect(ui->localizeFiducialMarkerPushButton, SIGNAL(clicked()), this, SLOT(OnLocalizeFiducials())); connect(ui->visualizeCTtoUSregistrationPushButton, SIGNAL(clicked()), this, SLOT(OnVisualizeCTtoUSregistration())); connect(ui->freezeUnfreezePushButton, SIGNAL(clicked()), this, SLOT(OnFreeze())); connect(ui->addCtImagePushButton, SIGNAL(clicked()), this, SLOT(OnAddCtImageClicked())); connect(ui->removeCtImagePushButton, SIGNAL(clicked()), this, SLOT(OnRemoveCtImageClicked())); connect(ui->evaluateProtocolPushButton, SIGNAL(clicked()), this, SLOT(OnEvaluateGroundTruthFiducialLocalizationProtocol())); connect(ui->actualizeSegmentationSurfacePSetDataPushButton, SIGNAL(clicked()), this, SLOT(OnActualizeSegmentationSurfacePointSetData())); connect(ui->calculateTREPushButton, SIGNAL(clicked()), this, SLOT(OnGetCursorPosition())); connect(ui->calculateCenterPushButton, SIGNAL(clicked()), this, SLOT(OnCalculateCenter())); } void QmitkUSNavigationStepCtUsRegistration::OnFloatingImageComboBoxSelectionChanged(const mitk::DataNode* node) { MITK_INFO << "OnFloatingImageComboBoxSelectionChanged()"; if (m_FloatingImage.IsNotNull()) { //TODO: Define, what will happen if the imageCT is not null... } if (node == nullptr) { this->UnsetFloatingImageGeometry(); m_FloatingImage = nullptr; return; } mitk::DataNode* selectedFloatingImage = ui->floatingImageComboBox->GetSelectedNode(); if (selectedFloatingImage == nullptr) { this->UnsetFloatingImageGeometry(); m_FloatingImage = nullptr; return; } mitk::Image::Pointer floatingImage = dynamic_cast(selectedFloatingImage->GetData()); if (floatingImage.IsNull()) { MITK_WARN << "Failed to cast selected segmentation node to mitk::Image*"; this->UnsetFloatingImageGeometry(); m_FloatingImage = nullptr; return; } m_FloatingImage = floatingImage; this->SetFloatingImageGeometryInformation(floatingImage.GetPointer()); } void QmitkUSNavigationStepCtUsRegistration::OnRegisterMarkerToFloatingImageCS() { this->CreateMarkerModelCoordinateSystemPointSet(); //Check for initialization if( m_MarkerModelCoordinateSystemPointSet.IsNull() || m_MarkerFloatingImageCoordinateSystemPointSet.IsNull() ) { MITK_WARN << "Fiducial Landmarks are not initialized yet, cannot register"; return; } //Retrieve fiducials if (m_MarkerFloatingImageCoordinateSystemPointSet->GetSize() != m_MarkerModelCoordinateSystemPointSet->GetSize()) { MITK_WARN << "Not the same number of fiducials, cannot register"; return; } else if (m_MarkerFloatingImageCoordinateSystemPointSet->GetSize() < 3) { MITK_WARN << "Need at least 3 fiducials, cannot register"; return; } //############### conversion to vtk data types (we will use the vtk landmark based transform) ########################## //convert point sets to vtk poly data vtkSmartPointer sourcePoints = vtkSmartPointer::New(); vtkSmartPointer targetPoints = vtkSmartPointer::New(); for (int i = 0; iGetSize(); i++) { double point[3] = { m_MarkerModelCoordinateSystemPointSet->GetPoint(i)[0], m_MarkerModelCoordinateSystemPointSet->GetPoint(i)[1], m_MarkerModelCoordinateSystemPointSet->GetPoint(i)[2] }; sourcePoints->InsertNextPoint(point); double point_targets[3] = { m_MarkerFloatingImageCoordinateSystemPointSet->GetPoint(i)[0], m_MarkerFloatingImageCoordinateSystemPointSet->GetPoint(i)[1], m_MarkerFloatingImageCoordinateSystemPointSet->GetPoint(i)[2] }; targetPoints->InsertNextPoint(point_targets); } //########################### here, the actual transform is computed ########################## //compute transform vtkSmartPointer transform = vtkSmartPointer::New(); transform->SetSourceLandmarks(sourcePoints); transform->SetTargetLandmarks(targetPoints); transform->SetModeToRigidBody(); transform->Modified(); transform->Update(); //compute FRE of transform double FRE = mitk::StaticIGTHelperFunctions::ComputeFRE(m_MarkerModelCoordinateSystemPointSet, m_MarkerFloatingImageCoordinateSystemPointSet, transform); MITK_INFO << "FRE: " << FRE << " mm"; if (m_PerformingGroundTruthProtocolEvaluation) { m_GroundTruthProtocolFRE.push_back(FRE); } //############################################################################################# //############### conversion back to itk/mitk data types ########################## //convert from vtk to itk data types itk::Matrix rotationFloat = itk::Matrix(); itk::Vector translationFloat = itk::Vector(); itk::Matrix rotationDouble = itk::Matrix(); itk::Vector translationDouble = itk::Vector(); vtkSmartPointer m = transform->GetMatrix(); for (int k = 0; k<3; k++) for (int l = 0; l<3; l++) { rotationFloat[k][l] = m->GetElement(k, l); rotationDouble[k][l] = m->GetElement(k, l); } for (int k = 0; k<3; k++) { translationFloat[k] = m->GetElement(k, 3); translationDouble[k] = m->GetElement(k, 3); } //create mitk affine transform 3D and save it to the class member m_TransformMarkerCSToFloatingImageCS = mitk::AffineTransform3D::New(); m_TransformMarkerCSToFloatingImageCS->SetMatrix(rotationDouble); m_TransformMarkerCSToFloatingImageCS->SetOffset(translationDouble); MITK_INFO << m_TransformMarkerCSToFloatingImageCS; //################################################################ //############### object is transformed ########################## //transform surface/image //only move image if we have one. Sometimes, this widget is used just to register point sets without images. /*if (m_ImageNode.IsNotNull()) { //first we have to store the original ct image transform to compose it with the new transform later mitk::AffineTransform3D::Pointer imageTransform = m_ImageNode->GetData()->GetGeometry()->GetIndexToWorldTransform(); imageTransform->Compose(mitkTransform); mitk::AffineTransform3D::Pointer newImageTransform = mitk::AffineTransform3D::New(); //create new image transform... setting the composed directly leads to an error itk::Matrix rotationFloatNew = imageTransform->GetMatrix(); itk::Vector translationFloatNew = imageTransform->GetOffset(); newImageTransform->SetMatrix(rotationFloatNew); newImageTransform->SetOffset(translationFloatNew); m_ImageNode->GetData()->GetGeometry()->SetIndexToWorldTransform(newImageTransform); }*/ //If this option is set, each point will be transformed and the acutal coordinates of the points change. if( !m_PerformingGroundTruthProtocolEvaluation ) { mitk::PointSet* pointSet_orig = m_MarkerModelCoordinateSystemPointSet; mitk::PointSet::Pointer pointSet_moved = mitk::PointSet::New(); for (int i = 0; i < pointSet_orig->GetSize(); i++) { pointSet_moved->InsertPoint(m_TransformMarkerCSToFloatingImageCS->TransformPoint(pointSet_orig->GetPoint(i))); } pointSet_orig->Clear(); for (int i = 0; i < pointSet_moved->GetSize(); i++) pointSet_orig->InsertPoint(pointSet_moved->GetPoint(i)); //Do a global reinit mitk::RenderingManager::GetInstance()->InitializeViewsByBoundingObjects(this->GetDataStorage()); } } void QmitkUSNavigationStepCtUsRegistration::OnLocalizeFiducials() { m_FiducialMarkerCentroids.clear(); m_CentroidsOfFiducialCandidates.clear(); if (m_MarkerFloatingImageCoordinateSystemPointSet.IsNotNull()) { m_MarkerFloatingImageCoordinateSystemPointSet->Clear(); } if (!this->FilterFloatingImage()) { QMessageBox msgBox; msgBox.setText("Cannot perform filtering of the image. The floating image = nullptr."); msgBox.exec(); return; } mitk::AffineTransform3D::Pointer transform = m_FloatingImage->GetGeometry()->GetIndexToWorldTransform(); MITK_WARN << "IndexToWorldTransform_CTimage = " << transform; this->GetCentroidsOfLabeledObjects(); if (!this->EliminateFiducialCandidatesByEuclideanDistances() || m_CentroidsOfFiducialCandidates.size() != NUMBER_FIDUCIALS_NEEDED) { QMessageBox msgBox; QString text = QString("Have found %1 instead of 8 fiducial candidates.\ Cannot perform fiducial localization procedure.").arg(m_CentroidsOfFiducialCandidates.size()); msgBox.setText(text); msgBox.exec(); return; } //Before calling NumerateFiducialMarks it must be sure, // that there rested only 8 fiducial candidates. this->NumerateFiducialMarks(); } void QmitkUSNavigationStepCtUsRegistration::OnVisualizeCTtoUSregistration() { emit this->ActualizeCtToUsRegistrationWidget(); mitk::DataNode* segmentationNode = ui->segmentationComboBox->GetSelectedNode(); if (segmentationNode == nullptr) { QMessageBox msgBox; msgBox.setText("Cannot visualize CT-to-US registration. There is no segmentation selected."); msgBox.exec(); return; } mitk::AffineTransform3D::Pointer transform = segmentationNode->GetData()->GetGeometry()->GetIndexToWorldTransform(); MITK_WARN << "IndexToWorldTransform_segmentation = " << transform; mitk::DataNode* surfaceNode = ui->selectedSurfaceComboBox->GetSelectedNode(); if (surfaceNode == nullptr) { QMessageBox msgBox; msgBox.setText("Cannot visualize CT-to-US registration. There is no surface selected."); msgBox.exec(); return; } mitk::DataNode* pointSetNode = ui->pointSetComboBox->GetSelectedNode(); if (pointSetNode == nullptr) { QMessageBox msgBox; msgBox.setText("Cannot visualize CT-to-US registration. There is no pointSet selected."); msgBox.exec(); return; } if (this->GetCombinedModality(false).IsNull()) { QMessageBox msgBox; msgBox.setText("CombinedModality not yet set.\nPlease try again and click on the button."); msgBox.exec(); return; } if (m_FloatingImageToUltrasoundRegistrationFilter.IsNull()) { QMessageBox msgBox; msgBox.setText("Cannot visualize CT-to-US registration.\ The FloatingImageToUltrasoundRegistrationFilter is not initialized."); msgBox.exec(); return; } //Set the transformation from marker-CS to the sensor-CS accordingly to the chosen user-option m_FloatingImageToUltrasoundRegistrationFilter ->InitializeTransformationMarkerCSToSensorCS(ui->useNdiTrackerCheckBox->isChecked()); m_FloatingImageToUltrasoundRegistrationFilter->SetPointSet(pointSetNode); m_FloatingImageToUltrasoundRegistrationFilter->SetSegmentation(segmentationNode, m_FloatingImage); m_FloatingImageToUltrasoundRegistrationFilter->SetSurface(surfaceNode); m_FloatingImageToUltrasoundRegistrationFilter ->SetTransformMarkerCSToFloatingImageCS(m_TransformMarkerCSToFloatingImageCS); m_FloatingImageToUltrasoundRegistrationFilter ->SetTransformUSimageCSToTrackingCS(this->GetCombinedModality()->GetCalibration()); m_FloatingImageToUltrasoundRegistrationFilter ->ConnectTo(this->GetCombinedModality()->GetNavigationDataSource()); } void QmitkUSNavigationStepCtUsRegistration::OnFreeze() { if (this->GetCombinedModality(false).IsNull()) { return; } if (!m_FreezeCombinedModality) { m_FreezeCombinedModality = true; ui->freezeUnfreezePushButton->setText("Unfreeze"); this->GetCombinedModality()->SetIsFreezed(true); } else { m_FreezeCombinedModality = false; ui->freezeUnfreezePushButton->setText("Freeze"); this->GetCombinedModality()->SetIsFreezed(false); } } void QmitkUSNavigationStepCtUsRegistration::OnActualizeSegmentationSurfacePointSetData() { mitk::DataNode* segmentationNode = ui->segmentationComboBox->GetSelectedNode(); if (segmentationNode == nullptr) { QMessageBox msgBox; msgBox.setText("Cannot actualize segmentation + surface + pointset data. There is no segmentation selected."); msgBox.exec(); return; } mitk::DataNode* surfaceNode = ui->selectedSurfaceComboBox->GetSelectedNode(); if (surfaceNode == nullptr) { QMessageBox msgBox; msgBox.setText("Cannot actualize segmentation + surface + pointset data. There is no surface selected."); msgBox.exec(); return; } mitk::DataNode* pointSetNode = ui->pointSetComboBox->GetSelectedNode(); if (pointSetNode == nullptr) { QMessageBox msgBox; msgBox.setText("Cannot actualize segmentation + surface + pointset data. There is no pointSet selected."); msgBox.exec(); return; } m_FloatingImageToUltrasoundRegistrationFilter->SetPointSet(pointSetNode); m_FloatingImageToUltrasoundRegistrationFilter->SetSegmentation(segmentationNode, m_FloatingImage); m_FloatingImageToUltrasoundRegistrationFilter->SetSurface(surfaceNode); } void QmitkUSNavigationStepCtUsRegistration::OnGetCursorPosition() { emit GetCursorPosition(); } void QmitkUSNavigationStepCtUsRegistration::OnCalculateTRE(mitk::Point3D centroidOfTargetInUSImage) { mitk::DataNode::Pointer pointSetNode = ui->pointSetComboBox->GetSelectedNode(); if (pointSetNode.IsNull()) { QMessageBox msgBox; msgBox.setText("Cannot calculate TRE. The pointSetComboBox node returned a nullptr."); msgBox.exec(); return; } mitk::PointSet::Pointer pointSet = dynamic_cast(pointSetNode->GetData()); if (pointSet.IsNull()) { ui->distanceTREValue->setText(QString("Unknown")); return; } double distance = pointSet->GetPoint(0).EuclideanDistanceTo(centroidOfTargetInUSImage); ui->distanceTREValue->setText(QString("%1").arg(distance)); } void QmitkUSNavigationStepCtUsRegistration::OnCalculateCenter() { mitk::DataNode::Pointer node = ui->segmentationComboBox->GetSelectedNode(); if (node.IsNull()) { QMessageBox msgBox; msgBox.setText("Cannot calculate the centroid of the segmentation."\ "The segmentationComboBox node returned a nullptr."); msgBox.exec(); return; } mitk::LabelSetImage::Pointer image = dynamic_cast(node->GetData()); if (image.IsNull()) { MITK_WARN << "Cannot CalculateCenter - the segmentation cannot be converted to mitk::Image"; return; } ImageType::Pointer itkImage = ImageType::New(); mitk::CastToItkImage(image, itkImage); //Initialize binary image to shape label map filter BinaryImageToShapeLabelMapFilterType::Pointer shapeLabelMapFilter = BinaryImageToShapeLabelMapFilterType::New(); shapeLabelMapFilter->SetInputForegroundValue(1); shapeLabelMapFilter->SetInput(itkImage); shapeLabelMapFilter->Update(); BinaryImageToShapeLabelMapFilterType::OutputImageType::Pointer labelMap = shapeLabelMapFilter->GetOutput(); for (int i = labelMap->GetNumberOfLabelObjects() - 1; i >= 0; --i) { // Get the ith region BinaryImageToShapeLabelMapFilterType::OutputImageType::LabelObjectType* labelObject = labelMap->GetNthLabelObject(i); mitk::Vector3D centroid; centroid[0] = labelObject->GetCentroid()[0]; centroid[1] = labelObject->GetCentroid()[1]; centroid[2] = labelObject->GetCentroid()[2]; MITK_INFO << "Centroid of segmentation = " << centroid; } } void QmitkUSNavigationStepCtUsRegistration::OnAddCtImageClicked() { mitk::DataNode* selectedCtImage = ui->ctImagesToChooseComboBox->GetSelectedNode(); if (selectedCtImage == nullptr) { return; } mitk::Image::Pointer ctImage = dynamic_cast(selectedCtImage->GetData()); if (ctImage.IsNull()) { MITK_WARN << "Failed to cast selected segmentation node to mitk::Image*"; return; } QString name = QString::fromStdString(selectedCtImage->GetName()); for( int counter = 0; counter < ui->chosenCtImagesListWidget->count(); ++counter) { MITK_INFO << ui->chosenCtImagesListWidget->item(counter)->text() << " - " << counter; MITK_INFO << m_ImagesGroundTruthProtocol.at(counter).GetPointer(); if (ui->chosenCtImagesListWidget->item(counter)->text().compare(name) == 0) { MITK_INFO << "CT image already exist in list of chosen CT images. Do not add the image."; return; } } ui->chosenCtImagesListWidget->addItem(name); m_ImagesGroundTruthProtocol.push_back(ctImage); } void QmitkUSNavigationStepCtUsRegistration::OnRemoveCtImageClicked() { int position = ui->chosenCtImagesListWidget->currentRow(); if (ui->chosenCtImagesListWidget->count() == 0 || position < 0) { return; } m_ImagesGroundTruthProtocol.erase(m_ImagesGroundTruthProtocol.begin() + position); QListWidgetItem *item = ui->chosenCtImagesListWidget->currentItem(); ui->chosenCtImagesListWidget->removeItemWidget(item); delete item; } void QmitkUSNavigationStepCtUsRegistration::OnEvaluateGroundTruthFiducialLocalizationProtocol() { m_GroundTruthProtocolFRE.clear(); if (m_ImagesGroundTruthProtocol.size() != 6) { QMessageBox msgBox; msgBox.setText("For evaluating the Ground-Truth-Fiducial-Localization-Protocol there must be loaded 6 different CT images."); msgBox.exec(); return; } m_PerformingGroundTruthProtocolEvaluation = true; this->CreatePointsToTransformForGroundTruthProtocol(); m_GroundTruthProtocolTransformedPoints.clear(); for (unsigned int cycleNo = 0; cycleNo < m_ImagesGroundTruthProtocol.size(); ++cycleNo) { m_FloatingImage = m_ImagesGroundTruthProtocol.at(cycleNo); this->SetFloatingImageGeometryInformation(m_FloatingImage.GetPointer()); this->OnLocalizeFiducials(); this->OnRegisterMarkerToFloatingImageCS(); this->TransformPointsGroundTruthProtocol(); } this->AddTransformedPointsToDataStorage(); double meanFRE = this->CalculateMeanFRE(); double sdOfFRE = this->CalculateStandardDeviationOfFRE(meanFRE); this->CalculateGroundTruthProtocolTRE(); ui->meanFREValue->setText(QString("%1").arg(meanFRE)); ui->sdFREValue->setText(QString("%1").arg(sdOfFRE)); if (ui->protocolEvaluationTypeComboBox->currentText().compare("ANGLE") == 0) { if (m_GroundTruthProtocolTRE.find(0) != m_GroundTruthProtocolTRE.end()) { ui->TREValue->setText(QString("%1").arg(m_GroundTruthProtocolTRE.at(0))); } } else if (ui->protocolEvaluationTypeComboBox->currentText().compare("PLANE") == 0) { if (m_GroundTruthProtocolTRE.find(0) != m_GroundTruthProtocolTRE.end() && m_GroundTruthProtocolTRE.find(20) != m_GroundTruthProtocolTRE.end() && m_GroundTruthProtocolTRE.find(40) != m_GroundTruthProtocolTRE.end() && m_GroundTruthProtocolTRE.find(60) != m_GroundTruthProtocolTRE.end() && m_GroundTruthProtocolTRE.find(80) != m_GroundTruthProtocolTRE.end() && m_GroundTruthProtocolTRE.find(100) != m_GroundTruthProtocolTRE.end()) { ui->TREValue->setText(QString("Depth 0mm: %1\nDepth 20mm: %2\nDepth 40mm: %3\ \nDepth 60mm: %4\nDepth 80mm: %5\nDepth 100mm: %6") .arg(m_GroundTruthProtocolTRE.at(0)) .arg(m_GroundTruthProtocolTRE.at(20)) .arg(m_GroundTruthProtocolTRE.at(40)) .arg(m_GroundTruthProtocolTRE.at(60)) .arg(m_GroundTruthProtocolTRE.at(80)) .arg(m_GroundTruthProtocolTRE.at(100))); } } m_PerformingGroundTruthProtocolEvaluation = false; } diff --git a/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/NavigationStepWidgets/QmitkUSNavigationStepPunctuationIntervention.cpp b/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/NavigationStepWidgets/QmitkUSNavigationStepPunctuationIntervention.cpp index 14450e93af..7fd7430f85 100644 --- a/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/NavigationStepWidgets/QmitkUSNavigationStepPunctuationIntervention.cpp +++ b/Plugins/org.mitk.gui.qt.igt.app.ultrasoundtrackingnavigation/src/internal/NavigationStepWidgets/QmitkUSNavigationStepPunctuationIntervention.cpp @@ -1,290 +1,290 @@ /*============================================================================ 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 "QmitkUSNavigationStepPunctuationIntervention.h" #include "ui_QmitkUSNavigationStepPunctuationIntervention.h" #include "mitkNeedleProjectionFilter.h" #include "../Widgets/QmitkZoneProgressBar.h" #include "../QmitkUSNavigationMarkerPlacement.h" #include "usModuleRegistry.h" #include QmitkUSNavigationStepPunctuationIntervention::QmitkUSNavigationStepPunctuationIntervention(QWidget *parent) : QmitkUSAbstractNavigationStep(parent), m_Ui(new Ui::QmitkUSNavigationStepPunctuationIntervention), m_ZoneNodes(nullptr), m_NeedleProjectionFilter(mitk::NeedleProjectionFilter::New()), m_NeedleNavigationTool(mitk::NavigationTool::New()), m_OldColors(), m_SphereSource(vtkSmartPointer::New()), m_OBBTree(vtkSmartPointer::New()), m_IntersectPoints(vtkSmartPointer::New()) { m_Ui->setupUi(this); connect(m_Ui->m_AddNewAblationZone, SIGNAL(clicked()), this, SLOT(OnAddAblationZoneClicked())); connect(m_Ui->m_ShowToolAxisN, SIGNAL(stateChanged(int)), this, SLOT(OnShowToolAxisEnabled(int))); connect(m_Ui->m_EnableAblationMarking, SIGNAL(clicked()), this, SLOT(OnEnableAblationZoneMarkingClicked())); connect(m_Ui->m_AblationZoneSizeSlider, SIGNAL(valueChanged(int)), this, SLOT(OnAblationZoneSizeSliderChanged(int))); m_Ui->m_AblationZonesBox->setVisible(false); } void QmitkUSNavigationStepPunctuationIntervention::SetNeedleMetaData(mitk::NavigationTool::Pointer needleNavigationTool) { this->m_NeedleNavigationTool = needleNavigationTool; } void QmitkUSNavigationStepPunctuationIntervention::OnEnableAblationZoneMarkingClicked() { if(m_Ui->m_EnableAblationMarking->isChecked()) m_Ui->m_AblationZonesBox->setVisible(true); else m_Ui->m_AblationZonesBox->setVisible(false); } void QmitkUSNavigationStepPunctuationIntervention::OnAblationZoneSizeSliderChanged(int size) { int id = m_Ui->m_AblationZonesList->currentRow(); if (id!=-1) {emit AblationZoneChanged(id,size);} }// void QmitkUSNavigationStepPunctuationIntervention::OnAddAblationZoneClicked() { QListWidgetItem* newItem = new QListWidgetItem("Ablation Zone (initial size: " + QString::number(m_Ui->m_AblationZoneSizeSlider->value()) + " mm)", m_Ui->m_AblationZonesList); newItem->setSelected(true); emit AddAblationZoneClicked(m_Ui->m_AblationZoneSizeSlider->value()); } QmitkUSNavigationStepPunctuationIntervention::~QmitkUSNavigationStepPunctuationIntervention() { mitk::DataStorage::Pointer dataStorage = this->GetDataStorage(false); if ( dataStorage.IsNotNull() ) { // remove needle path from data storage if it is there mitk::DataNode::Pointer node = this->GetNamedDerivedNode ("Needle Path", QmitkUSAbstractNavigationStep::DATANAME_BASENODE); if ( node.IsNotNull() ) { dataStorage->Remove(node); } } delete m_Ui; } bool QmitkUSNavigationStepPunctuationIntervention::OnStartStep() { // create node for Needle Projection mitk::DataNode::Pointer node = this->GetNamedDerivedNodeAndCreate ("Needle Path", QmitkUSAbstractNavigationStep::DATANAME_BASENODE); node->SetData(m_NeedleProjectionFilter->GetProjection()); node->SetBoolProperty("show contour", true); //m_NeedleProjectionFilter->SetToolAxisForFilter(m_NeedleNavigationTool->GetToolAxis()); return true; } bool QmitkUSNavigationStepPunctuationIntervention::OnRestartStep() { return this->OnActivateStep(); } bool QmitkUSNavigationStepPunctuationIntervention::OnFinishStep() { mitk::DataNode::Pointer finishPunctionResult = mitk::DataNode::New(); finishPunctionResult->SetName("PunctionResult"); mitk::Point3D needlePos = m_NeedleProjectionFilter->GetOutput(0)->GetPosition(); mitk::Quaternion needleRot = m_NeedleProjectionFilter->GetOutput(0)->GetOrientation(); finishPunctionResult->SetProperty("USNavigation::TipPositionEnd", mitk::Point3dProperty::New(needlePos)); MITK_INFO("USNavigationLogging") << "Instrument tip at end: " <ClearZones(); mitk::DataStorage::Pointer dataStorage = this->GetDataStorage(); // add progress bars for risk zone nodes m_ZoneNodes = dataStorage->GetDerivations(dataStorage->GetNamedNode(QmitkUSNavigationMarkerPlacement::DATANAME_ZONES)); // add zones to the widgets for risk structures for (mitk::DataStorage::SetOfObjects::ConstIterator it = m_ZoneNodes->Begin(); it != m_ZoneNodes->End(); ++it) { m_Ui->riskStructuresRangeWidget->AddZone(it->Value()); float rgb[3]; it->Value()->GetColor(rgb); mitk::Color color; color.SetRed(rgb[0]); color.SetGreen(rgb[1]); color.SetBlue(rgb[2]); m_OldColors[it->Value()] = color; } m_NeedleProjectionFilter->SelectInput(0); return true; } void QmitkUSNavigationStepPunctuationIntervention::OnShowToolAxisEnabled(int enabled) { if (enabled == 0) { m_NeedleProjectionFilter->ShowToolAxis(false); } else { m_NeedleProjectionFilter->ShowToolAxis(true); } } void QmitkUSNavigationStepPunctuationIntervention::OnUpdate() { if (this->GetCombinedModality(false).IsNull()) return; // get navigation data source and make sure that it is not null mitk::NavigationDataSource::Pointer navigationDataSource = this->GetCombinedModality()->GetNavigationDataSource(); if ( navigationDataSource.IsNull() ) { MITK_ERROR("QmitkUSAbstractNavigationStep")("QmitkUSNavigationStepPunctuationIntervention") << "Navigation Data Source of Combined Modality must not be null."; mitkThrow() << "Navigation Data Source of Combined Modality must not be null."; } // update body marker this->UpdateBodyMarkerStatus(navigationDataSource->GetOutput(1)); // update critical structures this->UpdateCriticalStructures(navigationDataSource->GetOutput(0),m_NeedleProjectionFilter->GetProjection()); m_NeedleProjectionFilter->Update(); //Update Distance to US image mitk::Point3D point1 = m_NeedleProjectionFilter->GetProjection()->GetPoint(0); mitk::Point3D point2 = m_NeedleProjectionFilter->GetProjection()->GetPoint(1); double distance = point1.EuclideanDistanceTo(point2); m_Ui->m_DistanceToUSPlane->setText(QString::number(distance) + " mm"); } void QmitkUSNavigationStepPunctuationIntervention::OnSettingsChanged(const itk::SmartPointer settingsNode) { if ( settingsNode.IsNull() ) { return; } } QString QmitkUSNavigationStepPunctuationIntervention::GetTitle() { return "Computer-assisted Intervention"; } bool QmitkUSNavigationStepPunctuationIntervention::GetIsRestartable() { return false; } QmitkUSNavigationStepPunctuationIntervention::FilterVector QmitkUSNavigationStepPunctuationIntervention::GetFilter() { return FilterVector(1, m_NeedleProjectionFilter.GetPointer()); } void QmitkUSNavigationStepPunctuationIntervention::OnSetCombinedModality() { mitk::AbstractUltrasoundTrackerDevice::Pointer combinedModality = this->GetCombinedModality(false); if ( combinedModality.IsNotNull() ) { m_NeedleProjectionFilter->ConnectTo(combinedModality->GetNavigationDataSource()); // set calibration of the combined modality to the needle projection filter mitk::AffineTransform3D::Pointer usPlaneTransform = combinedModality->GetUSPlaneTransform(); if (usPlaneTransform.IsNotNull()) { m_NeedleProjectionFilter->SetTargetPlane(usPlaneTransform); } } else { MITK_WARN << "CombinedModality is null!"; } } void QmitkUSNavigationStepPunctuationIntervention::ClearZones() { m_Ui->riskStructuresRangeWidget->ClearZones(); } void QmitkUSNavigationStepPunctuationIntervention::UpdateBodyMarkerStatus(mitk::NavigationData::Pointer bodyMarker) { if ( bodyMarker.IsNull() ) { MITK_ERROR("QmitkUSAbstractNavigationStep")("QmitkUSNavigationStepPunctuationIntervention") << "Current Navigation Data for body marker of Combined Modality must not be null."; mitkThrow() << "Current Navigation Data for body marker of Combined Modality must not be null."; } bool valid = bodyMarker->IsDataValid(); // update body marker status label if (valid) { m_Ui->bodyMarkerTrackingStatusLabel->setStyleSheet( "background-color: #8bff8b; margin-right: 1em; margin-left: 1em; border: 1px solid grey"); m_Ui->bodyMarkerTrackingStatusLabel->setText("Body marker is inside the tracking volume."); } else { m_Ui->bodyMarkerTrackingStatusLabel->setStyleSheet( "background-color: #ff7878; margin-right: 1em; margin-left: 1em; border: 1px solid grey"); m_Ui->bodyMarkerTrackingStatusLabel->setText("Body marker is not inside the tracking volume."); } m_Ui->riskStructuresRangeGroupBox->setEnabled(valid); } void QmitkUSNavigationStepPunctuationIntervention::UpdateCriticalStructures(mitk::NavigationData::Pointer needle, mitk::PointSet::Pointer path) { // update the distances for the risk structures widget m_Ui->riskStructuresRangeWidget->UpdateDistancesToNeedlePosition(needle); //iterate through all zones for (mitk::DataStorage::SetOfObjects::ConstIterator it = m_ZoneNodes->Begin(); it != m_ZoneNodes->End(); ++it) { mitk::DataNode::Pointer currentNode = it->Value(); //get center point and radius float radius = -1; mitk::Point3D center; currentNode->GetFloatProperty("zone.size", radius); - center = currentNode->GetData()->GetGeometry()->GetIndexToWorldTransform()->GetTranslation(); + center = mitk::Point3D(currentNode->GetData()->GetGeometry()->GetIndexToWorldTransform()->GetTranslation()); mitk::Point3D point0 = path->GetPoint(0); mitk::Point3D point1 = path->GetPoint(1); if (CheckSphereLineIntersection(center,radius,point0,point1)) {currentNode->SetColor(mitk::IGTColor_WARNING);} else {currentNode->SetColor(m_OldColors[currentNode]);} } } bool QmitkUSNavigationStepPunctuationIntervention::CheckSphereLineIntersection(mitk::Point3D& sphereOrigin, float& sphereRadius, mitk::Point3D& lineStart, mitk::Point3D& lineEnd) { double center[3] = {sphereOrigin[0],sphereOrigin[1],sphereOrigin[2]}; m_SphereSource->SetCenter(center); m_SphereSource->SetRadius(sphereRadius); m_SphereSource->Update(); m_OBBTree->SetDataSet(m_SphereSource->GetOutput()); m_OBBTree->BuildLocator(); double lineP0[3] = {lineStart[0], lineStart[1], lineStart[2]}; double lineP1[3] = {lineEnd[0], lineEnd[1], lineEnd[2]}; m_OBBTree->IntersectWithLine(lineP0, lineP1, m_IntersectPoints, nullptr); if (m_IntersectPoints->GetNumberOfPoints() > 0) {return true;} else {return false;} } diff --git a/Plugins/org.mitk.gui.qt.igttracking/src/internal/QmitkIGTNavigationToolCalibration.cpp b/Plugins/org.mitk.gui.qt.igttracking/src/internal/QmitkIGTNavigationToolCalibration.cpp index 9b73b74a67..74ac043b7c 100644 --- a/Plugins/org.mitk.gui.qt.igttracking/src/internal/QmitkIGTNavigationToolCalibration.cpp +++ b/Plugins/org.mitk.gui.qt.igttracking/src/internal/QmitkIGTNavigationToolCalibration.cpp @@ -1,756 +1,756 @@ /*============================================================================ 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 // Blueberry #include #include // Qmitk #include "QmitkIGTNavigationToolCalibration.h" // mitk #include #include #include #include #include #include #include // Qt #include #include #include // vtk #include const std::string QmitkIGTNavigationToolCalibration::VIEW_ID = "org.mitk.views.igtnavigationtoolcalibration"; QmitkIGTNavigationToolCalibration::QmitkIGTNavigationToolCalibration() { m_ToolTransformationWidget = new QmitkInteractiveTransformationWidget(); } QmitkIGTNavigationToolCalibration::~QmitkIGTNavigationToolCalibration() { //The following code is required due to a bug in the point list widget. //If this is removed, MITK crashes when closing the view: m_Controls.m_RegistrationLandmarkWidget->SetPointSetNode(nullptr); m_Controls.m_CalibrationLandmarkWidget->SetPointSetNode(nullptr); //clean up data storage this->GetDataStorage()->Remove(m_ToolTipPointPreview); delete m_ToolTransformationWidget; } void QmitkIGTNavigationToolCalibration::SetFocus() { } void QmitkIGTNavigationToolCalibration::OnToolCalibrationMethodChanged(int index) { //if pivot calibration (3) or manual(0) is chosen only calibration pointer is needed if (index == 0 || index == 3) { if (!CheckInitialization(false)) { return; } } else{ if (!CheckInitialization()) { return; } } UpdateManualToolTipCalibrationView(); m_Controls.m_CalibrationMethodsWidget->setCurrentIndex(index); m_IndexCurrentCalibrationMethod = index; } void QmitkIGTNavigationToolCalibration::CreateQtPartControl(QWidget *parent) { m_TrackingTimer = new QTimer(this); // create GUI widgets from the Qt Designer's .ui file m_Controls.setupUi(parent); connect(m_Controls.m_SetToolToCalibrate, SIGNAL(clicked()), this, SLOT(SetToolToCalibrate())); connect(m_Controls.m_SetPointer, SIGNAL(clicked()), this, SLOT(SetCalibrationPointer())); connect(m_TrackingTimer, SIGNAL(timeout()), this, SLOT(UpdateTrackingTimer())); connect(m_Controls.m_AddLandmark, SIGNAL(clicked()), this, SLOT(AddLandmark())); connect(m_Controls.m_SaveCalibratedTool, SIGNAL(clicked()), this, SLOT(SaveCalibratedTool())); connect(m_Controls.m_AddPivotPose, SIGNAL(clicked()), this, SLOT(OnAddPivotPose())); connect(m_Controls.m_ComputePivot, SIGNAL(clicked()), this, SLOT(OnComputePivot())); connect(m_Controls.m_UseComputedPivotPoint, SIGNAL(clicked()), this, SLOT(OnUseComputedPivotPoint())); connect(m_Controls.m_StartEditTooltipManually, SIGNAL(clicked()), this, SLOT(OnStartManualToolTipCalibration())); connect(m_Controls.m_GetPositions, SIGNAL(clicked()), this, SLOT(OnGetPositions())); connect(m_Controls.m_ToolAxis_X, SIGNAL(valueChanged(double)), this, SLOT(OnToolAxisSpinboxChanged())); connect(m_Controls.m_ToolAxis_Y, SIGNAL(valueChanged(double)), this, SLOT(OnToolAxisSpinboxChanged())); connect(m_Controls.m_ToolAxis_Z, SIGNAL(valueChanged(double)), this, SLOT(OnToolAxisSpinboxChanged())); connect(m_Controls.m_CalibrateToolAxis, SIGNAL(clicked()), this, SLOT(OnCalibrateToolAxis())); connect((QObject*)(m_ToolTransformationWidget), SIGNAL(EditToolTipFinished(mitk::AffineTransform3D::Pointer)), this, SLOT(OnManualEditToolTipFinished(mitk::AffineTransform3D::Pointer))); connect(m_Controls.m_CalibrationMethodComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(OnToolCalibrationMethodChanged(int))); connect((QObject*)(m_Controls.m_RunCalibrationButton), SIGNAL(clicked()), (QObject*) this, SLOT(OnRunSingleRefToolCalibrationClicked())); connect((QObject*)(m_Controls.m_CollectNavigationDataButton), SIGNAL(clicked()), (QObject*) this, SLOT(OnLoginSingleRefToolNavigationDataClicked())); connect((QObject*)(m_Controls.m_SetNewToolTipPosButton), SIGNAL(clicked()), (QObject*) this, SLOT(OnSetNewToolTipPosButtonClicked())); m_IDToolToCalibrate = -1; m_IDCalibrationPointer = -1; m_IndexCurrentCalibrationMethod = -1; m_OnLoginSingleRefToolNavigationDataClicked = false; m_NumberOfNavigationDataCounter = 0; m_NumberOfNavigationData = -1; //for pivot calibration m_OnAddPivotPoseClicked = false; PivotCount = 0; m_PivotPoses = std::vector(); m_CalibrationLandmarks = mitk::PointSet::New(); m_CalibrationLandmarksNode = mitk::DataNode::New(); m_CalibrationLandmarksNode->SetData(m_CalibrationLandmarks); m_Controls.m_CalibrationLandmarkWidget->SetPointSetNode(m_CalibrationLandmarksNode); m_RegistrationLandmarks = mitk::PointSet::New(); m_RegistrationLandmarksNode = mitk::DataNode::New(); m_RegistrationLandmarksNode->SetData(m_RegistrationLandmarks); m_Controls.m_RegistrationLandmarkWidget->SetPointSetNode(m_RegistrationLandmarksNode); m_ToolSurfaceInToolCoordinatesDataNode = mitk::DataNode::New(); m_ToolSurfaceInToolCoordinatesDataNode->SetName("ToolSurface(ToolCoordinates)"); m_LoggedNavigationDataDifferences = std::vector< mitk::NavigationData::Pointer >(); } void QmitkIGTNavigationToolCalibration::OnRunSingleRefToolCalibrationClicked() { if (!CheckInitialization()) { return; } mitk::NavigationData::Pointer ToolTipTransform = mitk::NavigationData::New(); if (m_Controls.m_CalibratePosition->isChecked()) { //1: Compute mean translational offset vector m_ResultOffsetVector.Fill(0); for (std::vector::iterator vecIter = m_LoggedNavigationDataOffsets.begin(); vecIter != m_LoggedNavigationDataOffsets.end(); vecIter++) { m_ResultOffsetVector[0] = m_ResultOffsetVector[0] + (*vecIter)[0]; m_ResultOffsetVector[1] = m_ResultOffsetVector[1] + (*vecIter)[1]; m_ResultOffsetVector[2] = m_ResultOffsetVector[2] + (*vecIter)[2]; } m_ResultOffsetVector[0] = m_ResultOffsetVector[0] / m_LoggedNavigationDataOffsets.size(); m_ResultOffsetVector[1] = m_ResultOffsetVector[1] / m_LoggedNavigationDataOffsets.size(); m_ResultOffsetVector[2] = m_ResultOffsetVector[2] / m_LoggedNavigationDataOffsets.size(); this->m_Controls.m_ResultOfCalibration->setText( QString("x: ") + QString(QString::number(m_ResultOffsetVector[0], 103, 3)) + QString("; y: ") + (QString::number(m_ResultOffsetVector[1], 103, 3)) + QString("; z: ") + (QString::number(m_ResultOffsetVector[2], 103, 3))); ToolTipTransform->SetPosition(m_ResultOffsetVector); } if (m_Controls.m_CalibrateOrientation->isChecked()) { //2: Compute mean orientation mitk::Quaternion meanOrientation; std::vector allOrientations = std::vector (); for (std::size_t i = 0; i < m_LoggedNavigationDataDifferences.size(); i++) { allOrientations.push_back(m_LoggedNavigationDataDifferences.at(i)->GetOrientation()); } meanOrientation = mitk::QuaternionAveraging::CalcAverage(allOrientations); this->m_Controls.m_ResultOfCalibrationOrientation->setText( QString("qx: ") + QString(QString::number(meanOrientation.x(), 103, 3)) + QString("; qy: ") + (QString::number(meanOrientation.y(), 103, 3)) + QString("; qz: ") + (QString::number(meanOrientation.z(), 103, 3)) + QString("; qr: ") + (QString::number(meanOrientation.r(), 103, 3))); ToolTipTransform->SetOrientation(meanOrientation); } MITK_INFO << "Computed calibration: "; MITK_INFO << "Translation Vector: " << ToolTipTransform->GetPosition(); MITK_INFO << "Quaternion: (" << ToolTipTransform->GetOrientation() << ")"; MITK_INFO << "Euler Angles [rad]: (" << ToolTipTransform->GetOrientation().rotation_euler_angles() << ")"; MITK_INFO << "Matrix:"; vnl_matrix_fixed rotMatrix = ToolTipTransform->GetOrientation().rotation_matrix_transpose(); MITK_INFO << rotMatrix[0][0] << " " << rotMatrix[0][1] << " " << rotMatrix[0][2] << std::endl; MITK_INFO << rotMatrix[1][0] << " " << rotMatrix[1][1] << " " << rotMatrix[1][2] << std::endl; MITK_INFO << rotMatrix[2][0] << " " << rotMatrix[2][1] << " " << rotMatrix[2][2] << std::endl; //3: write everything into the final tool tip transform and save it as member (it will be written to the tool later on) mitk::NavigationData::Pointer ToolTipInTrackingCoordinates = mitk::NavigationData::New(); ToolTipInTrackingCoordinates->Compose(ToolTipTransform); ToolTipInTrackingCoordinates->Compose(m_NavigationDataSourceOfToolToCalibrate->GetOutput(m_IDToolToCalibrate)); ShowToolTipPreview(ToolTipInTrackingCoordinates); m_Controls.m_SetNewToolTipPosButton->setEnabled(true); m_ComputedToolTipTransformation = ToolTipTransform; } void QmitkIGTNavigationToolCalibration::OnLoginSingleRefToolNavigationDataClicked() { if (!CheckInitialization()) { return; } //reset old data m_LoggedNavigationDataOffsets.clear(); m_LoggedNavigationDataDifferences.clear(); m_OnLoginSingleRefToolNavigationDataClicked = true; m_Controls.m_CollectNavigationDataButton->setEnabled(false); m_NumberOfNavigationData = m_Controls.m_NumberOfNavigationDataToCollect->value(); MITK_INFO << "Collecting " << m_NumberOfNavigationData << " NavigationData ... " << endl; } void QmitkIGTNavigationToolCalibration::LoginSingleRefToolNavigationData() { if (!CheckInitialization()) { return; } if (m_NumberOfNavigationDataCounter < m_NumberOfNavigationData) { //update label text QString labelText = "Collecting Data: " + QString::number(m_NumberOfNavigationDataCounter); m_Controls.m_CollectionStatus->setText(labelText); mitk::NavigationData::Pointer referenceTool = m_NavigationDataSourceOfCalibrationPointer->GetOutput(m_IDCalibrationPointer); mitk::NavigationData::Pointer toolToCalibrate = m_NavigationDataSourceOfToolToCalibrate->GetOutput(m_IDToolToCalibrate); //compute difference: // differenceND = toolToCalibrate^-1 * referenceTool mitk::NavigationData::Pointer differenceND = mitk::NavigationData::New(); differenceND->Compose(referenceTool); differenceND->Compose(toolToCalibrate->GetInverse()); //inverse mode... if (m_Controls.m_InvertQuaternions->isChecked()) { // negate identity matrix to directly show parameters that will set up in NDI 6D Software Architect differenceND = differenceND->GetInverse(); } //save difference in member m_LoggedNavigationDataOffsets.push_back(differenceND->GetPosition()); m_LoggedNavigationDataDifferences.push_back(differenceND); m_NumberOfNavigationDataCounter++; } if (m_NumberOfNavigationDataCounter == m_NumberOfNavigationData) { m_NumberOfNavigationDataCounter = 0; m_OnLoginSingleRefToolNavigationDataClicked = false; m_Controls.m_CollectNavigationDataButton->setEnabled(true); m_Controls.m_RunCalibrationButton->setEnabled(true); MITK_INFO << "Collecting " << m_NumberOfNavigationData << " NavigationData ... Finished" << endl; QString labelText = "Collected " + QString::number(m_NumberOfNavigationData) + " data samples!"; m_Controls.m_CollectionStatus->setText(labelText); } } void QmitkIGTNavigationToolCalibration::OnSetNewToolTipPosButtonClicked() { ApplyToolTipTransform(m_ComputedToolTipTransformation); RemoveToolTipPreview(); } void QmitkIGTNavigationToolCalibration::ClearOldPivot() { mitk::NavigationData::Pointer tempND = mitk::NavigationData::New(); this->ApplyToolTipTransform(tempND); UpdateManualToolTipCalibrationView(); //m_ManualToolTipEditWidget->hide(); //TODO this->GetDataStorage()->Remove(m_ToolSurfaceInToolCoordinatesDataNode); } void QmitkIGTNavigationToolCalibration::OnAddPivotPose() { ClearOldPivot(); //When the collect Poses Button is Clicked m_OnAddPivotPoseClicked = true; m_NumberOfNavigationData = m_Controls.m_PosesToCollect->value(); } void QmitkIGTNavigationToolCalibration::AddPivotPose() { //Save the poses to be used in computation if (PivotCount < m_NumberOfNavigationData) { mitk::NavigationData::Pointer currentPose = mitk::NavigationData::New(); currentPose->Graft(m_Controls.m_SelectionWidget->GetSelectedNavigationDataSource()->GetOutput(m_IDToolToCalibrate)); m_PivotPoses.push_back(currentPose); m_Controls.m_PoseNumber->setText(QString::number(m_PivotPoses.size())); PivotCount++; } if (PivotCount == m_NumberOfNavigationData) { m_OnAddPivotPoseClicked = false; } } void QmitkIGTNavigationToolCalibration::OnComputePivot() { mitk::PivotCalibration::Pointer myPivotCalibration = mitk::PivotCalibration::New(); for (std::size_t i = 0; i < this->m_PivotPoses.size(); i++) { myPivotCalibration->AddNavigationData(m_PivotPoses.at(i)); } QString resultString; if (myPivotCalibration->ComputePivotResult()) { mitk::NavigationData::Pointer markerTransformationTrackingCoordinates = m_PivotPoses.at(0); //Get computed pivot transfromation in tool coordinates mitk::NavigationData::Pointer ToolTipToTool = mitk::NavigationData::New(); ToolTipToTool->SetPosition(myPivotCalibration->GetResultPivotPoint()); ToolTipToTool->SetOrientation(mitk::Quaternion(0,0,0,1)); mitk::NavigationData::Pointer TrackerToTool = mitk::NavigationData::New(); TrackerToTool->SetOrientation(markerTransformationTrackingCoordinates->GetOrientation()); TrackerToTool->SetPosition(markerTransformationTrackingCoordinates->GetPosition()); TrackerToTool->Compose(ToolTipToTool); // Compute pivot point in relation to marker transformation for preview mitk::NavigationData::Pointer ToolTipToTracker = mitk::NavigationData::New(); ToolTipToTracker->Compose(ToolTipToTool); ToolTipToTracker->Compose(markerTransformationTrackingCoordinates); //add the preview node to the data storage ShowToolTipPreview(ToolTipToTracker); //parse result string resultString = QString("Pivot computation succeeded!\n") + QString("RMS Error: ") + QString::number(myPivotCalibration->GetResultRMSError()) + QString("\n") + QString("Pivot Point: ") + QString::number(myPivotCalibration->GetResultPivotPoint()[0]) + ";" + QString::number(myPivotCalibration->GetResultPivotPoint()[1]) + ";" + QString::number(myPivotCalibration->GetResultPivotPoint()[2]) + QString("\n"); //finally: save results to member variable m_ComputedToolTipTransformation = ToolTipToTool; //enable button to use the computed point with the tool m_Controls.m_UseComputedPivotPoint->setEnabled(true); } else { resultString = "Pivot computation failed!"; } MITK_INFO << resultString.toStdString().c_str(); m_Controls.m_ResultText->setText(resultString); } void QmitkIGTNavigationToolCalibration::UpdatePivotCount() { PivotCount = 0; while (!m_PivotPoses.empty()) { m_PivotPoses.pop_back(); } m_Controls.m_PoseNumber->setText(QString::number(PivotCount)); } void QmitkIGTNavigationToolCalibration::OnUseComputedPivotPoint() { RemoveToolTipPreview(); QString resultString = QString("Pivoted tool tip transformation was written to the tool ") + m_ToolToCalibrate->GetToolName().c_str(); ApplyToolTipTransform(m_ComputedToolTipTransformation, resultString.toStdString()); m_Controls.m_ResultText->setText(resultString); UpdatePivotCount(); } void QmitkIGTNavigationToolCalibration::ApplyToolTipTransform(mitk::NavigationData::Pointer ToolTipTransformInToolCoordinates, std::string message) { if (!CheckInitialization(false)) { return; } //Update tool in tool storage m_ToolToCalibrate->SetToolTipPosition(ToolTipTransformInToolCoordinates->GetPosition()); m_ToolToCalibrate->SetToolAxisOrientation(ToolTipTransformInToolCoordinates->GetOrientation()); //And also update tracking device, so the transform is directly used mitk::TrackingDeviceSource::Pointer trackingDeviceSource; try { trackingDeviceSource = dynamic_cast(m_NavigationDataSourceOfToolToCalibrate.GetPointer()); mitk::TrackingTool::Pointer TrackingToolToCalibrate = trackingDeviceSource->GetTrackingDevice()->GetTool(m_IDToolToCalibrate); TrackingToolToCalibrate->SetToolTipPosition(ToolTipTransformInToolCoordinates->GetPosition(), ToolTipTransformInToolCoordinates->GetOrientation()); } catch (std::exception& e) { MITK_ERROR << "Error while trying to set the tool tip to the running tracking device. Aborting! (" << e.what() << ")"; } MITK_INFO << message; } void QmitkIGTNavigationToolCalibration::ShowToolTipPreview(mitk::NavigationData::Pointer ToolTipInTrackingCoordinates) { if(m_ToolTipPointPreview.IsNull()) { m_ToolTipPointPreview = mitk::DataNode::New(); m_ToolTipPointPreview->SetName("Modified Tool Tip Preview"); mitk::Color blue; blue.SetBlue(1); m_ToolTipPointPreview->SetColor(blue); mitk::Surface::Pointer mySphere = mitk::Surface::New(); vtkSmartPointer vtkData = vtkSmartPointer::New(); vtkData->SetRadius(3.0f); vtkData->SetCenter(0.0, 0.0, 0.0); vtkData->Update(); mySphere->SetVtkPolyData(vtkData->GetOutput()); m_ToolTipPointPreview->SetData(mySphere); this->GetDataStorage()->Add(m_ToolTipPointPreview); } m_ToolTipPointPreview->GetData()->GetGeometry()->SetIndexToWorldTransform(ToolTipInTrackingCoordinates->GetAffineTransform3D()); } void QmitkIGTNavigationToolCalibration::RemoveToolTipPreview() { this->GetDataStorage()->Remove(m_ToolTipPointPreview.GetPointer()); } void QmitkIGTNavigationToolCalibration::UpdateManualToolTipCalibrationView() { if (m_ToolToCalibrate.IsNull()) { return; } //parse human readable transformation data and display it std::stringstream translation; std::stringstream orientation; translation << m_ToolToCalibrate->GetToolTipPosition(); orientation << "Quaternion: (" << m_ToolToCalibrate->GetToolAxisOrientation() << ")" << std::endl; orientation << std::endl; orientation << "Euler Angles [rad]: (" << m_ToolToCalibrate->GetToolAxisOrientation().rotation_euler_angles() << ")" << std::endl; orientation << std::endl; orientation << "Matrix:" << std::endl; vnl_matrix_fixed rotMatrix = m_ToolToCalibrate->GetToolAxisOrientation().rotation_matrix_transpose(); orientation << rotMatrix[0][0] << " " << rotMatrix[0][1] << " " << rotMatrix[0][2] << std::endl; orientation << rotMatrix[1][0] << " " << rotMatrix[1][1] << " " << rotMatrix[1][2] << std::endl; orientation << rotMatrix[2][0] << " " << rotMatrix[2][1] << " " << rotMatrix[2][2] << std::endl; m_Controls.m_ManualCurrentTranslation->setText(translation.str().c_str()); m_Controls.m_ManualCurrentOrientation->setPlainText(orientation.str().c_str()); } void QmitkIGTNavigationToolCalibration::OnStartManualToolTipCalibration() { if (!CheckInitialization(false)) { return; } m_ToolTransformationWidget->SetToolToEdit(m_ToolToCalibrate); m_ToolTransformationWidget->SetDefaultOffset(m_ToolToCalibrate->GetToolTipPosition()); m_ToolTransformationWidget->SetDefaultRotation(m_ToolToCalibrate->GetToolAxisOrientation()); m_ToolTransformationWidget->open(); } void QmitkIGTNavigationToolCalibration::OnManualEditToolTipFinished(mitk::AffineTransform3D::Pointer toolTip) { //This function is called, when the toolTipEdit view is closed. //if user pressed cancle, nullptr is returned. Do nothing. Else, set values. if (toolTip) { mitk::NavigationData::Pointer tempND = mitk::NavigationData::New(toolTip);//Convert to Navigation data for simple transversion to quaternion QString resultString = QString("Manual edited values are written to ") + m_ToolToCalibrate->GetToolName().c_str(); ApplyToolTipTransform(tempND, resultString.toStdString()); m_Controls.m_ResultText->setText(resultString); m_ComputedToolTipTransformation = tempND; } UpdateManualToolTipCalibrationView(); } void QmitkIGTNavigationToolCalibration::OnGetPositions() { if (!CheckInitialization(true)) { return; } //Navigation Data from Tool which should be calibrated if (!m_AxisCalibration_ToolToCalibrate) m_AxisCalibration_ToolToCalibrate = mitk::NavigationData::New(); m_AxisCalibration_ToolToCalibrate->Graft(m_Controls.m_SelectionWidget->GetSelectedNavigationDataSource()->GetOutput(m_IDToolToCalibrate)); //Navigation Data from calibration pointer tool if (!m_AxisCalibration_NavDataCalibratingTool) m_AxisCalibration_NavDataCalibratingTool = mitk::NavigationData::New(); m_AxisCalibration_NavDataCalibratingTool->Graft(m_Controls.m_SelectionWidget->GetSelectedNavigationDataSource()->GetOutput(m_IDCalibrationPointer)); MITK_DEBUG << "Positions for tool axis calibration:"; MITK_DEBUG << " ToolTip: " << m_AxisCalibration_ToolToCalibrate->GetPosition() << ","; MITK_DEBUG << " Rotation: \n" << m_AxisCalibration_ToolToCalibrate->GetRotationMatrix(); MITK_DEBUG << " End of the tool: " << m_AxisCalibration_NavDataCalibratingTool->GetPosition(); QString _label = "Position recorded: " + QString::number(m_AxisCalibration_NavDataCalibratingTool->GetPosition()[0], 'f', 1) + ", " + QString::number(m_AxisCalibration_NavDataCalibratingTool->GetPosition()[1], 'f', 1) + ", " + QString::number(m_AxisCalibration_NavDataCalibratingTool->GetPosition()[2], 'f', 1); m_Controls.m_ToolAxisPositionLabel->setText(_label); } void QmitkIGTNavigationToolCalibration::OnCalibrateToolAxis() { if (!m_ComputedToolTipTransformation) { MITK_ERROR << "Please compute tool tip first."; QMessageBox::information(nullptr, "Error", "Please compute / specifiy tool tip first"); return; } if (!m_AxisCalibration_ToolToCalibrate || !m_AxisCalibration_NavDataCalibratingTool) { MITK_ERROR << "Please record position first."; QMessageBox::information(nullptr, "Error", "Please record position first"); return; } //Calculate the tool tip //here is an explanation, what is happening here: /* The axis is equal to the (tool tip) minus the (end of the tool) in tool coordinates of the tool which should be calibrated. The tool tip is defined as the origin of the tool coordinate system. The end of the tool is recorded by the calibration pointer's position and is transformed into the coordinate system of the tool to be calibrated Normalize it. */ //m_CalibratedToolAxis = -m_AxisCalibration_ToolToCalibrate->TransformPoint(m_AxisCalibration_NavDataCalibratingTool->GetPosition()).GetVectorFromOrigin(); m_CalibratedToolAxis = -m_AxisCalibration_ToolToCalibrate->GetInverse()->TransformPoint(m_AxisCalibration_NavDataCalibratingTool->GetPosition()).GetVectorFromOrigin(); MITK_DEBUG << "Tool Endpoint in Tool coordinates: " << m_CalibratedToolAxis; m_CalibratedToolAxis.Normalize(); MITK_DEBUG << "Tool Axis: " << m_CalibratedToolAxis; - m_ToolToCalibrate->SetToolAxis(m_CalibratedToolAxis); + m_ToolToCalibrate->SetToolAxis(mitk::Point3D(m_CalibratedToolAxis)); // Update TrackingTool m_ComputedToolTipTransformation->SetPosition(m_ToolToCalibrate->GetToolTipPosition()); m_ComputedToolTipTransformation->SetOrientation(m_ToolToCalibrate->GetToolAxisOrientation()); ApplyToolTipTransform(m_ComputedToolTipTransformation); //Update GUI QString calibratedToolAxisString = "Tool Axis: " + QString::number(m_CalibratedToolAxis.GetElement(0), 'f', 3) + ", " + QString::number(m_CalibratedToolAxis.GetElement(1), 'f', 3) + ", " + QString::number(m_CalibratedToolAxis.GetElement(2), 'f', 3); m_Controls.m_ToolAxisCalibrationLabel->setText(calibratedToolAxisString); //Block QT signals, we don't want to emit SpinboxChanged on the first value to overwrite the next ones m_Controls.m_ToolAxis_X->blockSignals(true); m_Controls.m_ToolAxis_Y->blockSignals(true); m_Controls.m_ToolAxis_Z->blockSignals(true); m_Controls.m_ToolAxis_X->setValue(m_CalibratedToolAxis[0]); m_Controls.m_ToolAxis_Y->setValue(m_CalibratedToolAxis[1]); m_Controls.m_ToolAxis_Z->setValue(m_CalibratedToolAxis[2]); m_Controls.m_ToolAxis_X->blockSignals(false); m_Controls.m_ToolAxis_Y->blockSignals(false); m_Controls.m_ToolAxis_Z->blockSignals(false); } void QmitkIGTNavigationToolCalibration::OnToolAxisSpinboxChanged() { m_CalibratedToolAxis.SetElement(0, m_Controls.m_ToolAxis_X->value()); m_CalibratedToolAxis.SetElement(1, m_Controls.m_ToolAxis_Y->value()); m_CalibratedToolAxis.SetElement(2, m_Controls.m_ToolAxis_Z->value()); - m_ToolToCalibrate->SetToolAxis(m_CalibratedToolAxis); + m_ToolToCalibrate->SetToolAxis(mitk::Point3D(m_CalibratedToolAxis)); // Update TrackingTool if (m_ComputedToolTipTransformation.IsNull()) { m_ComputedToolTipTransformation = mitk::NavigationData::New(); } m_ComputedToolTipTransformation->SetPosition(m_ToolToCalibrate->GetToolTipPosition()); m_ComputedToolTipTransformation->SetOrientation(m_ToolToCalibrate->GetToolAxisOrientation()); ApplyToolTipTransform(m_ComputedToolTipTransformation); MITK_INFO << "Tool axis changed to " << m_CalibratedToolAxis; } void QmitkIGTNavigationToolCalibration::SetToolToCalibrate() { m_IDToolToCalibrate = m_Controls.m_SelectionWidget->GetSelectedToolID(); if (m_IDToolToCalibrate == -1) //no valid tool to calibrate { m_Controls.m_CalToolLabel->setText(""); m_Controls.m_StatusWidgetToolToCalibrate->RemoveStatusLabels(); m_TrackingTimer->stop(); } else { m_ToolToCalibrate = m_Controls.m_SelectionWidget->GetSelectedNavigationTool(); m_NavigationDataSourceOfToolToCalibrate = m_Controls.m_SelectionWidget->GetSelectedNavigationDataSource(); m_Controls.m_CalToolLabel->setText(m_NavigationDataSourceOfToolToCalibrate->GetOutput(m_IDToolToCalibrate)->GetName()); //initialize widget m_Controls.m_StatusWidgetToolToCalibrate->RemoveStatusLabels(); m_Controls.m_StatusWidgetToolToCalibrate->SetShowPositions(true); m_Controls.m_StatusWidgetToolToCalibrate->SetTextAlignment(Qt::AlignLeft); m_Controls.m_StatusWidgetToolToCalibrate->AddNavigationData(m_NavigationDataSourceOfToolToCalibrate->GetOutput(m_IDToolToCalibrate)); m_Controls.m_StatusWidgetToolToCalibrate->ShowStatusLabels(); //initialize manual tool tip calibration view UpdateManualToolTipCalibrationView(); //save tool surface in tool coordinates for further editing mitk::Surface::Pointer ToolSurface = dynamic_cast(m_ToolToCalibrate->GetDataNode()->GetData())->Clone(); m_ToolSurfaceInToolCoordinatesDataNode->SetData(ToolSurface); m_ToolSurfaceInToolCoordinatesDataNode->GetData()->GetGeometry()->SetIdentity(); // update tool tip and rotation information for tool tip calibration tab UpdateManualToolTipCalibrationView(); // update tool axis information for tool axis calibration tab mitk::Point3D toolAxis = m_ToolToCalibrate->GetToolAxis(); m_CalibratedToolAxis[0] = toolAxis[0]; m_CalibratedToolAxis[1] = toolAxis[1]; m_CalibratedToolAxis[2] = toolAxis[2]; m_Controls.m_ToolAxis_X->blockSignals(true); m_Controls.m_ToolAxis_Y->blockSignals(true); m_Controls.m_ToolAxis_Z->blockSignals(true); m_Controls.m_ToolAxis_X->setValue(m_CalibratedToolAxis[0]); m_Controls.m_ToolAxis_Y->setValue(m_CalibratedToolAxis[1]); m_Controls.m_ToolAxis_Z->setValue(m_CalibratedToolAxis[2]); m_Controls.m_ToolAxis_X->blockSignals(false); m_Controls.m_ToolAxis_Y->blockSignals(false); m_Controls.m_ToolAxis_Z->blockSignals(false); //start updating timer for status widgets, etc. if (!m_TrackingTimer->isActive()) m_TrackingTimer->start(100); } } void QmitkIGTNavigationToolCalibration::SetCalibrationPointer() { m_IDCalibrationPointer = m_Controls.m_SelectionWidget->GetSelectedToolID(); if (m_IDCalibrationPointer == -1) { m_Controls.m_PointerLabel->setText(""); m_Controls.m_StatusWidgetCalibrationPointer->RemoveStatusLabels(); m_TrackingTimer->stop(); } else { m_NavigationDataSourceOfCalibrationPointer = m_Controls.m_SelectionWidget->GetSelectedNavigationDataSource(); m_Controls.m_PointerLabel->setText(m_NavigationDataSourceOfCalibrationPointer->GetOutput(m_IDCalibrationPointer)->GetName()); //initialize widget m_Controls.m_StatusWidgetCalibrationPointer->RemoveStatusLabels(); m_Controls.m_StatusWidgetCalibrationPointer->SetShowPositions(true); m_Controls.m_StatusWidgetCalibrationPointer->SetTextAlignment(Qt::AlignLeft); m_Controls.m_StatusWidgetCalibrationPointer->AddNavigationData(m_NavigationDataSourceOfCalibrationPointer->GetOutput(m_IDCalibrationPointer)); m_Controls.m_StatusWidgetCalibrationPointer->ShowStatusLabels(); if (!m_TrackingTimer->isActive()) m_TrackingTimer->start(100); } } void QmitkIGTNavigationToolCalibration::UpdateOffsetCoordinates() { if (m_NavigationDataSourceOfCalibrationPointer.IsNull() || m_NavigationDataSourceOfToolToCalibrate.IsNull()) { return; } mitk::NavigationData::Pointer referenceToolND = m_NavigationDataSourceOfCalibrationPointer->GetOutput(m_IDCalibrationPointer); mitk::NavigationData::Pointer toolToCalibrateND = m_NavigationDataSourceOfToolToCalibrate->GetOutput(m_IDToolToCalibrate); if (referenceToolND->IsDataValid() && toolToCalibrateND->IsDataValid()) { //computation: difference between both tools (in tool coordinates) //differenceND = toolToCalibrateND^-1 * referenceToolND mitk::NavigationData::Pointer differenceND = mitk::NavigationData::New(); differenceND->Compose(referenceToolND); differenceND->Compose(toolToCalibrateND->GetInverse()); //display this orientation in the UI m_Controls.m_OffsetCoordinates->setText( QString("x: ") + QString(QString::number(differenceND->GetPosition()[0], 103, 3)) + QString("; y: ") + (QString::number(differenceND->GetPosition()[1], 103, 3)) + QString("; z: ") + (QString::number(differenceND->GetPosition()[2], 103, 3))); m_Controls.m_OrientationOffsetCoordinates->setText( QString("qx: ") + QString(QString::number(differenceND->GetOrientation().x(), 103, 3)) + QString("; qy: ") + (QString::number(differenceND->GetOrientation().y(), 103, 3)) + QString("; qz: ") + (QString::number(differenceND->GetOrientation().z(), 103, 3)) + QString("; qr: ") + (QString::number(differenceND->GetOrientation().r(), 103, 3))); //also update preview if active if (m_ToolTipPointPreview.IsNotNull()) //NOT WORKING! TODO: fix or remove! { mitk::NavigationData::Pointer ToolTipTransform = mitk::NavigationData::New(); ToolTipTransform->SetPosition(m_ResultOffsetVector); mitk::NavigationData::Pointer ToolTipInTrackingCoordinates = mitk::NavigationData::New(); //maybe store as for better peformance... ToolTipInTrackingCoordinates->Compose(m_NavigationDataSourceOfToolToCalibrate->GetOutput(m_IDToolToCalibrate)); ToolTipInTrackingCoordinates->Compose(ToolTipTransform); m_ToolTipPointPreview->GetData()->GetGeometry()->SetIndexToWorldTransform(ToolTipInTrackingCoordinates->GetAffineTransform3D()); } } } void QmitkIGTNavigationToolCalibration::UpdateTrackingTimer() { m_Controls.m_StatusWidgetToolToCalibrate->Refresh(); m_Controls.m_StatusWidgetCalibrationPointer->Refresh(); if (m_OnLoginSingleRefToolNavigationDataClicked) LoginSingleRefToolNavigationData(); if (m_OnAddPivotPoseClicked) AddPivotPose(); // 1 == Single Reference Calibration Method if (m_IndexCurrentCalibrationMethod == 1) UpdateOffsetCoordinates(); } void QmitkIGTNavigationToolCalibration::AddLandmark() { if (!CheckInitialization()) { return; } mitk::NavigationData::Pointer navDataTool = m_NavigationDataSourceOfToolToCalibrate->GetOutput(m_IDToolToCalibrate); mitk::Point3D landmark = m_NavigationDataSourceOfCalibrationPointer->GetOutput(m_IDCalibrationPointer)->GetPosition(); //convert to itk transform itk::Vector translation; for (int k = 0; k < 3; k++) translation[k] = navDataTool->GetPosition()[k]; itk::Matrix rotation; for (int k = 0; k < 3; k++) for (int l = 0; l < 3; l++) rotation[k][l] = navDataTool->GetOrientation().rotation_matrix_transpose()[k][l]; rotation = rotation.GetTranspose(); itk::Vector landmarkItk; landmarkItk[0] = landmark[0]; landmarkItk[1] = landmark[1]; landmarkItk[2] = landmark[2]; //compute landmark in tool coordinates itk::Matrix rotationInverse; for (int k = 0; k < 3; k++) for (int l = 0; l < 3; l++) rotationInverse[k][l] = rotation.GetInverse()[k][l]; landmarkItk = rotationInverse * (landmarkItk - translation); //convert back and add landmark to pointset landmark[0] = landmarkItk[0]; landmark[1] = landmarkItk[1]; landmark[2] = landmarkItk[2]; m_RegistrationLandmarks->InsertPoint(m_RegistrationLandmarks->GetSize(), landmark); } void QmitkIGTNavigationToolCalibration::SaveCalibratedTool() { if (m_ToolToCalibrate.IsNotNull()) { mitk::NavigationTool::Pointer calibratedTool = m_ToolToCalibrate; calibratedTool->SetToolControlPoints(this->m_CalibrationLandmarks); calibratedTool->SetToolLandmarks(this->m_RegistrationLandmarks); mitk::NavigationToolWriter::Pointer myWriter = mitk::NavigationToolWriter::New(); QString filename = QFileDialog::getSaveFileName(nullptr,tr("Save Navigation Tool"), "/", "*.IGTTool"); if (filename.isEmpty()) return; // ensure that file suffix is set QFileInfo file(filename); if (file.suffix().isEmpty()) { filename += ".IGTTool"; } if (myWriter->DoWrite(filename.toStdString(), calibratedTool)) MITK_INFO << "Saved calibrated tool to file " << filename; else MITK_WARN << "Can't write tool to file " << filename; } else { MITK_ERROR << "Did not find navigation tool storage of calibrated tool, aborting!"; } } bool QmitkIGTNavigationToolCalibration::CheckInitialization(bool CalibrationPointerRequired) { if ((m_IDToolToCalibrate == -1) || ((CalibrationPointerRequired) && (m_IDCalibrationPointer == -1) ) ) { QMessageBox msgBox; msgBox.setText("Tool to calibrate and/or calibration pointer not initialized, cannot proceed!"); msgBox.exec(); return false; } else { return true; } }