simind-python-connector 1.0.0__tar.gz → 1.1.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.
Files changed (134) hide show
  1. {simind_python_connector-1.0.0/simind_python_connector.egg-info → simind_python_connector-1.1.0}/PKG-INFO +12 -3
  2. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/README.md +11 -2
  3. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/backends.rst +1 -1
  4. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/changelog.rst +88 -7
  5. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/conf.py +1 -1
  6. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/contributing.rst +3 -5
  7. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0/docs}/examples/07A_stir_adaptor_osem.py +4 -0
  8. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0/docs}/examples/07B_sirf_adaptor_osem.py +4 -0
  9. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/07C_pytomography_adaptor_osem.py +4 -0
  10. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples.rst +20 -2
  11. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/geometry.rst +28 -4
  12. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/index.rst +1 -1
  13. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/installation.rst +40 -14
  14. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/testing.rst +21 -16
  15. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/usage.rst +4 -0
  16. {simind_python_connector-1.0.0/docs → simind_python_connector-1.1.0}/examples/07A_stir_adaptor_osem.py +4 -0
  17. {simind_python_connector-1.0.0/docs → simind_python_connector-1.1.0}/examples/07B_sirf_adaptor_osem.py +4 -0
  18. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/07C_pytomography_adaptor_osem.py +4 -0
  19. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/pyproject.toml +1 -1
  20. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/__init__.py +1 -1
  21. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/backends/__init__.py +7 -0
  22. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/builders/acquisition_builder.py +77 -29
  23. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/builders/image_builder.py +18 -6
  24. simind_python_connector-1.1.0/simind_python_connector/connectors/__init__.py +43 -0
  25. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/connectors/_spacing.py +13 -7
  26. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/connectors/python_connector.py +146 -27
  27. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/connectors/pytomography_adaptor.py +5 -0
  28. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/connectors/sirf_adaptor.py +3 -0
  29. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/connectors/stir_adaptor.py +3 -0
  30. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/converters/attenuation.py +3 -1
  31. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/converters/simind_to_stir.py +12 -10
  32. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/core/config.py +208 -66
  33. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/core/executor.py +36 -7
  34. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/utils/import_helpers.py +4 -0
  35. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/utils/interfile_numpy.py +18 -8
  36. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/utils/interfile_parser.py +4 -2
  37. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/utils/simind_utils.py +50 -11
  38. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/utils/stir_utils.py +7 -2
  39. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0/simind_python_connector.egg-info}/PKG-INFO +12 -3
  40. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector.egg-info/SOURCES.txt +7 -0
  41. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_acquisition_builder_unit.py +77 -2
  42. simind_python_connector-1.1.0/tests/test_attenuation_utils.py +116 -0
  43. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_backends.py +78 -0
  44. simind_python_connector-1.1.0/tests/test_connector_backend_separation.py +118 -0
  45. simind_python_connector-1.1.0/tests/test_container_scripts.py +290 -0
  46. simind_python_connector-1.1.0/tests/test_dicom_builder.py +156 -0
  47. simind_python_connector-1.1.0/tests/test_executor.py +217 -0
  48. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_image_builder_unit.py +23 -0
  49. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_interfile_numpy.py +128 -4
  50. simind_python_connector-1.1.0/tests/test_legacy_surface_policy.py +20 -0
  51. simind_python_connector-1.1.0/tests/test_marker_policy.py +117 -0
  52. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_native_adaptors.py +108 -12
  53. simind_python_connector-1.1.0/tests/test_non_square_slices_simind.py +64 -0
  54. simind_python_connector-1.1.0/tests/test_packaging_resources.py +113 -0
  55. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_python_connector.py +318 -0
  56. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_pytomography_adaptor.py +85 -0
  57. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_schneider_density.py +3 -0
  58. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_simind_to_stir_converter.py +41 -9
  59. simind_python_connector-1.1.0/tests/test_simulation_config.py +208 -0
  60. simind_python_connector-1.1.0/tests/test_utils_small.py +96 -0
  61. simind_python_connector-1.0.0/simind_python_connector/connectors/__init__.py +0 -24
  62. simind_python_connector-1.0.0/tests/test_connector_backend_separation.py +0 -42
  63. simind_python_connector-1.0.0/tests/test_marker_policy.py +0 -72
  64. simind_python_connector-1.0.0/tests/test_simulation_config.py +0 -89
  65. simind_python_connector-1.0.0/tests/test_utils_small.py +0 -53
  66. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/LICENSE +0 -0
  67. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/MANIFEST.in +0 -0
  68. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/api.rst +0 -0
  69. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/connector_modernization_plan.rst +0 -0
  70. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/01_basic_simulation.py +0 -0
  71. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/02_runtime_switch_comparison.py +0 -0
  72. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/03_multi_window.py +0 -0
  73. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/04_custom_config.py +0 -0
  74. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/05_scattwin_vs_penetrate_comparison.py +0 -0
  75. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/06_schneider_density_conversion.py +0 -0
  76. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/08A_stir_adaptor_from_dicom.py +0 -0
  77. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/08B_sirf_adaptor_from_dicom.py +0 -0
  78. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/08C_pytomography_adaptor_from_dicom.py +0 -0
  79. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/examples/_python_connector_helpers.py +0 -0
  80. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/docs/intro.rst +0 -0
  81. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/01_basic_simulation.py +0 -0
  82. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/02_runtime_switch_comparison.py +0 -0
  83. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/03_multi_window.py +0 -0
  84. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/04_custom_config.py +0 -0
  85. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/05_scattwin_vs_penetrate_comparison.py +0 -0
  86. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/06_schneider_density_conversion.py +0 -0
  87. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/08A_stir_adaptor_from_dicom.py +0 -0
  88. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/08B_sirf_adaptor_from_dicom.py +0 -0
  89. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/08C_pytomography_adaptor_from_dicom.py +0 -0
  90. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/examples/_python_connector_helpers.py +0 -0
  91. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/requirements-dev.txt +0 -0
  92. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/requirements.txt +0 -0
  93. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/setup.cfg +0 -0
  94. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/backends/base.py +0 -0
  95. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/backends/sirf_backend.py +0 -0
  96. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/backends/stir_backend.py +0 -0
  97. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/builders/__init__.py +0 -0
  98. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/configs/AnyScan.yaml +0 -0
  99. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/configs/Discovery670.yaml +0 -0
  100. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/configs/Example.yaml +0 -0
  101. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/configs/MLD001_SCAN0.yaml +0 -0
  102. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/configs/__init__.py +0 -0
  103. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/configs/input.smc +0 -0
  104. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/connectors/base.py +0 -0
  105. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/converters/__init__.py +0 -0
  106. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/converters/dicom_to_stir.py +0 -0
  107. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/core/__init__.py +0 -0
  108. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/core/types.py +0 -0
  109. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/data/Schneider2000.json +0 -0
  110. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/data/__init__.py +0 -0
  111. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/data/bone.atn +0 -0
  112. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/data/h2o.atn +0 -0
  113. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/utils/__init__.py +0 -0
  114. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/utils/backend_access.py +0 -0
  115. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/utils/io_utils.py +0 -0
  116. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector/utils/sirf_stir_utils.py +0 -0
  117. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector.egg-info/dependency_links.txt +0 -0
  118. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector.egg-info/requires.txt +0 -0
  119. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/simind_python_connector.egg-info/top_level.txt +0 -0
  120. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/__init__.py +0 -0
  121. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/conftest.py +0 -0
  122. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_arithmetic_operations.py +0 -0
  123. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_connectors_base.py +0 -0
  124. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_container_library_isolation.py +0 -0
  125. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_example_config_profile.py +0 -0
  126. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_examples_backend_separation.py +0 -0
  127. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_geometry_isolation_forward_projection.py +0 -0
  128. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_integration.py +0 -0
  129. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_interfile_parser.py +0 -0
  130. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_osem_geometry_diagnostics.py +0 -0
  131. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_package_identity.py +0 -0
  132. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_sirf_stir_utils.py +0 -0
  133. {simind_python_connector-1.0.0 → simind_python_connector-1.1.0}/tests/test_stir_utils_backend_choice_unit.py +0 -0
  134. {simind_python_connector-1.0.0 → simind_python_connector-1.1.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: 1.0.0
3
+ Version: 1.1.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 path remains:
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 our [Contributing Guide](CONTRIBUTING.md).
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 path remains:
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 our [Contributing Guide](CONTRIBUTING.md).
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 only (no SIRF/STIR/PyTomography requirement)
17
+ - SIMIND executable on ``PATH`` (no SIRF/STIR/PyTomography requirement)
18
18
  * - ``StirSimindAdaptor``
19
19
  - STIR Python (``stir``)
20
20
  * - ``SirfSimindAdaptor``
@@ -7,17 +7,94 @@ 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
- Unreleased
11
- ----------
10
+ 1.1.0 - 2026-10-11
11
+ ------------------
12
12
 
13
- Changed
13
+ Replaces 2.0.0 and 3.0.0, which were published by mistake and have been
14
+ withdrawn.
15
+
16
+ Breaking Changes
17
+ ~~~~~~~~~~~~~~~~
18
+
19
+ - The projection bin size and matrix (Index 28, 76, 77) come from the
20
+ configuration. ``configure_voxel_phantom`` no longer sets them from the
21
+ phantom image, which made the projections square whenever the image slices
22
+ were square and took the number of axial rows from the image height.
23
+ - ``configure_voxel_phantom`` keeps the configured scoring routine (Index 84)
24
+ unless ``scoring_routine`` is given.
25
+ - ``STIRSPECTAcquisitionDataBuilder`` no longer flips projection data along the
26
+ last axis.
27
+ - Runtime-operator switches passed to ``run()`` apply to that run only. They
28
+ are no longer kept on the connector for later runs.
29
+ - Each run deletes earlier outputs that share the output prefix before SIMIND
30
+ starts.
31
+ - The STIR backend needs ``stirextra`` as well as ``stir``.
32
+ - STIR/SIRF adaptors take the z voxel spacing from a different axis (see
33
+ Fixed). Results change for anisotropic voxels.
34
+ - Invalid inputs now raise ``ValueError``: empty, non-finite or negative source
35
+ and attenuation maps, array shapes that do not match the header, and
36
+ ``output_prefix`` values that contain a path. Energy-window validation raises
37
+ ``ValueError`` instead of ``AssertionError``.
38
+
39
+ Added
40
+ ~~~~~
41
+
42
+ - ``run()`` warns when a voxel phantom is wider or longer than the projection
43
+ field of view.
44
+
45
+ Fixed
46
+ ~~~~~
47
+
48
+ - The packaged ``configs/input.smc`` is now included in the wheel. SMC parsing
49
+ handles fixed-width fields and checks section counts.
50
+ - STIR/SIRF adaptors read the z voxel spacing from the correct axis:
51
+ ``voxel_sizes()`` is ``(z, y, x)``, and raw ``get_grid_spacing()`` is
52
+ ``(unused, z, y, x)``.
53
+ - Voxel-map dimension indices 78, 79, 81 and 82 are set correctly.
54
+ - The executor resolves ``SIMIND_BIN`` and accepts executable paths that
55
+ contain spaces. The connector no longer changes the process working directory.
56
+ - Runtime switches apply to one run only. Output prefixes cannot escape the
57
+ output directory. Stale outputs are removed, and ``.win``, ``.smi`` and
58
+ ``.dmi`` inputs are kept.
59
+ - Energy-window validation raises ``ValueError`` instead of failing an
60
+ ``assert``.
61
+ - The Interfile loader honours data offsets, strips quoted values and rejects
62
+ truncated or oversized payloads. The SIMIND-to-STIR converter keeps data-file
63
+ lines and applies the radius scale factor.
64
+ - Attenuation utilities handle the ``rho*1000`` selector and STIR ``#`` header
65
+ keys, and reject unknown attenuation types.
66
+ - Image and acquisition builders validate array shapes, split multi-energy data
67
+ deterministically and no longer flip images. The DICOM builder handles missing
68
+ timing information and 2D pixel data.
69
+ - Backend adaptors are imported lazily, so the core package imports without
70
+ STIR, SIRF or PyTomography.
71
+
72
+ Removed
14
73
  ~~~~~~~
15
74
 
16
- - Updated user-facing branding to ``simind-python-connector`` and added explicit non-affiliation
17
- and external-SIMIND-install disclaimers in README/docs.
75
+ - The legacy ``scripts/simulation.py``.
76
+ - The "non-square matrix" warning from
77
+ ``SimulationConfig.validate_parameters``; SIMIND supports non-square
78
+ projections.
18
79
 
19
- [1.0.0] - 2026-03-05
20
- --------------------
80
+ 1.0.1 - 2026-04-16
81
+ ------------------
82
+
83
+ Fixed
84
+ ~~~~~
85
+
86
+ - Corrected installation documentation to lead with the PyPI package install
87
+ path instead of editable development installs.
88
+ - Clarified that SIMIND is an external runtime dependency and must be available
89
+ as ``simind`` on ``PATH`` for local connector runs.
90
+ - Updated contributing and testing documentation to use Ruff for linting,
91
+ import sorting, and formatting checks.
92
+ - Updated examples documentation to include the DICOM-driven adaptor examples.
93
+ - Fixed the GitHub Actions coverage target after the import package rename.
94
+ - Fixed README links and release metadata for the patch release.
95
+
96
+ 1.0.0 - 2026-04-16
97
+ ------------------
21
98
 
22
99
  **Breaking Changes**
23
100
  ~~~~~~~~~~~~~~~~~~~~
@@ -56,6 +133,10 @@ Added
56
133
  Changed
57
134
  ~~~~~~~
58
135
 
136
+ - Renamed the PyPI distribution to ``simind-python-connector``.
137
+ - Renamed the import package to ``simind_python_connector``.
138
+ - Updated user-facing branding to ``simind-python-connector`` and added explicit non-affiliation
139
+ and external-SIMIND-install disclaimers in README/docs.
59
140
  - ``SimindPythonConnector`` is now the central execution path for adaptor
60
141
  workflows.
61
142
  - ``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 = "1.0.0"
16
+ release = "1.1.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 code formatting
36
- black --check .
35
+ # Check linting and formatting
37
36
  ruff check .
38
- isort --check-only .
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
- cd docs/
48
- make html # Build documentation locally
46
+ sphinx-build -b html docs docs/_build/html
@@ -188,6 +188,10 @@ def main() -> None:
188
188
  adaptor.add_config_value(1, 140.0)
189
189
  adaptor.add_config_value(19, 2)
190
190
  adaptor.add_config_value(53, 0)
191
+ # Projections on the reconstruction grid: 32 x 32 bins of 4 mm (Index 28 in cm).
192
+ adaptor.add_config_value(28, 0.4)
193
+ adaptor.add_config_value(76, 32)
194
+ adaptor.add_config_value(77, 32)
191
195
  adaptor.run()
192
196
 
193
197
  projection = adaptor.get_total_output(window=1)
@@ -170,6 +170,10 @@ def main() -> None:
170
170
  adaptor.add_config_value(1, 140.0)
171
171
  adaptor.add_config_value(19, 2)
172
172
  adaptor.add_config_value(53, 0)
173
+ # Projections on the reconstruction grid: 32 x 32 bins of 4 mm (Index 28 in cm).
174
+ adaptor.add_config_value(28, 0.4)
175
+ adaptor.add_config_value(76, 32)
176
+ adaptor.add_config_value(77, 32)
173
177
  adaptor.run()
174
178
 
175
179
  projection = adaptor.get_total_output(window=1)
@@ -144,6 +144,10 @@ def main() -> None:
144
144
  adaptor.add_config_value(1, 140.0)
145
145
  adaptor.add_config_value(19, 2)
146
146
  adaptor.add_config_value(53, 0)
147
+ # Projections on the reconstruction grid: 32 x 32 bins of 4 mm (Index 28 in cm).
148
+ adaptor.add_config_value(28, 0.4)
149
+ adaptor.add_config_value(76, 32)
150
+ adaptor.add_config_value(77, 32)
147
151
  adaptor.run()
148
152
 
149
153
  projection_tensor = adaptor.get_total_output(window=1).to(dtype=torch.float32)
@@ -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 # Run all examples sequentially
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,32 @@ 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. It uses
39
- ``voxel_sizes()[2]`` when available, and falls back to ``get_grid_spacing()``
40
- using the last spatial element (index 3 for 4-element spacing, index 2 for
41
- 3-element spacing).
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.
43
+
44
+ Projection Size
45
+ ---------------
46
+
47
+ The projection bin size and matrix come from the configuration: Index 28 (bin
48
+ size in cm), Index 76 (bins) and Index 77 (axial rows). Configuring a voxel
49
+ phantom does not change them, so set them to cover the phantom; ``run()`` warns
50
+ when the phantom is wider or longer than the projection field of view. The
51
+ matrix does not have to be square.
52
+
53
+ Some projectors need the projections sampled like the image. STIR's SPECTUB
54
+ needs equal axial sampling, so the OSEM examples set Index 28 to the voxel size.
55
+
56
+ Voxel Phantom Slices
57
+ --------------------
58
+
59
+ Image slices do not have to be square. The columns (x, Index 78 and 79) and
60
+ rows (y, Index 81 and 82) of the source and density maps are set separately,
61
+ and a SIMIND test checks that non-square slices give the same projections as
62
+ the same slices padded to square. The voxels themselves must be square in-plane:
63
+ SIMIND takes a single voxel side (Index 31 and the ``PX`` switch).
42
64
 
43
65
  Example Configuration Guardrails
44
66
  --------------------------------
@@ -50,6 +72,8 @@ are reproducible:
50
72
  - ``config[29]=24`` for projection count.
51
73
  - ``config[53]=0`` to keep collimator modeling geometric-only in these tests.
52
74
  - ``config[19]=2`` to keep a consistent mapping used by current examples.
75
+ - ``config[28]=0.4``, ``config[76]=32`` and ``config[77]=32`` to put the
76
+ projections on the 4 mm, 32-voxel reconstruction grid.
53
77
 
54
78
  Debug Checklist
55
79
  ---------------
@@ -1,5 +1,5 @@
1
1
  simind-python-connector Documentation
2
- ====================
2
+ =====================================
3
3
 
4
4
  .. toctree::
5
5
  :maxdepth: 2
@@ -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
- 1. Clone the repository:
20
+ Install the released package from PyPI:
19
21
 
20
- .. code-block:: bash
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
- git clone https://github.com/samdporter/simind-python-connector.git
23
- cd simind-python-connector
38
+ Development Install
39
+ -------------------
24
40
 
25
- 2. Install in editable/development mode:
41
+ For local development from a checkout:
26
42
 
27
- .. code-block:: bash
43
+ .. code-block:: bash
28
44
 
29
- pip install -e ".[dev]"
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
- Recommended Local Layout
52
- ~~~~~~~~~~~~~~~~~~~~~~~~
69
+ Repo/Docker Layout
70
+ ~~~~~~~~~~~~~~~~~~
53
71
 
54
- For the package scripts and Docker setup, place SIMIND under:
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 (Non-Docker) Runtime Environment
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
- For direct local runs, ensure SIMIND is on ``PATH`` and ``SMC_DIR`` points to
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
- * **Utility helpers** – ``tests/test_components.py``, ``tests/test_utils.py``, and ``tests/test_utils_small.py`` cover enums, file parsing, and temporary-directory/energy-window helpers.
141
- * **Backend guards** – ``tests/test_backends.py`` ensures the dynamic backend selection code behaves without importing SIRF.
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
- SIRF-Dependent Suites
144
- ~~~~~~~~~~~~~~~~~~~~~
144
+ Backend-Dependent Suites
145
+ ~~~~~~~~~~~~~~~~~~~~~~~~
145
146
 
146
- Tests that construct real SIRF/STIR objects carry the ``requires_sirf`` marker and are skipped in CI. These include:
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/test_simind_simulator.py`` and ``tests/test_simind_projector.py`` for the legacy projector/simulator path.
149
- * ``tests/test_arithmetic_operations.py`` for wrapped acquisition arithmetic.
150
- * ``tests/test_coordinator.py`` for the SIMIND coordinator.
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
- CIL and Hybrid Dependencies
154
- ~~~~~~~~~~~~~~~~~~~~~~~~~~~
154
+ SIMIND and Container Diagnostics
155
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
155
156
 
156
- Two suites rely on additional packages:
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/test_cil_partitioner.py`` (``requires_sirf`` + ``requires_cil``) exercises the CIL partitioner adapter.
159
- * ``tests/test_step_size_rules.py`` (``requires_cil``) checks Armijo step-size helpers with lightweight stand-ins.
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 both SIMIND and SIRF. It is marked with ``integration``, ``requires_simind``, and ``requires_sirf``.
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** (black, isort, ruff)
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
@@ -188,6 +188,10 @@ def main() -> None:
188
188
  adaptor.add_config_value(1, 140.0)
189
189
  adaptor.add_config_value(19, 2)
190
190
  adaptor.add_config_value(53, 0)
191
+ # Projections on the reconstruction grid: 32 x 32 bins of 4 mm (Index 28 in cm).
192
+ adaptor.add_config_value(28, 0.4)
193
+ adaptor.add_config_value(76, 32)
194
+ adaptor.add_config_value(77, 32)
191
195
  adaptor.run()
192
196
 
193
197
  projection = adaptor.get_total_output(window=1)
@@ -170,6 +170,10 @@ def main() -> None:
170
170
  adaptor.add_config_value(1, 140.0)
171
171
  adaptor.add_config_value(19, 2)
172
172
  adaptor.add_config_value(53, 0)
173
+ # Projections on the reconstruction grid: 32 x 32 bins of 4 mm (Index 28 in cm).
174
+ adaptor.add_config_value(28, 0.4)
175
+ adaptor.add_config_value(76, 32)
176
+ adaptor.add_config_value(77, 32)
173
177
  adaptor.run()
174
178
 
175
179
  projection = adaptor.get_total_output(window=1)
@@ -144,6 +144,10 @@ def main() -> None:
144
144
  adaptor.add_config_value(1, 140.0)
145
145
  adaptor.add_config_value(19, 2)
146
146
  adaptor.add_config_value(53, 0)
147
+ # Projections on the reconstruction grid: 32 x 32 bins of 4 mm (Index 28 in cm).
148
+ adaptor.add_config_value(28, 0.4)
149
+ adaptor.add_config_value(76, 32)
150
+ adaptor.add_config_value(77, 32)
147
151
  adaptor.run()
148
152
 
149
153
  projection_tensor = adaptor.get_total_output(window=1).to(dtype=torch.float32)
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simind-python-connector"
7
- version = "1.0.0"
7
+ version = "1.1.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"
@@ -14,7 +14,7 @@ for _dist_name in ("simind-python-connector", __name__):
14
14
  except _meta.PackageNotFoundError:
15
15
  continue
16
16
  else: # editable / source checkout
17
- __version__ = "1.0.0"
17
+ __version__ = "1.1.0"
18
18
 
19
19
 
20
20
  def __getattr__(name: str) -> Any:
@@ -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)