multimodars 0.7.2__tar.gz → 0.7.4__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (162) hide show
  1. {multimodars-0.7.2 → multimodars-0.7.4}/CHANGELOG.md +68 -1
  2. {multimodars-0.7.2 → multimodars-0.7.4}/Cargo.lock +1 -1
  3. {multimodars-0.7.2 → multimodars-0.7.4}/Cargo.toml +1 -1
  4. {multimodars-0.7.2 → multimodars-0.7.4}/PKG-INFO +3 -3
  5. {multimodars-0.7.2 → multimodars-0.7.4}/README.md +2 -2
  6. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/_processing.py +63 -20
  7. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/__init__.py +19 -13
  8. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/stitching/boundary.py +337 -168
  9. multimodars-0.7.4/multimodars/ccta/stitching/core.py +459 -0
  10. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/io/read_geometrical.py +9 -25
  11. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/discretizing/resampling.rs +2 -11
  12. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/binding/align.rs +12 -3
  13. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/centerline_align/align.rs +20 -123
  14. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/centerline_align/align_algorithms.rs +94 -42
  15. multimodars-0.7.4/src/intravascular/centerline_align/preprocessing.rs +316 -0
  16. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/io/input.rs +6 -7
  17. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/native/centerline.rs +117 -32
  18. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/native/geometry.rs +38 -6
  19. multimodars-0.7.4/src/types/native.rs +111 -0
  20. {multimodars-0.7.2 → multimodars-0.7.4}/tests/test_ccta.py +369 -29
  21. multimodars-0.7.4/uv.lock +4004 -0
  22. multimodars-0.7.2/.claude/debug_meshes/stitched_after_fix.stl +0 -0
  23. multimodars-0.7.2/multimodars/ccta/stitching/core.py +0 -366
  24. multimodars-0.7.2/src/intravascular/centerline_align/preprocessing.rs +0 -605
  25. multimodars-0.7.2/src/types/native.rs +0 -52
  26. {multimodars-0.7.2 → multimodars-0.7.4}/.gitattributes +0 -0
  27. {multimodars-0.7.2 → multimodars-0.7.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  28. {multimodars-0.7.2 → multimodars-0.7.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  29. {multimodars-0.7.2 → multimodars-0.7.4}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  30. {multimodars-0.7.2 → multimodars-0.7.4}/.github/workflows/CI.yml +0 -0
  31. {multimodars-0.7.2 → multimodars-0.7.4}/.gitignore +0 -0
  32. {multimodars-0.7.2 → multimodars-0.7.4}/.pre-commit-config.yaml +0 -0
  33. {multimodars-0.7.2 → multimodars-0.7.4}/.readthedocs.yaml +0 -0
  34. {multimodars-0.7.2 → multimodars-0.7.4}/CITATION.cff +0 -0
  35. {multimodars-0.7.2 → multimodars-0.7.4}/CONTRIBUTING.md +0 -0
  36. {multimodars-0.7.2 → multimodars-0.7.4}/LICENSE +0 -0
  37. {multimodars-0.7.2 → multimodars-0.7.4}/ROADMAP.MD +0 -0
  38. {multimodars-0.7.2 → multimodars-0.7.4}/benchmarks/BENCHMARK_PLAN.md +0 -0
  39. {multimodars-0.7.2 → multimodars-0.7.4}/benchmarks/benchmark_bruteforce_stepsize.py +0 -0
  40. {multimodars-0.7.2 → multimodars-0.7.4}/benchmarks/benchmark_cpu_scaling.py +0 -0
  41. {multimodars-0.7.2 → multimodars-0.7.4}/benchmarks/results/bruteforce_stepsize.png +0 -0
  42. {multimodars-0.7.2 → multimodars-0.7.4}/benchmarks/results/cpu_scaling.png +0 -0
  43. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/branch_diastolic_contours.csv +0 -0
  44. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/branch_systolic_contours.csv +0 -0
  45. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/calcium_diastolic_contours.csv +0 -0
  46. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/calcium_systolic_contours.csv +0 -0
  47. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/diastolic_contours.csv +0 -0
  48. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/diastolic_reference_points.csv +0 -0
  49. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/eem_diastolic_contours.csv +0 -0
  50. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/eem_systolic_contours.csv +0 -0
  51. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/systolic_contours.csv +0 -0
  52. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/idealized_geometry/systolic_reference_points.csv +0 -0
  53. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/branch_diastolic_contours.csv +0 -0
  54. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/branch_systolic_contours.csv +0 -0
  55. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/calcium_diastolic_contours.csv +0 -0
  56. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/calcium_systolic_contours.csv +0 -0
  57. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/diastolic_contours.csv +0 -0
  58. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/diastolic_reference_points.csv +0 -0
  59. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/eem_diastolic_contours.csv +0 -0
  60. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/eem_systolic_contours.csv +0 -0
  61. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/systolic_contours.csv +0 -0
  62. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_full/systolic_reference_points.csv +0 -0
  63. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_rest/combined_sorted_manual.csv +0 -0
  64. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_rest/diastolic_contours.csv +0 -0
  65. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_rest/diastolic_reference_points.csv +0 -0
  66. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_rest/systolic_contours.csv +0 -0
  67. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_rest/systolic_reference_points.csv +0 -0
  68. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_stress/combined_sorted_manual.csv +0 -0
  69. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_stress/diastolic_contours.csv +0 -0
  70. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_stress/diastolic_reference_points.csv +0 -0
  71. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_stress/systolic_contours.csv +0 -0
  72. {multimodars-0.7.2 → multimodars-0.7.4}/data/fixtures/ivus_stress/systolic_reference_points.csv +0 -0
  73. {multimodars-0.7.2 → multimodars-0.7.4}/media/coronary_artery_disease.jpg +0 -0
  74. {multimodars-0.7.2 → multimodars-0.7.4}/media/dynamic_lumen_changes.png +0 -0
  75. {multimodars-0.7.2 → multimodars-0.7.4}/media/multimoda-rs.jpg +0 -0
  76. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/__init__.py +0 -0
  77. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/_converters.py +0 -0
  78. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/centerline_prep.py +0 -0
  79. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/debug_plots.py +0 -0
  80. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/discretization_map.py +0 -0
  81. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/labeling/__init__.py +0 -0
  82. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/labeling/core.py +0 -0
  83. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/labeling/helpers.py +0 -0
  84. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/mesh_regions.py +0 -0
  85. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/postprocessing.py +0 -0
  86. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/scaling.py +0 -0
  87. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/scaling_helpers.py +0 -0
  88. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/stitching/__init__.py +0 -0
  89. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/ccta/stitching/helpers.py +0 -0
  90. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/io/__init__.py +0 -0
  91. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/io/write_geometries.py +0 -0
  92. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/multimodars.pyi +0 -0
  93. {multimodars-0.7.2 → multimodars-0.7.4}/multimodars/py.typed +0 -0
  94. {multimodars-0.7.2 → multimodars-0.7.4}/paper/REVIEWER1.md +0 -0
  95. {multimodars-0.7.2 → multimodars-0.7.4}/paper/REVIEWER2.md +0 -0
  96. {multimodars-0.7.2 → multimodars-0.7.4}/paper/bibliography.bib +0 -0
  97. {multimodars-0.7.2 → multimodars-0.7.4}/paper/figures/Figure1.jpg +0 -0
  98. {multimodars-0.7.2 → multimodars-0.7.4}/paper/figures/Figure2.jpg +0 -0
  99. {multimodars-0.7.2 → multimodars-0.7.4}/paper/figures/Figure3.png +0 -0
  100. {multimodars-0.7.2 → multimodars-0.7.4}/paper/figures/Figure4.jpg +0 -0
  101. {multimodars-0.7.2 → multimodars-0.7.4}/paper/paper.md +0 -0
  102. {multimodars-0.7.2 → multimodars-0.7.4}/pyproject.toml +0 -0
  103. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/adjust_mesh/label_coronary.rs +0 -0
  104. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/adjust_mesh/scale_coronary.rs +0 -0
  105. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/adjust_mesh.rs +0 -0
  106. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/binding/ccta_py.rs +0 -0
  107. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/binding.rs +0 -0
  108. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/discretizing/plan.md +0 -0
  109. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/discretizing/projecting.rs +0 -0
  110. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/discretizing/vessel_tree.rs +0 -0
  111. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta/discretizing.rs +0 -0
  112. {multimodars-0.7.2 → multimodars-0.7.4}/src/ccta.rs +0 -0
  113. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/binding/entry.rs +0 -0
  114. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/binding/functions.rs +0 -0
  115. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/binding.rs +0 -0
  116. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/centerline_align.rs +0 -0
  117. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/io/build.rs +0 -0
  118. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/io/integrity_check.rs +0 -0
  119. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/io/output.rs +0 -0
  120. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/io.rs +0 -0
  121. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/processing/align_between.rs +0 -0
  122. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/processing/align_within.rs +0 -0
  123. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/processing/postprocessing.rs +0 -0
  124. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/processing/preprocessing.rs +0 -0
  125. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/processing/process_utils.rs +0 -0
  126. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/processing/wall.rs +0 -0
  127. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/processing.rs +0 -0
  128. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/to_object/interpolation.rs +0 -0
  129. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/to_object/process.rs +0 -0
  130. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/to_object/texture.rs +0 -0
  131. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/to_object/write_mtl.rs +0 -0
  132. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/to_object.rs +0 -0
  133. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/utils/general_utils.rs +0 -0
  134. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/utils/test_utils.rs +0 -0
  135. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular/utils.rs +0 -0
  136. {multimodars-0.7.2 → multimodars-0.7.4}/src/intravascular.rs +0 -0
  137. {multimodars-0.7.2 → multimodars-0.7.4}/src/lib.rs +0 -0
  138. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_centerline.rs +0 -0
  139. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_centerline_point.rs +0 -0
  140. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_contour.rs +0 -0
  141. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_contour_point.rs +0 -0
  142. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_discretized_vessel_tree.rs +0 -0
  143. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_frame.rs +0 -0
  144. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_geometry.rs +0 -0
  145. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_geometry_pair.rs +0 -0
  146. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_input_data.rs +0 -0
  147. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding/py_record.rs +0 -0
  148. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/binding.rs +0 -0
  149. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/native/centerline_point.rs +0 -0
  150. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/native/contour.rs +0 -0
  151. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/native/contour_point.rs +0 -0
  152. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/native/discretized_tree.rs +0 -0
  153. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/native/frame.rs +0 -0
  154. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/native/geometry_pair.rs +0 -0
  155. {multimodars-0.7.2 → multimodars-0.7.4}/src/types/native/record.rs +0 -0
  156. {multimodars-0.7.2 → multimodars-0.7.4}/src/types.rs +0 -0
  157. {multimodars-0.7.2 → multimodars-0.7.4}/tests/conftest.py +0 -0
  158. {multimodars-0.7.2 → multimodars-0.7.4}/tests/test_converters.py +0 -0
  159. {multimodars-0.7.2 → multimodars-0.7.4}/tests/test_core.py +0 -0
  160. {multimodars-0.7.2 → multimodars-0.7.4}/tests/test_intravascular.py +0 -0
  161. {multimodars-0.7.2 → multimodars-0.7.4}/tests/test_wrappers.py +0 -0
  162. {multimodars-0.7.2 → multimodars-0.7.4}/tests/tests_todo.md +0 -0
@@ -3,6 +3,73 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  This project adheres to [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.7.4] - 2026-10-02
7
+
8
+ ### Fixed
9
+ - Alignment: the reference point is found on the full-resolution centerline and the resampling
10
+ is anchored on it, instead of snapping to the nearest frame-spaced sample (up to half a spacing).
11
+ - Alignment: the reference frame, not frame 0, is placed on the reference point, matching the
12
+ rotation search. Frames off either end of the centerline no longer shift the rest.
13
+ - `find_ref_frame_idx` returns the frame's position instead of its `id`.
14
+ - Alignment: centerlines given in ascending z kept backwards tangents after being reversed, so
15
+ frames were rotated to face against the centerline.
16
+ - Alignment: the resampled centerline no longer drops up to one spacing at its distal end.
17
+ - `Centerline.resample` no longer adds a near-duplicate point before the end through float drift.
18
+
19
+ ### Internal
20
+ - Alignment preprocessing uses `Centerline::resample_anchored` instead of its own copy. Shared
21
+ `cumulative_arc_length` / `mean_spacing` helpers replace the hand-written versions.
22
+
23
+ ## [0.7.3] - 2026-09-28
24
+ Smoother anomalous ostium. The two halves of the ostial ring now meet in smooth arcs instead of
25
+ sharp kinks, the ring's correction is faded into the aortic mesh all the way round, and the fillet
26
+ only rounds the aorta-facing half - towards the aorta rather than back into the vessel. On
27
+ `examples/data` the raw stitched mesh goes from 220 to 81 self-intersecting faces at the ostium
28
+ (235 to 47 after `fix_and_remesh_stitched_mesh`). Still to do: most of the rest sit at one tip of
29
+ the slit-like ostium, where the aortic wall touches the first millimetres of the intravascular tube.
30
+
31
+ ### Added
32
+ - `stitch_ccta_to_intravascular` gained `seam_points_a` / `seam_points_b` (default 2 / 4). At both
33
+ seams where the ostium's aorta-facing Half A meets its coronary-facing Half B, that many Half A /
34
+ Half B points are replaced by a smooth arc that follows each half's own direction. Everything
35
+ outside those ranges stays exactly in place, so Half A's mid-section keeps its wall-thickness
36
+ offset from the intravascular ostium. The counts are points on the CCTA ring as cut from the
37
+ mesh, before it is densified; more than a half can give are reduced with a warning. Only used
38
+ with `prox_start_mode="highest_z"` and `proximal_is_ostium=True`.
39
+
40
+ ### Changed
41
+ - `fillet_bulge` now only rounds the ostium's Half A, fading out along the seam arcs; Half B and
42
+ the distal seam stay direct strips (0.7.2 rounded every strut of both seams). It needs
43
+ `prox_start_mode="highest_z"` with `proximal_is_ostium=True`, otherwise a warning is printed and
44
+ the direct strip is used.
45
+ - The two-half ostium's IV-plane clamp now runs before the seam arcs are built, so it cannot
46
+ flatten them back into a kink. What triggers it (ring and IV plane at least 45° apart) is
47
+ unchanged.
48
+
49
+ ### Fixed
50
+ - The fillet's arcs bulged away from the combined centroid of both rings, which on an anomalous
51
+ ostium sent many of them back into the vessel (47 of 100 on `examples/data`). They now bulge
52
+ away from the intravascular geometry, along the ostial frame's outward normal.
53
+ - Only Half A's displacement was faded into the surrounding aortic mesh, so the faces behind
54
+ Half B's moved rim folded through the intravascular tube. The whole ring's displacement is now
55
+ faded in.
56
+ - A zero-length strut no longer misaligns the fillet's intermediate rings.
57
+ - `stitch` (the one-call wrapper) failed with its default regions: it removed them with
58
+ `target_boundaries=1`, merging the two rims into one ring, which stitching then rejected. It now
59
+ keeps both rims.
60
+ - `read_mesh` no longer falls back to face-cleanup methods that trimesh 4 removed; they could only
61
+ hide an error.
62
+
63
+ ### Internal
64
+ - `_redistribute_ring_evenly`'s arc-length sampling moved into `_even_arclength_samples`, so
65
+ per-point values (the Half A weights) resample with the points.
66
+ - `_condition_ostium_ring_two_half` and `_prepare_prox_dist_boundary_pts` also return the Half A
67
+ weights; `_carry_ring_weights` keeps them with their points through densification, start-point
68
+ rotation and winding fixes.
69
+ - `_bulge_arc_points` is replaced by the vectorised `_fillet_arc_layers`.
70
+ `_taper_ring_displacement` lost `protected_pts` / `seam_damping`, which fading the whole ring
71
+ no longer needs.
72
+
6
73
  ## [0.7.2] - 2026-09-19
7
74
  More anatomical approach in creating the anomalous ostium. Takes the wall measurement to create
8
75
  and offset. This ensures that the ostium is represented like the ostium from the intravascular
@@ -162,7 +229,7 @@ kind of a dome on top of this plane.
162
229
  ### Fixed
163
230
  - `align_manual` no explicitly states `rotation_angle_deg` instead of `rotation_angle`
164
231
 
165
- ## Changed
232
+ ### Changed
166
233
  - `align_manual`, `align_combined` and `align_three_points` all return their rotation angle now.
167
234
 
168
235
  ## [0.5.4] - 2026-08-03 Hotfix
@@ -696,7 +696,7 @@ dependencies = [
696
696
 
697
697
  [[package]]
698
698
  name = "multimodars"
699
- version = "0.7.2"
699
+ version = "0.7.4"
700
700
  dependencies = [
701
701
  "anyhow",
702
702
  "approx",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "multimodars"
3
- version = "0.7.2"
3
+ version = "0.7.4"
4
4
  edition = "2021"
5
5
  license = "MIT"
6
6
  readme = "README.md"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: multimodars
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -36,7 +36,7 @@ Project-URL: Issues, https://github.com/yungselm/multimoda-rs/issues
36
36
  > Brace for breaking changes!
37
37
 
38
38
  [![PyPI](https://img.shields.io/pypi/v/multimodars.svg)](https://pypi.org/project/multimodars)
39
- [![Downloads](https://static.pepy.tech/badge/multimodars)](https://pepy.tech/project/multimodars)
39
+ <!-- [![Downloads](https://static.pepy.tech/badge/multimodars)](https://pepy.tech/project/multimodars) -->
40
40
  [![License](https://img.shields.io/pypi/l/multimodars.svg)](LICENSE)
41
41
  [![Docs](https://img.shields.io/readthedocs/multimoda-rs)](https://multimoda-rs.readthedocs.io)
42
42
  [![Tests and Build](https://github.com/yungselm/multimoda-rs/actions/workflows/CI.yml/badge.svg)](https://github.com/yungselm/multimoda-rs/actions/workflows/CI.yml)
@@ -104,7 +104,7 @@ rest, stress, dia, sys, _ = mm.from_file_full(
104
104
  # Align geometry onto a CCTA-derived centerline
105
105
  cl_raw = np.genfromtxt("examples/data/centerline_raw.csv", delimiter=",")
106
106
  centerline = mm.numpy_to_centerline(cl_raw)
107
- aligned_pair, cl_resampled, total_rotation_deg = mm.align_three_point(
107
+ aligned_pair, spacing_mm, total_rotation_deg = mm.align_three_point(
108
108
  centerline, rest,
109
109
  main_ref_pt=(12.2605, -201.3643, 1751.0554),
110
110
  counterclockwise_ref_pt=(11.7567, -202.1920, 1754.7975),
@@ -12,7 +12,7 @@
12
12
  > Brace for breaking changes!
13
13
 
14
14
  [![PyPI](https://img.shields.io/pypi/v/multimodars.svg)](https://pypi.org/project/multimodars)
15
- [![Downloads](https://static.pepy.tech/badge/multimodars)](https://pepy.tech/project/multimodars)
15
+ <!-- [![Downloads](https://static.pepy.tech/badge/multimodars)](https://pepy.tech/project/multimodars) -->
16
16
  [![License](https://img.shields.io/pypi/l/multimodars.svg)](LICENSE)
17
17
  [![Docs](https://img.shields.io/readthedocs/multimoda-rs)](https://multimoda-rs.readthedocs.io)
18
18
  [![Tests and Build](https://github.com/yungselm/multimoda-rs/actions/workflows/CI.yml/badge.svg)](https://github.com/yungselm/multimoda-rs/actions/workflows/CI.yml)
@@ -80,7 +80,7 @@ rest, stress, dia, sys, _ = mm.from_file_full(
80
80
  # Align geometry onto a CCTA-derived centerline
81
81
  cl_raw = np.genfromtxt("examples/data/centerline_raw.csv", delimiter=",")
82
82
  centerline = mm.numpy_to_centerline(cl_raw)
83
- aligned_pair, cl_resampled, total_rotation_deg = mm.align_three_point(
83
+ aligned_pair, spacing_mm, total_rotation_deg = mm.align_three_point(
84
84
  centerline, rest,
85
85
  main_ref_pt=(12.2605, -201.3643, 1751.0554),
86
86
  counterclockwise_ref_pt=(11.7567, -202.1920, 1754.7975),
@@ -1,31 +1,65 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from .multimodars import (
4
+ PyCenterline,
4
5
  PyContour,
5
6
  PyContourType,
7
+ PyDiscretizedVesselTree, # noqa: F401 — re-exported for type annotations
6
8
  PyGeometry,
7
9
  PyGeometryPair,
8
10
  PyInputData,
9
- PyCenterline,
10
- PyDiscretizedVesselTree, # noqa: F401 — re-exported for type annotations
11
- from_file_full as _from_file_full,
12
- from_file_doublepair as _from_file_doublepair,
13
- from_file_singlepair as _from_file_singlepair,
14
- from_file_single as _from_file_single,
15
- from_array_full as _from_array_full,
16
- from_array_doublepair as _from_array_doublepair,
17
- from_array_singlepair as _from_array_singlepair,
18
- from_array_single as _from_array_single,
19
- align_three_point as _align_three_point,
20
- align_manual as _align_manual,
11
+ )
12
+ from .multimodars import (
21
13
  align_combined as _align_combined,
22
- to_obj as _to_obj,
23
- read_centerline_vtp as _read_centerline_vtp,
24
- find_centerline_bounded_points_simple as _find_centerline_bounded_points_simple,
25
- find_proximal_distal_scaling as _find_proximal_distal_scaling,
14
+ )
15
+ from .multimodars import (
16
+ align_manual as _align_manual,
17
+ )
18
+ from .multimodars import (
19
+ align_three_point as _align_three_point,
20
+ )
21
+ from .multimodars import (
26
22
  build_adjacency_map as _build_adjacency_map,
23
+ )
24
+ from .multimodars import (
27
25
  discretize_vessel as _discretize_vessel,
28
26
  )
27
+ from .multimodars import (
28
+ find_centerline_bounded_points_simple as _find_centerline_bounded_points_simple,
29
+ )
30
+ from .multimodars import (
31
+ find_proximal_distal_scaling as _find_proximal_distal_scaling,
32
+ )
33
+ from .multimodars import (
34
+ from_array_doublepair as _from_array_doublepair,
35
+ )
36
+ from .multimodars import (
37
+ from_array_full as _from_array_full,
38
+ )
39
+ from .multimodars import (
40
+ from_array_single as _from_array_single,
41
+ )
42
+ from .multimodars import (
43
+ from_array_singlepair as _from_array_singlepair,
44
+ )
45
+ from .multimodars import (
46
+ from_file_doublepair as _from_file_doublepair,
47
+ )
48
+ from .multimodars import (
49
+ from_file_full as _from_file_full,
50
+ )
51
+ from .multimodars import (
52
+ from_file_single as _from_file_single,
53
+ )
54
+ from .multimodars import (
55
+ from_file_singlepair as _from_file_singlepair,
56
+ )
57
+ from .multimodars import (
58
+ read_centerline_vtp as _read_centerline_vtp,
59
+ )
60
+ from .multimodars import (
61
+ to_obj as _to_obj,
62
+ )
29
63
 
30
64
  _AlignLog = list[tuple[int, int, float, float, float, float, float]]
31
65
 
@@ -1035,7 +1069,10 @@ def align_three_point(
1035
1069
  geometry : PyGeometryPair or PyGeometry
1036
1070
  Single geometry or diastolic/systolic geometry pair to align.
1037
1071
  main_ref_pt : tuple of float
1038
- ``(x, y, z)`` reference point at the aortic ostium.
1072
+ ``(x, y, z)`` reference point at the aortic ostium. The geometry's reference
1073
+ frame (the first frame with a reference point) is placed on the closest
1074
+ centerline point; frames that do not fit on the centerline are left
1075
+ unaligned and reported in a warning.
1039
1076
  counterclockwise_ref_pt : tuple of float
1040
1077
  ``(x, y, z)`` counterclockwise reference point (viewed proximal → distal).
1041
1078
  clockwise_ref_pt : tuple of float
@@ -1131,7 +1168,10 @@ def align_manual(
1131
1168
  rotation_angle_deg : float
1132
1169
  Rotation angle in degrees to apply.
1133
1170
  ref_point : tuple of float
1134
- ``(x, y, z)`` reference point on the centerline.
1171
+ ``(x, y, z)`` reference point on the centerline. The geometry's reference
1172
+ frame (the first frame with a reference point, else frame 0) is placed on
1173
+ the closest centerline point; frames that do not fit on the centerline are
1174
+ left unaligned and reported in a warning.
1135
1175
  write : bool, optional
1136
1176
  Whether to write the aligned meshes to OBJ files. Default is ``False``.
1137
1177
  watertight : bool, optional
@@ -1219,7 +1259,10 @@ def align_combined(
1219
1259
  geometry : PyGeometryPair or PyGeometry
1220
1260
  Single geometry or diastolic/systolic geometry pair to align.
1221
1261
  main_ref_pt : tuple of float
1222
- ``(x, y, z)`` reference point at the aortic ostium.
1262
+ ``(x, y, z)`` reference point at the aortic ostium. The geometry's reference
1263
+ frame (the first frame with a reference point) is placed on the closest
1264
+ centerline point; frames that do not fit on the centerline are left
1265
+ unaligned and reported in a warning.
1223
1266
  counterclockwise_ref_pt : tuple of float
1224
1267
  ``(x, y, z)`` counterclockwise reference point (viewed proximal → distal).
1225
1268
  clockwise_ref_pt : tuple of float
@@ -1505,7 +1548,7 @@ def build_adjacency_map(
1505
1548
 
1506
1549
 
1507
1550
  def discretize_vessel(
1508
- centerline: "PyCenterline",
1551
+ centerline: PyCenterline,
1509
1552
  points: list[tuple[float, float, float]],
1510
1553
  branch_id: int = 0,
1511
1554
  step_size: float = 0.5,
@@ -1,19 +1,15 @@
1
1
  from __future__ import annotations
2
2
 
3
- from . import stitching
4
- from . import scaling
5
- from . import labeling
6
- from . import centerline_prep
7
- from . import debug_plots as debug_plots
8
- from . import mesh_regions
9
- from . import postprocessing as _postprocessing
10
-
11
3
  from pathlib import Path
12
4
  from typing import TYPE_CHECKING
5
+
13
6
  import numpy as np
14
7
  import trimesh
15
8
 
16
9
  from ..io.write_geometries import export_section_stl as export_section_stl
10
+ from . import centerline_prep, labeling, mesh_regions, scaling, stitching
11
+ from . import debug_plots as debug_plots
12
+ from . import postprocessing as _postprocessing
17
13
 
18
14
  if TYPE_CHECKING:
19
15
  from ..multimodars import PyCenterline, PyFrame, PyGeometry
@@ -264,7 +260,7 @@ def scale(
264
260
  def stitch(
265
261
  results: dict,
266
262
  geometry: PyGeometry,
267
- region_remove: list[str] | str = ["anomalous_points", "proximal_points"],
263
+ region_remove: list[str] | str | None = None,
268
264
  prox_start_mode: str = "highest_z",
269
265
  dist_start_mode: str = "nearest_iv",
270
266
  ) -> dict:
@@ -291,12 +287,18 @@ def stitch(
291
287
  geometry : PyGeometry
292
288
  Intravascular imaging geometry whose contours define the vessel lumen
293
289
  used as the stitching target.
290
+ region_remove : str or list of str, optional
291
+ Labeled regions cut out of the CCTA mesh before stitching (default
292
+ ``["anomalous_points", "proximal_points"]``).
294
293
  prox_start_mode : str, optional
295
294
  How to choose index 0 of the proximal boundary ring before stitching.
296
- ``"nearest_iv"`` (default) rotates to the point closest to IV point 0;
297
- ``"highest_z"`` rotates to the point with the largest z-coordinate.
295
+ ``"highest_z"`` (default here) rotates to the point with the largest
296
+ z-coordinate and treats the ostium as two halves (see
297
+ :func:`~multimodars.ccta.stitching.stitch_ccta_to_intravascular`);
298
+ ``"nearest_iv"`` rotates to the point closest to IV point 0.
298
299
  dist_start_mode : str, optional
299
- Same as *prox_start_mode* but for the distal boundary ring.
300
+ Same as *prox_start_mode* but for the distal boundary ring (default
301
+ ``"nearest_iv"``).
300
302
 
301
303
  Returns
302
304
  -------
@@ -304,8 +306,12 @@ def stitch(
304
306
  Stitched results dictionary with the same structure as *results*, where
305
307
  ``"mesh"`` is the stitched, hole-filled surface.
306
308
  """
309
+ if region_remove is None:
310
+ region_remove = ["anomalous_points", "proximal_points"]
311
+ # Cutting a segment out of the vessel opens a rim at each end, and the
312
+ # stitch needs both as separate rings.
307
313
  updated_results = mesh_regions.remove_labeled_points_from_mesh(
308
- results, region_remove
314
+ results, region_remove, target_boundaries=2
309
315
  )
310
316
 
311
317
  stitched = stitching.stitch_ccta_to_intravascular(