simind-python-connector 1.0.0__tar.gz → 2.0.0__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.
- {simind_python_connector-1.0.0/simind_python_connector.egg-info → simind_python_connector-2.0.0}/PKG-INFO +12 -3
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/README.md +11 -2
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/backends.rst +1 -1
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/changelog.rst +70 -7
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/conf.py +1 -1
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/contributing.rst +3 -5
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples.rst +20 -2
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/geometry.rst +5 -4
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/index.rst +1 -1
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/installation.rst +40 -14
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/testing.rst +21 -16
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/usage.rst +4 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/pyproject.toml +1 -1
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/__init__.py +1 -1
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/backends/__init__.py +7 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/builders/acquisition_builder.py +77 -29
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/builders/image_builder.py +18 -6
- simind_python_connector-2.0.0/simind_python_connector/connectors/__init__.py +43 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/connectors/_spacing.py +13 -7
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/connectors/python_connector.py +114 -22
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/connectors/pytomography_adaptor.py +5 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/connectors/sirf_adaptor.py +3 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/connectors/stir_adaptor.py +3 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/converters/attenuation.py +3 -1
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/converters/simind_to_stir.py +12 -10
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/core/config.py +208 -58
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/core/executor.py +36 -7
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/utils/import_helpers.py +4 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/utils/interfile_numpy.py +18 -8
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/utils/interfile_parser.py +4 -2
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/utils/simind_utils.py +50 -11
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/utils/stir_utils.py +7 -2
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0/simind_python_connector.egg-info}/PKG-INFO +12 -3
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector.egg-info/SOURCES.txt +6 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_acquisition_builder_unit.py +77 -2
- simind_python_connector-2.0.0/tests/test_attenuation_utils.py +116 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_backends.py +78 -0
- simind_python_connector-2.0.0/tests/test_connector_backend_separation.py +118 -0
- simind_python_connector-2.0.0/tests/test_container_scripts.py +290 -0
- simind_python_connector-2.0.0/tests/test_dicom_builder.py +156 -0
- simind_python_connector-2.0.0/tests/test_executor.py +217 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_image_builder_unit.py +23 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_interfile_numpy.py +128 -4
- simind_python_connector-2.0.0/tests/test_legacy_surface_policy.py +20 -0
- simind_python_connector-2.0.0/tests/test_marker_policy.py +117 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_native_adaptors.py +108 -12
- simind_python_connector-2.0.0/tests/test_packaging_resources.py +113 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_python_connector.py +245 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_pytomography_adaptor.py +85 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_schneider_density.py +3 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_simind_to_stir_converter.py +41 -9
- simind_python_connector-2.0.0/tests/test_simulation_config.py +208 -0
- simind_python_connector-2.0.0/tests/test_utils_small.py +96 -0
- simind_python_connector-1.0.0/simind_python_connector/connectors/__init__.py +0 -24
- simind_python_connector-1.0.0/tests/test_connector_backend_separation.py +0 -42
- simind_python_connector-1.0.0/tests/test_marker_policy.py +0 -72
- simind_python_connector-1.0.0/tests/test_simulation_config.py +0 -89
- simind_python_connector-1.0.0/tests/test_utils_small.py +0 -53
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/LICENSE +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/MANIFEST.in +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/api.rst +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/connector_modernization_plan.rst +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/01_basic_simulation.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/02_runtime_switch_comparison.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/03_multi_window.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/04_custom_config.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/05_scattwin_vs_penetrate_comparison.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/06_schneider_density_conversion.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/07A_stir_adaptor_osem.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/07B_sirf_adaptor_osem.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/07C_pytomography_adaptor_osem.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/08A_stir_adaptor_from_dicom.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/08B_sirf_adaptor_from_dicom.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/08C_pytomography_adaptor_from_dicom.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/examples/_python_connector_helpers.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/docs/intro.rst +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/01_basic_simulation.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/02_runtime_switch_comparison.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/03_multi_window.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/04_custom_config.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/05_scattwin_vs_penetrate_comparison.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/06_schneider_density_conversion.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/07A_stir_adaptor_osem.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/07B_sirf_adaptor_osem.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/07C_pytomography_adaptor_osem.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/08A_stir_adaptor_from_dicom.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/08B_sirf_adaptor_from_dicom.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/08C_pytomography_adaptor_from_dicom.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/examples/_python_connector_helpers.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/requirements-dev.txt +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/requirements.txt +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/setup.cfg +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/backends/base.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/backends/sirf_backend.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/backends/stir_backend.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/builders/__init__.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/configs/AnyScan.yaml +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/configs/Discovery670.yaml +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/configs/Example.yaml +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/configs/MLD001_SCAN0.yaml +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/configs/__init__.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/configs/input.smc +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/connectors/base.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/converters/__init__.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/converters/dicom_to_stir.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/core/__init__.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/core/types.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/data/Schneider2000.json +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/data/__init__.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/data/bone.atn +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/data/h2o.atn +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/utils/__init__.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/utils/backend_access.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/utils/io_utils.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector/utils/sirf_stir_utils.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector.egg-info/dependency_links.txt +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector.egg-info/requires.txt +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/simind_python_connector.egg-info/top_level.txt +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/__init__.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/conftest.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_arithmetic_operations.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_connectors_base.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_container_library_isolation.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_example_config_profile.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_examples_backend_separation.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_geometry_isolation_forward_projection.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_integration.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_interfile_parser.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_osem_geometry_diagnostics.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_package_identity.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_sirf_stir_utils.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_stir_utils_backend_choice_unit.py +0 -0
- {simind_python_connector-1.0.0 → simind_python_connector-2.0.0}/tests/test_utils.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simind-python-connector
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
4
4
|
Summary: Python SIMIND Monte Carlo connector with STIR/SIRF/PyTomography adaptors
|
|
5
5
|
Author-email: Sam Porter <sam.porter.18@ucl.ac.uk>, Rebecca Gillen <rebecca.gillen.18@ucl.ac.uk>, "Efstathios (Stahis) Varzakis" <efstathios.varzakis.22@ucl.ac.uk>
|
|
6
6
|
Maintainer-email: Sam Porter <sam.porter.18@ucl.ac.uk>
|
|
@@ -87,12 +87,18 @@ install a licensed SIMIND installation.
|
|
|
87
87
|
pip install simind-python-connector
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
Import
|
|
90
|
+
Import with:
|
|
91
91
|
|
|
92
92
|
```python
|
|
93
93
|
import simind_python_connector
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
+
For example plotting dependencies:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
pip install "simind-python-connector[examples]"
|
|
100
|
+
```
|
|
101
|
+
|
|
96
102
|
### Adaptor Dependencies
|
|
97
103
|
|
|
98
104
|
`SimindPythonConnector` works without SIRF/STIR/PyTomography.
|
|
@@ -168,6 +174,9 @@ and ensure SIMIND data files are available under:
|
|
|
168
174
|
The Docker helper scripts use this layout and automatically wire SIMIND paths
|
|
169
175
|
when the binary exists at `./simind/simind`.
|
|
170
176
|
|
|
177
|
+
For direct local runs outside the repository helpers, make sure the SIMIND
|
|
178
|
+
executable is available as `simind` on `PATH`.
|
|
179
|
+
|
|
171
180
|
## Quick Start
|
|
172
181
|
```python
|
|
173
182
|
import numpy as np
|
|
@@ -268,7 +277,7 @@ bash scripts/run_container_examples.sh
|
|
|
268
277
|
```
|
|
269
278
|
|
|
270
279
|
## Contributing
|
|
271
|
-
Please see
|
|
280
|
+
Please see the [Contributing Guide](docs/contributing.rst).
|
|
272
281
|
|
|
273
282
|
## License
|
|
274
283
|
Apache License 2.0
|
|
@@ -44,12 +44,18 @@ install a licensed SIMIND installation.
|
|
|
44
44
|
pip install simind-python-connector
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
Import
|
|
47
|
+
Import with:
|
|
48
48
|
|
|
49
49
|
```python
|
|
50
50
|
import simind_python_connector
|
|
51
51
|
```
|
|
52
52
|
|
|
53
|
+
For example plotting dependencies:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install "simind-python-connector[examples]"
|
|
57
|
+
```
|
|
58
|
+
|
|
53
59
|
### Adaptor Dependencies
|
|
54
60
|
|
|
55
61
|
`SimindPythonConnector` works without SIRF/STIR/PyTomography.
|
|
@@ -125,6 +131,9 @@ and ensure SIMIND data files are available under:
|
|
|
125
131
|
The Docker helper scripts use this layout and automatically wire SIMIND paths
|
|
126
132
|
when the binary exists at `./simind/simind`.
|
|
127
133
|
|
|
134
|
+
For direct local runs outside the repository helpers, make sure the SIMIND
|
|
135
|
+
executable is available as `simind` on `PATH`.
|
|
136
|
+
|
|
128
137
|
## Quick Start
|
|
129
138
|
```python
|
|
130
139
|
import numpy as np
|
|
@@ -225,7 +234,7 @@ bash scripts/run_container_examples.sh
|
|
|
225
234
|
```
|
|
226
235
|
|
|
227
236
|
## Contributing
|
|
228
|
-
Please see
|
|
237
|
+
Please see the [Contributing Guide](docs/contributing.rst).
|
|
229
238
|
|
|
230
239
|
## License
|
|
231
240
|
Apache License 2.0
|
|
@@ -14,7 +14,7 @@ Dependency Matrix
|
|
|
14
14
|
* - Component
|
|
15
15
|
- Required dependency
|
|
16
16
|
* - ``SimindPythonConnector``
|
|
17
|
-
- SIMIND
|
|
17
|
+
- SIMIND executable on ``PATH`` (no SIRF/STIR/PyTomography requirement)
|
|
18
18
|
* - ``StirSimindAdaptor``
|
|
19
19
|
- STIR Python (``stir``)
|
|
20
20
|
* - ``SirfSimindAdaptor``
|
|
@@ -7,17 +7,76 @@ All notable changes to this project are documented in this file.
|
|
|
7
7
|
|
|
8
8
|
The format is based on `Keep a Changelog <https://keepachangelog.com/en/1.0.0/>`_, and this project adheres to `Semantic Versioning <https://semver.org/spec/v2.0.0.html>`_.
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
2.0.0 - 2026-10-09
|
|
11
|
+
------------------
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
Breaking Changes
|
|
14
|
+
~~~~~~~~~~~~~~~~
|
|
15
|
+
|
|
16
|
+
- ``STIRSPECTAcquisitionDataBuilder`` no longer flips projection data along the
|
|
17
|
+
last axis.
|
|
18
|
+
- Runtime-operator switches passed to ``run()`` apply to that run only. They
|
|
19
|
+
are no longer kept on the connector for later runs.
|
|
20
|
+
- Each run deletes earlier outputs that share the output prefix before SIMIND
|
|
21
|
+
starts.
|
|
22
|
+
- The STIR backend needs ``stirextra`` as well as ``stir``.
|
|
23
|
+
- STIR/SIRF adaptors take the z voxel spacing from a different axis (see
|
|
24
|
+
Fixed). Results change for anisotropic voxels.
|
|
25
|
+
- Invalid inputs now raise ``ValueError``: empty, non-finite or negative source
|
|
26
|
+
and attenuation maps, array shapes that do not match the header, and
|
|
27
|
+
``output_prefix`` values that contain a path. Energy-window validation raises
|
|
28
|
+
``ValueError`` instead of ``AssertionError``.
|
|
29
|
+
|
|
30
|
+
Fixed
|
|
31
|
+
~~~~~
|
|
32
|
+
|
|
33
|
+
- The packaged ``configs/input.smc`` is now included in the wheel. SMC parsing
|
|
34
|
+
handles fixed-width fields and checks section counts.
|
|
35
|
+
- STIR/SIRF adaptors read the z voxel spacing from the correct axis:
|
|
36
|
+
``voxel_sizes()`` is ``(z, y, x)``, and raw ``get_grid_spacing()`` is
|
|
37
|
+
``(unused, z, y, x)``.
|
|
38
|
+
- Voxel-map dimension indices 76, 77, 78, 79, 81 and 82 are set correctly.
|
|
39
|
+
- The executor resolves ``SIMIND_BIN`` and accepts executable paths that
|
|
40
|
+
contain spaces. The connector no longer changes the process working directory.
|
|
41
|
+
- Runtime switches apply to one run only. Output prefixes cannot escape the
|
|
42
|
+
output directory. Stale outputs are removed, and ``.win``, ``.smi`` and
|
|
43
|
+
``.dmi`` inputs are kept.
|
|
44
|
+
- Energy-window validation raises ``ValueError`` instead of failing an
|
|
45
|
+
``assert``.
|
|
46
|
+
- The Interfile loader honours data offsets, strips quoted values and rejects
|
|
47
|
+
truncated or oversized payloads. The SIMIND-to-STIR converter keeps data-file
|
|
48
|
+
lines and applies the radius scale factor.
|
|
49
|
+
- Attenuation utilities handle the ``rho*1000`` selector and STIR ``#`` header
|
|
50
|
+
keys, and reject unknown attenuation types.
|
|
51
|
+
- Image and acquisition builders validate array shapes, split multi-energy data
|
|
52
|
+
deterministically and no longer flip images. The DICOM builder handles missing
|
|
53
|
+
timing information and 2D pixel data.
|
|
54
|
+
- Backend adaptors are imported lazily, so the core package imports without
|
|
55
|
+
STIR, SIRF or PyTomography.
|
|
56
|
+
|
|
57
|
+
Removed
|
|
14
58
|
~~~~~~~
|
|
15
59
|
|
|
16
|
-
-
|
|
17
|
-
and external-SIMIND-install disclaimers in README/docs.
|
|
60
|
+
- The legacy ``scripts/simulation.py``.
|
|
18
61
|
|
|
19
|
-
|
|
20
|
-
|
|
62
|
+
1.0.1 - 2026-04-16
|
|
63
|
+
------------------
|
|
64
|
+
|
|
65
|
+
Fixed
|
|
66
|
+
~~~~~
|
|
67
|
+
|
|
68
|
+
- Corrected installation documentation to lead with the PyPI package install
|
|
69
|
+
path instead of editable development installs.
|
|
70
|
+
- Clarified that SIMIND is an external runtime dependency and must be available
|
|
71
|
+
as ``simind`` on ``PATH`` for local connector runs.
|
|
72
|
+
- Updated contributing and testing documentation to use Ruff for linting,
|
|
73
|
+
import sorting, and formatting checks.
|
|
74
|
+
- Updated examples documentation to include the DICOM-driven adaptor examples.
|
|
75
|
+
- Fixed the GitHub Actions coverage target after the import package rename.
|
|
76
|
+
- Fixed README links and release metadata for the patch release.
|
|
77
|
+
|
|
78
|
+
1.0.0 - 2026-04-16
|
|
79
|
+
------------------
|
|
21
80
|
|
|
22
81
|
**Breaking Changes**
|
|
23
82
|
~~~~~~~~~~~~~~~~~~~~
|
|
@@ -56,6 +115,10 @@ Added
|
|
|
56
115
|
Changed
|
|
57
116
|
~~~~~~~
|
|
58
117
|
|
|
118
|
+
- Renamed the PyPI distribution to ``simind-python-connector``.
|
|
119
|
+
- Renamed the import package to ``simind_python_connector``.
|
|
120
|
+
- Updated user-facing branding to ``simind-python-connector`` and added explicit non-affiliation
|
|
121
|
+
and external-SIMIND-install disclaimers in README/docs.
|
|
59
122
|
- ``SimindPythonConnector`` is now the central execution path for adaptor
|
|
60
123
|
workflows.
|
|
61
124
|
- ``StirSimindAdaptor`` and ``SirfSimindAdaptor`` now delegate SIMIND setup/run
|
|
@@ -13,7 +13,7 @@ sys.path.insert(0, os.path.abspath(".."))
|
|
|
13
13
|
project = "simind-python-connector"
|
|
14
14
|
copyright = f"{datetime.now().year}, Sam Porter, Efstathios Varzakis"
|
|
15
15
|
author = "Sam Porter, Efstathios Varzakis"
|
|
16
|
-
release = "
|
|
16
|
+
release = "2.0.0"
|
|
17
17
|
for dist_name in ("simind-python-connector",):
|
|
18
18
|
try:
|
|
19
19
|
release = importlib_metadata.version(dist_name)
|
|
@@ -32,10 +32,9 @@ Before submitting:
|
|
|
32
32
|
# Run all tests
|
|
33
33
|
python -m pytest tests/ -v
|
|
34
34
|
|
|
35
|
-
# Check
|
|
36
|
-
black --check .
|
|
35
|
+
# Check linting and formatting
|
|
37
36
|
ruff check .
|
|
38
|
-
|
|
37
|
+
ruff format --check .
|
|
39
38
|
|
|
40
39
|
Documentation Updates
|
|
41
40
|
---------------------
|
|
@@ -44,5 +43,4 @@ If you make changes that affect the documentation:
|
|
|
44
43
|
|
|
45
44
|
.. code-block:: bash
|
|
46
45
|
|
|
47
|
-
|
|
48
|
-
make html # Build documentation locally
|
|
46
|
+
sphinx-build -b html docs docs/_build/html
|
|
@@ -56,6 +56,18 @@ See the following examples for comprehensive use:
|
|
|
56
56
|
Simulate with the PyTomography adaptor and run PyTomography OSEM in one Python workflow.
|
|
57
57
|
Uses ``configs/Example.yaml`` plus Y-90 runtime switches (``FI`` and ``CC``).
|
|
58
58
|
|
|
59
|
+
8A. **STIR Adaptor from DICOM** - ``examples/08A_stir_adaptor_from_dicom.py``
|
|
60
|
+
|
|
61
|
+
Build a STIR adaptor workflow from DICOM-derived scanner/input data.
|
|
62
|
+
|
|
63
|
+
8B. **SIRF Adaptor from DICOM** - ``examples/08B_sirf_adaptor_from_dicom.py``
|
|
64
|
+
|
|
65
|
+
Build a SIRF adaptor workflow from DICOM-derived scanner/input data.
|
|
66
|
+
|
|
67
|
+
8C. **PyTomography Adaptor from DICOM** - ``examples/08C_pytomography_adaptor_from_dicom.py``
|
|
68
|
+
|
|
69
|
+
Build a PyTomography adaptor workflow from DICOM-derived scanner/input data.
|
|
70
|
+
|
|
59
71
|
Running Examples
|
|
60
72
|
----------------
|
|
61
73
|
|
|
@@ -72,13 +84,16 @@ Each example can be run individually:
|
|
|
72
84
|
python examples/07A_stir_adaptor_osem.py
|
|
73
85
|
python examples/07B_sirf_adaptor_osem.py
|
|
74
86
|
python examples/07C_pytomography_adaptor_osem.py
|
|
87
|
+
python examples/08A_stir_adaptor_from_dicom.py
|
|
88
|
+
python examples/08B_sirf_adaptor_from_dicom.py
|
|
89
|
+
python examples/08C_pytomography_adaptor_from_dicom.py
|
|
75
90
|
|
|
76
|
-
Or run all examples sequentially:
|
|
91
|
+
Or run all core Python connector examples sequentially:
|
|
77
92
|
|
|
78
93
|
.. code-block:: bash
|
|
79
94
|
|
|
80
95
|
cd scripts/
|
|
81
|
-
python run_all_examples.py
|
|
96
|
+
python run_all_examples.py
|
|
82
97
|
|
|
83
98
|
Or run one backend-specific adaptor example inside each isolated Docker image:
|
|
84
99
|
|
|
@@ -100,6 +115,9 @@ Each example creates output in its own directory under ``output/``:
|
|
|
100
115
|
- ``output/stir_adaptor_osem/`` - STIR adaptor simulation + STIR OSEM reconstruction
|
|
101
116
|
- ``output/sirf_adaptor_osem/`` - SIRF adaptor simulation + SIRF OSEM reconstruction
|
|
102
117
|
- ``output/pytomography_adaptor_osem/`` - PyTomography adaptor simulation + PyTomography OSEM reconstruction
|
|
118
|
+
- ``output/dicom_projection_objects/stir/`` - STIR adaptor setup from DICOM-derived inputs
|
|
119
|
+
- ``output/dicom_projection_objects/sirf/`` - SIRF adaptor setup from DICOM-derived inputs
|
|
120
|
+
- ``output/dicom_projection_objects/pytomography/`` - PyTomography adaptor setup from DICOM-derived inputs
|
|
103
121
|
|
|
104
122
|
Each OSEM example also writes a summary plot with:
|
|
105
123
|
- input source slice
|
|
@@ -35,10 +35,11 @@ Units
|
|
|
35
35
|
- STIR/SIRF image geometry is typically expressed in **mm**.
|
|
36
36
|
- Connectors handle conversion internally via voxel-size settings (for example,
|
|
37
37
|
runtime switch ``PX`` is set in cm for SIMIND).
|
|
38
|
-
- STIR/SIRF adaptor voxel size extraction expects z-spacing in mm.
|
|
39
|
-
``voxel_sizes()
|
|
40
|
-
|
|
41
|
-
|
|
38
|
+
- STIR/SIRF adaptor voxel size extraction expects z-spacing in mm. Images
|
|
39
|
+
report ``voxel_sizes()`` as ``(z, y, x)``, so the **first** element is the z
|
|
40
|
+
spacing. Raw ``get_grid_spacing()`` sequences follow the STIR coordinate
|
|
41
|
+
layout ``(unused, z, y, x)`` and use index 1; coordinate objects with a
|
|
42
|
+
``z`` accessor use that accessor directly.
|
|
42
43
|
|
|
43
44
|
Example Configuration Guardrails
|
|
44
45
|
--------------------------------
|
|
@@ -7,6 +7,8 @@ Prerequisites
|
|
|
7
7
|
-------------
|
|
8
8
|
|
|
9
9
|
- Python 3.9+
|
|
10
|
+
- A separate SIMIND installation for running simulations. SIMIND is not
|
|
11
|
+
distributed with this package or installed by pip.
|
|
10
12
|
- Optional backend libraries depending on workflow:
|
|
11
13
|
- STIR Python for STIR-based paths
|
|
12
14
|
- SIRF for SIRF-based paths
|
|
@@ -15,18 +17,34 @@ Prerequisites
|
|
|
15
17
|
Install the Python Package
|
|
16
18
|
--------------------------
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
Install the released package from PyPI:
|
|
19
21
|
|
|
20
|
-
|
|
22
|
+
.. code-block:: bash
|
|
23
|
+
|
|
24
|
+
pip install simind-python-connector
|
|
25
|
+
|
|
26
|
+
Import it with:
|
|
27
|
+
|
|
28
|
+
.. code-block:: python
|
|
29
|
+
|
|
30
|
+
import simind_python_connector
|
|
31
|
+
|
|
32
|
+
Install the examples extra if you want to run the plotting examples:
|
|
33
|
+
|
|
34
|
+
.. code-block:: bash
|
|
35
|
+
|
|
36
|
+
pip install "simind-python-connector[examples]"
|
|
21
37
|
|
|
22
|
-
|
|
23
|
-
|
|
38
|
+
Development Install
|
|
39
|
+
-------------------
|
|
24
40
|
|
|
25
|
-
|
|
41
|
+
For local development from a checkout:
|
|
26
42
|
|
|
27
|
-
|
|
43
|
+
.. code-block:: bash
|
|
28
44
|
|
|
29
|
-
|
|
45
|
+
git clone https://github.com/samdporter/simind-python-connector.git
|
|
46
|
+
cd simind-python-connector
|
|
47
|
+
pip install -e ".[dev,examples]"
|
|
30
48
|
|
|
31
49
|
SIMIND Requirement (External Dependency)
|
|
32
50
|
----------------------------------------
|
|
@@ -48,10 +66,10 @@ documentation:
|
|
|
48
66
|
- SIMIND manual page:
|
|
49
67
|
https://www.msf.lu.se/en/research/simind-monte-carlo-program/manual
|
|
50
68
|
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
Repo/Docker Layout
|
|
70
|
+
~~~~~~~~~~~~~~~~~~
|
|
53
71
|
|
|
54
|
-
For the
|
|
72
|
+
For the repository helper scripts and Docker setup, place SIMIND under:
|
|
55
73
|
|
|
56
74
|
.. code-block:: text
|
|
57
75
|
|
|
@@ -70,11 +88,19 @@ If needed, make the binary executable:
|
|
|
70
88
|
|
|
71
89
|
chmod +x ./simind/simind
|
|
72
90
|
|
|
73
|
-
Local
|
|
74
|
-
|
|
91
|
+
Local Runtime Environment
|
|
92
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
93
|
+
|
|
94
|
+
For direct local runs, ensure the SIMIND executable is available as ``simind``
|
|
95
|
+
on ``PATH``. If your SIMIND installation needs ``SMC_DIR``, set it to the
|
|
96
|
+
SIMIND data directory:
|
|
97
|
+
|
|
98
|
+
.. code-block:: bash
|
|
99
|
+
|
|
100
|
+
export PATH="/path/to/simind/bin:$PATH"
|
|
101
|
+
export SMC_DIR="/path/to/simind/smc_dir/"
|
|
75
102
|
|
|
76
|
-
|
|
77
|
-
the SIMIND data directory:
|
|
103
|
+
When working from this repository with the layout above, those commands become:
|
|
78
104
|
|
|
79
105
|
.. code-block:: bash
|
|
80
106
|
|
|
@@ -137,31 +137,36 @@ The default CI job runs the tests that avoid heavyweight dependencies. These foc
|
|
|
137
137
|
|
|
138
138
|
* **Conversion maths** – ``tests/test_schneider_density.py`` exercises the HU-to-density pipelines.
|
|
139
139
|
* **Configuration & builders** – ``tests/test_simulation_config.py`` and ``tests/test_acquisition_builder_unit.py`` validate YAML/SMC handling plus Interfile header generation without a SIRF runtime.
|
|
140
|
-
* **
|
|
141
|
-
* **
|
|
140
|
+
* **Connector behavior** – ``tests/test_python_connector.py``, ``tests/test_connectors_base.py``, and ``tests/test_connector_backend_separation.py`` cover the connector-first API without requiring reconstruction packages.
|
|
141
|
+
* **Utility helpers** – ``tests/test_utils.py``, ``tests/test_utils_small.py``, ``tests/test_interfile_parser.py``, and ``tests/test_interfile_numpy.py`` cover file parsing and small helper functions.
|
|
142
|
+
* **Backend guards** – ``tests/test_backends.py``, ``tests/test_marker_policy.py``, and ``tests/test_examples_backend_separation.py`` ensure optional backend imports remain isolated.
|
|
142
143
|
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
Backend-Dependent Suites
|
|
145
|
+
~~~~~~~~~~~~~~~~~~~~~~~~
|
|
145
146
|
|
|
146
|
-
Tests that construct real SIRF/
|
|
147
|
+
Tests that construct real STIR/SIRF/PyTomography objects carry dependency
|
|
148
|
+
markers and are skipped when those packages are unavailable. These include:
|
|
147
149
|
|
|
148
|
-
* ``tests/
|
|
149
|
-
* ``tests/
|
|
150
|
-
* ``tests/
|
|
151
|
-
* SIRF-backed helpers in ``tests/test_utils.py`` and ``tests/test_components.py``.
|
|
150
|
+
* ``tests/test_native_adaptors.py`` for STIR/SIRF adaptor behavior.
|
|
151
|
+
* ``tests/test_pytomography_adaptor.py`` for PyTomography tensor and output adaptation.
|
|
152
|
+
* ``tests/test_arithmetic_operations.py`` and ``tests/test_sirf_stir_utils.py`` for backend wrapper/helper behavior.
|
|
152
153
|
|
|
153
|
-
|
|
154
|
-
|
|
154
|
+
SIMIND and Container Diagnostics
|
|
155
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
155
156
|
|
|
156
|
-
|
|
157
|
+
SIMIND-dependent tests use ``requires_simind`` and are skipped unless the
|
|
158
|
+
``simind`` command is available. Container and geometry diagnostics include:
|
|
157
159
|
|
|
158
|
-
* ``tests/
|
|
159
|
-
* ``tests/
|
|
160
|
+
* ``tests/test_integration.py`` for full workflow checks.
|
|
161
|
+
* ``tests/test_container_library_isolation.py`` for backend container import isolation.
|
|
162
|
+
* ``tests/test_geometry_isolation_forward_projection.py`` and ``tests/test_osem_geometry_diagnostics.py`` for backend geometry diagnostics.
|
|
160
163
|
|
|
161
164
|
Integration Test
|
|
162
165
|
~~~~~~~~~~~~~~~~
|
|
163
166
|
|
|
164
|
-
``tests/test_integration.py`` orchestrates a full example run and needs
|
|
167
|
+
``tests/test_integration.py`` orchestrates a full example run and needs
|
|
168
|
+
external runtime dependencies. It is marked with dependency markers so it is
|
|
169
|
+
skipped in lightweight CI environments.
|
|
165
170
|
|
|
166
171
|
Configuration Files
|
|
167
172
|
-------------------
|
|
@@ -223,7 +228,7 @@ Continuous Integration
|
|
|
223
228
|
GitHub Actions is used to run tests automatically. The CI workflow:
|
|
224
229
|
|
|
225
230
|
1. **Installs only basic Python dependencies** (no SIRF/SIMIND)
|
|
226
|
-
2. **Runs code quality checks** (
|
|
231
|
+
2. **Runs code quality checks** (``ruff check`` and ``ruff format --check``)
|
|
227
232
|
3. **Executes CI-friendly tests** using dependency markers
|
|
228
233
|
4. **Generates coverage reports** for the tested code
|
|
229
234
|
5. **Builds and validates the package**
|
|
@@ -9,6 +9,10 @@ Quick Start
|
|
|
9
9
|
Use ``SimindPythonConnector`` when you want direct Python control of SIMIND
|
|
10
10
|
inputs/outputs without any reconstruction-package dependency.
|
|
11
11
|
|
|
12
|
+
The Python package does not bundle SIMIND. Before calling ``run()``, make sure
|
|
13
|
+
the SIMIND executable is available as ``simind`` on ``PATH``; see
|
|
14
|
+
:doc:`installation`.
|
|
15
|
+
|
|
12
16
|
.. code-block:: python
|
|
13
17
|
|
|
14
18
|
import numpy as np
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "simind-python-connector"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
description = "Python SIMIND Monte Carlo connector with STIR/SIRF/PyTomography adaptors"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.9"
|
|
@@ -19,6 +19,7 @@ Usage:
|
|
|
19
19
|
|
|
20
20
|
import importlib
|
|
21
21
|
import logging
|
|
22
|
+
import os
|
|
22
23
|
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
|
23
24
|
|
|
24
25
|
from .base import AcquisitionDataInterface, ImageDataInterface
|
|
@@ -233,6 +234,9 @@ def create_image_data(
|
|
|
233
234
|
if isinstance(filepath_or_object, ImageDataInterface):
|
|
234
235
|
return filepath_or_object
|
|
235
236
|
|
|
237
|
+
if isinstance(filepath_or_object, os.PathLike):
|
|
238
|
+
filepath_or_object = os.fspath(filepath_or_object)
|
|
239
|
+
|
|
236
240
|
backend_hint = detect_image_backend(filepath_or_object)
|
|
237
241
|
if backend_hint is None:
|
|
238
242
|
backend_hint = detect_backend_from_interface(filepath_or_object)
|
|
@@ -314,6 +318,9 @@ def create_acquisition_data(
|
|
|
314
318
|
if isinstance(filepath_or_object, AcquisitionDataInterface):
|
|
315
319
|
return filepath_or_object
|
|
316
320
|
|
|
321
|
+
if isinstance(filepath_or_object, os.PathLike):
|
|
322
|
+
filepath_or_object = os.fspath(filepath_or_object)
|
|
323
|
+
|
|
317
324
|
backend_hint = detect_acquisition_backend(filepath_or_object)
|
|
318
325
|
if backend_hint is None:
|
|
319
326
|
backend_hint = detect_backend_from_interface(filepath_or_object)
|
|
@@ -97,6 +97,19 @@ class STIRSPECTAcquisitionDataBuilder:
|
|
|
97
97
|
else:
|
|
98
98
|
self.pixel_array = np.array(self.pixel_array, dtype=np.float32)
|
|
99
99
|
|
|
100
|
+
expected_shape = (
|
|
101
|
+
1,
|
|
102
|
+
matrix_size_1,
|
|
103
|
+
num_projections,
|
|
104
|
+
matrix_size_2,
|
|
105
|
+
)
|
|
106
|
+
if self.pixel_array.shape != expected_shape:
|
|
107
|
+
raise ValueError(
|
|
108
|
+
f"pixel array shape {self.pixel_array.shape} does not match "
|
|
109
|
+
f"header dimensions {expected_shape} "
|
|
110
|
+
"(tof, bin, view, axial)"
|
|
111
|
+
)
|
|
112
|
+
|
|
100
113
|
def _write(base_path: Path, cleanup: bool) -> AcquisitionData:
|
|
101
114
|
header_path = base_path.with_suffix(".hs")
|
|
102
115
|
raw_file_path = base_path.with_suffix(".s")
|
|
@@ -112,9 +125,8 @@ class STIRSPECTAcquisitionDataBuilder:
|
|
|
112
125
|
|
|
113
126
|
acqdata = self._load_acquisition(str(header_path))
|
|
114
127
|
|
|
115
|
-
flipped = np.flip(self.pixel_array, axis=-1)
|
|
116
128
|
acqdata = acqdata.clone()
|
|
117
|
-
acqdata.fill(
|
|
129
|
+
acqdata.fill(self.pixel_array)
|
|
118
130
|
acqdata.write(str(header_path))
|
|
119
131
|
|
|
120
132
|
if cleanup:
|
|
@@ -194,27 +206,50 @@ class STIRSPECTAcquisitionDataBuilder:
|
|
|
194
206
|
acqdata = self.build(output_path=output_path_base)
|
|
195
207
|
return [acqdata]
|
|
196
208
|
|
|
209
|
+
if self.pixel_array is None:
|
|
210
|
+
raise ValueError(
|
|
211
|
+
"build_multi_energy requires pixel_array to be set "
|
|
212
|
+
"(use update_header_from_dicom or set it directly)"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
num_windows = len(self.energy_windows)
|
|
216
|
+
total_projections = int(self.header.get("!number of projections", 1))
|
|
217
|
+
if self.pixel_array.ndim != 4 or (
|
|
218
|
+
self.pixel_array.shape[2] != total_projections
|
|
219
|
+
):
|
|
220
|
+
raise ValueError(
|
|
221
|
+
f"pixel array shape {self.pixel_array.shape} does not match "
|
|
222
|
+
f"declared {total_projections} projections"
|
|
223
|
+
)
|
|
224
|
+
if total_projections % num_windows != 0:
|
|
225
|
+
raise ValueError(
|
|
226
|
+
f"Number of projections ({total_projections}) must be divisible "
|
|
227
|
+
f"by the number of energy windows ({num_windows})"
|
|
228
|
+
)
|
|
229
|
+
projections_per_window = total_projections // num_windows
|
|
230
|
+
|
|
197
231
|
# number of projections needs dividing by number of energy windows
|
|
198
|
-
|
|
199
|
-
num_projections //= len(self.energy_windows)
|
|
200
|
-
self.header["!number of projections"] = str(num_projections)
|
|
232
|
+
self.header["!number of projections"] = str(projections_per_window)
|
|
201
233
|
|
|
202
234
|
# split pixel_array into energy windows along 3rd axis
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
235
|
+
original_pixel_array = self.pixel_array
|
|
236
|
+
try:
|
|
237
|
+
pixel_array_list = np.array_split(original_pixel_array, num_windows, axis=2)
|
|
238
|
+
|
|
239
|
+
acqdata_list = []
|
|
240
|
+
for idx, ew in enumerate(self.energy_windows):
|
|
241
|
+
# Update header for this energy window.
|
|
242
|
+
self.header["energy window lower level[1]"] = ew["lower"]
|
|
243
|
+
self.header["energy window upper level[1]"] = ew["upper"]
|
|
244
|
+
suffix = f"_ew{idx + 1}"
|
|
245
|
+
output_path = output_path_base + suffix
|
|
246
|
+
self.pixel_array = pixel_array_list[idx]
|
|
247
|
+
acqdata = self.build(output_path=output_path)
|
|
248
|
+
acqdata_list.append(acqdata)
|
|
249
|
+
return acqdata_list
|
|
250
|
+
finally:
|
|
251
|
+
self.pixel_array = original_pixel_array
|
|
252
|
+
self.header["!number of projections"] = str(total_projections)
|
|
218
253
|
|
|
219
254
|
def update_header_from_dicom(self, dicom_filepath):
|
|
220
255
|
"""
|
|
@@ -303,6 +338,8 @@ class STIRSPECTAcquisitionDataBuilder:
|
|
|
303
338
|
)
|
|
304
339
|
|
|
305
340
|
# Rotation Information Sequence processing
|
|
341
|
+
num_frames = ds.get("NumberOfFrames", None)
|
|
342
|
+
time_per_projection = None
|
|
306
343
|
try:
|
|
307
344
|
if (0x0054, 0x0052) in ds:
|
|
308
345
|
rot_seq = ds[(0x0054, 0x0052)].value
|
|
@@ -322,16 +359,17 @@ class STIRSPECTAcquisitionDataBuilder:
|
|
|
322
359
|
rot_item[(0x0018, 0x1242)].value / 1000
|
|
323
360
|
)
|
|
324
361
|
|
|
325
|
-
if num_frames is not None:
|
|
362
|
+
if time_per_projection is not None and num_frames is not None:
|
|
326
363
|
self.header["number of time frames"] = str(1)
|
|
327
364
|
self.header["!image duration (sec)[1]"] = str(
|
|
328
365
|
int(
|
|
329
366
|
np.round(
|
|
330
|
-
float(time_per_projection) * float(num_frames),
|
|
367
|
+
float(time_per_projection) * float(num_frames),
|
|
368
|
+
0,
|
|
331
369
|
)
|
|
332
370
|
)
|
|
333
371
|
)
|
|
334
|
-
|
|
372
|
+
elif time_per_projection is not None:
|
|
335
373
|
self.header["!time per projection (sec)[1]"] = (
|
|
336
374
|
time_per_projection
|
|
337
375
|
)
|
|
@@ -516,11 +554,21 @@ class STIRSPECTAcquisitionDataBuilder:
|
|
|
516
554
|
except AttributeError:
|
|
517
555
|
warnings.warn("StudyDescription not found in DICOM.")
|
|
518
556
|
try:
|
|
519
|
-
|
|
520
|
-
print(self.pixel_array.shape)
|
|
521
|
-
self.pixel_array = np.transpose(self.pixel_array, (2, 0, 1))
|
|
522
|
-
# rotate the image by 90 degrees cW in axis 1
|
|
523
|
-
self.pixel_array = np.rot90(self.pixel_array, 3, axes=(0, 2))
|
|
524
|
-
self.pixel_array = np.expand_dims(self.pixel_array, axis=0)
|
|
557
|
+
raw_pixel_array = ds.pixel_array
|
|
525
558
|
except AttributeError:
|
|
526
559
|
warnings.warn("Pixel data not found in DICOM.")
|
|
560
|
+
return
|
|
561
|
+
|
|
562
|
+
raw_pixel_array = np.asarray(raw_pixel_array)
|
|
563
|
+
if raw_pixel_array.ndim == 2:
|
|
564
|
+
# Single frame: promote to (frames=1, rows, columns) so the
|
|
565
|
+
# transpose below sees the same layout as multiframe data.
|
|
566
|
+
raw_pixel_array = raw_pixel_array[np.newaxis, :, :]
|
|
567
|
+
elif raw_pixel_array.ndim != 3:
|
|
568
|
+
raise ValueError(
|
|
569
|
+
f"pixel data must be 2D or 3D, got {raw_pixel_array.ndim}D"
|
|
570
|
+
)
|
|
571
|
+
self.pixel_array = np.transpose(raw_pixel_array, (2, 0, 1))
|
|
572
|
+
# rotate the image by 90 degrees cW in axis 1
|
|
573
|
+
self.pixel_array = np.rot90(self.pixel_array, 3, axes=(0, 2))
|
|
574
|
+
self.pixel_array = np.expand_dims(self.pixel_array, axis=0)
|