|
4 | 4 | #include <itkVectorLinearInterpolateImageFunction.h> |
5 | 5 | #include <tbb/parallel_for.h> |
6 | 6 | #include <vtkPointData.h> |
| 7 | +#include <vtkSelectEnclosedPoints.h> |
7 | 8 | #include <vtkStaticCellLocator.h> |
8 | 9 | #include <vtkStaticPointLocator.h> |
9 | 10 |
|
@@ -126,7 +127,9 @@ static std::vector<double> median_smooth_signal_intensities(std::vector<double> |
126 | 127 | for (int i = 0; i < intensities.size(); i++) { |
127 | 128 | std::vector<double> local_intensities; |
128 | 129 | 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 | + } |
130 | 133 | } |
131 | 134 | // compute median |
132 | 135 | std::sort(local_intensities.begin(), local_intensities.end()); |
@@ -174,6 +177,58 @@ static double get_distance_to_opposite_side(Mesh& mesh, int point_id) { |
174 | 177 | return distance; |
175 | 178 | } |
176 | 179 |
|
| 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 | + |
177 | 232 | //--------------------------------------------------------------------------- |
178 | 233 | double compute_thickness_from_signal(const std::vector<double>& intensities_input, double step_size) { |
179 | 234 | 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 |
358 | 413 | } |
359 | 414 | }; |
360 | 415 |
|
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(); |
369 | 417 | double step_size = spacing / 10.0; |
370 | 418 |
|
371 | 419 | 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 |
518 | 566 |
|
519 | 567 | d_mesh.write(distance_mesh); |
520 | 568 | } |
| 569 | + |
| 570 | + // compute inner mesh |
| 571 | + Mesh inner_mesh = compute_inner_mesh(mesh, "thickness"); |
| 572 | + summarize_internal_intensities(mesh, inner_mesh, image); |
521 | 573 | } |
522 | 574 |
|
523 | 575 | //--------------------------------------------------------------------------- |
@@ -564,4 +616,106 @@ Mesh compute_inner_mesh(const Mesh& mesh, std::string array_name) { |
564 | 616 | return new_mesh; |
565 | 617 | } |
566 | 618 |
|
| 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 | + |
567 | 721 | } // namespace shapeworks::mesh |
0 commit comments