simind-python-connector 1.0.0__py3-none-any.whl

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 (47) hide show
  1. simind_python_connector/__init__.py +72 -0
  2. simind_python_connector/backends/__init__.py +480 -0
  3. simind_python_connector/backends/base.py +387 -0
  4. simind_python_connector/backends/sirf_backend.py +309 -0
  5. simind_python_connector/backends/stir_backend.py +395 -0
  6. simind_python_connector/builders/__init__.py +19 -0
  7. simind_python_connector/builders/acquisition_builder.py +526 -0
  8. simind_python_connector/builders/image_builder.py +217 -0
  9. simind_python_connector/configs/AnyScan.yaml +420 -0
  10. simind_python_connector/configs/Discovery670.yaml +412 -0
  11. simind_python_connector/configs/Example.yaml +420 -0
  12. simind_python_connector/configs/MLD001_SCAN0.yaml +426 -0
  13. simind_python_connector/configs/__init__.py +41 -0
  14. simind_python_connector/configs/input.smc +51 -0
  15. simind_python_connector/connectors/__init__.py +24 -0
  16. simind_python_connector/connectors/_spacing.py +69 -0
  17. simind_python_connector/connectors/base.py +40 -0
  18. simind_python_connector/connectors/python_connector.py +355 -0
  19. simind_python_connector/connectors/pytomography_adaptor.py +263 -0
  20. simind_python_connector/connectors/sirf_adaptor.py +164 -0
  21. simind_python_connector/connectors/stir_adaptor.py +164 -0
  22. simind_python_connector/converters/__init__.py +16 -0
  23. simind_python_connector/converters/attenuation.py +367 -0
  24. simind_python_connector/converters/dicom_to_stir.py +3 -0
  25. simind_python_connector/converters/simind_to_stir.py +769 -0
  26. simind_python_connector/core/__init__.py +7 -0
  27. simind_python_connector/core/config.py +939 -0
  28. simind_python_connector/core/executor.py +96 -0
  29. simind_python_connector/core/types.py +203 -0
  30. simind_python_connector/data/Schneider2000.json +222 -0
  31. simind_python_connector/data/__init__.py +25 -0
  32. simind_python_connector/data/bone.atn +187 -0
  33. simind_python_connector/data/h2o.atn +92 -0
  34. simind_python_connector/utils/__init__.py +120 -0
  35. simind_python_connector/utils/backend_access.py +121 -0
  36. simind_python_connector/utils/import_helpers.py +74 -0
  37. simind_python_connector/utils/interfile_numpy.py +195 -0
  38. simind_python_connector/utils/interfile_parser.py +175 -0
  39. simind_python_connector/utils/io_utils.py +14 -0
  40. simind_python_connector/utils/simind_utils.py +70 -0
  41. simind_python_connector/utils/sirf_stir_utils.py +194 -0
  42. simind_python_connector/utils/stir_utils.py +485 -0
  43. simind_python_connector-1.0.0.dist-info/METADATA +274 -0
  44. simind_python_connector-1.0.0.dist-info/RECORD +47 -0
  45. simind_python_connector-1.0.0.dist-info/WHEEL +5 -0
  46. simind_python_connector-1.0.0.dist-info/licenses/LICENSE +195 -0
  47. simind_python_connector-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,187 @@
1
+ <head><title>NIST XCOM database</title><!-- Google Analtyics 4 -->
2
+ <script async src="https://www.googletagmanager.com/gtag/js?id=G-9SLN1VEEJ2"></script>
3
+ <script>
4
+ window.dataLayer = window.dataLayer || [];
5
+ function gtag(){dataLayer.push(arguments);}
6
+ gtag('js', new Date());
7
+ gtag('config', 'G-9SLN1VEEJ2');
8
+ </script>
9
+ <!-- End of Google Analytics 4 -->
10
+ <SCRIPT async type="text/javascript" id="_fed_an_ua_tag" src="https://dap.digitalgov.gov/Universal-Federated-Analytics-Min.js?agency=DOC&subagency=NIST&pua=UA-37115410-46&yt=false&exts=ppsx,pps,f90,sch,rtf,wrl,txz,m1v,xlsm,msi,xsd,f,tif,eps,mpg,xml,pl,xlt,c"></script></head>Photon
11
+ Energy Coherent
12
+
13
+ 1.000E-03 3.442E+03
14
+ 1.000E-03 3.442E+03
15
+ 1.305E-03 1.689E+03
16
+ 1.305E-03 1.699E+03
17
+ 1.500E-03 1.166E+03
18
+ 2.000E-03 5.253E+02
19
+ 2.145E-03 4.310E+02
20
+ 2.145E-03 5.867E+02
21
+ 2.303E-03 4.854E+02
22
+ 2.472E-03 4.014E+02
23
+ 2.472E-03 4.051E+02
24
+ 3.000E-03 2.397E+02
25
+ 4.000E-03 1.068E+02
26
+ 4.038E-03 1.040E+02
27
+ 4.038E-03 2.370E+02
28
+ 5.000E-03 1.354E+02
29
+ 6.000E-03 8.246E+01
30
+ 7.000E-03 5.396E+01
31
+ 8.000E-03 3.728E+01
32
+ 9.000E-03 2.682E+01
33
+ 1.000E-02 1.993E+01
34
+ 1.100E-02 1.522E+01
35
+ 1.200E-02 1.190E+01
36
+ 1.300E-02 9.482E+00
37
+ 1.400E-02 7.687E+00
38
+ 1.500E-02 6.325E+00
39
+ 1.600E-02 5.273E+00
40
+ 1.700E-02 4.447E+00
41
+ 1.800E-02 3.791E+00
42
+ 1.900E-02 3.262E+00
43
+ 2.000E-02 2.832E+00
44
+ 2.100E-02 2.477E+00
45
+ 2.200E-02 2.184E+00
46
+ 2.300E-02 1.938E+00
47
+ 2.400E-02 1.731E+00
48
+ 2.500E-02 1.555E+00
49
+ 2.600E-02 1.404E+00
50
+ 2.700E-02 1.275E+00
51
+ 2.800E-02 1.164E+00
52
+ 2.900E-02 1.067E+00
53
+ 3.000E-02 9.820E-01
54
+ 3.100E-02 9.078E-01
55
+ 3.200E-02 8.424E-01
56
+ 3.300E-02 7.847E-01
57
+ 3.400E-02 7.334E-01
58
+ 3.500E-02 6.877E-01
59
+ 3.600E-02 6.468E-01
60
+ 3.700E-02 6.102E-01
61
+ 3.800E-02 5.773E-01
62
+ 3.900E-02 5.476E-01
63
+ 4.000E-02 5.207E-01
64
+ 4.100E-02 4.963E-01
65
+ 4.200E-02 4.742E-01
66
+ 4.300E-02 4.540E-01
67
+ 4.400E-02 4.355E-01
68
+ 4.500E-02 4.186E-01
69
+ 4.600E-02 4.031E-01
70
+ 4.700E-02 3.889E-01
71
+ 4.800E-02 3.757E-01
72
+ 4.900E-02 3.636E-01
73
+ 5.000E-02 3.524E-01
74
+ 5.100E-02 3.420E-01
75
+ 5.200E-02 3.323E-01
76
+ 5.300E-02 3.234E-01
77
+ 5.400E-02 3.150E-01
78
+ 5.500E-02 3.072E-01
79
+ 5.600E-02 3.000E-01
80
+ 5.700E-02 2.932E-01
81
+ 5.800E-02 2.868E-01
82
+ 5.900E-02 2.808E-01
83
+ 6.000E-02 2.752E-01
84
+ 6.100E-02 2.699E-01
85
+ 6.200E-02 2.649E-01
86
+ 6.300E-02 2.603E-01
87
+ 6.400E-02 2.558E-01
88
+ 6.500E-02 2.516E-01
89
+ 6.600E-02 2.477E-01
90
+ 6.700E-02 2.439E-01
91
+ 6.800E-02 2.403E-01
92
+ 6.900E-02 2.370E-01
93
+ 7.000E-02 2.337E-01
94
+ 7.100E-02 2.307E-01
95
+ 7.200E-02 2.278E-01
96
+ 7.300E-02 2.250E-01
97
+ 7.400E-02 2.223E-01
98
+ 7.500E-02 2.198E-01
99
+ 7.600E-02 2.174E-01
100
+ 7.700E-02 2.151E-01
101
+ 7.800E-02 2.129E-01
102
+ 7.900E-02 2.107E-01
103
+ 8.000E-02 2.087E-01
104
+ 8.100E-02 2.067E-01
105
+ 8.200E-02 2.049E-01
106
+ 8.300E-02 2.030E-01
107
+ 8.400E-02 2.013E-01
108
+ 8.500E-02 1.996E-01
109
+ 8.600E-02 1.980E-01
110
+ 8.700E-02 1.965E-01
111
+ 8.800E-02 1.949E-01
112
+ 8.900E-02 1.935E-01
113
+ 9.000E-02 1.921E-01
114
+ 9.100E-02 1.907E-01
115
+ 9.200E-02 1.894E-01
116
+ 9.300E-02 1.881E-01
117
+ 9.400E-02 1.869E-01
118
+ 9.500E-02 1.857E-01
119
+ 9.600E-02 1.846E-01
120
+ 9.700E-02 1.834E-01
121
+ 9.800E-02 1.823E-01
122
+ 9.900E-02 1.813E-01
123
+ 1.000E-01 1.802E-01
124
+ 1.500E-01 1.491E-01
125
+ 2.000E-01 1.332E-01
126
+ 3.000E-01 1.141E-01
127
+ 4.000E-01 1.018E-01
128
+ 5.000E-01 9.275E-02
129
+ 6.000E-01 8.570E-02
130
+ 8.000E-01 7.520E-02
131
+ 1.000E+00 6.758E-02
132
+ 1.022E+00 6.687E-02
133
+ 1.250E+00 6.043E-02
134
+ 1.500E+00 5.501E-02
135
+ 2.000E+00 4.733E-02
136
+ 2.044E+00 4.678E-02
137
+ 3.000E+00 3.826E-02
138
+ 4.000E+00 3.307E-02
139
+ 5.000E+00 2.971E-02
140
+ 6.000E+00 2.738E-02
141
+ 7.000E+00 2.568E-02
142
+ 8.000E+00 2.440E-02
143
+ 9.000E+00 2.341E-02
144
+ 1.000E+01 2.263E-02
145
+ 1.100E+01 2.200E-02
146
+ 1.200E+01 2.148E-02
147
+ 1.300E+01 2.105E-02
148
+ 1.400E+01 2.069E-02
149
+ 1.500E+01 2.040E-02
150
+ 1.600E+01 2.015E-02
151
+ 1.800E+01 1.976E-02
152
+ 2.000E+01 1.948E-02
153
+ 2.200E+01 1.928E-02
154
+ 2.400E+01 1.914E-02
155
+ 2.600E+01 1.904E-02
156
+ 2.800E+01 1.897E-02
157
+ 3.000E+01 1.892E-02
158
+ 4.000E+01 1.892E-02
159
+ 5.000E+01 1.908E-02
160
+ 6.000E+01 1.930E-02
161
+ 8.000E+01 1.975E-02
162
+ 1.000E+02 2.016E-02
163
+ 1.500E+02 2.099E-02
164
+ 2.000E+02 2.157E-02
165
+ 3.000E+02 2.233E-02
166
+ 4.000E+02 2.282E-02
167
+ 5.000E+02 2.315E-02
168
+ 6.000E+02 2.340E-02
169
+ 8.000E+02 2.374E-02
170
+ 1.000E+03 2.397E-02
171
+ 1.500E+03 2.431E-02
172
+ 2.000E+03 2.450E-02
173
+ 3.000E+03 2.471E-02
174
+ 4.000E+03 2.483E-02
175
+ 5.000E+03 2.490E-02
176
+ 6.000E+03 2.496E-02
177
+ 8.000E+03 2.503E-02
178
+ 1.000E+04 2.507E-02
179
+ 1.500E+04 2.513E-02
180
+ 2.000E+04 2.517E-02
181
+ 3.000E+04 2.520E-02
182
+ 4.000E+04 2.522E-02
183
+ 5.000E+04 2.523E-02
184
+ 6.000E+04 2.524E-02
185
+ 8.000E+04 2.525E-02
186
+ 1.000E+05 2.526E-02
187
+
@@ -0,0 +1,92 @@
1
+ <head><title>NIST XCOM database</title><!-- Google Analtyics 4 -->
2
+ <script async src="https://www.googletagmanager.com/gtag/js?id=G-9SLN1VEEJ2"></script>
3
+ <script>
4
+ window.dataLayer = window.dataLayer || [];
5
+ function gtag(){dataLayer.push(arguments);}
6
+ gtag('js', new Date());
7
+ gtag('config', 'G-9SLN1VEEJ2');
8
+ </script>
9
+ <!-- End of Google Analytics 4 -->
10
+ <SCRIPT async type="text/javascript" id="_fed_an_ua_tag" src="https://dap.digitalgov.gov/Universal-Federated-Analytics-Min.js?agency=DOC&subagency=NIST&pua=UA-37115410-46&yt=false&exts=ppsx,pps,f90,sch,rtf,wrl,txz,m1v,xlsm,msi,xsd,f,tif,eps,mpg,xml,pl,xlt,c"></script></head>Photon Tot. w/
11
+ Energy Coherent
12
+
13
+ 1.000E-03 4.077E+03
14
+ 1.500E-03 1.376E+03
15
+ 2.000E-03 6.173E+02
16
+ 3.000E-03 1.928E+02
17
+ 4.000E-03 8.277E+01
18
+ 5.000E-03 4.259E+01
19
+ 6.000E-03 2.464E+01
20
+ 8.000E-03 1.037E+01
21
+ 1.000E-02 5.330E+00
22
+ 1.500E-02 1.672E+00
23
+ 2.000E-02 8.098E-01
24
+ 3.000E-02 3.756E-01
25
+ 4.000E-02 2.683E-01
26
+ 5.000E-02 2.269E-01
27
+ 6.000E-02 2.059E-01
28
+ 8.000E-02 1.837E-01
29
+ 1.000E-01 1.707E-01
30
+ 1.500E-01 1.505E-01
31
+ 2.000E-01 1.370E-01
32
+ 3.000E-01 1.186E-01
33
+ 4.000E-01 1.061E-01
34
+ 5.000E-01 9.687E-02
35
+ 6.000E-01 8.956E-02
36
+ 8.000E-01 7.866E-02
37
+ 1.000E+00 7.072E-02
38
+ 1.022E+00 6.997E-02
39
+ 1.250E+00 6.323E-02
40
+ 1.500E+00 5.754E-02
41
+ 2.000E+00 4.942E-02
42
+ 2.044E+00 4.883E-02
43
+ 3.000E+00 3.969E-02
44
+ 4.000E+00 3.403E-02
45
+ 5.000E+00 3.031E-02
46
+ 6.000E+00 2.770E-02
47
+ 7.000E+00 2.577E-02
48
+ 8.000E+00 2.429E-02
49
+ 9.000E+00 2.313E-02
50
+ 1.000E+01 2.219E-02
51
+ 1.100E+01 2.142E-02
52
+ 1.200E+01 2.079E-02
53
+ 1.300E+01 2.025E-02
54
+ 1.400E+01 1.980E-02
55
+ 1.500E+01 1.941E-02
56
+ 1.600E+01 1.908E-02
57
+ 1.800E+01 1.854E-02
58
+ 2.000E+01 1.813E-02
59
+ 2.200E+01 1.782E-02
60
+ 2.400E+01 1.757E-02
61
+ 2.600E+01 1.738E-02
62
+ 2.800E+01 1.722E-02
63
+ 3.000E+01 1.710E-02
64
+ 4.000E+01 1.679E-02
65
+ 5.000E+01 1.674E-02
66
+ 6.000E+01 1.679E-02
67
+ 8.000E+01 1.702E-02
68
+ 1.000E+02 1.727E-02
69
+ 1.500E+02 1.785E-02
70
+ 2.000E+02 1.829E-02
71
+ 3.000E+02 1.888E-02
72
+ 4.000E+02 1.927E-02
73
+ 5.000E+02 1.954E-02
74
+ 6.000E+02 1.974E-02
75
+ 8.000E+02 2.002E-02
76
+ 1.000E+03 2.020E-02
77
+ 1.500E+03 2.048E-02
78
+ 2.000E+03 2.064E-02
79
+ 3.000E+03 2.082E-02
80
+ 4.000E+03 2.091E-02
81
+ 5.000E+03 2.097E-02
82
+ 6.000E+03 2.102E-02
83
+ 8.000E+03 2.108E-02
84
+ 1.000E+04 2.111E-02
85
+ 1.500E+04 2.117E-02
86
+ 2.000E+04 2.120E-02
87
+ 3.000E+04 2.123E-02
88
+ 4.000E+04 2.124E-02
89
+ 5.000E+04 2.125E-02
90
+ 6.000E+04 2.126E-02
91
+ 8.000E+04 2.127E-02
92
+ 1.000E+05 2.127E-02
@@ -0,0 +1,120 @@
1
+ """
2
+ Grab-bag of general utilities shared across modules.
3
+ They are re-exported here for a single, easy import path.
4
+ """
5
+
6
+ import contextlib
7
+ import importlib
8
+
9
+
10
+ # Lazy imports to avoid SIRF dependencies in CI
11
+ def __getattr__(name):
12
+ if name in (
13
+ "interfile_numpy",
14
+ "io_utils",
15
+ "simind_utils",
16
+ "stir_utils",
17
+ "sirf_stir_utils",
18
+ ):
19
+ return importlib.import_module(f".{name}", __name__)
20
+ raise AttributeError(f"module '{__name__}' has no attribute '{name}'")
21
+
22
+
23
+ def get_array(obj):
24
+ """Return NumPy array from SIRF or STIR object.
25
+
26
+ This function works with both SIRF and STIR Python backends:
27
+ - SIRF: Uses .asarray() or .as_array()
28
+ - STIR: Uses stirextra.to_numpy()
29
+ - Backend wrappers: Uses .as_array() method
30
+
31
+ Args:
32
+ obj: Image or acquisition data object (SIRF, STIR, or wrapped)
33
+
34
+ Returns:
35
+ np.ndarray: NumPy array view of the data
36
+
37
+ Raises:
38
+ AttributeError: If object cannot be converted to array
39
+ """
40
+ # Check if it's a wrapped backend object
41
+ if hasattr(obj, "as_array") and callable(obj.as_array):
42
+ return obj.as_array()
43
+
44
+ # Try SIRF native methods
45
+ if hasattr(obj, "asarray"):
46
+ try:
47
+ return obj.asarray()
48
+ except Exception:
49
+ return obj.as_array() # Fallback to as_array if asarray fails
50
+ if hasattr(obj, "as_array"):
51
+ return obj.as_array()
52
+
53
+ # Try STIR native conversion
54
+ with contextlib.suppress(ImportError, TypeError, AttributeError):
55
+ import stirextra
56
+
57
+ return stirextra.to_numpy(obj)
58
+ raise AttributeError(
59
+ f"Cannot convert {type(obj)} to numpy array. "
60
+ f"Object must have asarray(), as_array() method, or be a STIR object."
61
+ )
62
+
63
+
64
+ def to_projdata_in_memory(proj_data):
65
+ """Convert ProjData to ProjDataInMemory for arithmetic operations.
66
+
67
+ STIR's ProjData objects don't support arithmetic operations directly.
68
+ This function converts them to ProjDataInMemory which does.
69
+
70
+ Args:
71
+ proj_data: STIR ProjData object or SIRF AcquisitionData
72
+
73
+ Returns:
74
+ ProjDataInMemory object (STIR) or cloned AcquisitionData (SIRF)
75
+
76
+ Note:
77
+ For SIRF AcquisitionData, this returns a clone since SIRF already
78
+ supports arithmetic operations.
79
+ """
80
+ # Check if it's SIRF (has clone method and supports arithmetic)
81
+ if hasattr(proj_data, "clone") and not hasattr(proj_data, "get_proj_data_info"):
82
+ return proj_data.clone()
83
+
84
+ # STIR backend: convert to ProjDataInMemory
85
+ try:
86
+ import stir
87
+
88
+ # Check if already ProjDataInMemory
89
+ if isinstance(proj_data, stir.ProjDataInMemory):
90
+ return proj_data
91
+
92
+ # Convert ProjData to ProjDataInMemory
93
+ proj_data_in_mem = stir.ProjDataInMemory(
94
+ proj_data.get_exam_info(), proj_data.get_proj_data_info()
95
+ )
96
+
97
+ # Copy data segment by segment
98
+ for seg_num in range(
99
+ proj_data.get_min_segment_num(), proj_data.get_max_segment_num() + 1
100
+ ):
101
+ seg = proj_data.get_segment_by_sinogram(seg_num)
102
+ proj_data_in_mem.set_segment(seg)
103
+
104
+ return proj_data_in_mem
105
+ except ImportError:
106
+ # Neither SIRF nor STIR available - return original
107
+ return proj_data
108
+
109
+
110
+ __all__ = [
111
+ "get_array",
112
+ "interfile_numpy",
113
+ "to_projdata_in_memory",
114
+ "io_utils",
115
+ "simind_utils",
116
+ "stir_utils",
117
+ "sirf_stir_utils",
118
+ "step_size_rules",
119
+ "cil_partitioner",
120
+ ]
@@ -0,0 +1,121 @@
1
+ """
2
+ Centralized backend import and access helpers.
3
+
4
+ This module provides a single source of truth for backend imports, eliminating
5
+ the need for duplicate try/except import blocks scattered throughout the codebase.
6
+
7
+ All backend-related imports (factory functions, interfaces, detection utilities,
8
+ and wrappers) are centralized here, with proper fallback handling when backends
9
+ are unavailable.
10
+ """
11
+
12
+ from types import SimpleNamespace
13
+ from typing import Any, Dict
14
+
15
+
16
+ def get_backend_interfaces() -> tuple[bool, Dict[str, Any]]:
17
+ """Get all backend interfaces and utilities with fallback handling.
18
+
19
+ This is the single source of truth for backend imports. When backends are
20
+ unavailable, it returns stub values (None or type(None)) so calling code
21
+ can check BACKEND_AVAILABLE and handle the missing dependencies gracefully.
22
+
23
+ Returns:
24
+ Tuple of (BACKEND_AVAILABLE, interfaces_dict) where interfaces_dict contains:
25
+ - 'factories': Backend factory functions
26
+ (create_acquisition_data, create_image_data)
27
+ - 'types': Interface type hints (AcquisitionDataInterface, ImageDataInterface)
28
+ - 'wrappers': Wrapping utilities
29
+ (ensure_acquisition_interface, ensure_image_interface, etc.)
30
+ - 'detection': Backend detection functions (detect_*_backend, get/set_backend)
31
+
32
+ Example:
33
+ >>> BACKEND_AVAILABLE, backends = get_backend_interfaces()
34
+ >>> if BACKEND_AVAILABLE:
35
+ >>> create_acq = backends['factories']['create_acquisition_data']
36
+ >>> data = create_acq('template.hs')
37
+ """
38
+ try:
39
+ from simind_python_connector.backends import (
40
+ AcquisitionDataInterface,
41
+ ImageDataInterface,
42
+ create_acquisition_data,
43
+ create_image_data,
44
+ detect_acquisition_backend,
45
+ detect_backend_from_interface,
46
+ detect_image_backend,
47
+ get_backend,
48
+ set_backend,
49
+ )
50
+ from simind_python_connector.utils.sirf_stir_utils import (
51
+ ensure_acquisition_interface,
52
+ ensure_image_interface,
53
+ to_native_acquisition,
54
+ to_native_image,
55
+ )
56
+
57
+ return True, {
58
+ "factories": {
59
+ "create_acquisition_data": create_acquisition_data,
60
+ "create_image_data": create_image_data,
61
+ },
62
+ "types": {
63
+ "AcquisitionDataInterface": AcquisitionDataInterface,
64
+ "ImageDataInterface": ImageDataInterface,
65
+ },
66
+ "wrappers": {
67
+ "ensure_acquisition_interface": ensure_acquisition_interface,
68
+ "ensure_image_interface": ensure_image_interface,
69
+ "to_native_acquisition": to_native_acquisition,
70
+ "to_native_image": to_native_image,
71
+ },
72
+ "detection": {
73
+ "detect_acquisition_backend": detect_acquisition_backend,
74
+ "detect_image_backend": detect_image_backend,
75
+ "detect_backend_from_interface": detect_backend_from_interface,
76
+ "get_backend": get_backend,
77
+ "set_backend": set_backend,
78
+ },
79
+ }
80
+ except ImportError:
81
+ # Return fallback stubs when backends are unavailable
82
+ return False, {
83
+ "factories": {
84
+ "create_acquisition_data": None,
85
+ "create_image_data": None,
86
+ },
87
+ "types": {
88
+ "AcquisitionDataInterface": type(None),
89
+ "ImageDataInterface": type(None),
90
+ },
91
+ "wrappers": {
92
+ "ensure_acquisition_interface": None,
93
+ "ensure_image_interface": None,
94
+ "to_native_acquisition": None,
95
+ "to_native_image": None,
96
+ },
97
+ "detection": {
98
+ "detect_acquisition_backend": None,
99
+ "detect_image_backend": None,
100
+ "detect_backend_from_interface": None,
101
+ "get_backend": None,
102
+ "set_backend": None,
103
+ },
104
+ }
105
+
106
+
107
+ def _to_namespace(mapping: Dict[str, Any]) -> SimpleNamespace:
108
+ """Convert a dictionary to a SimpleNamespace (handles None gracefully)."""
109
+ return SimpleNamespace(**(mapping or {}))
110
+
111
+
112
+ BACKEND_AVAILABLE, _interfaces = get_backend_interfaces()
113
+ BACKENDS = SimpleNamespace(
114
+ factories=_to_namespace(_interfaces.get("factories", {})),
115
+ types=_to_namespace(_interfaces.get("types", {})),
116
+ wrappers=_to_namespace(_interfaces.get("wrappers", {})),
117
+ detection=_to_namespace(_interfaces.get("detection", {})),
118
+ )
119
+
120
+
121
+ __all__ = ["get_backend_interfaces", "BACKEND_AVAILABLE", "BACKENDS"]
@@ -0,0 +1,74 @@
1
+ """
2
+ Centralized import helpers for SIRF/STIR backends.
3
+
4
+ This module provides reusable functions for importing backend types with
5
+ proper fallback handling. This eliminates the need for duplicate try/except
6
+ import blocks scattered throughout the codebase.
7
+ """
8
+
9
+ from typing import Tuple, Type
10
+
11
+
12
+ def get_sirf_types() -> Tuple[Type, Type, bool]:
13
+ """Import SIRF types with fallback.
14
+
15
+ Returns:
16
+ Tuple of (ImageData, AcquisitionData, SIRF_AVAILABLE)
17
+ If SIRF is not available, returns (type(None), type(None), False)
18
+ """
19
+ try:
20
+ from sirf.STIR import AcquisitionData, ImageData
21
+
22
+ return ImageData, AcquisitionData, True
23
+ except ImportError:
24
+ return type(None), type(None), False
25
+
26
+
27
+ def get_stir_types() -> Tuple[Type, Type, bool]:
28
+ """Import STIR Python types with fallback.
29
+
30
+ Returns:
31
+ Tuple of (FloatVoxelsOnCartesianGrid, ProjData, STIR_AVAILABLE)
32
+ If STIR is not available, returns (type(None), type(None), False)
33
+ """
34
+ try:
35
+ import stir
36
+
37
+ return stir.FloatVoxelsOnCartesianGrid, stir.ProjData, True
38
+ except ImportError:
39
+ return type(None), type(None), False
40
+
41
+
42
+ def get_backend_types() -> Tuple[Type, Type, Type, Type, str]:
43
+ """Import backend-agnostic types (tries SIRF first, then STIR).
44
+
45
+ Returns:
46
+ Tuple of (ImageData, AcquisitionData, ImageType, AcqType, backend_name)
47
+ where ImageType/AcqType are the native backend types
48
+
49
+ Raises:
50
+ ImportError: If neither SIRF nor STIR is available
51
+ """
52
+ # Try SIRF first
53
+ ImageData, AcquisitionData, sirf_available = get_sirf_types()
54
+ if sirf_available:
55
+ return ImageData, AcquisitionData, ImageData, AcquisitionData, "sirf"
56
+
57
+ # Fall back to STIR
58
+ StirImage, StirProj, stir_available = get_stir_types()
59
+ if stir_available:
60
+ return StirImage, StirProj, StirImage, StirProj, "stir"
61
+
62
+ raise ImportError(
63
+ "Neither SIRF nor STIR Python is available. "
64
+ "Please install one of:\n"
65
+ " - SIRF (sirf.STIR)\n"
66
+ " - STIR Python (stir + stirextra)"
67
+ )
68
+
69
+
70
+ __all__ = [
71
+ "get_sirf_types",
72
+ "get_stir_types",
73
+ "get_backend_types",
74
+ ]