multimodars 0.3.1__tar.gz → 0.3.2__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.2/.readthedocs.yaml +19 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/CHANGELOG.md +21 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/Cargo.lock +1 -1
- {multimodars-0.3.1 → multimodars-0.3.2}/Cargo.toml +1 -1
- {multimodars-0.3.1 → multimodars-0.3.2}/PKG-INFO +3 -3
- {multimodars-0.3.1 → multimodars-0.3.2}/README.md +2 -2
- {multimodars-0.3.1 → multimodars-0.3.2}/ROADMAP.MD +5 -4
- multimodars-0.3.2/benchmarks/BENCHMARK_PLAN.md +143 -0
- multimodars-0.3.2/benchmarks/benchmark_bruteforce_stepsize.py +196 -0
- multimodars-0.3.2/benchmarks/benchmark_cpu_scaling.py +258 -0
- multimodars-0.3.2/benchmarks/results/bruteforce_stepsize.png +0 -0
- multimodars-0.3.2/benchmarks/results/cpu_scaling.png +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/__init__.py +1 -1
- {multimodars-0.3.1 → multimodars-0.3.2}/paper/paper.md +6 -2
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/processing/align_within.rs +2 -3
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/processing/process_utils.rs +12 -20
- multimodars-0.3.1/.readthedocs.yaml +0 -36
- {multimodars-0.3.1 → multimodars-0.3.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/.github/workflows/CI.yml +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/.github/workflows/draft-pdf.yml +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/.gitignore +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/.pre-commit-config.yaml +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/CONTRIBUTING.md +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/LICENSE +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/branch_diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/branch_systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/calcium_diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/calcium_systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/diastolic_reference_points.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/eem_diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/eem_systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/idealized_geometry/systolic_reference_points.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/branch_diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/branch_systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/calcium_diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/calcium_systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/diastolic_reference_points.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/eem_diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/eem_systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_full/systolic_reference_points.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_rest/combined_sorted_manual.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_rest/diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_rest/diastolic_reference_points.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_rest/systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_rest/systolic_reference_points.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_stress/combined_sorted_manual.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_stress/diastolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_stress/diastolic_reference_points.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_stress/systolic_contours.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/data/fixtures/ivus_stress/systolic_reference_points.csv +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/media/coronary_artery_disease.jpg +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/media/dynamic_lumen_changes.png +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/media/multimoda-rs.jpg +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/_converters.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/_processing.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/ccta/__init__.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/ccta/debug_plots.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/ccta/fixing_functions.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/ccta/labeling.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/ccta/manipulating.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/io/__init__.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/io/read_geometrical.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/io/write_geometries.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/multimodars.pyi +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/multimodars/py.typed +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/paper/REVIEWER1.md +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/paper/bibliography.bib +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/paper/figures/Figure1.jpg +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/paper/figures/Figure2.jpg +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/paper/figures/Figure3.png +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/paper/figures/Figure4.jpg +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/pyproject.toml +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/ccta/adjust_mesh/label_coronary.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/ccta/adjust_mesh/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/ccta/adjust_mesh/scale_coronary.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/ccta/binding/ccta_py.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/ccta/binding/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/ccta/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/binding/align.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/binding/classes.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/binding/entry.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/binding/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/centerline_align/align_algorithms.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/centerline_align/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/centerline_align/preprocessing.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/io/geometry.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/io/input.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/io/integrity_check.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/io/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/io/output.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/processing/align_between.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/processing/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/processing/postprocessing.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/processing/preprocessing.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/processing/wall.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/to_object/interpolation.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/to_object/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/to_object/texture.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/to_object/write_mtl.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/utils/general_utils.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/utils/mod.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/intravascular/utils/test_utils.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/src/lib.rs +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/tests/conftest.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/tests/test_ccta.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/tests/test_converters.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/tests/test_core.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/tests/test_wrappers.py +0 -0
- {multimodars-0.3.1 → multimodars-0.3.2}/tests/tests_todo.md +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
build:
|
|
4
|
+
os: ubuntu-22.04
|
|
5
|
+
tools:
|
|
6
|
+
python: "3.12"
|
|
7
|
+
rust: "latest"
|
|
8
|
+
jobs:
|
|
9
|
+
post_create_environment:
|
|
10
|
+
- "$READTHEDOCS_VIRTUALENV_PATH/bin/pip install maturin sphinx sphinx-rtd-theme sphinx-autodoc-typehints myst-nb plotly furo"
|
|
11
|
+
|
|
12
|
+
sphinx:
|
|
13
|
+
configuration: docs/conf.py
|
|
14
|
+
fail_on_warning: true
|
|
15
|
+
|
|
16
|
+
python:
|
|
17
|
+
install:
|
|
18
|
+
- method: pip
|
|
19
|
+
path: .
|
|
@@ -3,6 +3,27 @@
|
|
|
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.2] - 2026-05-05
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- Benchmark suite (`benchmarks/`) with two scripts: `benchmark_bruteforce_stepsize.py`
|
|
10
|
+
(step-size scaling, `from_file_full`) and `benchmark_cpu_scaling.py` (core-count
|
|
11
|
+
scaling, `from_array_single`). Results saved to `benchmarks/results/`.
|
|
12
|
+
- Benchmark documentation page (`docs/benchmark.rst`) with result figures and table,
|
|
13
|
+
linked in the ReadTheDocs table of contents.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
- `paper/paper.md` Performance section updated with quantified benchmark results and
|
|
17
|
+
corrected parallelisation description.
|
|
18
|
+
|
|
19
|
+
### Performance
|
|
20
|
+
- Intra-pullback alignment (`search_range`) now parallelises over candidate rotation
|
|
21
|
+
angles via `par_iter` instead of the point-rotation loop. The inner cost-function
|
|
22
|
+
loop is now sequential. This provides enough rayon tasks per frame step to saturate
|
|
23
|
+
all cores: bruteforce scales 6.5× and optimised 4.2× from 2 to 16 cores (previously
|
|
24
|
+
~1.4× for both). Combined algorithmic + parallelisation gain reaches **38.5×** vs.
|
|
25
|
+
brute-force at 2 cores.
|
|
26
|
+
|
|
6
27
|
## [0.3.1] - 2026-05-04
|
|
7
28
|
|
|
8
29
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: multimodars
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: Python :: Implementation :: CPython
|
|
6
6
|
Classifier: Programming Language :: Python :: Implementation :: PyPy
|
|
@@ -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
|
-
|
|
82
|
+
Checkout our **[Documentation](https://multimod-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
|
|
@@ -133,7 +133,7 @@ aligned_pair, cl_resampled = mm.align_three_point(
|
|
|
133
133
|
|
|
134
134
|
## Documentation
|
|
135
135
|
|
|
136
|
-
Full documentation — installation, step-by-step tutorials, interactive Jupyter notebooks, and API reference — is available at **[multimoda-rs.readthedocs.io](https://multimoda-rs.readthedocs.io)**.
|
|
136
|
+
Full documentation — installation, step-by-step tutorials, interactive (preloaded) Jupyter notebooks, and API reference — is available at **[multimoda-rs.readthedocs.io](https://multimoda-rs.readthedocs.io)**.
|
|
137
137
|
|
|
138
138
|
## License
|
|
139
139
|
|
|
@@ -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
|
-
|
|
58
|
+
Checkout our **[Documentation](https://multimod-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
|
|
@@ -109,7 +109,7 @@ aligned_pair, cl_resampled = mm.align_three_point(
|
|
|
109
109
|
|
|
110
110
|
## Documentation
|
|
111
111
|
|
|
112
|
-
Full documentation — installation, step-by-step tutorials, interactive Jupyter notebooks, and API reference — is available at **[multimoda-rs.readthedocs.io](https://multimoda-rs.readthedocs.io)**.
|
|
112
|
+
Full documentation — installation, step-by-step tutorials, interactive (preloaded) Jupyter notebooks, and API reference — is available at **[multimoda-rs.readthedocs.io](https://multimoda-rs.readthedocs.io)**.
|
|
113
113
|
|
|
114
114
|
## License
|
|
115
115
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Python package that has one purpose: Combining any intravascular image modality with CCTA meshes.
|
|
2
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
|
+
|
|
3
7
|
# Centerline alignment module
|
|
4
8
|
(- [] Create Centerline directly from mesh) (v1.0.0)
|
|
5
9
|
|
|
@@ -16,9 +20,6 @@
|
|
|
16
20
|
- [] helper function to automatically export only certain points on mesh (e.g. RCA or LCA)
|
|
17
21
|
- [] function to combine anomalous section only with the distal part
|
|
18
22
|
|
|
19
|
-
# Miscellaneous
|
|
20
|
-
- [x] Improve print statements in the whole package
|
|
21
|
-
|
|
22
23
|
# Prepublish checklist (JOSS review feedback — open until resolved):
|
|
23
24
|
|
|
24
25
|
## Dependencies / packaging
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
so regressions are caught automatically.
|
|
41
42
|
|
|
42
43
|
## Performance claim
|
|
43
|
-
- [] Provide reproducible code + sample data for the performance claim in the paper (lines 102-104):
|
|
44
|
+
- [x] Provide reproducible code + sample data for the performance claim in the paper (lines 102-104):
|
|
44
45
|
"Empirical performance on a 16-core CPU: an OCT pullback with 280 frames and a rotation search range
|
|
45
46
|
of ±3° (final accuracy 0.01°) saw alignment time reduced from 150 s to 18 s with the optimized
|
|
46
47
|
multiscale search."
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# Benchmark Plan: multimoda-rs Alignment Performance
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Two benchmarks to characterise the alignment performance of `multimodars`.
|
|
6
|
+
|
|
7
|
+
| # | What | Function | Dataset |
|
|
8
|
+
|---|------|----------|---------|
|
|
9
|
+
| 1 | Bruteforce vs. optimised at varying step sizes | `from_file_full` | IVUS rest/stress |
|
|
10
|
+
| 2 | CPU-core scaling (parallelism) | `from_array_single` | OCT (280 frames) |
|
|
11
|
+
|
|
12
|
+
Results land in `benchmarks/results/`.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## Benchmark 1 — Bruteforce vs. Optimised: Step-Size Scaling
|
|
17
|
+
|
|
18
|
+
### Goal
|
|
19
|
+
|
|
20
|
+
Quantify how bruteforce and optimised alignment scale as `step_rotation_deg` decreases
|
|
21
|
+
(i.e. as the rotation-search grid becomes finer).
|
|
22
|
+
|
|
23
|
+
### Setup
|
|
24
|
+
|
|
25
|
+
| Parameter | Value |
|
|
26
|
+
|-----------|-------|
|
|
27
|
+
| Function | `from_file_full` |
|
|
28
|
+
| Dataset | `examples/data/ivus_rest` / `examples/data/ivus_stress` (IVUS) |
|
|
29
|
+
| `range_rotation_deg` | 90.0° (±90° → 180° total sweep) |
|
|
30
|
+
| `write_obj` | `False` — avoids disk-I/O noise |
|
|
31
|
+
| `smooth` | `False` |
|
|
32
|
+
| `postprocessing` | `False` |
|
|
33
|
+
| `interpolation_steps` | `0` |
|
|
34
|
+
| Repetitions per condition | 3 (median wall time reported) |
|
|
35
|
+
|
|
36
|
+
### Independent variable
|
|
37
|
+
|
|
38
|
+
`step_rotation_deg` ∈ {5.0, 2.5, 1.0, 0.5, 0.1} degrees.
|
|
39
|
+
|
|
40
|
+
Corresponding number of rotation steps n = 180 / step (using the full ±90° range):
|
|
41
|
+
|
|
42
|
+
| step (°) | n\_steps |
|
|
43
|
+
|----------|---------|
|
|
44
|
+
| 5.0 | 36 |
|
|
45
|
+
| 2.5 | 72 |
|
|
46
|
+
| 1.0 | 180 |
|
|
47
|
+
| 0.5 | 360 |
|
|
48
|
+
| 0.1 | 1 800 |
|
|
49
|
+
|
|
50
|
+
### Expected behaviour
|
|
51
|
+
|
|
52
|
+
- **Bruteforce**: exhaustively scores every rotation angle for every frame pair →
|
|
53
|
+
wall time grows linearly with n\_steps (O(n)).
|
|
54
|
+
- **Optimised**: coarse-to-fine hierarchical search → sub-linear in n\_steps,
|
|
55
|
+
asymptotically O(log n).
|
|
56
|
+
|
|
57
|
+
The plot axes are logarithmic so the two complexity classes appear as straight lines
|
|
58
|
+
with clearly different slopes.
|
|
59
|
+
|
|
60
|
+
### Output
|
|
61
|
+
|
|
62
|
+
`benchmarks/results/bruteforce_stepsize.png` — log-log line plot:
|
|
63
|
+
|
|
64
|
+
- x-axis (bottom): number of rotation steps; (top): step size in degrees
|
|
65
|
+
- y-axis: median wall time (seconds)
|
|
66
|
+
- Line 1: bruteforce (red ○–) + O(n) reference dashed line
|
|
67
|
+
- Line 2: optimised (blue ■ --)
|
|
68
|
+
- Right y-axis: speedup factor (bruteforce / optimised)
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Benchmark 2 — CPU-Core Scaling
|
|
73
|
+
|
|
74
|
+
### Goal
|
|
75
|
+
|
|
76
|
+
Measure how parallelism scales from 1 → 16 CPU cores for both alignment modes,
|
|
77
|
+
using a large OCT pullback (280 frames) to stress the frame-level parallelism
|
|
78
|
+
in the rayon thread pool.
|
|
79
|
+
|
|
80
|
+
### Note on function choice
|
|
81
|
+
|
|
82
|
+
The OCT example data (`examples/data/oct_single/`) ships as
|
|
83
|
+
`oct_contours_raw.csv` + `oct_ref.csv`, which do not follow the AIVUS-CAA
|
|
84
|
+
file-naming convention expected by `from_file_single`. The benchmark therefore
|
|
85
|
+
uses `from_array_single` (as demonstrated in the docs notebook), which accepts
|
|
86
|
+
raw numpy arrays directly. The algorithm under test is identical.
|
|
87
|
+
|
|
88
|
+
### Setup
|
|
89
|
+
|
|
90
|
+
| Parameter | Value |
|
|
91
|
+
|-----------|-------|
|
|
92
|
+
| Function | `from_array_single` |
|
|
93
|
+
| Dataset | `examples/data/oct_single/` (280 frames, OCT) |
|
|
94
|
+
| `step_rotation_deg` | 0.01° (from docs/notebooks) |
|
|
95
|
+
| `range_rotation_deg` | 6.0° (from docs/notebooks) |
|
|
96
|
+
| `image_center` | (5.0, 5.0) (from docs/notebooks) |
|
|
97
|
+
| `n_points` | 40 (from docs/notebooks) |
|
|
98
|
+
| `smooth` | `False` |
|
|
99
|
+
| `write_obj` | `False` |
|
|
100
|
+
| Repetitions per condition | 3 (median wall time reported) |
|
|
101
|
+
|
|
102
|
+
### Independent variable
|
|
103
|
+
|
|
104
|
+
`RAYON_NUM_THREADS` ∈ {1, 2, 4, 8, 12, 16}
|
|
105
|
+
|
|
106
|
+
**Important**: rayon's global thread pool is initialised once per process from
|
|
107
|
+
the `RAYON_NUM_THREADS` environment variable. To guarantee a fresh pool at each
|
|
108
|
+
thread count, every (n\_cores × bruteforce × repeat) combination is executed in
|
|
109
|
+
a **separate subprocess** with the variable set before any import of `multimodars`.
|
|
110
|
+
|
|
111
|
+
### Expected behaviour
|
|
112
|
+
|
|
113
|
+
- **Optimised**: near-linear speedup up to ~8 cores, then diminishing returns
|
|
114
|
+
(Amdahl's law — serial fraction of the pipeline limits gains beyond that).
|
|
115
|
+
- **Bruteforce**: also benefits from rayon frame-level parallelism, but expected
|
|
116
|
+
to saturate earlier because the per-step work is less cache-friendly.
|
|
117
|
+
|
|
118
|
+
### Output
|
|
119
|
+
|
|
120
|
+
`benchmarks/results/cpu_scaling.png` — two-panel figure:
|
|
121
|
+
|
|
122
|
+
- Left panel: wall time (s) vs. CPU cores, two lines (bruteforce, optimised)
|
|
123
|
+
- Right panel: speedup relative to 1 core vs. CPU cores +
|
|
124
|
+
ideal linear-speedup reference; same two lines
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## Running the Benchmarks
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# From project root
|
|
132
|
+
python benchmarks/benchmark_bruteforce_stepsize.py
|
|
133
|
+
python benchmarks/benchmark_cpu_scaling.py
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Estimated runtimes (rough, machine-dependent):
|
|
137
|
+
|
|
138
|
+
| Benchmark | Estimate |
|
|
139
|
+
|-----------|----------|
|
|
140
|
+
| Benchmark 1 (all step sizes × 2 modes × 3 reps) | 5–30 min |
|
|
141
|
+
| Benchmark 2 (6 core counts × 2 modes × 3 reps in subprocesses) | 20–60 min |
|
|
142
|
+
|
|
143
|
+
Results are saved as PNG files in `benchmarks/results/`.
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""Benchmark 1: bruteforce vs. optimised alignment at varying step sizes.
|
|
2
|
+
|
|
3
|
+
Tests from_file_full on the bundled IVUS rest/stress example data with
|
|
4
|
+
step_rotation_deg in {5.0, 2.5, 1.0, 0.5, 0.1} degrees.
|
|
5
|
+
|
|
6
|
+
write_obj / smooth / postprocessing are all disabled so only the alignment
|
|
7
|
+
algorithm itself is timed.
|
|
8
|
+
|
|
9
|
+
Output: benchmarks/results/bruteforce_stepsize.png
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
import statistics
|
|
13
|
+
import time
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
import matplotlib.pyplot as plt
|
|
17
|
+
|
|
18
|
+
import multimodars as mm
|
|
19
|
+
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
# Configuration
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
_ROOT = Path(__file__).parent.parent
|
|
25
|
+
DATA_ROOT = _ROOT / "examples" / "data"
|
|
26
|
+
INPUT_AB = str(DATA_ROOT / "ivus_rest")
|
|
27
|
+
INPUT_CD = str(DATA_ROOT / "ivus_stress")
|
|
28
|
+
RESULTS_DIR = Path(__file__).parent / "results"
|
|
29
|
+
|
|
30
|
+
STEP_SIZES: list[float] = [5.0, 2.5, 1.0, 0.5, 0.1, 0.05]
|
|
31
|
+
RANGE_DEG: float = 90.0
|
|
32
|
+
REPEATS: int = 3
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# ---------------------------------------------------------------------------
|
|
36
|
+
# Helpers
|
|
37
|
+
# ---------------------------------------------------------------------------
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _n_steps(step: float) -> float:
|
|
41
|
+
return (RANGE_DEG * 2) / step
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _run_once(step: float, bruteforce: bool) -> float:
|
|
45
|
+
t0 = time.perf_counter()
|
|
46
|
+
mm.from_file_full(
|
|
47
|
+
input_path_ab=INPUT_AB,
|
|
48
|
+
input_path_cd=INPUT_CD,
|
|
49
|
+
step_rotation_deg=step,
|
|
50
|
+
range_rotation_deg=RANGE_DEG,
|
|
51
|
+
write_obj=False,
|
|
52
|
+
smooth=False,
|
|
53
|
+
postprocessing=False,
|
|
54
|
+
bruteforce=bruteforce,
|
|
55
|
+
interpolation_steps=0,
|
|
56
|
+
)
|
|
57
|
+
return time.perf_counter() - t0
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
# ---------------------------------------------------------------------------
|
|
61
|
+
# Benchmark
|
|
62
|
+
# ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def run_benchmark() -> dict[str, list[float]]:
|
|
66
|
+
results: dict[str, list[float]] = {"bruteforce": [], "optimised": []}
|
|
67
|
+
|
|
68
|
+
for step in STEP_SIZES:
|
|
69
|
+
print(f"step={step:4.1f}° bruteforce=True ", end="", flush=True)
|
|
70
|
+
times_bf = [_run_once(step, bruteforce=True) for _ in range(REPEATS)]
|
|
71
|
+
med_bf = statistics.median(times_bf)
|
|
72
|
+
results["bruteforce"].append(med_bf)
|
|
73
|
+
print(f"{med_bf:.2f} s")
|
|
74
|
+
|
|
75
|
+
print(f"step={step:4.1f}° bruteforce=False ", end="", flush=True)
|
|
76
|
+
times_opt = [_run_once(step, bruteforce=False) for _ in range(REPEATS)]
|
|
77
|
+
med_opt = statistics.median(times_opt)
|
|
78
|
+
results["optimised"].append(med_opt)
|
|
79
|
+
speedup = med_bf / med_opt if med_opt > 0 else float("inf")
|
|
80
|
+
print(f"{med_opt:.2f} s (speedup {speedup:.1f}x)")
|
|
81
|
+
|
|
82
|
+
return results
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ---------------------------------------------------------------------------
|
|
86
|
+
# Plotting
|
|
87
|
+
# ---------------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def plot_results(results: dict[str, list[float]]) -> None:
|
|
91
|
+
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
|
|
93
|
+
n_steps_list = [_n_steps(s) for s in STEP_SIZES]
|
|
94
|
+
bf_times = results["bruteforce"]
|
|
95
|
+
opt_times = results["optimised"]
|
|
96
|
+
speedups = [bf / opt for bf, opt in zip(bf_times, opt_times)]
|
|
97
|
+
|
|
98
|
+
fig, (ax_main, ax_speedup) = plt.subplots(
|
|
99
|
+
1, 2, figsize=(14, 5), constrained_layout=True
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# --- Left panel: wall time ---
|
|
103
|
+
ax_main.plot(
|
|
104
|
+
n_steps_list,
|
|
105
|
+
bf_times,
|
|
106
|
+
"o-",
|
|
107
|
+
color="tab:red",
|
|
108
|
+
linewidth=2,
|
|
109
|
+
markersize=7,
|
|
110
|
+
label="Bruteforce",
|
|
111
|
+
)
|
|
112
|
+
ax_main.plot(
|
|
113
|
+
n_steps_list,
|
|
114
|
+
opt_times,
|
|
115
|
+
"s--",
|
|
116
|
+
color="tab:blue",
|
|
117
|
+
linewidth=2,
|
|
118
|
+
markersize=7,
|
|
119
|
+
label="Optimised",
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
# O(n) reference anchored at the first bruteforce point
|
|
123
|
+
ref_n, ref_t = n_steps_list[0], bf_times[0]
|
|
124
|
+
linear_ref = [ref_t * (n / ref_n) for n in n_steps_list]
|
|
125
|
+
ax_main.plot(
|
|
126
|
+
n_steps_list,
|
|
127
|
+
linear_ref,
|
|
128
|
+
":",
|
|
129
|
+
color="tab:red",
|
|
130
|
+
linewidth=1.5,
|
|
131
|
+
alpha=0.55,
|
|
132
|
+
label="O(n) reference",
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
ax_main.set_xscale("log")
|
|
136
|
+
ax_main.set_yscale("log")
|
|
137
|
+
ax_main.set_xlabel("Number of rotation steps (2 × range / step)", fontsize=12)
|
|
138
|
+
ax_main.set_ylabel("Median wall time (s)", fontsize=12)
|
|
139
|
+
ax_main.set_title(
|
|
140
|
+
"Bruteforce vs. Optimised — Step-Size Scaling\n"
|
|
141
|
+
"from_file_full · IVUS rest/stress · range = 90°",
|
|
142
|
+
fontsize=11,
|
|
143
|
+
)
|
|
144
|
+
ax_main.legend(fontsize=11)
|
|
145
|
+
ax_main.grid(True, which="both", alpha=0.3)
|
|
146
|
+
|
|
147
|
+
# Secondary x-axis with step-size labels
|
|
148
|
+
ax_top = ax_main.twiny()
|
|
149
|
+
ax_top.set_xscale("log")
|
|
150
|
+
ax_top.set_xlim(ax_main.get_xlim())
|
|
151
|
+
ax_top.set_xticks(n_steps_list)
|
|
152
|
+
ax_top.set_xticklabels([f"{s}°" for s in STEP_SIZES])
|
|
153
|
+
ax_top.set_xlabel("Step size (degrees)", fontsize=12)
|
|
154
|
+
|
|
155
|
+
# --- Right panel: speedup factor ---
|
|
156
|
+
ax_speedup.plot(
|
|
157
|
+
n_steps_list,
|
|
158
|
+
speedups,
|
|
159
|
+
"D-",
|
|
160
|
+
color="tab:green",
|
|
161
|
+
linewidth=2,
|
|
162
|
+
markersize=7,
|
|
163
|
+
)
|
|
164
|
+
ax_speedup.axhline(1.0, color="gray", linewidth=1, linestyle=":")
|
|
165
|
+
ax_speedup.set_xscale("log")
|
|
166
|
+
ax_speedup.set_xlabel("Number of rotation steps (2 × range / step)", fontsize=12)
|
|
167
|
+
ax_speedup.set_ylabel("Speedup (bruteforce / optimised)", fontsize=12)
|
|
168
|
+
ax_speedup.set_title(
|
|
169
|
+
"Speedup of Optimised over Bruteforce\n"
|
|
170
|
+
"from_file_full · IVUS rest/stress · range = 90°",
|
|
171
|
+
fontsize=11,
|
|
172
|
+
)
|
|
173
|
+
ax_speedup.grid(True, which="both", alpha=0.3)
|
|
174
|
+
|
|
175
|
+
ax_top2 = ax_speedup.twiny()
|
|
176
|
+
ax_top2.set_xscale("log")
|
|
177
|
+
ax_top2.set_xlim(ax_speedup.get_xlim())
|
|
178
|
+
ax_top2.set_xticks(n_steps_list)
|
|
179
|
+
ax_top2.set_xticklabels([f"{s}°" for s in STEP_SIZES])
|
|
180
|
+
ax_top2.set_xlabel("Step size (degrees)", fontsize=12)
|
|
181
|
+
|
|
182
|
+
out = RESULTS_DIR / "bruteforce_stepsize.png"
|
|
183
|
+
fig.savefig(out, dpi=150)
|
|
184
|
+
print(f"\nPlot saved → {out}")
|
|
185
|
+
plt.show()
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ---------------------------------------------------------------------------
|
|
189
|
+
# Entry point
|
|
190
|
+
# ---------------------------------------------------------------------------
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
print("=== Benchmark 1: Bruteforce vs. Optimised — Step-Size Scaling ===\n")
|
|
194
|
+
results = run_benchmark()
|
|
195
|
+
print("\nPlotting results …")
|
|
196
|
+
plot_results(results)
|