Skip to content

Commit acb2f40

Browse files
authored
#2189 internal image summary (#2193)
* toDistanceTransform needs to invalidate the cell locator in case it's not properly reset. * Add Mesh isPointInside (currently unused) * Add helpers to return minimum image spacing. * Add internal image summary information fo mesh compute thickness functionality (#2189) * Catch Image exception * Check DT by spacing * Cleanup, add smoothing * Update baseline * Fix bug causing undeterministic behavior * Check inside image * Update baseline
1 parent 23f1c9a commit acb2f40

File tree

8 files changed

+214
-16
lines changed

8 files changed

+214
-16
lines changed

Libs/Analyze/Shape.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -568,9 +568,13 @@ std::shared_ptr<Image> Shape::get_image_volume(std::string image_volume_name) {
568568
auto filename = filenames[image_volume_name];
569569

570570
if (image_volume_filename_ != filename) {
571-
std::shared_ptr<Image> image = std::make_shared<Image>(filename);
572-
image_volume_ = image;
573-
image_volume_filename_ = filename;
571+
try {
572+
std::shared_ptr<Image> image = std::make_shared<Image>(filename);
573+
image_volume_ = image;
574+
image_volume_filename_ = filename;
575+
} catch (std::exception &ex) {
576+
SW_ERROR("Unable to open file: {}", filename);
577+
}
574578
}
575579

576580
return image_volume_;

Libs/Image/Image.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,11 @@ Image& Image::isolate() {
859859
return *this;
860860
}
861861

862+
double Image::get_minimum_spacing() const {
863+
auto spacing = this->spacing();
864+
return std::min(spacing[0], std::min(spacing[1], spacing[2]));
865+
}
866+
862867
Point3 Image::centerOfMass(PixelType minVal, PixelType maxVal) const {
863868
itk::ImageRegionIteratorWithIndex<ImageType> imageIt(this->itk_image_, itk_image_->GetLargestPossibleRegion());
864869
int numPixels = 0;

Libs/Image/Image.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,9 @@ class Image {
216216
/// physical spacing of the image
217217
Vector spacing() const { return itk_image_->GetSpacing(); }
218218

219+
/// minimum physical spacing of the image
220+
double get_minimum_spacing() const;
221+
219222
/// physical coordinates of image origin
220223
Point3 origin() const { return itk_image_->GetOrigin(); }
221224

Libs/Mesh/Mesh.cpp

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,23 @@ int Mesh::closestPointId(const Point3 point) const {
763763
return closestPointId;
764764
}
765765

766+
bool Mesh::isPointInside(const Point3 point) const {
767+
// create point set
768+
auto points = vtkSmartPointer<vtkPoints>::New();
769+
points->InsertNextPoint(point.GetDataPointer());
770+
auto polydata = vtkSmartPointer<vtkPolyData>::New();
771+
polydata->SetPoints(points);
772+
773+
auto select = vtkSmartPointer<vtkSelectEnclosedPoints>::New();
774+
775+
select->SetInputData(polydata);
776+
select->SetSurfaceData(this->poly_data_);
777+
select->SetTolerance(0.0001);
778+
select->Update();
779+
780+
return select->IsInside(0);
781+
}
782+
766783
double Mesh::geodesicDistance(int source, int target) const {
767784
if (source < 0 || target < 0 || numPoints() < source || numPoints() < target) {
768785
throw std::invalid_argument("requested point ids outside range of points available in mesh");
@@ -1066,6 +1083,7 @@ Image Mesh::toImage(PhysicalRegion region, Point3 spacing) const {
10661083
}
10671084

10681085
Image Mesh::toDistanceTransform(PhysicalRegion region, const Point3 spacing, const Dims padding) const {
1086+
invalidateLocators();
10691087
this->updateCellLocator();
10701088

10711089
// if no region, use mesh bounding box
@@ -1106,14 +1124,20 @@ Image Mesh::toDistanceTransform(PhysicalRegion region, const Point3 spacing, con
11061124
enclosed->SetSurfaceData(this->poly_data_);
11071125
enclosed->Update();
11081126

1127+
std::mutex cell_mutex;
1128+
11091129
tbb::parallel_for(tbb::blocked_range<size_t>(0, indices.size()), [&](const tbb::blocked_range<size_t>& r) {
11101130
for (size_t i = r.begin(); i != r.end(); ++i) {
11111131
Image::ImageType::PointType p;
11121132
itkimg->TransformIndexToPhysicalPoint(indices[i], p);
11131133

11141134
double distance = 0.0;
11151135
vtkIdType face_id = 0;
1116-
closestPoint(p, distance, face_id);
1136+
1137+
{
1138+
// std::lock_guard<std::mutex> lock(cell_mutex);
1139+
closestPoint(p, distance, face_id);
1140+
}
11171141

11181142
bool outside = !enclosed->IsInside(i);
11191143

@@ -1125,7 +1149,8 @@ Image Mesh::toDistanceTransform(PhysicalRegion region, const Point3 spacing, con
11251149
return img;
11261150
}
11271151

1128-
Mesh& Mesh::computeThickness(Image& image, Image* dt, double max_dist, double median_radius, std::string distance_mesh) {
1152+
Mesh& Mesh::computeThickness(Image& image, Image* dt, double max_dist, double median_radius,
1153+
std::string distance_mesh) {
11291154
mesh::compute_thickness(*this, image, dt, max_dist, median_radius, distance_mesh);
11301155
return *this;
11311156
}

Libs/Mesh/Mesh.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,9 @@ class Mesh {
154154
/// returns closest point id in this mesh to the given point in space
155155
int closestPointId(const Point3 point) const;
156156

157+
/// returns if the given point is inside the mesh
158+
bool isPointInside(const Point3 point) const;
159+
157160
/// computes geodesic distance between two vertices (specified by their indices) on mesh
158161
double geodesicDistance(int source, int target) const;
159162

Libs/Mesh/MeshComputeThickness.cpp

Lines changed: 163 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <itkVectorLinearInterpolateImageFunction.h>
55
#include <tbb/parallel_for.h>
66
#include <vtkPointData.h>
7+
#include <vtkSelectEnclosedPoints.h>
78
#include <vtkStaticCellLocator.h>
89
#include <vtkStaticPointLocator.h>
910

@@ -126,7 +127,9 @@ static std::vector<double> median_smooth_signal_intensities(std::vector<double>
126127
for (int i = 0; i < intensities.size(); i++) {
127128
std::vector<double> local_intensities;
128129
for (int j = -2; j <= 2; j++) {
129-
local_intensities.push_back(intensities[i + j]);
130+
if (i + j >= 0 && i + j < intensities.size()) {
131+
local_intensities.push_back(intensities[i + j]);
132+
}
130133
}
131134
// compute median
132135
std::sort(local_intensities.begin(), local_intensities.end());
@@ -174,6 +177,58 @@ static double get_distance_to_opposite_side(Mesh& mesh, int point_id) {
174177
return distance;
175178
}
176179

180+
/*
181+
182+
//---------------------------------------------------------------------------
183+
static void get_distances_to_inner_mesh(Mesh &outer_mesh, int point_id, Mesh &inner_mesh, double &p1, double &p2) {
184+
// using the surface normal from the points on the outer mesh, find the two intersections of the inner mesh
185+
186+
vtkSmartPointer<vtkPolyData> poly_data = outer_mesh.getVTKMesh();
187+
188+
// Get the surface normal from the given point
189+
double normal[3];
190+
poly_data->GetPointData()->GetNormals()->GetTuple(point_id, normal);
191+
192+
// Create a ray in the opposite direction of the normal
193+
double ray_start[3];
194+
double ray_end[3];
195+
poly_data->GetPoint(point_id, ray_start);
196+
for (int i = 0; i < 3; ++i) {
197+
ray_start[i] = ray_start[i] - normal[i] * 0.001;
198+
ray_end[i] = ray_start[i] - normal[i] * 100.0;
199+
}
200+
201+
int result = 0;
202+
// Find the intersection point with the mesh
203+
204+
205+
206+
207+
}
208+
209+
*/
210+
211+
/*
212+
//---------------------------------------------------------------------------
213+
bool is_inside(Mesh &mesh, Point3 start, Point3 current) {
214+
215+
// use the sign of t to determine if the point is inside or outside the mesh
216+
217+
// lock mutex (IntersectWithLine is not thread safe)
218+
std::lock_guard<std::mutex> lock(cell_mutex);
219+
auto cellLocator = mesh.getCellLocator();
220+
double t;
221+
double intersectionPoint[3];
222+
double pcoords[3];
223+
int subId;
224+
int result = cellLocator->IntersectWithLine(start.GetDataPointer(), current.GetDataPointer(), 0.0, t,
225+
intersectionPoint, pcoords, subId);
226+
227+
return result != 0/;
228+
229+
}
230+
*/
231+
177232
//---------------------------------------------------------------------------
178233
double compute_thickness_from_signal(const std::vector<double>& intensities_input, double step_size) {
179234
auto smoothed = median_smooth_signal_intensities(intensities_input);
@@ -358,14 +413,7 @@ void compute_thickness(Mesh& mesh, Image& image, Image* dt, double max_dist, dou
358413
}
359414
};
360415

361-
// get minimum spacing
362-
double spacing = image.spacing()[0];
363-
for (int i = 1; i < 3; i++) {
364-
if (image.spacing()[i] < spacing) {
365-
spacing = image.spacing()[i];
366-
}
367-
}
368-
416+
double spacing = image.get_minimum_spacing();
369417
double step_size = spacing / 10.0;
370418

371419
auto step = [&](Point3 point, VectorPixelType gradient, double multiplier) -> Point3 {
@@ -518,6 +566,10 @@ void compute_thickness(Mesh& mesh, Image& image, Image* dt, double max_dist, dou
518566

519567
d_mesh.write(distance_mesh);
520568
}
569+
570+
// compute inner mesh
571+
Mesh inner_mesh = compute_inner_mesh(mesh, "thickness");
572+
summarize_internal_intensities(mesh, inner_mesh, image);
521573
}
522574

523575
//---------------------------------------------------------------------------
@@ -564,4 +616,106 @@ Mesh compute_inner_mesh(const Mesh& mesh, std::string array_name) {
564616
return new_mesh;
565617
}
566618

619+
//---------------------------------------------------------------------------
620+
void summarize_internal_intensities(Mesh& outer_mesh, Mesh& inner_mesh, Image& image) {
621+
outer_mesh.computeNormals();
622+
inner_mesh.computeNormals();
623+
624+
vtkSmartPointer<vtkPolyData> outerMeshWithNormals = outer_mesh.getVTKMesh();
625+
vtkSmartPointer<vtkPolyData> innerMesh = inner_mesh.getVTKMesh();
626+
627+
// Get the normal array
628+
vtkDataArray* normalArray = outerMeshWithNormals->GetPointData()->GetNormals();
629+
630+
double spacing = image.get_minimum_spacing();
631+
double step_size = spacing / 10.0;
632+
633+
const unsigned long num_points = outerMeshWithNormals->GetNumberOfPoints();
634+
635+
// create arrays for min, max, mean
636+
auto values_min = vtkSmartPointer<vtkDoubleArray>::New();
637+
values_min->SetNumberOfComponents(1);
638+
values_min->SetNumberOfTuples(num_points);
639+
values_min->SetName("intensity_min");
640+
641+
auto values_max = vtkSmartPointer<vtkDoubleArray>::New();
642+
values_max->SetNumberOfComponents(1);
643+
values_max->SetNumberOfTuples(num_points);
644+
values_max->SetName("intensity_max");
645+
646+
auto values_mean = vtkSmartPointer<vtkDoubleArray>::New();
647+
values_mean->SetNumberOfComponents(1);
648+
values_mean->SetNumberOfTuples(num_points);
649+
values_mean->SetName("intensity_mean");
650+
651+
auto inner_dt = inner_mesh.toDistanceTransform(PhysicalRegion(), image.spacing(), {1, 1, 1});
652+
auto outer_dt = outer_mesh.toDistanceTransform(PhysicalRegion(), image.spacing(), {1, 1, 1});
653+
654+
// Iterate over each point in the outer mesh
655+
for (vtkIdType pointId = 0; pointId < outerMeshWithNormals->GetNumberOfPoints(); ++pointId) {
656+
// parallel loop over all points using TBB
657+
////tbb::parallel_for(tbb::blocked_range<size_t>{0, num_points}, [&](const tbb::blocked_range<size_t>& r) {
658+
////for (size_t i = r.begin(); i < r.end(); ++i) {
659+
// int pointId = i;
660+
661+
// Get the surface normal at this point
662+
double normal[3];
663+
normalArray->GetTuple(pointId, normal);
664+
665+
// Get the coordinates of the current point
666+
double point[3];
667+
outerMeshWithNormals->GetPoint(pointId, point);
668+
669+
// Move to the next voxel along the surface normal by the step_size
670+
for (int i = 0; i < 3; ++i) {
671+
point[i] -= normal[i] * step_size * 10;
672+
}
673+
674+
double max_value = 0;
675+
double min_value = std::numeric_limits<double>::max();
676+
double sum = 0;
677+
double count = 0;
678+
679+
while (outer_dt.evaluate(point) > 0) {
680+
if (inner_dt.isInside(point) && inner_dt.evaluate(point) > spacing && image.isInside(point)) {
681+
double value = image.evaluate(point);
682+
max_value = std::max<double>(max_value, value);
683+
min_value = std::min<double>(min_value, value);
684+
sum += value;
685+
count++;
686+
}
687+
688+
// Move to the next voxel along the surface normal by the step_size
689+
for (int i = 0; i < 3; ++i) {
690+
point[i] -= normal[i] * step_size;
691+
}
692+
}
693+
694+
if (count == 0) {
695+
min_value = max_value = sum = 0; // no points found
696+
count = 1;
697+
}
698+
699+
// Set the values in the output arrays
700+
values_min->SetValue(pointId, min_value);
701+
values_max->SetValue(pointId, max_value);
702+
values_mean->SetValue(pointId, sum / count);
703+
704+
// std::cout << "point " << pointId << " min " << min_value << " max " << max_value << " mean " << sum / count
705+
// << std::endl;
706+
}
707+
//});
708+
709+
outer_mesh.setField("intensity_min", values_min, Mesh::Point);
710+
outer_mesh.setField("intensity_max", values_max, Mesh::Point);
711+
outer_mesh.setField("intensity_mean", values_mean, Mesh::Point);
712+
713+
double average_edge_length = compute_average_edge_length(outer_mesh.getVTKMesh());
714+
715+
double median_radius = 5.0;
716+
median_smooth(outer_mesh.getVTKMesh(), "intensity_min", average_edge_length * median_radius);
717+
median_smooth(outer_mesh.getVTKMesh(), "intensity_max", average_edge_length * median_radius);
718+
median_smooth(outer_mesh.getVTKMesh(), "intensity_mean", average_edge_length * median_radius);
719+
}
720+
567721
} // namespace shapeworks::mesh

Libs/Mesh/MeshComputeThickness.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ namespace shapeworks::mesh {
99
void compute_thickness(Mesh &mesh, Image &image, Image *dt, double max_dist, double median_radius,
1010
std::string distance_mesh);
1111

12+
//! Compute an internal mesh based on the thickness values
1213
Mesh compute_inner_mesh(const Mesh &mesh, std::string array_name);
1314

15+
//! Summarize the internal intensities values of the area inside the inner mesh on the outer mesh
16+
void summarize_internal_intensities(Mesh &outer_mesh, Mesh &inner_mesh, Image &image);
17+
1418
} // namespace shapeworks::mesh
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
version https://git-lfs.github.com/spec/v1
2-
oid sha256:a38d49a33baf9186fc640e216a316f85e3dd180ff92d00ff6bb4a17c3683afca
3-
size 687238
2+
oid sha256:c98a3f196ecd875435294b2323d96d7624e6da879fe48db2a211db57430564b8
3+
size 743076

0 commit comments

Comments
 (0)