diff --git a/Libs/Particles/CorrespondenceEvaluation.cpp b/Libs/Particles/CorrespondenceEvaluation.cpp index 2d71bc3e82..a3b3cafa8d 100644 --- a/Libs/Particles/CorrespondenceEvaluation.cpp +++ b/Libs/Particles/CorrespondenceEvaluation.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include @@ -134,6 +136,41 @@ CorrespondenceDistanceStats weighted_stats(const std::vector& values, st } // namespace +//--------------------------------------------------------------------------- +std::vector CorrespondenceEvaluation::kept_by_constraints(vtkPolyData* poly_data, Constraints* constraints) { + std::vector kept(poly_data->GetNumberOfPoints(), true); + if (!constraints || !constraints->hasConstraints()) { + return kept; + } + + for (auto& plane : constraints->getPlaneConstraints()) { + for (vtkIdType v = 0; v < poly_data->GetNumberOfPoints(); v++) { + double p[3]; + poly_data->GetPoint(v, p); + if (kept[v] && plane.isViolated(Eigen::Vector3d(p[0], p[1], p[2]))) { + kept[v] = false; + } + } + } + + auto& ffc = constraints->getFreeformConstraint(); + if (ffc.isSet()) { + // paint a shallow copy, so the caller's mesh does not pick up an ffc_paint array + auto copy = vtkSmartPointer::New(); + copy->ShallowCopy(poly_data); + ffc.applyToPolyData(copy); + auto paint = copy->GetPointData()->GetArray("ffc_paint"); + if (paint) { + for (vtkIdType v = 0; v < copy->GetNumberOfPoints(); v++) { + if (paint->GetTuple1(v) != 1.0) { + kept[v] = false; + } + } + } + } + return kept; +} + //--------------------------------------------------------------------------- CorrespondenceQualityStats CorrespondenceEvaluation::summarize(std::vector values) { CorrespondenceQualityStats s; @@ -151,7 +188,7 @@ CorrespondenceQualityStats CorrespondenceEvaluation::summarize(std::vector reconstructed, const Mesh& groomed, DistanceMethod method, vtkSmartPointer* out_distance, vtkSmartPointer* out_disagreement, - vtkSmartPointer* out_push) { + vtkSmartPointer* out_push, Constraints* constraints) { CorrespondenceQualityRow row; if (!reconstructed || reconstructed->GetNumberOfPoints() == 0) { return row; @@ -172,30 +209,63 @@ CorrespondenceQualityRow CorrespondenceEvaluation::evaluate_reconstruction( return row; } - std::vector values(n); + // what the constraints cut away was never given particles, so it is no part of the shape being + // scored; those vertices read as zero and are left out of every statistic + auto groomed_poly_data = groomed.getVTKMesh(); + const vtkIdType num_groomed = groomed_poly_data->GetNumberOfPoints(); + const auto recon_kept = kept_by_constraints(reconstructed, constraints); + const auto groomed_kept = kept_by_constraints(groomed_poly_data, constraints); + + std::vector values; + values.reserve(n); double sum = 0.0; double maxv = 0.0; for (int k = 0; k < n; ++k) { + if (!recon_kept[k]) { + field->SetTuple1(k, 0.0); + continue; + } const double v = std::fabs(field->GetTuple1(k)); - values[k] = v; + values.push_back(v); sum += v; if (v > maxv) maxv = v; } - const size_t mid = values.size() / 2; - std::nth_element(values.begin(), values.begin() + mid, values.end()); - const double median = values[mid]; + if (!values.empty()) { + const size_t mid = values.size() / 2; + std::nth_element(values.begin(), values.begin() + mid, values.end()); + const double median = values[mid]; - const size_t p99_idx = std::min(values.size() - 1, static_cast(0.99 * values.size())); - std::nth_element(values.begin(), values.begin() + p99_idx, values.end()); + const size_t p99_idx = std::min(values.size() - 1, static_cast(0.99 * values.size())); + std::nth_element(values.begin(), values.begin() + p99_idx, values.end()); - row.mean_dist = sum / n; - row.median_dist = median; - row.p99_dist = values[p99_idx]; - row.max_dist = maxv; + row.mean_dist = sum / values.size(); + row.median_dist = median; + row.p99_dist = values[p99_idx]; + row.max_dist = maxv; + } - const auto bbox = groomed.boundingBox(); - row.bbox_diag = (bbox.max - bbox.min).GetNorm(); + // the bounding box of the part of the groomed surface being scored + double bbox_min[3] = {std::numeric_limits::max(), std::numeric_limits::max(), + std::numeric_limits::max()}; + double bbox_max[3] = {std::numeric_limits::lowest(), std::numeric_limits::lowest(), + std::numeric_limits::lowest()}; + bool any_groomed_kept = false; + for (vtkIdType v = 0; v < num_groomed; v++) { + if (!groomed_kept[v]) { + continue; + } + any_groomed_kept = true; + double p[3]; + groomed_poly_data->GetPoint(v, p); + for (int i = 0; i < 3; i++) { + bbox_min[i] = std::min(bbox_min[i], p[i]); + bbox_max[i] = std::max(bbox_max[i], p[i]); + } + } + if (any_groomed_kept) { + row.bbox_diag = std::sqrt(vtkMath::Distance2BetweenPoints(bbox_min, bbox_max)); + } if (row.bbox_diag > 0.0) { row.norm_mean = row.mean_dist / row.bbox_diag; row.norm_median = row.median_dist / row.bbox_diag; @@ -212,21 +282,25 @@ CorrespondenceQualityRow CorrespondenceEvaluation::evaluate_reconstruction( // groomed cell that reconstructed vertex is nearest (or onto that vertex, point-to-point). There is no // check that the two surfaces face the same way, because a fold in the reconstruction has reversed // normals and is exactly what the pull term is there to catch. - auto groomed_poly_data = groomed.getVTKMesh(); - const vtkIdType num_groomed = groomed_poly_data->GetNumberOfPoints(); std::vector push_values(num_groomed); std::vector disagreement_values(num_groomed); for (vtkIdType v = 0; v < num_groomed; v++) { + if (!groomed_kept[v]) { + push->SetTuple1(v, 0.0); + } push_values[v] = std::fabs(push->GetTuple1(v)); disagreement_values[v] = push_values[v]; } auto corners = vtkSmartPointer::New(); for (int k = 0; k < n; ++k) { + if (!recon_kept[k]) { + continue; + } const double pull = std::fabs(field->GetTuple1(k)); const auto target = static_cast(nearest->GetTuple1(k)); if (distance_method == Mesh::DistanceMethod::PointToPoint) { - if (target >= 0 && target < num_groomed) { + if (target >= 0 && target < num_groomed && groomed_kept[target]) { disagreement_values[target] = std::max(disagreement_values[target], pull); } continue; @@ -237,13 +311,26 @@ CorrespondenceQualityRow CorrespondenceEvaluation::evaluate_reconstruction( groomed_poly_data->GetCellPoints(target, corners); for (vtkIdType c = 0; c < corners->GetNumberOfIds(); c++) { const vtkIdType corner = corners->GetId(c); - disagreement_values[corner] = std::max(disagreement_values[corner], pull); + if (groomed_kept[corner]) { + disagreement_values[corner] = std::max(disagreement_values[corner], pull); + } } } - const auto areas = vertex_areas(groomed_poly_data); - row.push = weighted_stats(push_values, areas, row.bbox_diag); - row.disagreement = weighted_stats(disagreement_values, areas, row.bbox_diag); + // a vertex that was cut away stands for no area, so it carries no weight + auto areas = vertex_areas(groomed_poly_data); + std::vector kept_push; + std::vector kept_disagreement; + std::vector kept_areas; + for (vtkIdType v = 0; v < num_groomed; v++) { + if (groomed_kept[v]) { + kept_push.push_back(push_values[v]); + kept_disagreement.push_back(disagreement_values[v]); + kept_areas.push_back(areas[v]); + } + } + row.push = weighted_stats(kept_push, kept_areas, row.bbox_diag); + row.disagreement = weighted_stats(kept_disagreement, kept_areas, row.bbox_diag); if (out_distance) { field->SetName("distance"); @@ -321,14 +408,17 @@ CorrespondenceQualityReport CorrespondenceEvaluation::evaluate(ProjectHandle pro std::vector name_per_subject; std::vector> particles_per_subject_domain; std::vector> groomed_per_subject_domain; + std::vector> constraints_per_subject_domain; for (auto& subj : subjects) { auto particle_files = subj->get_local_particle_filenames(); auto groomed_files = subj->get_groomed_filenames(); + auto constraint_files = subj->get_constraints_filenames(); bool complete = true; std::vector subj_particles; std::vector subj_groomed; + std::vector subj_constraints(num_domains); subj_particles.reserve(num_domains); subj_groomed.reserve(num_domains); @@ -347,12 +437,23 @@ CorrespondenceQualityReport CorrespondenceEvaluation::evaluate(ProjectHandle pro break; } subj_groomed.push_back(groomed_files[d]); + if (d < static_cast(constraint_files.size()) && !constraint_files[d].empty()) { + try { + subj_constraints[d].read(constraint_files[d]); + } catch (const std::exception& e) { + SW_LOG("Skipping subject '{}' domain {}: constraints load failed: {}", subj->get_display_name(), d, + e.what()); + complete = false; + break; + } + } } if (!complete) continue; name_per_subject.push_back(subj->get_display_name()); particles_per_subject_domain.push_back(std::move(subj_particles)); groomed_per_subject_domain.push_back(std::move(subj_groomed)); + constraints_per_subject_domain.push_back(std::move(subj_constraints)); } const int num_subjects = static_cast(name_per_subject.size()); @@ -410,7 +511,10 @@ CorrespondenceQualityReport CorrespondenceEvaluation::evaluate(ProjectHandle pro for (int domain = 0; domain < num_domains; ++domain) { SW_LOG("=== Domain {} ===", domain); + // clipped the way Studio clips its warp reference, so the cut-away part, which has no particles to + // steer it, is not extrapolated into every reconstruction Mesh template_mesh = load_groomed_as_mesh(groomed_per_subject_domain[template_idx][domain]); + constraints_per_subject_domain[template_idx][domain].clipMesh(template_mesh); MeshWarper warper; warper.set_warp_method(WarpMethod::Biharmonic); warper.set_reference_mesh(template_mesh.getVTKMesh(), particles_per_subject_domain[template_idx][domain]); @@ -430,8 +534,9 @@ CorrespondenceQualityReport CorrespondenceEvaluation::evaluate(ProjectHandle pro vtkSmartPointer field; vtkSmartPointer disagreement; vtkSmartPointer push; - CorrespondenceQualityRow row = - evaluate_reconstruction(reconstructed, groomed_mesh, method, &field, &disagreement, &push); + CorrespondenceQualityRow row = evaluate_reconstruction(reconstructed, groomed_mesh, method, &field, + &disagreement, &push, + &constraints_per_subject_domain[i][domain]); if (!field) continue; row.subject = name_per_subject[i]; diff --git a/Libs/Particles/CorrespondenceEvaluation.h b/Libs/Particles/CorrespondenceEvaluation.h index a72352da1f..ac11871680 100644 --- a/Libs/Particles/CorrespondenceEvaluation.h +++ b/Libs/Particles/CorrespondenceEvaluation.h @@ -12,6 +12,7 @@ class vtkPolyData; namespace shapeworks { +class Constraints; class Mesh; class Project; using ProjectHandle = std::shared_ptr; @@ -100,6 +101,10 @@ struct CorrespondenceQualityReport { * only surface that can show a gap. Distances are also normalized by each * subject's bounding-box diagonal so the metric is scale-invariant. * + * Cutting planes and free-form constraints are honored: whatever they cut away + * from a subject is left out of every statistic, because the optimizer never + * placed particles there. + * * The template row itself is included in `rows` (with is_template=true) but * excluded from aggregate statistics — its reconstruction is near-identity * and would skew small cohorts. @@ -139,12 +144,22 @@ class CorrespondenceEvaluation { //! color by, since a gap in the reconstruction has no reconstructed surface //! to show it on. //! + //! If \p constraints is given, vertices of either surface that its cutting planes or free-form + //! constraint cut away are left out of the statistics, read as zero in the output fields, and the + //! bounding box diagonal is taken over the part of the groomed surface that remains. + //! //! Returns a default-constructed row if \p reconstructed is null or empty. static CorrespondenceQualityRow evaluate_reconstruction(vtkSmartPointer reconstructed, const Mesh& groomed, DistanceMethod method, vtkSmartPointer* out_distance = nullptr, vtkSmartPointer* out_disagreement = nullptr, - vtkSmartPointer* out_push = nullptr); + vtkSmartPointer* out_push = nullptr, + Constraints* constraints = nullptr); + + //! Which vertices of \p poly_data the constraints keep, matching Constraints::clipMesh: the positive + //! side of every cutting plane and the painted-in part of the free-form constraint. Every vertex is + //! kept when \p constraints is null or empty. + static std::vector kept_by_constraints(vtkPolyData* poly_data, Constraints* constraints); //! Summary statistics (mean/median/p95/max) over a set of values. static CorrespondenceQualityStats summarize(std::vector values); diff --git a/Studio/Job/CorrespondenceQualityJob.cpp b/Studio/Job/CorrespondenceQualityJob.cpp index 54b1f01431..07ea7c60b0 100644 --- a/Studio/Job/CorrespondenceQualityJob.cpp +++ b/Studio/Job/CorrespondenceQualityJob.cpp @@ -108,8 +108,10 @@ void CorrespondenceQualityJob::run() { Mesh groomed_mesh(groomed_poly_data); vtkSmartPointer distance; vtkSmartPointer disagreement; + // leave out whatever the sample's cutting planes and free-form constraint cut away + auto& constraints = shape->get_constraints(d); auto row = CorrespondenceEvaluation::evaluate_reconstruction(reconstructed_poly_data, groomed_mesh, method_, - &distance, &disagreement); + &distance, &disagreement, nullptr, &constraints); if (!distance || !disagreement) { per_particle.insert(per_particle.end(), particles.size(), 0.0); // keep later domains aligned continue; @@ -148,9 +150,13 @@ void CorrespondenceQualityJob::run() { locator->SetDataSet(particle_poly_data); locator->BuildLocator(); + const auto groomed_kept = CorrespondenceEvaluation::kept_by_constraints(groomed_poly_data, &constraints); std::vector sums(particles.size(), 0.0); std::vector counts(particles.size(), 0); for (vtkIdType v = 0; v < groomed_poly_data->GetNumberOfPoints(); v++) { + if (!groomed_kept[v]) { + continue; // cut away by the constraints, so no part of any particle's neighbourhood + } double vertex[3]; groomed_poly_data->GetPoint(v, vertex); vtkIdType id = locator->FindClosestPoint(vertex); diff --git a/Testing/ParticlesTests/ParticlesTests.cpp b/Testing/ParticlesTests/ParticlesTests.cpp index 8ab5d0bb4a..ec356244cb 100644 --- a/Testing/ParticlesTests/ParticlesTests.cpp +++ b/Testing/ParticlesTests/ParticlesTests.cpp @@ -4,11 +4,14 @@ #include #include "CorrespondenceEvaluation.h" +#include "Libs/Optimize/Constraints/Constraints.h" #include "Libs/Optimize/Domain/Surface.h" #include "Mesh/Mesh.h" #include "ParticleNormalEvaluation.h" #include "ParticleShapeStatistics.h" #include "ParticleSystemEvaluation.h" +#include "Project/Project.h" +#include "Project/Subject.h" #include "ReconstructSurface.h" #include "ShapeEvaluation.h" #include "Testing.h" @@ -535,3 +538,81 @@ TEST(CorrespondenceEvaluationTests, disagreementAggregatesExcludeTheTemplate) { ASSERT_NEAR(report.agg_disagreement_norm.max, 0.3, 1e-9); ASSERT_NEAR(report.agg_push_raw.mean, 1.0, 1e-9); } + +//--------------------------------------------------------------------------- +// What a cutting plane removes was never given particles, so a reconstruction that is wrong only there +// is not wrong at all (#2673). +TEST(CorrespondenceEvaluationTests, cutAwayRegionIsNotScored) { + const int n = 21; // puts a column of vertices at x = 0.5, on the plane + Mesh groomed = make_grid_mesh(n); + + // lift everything left of the plane well off the groomed surface + Mesh reconstructed = make_grid_mesh(n); + auto poly_data = reconstructed.getVTKMesh(); + for (vtkIdType v = 0; v < poly_data->GetNumberOfPoints(); v++) { + double p[3]; + poly_data->GetPoint(v, p); + if (p[0] < 0.5 - 1e-9) { + p[2] += 1.0; + poly_data->GetPoints()->SetPoint(v, p); + } + } + poly_data->Modified(); + + const auto method = CorrespondenceEvaluation::DistanceMethod::PointToCell; + auto unconstrained = CorrespondenceEvaluation::evaluate_reconstruction(poly_data, groomed, method); + ASSERT_GT(unconstrained.max_dist, 0.9); + ASSERT_GT(unconstrained.disagreement.mean, 0.1); + + // plane x = 0.5 with its normal along +x, so the right half is kept + Constraints constraints; + vnl_vector a(3), b(3), c(3); + a[0] = 0.5, a[1] = 0.0, a[2] = 0.0; + b[0] = 0.5, b[1] = 1.0, b[2] = 0.0; + c[0] = 0.5, c[1] = 0.0, c[2] = 1.0; + constraints.addPlane(a, b, c); + + vtkSmartPointer pull; + vtkSmartPointer disagreement; + vtkSmartPointer push; + auto row = CorrespondenceEvaluation::evaluate_reconstruction(poly_data, groomed, method, &pull, &disagreement, &push, + &constraints); + + ASSERT_NEAR(row.max_dist, 0.0, 1e-9); + ASSERT_NEAR(row.push.max, 0.0, 1e-9); + ASSERT_NEAR(row.disagreement.max, 0.0, 1e-9); + + // normalized by the kept half of the unit square + ASSERT_NEAR(row.bbox_diag, std::sqrt(1.25), 1e-6); + + // the fields still cover every vertex, reading zero where the plane cut + ASSERT_EQ(pull->GetNumberOfTuples(), poly_data->GetNumberOfPoints()); + ASSERT_EQ(disagreement->GetNumberOfTuples(), static_cast(groomed.numPoints())); + for (vtkIdType v = 0; v < pull->GetNumberOfTuples(); v++) { + ASSERT_NEAR(pull->GetTuple1(v), 0.0, 1e-9); + } + for (vtkIdType v = 0; v < disagreement->GetNumberOfTuples(); v++) { + ASSERT_NEAR(disagreement->GetTuple1(v), 0.0, 1e-9); + } +} + +//--------------------------------------------------------------------------- +// Five ellipsoids that agree above a z = 0 cutting plane and differ wildly below it, optimized with +// the plane. See Testing/data/correspondence_quality_constraints/make_dataset.py. +TEST(CorrespondenceEvaluationTests, projectCuttingPlanesAreHonored) { + TestUtils::Instance().prep_temp(std::string(TEST_DATA_DIR) + "/correspondence_quality_constraints", + "correspondence_quality_constraints"); + + auto project = std::make_shared(); + ASSERT_TRUE(project->load("project.swproj")); + auto report = CorrespondenceEvaluation::evaluate(project); + ASSERT_EQ(report.num_evaluated, 4); + ASSERT_LT(report.agg_disagreement_norm.max, 0.01); + + // without the planes the bottom halves are scored, and they disagree by several percent + for (auto& subject : project->get_subjects()) { + subject->set_constraints_filenames({}); + } + auto unconstrained = CorrespondenceEvaluation::evaluate(project); + ASSERT_GT(unconstrained.agg_disagreement_norm.mean, 0.03); +} diff --git a/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_0_constraints.json b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_0_constraints.json new file mode 100644 index 0000000000..f3a69190e9 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_0_constraints.json @@ -0,0 +1,23 @@ +{ + "planes": [ + { + "points": [ + [ + 10.0, + 10.0, + 0.0 + ], + [ + -10.0, + 10.0, + 0.0 + ], + [ + -10.0, + -10.0, + 0.0 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_1_constraints.json b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_1_constraints.json new file mode 100644 index 0000000000..f3a69190e9 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_1_constraints.json @@ -0,0 +1,23 @@ +{ + "planes": [ + { + "points": [ + [ + 10.0, + 10.0, + 0.0 + ], + [ + -10.0, + 10.0, + 0.0 + ], + [ + -10.0, + -10.0, + 0.0 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_2_constraints.json b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_2_constraints.json new file mode 100644 index 0000000000..f3a69190e9 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_2_constraints.json @@ -0,0 +1,23 @@ +{ + "planes": [ + { + "points": [ + [ + 10.0, + 10.0, + 0.0 + ], + [ + -10.0, + 10.0, + 0.0 + ], + [ + -10.0, + -10.0, + 0.0 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_3_constraints.json b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_3_constraints.json new file mode 100644 index 0000000000..f3a69190e9 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_3_constraints.json @@ -0,0 +1,23 @@ +{ + "planes": [ + { + "points": [ + [ + 10.0, + 10.0, + 0.0 + ], + [ + -10.0, + 10.0, + 0.0 + ], + [ + -10.0, + -10.0, + 0.0 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_4_constraints.json b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_4_constraints.json new file mode 100644 index 0000000000..f3a69190e9 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/constraints/ellipsoid_4_constraints.json @@ -0,0 +1,23 @@ +{ + "planes": [ + { + "points": [ + [ + 10.0, + 10.0, + 0.0 + ], + [ + -10.0, + 10.0, + 0.0 + ], + [ + -10.0, + -10.0, + 0.0 + ] + ] + } + ] +} \ No newline at end of file diff --git a/Testing/data/correspondence_quality_constraints/make_dataset.py b/Testing/data/correspondence_quality_constraints/make_dataset.py new file mode 100644 index 0000000000..bfc0791a95 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/make_dataset.py @@ -0,0 +1,113 @@ +"""Generate the correspondence-quality cutting-plane dataset (issue #2673). + +Five ellipsoids that vary smoothly above z = 0 and wildly below it. Each has a cutting plane at +z = 0 that keeps the top half, so the optimizer only places particles there. A correspondence +quality evaluation that ignores the plane scores the bottom halves too, which no particle describes. + +Run from this directory, then optimize: + python make_dataset.py + shapeworks optimize --name project.swproj +""" +import json +import os + +import numpy as np +import vtk + +# (rx, ry, rz) of the top half, then the bottom half's sideways shear (x, y) and vertical stretch +SUBJECTS = [ + ("ellipsoid_0", (10.0, 10.0, 10.0), (0.00, 0.00), 1.0), + ("ellipsoid_1", (11.0, 9.5, 10.5), (0.10, -0.05), 1.6), + ("ellipsoid_2", (9.5, 10.5, 9.0), (-0.08, 0.09), 0.6), + ("ellipsoid_3", (10.5, 10.0, 11.0), (0.04, 0.12), 2.0), + ("ellipsoid_4", (9.0, 9.0, 10.0), (-0.12, -0.10), 1.3), +] + +# three counter-clockwise points in z = 0, so the normal points to +z and the top half is kept +PLANE = [[10.0, 10.0, 0.0], [-10.0, 10.0, 0.0], [-10.0, -10.0, 0.0]] + +PROJECT_OPTIMIZE = { + "checkpointing_interval": "0", + "ending_regularization": "10.000000", + "initial_relative_weighting": "0.050000", + "iterations_per_split": "300", + "multiscale": "false", + "number_of_particles": "64", + "optimization_iterations": "300", + "procrustes": "false", + "relative_weighting": "1.000000", + "starting_regularization": "500.000000", + "use_geodesic_distance": "false", + "use_normals": "0", + "verbosity": "0", +} + + +def make_mesh(radii, shear, stretch): + sphere = vtk.vtkSphereSource() + sphere.SetRadius(1.0) + sphere.SetThetaResolution(32) + sphere.SetPhiResolution(32) + sphere.Update() + + # the source duplicates the seam vertices; merge them so the surface is closed + clean = vtk.vtkCleanPolyData() + clean.SetInputData(sphere.GetOutput()) + clean.Update() + + triangles = vtk.vtkTriangleFilter() + triangles.SetInputData(clean.GetOutput()) + triangles.Update() + poly = triangles.GetOutput() + + points = poly.GetPoints() + for i in range(points.GetNumberOfPoints()): + x, y, z = np.array(points.GetPoint(i)) * np.array(radii) + if z < 0: + # zero slope at z = 0, so the surface stays smooth across the plane + x += shear[0] * z * z + y += shear[1] * z * z + z *= stretch + points.SetPoint(i, x, y, z) + poly.Modified() + return poly + + +def write_mesh(poly, filename): + writer = vtk.vtkPolyDataWriter() + writer.SetFileName(filename) + writer.SetInputData(poly) + writer.Write() + + +def main(): + os.makedirs("meshes", exist_ok=True) + os.makedirs("constraints", exist_ok=True) + + data = [] + for name, radii, shear, stretch in SUBJECTS: + mesh_file = f"meshes/{name}.vtk" + constraint_file = f"constraints/{name}_constraints.json" + write_mesh(make_mesh(radii, shear, stretch), mesh_file) + with open(constraint_file, "w") as f: + json.dump({"planes": [{"points": PLANE}]}, f, indent=4) + data.append({ + "name": name, + "shape_1": mesh_file, + "groomed_1": mesh_file, + "constraints_1": constraint_file, + "local_particles_1": f"project_particles/{name}_local.particles", + "world_particles_1": f"project_particles/{name}_world.particles", + }) + + project = { + "data": data, + "optimize": PROJECT_OPTIMIZE, + "project": {"version": "2"}, + } + with open("project.swproj", "w") as f: + json.dump(project, f, indent=1) + + +if __name__ == "__main__": + main() diff --git a/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_0.vtk b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_0.vtk new file mode 100644 index 0000000000..da5381e9f6 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_0.vtk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:488c6335b9fdaa72f641cb3bf91aeed2909f44f09f2ad1d62eb6c9dec466ff1a +size 85316 diff --git a/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_1.vtk b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_1.vtk new file mode 100644 index 0000000000..90d95e6913 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_1.vtk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca98169fd7c875bccea75e57ec8a09edc672313b7a0993febbab7776c7500124 +size 85174 diff --git a/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_2.vtk b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_2.vtk new file mode 100644 index 0000000000..e7a90779fd --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_2.vtk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:32bcb421ecafe7eb08b13e3377cb40516ff4998ba81b4a178df5bb2eb5db9cf6 +size 85194 diff --git a/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_3.vtk b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_3.vtk new file mode 100644 index 0000000000..edabedf642 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_3.vtk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:699db273c1f659b4e93456127bd705e2f5d51372bfd7b8904de26180a94393e3 +size 84926 diff --git a/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_4.vtk b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_4.vtk new file mode 100644 index 0000000000..6488eb1c4a --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/meshes/ellipsoid_4.vtk @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9f52b6b06bddf0eb868277cb800af92ca40388b7dfd6da5c688503036e90c45f +size 85530 diff --git a/Testing/data/correspondence_quality_constraints/project.swproj b/Testing/data/correspondence_quality_constraints/project.swproj new file mode 100644 index 0000000000..6790349727 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project.swproj @@ -0,0 +1,79 @@ +{ + "data": [ + { + "name": "ellipsoid_0", + "notes": "", + "shape_1": "meshes/ellipsoid_0.vtk", + "constraints_1": "constraints/ellipsoid_0_constraints.json", + "groomed_1": "meshes/ellipsoid_0.vtk", + "local_particles_1": "project_particles/ellipsoid_0_local.particles", + "world_particles_1": "project_particles/ellipsoid_0_world.particles", + "procrustes_1": "1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000" + }, + { + "name": "ellipsoid_1", + "notes": "", + "shape_1": "meshes/ellipsoid_1.vtk", + "constraints_1": "constraints/ellipsoid_1_constraints.json", + "groomed_1": "meshes/ellipsoid_1.vtk", + "local_particles_1": "project_particles/ellipsoid_1_local.particles", + "world_particles_1": "project_particles/ellipsoid_1_world.particles", + "procrustes_1": "1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000" + }, + { + "name": "ellipsoid_2", + "notes": "", + "shape_1": "meshes/ellipsoid_2.vtk", + "constraints_1": "constraints/ellipsoid_2_constraints.json", + "groomed_1": "meshes/ellipsoid_2.vtk", + "local_particles_1": "project_particles/ellipsoid_2_local.particles", + "world_particles_1": "project_particles/ellipsoid_2_world.particles", + "procrustes_1": "1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000" + }, + { + "name": "ellipsoid_3", + "notes": "", + "shape_1": "meshes/ellipsoid_3.vtk", + "constraints_1": "constraints/ellipsoid_3_constraints.json", + "groomed_1": "meshes/ellipsoid_3.vtk", + "local_particles_1": "project_particles/ellipsoid_3_local.particles", + "world_particles_1": "project_particles/ellipsoid_3_world.particles", + "procrustes_1": "1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000" + }, + { + "name": "ellipsoid_4", + "notes": "", + "shape_1": "meshes/ellipsoid_4.vtk", + "constraints_1": "constraints/ellipsoid_4_constraints.json", + "groomed_1": "meshes/ellipsoid_4.vtk", + "local_particles_1": "project_particles/ellipsoid_4_local.particles", + "world_particles_1": "project_particles/ellipsoid_4_world.particles", + "procrustes_1": "1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000 0.000000 0.000000 0.000000 0.000000 1.000000" + } + ], + "groom": { + "1": {} + }, + "optimize": { + "checkpointing_interval": "0", + "ending_regularization": "10.000000", + "initial_relative_weighting": "0.050000", + "iterations_per_split": "300", + "multiscale": "false", + "number_of_particles": "64", + "optimization_iterations": "300", + "procrustes": "false", + "relative_weighting": "1.000000", + "starting_regularization": "500.000000", + "use_geodesic_distance": "false", + "use_normals": "0", + "verbosity": "0" + }, + "studio": {}, + "project": { + "version": "2" + }, + "analysis": {}, + "deepssm": {}, + "landmarks": [] +} \ No newline at end of file diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_0_local.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_0_local.particles new file mode 100644 index 0000000000..d37f4b4343 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_0_local.particles @@ -0,0 +1,64 @@ +-4.36225 8.93812 0 +-1.19416 1.55732 9.80175 +1.58259 7.95482 5.82942 +9.02207 -4.20521 0 +-9.12804 -4.00695 0 +3.06907 -7.39633 5.97662 +3.68737 8.61113 3.47243 +4.60841 -8.80655 0 +-8.75659 4.70188 0 +-3.58529 8.6563 3.4919 +7.40433 2.61478 6.1505 +8.60265 -3.56364 3.62669 +-7.24803 -0.000447495 6.88951 +-2.18907 -5.03138 8.3429 +9.84122 1.48176 0 +-4.45938 -8.88621 0 +-1.45528 9.84383 0 +-2.4893 -1.66204 9.54148 +-1.57149 7.60191 6.28368 +7.7559 -1.54249 6.12097 +-8.64153 -2.29181 4.40412 +5.84704 0.00070388 8.10358 +5.58503 5.58548 6.13152 +-1.47824 -9.84157 0 +-9.80695 1.82976 0 +-7.20886 2.98484 6.24371 +1.8835 0.689692 9.79541 +6.5694 -6.03299 4.45701 +-7.98369 -5.33625 2.75442 +1.17412 -5.77458 8.0664 +6.64274 6.64226 3.42069 +-5.23552 -7.8359 3.3248 +-6.92417 7.1751 0 +-4.49334 1.8276 8.74302 +-0.000705861 9.5335 2.97568 +9.82164 -1.68059 0 +-9.82484 -1.64812 0 +5.28899 -4.73192 7.00685 +4.7813 8.71414 0 +1.62102 -9.82751 0 +-6.66886 6.66853 3.30267 +-3.22792 4.83172 8.13168 +8.67991 3.59222 3.42002 +7.62992 -6.36996 0 +-5.25085 -2.17548 8.22568 +-4.33077 -6.5189 6.21355 +8.93777 4.36292 0 +-7.06201 -7.06196 0 +1.94787 9.79531 0 +0.776107 -2.69292 9.58743 +-0.00963698 4.61818 8.8582 +9.37752 -0.000312268 3.47294 +-8.66344 3.58944 3.47256 +3.96897 -2.67146 8.77642 +2.69709 5.66789 7.75273 +-1.83837 -9.23998 3.33404 +-9.44552 0.200767 3.19329 +-5.22079 5.9684 6.04022 +4.05988 2.71117 8.72538 +5.54071 -7.95799 2.34793 +-6.57458 -4.39389 6.12106 +1.82969 -9.19727 3.47303 +7.19602 6.89868 0 +-0.947605 -7.86859 6.04324 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_0_world.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_0_world.particles new file mode 100644 index 0000000000..d37f4b4343 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_0_world.particles @@ -0,0 +1,64 @@ +-4.36225 8.93812 0 +-1.19416 1.55732 9.80175 +1.58259 7.95482 5.82942 +9.02207 -4.20521 0 +-9.12804 -4.00695 0 +3.06907 -7.39633 5.97662 +3.68737 8.61113 3.47243 +4.60841 -8.80655 0 +-8.75659 4.70188 0 +-3.58529 8.6563 3.4919 +7.40433 2.61478 6.1505 +8.60265 -3.56364 3.62669 +-7.24803 -0.000447495 6.88951 +-2.18907 -5.03138 8.3429 +9.84122 1.48176 0 +-4.45938 -8.88621 0 +-1.45528 9.84383 0 +-2.4893 -1.66204 9.54148 +-1.57149 7.60191 6.28368 +7.7559 -1.54249 6.12097 +-8.64153 -2.29181 4.40412 +5.84704 0.00070388 8.10358 +5.58503 5.58548 6.13152 +-1.47824 -9.84157 0 +-9.80695 1.82976 0 +-7.20886 2.98484 6.24371 +1.8835 0.689692 9.79541 +6.5694 -6.03299 4.45701 +-7.98369 -5.33625 2.75442 +1.17412 -5.77458 8.0664 +6.64274 6.64226 3.42069 +-5.23552 -7.8359 3.3248 +-6.92417 7.1751 0 +-4.49334 1.8276 8.74302 +-0.000705861 9.5335 2.97568 +9.82164 -1.68059 0 +-9.82484 -1.64812 0 +5.28899 -4.73192 7.00685 +4.7813 8.71414 0 +1.62102 -9.82751 0 +-6.66886 6.66853 3.30267 +-3.22792 4.83172 8.13168 +8.67991 3.59222 3.42002 +7.62992 -6.36996 0 +-5.25085 -2.17548 8.22568 +-4.33077 -6.5189 6.21355 +8.93777 4.36292 0 +-7.06201 -7.06196 0 +1.94787 9.79531 0 +0.776107 -2.69292 9.58743 +-0.00963698 4.61818 8.8582 +9.37752 -0.000312268 3.47294 +-8.66344 3.58944 3.47256 +3.96897 -2.67146 8.77642 +2.69709 5.66789 7.75273 +-1.83837 -9.23998 3.33404 +-9.44552 0.200767 3.19329 +-5.22079 5.9684 6.04022 +4.05988 2.71117 8.72538 +5.54071 -7.95799 2.34793 +-6.57458 -4.39389 6.12106 +1.82969 -9.19727 3.47303 +7.19602 6.89868 0 +-0.947605 -7.86859 6.04324 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_1_local.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_1_local.particles new file mode 100644 index 0000000000..da8645c8da --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_1_local.particles @@ -0,0 +1,64 @@ +-4.35785 8.68416 -3.67664e-17 +-1.23047 1.58964 10.2851 +1.75264 7.76796 5.7845 +9.67338 -4.42333 -3.67664e-17 +-9.71507 -4.3208 -3.67664e-17 +3.30041 -7.19788 6.04549 +3.96598 8.26877 3.50072 +4.80384 -8.4992 -3.67664e-17 +-9.39756 4.82294 -3.67664e-17 +-3.87715 8.24893 3.64676 +7.94284 2.78258 6.56179 +9.38263 -3.3939 3.956 +-7.97231 0.000888109 7.23443 +-2.40497 -5.01335 8.61832 +10.8357 1.4068 -3.67664e-17 +-4.41417 -8.66904 -3.67664e-17 +-1.36913 9.36498 -3.67664e-17 +-2.66785 -1.63353 10.0222 +-1.69716 7.36836 6.42654 +8.42874 -1.44826 6.54337 +-9.41523 -2.39555 4.64712 +6.33199 2.36514e-05 8.58253 +5.92409 5.47224 6.4276 +-1.34703 -9.37774 -3.67664e-17 +-10.7639 1.84544 5.02768e-07 +-7.75966 3.06611 6.58317 +2.0507 0.720227 10.2849 +7.15673 -5.84916 4.62305 +-8.49426 -5.36526 2.90792 +1.30643 -5.67035 8.31473 +7.17545 6.43007 3.51895 +-5.54076 -7.56222 3.43692 +-7.12018 7.15503 -3.67664e-17 +-4.93238 1.76392 9.18049 +-0.0713299 9.09033 2.98531 +8.17963 -6.2928 -3.67664e-17 +-10.7744 -1.76481 -3.67664e-17 +5.62911 -4.75048 7.31936 +5.02014 8.38848 -3.67664e-17 +1.73637 -9.34647 -3.67664e-17 +-7.29166 6.40428 3.36414 +-3.48587 4.68313 8.49046 +9.53055 3.40831 3.64621 +10.6834 -2.14774 -3.67664e-17 +-5.61386 -2.16282 8.69106 +-4.54329 -6.37983 6.42719 +9.71277 4.3488 -3.67664e-17 +-7.26016 -7.06669 -3.67664e-17 +2.07913 9.30644 -3.67664e-17 +0.783158 -2.75253 10.0185 +0.000324273 4.61083 9.18031 +10.3156 0.000335393 3.64581 +-9.51557 3.4297 3.65026 +4.26637 -2.64043 9.21598 +2.77479 5.68406 7.98044 +-2.03042 -8.81467 3.36921 +-10.393 0.0834565 3.38127 +-5.47889 5.94814 6.22992 +4.33549 2.66937 9.18083 +5.81759 -7.69249 2.63099 +-7.09738 -4.31635 6.42668 +1.96953 -8.78271 3.50016 +7.73539 6.73441 -3.67664e-17 +-0.89591 -7.63726 6.10777 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_1_world.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_1_world.particles new file mode 100644 index 0000000000..da8645c8da --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_1_world.particles @@ -0,0 +1,64 @@ +-4.35785 8.68416 -3.67664e-17 +-1.23047 1.58964 10.2851 +1.75264 7.76796 5.7845 +9.67338 -4.42333 -3.67664e-17 +-9.71507 -4.3208 -3.67664e-17 +3.30041 -7.19788 6.04549 +3.96598 8.26877 3.50072 +4.80384 -8.4992 -3.67664e-17 +-9.39756 4.82294 -3.67664e-17 +-3.87715 8.24893 3.64676 +7.94284 2.78258 6.56179 +9.38263 -3.3939 3.956 +-7.97231 0.000888109 7.23443 +-2.40497 -5.01335 8.61832 +10.8357 1.4068 -3.67664e-17 +-4.41417 -8.66904 -3.67664e-17 +-1.36913 9.36498 -3.67664e-17 +-2.66785 -1.63353 10.0222 +-1.69716 7.36836 6.42654 +8.42874 -1.44826 6.54337 +-9.41523 -2.39555 4.64712 +6.33199 2.36514e-05 8.58253 +5.92409 5.47224 6.4276 +-1.34703 -9.37774 -3.67664e-17 +-10.7639 1.84544 5.02768e-07 +-7.75966 3.06611 6.58317 +2.0507 0.720227 10.2849 +7.15673 -5.84916 4.62305 +-8.49426 -5.36526 2.90792 +1.30643 -5.67035 8.31473 +7.17545 6.43007 3.51895 +-5.54076 -7.56222 3.43692 +-7.12018 7.15503 -3.67664e-17 +-4.93238 1.76392 9.18049 +-0.0713299 9.09033 2.98531 +8.17963 -6.2928 -3.67664e-17 +-10.7744 -1.76481 -3.67664e-17 +5.62911 -4.75048 7.31936 +5.02014 8.38848 -3.67664e-17 +1.73637 -9.34647 -3.67664e-17 +-7.29166 6.40428 3.36414 +-3.48587 4.68313 8.49046 +9.53055 3.40831 3.64621 +10.6834 -2.14774 -3.67664e-17 +-5.61386 -2.16282 8.69106 +-4.54329 -6.37983 6.42719 +9.71277 4.3488 -3.67664e-17 +-7.26016 -7.06669 -3.67664e-17 +2.07913 9.30644 -3.67664e-17 +0.783158 -2.75253 10.0185 +0.000324273 4.61083 9.18031 +10.3156 0.000335393 3.64581 +-9.51557 3.4297 3.65026 +4.26637 -2.64043 9.21598 +2.77479 5.68406 7.98044 +-2.03042 -8.81467 3.36921 +-10.393 0.0834565 3.38127 +-5.47889 5.94814 6.22992 +4.33549 2.66937 9.18083 +5.81759 -7.69249 2.63099 +-7.09738 -4.31635 6.42668 +1.96953 -8.78271 3.50016 +7.73539 6.73441 -3.67664e-17 +-0.89591 -7.63726 6.10777 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_2_local.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_2_local.particles new file mode 100644 index 0000000000..e02d68e9b2 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_2_local.particles @@ -0,0 +1,64 @@ +-4.34933 9.28164 1.89626e-17 +-1.18448 1.44388 8.83646 +1.49452 8.1342 5.5086 +8.72447 -4.06488 1.89626e-17 +-9.35415 -1.60475 1.89626e-17 +2.98814 -7.68049 5.42568 +3.63048 8.88217 3.27442 +4.33574 -9.25401 1.89626e-17 +-8.42279 4.75503 1.89626e-17 +-3.36323 8.97447 3.3905 +7.03277 2.53508 5.59815 +7.99264 -3.53597 3.76593 +-6.87981 -0.00719884 6.20527 +-1.96873 -4.88803 7.7266 +9.34347 1.51485 1.89626e-17 +-4.49483 -9.1723 1.89626e-17 +-1.50547 10.3354 1.89626e-17 +-2.41522 -1.56989 8.59481 +-1.48211 7.58658 6.03842 +7.25203 -1.57912 5.64096 +-8.21508 -2.31817 3.99423 +5.42634 0.00295247 7.38713 +5.28441 5.59819 5.70641 +-1.5319 -10.3092 1.89626e-17 +-9.33849 1.80383 1.89626e-17 +-6.73727 3.07808 5.75358 +1.8152 0.62947 8.81588 +5.96756 -6.59613 4.11623 +-7.56113 -5.58226 2.56924 +1.11384 -5.86472 7.38679 +6.34796 6.84842 3.18761 +-4.96036 -8.16753 3.14047 +-6.74374 7.39384 1.89626e-17 +-4.30468 1.7785 7.86993 +0.000987997 9.90688 2.96021 +9.33918 -1.53959 1.89626e-17 +-8.77597 -4.00136 -5.97475e-08 +4.96048 -4.75194 6.46346 +4.71114 9.05561 1.89626e-17 +1.58178 -10.3015 1.89626e-17 +-6.30316 6.95746 3.12551 +-2.99279 4.8695 7.44421 +8.25299 3.68903 3.12348 +7.61028 -6.05409 1.24596 +-4.98974 -2.27984 7.40304 +-3.98586 -6.59358 5.88161 +8.57547 4.39635 1.89626e-17 +-6.91959 -7.13364 1.89626e-17 +1.84065 10.2967 1.89626e-17 +0.716096 -2.59066 8.68076 +-0.00032307 4.35393 8.18097 +8.90868 -3.27482e-05 3.1257 +-8.23054 3.76811 3.12563 +3.66634 -2.7113 7.9615 +2.4945 5.63487 7.18703 +-1.73863 -9.65681 3.12609 +-8.91998 0.293175 3.01265 +-5.07125 6.00034 5.5782 +3.86731 2.76006 7.86928 +6.30264 -7.76248 1.89626e-17 +-6.21327 -4.5595 5.56656 +2.19222 -9.46722 3.19813 +7.00869 7.00903 1.89626e-17 +-0.880544 -8.0837 5.63235 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_2_world.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_2_world.particles new file mode 100644 index 0000000000..e02d68e9b2 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_2_world.particles @@ -0,0 +1,64 @@ +-4.34933 9.28164 1.89626e-17 +-1.18448 1.44388 8.83646 +1.49452 8.1342 5.5086 +8.72447 -4.06488 1.89626e-17 +-9.35415 -1.60475 1.89626e-17 +2.98814 -7.68049 5.42568 +3.63048 8.88217 3.27442 +4.33574 -9.25401 1.89626e-17 +-8.42279 4.75503 1.89626e-17 +-3.36323 8.97447 3.3905 +7.03277 2.53508 5.59815 +7.99264 -3.53597 3.76593 +-6.87981 -0.00719884 6.20527 +-1.96873 -4.88803 7.7266 +9.34347 1.51485 1.89626e-17 +-4.49483 -9.1723 1.89626e-17 +-1.50547 10.3354 1.89626e-17 +-2.41522 -1.56989 8.59481 +-1.48211 7.58658 6.03842 +7.25203 -1.57912 5.64096 +-8.21508 -2.31817 3.99423 +5.42634 0.00295247 7.38713 +5.28441 5.59819 5.70641 +-1.5319 -10.3092 1.89626e-17 +-9.33849 1.80383 1.89626e-17 +-6.73727 3.07808 5.75358 +1.8152 0.62947 8.81588 +5.96756 -6.59613 4.11623 +-7.56113 -5.58226 2.56924 +1.11384 -5.86472 7.38679 +6.34796 6.84842 3.18761 +-4.96036 -8.16753 3.14047 +-6.74374 7.39384 1.89626e-17 +-4.30468 1.7785 7.86993 +0.000987997 9.90688 2.96021 +9.33918 -1.53959 1.89626e-17 +-8.77597 -4.00136 -5.97475e-08 +4.96048 -4.75194 6.46346 +4.71114 9.05561 1.89626e-17 +1.58178 -10.3015 1.89626e-17 +-6.30316 6.95746 3.12551 +-2.99279 4.8695 7.44421 +8.25299 3.68903 3.12348 +7.61028 -6.05409 1.24596 +-4.98974 -2.27984 7.40304 +-3.98586 -6.59358 5.88161 +8.57547 4.39635 1.89626e-17 +-6.91959 -7.13364 1.89626e-17 +1.84065 10.2967 1.89626e-17 +0.716096 -2.59066 8.68076 +-0.00032307 4.35393 8.18097 +8.90868 -3.27482e-05 3.1257 +-8.23054 3.76811 3.12563 +3.66634 -2.7113 7.9615 +2.4945 5.63487 7.18703 +-1.73863 -9.65681 3.12609 +-8.91998 0.293175 3.01265 +-5.07125 6.00034 5.5782 +3.86731 2.76006 7.86928 +6.30264 -7.76248 1.89626e-17 +-6.21327 -4.5595 5.56656 +2.19222 -9.46722 3.19813 +7.00869 7.00903 1.89626e-17 +-0.880544 -8.0837 5.63235 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_3_local.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_3_local.particles new file mode 100644 index 0000000000..34289ac0c1 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_3_local.particles @@ -0,0 +1,64 @@ +-4.42347 9.0283 7.75466e-17 +-1.21838 1.6497 10.7725 +1.7052 8.16221 6.07811 +9.36226 -4.39777 7.75466e-17 +-10.3086 -1.66767 7.75466e-17 +3.29226 -7.5754 6.27309 +3.86343 8.66854 3.65448 +4.62927 -8.90293 7.75466e-17 +-9.10745 4.86189 7.75466e-17 +-3.76892 8.67332 3.78709 +7.82067 2.73255 6.65207 +9.09243 -3.58694 3.83205 +-7.70005 0.00410439 7.46863 +-2.29636 -5.27756 9.02826 +10.3358 1.50941 7.75466e-17 +-4.62063 -8.9031 7.75466e-17 +-1.46326 9.86194 7.75466e-17 +-2.63372 -1.68519 10.4837 +-1.62048 7.75719 6.73081 +8.20355 -1.55421 6.64116 +-9.08812 -2.33709 4.77669 +6.27978 0.000777719 8.80009 +5.90439 5.67947 6.59351 +-1.55757 -9.82826 7.75466e-17 +-10.2838 1.9322 7.75466e-17 +-7.64553 3.0708 6.73374 +2.03979 0.765652 10.7525 +6.93123 -6.08022 4.77941 +-8.32933 -5.43351 2.95136 +1.26738 -6.06416 8.62778 +7.00831 6.67937 3.59078 +-5.50533 -7.88369 3.4961 +-7.08797 7.32685 7.75466e-17 +-4.83776 1.91188 9.52483 +-0.0511811 9.58074 3.09104 +10.3004 -1.82694 7.75466e-17 +-9.54191 -4.06293 7.75466e-17 +5.58237 -4.89127 7.57895 +4.89039 8.79482 7.75466e-17 +1.5895 -9.82604 7.75466e-17 +-7.04355 6.70804 3.43764 +-3.51692 5.0137 8.75852 +9.15476 3.61109 3.61157 +7.85589 -6.54284 7.75466e-17 +-5.51741 -2.29346 9.00259 +-4.55275 -6.65361 6.6661 +9.32201 4.49433 7.75466e-17 +-7.38509 -7.06979 7.75466e-17 +2.0079 9.81162 7.75466e-17 +0.822037 -2.87538 10.4956 +0.000665746 4.85325 9.61761 +9.88232 -0.00048061 3.70025 +-9.12623 3.60002 3.71462 +4.21525 -2.74198 9.60878 +2.82277 5.91325 8.3464 +-1.94244 -9.29964 3.45453 +-9.96365 0.172016 3.36945 +-5.49005 6.17541 6.38225 +4.40961 2.84098 9.46693 +5.6935 -8.06322 2.54894 +-6.96771 -4.46688 6.58435 +1.93052 -9.24134 3.66265 +7.44139 7.04869 7.75466e-17 +-0.99806 -8.07401 6.32246 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_3_world.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_3_world.particles new file mode 100644 index 0000000000..34289ac0c1 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_3_world.particles @@ -0,0 +1,64 @@ +-4.42347 9.0283 7.75466e-17 +-1.21838 1.6497 10.7725 +1.7052 8.16221 6.07811 +9.36226 -4.39777 7.75466e-17 +-10.3086 -1.66767 7.75466e-17 +3.29226 -7.5754 6.27309 +3.86343 8.66854 3.65448 +4.62927 -8.90293 7.75466e-17 +-9.10745 4.86189 7.75466e-17 +-3.76892 8.67332 3.78709 +7.82067 2.73255 6.65207 +9.09243 -3.58694 3.83205 +-7.70005 0.00410439 7.46863 +-2.29636 -5.27756 9.02826 +10.3358 1.50941 7.75466e-17 +-4.62063 -8.9031 7.75466e-17 +-1.46326 9.86194 7.75466e-17 +-2.63372 -1.68519 10.4837 +-1.62048 7.75719 6.73081 +8.20355 -1.55421 6.64116 +-9.08812 -2.33709 4.77669 +6.27978 0.000777719 8.80009 +5.90439 5.67947 6.59351 +-1.55757 -9.82826 7.75466e-17 +-10.2838 1.9322 7.75466e-17 +-7.64553 3.0708 6.73374 +2.03979 0.765652 10.7525 +6.93123 -6.08022 4.77941 +-8.32933 -5.43351 2.95136 +1.26738 -6.06416 8.62778 +7.00831 6.67937 3.59078 +-5.50533 -7.88369 3.4961 +-7.08797 7.32685 7.75466e-17 +-4.83776 1.91188 9.52483 +-0.0511811 9.58074 3.09104 +10.3004 -1.82694 7.75466e-17 +-9.54191 -4.06293 7.75466e-17 +5.58237 -4.89127 7.57895 +4.89039 8.79482 7.75466e-17 +1.5895 -9.82604 7.75466e-17 +-7.04355 6.70804 3.43764 +-3.51692 5.0137 8.75852 +9.15476 3.61109 3.61157 +7.85589 -6.54284 7.75466e-17 +-5.51741 -2.29346 9.00259 +-4.55275 -6.65361 6.6661 +9.32201 4.49433 7.75466e-17 +-7.38509 -7.06979 7.75466e-17 +2.0079 9.81162 7.75466e-17 +0.822037 -2.87538 10.4956 +0.000665746 4.85325 9.61761 +9.88232 -0.00048061 3.70025 +-9.12623 3.60002 3.71462 +4.21525 -2.74198 9.60878 +2.82277 5.91325 8.3464 +-1.94244 -9.29964 3.45453 +-9.96365 0.172016 3.36945 +-5.49005 6.17541 6.38225 +4.40961 2.84098 9.46693 +5.6935 -8.06322 2.54894 +-6.96771 -4.46688 6.58435 +1.93052 -9.24134 3.66265 +7.44139 7.04869 7.75466e-17 +-0.99806 -8.07401 6.32246 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_4_local.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_4_local.particles new file mode 100644 index 0000000000..b528fa5986 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_4_local.particles @@ -0,0 +1,64 @@ +-3.92749 8.03953 2.59314e-17 +-1.05124 1.46955 9.79532 +1.4715 7.34522 5.52212 +8.09822 -3.81128 2.59314e-17 +-8.20129 -3.66854 2.59314e-17 +2.83028 -6.84262 5.66092 +3.31691 7.79406 3.33711 +4.11189 -7.94897 2.59314e-17 +-7.88602 4.23606 2.59314e-17 +-3.23939 7.79714 3.45748 +6.77851 2.41566 5.95239 +7.79711 -3.23035 3.47276 +-6.70212 0.0106833 6.65666 +-2.00141 -4.73272 8.20704 +8.83635 1.39726 2.59314e-17 +-4.03472 -8.00452 2.59314e-17 +-1.36177 8.84449 2.59314e-17 +-2.2997 -1.53343 9.51256 +-1.38782 6.98042 6.12085 +7.10195 -1.41541 5.92166 +-7.82644 -2.08691 4.2637 +5.50087 -0.00113053 7.89838 +5.10375 5.10509 5.95847 +-1.35037 -8.86792 2.59314e-17 +-8.83233 1.70983 2.59314e-17 +-6.57597 2.72331 6.12004 +1.80397 0.692407 9.75936 +5.97737 -5.43765 4.32889 +-7.21832 -4.82784 2.6067 +1.09146 -5.49164 7.81396 +6.00967 6.01022 3.26195 +-4.73893 -7.07852 3.19195 +-6.24864 6.44356 2.59314e-17 +-4.184 1.73376 8.63173 +-0.000880098 8.64049 2.7619 +8.83713 -1.41158 2.59314e-17 +-8.8723 -1.32633 2.59314e-17 +4.82733 -4.35116 6.8894 +4.25802 7.84855 2.59314e-17 +1.46591 -8.85391 2.59314e-17 +-6.04457 6.04438 3.08919 +-3.04077 4.54958 7.92308 +7.85477 3.25278 3.25369 +6.7428 -5.87906 2.59314e-17 +-4.84378 -1.9957 8.12305 +-3.96922 -5.94565 6.06978 +7.99818 3.97615 2.59314e-17 +-6.36919 -6.36693 2.59314e-17 +1.7402 8.80458 1.47744e-07 +0.724175 -2.58104 9.54173 +0.000374007 4.36736 8.74364 +8.48766 -0.00269903 3.3025 +-7.82727 3.24137 3.35911 +3.6807 -2.42069 8.71528 +2.45727 5.30109 7.58813 +-1.66683 -8.3811 3.09944 +-8.5514 0.141866 3.02824 +-4.82574 5.49826 5.75385 +3.85825 2.58661 8.5509 +4.93586 -7.20721 2.33382 +-6.03984 -4.00567 5.90897 +1.65323 -8.31015 3.35529 +6.41828 6.25219 2.59314e-17 +-0.941133 -7.25887 5.74658 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_4_world.particles b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_4_world.particles new file mode 100644 index 0000000000..b528fa5986 --- /dev/null +++ b/Testing/data/correspondence_quality_constraints/project_particles/ellipsoid_4_world.particles @@ -0,0 +1,64 @@ +-3.92749 8.03953 2.59314e-17 +-1.05124 1.46955 9.79532 +1.4715 7.34522 5.52212 +8.09822 -3.81128 2.59314e-17 +-8.20129 -3.66854 2.59314e-17 +2.83028 -6.84262 5.66092 +3.31691 7.79406 3.33711 +4.11189 -7.94897 2.59314e-17 +-7.88602 4.23606 2.59314e-17 +-3.23939 7.79714 3.45748 +6.77851 2.41566 5.95239 +7.79711 -3.23035 3.47276 +-6.70212 0.0106833 6.65666 +-2.00141 -4.73272 8.20704 +8.83635 1.39726 2.59314e-17 +-4.03472 -8.00452 2.59314e-17 +-1.36177 8.84449 2.59314e-17 +-2.2997 -1.53343 9.51256 +-1.38782 6.98042 6.12085 +7.10195 -1.41541 5.92166 +-7.82644 -2.08691 4.2637 +5.50087 -0.00113053 7.89838 +5.10375 5.10509 5.95847 +-1.35037 -8.86792 2.59314e-17 +-8.83233 1.70983 2.59314e-17 +-6.57597 2.72331 6.12004 +1.80397 0.692407 9.75936 +5.97737 -5.43765 4.32889 +-7.21832 -4.82784 2.6067 +1.09146 -5.49164 7.81396 +6.00967 6.01022 3.26195 +-4.73893 -7.07852 3.19195 +-6.24864 6.44356 2.59314e-17 +-4.184 1.73376 8.63173 +-0.000880098 8.64049 2.7619 +8.83713 -1.41158 2.59314e-17 +-8.8723 -1.32633 2.59314e-17 +4.82733 -4.35116 6.8894 +4.25802 7.84855 2.59314e-17 +1.46591 -8.85391 2.59314e-17 +-6.04457 6.04438 3.08919 +-3.04077 4.54958 7.92308 +7.85477 3.25278 3.25369 +6.7428 -5.87906 2.59314e-17 +-4.84378 -1.9957 8.12305 +-3.96922 -5.94565 6.06978 +7.99818 3.97615 2.59314e-17 +-6.36919 -6.36693 2.59314e-17 +1.7402 8.80458 1.47744e-07 +0.724175 -2.58104 9.54173 +0.000374007 4.36736 8.74364 +8.48766 -0.00269903 3.3025 +-7.82727 3.24137 3.35911 +3.6807 -2.42069 8.71528 +2.45727 5.30109 7.58813 +-1.66683 -8.3811 3.09944 +-8.5514 0.141866 3.02824 +-4.82574 5.49826 5.75385 +3.85825 2.58661 8.5509 +4.93586 -7.20721 2.33382 +-6.03984 -4.00567 5.90897 +1.65323 -8.31015 3.35529 +6.41828 6.25219 2.59314e-17 +-0.941133 -7.25887 5.74658 diff --git a/Testing/data/correspondence_quality_constraints/project_particles/transform b/Testing/data/correspondence_quality_constraints/project_particles/transform new file mode 100644 index 0000000000..59834fffb7 Binary files /dev/null and b/Testing/data/correspondence_quality_constraints/project_particles/transform differ diff --git a/docs/workflow/analyze.md b/docs/workflow/analyze.md index 9a203afa24..b91ecdd9e9 100644 --- a/docs/workflow/analyze.md +++ b/docs/workflow/analyze.md @@ -58,6 +58,8 @@ The command prints a summary — mean, median, p95 and max of the per-subject me Note that this measures the correspondence model against the *groomed* meshes, so it reflects both optimization quality and any grooming problems upstream of it. +Cutting planes and free-form constraints are honored. The optimizer never places particles in the region a constraint cuts away, so that region is left out of every statistic, reads as zero in the output fields, and is excluded from the bounding box used for normalization. The template mesh is also clipped before warping, as Studio does, so the cut-away part is not extrapolated into each reconstruction. + ### Python API ```python