Skip to content

Commit 6b5b02b

Browse files
authored
Merge branch 'master' into dependabot/pip/bokeh-3.8.2
2 parents 3934f23 + 15e22e6 commit 6b5b02b

15 files changed

Lines changed: 367 additions & 247 deletions

Libs/Groom/Groom.cpp

Lines changed: 47 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ bool Groom::image_pipeline(std::shared_ptr<Subject> subject, size_t domain) {
187187

188188
if (params.get_convert_to_mesh()) {
189189
Mesh mesh = image.toMesh(0.0);
190-
run_mesh_pipeline(mesh, params);
190+
run_mesh_pipeline(mesh, params, original);
191191
groomed_name = get_output_filename(original, DomainType::Mesh);
192192
// save the groomed mesh
193193
MeshUtils::threadSafeWriteMesh(groomed_name, mesh);
@@ -323,7 +323,7 @@ bool Groom::mesh_pipeline(std::shared_ptr<Subject> subject, size_t domain) {
323323
auto transform = vtkSmartPointer<vtkTransform>::New();
324324

325325
if (!params.get_skip_grooming()) {
326-
run_mesh_pipeline(mesh, params);
326+
run_mesh_pipeline(mesh, params, original);
327327

328328
// reflection
329329
if (params.get_reflect()) {
@@ -367,28 +367,17 @@ bool Groom::mesh_pipeline(std::shared_ptr<Subject> subject, size_t domain) {
367367
}
368368

369369
//---------------------------------------------------------------------------
370-
bool Groom::run_mesh_pipeline(Mesh& mesh, GroomParameters params) {
371-
// Extract largest component (should be first)
372-
if (params.get_mesh_largest_component()) {
373-
mesh.extractLargestComponent();
374-
increment_progress();
375-
}
370+
bool Groom::run_mesh_pipeline(Mesh& mesh, GroomParameters params, const std::string& filename) {
371+
// Repair mesh: triangulate, extract largest component, clean, fix non-manifold, remove zero-area triangles
372+
mesh = Mesh(MeshUtils::repair_mesh(mesh.getVTKMesh()));
376373

377374
if (params.get_fill_mesh_holes_tool()) {
378375
mesh.fillHoles();
376+
check_and_fix_mesh(mesh, "fill_holes", filename);
379377
increment_progress();
380378
}
381379

382380
if (params.get_remesh()) {
383-
auto poly_data = mesh.getVTKMesh();
384-
if (poly_data->GetNumberOfCells() == 0 || poly_data->GetCell(0)->GetNumberOfPoints() == 2) {
385-
SW_DEBUG("Number of cells: {}", poly_data->GetNumberOfCells());
386-
SW_DEBUG("Number of points in first cell: {}", poly_data->GetCell(0)->GetNumberOfPoints());
387-
// write out to /tmp
388-
std::string tmpname = "/tmp/bad_mesh.vtk";
389-
MeshUtils::threadSafeWriteMesh(tmpname, mesh);
390-
throw std::runtime_error("malformed mesh, mesh should be triangular");
391-
}
392381
int total_vertices = mesh.getVTKMesh()->GetNumberOfPoints();
393382
int num_vertices = params.get_remesh_num_vertices();
394383
if (params.get_remesh_percent_mode()) {
@@ -397,6 +386,7 @@ bool Groom::run_mesh_pipeline(Mesh& mesh, GroomParameters params) {
397386
num_vertices = std::max<int>(num_vertices, 25);
398387
double gradation = clamp<double>(params.get_remesh_gradation(), 0.0, 2.0);
399388
mesh.remesh(num_vertices, gradation);
389+
check_and_fix_mesh(mesh, "remesh", filename);
400390
increment_progress();
401391
}
402392

@@ -406,6 +396,7 @@ bool Groom::run_mesh_pipeline(Mesh& mesh, GroomParameters params) {
406396
} else if (params.get_mesh_smoothing_method() == GroomParameters::GROOM_SMOOTH_VTK_WINDOWED_SINC_C) {
407397
mesh.smoothSinc(params.get_mesh_vtk_windowed_sinc_iterations(), params.get_mesh_vtk_windowed_sinc_passband());
408398
}
399+
check_and_fix_mesh(mesh, "smooth", filename);
409400
increment_progress();
410401
}
411402
return true;
@@ -511,7 +502,6 @@ int Groom::get_total_ops() {
511502
(project_->get_original_domain_types()[i] == DomainType::Image && params.get_convert_to_mesh());
512503

513504
if (run_mesh) {
514-
num_tools += params.get_mesh_largest_component() ? 1 : 0;
515505
num_tools += params.get_fill_mesh_holes_tool() ? 1 : 0;
516506
num_tools += params.get_mesh_smooth() ? 1 : 0;
517507
num_tools += params.get_remesh() ? 1 : 0;
@@ -1064,6 +1054,45 @@ void Groom::assign_transforms(std::vector<std::vector<double>> transforms, int d
10641054
}
10651055
}
10661056

1057+
//---------------------------------------------------------------------------
1058+
Mesh Groom::check_and_fix_mesh(Mesh& mesh, const std::string& step, const std::string& filename) {
1059+
auto issues = mesh.checkIntegrity();
1060+
if (!issues.empty()) {
1061+
SW_WARN("Mesh issues detected after step '{}', file '{}', issues: {}", step, filename, issues);
1062+
// try cleaning the mesh
1063+
SW_LOG("Cleaning mesh...");
1064+
mesh.clean();
1065+
issues = mesh.checkIntegrity();
1066+
if (!issues.empty()) {
1067+
SW_ERROR("Mesh issues remain after cleaning after step '{}', file '{}', issues: {}", step, filename, issues);
1068+
throw std::runtime_error(fmt::format("Error in mesh, step: {}, file: {}, issues: {}", step, filename, issues));
1069+
}
1070+
}
1071+
auto poly_data = mesh.getVTKMesh();
1072+
1073+
if (poly_data->GetNumberOfCells() == 0) {
1074+
throw std::runtime_error(fmt::format("Error in mesh, step: {}, file: {}, mesh has zero cells", step, filename));
1075+
}
1076+
if (poly_data->GetCell(0)->GetNumberOfPoints() == 2) {
1077+
// try cleaning the mesh
1078+
Mesh m(poly_data);
1079+
m.clean();
1080+
poly_data = m.getVTKMesh();
1081+
1082+
if (poly_data->GetNumberOfCells() == 0 || poly_data->GetCell(0)->GetNumberOfPoints() == 2) {
1083+
// still failed
1084+
SW_DEBUG("Number of cells: {}", poly_data->GetNumberOfCells());
1085+
SW_DEBUG("Number of points in first cell: {}", poly_data->GetCell(0)->GetNumberOfPoints());
1086+
// write out to /tmp
1087+
// std::string tmpname = "/tmp/bad_mesh.vtk";
1088+
// MeshUtils::threadSafeWriteMesh(tmpname, mesh);
1089+
throw std::runtime_error(
1090+
fmt::format("Error in mesh, step: {}, file: {}, mesh has invalid cells", step, filename));
1091+
}
1092+
}
1093+
return Mesh(poly_data);
1094+
}
1095+
10671096
//---------------------------------------------------------------------------
10681097
vtkSmartPointer<vtkMatrix4x4> Groom::compute_landmark_transform(vtkSmartPointer<vtkPoints> source,
10691098
vtkSmartPointer<vtkPoints> target) {

Libs/Groom/Groom.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class Groom {
5555
//! Run the mesh based pipeline on a single subject
5656
bool mesh_pipeline(std::shared_ptr<Subject> subject, size_t domain);
5757

58-
bool run_mesh_pipeline(Mesh& mesh, GroomParameters params);
58+
bool run_mesh_pipeline(Mesh& mesh, GroomParameters params, const std::string& filename);
5959

6060
//! Run the contour based pipeline on a single subject
6161
bool contour_pipeline(std::shared_ptr<Subject> subject, size_t domain);
@@ -73,6 +73,8 @@ class Groom {
7373

7474
void assign_transforms(std::vector<std::vector<double>> transforms, int domain, bool global = false);
7575

76+
Mesh check_and_fix_mesh(Mesh& mesh, const std::string& step, const std::string& filename);
77+
7678
static std::vector<std::vector<double>> get_icp_transforms(const std::vector<Mesh> meshes, Mesh reference);
7779
static std::vector<std::vector<double>> get_landmark_transforms(
7880
const std::vector<vtkSmartPointer<vtkPoints>> landmarks, size_t reference);

Libs/Groom/GroomParameters.cpp

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ const std::string ISO_SPACING = "iso_spacing";
2323
const std::string SPACING = "spacing";
2424
const std::string CONVERT_MESH = "convert_to_mesh";
2525
const std::string FILL_MESH_HOLES = "fill_mesh_holes";
26-
const std::string MESH_LARGEST_COMPONENT = "mesh_largest_component";
2726
const std::string FILL_HOLES = "fill_holes";
2827
const std::string ISOLATE = "isolate";
2928
const std::string PAD = "pad";
@@ -78,7 +77,6 @@ const std::vector<double> spacing{0, 0, 0};
7877
const bool convert_mesh = false;
7978
const bool fill_holes = true;
8079
const bool fill_holes_mesh = false;
81-
const bool mesh_largest_component = true;
8280
const bool isolate = true;
8381
const bool pad = true;
8482
const int pad_value = 10;
@@ -158,7 +156,6 @@ GroomParameters::GroomParameters(ProjectHandle project, std::string domain_name)
158156
Keys::SPACING,
159157
Keys::CONVERT_MESH,
160158
Keys::FILL_MESH_HOLES,
161-
Keys::MESH_LARGEST_COMPONENT,
162159
Keys::FILL_HOLES,
163160
Keys::ISOLATE,
164161
Keys::PAD,
@@ -196,12 +193,20 @@ GroomParameters::GroomParameters(ProjectHandle project, std::string domain_name)
196193
Keys::SHARED_BOUNDARIES,
197194
};
198195

196+
// Deprecated parameters that should be silently removed
197+
std::vector<std::string> deprecated_params = {
198+
"mesh_largest_component", // Now handled by repair_mesh at start of pipeline
199+
};
200+
199201
std::vector<std::string> to_remove;
200202

201203
// check if params_ has any unknown keys
202204
for (auto& param : params_.get_map()) {
203205
if (std::find(all_params.begin(), all_params.end(), param.first) == all_params.end()) {
204-
SW_WARN("Unknown Grooming parameter: " + param.first);
206+
// Only warn if not a deprecated parameter
207+
if (std::find(deprecated_params.begin(), deprecated_params.end(), param.first) == deprecated_params.end()) {
208+
SW_WARN("Unknown Grooming parameter: " + param.first);
209+
}
205210
to_remove.push_back(param.first);
206211
}
207212
}
@@ -236,14 +241,6 @@ bool GroomParameters::get_fill_mesh_holes_tool() {
236241
//---------------------------------------------------------------------------
237242
void GroomParameters::set_fill_mesh_holes_tool(bool value) { params_.set(Keys::FILL_MESH_HOLES, value); }
238243

239-
//---------------------------------------------------------------------------
240-
bool GroomParameters::get_mesh_largest_component() {
241-
return params_.get(Keys::MESH_LARGEST_COMPONENT, Defaults::mesh_largest_component);
242-
}
243-
244-
//---------------------------------------------------------------------------
245-
void GroomParameters::set_mesh_largest_component(bool value) { params_.set(Keys::MESH_LARGEST_COMPONENT, value); }
246-
247244
//---------------------------------------------------------------------------
248245
bool GroomParameters::get_auto_pad_tool() { return params_.get(Keys::PAD, Defaults::pad); }
249246

Libs/Groom/GroomParameters.h

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,6 @@ class GroomParameters {
6161
bool get_fill_mesh_holes_tool();
6262
void set_fill_mesh_holes_tool(bool value);
6363

64-
bool get_mesh_largest_component();
65-
void set_mesh_largest_component(bool value);
66-
6764
bool get_auto_pad_tool();
6865
void set_auto_pad_tool(bool value);
6966

Libs/Mesh/Mesh.cpp

Lines changed: 138 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,9 @@ Mesh& Mesh::remesh(int numVertices, double adaptivity) {
341341
// Restore std::cout
342342
// std::cout.clear();
343343

344+
clean();
345+
checkIntegrity();
346+
344347
this->poly_data_ = remesh->GetOutput();
345348

346349
// must regenerate normals after smoothing
@@ -415,14 +418,12 @@ Mesh& Mesh::fillHoles(double hole_size) {
415418
filter->Update();
416419
this->poly_data_ = filter->GetOutput();
417420

418-
auto origNormal = poly_data_->GetPointData()->GetNormals();
421+
// vtkFillHolesFilter can create zero-area triangles, remove them
422+
this->poly_data_ = MeshUtils::remove_zero_area_triangles(this->poly_data_);
419423

420-
// Make the triangle window order consistent
424+
// Make the triangle window order consistent and recompute normals
421425
computeNormals();
422426

423-
// Restore the original normals
424-
poly_data_->GetPointData()->SetNormals(origNormal);
425-
426427
this->invalidateLocators();
427428
return *this;
428429
}
@@ -728,6 +729,11 @@ Mesh& Mesh::clipClosedSurface(const Plane plane) {
728729
}
729730

730731
Mesh& Mesh::computeNormals() {
732+
// Validate mesh first
733+
if (!poly_data_ || poly_data_->GetNumberOfPoints() == 0 || poly_data_->GetNumberOfCells() == 0) {
734+
return *this;
735+
}
736+
731737
// Remove existing normals first
732738
poly_data_->GetPointData()->SetNormals(nullptr);
733739
poly_data_->GetCellData()->SetNormals(nullptr);
@@ -1758,6 +1764,133 @@ void Mesh::interpolate_scalars_to_mesh(std::string name, Eigen::VectorXd positio
17581764
setField(name, scalars, Mesh::FieldType::Point);
17591765
}
17601766

1767+
std::string Mesh::checkIntegrity() const {
1768+
std::ostringstream issues;
1769+
1770+
if (!poly_data_) {
1771+
return "poly_data_ is null";
1772+
}
1773+
1774+
const vtkIdType numPoints = poly_data_->GetNumberOfPoints();
1775+
const vtkIdType numCells = poly_data_->GetNumberOfCells();
1776+
1777+
if (numPoints == 0) {
1778+
issues << "Mesh has no points\n";
1779+
}
1780+
if (numCells == 0) {
1781+
issues << "Mesh has no cells\n";
1782+
}
1783+
if (numPoints == 0 || numCells == 0) {
1784+
return issues.str();
1785+
}
1786+
1787+
// Check for NaN/Inf coordinates
1788+
vtkPoints* points = poly_data_->GetPoints();
1789+
vtkIdType nanCount = 0;
1790+
vtkIdType infCount = 0;
1791+
for (vtkIdType i = 0; i < numPoints; i++) {
1792+
double p[3];
1793+
points->GetPoint(i, p);
1794+
for (int j = 0; j < 3; j++) {
1795+
if (std::isnan(p[j])) nanCount++;
1796+
if (std::isinf(p[j])) infCount++;
1797+
}
1798+
}
1799+
if (nanCount > 0) {
1800+
issues << "Found " << nanCount << " NaN coordinates\n";
1801+
}
1802+
if (infCount > 0) {
1803+
issues << "Found " << infCount << " Inf coordinates\n";
1804+
}
1805+
1806+
// Check cell validity
1807+
vtkCellArray* polys = poly_data_->GetPolys();
1808+
vtkCellArray* strips = poly_data_->GetStrips();
1809+
vtkCellArray* lines = poly_data_->GetLines();
1810+
vtkCellArray* verts = poly_data_->GetVerts();
1811+
1812+
vtkIdType degenerateCells = 0;
1813+
vtkIdType invalidIndexCells = 0;
1814+
vtkIdType duplicatePointCells = 0;
1815+
vtkIdType zerAreaCells = 0;
1816+
1817+
for (vtkIdType cellId = 0; cellId < numCells; cellId++) {
1818+
vtkCell* cell = poly_data_->GetCell(cellId);
1819+
if (!cell) {
1820+
issues << "Null cell at index " << cellId << "\n";
1821+
continue;
1822+
}
1823+
1824+
vtkIdType npts = cell->GetNumberOfPoints();
1825+
1826+
// Check for degenerate cells (less than 3 points for a polygon)
1827+
if (cell->GetCellDimension() == 2 && npts < 3) {
1828+
degenerateCells++;
1829+
continue;
1830+
}
1831+
1832+
// Check for invalid point indices and duplicate points within cell
1833+
std::set<vtkIdType> pointsInCell;
1834+
bool hasInvalidIndex = false;
1835+
bool hasDuplicate = false;
1836+
1837+
for (vtkIdType i = 0; i < npts; i++) {
1838+
vtkIdType ptId = cell->GetPointId(i);
1839+
if (ptId < 0 || ptId >= numPoints) {
1840+
hasInvalidIndex = true;
1841+
}
1842+
if (pointsInCell.count(ptId) > 0) {
1843+
hasDuplicate = true;
1844+
}
1845+
pointsInCell.insert(ptId);
1846+
}
1847+
1848+
if (hasInvalidIndex) invalidIndexCells++;
1849+
if (hasDuplicate) duplicatePointCells++;
1850+
1851+
// Check for zero-area triangles
1852+
if (npts == 3 && !hasInvalidIndex) {
1853+
double p0[3], p1[3], p2[3];
1854+
points->GetPoint(cell->GetPointId(0), p0);
1855+
points->GetPoint(cell->GetPointId(1), p1);
1856+
points->GetPoint(cell->GetPointId(2), p2);
1857+
double area = vtkTriangle::TriangleArea(p0, p1, p2);
1858+
if (area < 1e-20) {
1859+
zerAreaCells++;
1860+
}
1861+
}
1862+
}
1863+
1864+
if (degenerateCells > 0) {
1865+
issues << "Found " << degenerateCells << " degenerate cells (< 3 points)\n";
1866+
}
1867+
if (invalidIndexCells > 0) {
1868+
issues << "Found " << invalidIndexCells << " cells with invalid point indices\n";
1869+
}
1870+
if (duplicatePointCells > 0) {
1871+
issues << "Found " << duplicatePointCells << " cells with duplicate point references\n";
1872+
}
1873+
if (zerAreaCells > 0) {
1874+
issues << "Found " << zerAreaCells << " zero-area triangles\n";
1875+
}
1876+
1877+
// Check for non-manifold edges (optional, can be slow)
1878+
auto featureEdges = vtkSmartPointer<vtkFeatureEdges>::New();
1879+
featureEdges->SetInputData(poly_data_);
1880+
featureEdges->BoundaryEdgesOff();
1881+
featureEdges->FeatureEdgesOff();
1882+
featureEdges->ManifoldEdgesOff();
1883+
featureEdges->NonManifoldEdgesOn();
1884+
featureEdges->Update();
1885+
1886+
vtkIdType nonManifoldEdges = featureEdges->GetOutput()->GetNumberOfCells();
1887+
if (nonManifoldEdges > 0) {
1888+
issues << "Found " << nonManifoldEdges << " non-manifold edges\n";
1889+
}
1890+
1891+
return issues.str();
1892+
}
1893+
17611894
double Mesh::getFFCValue(Eigen::Vector3d query) const {
17621895
Point3 point(query.data());
17631896
return interpolateFieldAtPoint("value", point);

Libs/Mesh/Mesh.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ class Mesh {
302302
//! given name
303303
void interpolate_scalars_to_mesh(std::string name, Eigen::VectorXd positions, Eigen::VectorXd scalar_values);
304304

305+
std::string checkIntegrity() const;
306+
305307
private:
306308
friend struct SharedCommandData;
307309
Mesh()

0 commit comments

Comments
 (0)