Skip to content

Commit 74d8c28

Browse files
committed
Add procrustes tests, add ability to create ParticleShapeStatistics from Project.
1 parent ba5b3ce commit 74d8c28

File tree

13 files changed

+1694
-30
lines changed

13 files changed

+1694
-30
lines changed

Libs/Particles/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ target_link_libraries(Particles PUBLIC
3333
Mesh
3434
tinyxml
3535
Eigen3::Eigen
36+
Project
3637
)
3738

3839
# set

Libs/Particles/ParticleShapeStatistics.cpp

+24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11

22
#include "ParticleShapeStatistics.h"
33
#include "ShapeEvaluation.h"
4+
#include <Libs/Project/Project.h>
5+
46

57
#include <vnl/algo/vnl_symmetric_eigensystem.h>
68
#include <tinyxml.h>
@@ -342,6 +344,28 @@ int ParticleShapeStatistics::ReadPointFiles(const std::string &s)
342344
return 0;
343345
}
344346

347+
//---------------------------------------------------------------------------
348+
ParticleShapeStatistics::ParticleShapeStatistics(std::shared_ptr<Project> project) {
349+
350+
std::vector<Eigen::VectorXd> points;
351+
std::vector<int> groups;
352+
for (auto& s : project->get_subjects()) {
353+
auto world_files = s->get_world_particle_filenames();
354+
Eigen::VectorXd particles;
355+
for (auto& file : world_files) {
356+
Eigen::VectorXd domain_particles;
357+
ParticleSystem::ReadParticleFile(file, domain_particles);
358+
Eigen::VectorXd combined(particles.size() + domain_particles.size());
359+
combined << particles, domain_particles;
360+
particles = combined;
361+
}
362+
points.push_back(particles);
363+
groups.push_back(1);
364+
}
365+
ImportPoints(points, groups);
366+
}
367+
368+
//---------------------------------------------------------------------------
345369
int ParticleShapeStatistics::DoPCA(std::vector<std::vector<Point>> global_pts, int domainsPerShape)
346370
{
347371
this->m_domainsPerShape = domainsPerShape;

Libs/Particles/ParticleShapeStatistics.h

+5-2
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
namespace shapeworks {
1818

19+
class Project;
20+
1921
/**
2022
* \class ParticleShapeStatistics
2123
* This class computes various statistics for a set of correspondence positions
@@ -27,8 +29,9 @@ class ParticleShapeStatistics {
2729

2830
constexpr static int VDimension = 3;
2931

30-
ParticleShapeStatistics() {}
31-
~ParticleShapeStatistics() {}
32+
ParticleShapeStatistics() {};
33+
ParticleShapeStatistics(std::shared_ptr<Project> project);
34+
~ParticleShapeStatistics() {};
3235

3336
int DoPCA(std::vector<std::vector<Point>> global_pts, int domainsPerShape = 1);
3437

Libs/Particles/ParticleSystem.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -120,4 +120,35 @@ bool ParticleSystem::EvaluationCompare(const ParticleSystem& other) const
120120
return good;
121121
}
122122

123+
bool ParticleSystem::ReadParticleFile(std::string filename, Eigen::VectorXd &points)
124+
{
125+
std::ifstream in(filename);
126+
if (!in.good()) {
127+
return false;
128+
}
129+
vtkSmartPointer<vtkPoints> vtk_points = vtkSmartPointer<vtkPoints>::New();
130+
int num_points = 0;
131+
while (in.good()) {
132+
double x, y, z;
133+
in >> x >> y >> z;
134+
if (!in.good()) {
135+
break;
136+
}
137+
vtk_points->InsertNextPoint(x, y, z);
138+
num_points++;
139+
}
140+
in.close();
141+
points.setZero();
142+
points.resize(num_points * 3);
143+
144+
int idx = 0;
145+
for (int i = 0; i < num_points; i++) {
146+
double* pos = vtk_points->GetPoint(i);
147+
points[idx++] = pos[0];
148+
points[idx++] = pos[1];
149+
points[idx++] = pos[2];
150+
}
151+
return true;
152+
}
153+
123154
}

Libs/Particles/ParticleSystem.h

+2
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ class ParticleSystem
4242

4343
bool EvaluationCompare(const ParticleSystem& other) const;
4444

45+
static bool ReadParticleFile(std::string filename, Eigen::VectorXd& points);
46+
4547
private:
4648
friend struct SharedCommandData;
4749

Studio/src/Data/Shape.cpp

+1-27
Original file line numberDiff line numberDiff line change
@@ -477,33 +477,7 @@ void Shape::generate_meshes(std::vector<std::string> filenames, MeshGroup& mesh_
477477

478478
//---------------------------------------------------------------------------
479479
bool Shape::import_point_file(QString filename, Eigen::VectorXd& points) {
480-
std::ifstream in(filename.toStdString().c_str());
481-
if (!in.good()) {
482-
return false;
483-
}
484-
vtkSmartPointer<vtkPoints> vtk_points = vtkSmartPointer<vtkPoints>::New();
485-
int num_points = 0;
486-
while (in.good()) {
487-
double x, y, z;
488-
in >> x >> y >> z;
489-
if (!in.good()) {
490-
break;
491-
}
492-
vtk_points->InsertNextPoint(x, y, z);
493-
num_points++;
494-
}
495-
in.close();
496-
points.setZero();
497-
points.resize(num_points * 3);
498-
499-
int idx = 0;
500-
for (int i = 0; i < num_points; i++) {
501-
double* pos = vtk_points->GetPoint(i);
502-
points[idx++] = pos[0];
503-
points[idx++] = pos[1];
504-
points[idx++] = pos[2];
505-
}
506-
return true;
480+
return ParticleSystem::ReadParticleFile(filename.toStdString(), points);
507481
}
508482

509483
//---------------------------------------------------------------------------

Testing/OptimizeTests/OptimizeTests.cpp

+91
Original file line numberDiff line numberDiff line change
@@ -632,3 +632,94 @@ TEST(OptimizeTests, mesh_ffc_test) {
632632
bool good = check_constraint_violations(app, 3.0e-1);
633633
ASSERT_TRUE(good);
634634
}
635+
636+
//---------------------------------------------------------------------------
637+
TEST(OptimizeTests, procrustes_disabled_test) {
638+
TestUtils::Instance().prep_temp(std::string(TEST_DATA_DIR) + "/optimize/procrustes", "procrustes_disabled_test");
639+
640+
ProjectHandle project = std::make_shared<Project>();
641+
project->load("procrustes.xlsx");
642+
OptimizeParameters params(project);
643+
params.set_use_procrustes(false);
644+
645+
Optimize app;
646+
params.set_up_optimize(&app);
647+
648+
// run optimize
649+
bool success = app.Run();
650+
ASSERT_TRUE(success);
651+
652+
// compute stats
653+
ParticleShapeStatistics stats(project);
654+
stats.ComputeModes();
655+
stats.PrincipalComponentProjections();
656+
657+
// print out eigenvalues (for debugging)
658+
auto values = stats.Eigenvalues();
659+
for (int i = 0; i < values.size(); i++) {
660+
std::cerr << "Eigenvalue " << i << " : " << values[i] << "\n";
661+
}
662+
ASSERT_GT(values[values.size() - 1], 700.0);
663+
}
664+
665+
//---------------------------------------------------------------------------
666+
TEST(OptimizeTests, procrustes_no_scale_test) {
667+
TestUtils::Instance().prep_temp(std::string(TEST_DATA_DIR) + "/optimize/procrustes", "procrustes_no_scale_test");
668+
669+
ProjectHandle project = std::make_shared<Project>();
670+
project->load("procrustes.xlsx");
671+
OptimizeParameters params(project);
672+
params.set_use_procrustes(true);
673+
params.set_use_procrustes_scaling(false);
674+
675+
Optimize app;
676+
params.set_up_optimize(&app);
677+
678+
// run optimize
679+
bool success = app.Run();
680+
ASSERT_TRUE(success);
681+
682+
// compute stats
683+
ParticleShapeStatistics stats(project);
684+
stats.ComputeModes();
685+
stats.PrincipalComponentProjections();
686+
687+
// print out eigenvalues (for debugging)
688+
auto values = stats.Eigenvalues();
689+
for (int i = 0; i < values.size(); i++) {
690+
std::cerr << "Eigenvalue " << i << " : " << values[i] << "\n";
691+
}
692+
ASSERT_GT(values[values.size() - 1], 150.0);
693+
ASSERT_LT(values[values.size() - 1], 200.0);
694+
}
695+
696+
//---------------------------------------------------------------------------
697+
TEST(OptimizeTests, procrustes_enabled_test) {
698+
TestUtils::Instance().prep_temp(std::string(TEST_DATA_DIR) + "/optimize/procrustes", "procrustes_enabled_test");
699+
700+
ProjectHandle project = std::make_shared<Project>();
701+
project->load("procrustes.xlsx");
702+
OptimizeParameters params(project);
703+
params.set_use_procrustes(true);
704+
params.set_use_procrustes_scaling(true);
705+
706+
Optimize app;
707+
params.set_up_optimize(&app);
708+
709+
// run optimize
710+
bool success = app.Run();
711+
ASSERT_TRUE(success);
712+
713+
// compute stats
714+
ParticleShapeStatistics stats(project);
715+
stats.ComputeModes();
716+
stats.PrincipalComponentProjections();
717+
718+
// print out eigenvalues (for debugging)
719+
auto values = stats.Eigenvalues();
720+
for (int i = 0; i < values.size(); i++) {
721+
std::cerr << "Eigenvalue " << i << " : " << values[i] << "\n";
722+
}
723+
// should be tiny with all of procrustes enabled
724+
ASSERT_LT(values[values.size() - 1], 1.0);
725+
}

Testing/Testing.cpp

+27-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ TestUtils::~TestUtils() {
3333
if (!temp_base_.empty() && !should_keep_dir()) {
3434
try {
3535
boost::filesystem::remove_all(temp_base_);
36-
} catch (std::exception &e) {
36+
} catch (std::exception& e) {
3737
std::cerr << "Error removing test output directory: " << temp_base_ << "\n";
3838
std::cerr << "Error: " << e.what();
3939
}
@@ -59,6 +59,14 @@ std::string TestUtils::get_output_dir(std::string test_name) {
5959
return test_temp.generic_string();
6060
}
6161

62+
//-----------------------------------------------------------------------------
63+
void TestUtils::prep_temp(std::string test_folder, std::string test_name) {
64+
auto temp_dir = get_output_dir(test_name);
65+
66+
recursive_copy(test_folder, temp_dir);
67+
boost::filesystem::current_path(temp_dir);
68+
}
69+
6270
//-----------------------------------------------------------------------------
6371
bool TestUtils::should_keep_dir() {
6472
bool keep = false;
@@ -72,4 +80,22 @@ bool TestUtils::should_keep_dir() {
7280
return keep;
7381
}
7482

83+
//-----------------------------------------------------------------------------
84+
void TestUtils::recursive_copy(const boost::filesystem::path& src, const boost::filesystem::path& dst) {
85+
// adapted from https://gist.github.com/ssteffl/30055ac128bb92801568899ebad73365
86+
87+
if (boost::filesystem::is_directory(src)) {
88+
boost::filesystem::create_directories(dst);
89+
for (boost::filesystem::directory_entry& item : boost::filesystem::directory_iterator(src)) {
90+
recursive_copy(item.path(), dst / item.path().filename());
91+
}
92+
} else if (boost::filesystem::is_regular_file(src)) {
93+
boost::filesystem::copy(src, dst);
94+
} else {
95+
throw std::runtime_error(dst.generic_string() + " not dir or file");
96+
}
97+
}
98+
99+
//-----------------------------------------------------------------------------
100+
75101
} // namespace shapeworks

Testing/Testing.h

+5
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,14 @@ class TestUtils {
2020
//! Return output directory
2121
std::string get_output_dir(std::string test_name);
2222

23+
//! Create a copy of a test data folder in a temp folder and chdir there
24+
void prep_temp(std::string test_folder, std::string test_name);
25+
2326
private:
2427
bool should_keep_dir();
2528

29+
void recursive_copy(const boost::filesystem::path &src, const boost::filesystem::path &dst);
30+
2631
boost::filesystem::path temp_base_;
2732
};
2833
} // namespace shapeworks

0 commit comments

Comments
 (0)