Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Applications/shapeworks/Commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ COMMAND_DECLARE(FieldMean, MeshCommand);
COMMAND_DECLARE(FieldStd, MeshCommand);
COMMAND_DECLARE(FieldNames, MeshCommand);
COMMAND_DECLARE(FixElement, MeshCommand);
COMMAND_DECLARE(MeshLargestComponent, MeshCommand);
COMMAND_DECLARE(ClipClosedSurface, MeshCommand);
COMMAND_DECLARE(ClosestPoint, MeshCommand);
COMMAND_DECLARE(GeodesicDistance, MeshCommand);
Expand Down
21 changes: 21 additions & 0 deletions Applications/shapeworks/MeshCommands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,27 @@ bool FixElement::execute(const optparse::Values &options, SharedCommandData &sha
return sharedData.validMesh();
}

///////////////////////////////////////////////////////////////////////////////
// MeshLargestComponent
///////////////////////////////////////////////////////////////////////////////
void MeshLargestComponent::buildParser() {
const std::string prog = "mesh-largest-component";
const std::string desc = "extract the largest connected component from the mesh";
parser.prog(prog).description(desc);

Command::buildParser();
}

bool MeshLargestComponent::execute(const optparse::Values &options, SharedCommandData &sharedData) {
if (!sharedData.validMesh()) {
std::cerr << "No mesh to operate on\n";
return false;
}

sharedData.mesh->extractLargestComponent();
return sharedData.validMesh();
}

///////////////////////////////////////////////////////////////////////////////
// ClipClosedSurface
///////////////////////////////////////////////////////////////////////////////
Expand Down
1 change: 1 addition & 0 deletions Applications/shapeworks/shapeworks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ int main(int argc, char *argv[])
shapeworks.addCommand(MeshBounds::getCommand());
shapeworks.addCommand(Distance::getCommand());
shapeworks.addCommand(FixElement::getCommand());
shapeworks.addCommand(MeshLargestComponent::getCommand());
shapeworks.addCommand(ClipClosedSurface::getCommand());
shapeworks.addCommand(ComputeNormals::getCommand());
shapeworks.addCommand(ClosestPoint::getCommand());
Expand Down
7 changes: 7 additions & 0 deletions Libs/Groom/Groom.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,12 @@ bool Groom::mesh_pipeline(std::shared_ptr<Subject> subject, size_t domain) {

//---------------------------------------------------------------------------
bool Groom::run_mesh_pipeline(Mesh& mesh, GroomParameters params) {
// Extract largest component (should be first)
if (params.get_mesh_largest_component()) {
mesh.extractLargestComponent();
increment_progress();
}

if (params.get_fill_mesh_holes_tool()) {
mesh.fillHoles();
increment_progress();
Expand Down Expand Up @@ -505,6 +511,7 @@ int Groom::get_total_ops() {
(project_->get_original_domain_types()[i] == DomainType::Image && params.get_convert_to_mesh());

if (run_mesh) {
num_tools += params.get_mesh_largest_component() ? 1 : 0;
num_tools += params.get_fill_mesh_holes_tool() ? 1 : 0;
num_tools += params.get_mesh_smooth() ? 1 : 0;
num_tools += params.get_remesh() ? 1 : 0;
Expand Down
11 changes: 11 additions & 0 deletions Libs/Groom/GroomParameters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const std::string ISO_SPACING = "iso_spacing";
const std::string SPACING = "spacing";
const std::string CONVERT_MESH = "convert_to_mesh";
const std::string FILL_MESH_HOLES = "fill_mesh_holes";
const std::string MESH_LARGEST_COMPONENT = "mesh_largest_component";
const std::string FILL_HOLES = "fill_holes";
const std::string ISOLATE = "isolate";
const std::string PAD = "pad";
Expand Down Expand Up @@ -77,6 +78,7 @@ const std::vector<double> spacing{0, 0, 0};
const bool convert_mesh = false;
const bool fill_holes = true;
const bool fill_holes_mesh = false;
const bool mesh_largest_component = true;
const bool isolate = true;
const bool pad = true;
const int pad_value = 10;
Expand Down Expand Up @@ -156,6 +158,7 @@ GroomParameters::GroomParameters(ProjectHandle project, std::string domain_name)
Keys::SPACING,
Keys::CONVERT_MESH,
Keys::FILL_MESH_HOLES,
Keys::MESH_LARGEST_COMPONENT,
Keys::FILL_HOLES,
Keys::ISOLATE,
Keys::PAD,
Expand Down Expand Up @@ -233,6 +236,14 @@ bool GroomParameters::get_fill_mesh_holes_tool() {
//---------------------------------------------------------------------------
void GroomParameters::set_fill_mesh_holes_tool(bool value) { params_.set(Keys::FILL_MESH_HOLES, value); }

//---------------------------------------------------------------------------
bool GroomParameters::get_mesh_largest_component() {
return params_.get(Keys::MESH_LARGEST_COMPONENT, Defaults::mesh_largest_component);
}

//---------------------------------------------------------------------------
void GroomParameters::set_mesh_largest_component(bool value) { params_.set(Keys::MESH_LARGEST_COMPONENT, value); }

//---------------------------------------------------------------------------
bool GroomParameters::get_auto_pad_tool() { return params_.get(Keys::PAD, Defaults::pad); }

Expand Down
3 changes: 3 additions & 0 deletions Libs/Groom/GroomParameters.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class GroomParameters {
bool get_fill_mesh_holes_tool();
void set_fill_mesh_holes_tool(bool value);

bool get_mesh_largest_component();
void set_mesh_largest_component(bool value);

bool get_auto_pad_tool();
void set_auto_pad_tool(bool value);

Expand Down
16 changes: 16 additions & 0 deletions Libs/Mesh/Mesh.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,22 @@ Mesh& Mesh::fixNonManifold() {
return *this;
}

Mesh& Mesh::extractLargestComponent() {
auto connectivityFilter = vtkSmartPointer<vtkPolyDataConnectivityFilter>::New();
connectivityFilter->SetExtractionModeToLargestRegion();
connectivityFilter->SetInputData(poly_data_);
connectivityFilter->Update();

// Clean to remove unused points from other components
auto cleanFilter = vtkSmartPointer<vtkCleanPolyData>::New();
cleanFilter->SetInputData(connectivityFilter->GetOutput());
cleanFilter->Update();
poly_data_ = cleanFilter->GetOutput();

this->invalidateLocators();
return *this;
}

bool Mesh::detectNonManifold() {
auto features = vtkSmartPointer<vtkFeatureEdges>::New();
features->SetInputData(this->poly_data_);
Expand Down
3 changes: 3 additions & 0 deletions Libs/Mesh/Mesh.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ class Mesh {
/// Attempt to fix non-manifold edges
Mesh& fixNonManifold();

/// Extract the largest connected component from the mesh
Mesh& extractLargestComponent();

/// Detect if mesh contain non-manifold edges
bool detectNonManifold();

Expand Down
2 changes: 2 additions & 0 deletions Libs/Python/ShapeworksPython.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -935,6 +935,8 @@ PYBIND11_MODULE(shapeworks_py, m) {

.def("fixElement", &Mesh::fixElement, "fix element winding of mesh")

.def("extractLargestComponent", &Mesh::extractLargestComponent, "extract the largest connected component from the mesh")

.def(
"distance",
[](Mesh& mesh, const Mesh& target, const Mesh::DistanceMethod method) -> decltype(auto) {
Expand Down
2 changes: 2 additions & 0 deletions Studio/Groom/GroomTool.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,7 @@ void GroomTool::set_ui_from_params(GroomParameters params) {
ui_->blur_checkbox->setChecked(params.get_blur_tool());
ui_->isolate_checkbox->setChecked(params.get_isolate_tool());
ui_->fill_holes_checkbox->setChecked(params.get_fill_holes_tool());
ui_->mesh_largest_component->setChecked(params.get_mesh_largest_component());
ui_->mesh_fill_holes->setChecked(params.get_fill_mesh_holes_tool());
ui_->antialias_iterations->setValue(params.get_antialias_iterations());
ui_->blur_sigma->setValue(params.get_blur_amount());
Expand Down Expand Up @@ -586,6 +587,7 @@ void GroomTool::store_params() {
params.set_blur_amount(ui_->blur_sigma->value());
params.set_isolate_tool(ui_->isolate_checkbox->isChecked());
params.set_fill_holes_tool(ui_->fill_holes_checkbox->isChecked());
params.set_mesh_largest_component(ui_->mesh_largest_component->isChecked());
params.set_fill_mesh_holes_tool(ui_->mesh_fill_holes->isChecked());
params.set_antialias_iterations(ui_->antialias_iterations->value());
params.set_groom_output_prefix(preferences_.get_groom_file_template().toStdString());
Expand Down
44 changes: 44 additions & 0 deletions Studio/Groom/GroomTool.ui
Original file line number Diff line number Diff line change
Expand Up @@ -1397,6 +1397,50 @@ QWidget#domain_panel {
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="mesh_largest_component_widget" native="true">
<property name="minimumSize">
<size>
<width>0</width>
<height>30</height>
</size>
</property>
<layout class="QGridLayout" name="gridLayout_22">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>6</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>6</number>
</property>
<property name="spacing">
<number>0</number>
</property>
<item row="0" column="0">
<widget class="QCheckBox" name="mesh_largest_component">
<property name="text">
<string>Extract Largest Component</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="Line" name="line_11">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="fill_holes_mesh_widget" native="true">
<property name="minimumSize">
Expand Down
33 changes: 33 additions & 0 deletions Testing/MeshTests/MeshTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -844,3 +844,36 @@ TEST(MeshTests, interpolateFieldAtPoint) {
ASSERT_DOUBLE_EQ(mesh.interpolateFieldAtPoint(fieldName, Point3({0, 0, 50})), 0);
}

TEST(MeshTests, extractLargestComponentTest) {
// Test extractLargestComponent on two_spheres.vtk which has two disconnected spheres
// Created with VTK: large sphere (82 points) + small sphere (12 points) = 94 points total
Mesh multi_component(std::string(TEST_DATA_DIR) + "/two_spheres.vtk");

// Verify initial state: should have both spheres
ASSERT_EQ(multi_component.numPoints(), 94);
ASSERT_EQ(multi_component.numFaces(), 180);

// Extract the largest component (should keep the larger sphere with 82 points)
multi_component.extractLargestComponent();

// Compare with baseline
Mesh baseline(std::string(TEST_DATA_DIR) + "/two_spheres_largest.vtk");
ASSERT_TRUE(multi_component == baseline);
}

TEST(MeshTests, extractLargestComponentSingleComponentTest) {
// Test that extracting from a mesh with only one component preserves count
Mesh femur(std::string(TEST_DATA_DIR) + "/femur.vtk");

int original_points = femur.numPoints();
int original_faces = femur.numFaces();

// Extract largest component from a mesh that's already a single component
femur.extractLargestComponent();

// Should have the same number of points and faces
// (even though VTK may reorder vertices internally)
ASSERT_EQ(femur.numPoints(), original_points);
ASSERT_EQ(femur.numFaces(), original_faces);
}

3 changes: 3 additions & 0 deletions Testing/data/two_spheres.vtk
Git LFS file not shown
3 changes: 3 additions & 0 deletions Testing/data/two_spheres_largest.vtk
Git LFS file not shown
3 changes: 3 additions & 0 deletions Testing/shapeworksTests/meshlargestcomponent.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#! /bin/bash

shapeworks readmesh --name $DATA/two_spheres.vtk mesh-largest-component comparemesh --name $DATA/two_spheres_largest.vtk
2 changes: 2 additions & 0 deletions Testing/shapeworksTests/shapeworksTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,3 +154,5 @@ TEST(shapeworksTests, isolateTest) { run_test("isolate.sh"); }
TEST(shapeworksTests, analyzeTest) { run_sandboxed_test("analyze"); }

TEST(shapeworksTests, thicknessTest) { run_test("thickness.sh"); }

TEST(shapeworksTests, meshlargestcomponentTest) { run_test("meshlargestcomponent.sh"); }
Loading