diff --git a/Modules/Remeshing/mitkACVD.cpp b/Modules/Remeshing/mitkACVD.cpp index 3509695d18..74939ebe0d 100644 --- a/Modules/Remeshing/mitkACVD.cpp +++ b/Modules/Remeshing/mitkACVD.cpp @@ -1,196 +1,209 @@ /*=================================================================== 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 "mitkACVD.h" +#include #include #include #include #include #include #include #include #include struct ClustersQuadrics { explicit ClustersQuadrics(int size) : Elements(new double*[size]), Size(size) { for (int i = 0; i < size; ++i) { Elements[i] = new double[9]; for (int j = 0; j < 9; ++j) Elements[i][j] = 0.0; } } ~ClustersQuadrics() { for (int i = 0; i < Size; ++i) delete[] Elements[i]; delete Elements; } double** Elements; int Size; private: ClustersQuadrics(const ClustersQuadrics&); ClustersQuadrics& operator=(const ClustersQuadrics&); }; -static bool ValidateSurface(mitk::Surface::Pointer surface, unsigned int t) +static void ValidateSurface(mitk::Surface::ConstPointer surface, unsigned int t) { if (surface.IsNull()) - { - MITK_ERROR << "Input surface is NULL!"; - return false; - } + mitkThrow() << "Input surface is NULL!"; if (t >= surface->GetSizeOfPolyDataSeries()) - { - MITK_ERROR << "Input surface doesn't have data at time step " << t << "!"; - return false; - } + mitkThrow() << "Input surface doesn't have data at time step " << t << "!"; - vtkPolyData* polyData = surface->GetVtkPolyData(t); + vtkPolyData* polyData = const_cast(surface.GetPointer())->GetVtkPolyData(t); if (polyData == NULL) - { - MITK_ERROR << "PolyData of input surface at time step " << t << " is NULL!"; - return false; - } + mitkThrow() << "PolyData of input surface at time step " << t << " is NULL!"; if (polyData->GetNumberOfPolys() == 0) - { - MITK_ERROR << "Input surface has no polygons at time step " << t << "!"; - return false; - } - - return true; + mitkThrow() << "Input surface has no polygons at time step " << t << "!"; } -mitk::Surface::Pointer mitk::ACVD::Remesh(mitk::Surface::Pointer surface, unsigned int t, int numVertices, double gradation, int subsampling, double edgeSplitting, int optimizationLevel, bool forceManifold, bool boundaryFixing) +mitk::Surface::Pointer mitk::ACVD::Remesh(mitk::Surface::ConstPointer surface, unsigned int t, int numVertices, double gradation, int subsampling, double edgeSplitting, int optimizationLevel, bool forceManifold, bool boundaryFixing) { - if (!ValidateSurface(surface, t)) - return NULL; + ValidateSurface(surface, t); MITK_INFO << "Start remeshing..."; vtkSmartPointer surfacePolyData = vtkSmartPointer::New(); - surfacePolyData->DeepCopy(surface->GetVtkPolyData(t)); + surfacePolyData->DeepCopy(const_cast(surface.GetPointer())->GetVtkPolyData(t)); vtkSmartPointer mesh = vtkSmartPointer::New(); mesh->CreateFromPolyData(surfacePolyData); mesh->GetCellData()->Initialize(); mesh->GetPointData()->Initialize(); mesh->DisplayMeshProperties(); + if (numVertices == 0) + numVertices = surfacePolyData->GetNumberOfPoints(); + if (edgeSplitting != 0.0) mesh->SplitLongEdges(edgeSplitting); vtkSmartPointer remesher = vtkSmartPointer::New(); remesher->GetMetric()->SetGradation(gradation); remesher->SetBoundaryFixing(boundaryFixing); remesher->SetConsoleOutput(1); remesher->SetForceManifold(forceManifold); remesher->SetInput(mesh); remesher->SetNumberOfClusters(numVertices); remesher->SetNumberOfThreads(vtkMultiThreader::GetGlobalDefaultNumberOfThreads()); remesher->SetSubsamplingThreshold(subsampling); remesher->Remesh(); // Optimization: Minimize distance between input surface and remeshed surface if (optimizationLevel != 0) { ClustersQuadrics clustersQuadrics(numVertices); vtkSmartPointer faceList = vtkSmartPointer::New(); vtkSmartPointer clustering = remesher->GetClustering(); vtkSmartPointer remesherInput = remesher->GetInput(); int clusteringType = remesher->GetClusteringType(); int numItems = remesher->GetNumberOfItems(); int numMisclassifiedItems = 0; for (int i = 0; i < numItems; ++i) { int cluster = clustering->GetValue(i); if (cluster >= 0 && cluster < numVertices) { if (clusteringType != 0) { remesherInput->GetVertexNeighbourFaces(i, faceList); int numIds = static_cast(faceList->GetNumberOfIds()); for (int j = 0; j < numIds; ++j) vtkQuadricTools::AddTriangleQuadric(clustersQuadrics.Elements[cluster], remesherInput, faceList->GetId(j), false); } else { vtkQuadricTools::AddTriangleQuadric(clustersQuadrics.Elements[cluster], remesherInput, i, false); } } else { ++numMisclassifiedItems; } } if (numMisclassifiedItems != 0) std::cout << numMisclassifiedItems << " items with wrong cluster association" << std::endl; vtkSmartPointer remesherOutput = remesher->GetOutput(); double point[3]; for (int i = 0; i < numVertices; ++i) { remesherOutput->GetPoint(i, point); vtkQuadricTools::ComputeRepresentativePoint(clustersQuadrics.Elements[i], point, optimizationLevel); remesherOutput->SetPointCoordinates(i, point); } std::cout << "After quadrics post-processing:" << std::endl; remesherOutput->DisplayMeshProperties(); } vtkSmartPointer normals = vtkSmartPointer::New(); normals->SetInput(remesher->GetOutput()); normals->AutoOrientNormalsOn(); normals->ComputeCellNormalsOff(); normals->ComputePointNormalsOn(); normals->ConsistencyOff(); normals->FlipNormalsOff(); normals->NonManifoldTraversalOff(); normals->SplittingOff(); normals->Update(); Surface::Pointer remeshedSurface = Surface::New(); remeshedSurface->SetVtkPolyData(normals->GetOutput()); MITK_INFO << "Finished remeshing"; return remeshedSurface; } + +mitk::ACVD::RemeshFilter::RemeshFilter() +: m_TimeStep(0), + m_NumVertices(0), + m_Gradation(1.0), + m_Subsampling(10), + m_EdgeSplitting(0.0), + m_OptimizationLevel(1), + m_ForceManifold(false), + m_BoundaryFixing(false) +{ + Surface::Pointer output = Surface::New(); + this->SetNthOutput(0, output); +} + +mitk::ACVD::RemeshFilter::~RemeshFilter() +{ +} + +void mitk::ACVD::RemeshFilter::GenerateData() +{ + Surface::Pointer output = Remesh(this->GetInput(), m_TimeStep, m_NumVertices, m_Gradation, m_Subsampling, m_EdgeSplitting, m_OptimizationLevel, m_ForceManifold, m_BoundaryFixing); + this->SetNthOutput(0, output); +} diff --git a/Modules/Remeshing/mitkACVD.h b/Modules/Remeshing/mitkACVD.h index 99b8cbc2c3..315b6cefc9 100644 --- a/Modules/Remeshing/mitkACVD.h +++ b/Modules/Remeshing/mitkACVD.h @@ -1,55 +1,90 @@ /*=================================================================== 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 mitkACVD_h #define mitkACVD_h #include +#include #include namespace mitk { namespace ACVD { /** \brief Remesh a surface and store the result in a new surface. * * The %ACVD library is used for remeshing which is based on the paper "Approximated Centroidal Voronoi Diagrams for Uniform Polygonal Mesh Coarsening" by S. Valette, and J. M. Chassery. * There are a few rules of thumbs regarding the ranges of parameters to gain high quality remeshed surfaces: * *
    *
  • numVertices is exact, however, if boundaryFixing is enabled, additional vertices are generated at boundaries *
  • %Set gradation to zero in case you want polygons of roughly the same size all over the remeshed surface; start with 1 otherwise *
  • subsampling has direct influence on the quality of the remeshed surface (higher values take more time) *
  • edgeSplitting is useful for surfaces that contain long and thin triangles but takes a long time *
  • Leave optimizationLevel set to 1 as greater values result in degenerated polygons *
  • Irregular shrinking of boundaries during remeshing can be avoided by boundaryFixing, however this results in additional, lower quality polygons at boundaries *
* * \param[in] surface Input surface. * \param[in] t Time step of a four-dimensional input surface, zero otherwise. - * \param[in] numVertices Desired number of vertices in the remeshed surface. + * \param[in] numVertices Desired number of vertices in the remeshed surface, set to zero to keep original vertex count. * \param[in] gradation Influence of surface curvature on polygon size. * \param[in] subsampling Subsample input surface until number of vertices exceeds initial count times this parameter. * \param[in] edgeSplitting Recursively split edges that are longer than the average edge length times this parameter. * \param[in] optimizationLevel Minimize distance between input surface and remeshed surface. * \param[in] boundaryFixing Keep original surface boundaries by adding additional polygons. * \return Returns the remeshed surface or NULL if input surface is invalid. */ - Remeshing_EXPORT Surface::Pointer Remesh(Surface::Pointer surface, unsigned int t, int numVertices, double gradation, int subsampling = 10, double edgeSplitting = 0.0, int optimizationLevel = 1, bool forceManifold = false, bool boundaryFixing = false); + Remeshing_EXPORT Surface::Pointer Remesh(Surface::ConstPointer surface, unsigned int t, int numVertices, double gradation, int subsampling = 10, double edgeSplitting = 0.0, int optimizationLevel = 1, bool forceManifold = false, bool boundaryFixing = false); + + /** \brief Encapsulates mitk::ACVD::Remesh function as filter. + */ + class Remeshing_EXPORT RemeshFilter : public mitk::SurfaceToSurfaceFilter + { + public: + mitkClassMacro(RemeshFilter, SurfaceToSurfaceFilter); + itkNewMacro(Self); + + itkSetMacro(TimeStep, unsigned int); + itkSetMacro(NumVertices, int); + itkSetMacro(Gradation, double); + itkSetMacro(Subsampling, int); + itkSetMacro(EdgeSplitting, double); + itkSetMacro(OptimizationLevel, int); + itkSetMacro(ForceManifold, bool); + itkSetMacro(BoundaryFixing, bool); + + protected: + void GenerateData(); + + private: + RemeshFilter(); + ~RemeshFilter(); + + unsigned int m_TimeStep; + int m_NumVertices; + double m_Gradation; + int m_Subsampling; + double m_EdgeSplitting; + int m_OptimizationLevel; + bool m_ForceManifold; + bool m_BoundaryFixing; + }; } } #endif diff --git a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp index ac5b17ecfa..2a6f1a03c5 100644 --- a/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp +++ b/Plugins/org.mitk.gui.qt.remeshing/src/internal/QmitkRemeshingView.cpp @@ -1,232 +1,255 @@ /*=================================================================== 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 "QmitkRemeshingView.h" // MITK #include #include #include #include #include #include #include // Qt #include // VTK #include #include // C++ Standard Library #include #include QmitkRemeshingView::QmitkRemeshingView() { } QmitkRemeshingView::~QmitkRemeshingView() { } void QmitkRemeshingView::CreateQtPartControl(QWidget* parent) { m_Controls.setupUi(parent); m_Controls.surfaceComboBox->SetDataStorage(this->GetDataStorage()); m_Controls.surfaceComboBox->SetPredicate(mitk::NodePredicateAnd::New( mitk::TNodePredicateDataType::New(), mitk::NodePredicateNot::New(mitk::NodePredicateProperty::New("helper object")))); QIntValidator* posIntValidator = new QIntValidator(0, std::numeric_limits::max(), parent); m_Controls.maxNumVerticesLineEdit->setValidator(posIntValidator); this->EnableWidgets(m_Controls.surfaceComboBox->GetSelectedNode().IsNotNull()); this->OnAdvancedSettingsButtonToggled(false); connect(m_Controls.surfaceComboBox, SIGNAL(OnSelectionChanged(const mitk::DataNode *)), this, SLOT(OnSelectedSurfaceChanged(const mitk::DataNode *))); connect(m_Controls.numVerticesSlider, SIGNAL(valueChanged(int)), this, SLOT(OnNumberOfVerticesChanged(int))); connect(m_Controls.numVerticesSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnNumberOfVerticesChanged(int))); connect(m_Controls.gradationSlider, SIGNAL(valueChanged(double)), this, SLOT(OnGradationChanged(double))); connect(m_Controls.gradationSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnGradationChanged(double))); connect(m_Controls.advancedSettingsButton, SIGNAL(toggled(bool)), this, SLOT(OnAdvancedSettingsButtonToggled(bool))); connect(m_Controls.maxNumVerticesLineEdit, SIGNAL(editingFinished()), this, SLOT(OnMaxNumVerticesLineEditEditingFinished())); connect(m_Controls.edgeSplittingSlider, SIGNAL(valueChanged(double)), this, SLOT(OnEdgeSplittingChanged(double))); connect(m_Controls.edgeSplittingSpinBox, SIGNAL(valueChanged(double)), this, SLOT(OnEdgeSplittingChanged(double))); connect(m_Controls.subsamplingSlider, SIGNAL(valueChanged(int)), this, SLOT(OnSubsamplingChanged(int))); connect(m_Controls.subsamplingSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnSubsamplingChanged(int))); connect(m_Controls.optimizationLevelSlider, SIGNAL(valueChanged(int)), this, SLOT(OnOptimizationLevelChanged(int))); connect(m_Controls.optimizationLevelSpinBox, SIGNAL(valueChanged(int)), this, SLOT(OnOptimizationLevelChanged(int))); connect(m_Controls.remeshPushButton, SIGNAL(clicked()), this, SLOT(OnRemeshButtonClicked())); this->OnSelectedSurfaceChanged(m_Controls.surfaceComboBox->GetSelectedNode()); } void QmitkRemeshingView::EnableWidgets(bool enable) { m_Controls.surfaceComboBox->setEnabled(enable); m_Controls.numVerticesSlider->setEnabled(enable); m_Controls.numVerticesSpinBox->setEnabled(enable); m_Controls.gradationSlider->setEnabled(enable); m_Controls.gradationSpinBox->setEnabled(enable); m_Controls.maxNumVerticesLineEdit->setEnabled(enable); m_Controls.edgeSplittingSlider->setEnabled(enable); m_Controls.edgeSplittingSpinBox->setEnabled(enable); m_Controls.subsamplingSlider->setEnabled(enable); m_Controls.subsamplingSpinBox->setEnabled(enable); m_Controls.optimizationLevelSlider->setEnabled(enable); m_Controls.optimizationLevelSpinBox->setEnabled(enable); m_Controls.forceManifoldCheckBox->setEnabled(enable); m_Controls.boundaryFixingCheckBox->setEnabled(enable); m_Controls.remeshPushButton->setEnabled(enable); } void QmitkRemeshingView::OnAdvancedSettingsButtonToggled(bool toggled) { m_Controls.maxNumVerticesLabel->setVisible(toggled); m_Controls.maxNumVerticesLineEdit->setVisible(toggled); m_Controls.edgeSplittingLabel->setVisible(toggled); m_Controls.edgeSplittingSlider->setVisible(toggled); m_Controls.edgeSplittingSpinBox->setVisible(toggled); m_Controls.subsamplingLabel->setVisible(toggled); m_Controls.subsamplingSlider->setVisible(toggled); m_Controls.subsamplingSpinBox->setVisible(toggled); m_Controls.optimizationLevelLabel->setVisible(toggled); m_Controls.optimizationLevelSlider->setVisible(toggled); m_Controls.optimizationLevelSpinBox->setVisible(toggled); m_Controls.forceManifoldCheckBox->setVisible(toggled); m_Controls.boundaryFixingCheckBox->setVisible(toggled); } void QmitkRemeshingView::OnEdgeSplittingChanged(double edgeSplitting) { if (edgeSplitting != m_Controls.edgeSplittingSlider->value()) m_Controls.edgeSplittingSlider->setValue(edgeSplitting); if (edgeSplitting != m_Controls.edgeSplittingSpinBox->value()) m_Controls.edgeSplittingSpinBox->setValue(edgeSplitting); } void QmitkRemeshingView::OnGradationChanged(double gradation) { if (gradation != m_Controls.gradationSlider->value()) m_Controls.gradationSlider->setValue(gradation); if (gradation != m_Controls.gradationSpinBox->value()) m_Controls.gradationSpinBox->setValue(gradation); } void QmitkRemeshingView::OnMaxNumVerticesLineEditEditingFinished() { int maximum = m_Controls.maxNumVerticesLineEdit->text().toInt(); if (m_Controls.numVerticesSpinBox->maximum() != maximum) { m_Controls.numVerticesSlider->setMaximum(maximum); m_Controls.numVerticesSpinBox->setMaximum(maximum); } } void QmitkRemeshingView::OnNumberOfVerticesChanged(int numVertices) { if (numVertices != m_Controls.numVerticesSlider->value()) m_Controls.numVerticesSlider->setValue(numVertices); if (numVertices != m_Controls.numVerticesSpinBox->value()) m_Controls.numVerticesSpinBox->setValue(numVertices); } void QmitkRemeshingView::OnOptimizationLevelChanged(int optimizationLevel) { if (optimizationLevel != m_Controls.optimizationLevelSlider->value()) m_Controls.optimizationLevelSlider->setValue(optimizationLevel); if (optimizationLevel != m_Controls.optimizationLevelSpinBox->value()) m_Controls.optimizationLevelSpinBox->setValue(optimizationLevel); } void QmitkRemeshingView::OnRemeshButtonClicked() { mitk::DataNode::Pointer selectedNode = m_Controls.surfaceComboBox->GetSelectedNode(); - mitk::Surface::Pointer surface = static_cast(selectedNode->GetData()); + mitk::Surface::ConstPointer surface = static_cast(selectedNode->GetData()); int numVertices = m_Controls.numVerticesSpinBox->value(); double gradation = m_Controls.gradationSpinBox->value(); int subsampling = m_Controls.subsamplingSpinBox->value(); double edgeSplitting = m_Controls.edgeSplittingSpinBox->value(); int optimizationLevel = m_Controls.optimizationLevelSpinBox->value(); bool forceManifold = m_Controls.forceManifoldCheckBox->isChecked(); bool boundaryFixing = m_Controls.boundaryFixingCheckBox->isChecked(); - mitk::Surface::Pointer remeshedSurface = mitk::ACVD::Remesh(surface, 0, numVertices, gradation, subsampling, edgeSplitting, optimizationLevel, forceManifold, boundaryFixing); + // mitk::Surface::Pointer remeshedSurface = mitk::ACVD::Remesh(surface, 0, numVertices, gradation, subsampling, edgeSplitting, optimizationLevel, forceManifold, boundaryFixing); + + mitk::ACVD::RemeshFilter::Pointer remesher = mitk::ACVD::RemeshFilter::New(); + remesher->SetInput(surface); + remesher->SetTimeStep(0); + remesher->SetNumVertices(numVertices); + remesher->SetGradation(gradation); + remesher->SetSubsampling(subsampling); + remesher->SetEdgeSplitting(edgeSplitting); + remesher->SetOptimizationLevel(optimizationLevel); + remesher->SetForceManifold(forceManifold); + remesher->SetBoundaryFixing(boundaryFixing); + + try + { + remesher->Update(); + } + catch(const mitk::Exception& exception) + { + MITK_ERROR << exception.GetDescription(); + return; + } + + mitk::Surface::Pointer remeshedSurface = remesher->GetOutput(); mitk::DataNode::Pointer newNode = mitk::DataNode::New(); newNode->SetName(QString("%1 (%2, %3)").arg(selectedNode->GetName().c_str()).arg(remeshedSurface->GetVtkPolyData()->GetNumberOfPoints()).arg(gradation).toStdString()); newNode->SetProperty("material.representation", mitk::VtkRepresentationProperty::New(VTK_WIREFRAME)); newNode->SetProperty("material.specularCoefficient", mitk::FloatProperty::New(0.0f)); newNode->SetData(remeshedSurface); this->GetDataStorage()->Add(newNode, selectedNode); } void QmitkRemeshingView::OnSelectedSurfaceChanged(const mitk::DataNode *node) { if (node != NULL) { int numVertices = static_cast(static_cast(node->GetData())->GetVtkPolyData()->GetNumberOfPoints()); int minimum = numVertices < 100 ? numVertices : 100; int maximum = numVertices == minimum ? numVertices * 10 : numVertices; int step = std::max(1, maximum / 10); this->SetNumberOfVertices(minimum, maximum, step, numVertices); this->EnableWidgets(true); } else { this->EnableWidgets(false); this->SetNumberOfVertices(0, 0, 0, 0); } } void QmitkRemeshingView::OnSubsamplingChanged(int subsampling) { if (subsampling != m_Controls.subsamplingSlider->value()) m_Controls.subsamplingSlider->setValue(subsampling); if (subsampling != m_Controls.subsamplingSpinBox->value()) m_Controls.subsamplingSpinBox->setValue(subsampling); } void QmitkRemeshingView::SetFocus() { m_Controls.surfaceComboBox->setFocus(); } void QmitkRemeshingView::SetNumberOfVertices(int minimum, int maximum, int step, int value) { m_Controls.numVerticesSlider->setMinimum(minimum); m_Controls.numVerticesSlider->setMaximum(maximum); m_Controls.numVerticesSlider->setSingleStep(1); m_Controls.numVerticesSlider->setPageStep(step); m_Controls.numVerticesSpinBox->setMinimum(minimum); m_Controls.numVerticesSpinBox->setMaximum(maximum); m_Controls.numVerticesSpinBox->setSingleStep(step); m_Controls.numVerticesSpinBox->setValue(value); m_Controls.maxNumVerticesLineEdit->setText(QString("%1").arg(maximum)); }