diff --git a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp index e0636bc1f7..285092941a 100644 --- a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp +++ b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.cpp @@ -1,275 +1,320 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkShowSegmentationAsSurface.h" #include "mitkManualSegmentationToSurfaceFilter.h" #include "mitkVtkRepresentationProperty.h" #include #include #include namespace mitk { - ShowSegmentationAsSurface::ShowSegmentationAsSurface() : m_UIDGeneratorSurfaces("Surface_") {} - ShowSegmentationAsSurface::~ShowSegmentationAsSurface() {} + ShowSegmentationAsSurface::ShowSegmentationAsSurface() + : m_UIDGeneratorSurfaces("Surface_"), + m_IsLabelSetImage(false) + { + } + + ShowSegmentationAsSurface::~ShowSegmentationAsSurface() + { + } + void ShowSegmentationAsSurface::Initialize(const NonBlockingAlgorithm *other) { Superclass::Initialize(other); bool syncVisibility(false); if (other) { other->GetParameter("Sync visibility", syncVisibility); } SetParameter("Sync visibility", syncVisibility); SetParameter("Median kernel size", 3u); SetParameter("Apply median", true); SetParameter("Smooth", true); - SetParameter("Gaussian SD", 1.5f); + SetParameter("Gaussian SD", 1.5); SetParameter("Decimate mesh", true); - SetParameter("Decimation rate", 0.8f); + SetParameter("Decimation rate", 0.8); SetParameter("Wireframe", false); - m_Surfaces.clear(); - m_DeleteMe.clear(); + m_SurfaceNodes.clear(); } bool ShowSegmentationAsSurface::ReadyToRun() { try { Image::Pointer image; GetPointerParameter("Input", image); return image.IsNotNull() && GetGroupNode(); } catch (std::invalid_argument &) { return false; } } bool ShowSegmentationAsSurface::ThreadedUpdateFunction() { Image::Pointer image; GetPointerParameter("Input", image); bool smooth(true); GetParameter("Smooth", smooth); bool applyMedian(true); GetParameter("Apply median", applyMedian); bool decimateMesh(true); GetParameter("Decimate mesh", decimateMesh); unsigned int medianKernelSize(3); GetParameter("Median kernel size", medianKernelSize); - float gaussianSD(1.5); + double gaussianSD(1.5); GetParameter("Gaussian SD", gaussianSD); - float reductionRate(0.8); + double reductionRate(0.8); GetParameter("Decimation rate", reductionRate); MITK_INFO << "Creating polygon model with smoothing " << smooth << " gaussianSD " << gaussianSD << " median " << applyMedian << " median kernel " << medianKernelSize << " mesh reduction " << decimateMesh << " reductionRate " << reductionRate; - // TODO: Move loop out of the multithreaded method - auto labelSetImage = dynamic_cast(image.GetPointer()); if (nullptr != labelSetImage) { auto numberOfLayers = labelSetImage->GetNumberOfLayers(); for (decltype(numberOfLayers) layerIndex = 0; layerIndex < numberOfLayers; ++layerIndex) { auto labelSet = labelSetImage->GetLabelSet(layerIndex); - auto numberOfLabels = labelSet->GetNumberOfLabels(); - for (decltype(numberOfLabels) labelIndex = 0; labelIndex < numberOfLabels; ++labelIndex) + for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) { - for (auto labelIter = labelSet->IteratorConstBegin(); labelIter != labelSet->IteratorConstEnd(); ++labelIter) - { - if (0 == labelIter->first) - continue; - - MITK_INFO << "Layer " << layerIndex << ", Label " << labelIndex << " (" << labelIter->first << ")"; - auto labelImage = labelSetImage->CreateLabelMask(labelIter->first, false, layerIndex); - m_DeleteMe.push_back(labelImage); - } + if (0 == labelIter->first) + continue; // Do not process background label + + auto labelImage = labelSetImage->CreateLabelMask(labelIter->first, false, layerIndex); + + if (labelImage.IsNull()) + continue; + + auto labelSurface = this->ConvertBinaryImageToSurface(labelImage); + + if (labelSurface.IsNull()) + continue; + + auto node = DataNode::New(); + node->SetData(labelSurface); + node->SetColor(labelIter->second->GetColor()); + node->SetName(labelIter->second->GetName()); + + m_SurfaceNodes.push_back(node); } } } else { - ManualSegmentationToSurfaceFilter::Pointer surfaceFilter = ManualSegmentationToSurfaceFilter::New(); - surfaceFilter->SetInput(image); - surfaceFilter->SetThreshold(0.5); // expects binary image with zeros and ones + auto surface = this->ConvertBinaryImageToSurface(image); - surfaceFilter->SetUseGaussianImageSmooth(smooth); // apply gaussian to thresholded image ? - surfaceFilter->SetSmooth(smooth); - if (smooth) + if (surface.IsNotNull()) { - surfaceFilter->InterpolationOn(); - surfaceFilter->SetGaussianStandardDeviation(gaussianSD); + auto node = DataNode::New(); + node->SetData(surface); + m_SurfaceNodes.push_back(node); } + } - surfaceFilter->SetMedianFilter3D(applyMedian); // apply median to segmentation before marching cubes ? - if (applyMedian) - { - surfaceFilter->SetMedianKernelSize( - medianKernelSize, medianKernelSize, medianKernelSize); // apply median to segmentation before marching cubes - } + m_IsLabelSetImage = nullptr != labelSetImage; + return true; + } - // fix to avoid vtk warnings see bug #5390 - if (image->GetDimension() > 3) - decimateMesh = false; + void ShowSegmentationAsSurface::ThreadedUpdateSuccessful() + { + for (const auto &node : m_SurfaceNodes) + { + bool wireframe = false; + GetParameter("Wireframe", wireframe); - if (decimateMesh) + if (wireframe) { - surfaceFilter->SetDecimate(ImageToSurfaceFilter::QuadricDecimation); - surfaceFilter->SetTargetReduction(reductionRate); + auto representation = dynamic_cast(node->GetProperty("material.representation")); + if (nullptr != representation) + representation->SetRepresentationToWireframe(); } - else + + node->SetProperty("opacity", FloatProperty::New(0.3f)); + node->SetProperty("line width", FloatProperty::New(1.0f)); + node->SetProperty("scalar visibility", BoolProperty::New(false)); + + auto name = node->GetName(); + auto groupNode = this->GetGroupNode(); + + if (!m_IsLabelSetImage) { - surfaceFilter->SetDecimate(ImageToSurfaceFilter::NoDecimation); - } + if ((name.empty() || DataNode::NO_NAME_VALUE() == name) && nullptr != groupNode) + name = groupNode->GetName(); - surfaceFilter->UpdateLargestPossibleRegion(); + if (name.empty()) + name = "Surface"; + } - // calculate normals for nicer display - auto surface = surfaceFilter->GetOutput(); - vtkPolyData *polyData = surface->GetVtkPolyData(); + bool smooth = true; + GetParameter("Smooth", smooth); - if (!polyData) - throw std::logic_error("Could not create polygon model"); + if (smooth) + name.append(" (smoothed)"); - polyData->SetVerts(nullptr); - polyData->SetLines(nullptr); + node->SetName(name); - if (smooth || applyMedian || decimateMesh) + if (!m_IsLabelSetImage) { - vtkPolyDataNormals *normalsGen = vtkPolyDataNormals::New(); + auto colorProp = groupNode->GetProperty("color"); + + if (nullptr != colorProp) + { + node->ReplaceProperty("color", colorProp->Clone()); + } + else + { + node->SetProperty("color", ColorProperty::New(1.0, 1.0, 0.0)); + } + } + + bool showResult = true; + GetParameter("Show result", showResult); - normalsGen->AutoOrientNormalsOn(); - normalsGen->FlipNormalsOff(); - normalsGen->SetInputData(polyData); - normalsGen->Update(); + bool syncVisibility = false; + GetParameter("Sync visibility", syncVisibility); - surface->SetVtkPolyData(normalsGen->GetOutput()); + auto visibleProp = groupNode->GetProperty("visible"); - normalsGen->Delete(); + if (nullptr != visibleProp && syncVisibility) + { + node->ReplaceProperty("visible", visibleProp->Clone()); } else { - surface->SetVtkPolyData(polyData); + node->SetProperty("visible", BoolProperty::New(showResult)); + } + + if (!m_IsLabelSetImage) + { + Image::Pointer image; + GetPointerParameter("Input", image); + + if (image.IsNotNull()) + { + auto organTypeProp = image->GetProperty("organ type"); + + if (nullptr != organTypeProp) + node->GetData()->SetProperty("organ type", organTypeProp); + } } - m_Surfaces.push_back(surface); + this->InsertBelowGroupNode(node); } - return true; + Superclass::ThreadedUpdateSuccessful(); } - void ShowSegmentationAsSurface::ThreadedUpdateSuccessful() + Surface::Pointer ShowSegmentationAsSurface::ConvertBinaryImageToSurface(Image::Pointer binaryImage) { - if (!m_DeleteMe.empty()) - { - for (const auto &labelImage : m_DeleteMe) - { - auto node = DataNode::New(); - node->SetData(labelImage); - this->InsertBelowGroupNode(node); - } + bool smooth = true; + GetParameter("Smooth", smooth); - return; - } + bool applyMedian = true; + GetParameter("Apply median", applyMedian); - for (auto surface : m_Surfaces) - { - auto node = DataNode::New(); + bool decimateMesh = true; + GetParameter("Decimate mesh", decimateMesh); - bool wireframe(false); - GetParameter("Wireframe", wireframe); - if (wireframe) - { - auto *np = - dynamic_cast(node->GetProperty("material.representation")); - if (np) - np->SetRepresentationToWireframe(); - } + unsigned int medianKernelSize = 3; + GetParameter("Median kernel size", medianKernelSize); - node->SetProperty("opacity", FloatProperty::New(0.3)); - node->SetProperty("line width", IntProperty::New(1)); - node->SetProperty("scalar visibility", BoolProperty::New(false)); + double gaussianSD = 1.5; + GetParameter("Gaussian SD", gaussianSD); - std::string groupNodesName("surface"); + double reductionRate = 0.8; + GetParameter("Decimation rate", reductionRate); - DataNode *groupNode = GetGroupNode(); - if (groupNode) - { - groupNode->GetName(groupNodesName); - // if parameter smooth is set add extension to node name - bool smooth(true); - GetParameter("Smooth", smooth); - if (smooth) - groupNodesName.append("_smoothed"); - } - node->SetProperty("name", StringProperty::New(groupNodesName)); + auto filter = ManualSegmentationToSurfaceFilter::New(); + filter->SetInput(binaryImage); + filter->SetThreshold(0.5); + filter->SetUseGaussianImageSmooth(smooth); + filter->SetSmooth(smooth); + filter->SetMedianFilter3D(applyMedian); - // synchronize this object's color with the parent's color - // surfaceNode->SetProperty( "color", parentNode->GetProperty( "color" ) ); - // surfaceNode->SetProperty( "visible", parentNode->GetProperty( "visible" ) ); + if (smooth) + { + filter->InterpolationOn(); + filter->SetGaussianStandardDeviation(gaussianSD); + } - node->SetData(surface); + if (applyMedian) + filter->SetMedianKernelSize(medianKernelSize, medianKernelSize, medianKernelSize); - BaseProperty *colorProp = groupNode->GetProperty("color"); - if (colorProp) - node->ReplaceProperty("color", colorProp->Clone()); - else - node->SetProperty("color", ColorProperty::New(1.0, 1.0, 0.0)); + // Fix to avoid VTK warnings (see T5390) + if (binaryImage->GetDimension() > 3) + decimateMesh = false; - bool showResult(true); - GetParameter("Show result", showResult); + if (decimateMesh) + { + filter->SetDecimate(ImageToSurfaceFilter::QuadricDecimation); + filter->SetTargetReduction(reductionRate); + } + else + { + filter->SetDecimate(ImageToSurfaceFilter::NoDecimation); + } - bool syncVisibility(false); - GetParameter("Sync visibility", syncVisibility); + filter->UpdateLargestPossibleRegion(); - Image::Pointer image; - GetPointerParameter("Input", image); + auto surface = filter->GetOutput(); + auto polyData = surface->GetVtkPolyData(); - BaseProperty *organTypeProp = image->GetProperty("organ type"); - if (organTypeProp) - surface->SetProperty("organ type", organTypeProp); + if (nullptr == polyData) + throw std::logic_error("Could not create polygon model"); - BaseProperty *visibleProp = groupNode->GetProperty("visible"); - if (visibleProp && syncVisibility) - node->ReplaceProperty("visible", visibleProp->Clone()); - else - node->SetProperty("visible", BoolProperty::New(showResult)); + polyData->SetVerts(nullptr); + polyData->SetLines(nullptr); + + if (smooth || applyMedian || decimateMesh) + { + auto normals = vtkSmartPointer::New(); + + normals->AutoOrientNormalsOn(); + normals->FlipNormalsOff(); + normals->SetInputData(polyData); - InsertBelowGroupNode(node); + normals->Update(); + + surface->SetVtkPolyData(normals->GetOutput()); + } + else + { + surface->SetVtkPolyData(polyData); } - Superclass::ThreadedUpdateSuccessful(); + return surface; } - -} // namespace +} diff --git a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.h b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.h index f9b3ddafea..55124bf463 100644 --- a/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.h +++ b/Modules/Segmentation/Algorithms/mitkShowSegmentationAsSurface.h @@ -1,52 +1,54 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #ifndef MITK_SHOW_SEGMENTATION_AS_SURFACE_H_INCLUDET_WAD #define MITK_SHOW_SEGMENTATION_AS_SURFACE_H_INCLUDET_WAD #include "mitkSegmentationSink.h" #include "mitkSurface.h" #include "mitkUIDGenerator.h" #include namespace mitk { class MITKSEGMENTATION_EXPORT ShowSegmentationAsSurface : public SegmentationSink { public: mitkClassMacro(ShowSegmentationAsSurface, SegmentationSink) mitkAlgorithmNewMacro(ShowSegmentationAsSurface); protected: ShowSegmentationAsSurface(); // use smart pointers ~ShowSegmentationAsSurface() override; void Initialize(const NonBlockingAlgorithm *other = nullptr) override; bool ReadyToRun() override; bool ThreadedUpdateFunction() override; // will be called from a thread after calling StartAlgorithm void ThreadedUpdateSuccessful() override; // will be called from a thread after calling StartAlgorithm private: + mitk::Surface::Pointer ConvertBinaryImageToSurface(mitk::Image::Pointer binaryImage); + UIDGenerator m_UIDGeneratorSurfaces; - std::vector m_Surfaces; - std::vector m_DeleteMe; + std::vector m_SurfaceNodes; + bool m_IsLabelSetImage; }; } // namespace #endif