multimodars 0.3.2__tar.gz → 0.3.5__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.
- multimodars-0.3.5/.gitattributes +9 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/.gitignore +2 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/CHANGELOG.md +33 -4
- multimodars-0.3.5/CITATION.cff +82 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/Cargo.lock +1 -1
- {multimodars-0.3.2 → multimodars-0.3.5}/Cargo.toml +1 -1
- {multimodars-0.3.2 → multimodars-0.3.5}/PKG-INFO +3 -3
- {multimodars-0.3.2 → multimodars-0.3.5}/README.md +2 -2
- multimodars-0.3.5/ROADMAP.MD +39 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/benchmarks/BENCHMARK_PLAN.md +8 -8
- {multimodars-0.3.2 → multimodars-0.3.5}/benchmarks/benchmark_bruteforce_stepsize.py +10 -10
- {multimodars-0.3.2 → multimodars-0.3.5}/benchmarks/benchmark_cpu_scaling.py +7 -6
- multimodars-0.3.5/benchmarks/results/bruteforce_stepsize.png +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/_converters.py +5 -1
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/_processing.py +42 -42
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/ccta/debug_plots.py +26 -5
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/ccta/manipulating.py +276 -10
- multimodars-0.3.5/multimodars/io/__init__.py +2 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/io/read_geometrical.py +5 -2
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/io/write_geometries.py +6 -5
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/multimodars.pyi +6 -6
- multimodars-0.3.5/paper/REVIEWER2.md +48 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/paper/bibliography.bib +48 -12
- multimodars-0.3.5/paper/paper.md +126 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/pyproject.toml +1 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/ccta/adjust_mesh/label_coronary.rs +52 -45
- {multimodars-0.3.2 → multimodars-0.3.5}/src/ccta/binding/ccta_py.rs +0 -1
- multimodars-0.3.5/src/intravascular/binding/align.rs +397 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/binding/classes.rs +2 -3
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/binding/entry.rs +8 -11
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/binding/mod.rs +145 -34
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/centerline_align/align_algorithms.rs +75 -62
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/centerline_align/mod.rs +139 -94
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/io/geometry.rs +10 -12
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/io/input.rs +11 -16
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/io/integrity_check.rs +3 -8
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/io/mod.rs +3 -8
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/io/output.rs +13 -16
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/processing/align_between.rs +2 -4
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/processing/align_within.rs +4 -4
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/processing/postprocessing.rs +1 -1
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/processing/preprocessing.rs +3 -7
- multimodars-0.3.5/src/intravascular/to_object/mod.rs +124 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/to_object/write_mtl.rs +15 -24
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/utils/general_utils.rs +3 -3
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/utils/test_utils.rs +1 -1
- {multimodars-0.3.2 → multimodars-0.3.5}/tests/test_ccta.py +406 -21
- {multimodars-0.3.2 → multimodars-0.3.5}/tests/test_converters.py +3 -5
- {multimodars-0.3.2 → multimodars-0.3.5}/tests/test_core.py +0 -3
- multimodars-0.3.5/tests/test_intravascular.py +256 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/tests/test_wrappers.py +0 -1
- multimodars-0.3.2/ROADMAP.MD +0 -93
- multimodars-0.3.2/benchmarks/results/bruteforce_stepsize.png +0 -0
- multimodars-0.3.2/multimodars/io/__init__.py +0 -2
- multimodars-0.3.2/paper/paper.md +0 -128
- multimodars-0.3.2/src/intravascular/binding/align.rs +0 -335
- multimodars-0.3.2/src/intravascular/to_object/mod.rs +0 -66
- {multimodars-0.3.2 → multimodars-0.3.5}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/.github/workflows/CI.yml +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/.github/workflows/draft-pdf.yml +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/.pre-commit-config.yaml +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/.readthedocs.yaml +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/CONTRIBUTING.md +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/LICENSE +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/benchmarks/results/cpu_scaling.png +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/branch_diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/branch_systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/calcium_diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/calcium_systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/diastolic_reference_points.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/eem_diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/eem_systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/idealized_geometry/systolic_reference_points.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/branch_diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/branch_systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/calcium_diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/calcium_systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/diastolic_reference_points.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/eem_diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/eem_systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_full/systolic_reference_points.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_rest/combined_sorted_manual.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_rest/diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_rest/diastolic_reference_points.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_rest/systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_rest/systolic_reference_points.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_stress/combined_sorted_manual.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_stress/diastolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_stress/diastolic_reference_points.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_stress/systolic_contours.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/data/fixtures/ivus_stress/systolic_reference_points.csv +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/media/coronary_artery_disease.jpg +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/media/dynamic_lumen_changes.png +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/media/multimoda-rs.jpg +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/__init__.py +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/ccta/__init__.py +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/ccta/fixing_functions.py +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/ccta/labeling.py +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/multimodars/py.typed +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/paper/REVIEWER1.md +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/paper/figures/Figure1.jpg +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/paper/figures/Figure2.jpg +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/paper/figures/Figure3.png +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/paper/figures/Figure4.jpg +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/ccta/adjust_mesh/mod.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/ccta/adjust_mesh/scale_coronary.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/ccta/binding/mod.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/ccta/mod.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/centerline_align/preprocessing.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/mod.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/processing/mod.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/processing/process_utils.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/processing/wall.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/to_object/interpolation.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/to_object/texture.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/intravascular/utils/mod.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/src/lib.rs +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/tests/conftest.py +0 -0
- {multimodars-0.3.2 → multimodars-0.3.5}/tests/tests_todo.md +0 -0
|
@@ -3,6 +3,35 @@
|
|
|
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.3.5] - 2026-05-15
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- `clamp_overshoot` parameter in `stitch_ccta_to_intravascular` (default 0.5 mm): for anomalous
|
|
10
|
+
ostia where the boundary-ring plane and the IV plane diverge by ≥ 45°, every proximal boundary
|
|
11
|
+
point is projected onto the IV plane and then pushed at least `clamp_overshoot` mm away from it,
|
|
12
|
+
creating an inward step that softens the stitching angle.
|
|
13
|
+
- `_enforce_layer_gap_from_plane`: the two mesh rings adjacent to the clamped boundary are
|
|
14
|
+
pushed radially outward within the IV plane (ring 1: 0.1 mm, ring 2: 0.2 mm) to eliminate
|
|
15
|
+
ridges at the clamping zone.
|
|
16
|
+
- Tests for `_clamp_to_plane`, `_enforce_layer_gap_from_plane`, and `_prepare_prox_dist_boundary_pts`
|
|
17
|
+
with idealized geometries (`test_ccta.py`, 17 tests).
|
|
18
|
+
|
|
19
|
+
### Performance
|
|
20
|
+
- Ray-intersection labeling step parallelized via `par_iter()`, reducing wall time on large meshes.
|
|
21
|
+
|
|
22
|
+
## [0.3.4] - 2026-05-13
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- `align_three_point`, `align_manual` and `align_combined` are now all generic, taking either `PyGeometryPair` or `PyGeometry` as input
|
|
26
|
+
- Test file added `test_intravascular.py` to demonstrate that same results with both
|
|
27
|
+
|
|
28
|
+
## [0.3.3] - 2026-05-06 Release JOSS paper
|
|
29
|
+
|
|
30
|
+
### Added
|
|
31
|
+
- `CITATION.cff` with full author list, ORCIDs, and preferred citation for JOSS/Zenodo archival.
|
|
32
|
+
- Zenodo DOI badge added to README.
|
|
33
|
+
- Created a release coupled to JOSS publication
|
|
34
|
+
|
|
6
35
|
## [0.3.2] - 2026-05-05
|
|
7
36
|
|
|
8
37
|
### Added
|
|
@@ -14,14 +43,14 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
14
43
|
|
|
15
44
|
### Changed
|
|
16
45
|
- `paper/paper.md` Performance section updated with quantified benchmark results and
|
|
17
|
-
corrected
|
|
46
|
+
corrected parallelization description.
|
|
18
47
|
|
|
19
48
|
### Performance
|
|
20
|
-
- Intra-pullback alignment (`search_range`) now
|
|
49
|
+
- Intra-pullback alignment (`search_range`) now parallelizes over candidate rotation
|
|
21
50
|
angles via `par_iter` instead of the point-rotation loop. The inner cost-function
|
|
22
51
|
loop is now sequential. This provides enough rayon tasks per frame step to saturate
|
|
23
|
-
all cores: bruteforce scales 6.5× and
|
|
24
|
-
~1.4× for both). Combined algorithmic +
|
|
52
|
+
all cores: bruteforce scales 6.5× and optimized 4.2× from 2 to 16 cores (previously
|
|
53
|
+
~1.4× for both). Combined algorithmic + parallelization gain reaches **38.5×** vs.
|
|
25
54
|
brute-force at 2 cores.
|
|
26
55
|
|
|
27
56
|
## [0.3.1] - 2026-05-04
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it using the metadata below."
|
|
3
|
+
title: "multimodars: A Rust-powered toolkit for multi-modality cardiac image fusion and registration"
|
|
4
|
+
version: 0.3.2
|
|
5
|
+
date-released: 2026-05-05
|
|
6
|
+
license: MIT
|
|
7
|
+
repository-code: "https://github.com/yungselm/multimoda-rs"
|
|
8
|
+
url: "https://multimoda-rs.readthedocs.io"
|
|
9
|
+
|
|
10
|
+
authors:
|
|
11
|
+
- family-names: Stark
|
|
12
|
+
given-names: Anselm W.
|
|
13
|
+
orcid: "https://orcid.org/0000-0002-5861-3753"
|
|
14
|
+
affiliation: "Department of Cardiology, Inselspital, Bern University Hospital, University of Bern, Switzerland"
|
|
15
|
+
- family-names: Ilic
|
|
16
|
+
given-names: Marc
|
|
17
|
+
orcid: "https://orcid.org/0009-0000-6443-8739"
|
|
18
|
+
affiliation: "Department of Cardiology, Inselspital, Bern University Hospital, University of Bern, Switzerland"
|
|
19
|
+
- family-names: Mokhtari
|
|
20
|
+
given-names: Ali
|
|
21
|
+
orcid: "https://orcid.org/0009-0004-8864-6988"
|
|
22
|
+
affiliation: "Department of Cardiology, Inselspital, Bern University Hospital, University of Bern, Switzerland"
|
|
23
|
+
- family-names: Mohammadi Kazaj
|
|
24
|
+
given-names: Pooya
|
|
25
|
+
orcid: "https://orcid.org/0000-0003-2747-1489"
|
|
26
|
+
affiliation: "Department of Cardiology, Inselspital, Bern University Hospital, University of Bern, Switzerland"
|
|
27
|
+
- family-names: Gräni
|
|
28
|
+
given-names: Christoph
|
|
29
|
+
orcid: "https://orcid.org/0000-0001-6029-0597"
|
|
30
|
+
affiliation: "Department of Cardiology, Inselspital, Bern University Hospital, University of Bern, Switzerland"
|
|
31
|
+
- family-names: Shiri
|
|
32
|
+
given-names: Isaac
|
|
33
|
+
orcid: "https://orcid.org/0000-0002-5735-0736"
|
|
34
|
+
affiliation: "Department of Cardiology, Inselspital, Bern University Hospital, University of Bern, Switzerland"
|
|
35
|
+
|
|
36
|
+
abstract: >
|
|
37
|
+
multimodars is a high-performance Python/Rust toolkit that registers and fuses
|
|
38
|
+
intravascular imaging (IVUS, OCT) with coronary CT angiography (CCTA) into unified
|
|
39
|
+
high-resolution 3D vessel models. It implements four alignment modes (full,
|
|
40
|
+
double-pair, single-pair, single) to support multi-state comparisons such as rest
|
|
41
|
+
vs. pharmacologic stress and pre- vs. post-intervention. The Rust backend (via
|
|
42
|
+
PyO3/maturin) parallelizes the multiscale angular search across all available cores,
|
|
43
|
+
achieving a combined 38.5x speedup over a Python brute-force baseline. Output
|
|
44
|
+
includes Wavefront OBJ/STL meshes, per-frame lumen metrics (minimum lumen area,
|
|
45
|
+
stenosis fraction, elliptic ratio), and optional interpolated deformation animations.
|
|
46
|
+
|
|
47
|
+
keywords:
|
|
48
|
+
- Rust
|
|
49
|
+
- Python
|
|
50
|
+
- Cardiology
|
|
51
|
+
- Intravascular imaging
|
|
52
|
+
- CCTA
|
|
53
|
+
- Image Fusion
|
|
54
|
+
- Image registration
|
|
55
|
+
- IVUS
|
|
56
|
+
- OCT
|
|
57
|
+
- Coronary artery anomalies
|
|
58
|
+
|
|
59
|
+
preferred-citation:
|
|
60
|
+
type: article
|
|
61
|
+
title: "multimodars: A Rust-powered toolkit for multi-modality cardiac image fusion and registration"
|
|
62
|
+
authors:
|
|
63
|
+
- family-names: Stark
|
|
64
|
+
given-names: Anselm W.
|
|
65
|
+
orcid: "https://orcid.org/0000-0002-5861-3753"
|
|
66
|
+
- family-names: Ilic
|
|
67
|
+
given-names: Marc
|
|
68
|
+
orcid: "https://orcid.org/0009-0000-6443-8739"
|
|
69
|
+
- family-names: Mokhtari
|
|
70
|
+
given-names: Ali
|
|
71
|
+
orcid: "https://orcid.org/0009-0004-8864-6988"
|
|
72
|
+
- family-names: Mohammadi Kazaj
|
|
73
|
+
given-names: Pooya
|
|
74
|
+
orcid: "https://orcid.org/0000-0003-2747-1489"
|
|
75
|
+
- family-names: Gräni
|
|
76
|
+
given-names: Christoph
|
|
77
|
+
orcid: "https://orcid.org/0000-0001-6029-0597"
|
|
78
|
+
- family-names: Shiri
|
|
79
|
+
given-names: Isaac
|
|
80
|
+
orcid: "https://orcid.org/0000-0002-5735-0736"
|
|
81
|
+
journal: "Journal of Open Source Software"
|
|
82
|
+
year: 2026
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multimodars
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
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
|
[](https://multimoda-rs.readthedocs.io)
|
|
37
37
|
[](https://github.com/yungselm/multimoda-rs/actions/workflows/CI.yml)
|
|
38
38
|
[](https://joss.theoj.org/papers/24992cb138d85b0bf08f01bc2b384a80)
|
|
39
|
-
|
|
39
|
+
[](https://doi.org/10.5281/zenodo.20051997)
|
|
40
40
|
|
|
41
41
|
<figure class="epigraph" style="text-align: center; font-style: italic;">
|
|
42
42
|
<blockquote>
|
|
@@ -79,7 +79,7 @@ For building from source or the full developer setup (tests, linting, docs), see
|
|
|
79
79
|
|
|
80
80
|
## Quick Example
|
|
81
81
|
|
|
82
|
-
Checkout our **[Documentation](https://
|
|
82
|
+
Checkout our **[Documentation](https://multimoda-rs.readthedocs.io/en/latest)** for in-depth explanations of the functionality and preloaded notebooks including 3D plots. Otherwise clone the repo to follow along the different examples:
|
|
83
83
|
|
|
84
84
|
```python
|
|
85
85
|
import multimodars as mm
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
[](https://multimoda-rs.readthedocs.io)
|
|
13
13
|
[](https://github.com/yungselm/multimoda-rs/actions/workflows/CI.yml)
|
|
14
14
|
[](https://joss.theoj.org/papers/24992cb138d85b0bf08f01bc2b384a80)
|
|
15
|
-
|
|
15
|
+
[](https://doi.org/10.5281/zenodo.20051997)
|
|
16
16
|
|
|
17
17
|
<figure class="epigraph" style="text-align: center; font-style: italic;">
|
|
18
18
|
<blockquote>
|
|
@@ -55,7 +55,7 @@ For building from source or the full developer setup (tests, linting, docs), see
|
|
|
55
55
|
|
|
56
56
|
## Quick Example
|
|
57
57
|
|
|
58
|
-
Checkout our **[Documentation](https://
|
|
58
|
+
Checkout our **[Documentation](https://multimoda-rs.readthedocs.io/en/latest)** for in-depth explanations of the functionality and preloaded notebooks including 3D plots. Otherwise clone the repo to follow along the different examples:
|
|
59
59
|
|
|
60
60
|
```python
|
|
61
61
|
import multimodars as mm
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# Python package that has one purpose: Combining any intravascular image modality with CCTA meshes.
|
|
2
|
+
|
|
3
|
+
# Optimizations
|
|
4
|
+
- [] Optimize the ray intersection algorithm
|
|
5
|
+
- [] Better parallelization strategy for frames alignment by processing in batches (frames only have to be aligned relative to each other not sequentially)
|
|
6
|
+
|
|
7
|
+
# Mesh combination (v0.3.0)
|
|
8
|
+
## New/improved features
|
|
9
|
+
- [] "Smooth" the aortic ring of boundary points first, by projecting on plane to shift outliers
|
|
10
|
+
- [] Write a more stable function to find intramural points on aorta (for now works with excluded RCA points)
|
|
11
|
+
- [] Create an aortic and coronary wall with a consistent connection
|
|
12
|
+
- [] Find bifurcation reference points automatically
|
|
13
|
+
- [] `numpy_to_report` function that allows to read in lists for the four variables of interest (frame, phase, measurement_1, measurement_2)
|
|
14
|
+
- [] write metadata to .obj files so they can be reconstructed completely to geometry/geometrypair
|
|
15
|
+
|
|
16
|
+
## Fixes/Improvements
|
|
17
|
+
- [] Write a more stable function to find intramural points on aorta (for now works with excluded RCA points)
|
|
18
|
+
- [] fix centerline resampling
|
|
19
|
+
|
|
20
|
+
## Investigate/check logic
|
|
21
|
+
- [] When aligning geometry with wall has a rotation in the wall -> logic error for centerline alignment?
|
|
22
|
+
|
|
23
|
+
# CCTA clinical assessment (v0.4.0)
|
|
24
|
+
- [] automatically find the required reference points for anomalous alignment
|
|
25
|
+
- [] automatically assess lumen area, minor-, major axis, mla, intramural length etc. from the CCTA mesh.
|
|
26
|
+
- [] return hausdorff distances per point between aligned ivus mesh and ccta mesh.
|
|
27
|
+
- [] function to automatically assign wall thickness to ccta and combined ivus/ccta mesh
|
|
28
|
+
|
|
29
|
+
# Miscellaneous
|
|
30
|
+
- [] Integrate coverage measurement (`pytest-cov`) and upload reports to Coveralls or Codecov.
|
|
31
|
+
- [] Add a coverage status badge to the README.
|
|
32
|
+
|
|
33
|
+
# Centerline alignment module (v1.0.0)
|
|
34
|
+
- [] Create Centerline directly from mesh
|
|
35
|
+
|
|
36
|
+
# Myocardial Fusion v2.0.0
|
|
37
|
+
- [] Read in LGE/T2 etc. from MRI and map onto CCTA myocardium
|
|
38
|
+
- [] Read in Perfusion map from PET/SPECT and load onto CCTA myocardium
|
|
39
|
+
- [] Calculate perfusion territories based on coronary anatomy
|
|
@@ -6,18 +6,18 @@ Two benchmarks to characterise the alignment performance of `multimodars`.
|
|
|
6
6
|
|
|
7
7
|
| # | What | Function | Dataset |
|
|
8
8
|
|---|------|----------|---------|
|
|
9
|
-
| 1 | Bruteforce vs.
|
|
9
|
+
| 1 | Bruteforce vs. optimized at varying step sizes | `from_file_full` | IVUS rest/stress |
|
|
10
10
|
| 2 | CPU-core scaling (parallelism) | `from_array_single` | OCT (280 frames) |
|
|
11
11
|
|
|
12
12
|
Results land in `benchmarks/results/`.
|
|
13
13
|
|
|
14
14
|
---
|
|
15
15
|
|
|
16
|
-
## Benchmark 1 — Bruteforce vs.
|
|
16
|
+
## Benchmark 1 — Bruteforce vs. optimized: Step-Size Scaling
|
|
17
17
|
|
|
18
18
|
### Goal
|
|
19
19
|
|
|
20
|
-
Quantify how bruteforce and
|
|
20
|
+
Quantify how bruteforce and optimized alignment scale as `step_rotation_deg` decreases
|
|
21
21
|
(i.e. as the rotation-search grid becomes finer).
|
|
22
22
|
|
|
23
23
|
### Setup
|
|
@@ -51,7 +51,7 @@ Corresponding number of rotation steps n = 180 / step (using the full ±90° ran
|
|
|
51
51
|
|
|
52
52
|
- **Bruteforce**: exhaustively scores every rotation angle for every frame pair →
|
|
53
53
|
wall time grows linearly with n\_steps (O(n)).
|
|
54
|
-
- **
|
|
54
|
+
- **optimized**: coarse-to-fine hierarchical search → sub-linear in n\_steps,
|
|
55
55
|
asymptotically O(log n).
|
|
56
56
|
|
|
57
57
|
The plot axes are logarithmic so the two complexity classes appear as straight lines
|
|
@@ -64,8 +64,8 @@ with clearly different slopes.
|
|
|
64
64
|
- x-axis (bottom): number of rotation steps; (top): step size in degrees
|
|
65
65
|
- y-axis: median wall time (seconds)
|
|
66
66
|
- Line 1: bruteforce (red ○–) + O(n) reference dashed line
|
|
67
|
-
- Line 2:
|
|
68
|
-
- Right y-axis: speedup factor (bruteforce /
|
|
67
|
+
- Line 2: optimized (blue ■ --)
|
|
68
|
+
- Right y-axis: speedup factor (bruteforce / optimized)
|
|
69
69
|
|
|
70
70
|
---
|
|
71
71
|
|
|
@@ -110,7 +110,7 @@ a **separate subprocess** with the variable set before any import of `multimodar
|
|
|
110
110
|
|
|
111
111
|
### Expected behaviour
|
|
112
112
|
|
|
113
|
-
- **
|
|
113
|
+
- **optimized**: near-linear speedup up to ~8 cores, then diminishing returns
|
|
114
114
|
(Amdahl's law — serial fraction of the pipeline limits gains beyond that).
|
|
115
115
|
- **Bruteforce**: also benefits from rayon frame-level parallelism, but expected
|
|
116
116
|
to saturate earlier because the per-step work is less cache-friendly.
|
|
@@ -119,7 +119,7 @@ a **separate subprocess** with the variable set before any import of `multimodar
|
|
|
119
119
|
|
|
120
120
|
`benchmarks/results/cpu_scaling.png` — two-panel figure:
|
|
121
121
|
|
|
122
|
-
- Left panel: wall time (s) vs. CPU cores, two lines (bruteforce,
|
|
122
|
+
- Left panel: wall time (s) vs. CPU cores, two lines (bruteforce, optimized)
|
|
123
123
|
- Right panel: speedup relative to 1 core vs. CPU cores +
|
|
124
124
|
ideal linear-speedup reference; same two lines
|
|
125
125
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Benchmark 1: bruteforce vs.
|
|
1
|
+
"""Benchmark 1: bruteforce vs. optimized alignment at varying step sizes.
|
|
2
2
|
|
|
3
3
|
Tests from_file_full on the bundled IVUS rest/stress example data with
|
|
4
4
|
step_rotation_deg in {5.0, 2.5, 1.0, 0.5, 0.1} degrees.
|
|
@@ -27,7 +27,7 @@ INPUT_AB = str(DATA_ROOT / "ivus_rest")
|
|
|
27
27
|
INPUT_CD = str(DATA_ROOT / "ivus_stress")
|
|
28
28
|
RESULTS_DIR = Path(__file__).parent / "results"
|
|
29
29
|
|
|
30
|
-
STEP_SIZES: list[float] = [5.0, 2.5, 1.0, 0.5, 0.1, 0.05]
|
|
30
|
+
STEP_SIZES: list[float] = [5.0, 2.5, 1.0, 0.5, 0.25, 0.1, 0.05]
|
|
31
31
|
RANGE_DEG: float = 90.0
|
|
32
32
|
REPEATS: int = 3
|
|
33
33
|
|
|
@@ -63,7 +63,7 @@ def _run_once(step: float, bruteforce: bool) -> float:
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
def run_benchmark() -> dict[str, list[float]]:
|
|
66
|
-
results: dict[str, list[float]] = {"bruteforce": [], "
|
|
66
|
+
results: dict[str, list[float]] = {"bruteforce": [], "optimized": []}
|
|
67
67
|
|
|
68
68
|
for step in STEP_SIZES:
|
|
69
69
|
print(f"step={step:4.1f}° bruteforce=True ", end="", flush=True)
|
|
@@ -75,7 +75,7 @@ def run_benchmark() -> dict[str, list[float]]:
|
|
|
75
75
|
print(f"step={step:4.1f}° bruteforce=False ", end="", flush=True)
|
|
76
76
|
times_opt = [_run_once(step, bruteforce=False) for _ in range(REPEATS)]
|
|
77
77
|
med_opt = statistics.median(times_opt)
|
|
78
|
-
results["
|
|
78
|
+
results["optimized"].append(med_opt)
|
|
79
79
|
speedup = med_bf / med_opt if med_opt > 0 else float("inf")
|
|
80
80
|
print(f"{med_opt:.2f} s (speedup {speedup:.1f}x)")
|
|
81
81
|
|
|
@@ -92,7 +92,7 @@ def plot_results(results: dict[str, list[float]]) -> None:
|
|
|
92
92
|
|
|
93
93
|
n_steps_list = [_n_steps(s) for s in STEP_SIZES]
|
|
94
94
|
bf_times = results["bruteforce"]
|
|
95
|
-
opt_times = results["
|
|
95
|
+
opt_times = results["optimized"]
|
|
96
96
|
speedups = [bf / opt for bf, opt in zip(bf_times, opt_times)]
|
|
97
97
|
|
|
98
98
|
fig, (ax_main, ax_speedup) = plt.subplots(
|
|
@@ -116,7 +116,7 @@ def plot_results(results: dict[str, list[float]]) -> None:
|
|
|
116
116
|
color="tab:blue",
|
|
117
117
|
linewidth=2,
|
|
118
118
|
markersize=7,
|
|
119
|
-
label="
|
|
119
|
+
label="optimized",
|
|
120
120
|
)
|
|
121
121
|
|
|
122
122
|
# O(n) reference anchored at the first bruteforce point
|
|
@@ -137,7 +137,7 @@ def plot_results(results: dict[str, list[float]]) -> None:
|
|
|
137
137
|
ax_main.set_xlabel("Number of rotation steps (2 × range / step)", fontsize=12)
|
|
138
138
|
ax_main.set_ylabel("Median wall time (s)", fontsize=12)
|
|
139
139
|
ax_main.set_title(
|
|
140
|
-
"Bruteforce vs.
|
|
140
|
+
"Bruteforce vs. optimized — Step-Size Scaling\n"
|
|
141
141
|
"from_file_full · IVUS rest/stress · range = 90°",
|
|
142
142
|
fontsize=11,
|
|
143
143
|
)
|
|
@@ -164,9 +164,9 @@ def plot_results(results: dict[str, list[float]]) -> None:
|
|
|
164
164
|
ax_speedup.axhline(1.0, color="gray", linewidth=1, linestyle=":")
|
|
165
165
|
ax_speedup.set_xscale("log")
|
|
166
166
|
ax_speedup.set_xlabel("Number of rotation steps (2 × range / step)", fontsize=12)
|
|
167
|
-
ax_speedup.set_ylabel("Speedup (bruteforce /
|
|
167
|
+
ax_speedup.set_ylabel("Speedup (bruteforce / optimized)", fontsize=12)
|
|
168
168
|
ax_speedup.set_title(
|
|
169
|
-
"Speedup of
|
|
169
|
+
"Speedup of optimized over Bruteforce\n"
|
|
170
170
|
"from_file_full · IVUS rest/stress · range = 90°",
|
|
171
171
|
fontsize=11,
|
|
172
172
|
)
|
|
@@ -190,7 +190,7 @@ def plot_results(results: dict[str, list[float]]) -> None:
|
|
|
190
190
|
# ---------------------------------------------------------------------------
|
|
191
191
|
|
|
192
192
|
if __name__ == "__main__":
|
|
193
|
-
print("=== Benchmark 1: Bruteforce vs.
|
|
193
|
+
print("=== Benchmark 1: Bruteforce vs. optimized — Step-Size Scaling ===\n")
|
|
194
194
|
results = run_benchmark()
|
|
195
195
|
print("\nPlotting results …")
|
|
196
196
|
plot_results(results)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Benchmark 2: CPU-core scaling for
|
|
1
|
+
"""Benchmark 2: CPU-core scaling for optimized vs. bruteforce alignment.
|
|
2
2
|
|
|
3
3
|
Tests from_array_single on the bundled OCT example dataset (280 frames) at
|
|
4
4
|
varying RAYON_NUM_THREADS values (1 → 16 cores).
|
|
@@ -103,6 +103,7 @@ def _run_worker(n_cores: int, bruteforce: bool) -> list[float]:
|
|
|
103
103
|
[sys.executable, "-c", script],
|
|
104
104
|
capture_output=True,
|
|
105
105
|
text=True,
|
|
106
|
+
encoding="utf-8",
|
|
106
107
|
)
|
|
107
108
|
if proc.returncode != 0 or not proc.stdout.strip():
|
|
108
109
|
print(f"\n [stdout] {proc.stdout.strip()[:600]}", file=sys.stderr)
|
|
@@ -122,7 +123,7 @@ def _run_worker(n_cores: int, bruteforce: bool) -> list[float]:
|
|
|
122
123
|
|
|
123
124
|
|
|
124
125
|
def run_benchmark() -> dict[str, list[float]]:
|
|
125
|
-
results: dict[str, list[float]] = {"bruteforce": [], "
|
|
126
|
+
results: dict[str, list[float]] = {"bruteforce": [], "optimized": []}
|
|
126
127
|
|
|
127
128
|
for n_cores in CORE_COUNTS:
|
|
128
129
|
label_width = len(str(max(CORE_COUNTS)))
|
|
@@ -144,7 +145,7 @@ def run_benchmark() -> dict[str, list[float]]:
|
|
|
144
145
|
)
|
|
145
146
|
times_opt = _run_worker(n_cores, bruteforce=False)
|
|
146
147
|
med_opt = statistics.median(times_opt)
|
|
147
|
-
results["
|
|
148
|
+
results["optimized"].append(med_opt)
|
|
148
149
|
print(f"{med_opt:.2f} s")
|
|
149
150
|
|
|
150
151
|
return results
|
|
@@ -159,7 +160,7 @@ def plot_results(results: dict[str, list[float]]) -> None:
|
|
|
159
160
|
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
160
161
|
|
|
161
162
|
bf_times = results["bruteforce"]
|
|
162
|
-
opt_times = results["
|
|
163
|
+
opt_times = results["optimized"]
|
|
163
164
|
|
|
164
165
|
bf_speedup = [bf_times[0] / t for t in bf_times]
|
|
165
166
|
opt_speedup = [opt_times[0] / t for t in opt_times]
|
|
@@ -186,7 +187,7 @@ def plot_results(results: dict[str, list[float]]) -> None:
|
|
|
186
187
|
color="tab:blue",
|
|
187
188
|
linewidth=2,
|
|
188
189
|
markersize=7,
|
|
189
|
-
label="
|
|
190
|
+
label="optimized",
|
|
190
191
|
)
|
|
191
192
|
ax_time.set_xlabel("CPU cores (RAYON_NUM_THREADS)", fontsize=12)
|
|
192
193
|
ax_time.set_ylabel("Median wall time (s)", fontsize=12)
|
|
@@ -224,7 +225,7 @@ def plot_results(results: dict[str, list[float]]) -> None:
|
|
|
224
225
|
color="tab:blue",
|
|
225
226
|
linewidth=2,
|
|
226
227
|
markersize=7,
|
|
227
|
-
label="
|
|
228
|
+
label="optimized",
|
|
228
229
|
)
|
|
229
230
|
ax_speedup.set_xlabel("CPU cores (RAYON_NUM_THREADS)", fontsize=12)
|
|
230
231
|
ax_speedup.set_ylabel("Speedup (relative to 1 core)", fontsize=12)
|
|
Binary file
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from typing import cast
|
|
4
|
+
|
|
3
5
|
import numpy as np
|
|
4
6
|
import trimesh
|
|
5
7
|
from .multimodars import (
|
|
@@ -139,7 +141,9 @@ def _geometry_to_numpy(geom) -> dict[str, np.ndarray]:
|
|
|
139
141
|
if len(result[key]) == 0:
|
|
140
142
|
result[key] = frame_data[key]
|
|
141
143
|
else:
|
|
142
|
-
result[key] =
|
|
144
|
+
result[key] = cast(
|
|
145
|
+
np.ndarray, np.vstack([result[key], frame_data[key]])
|
|
146
|
+
)
|
|
143
147
|
|
|
144
148
|
return result
|
|
145
149
|
|