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.
- simind_python_connector/__init__.py +72 -0
- simind_python_connector/backends/__init__.py +480 -0
- simind_python_connector/backends/base.py +387 -0
- simind_python_connector/backends/sirf_backend.py +309 -0
- simind_python_connector/backends/stir_backend.py +395 -0
- simind_python_connector/builders/__init__.py +19 -0
- simind_python_connector/builders/acquisition_builder.py +526 -0
- simind_python_connector/builders/image_builder.py +217 -0
- simind_python_connector/configs/AnyScan.yaml +420 -0
- simind_python_connector/configs/Discovery670.yaml +412 -0
- simind_python_connector/configs/Example.yaml +420 -0
- simind_python_connector/configs/MLD001_SCAN0.yaml +426 -0
- simind_python_connector/configs/__init__.py +41 -0
- simind_python_connector/configs/input.smc +51 -0
- simind_python_connector/connectors/__init__.py +24 -0
- simind_python_connector/connectors/_spacing.py +69 -0
- simind_python_connector/connectors/base.py +40 -0
- simind_python_connector/connectors/python_connector.py +355 -0
- simind_python_connector/connectors/pytomography_adaptor.py +263 -0
- simind_python_connector/connectors/sirf_adaptor.py +164 -0
- simind_python_connector/connectors/stir_adaptor.py +164 -0
- simind_python_connector/converters/__init__.py +16 -0
- simind_python_connector/converters/attenuation.py +367 -0
- simind_python_connector/converters/dicom_to_stir.py +3 -0
- simind_python_connector/converters/simind_to_stir.py +769 -0
- simind_python_connector/core/__init__.py +7 -0
- simind_python_connector/core/config.py +939 -0
- simind_python_connector/core/executor.py +96 -0
- simind_python_connector/core/types.py +203 -0
- simind_python_connector/data/Schneider2000.json +222 -0
- simind_python_connector/data/__init__.py +25 -0
- simind_python_connector/data/bone.atn +187 -0
- simind_python_connector/data/h2o.atn +92 -0
- simind_python_connector/utils/__init__.py +120 -0
- simind_python_connector/utils/backend_access.py +121 -0
- simind_python_connector/utils/import_helpers.py +74 -0
- simind_python_connector/utils/interfile_numpy.py +195 -0
- simind_python_connector/utils/interfile_parser.py +175 -0
- simind_python_connector/utils/io_utils.py +14 -0
- simind_python_connector/utils/simind_utils.py +70 -0
- simind_python_connector/utils/sirf_stir_utils.py +194 -0
- simind_python_connector/utils/stir_utils.py +485 -0
- simind_python_connector-1.0.0.dist-info/METADATA +274 -0
- simind_python_connector-1.0.0.dist-info/RECORD +47 -0
- simind_python_connector-1.0.0.dist-info/WHEEL +5 -0
- simind_python_connector-1.0.0.dist-info/licenses/LICENSE +195 -0
- simind_python_connector-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""
|
|
2
|
+
simind-python-connector connector/adaptor API.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import importlib
|
|
6
|
+
from importlib import metadata as _meta
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
for _dist_name in ("simind-python-connector", __name__):
|
|
11
|
+
try: # installed (pip/poetry)
|
|
12
|
+
__version__ = _meta.version(_dist_name)
|
|
13
|
+
break
|
|
14
|
+
except _meta.PackageNotFoundError:
|
|
15
|
+
continue
|
|
16
|
+
else: # editable / source checkout
|
|
17
|
+
__version__ = "1.0.0"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def __getattr__(name: str) -> Any:
|
|
21
|
+
if name in {
|
|
22
|
+
"builders",
|
|
23
|
+
"configs",
|
|
24
|
+
"connectors",
|
|
25
|
+
"converters",
|
|
26
|
+
"core",
|
|
27
|
+
"data",
|
|
28
|
+
"utils",
|
|
29
|
+
}:
|
|
30
|
+
mod = importlib.import_module(f".{name}", __name__)
|
|
31
|
+
globals()[name] = mod
|
|
32
|
+
return mod
|
|
33
|
+
elif name in {"SimulationConfig"}:
|
|
34
|
+
core = importlib.import_module(".core", __name__)
|
|
35
|
+
obj = getattr(core, name)
|
|
36
|
+
globals()[name] = obj
|
|
37
|
+
return obj
|
|
38
|
+
elif name in {
|
|
39
|
+
"BaseConnector",
|
|
40
|
+
"NumpyConnector",
|
|
41
|
+
"PyTomographySimindAdaptor",
|
|
42
|
+
"ProjectionResult",
|
|
43
|
+
"RuntimeOperator",
|
|
44
|
+
"SirfSimindAdaptor",
|
|
45
|
+
"SimindPythonConnector",
|
|
46
|
+
"StirSimindAdaptor",
|
|
47
|
+
}:
|
|
48
|
+
connectors = importlib.import_module(".connectors", __name__)
|
|
49
|
+
obj = getattr(connectors, name)
|
|
50
|
+
globals()[name] = obj
|
|
51
|
+
return obj
|
|
52
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"BaseConnector",
|
|
57
|
+
"NumpyConnector",
|
|
58
|
+
"ProjectionResult",
|
|
59
|
+
"PyTomographySimindAdaptor",
|
|
60
|
+
"RuntimeOperator",
|
|
61
|
+
"SirfSimindAdaptor",
|
|
62
|
+
"SimindPythonConnector",
|
|
63
|
+
"SimulationConfig",
|
|
64
|
+
"StirSimindAdaptor",
|
|
65
|
+
"builders",
|
|
66
|
+
"configs",
|
|
67
|
+
"connectors",
|
|
68
|
+
"converters",
|
|
69
|
+
"core",
|
|
70
|
+
"data",
|
|
71
|
+
"utils",
|
|
72
|
+
]
|
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backend abstraction layer for SIRF and STIR Python.
|
|
3
|
+
|
|
4
|
+
This module provides automatic backend detection and factory functions
|
|
5
|
+
for creating image and acquisition data objects that work with both
|
|
6
|
+
SIRF and STIR Python interfaces.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
# Automatic detection
|
|
10
|
+
from simind_python_connector.backends import get_backend, create_image_data
|
|
11
|
+
|
|
12
|
+
print(f"Using backend: {get_backend()}")
|
|
13
|
+
img = create_image_data("phantom.hv")
|
|
14
|
+
|
|
15
|
+
# Manual selection
|
|
16
|
+
from simind_python_connector.backends import set_backend
|
|
17
|
+
set_backend("stir") # Force STIR Python
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import importlib
|
|
21
|
+
import logging
|
|
22
|
+
from typing import TYPE_CHECKING, Any, Literal, Optional, Union
|
|
23
|
+
|
|
24
|
+
from .base import AcquisitionDataInterface, ImageDataInterface
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
try:
|
|
31
|
+
from sirf.STIR import AcquisitionData, ImageData
|
|
32
|
+
except ImportError: # pragma: no cover - for type checkers without SIRF
|
|
33
|
+
AcquisitionData = Any # type: ignore[assignment]
|
|
34
|
+
ImageData = Any # type: ignore[assignment]
|
|
35
|
+
try:
|
|
36
|
+
import stir
|
|
37
|
+
|
|
38
|
+
ProjData = stir.ProjData # type: ignore[attr-defined]
|
|
39
|
+
FloatVoxelsOnCartesianGrid = stir.FloatVoxelsOnCartesianGrid # type: ignore[attr-defined]
|
|
40
|
+
except ImportError: # pragma: no cover - for type checkers without STIR
|
|
41
|
+
ProjData = Any # type: ignore[assignment]
|
|
42
|
+
FloatVoxelsOnCartesianGrid = Any # type: ignore[assignment]
|
|
43
|
+
|
|
44
|
+
# Global backend state
|
|
45
|
+
_backend: Optional[str] = None
|
|
46
|
+
_backend_initialized = False
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _import_sirf_types() -> tuple[Optional[type], Optional[type]]:
|
|
50
|
+
"""Import SIRF image/acquisition types lazily."""
|
|
51
|
+
try:
|
|
52
|
+
from sirf.STIR import AcquisitionData as SirfAcquisitionData # type: ignore
|
|
53
|
+
from sirf.STIR import ImageData as SirfImageData # type: ignore
|
|
54
|
+
except ImportError:
|
|
55
|
+
return None, None
|
|
56
|
+
return SirfImageData, SirfAcquisitionData
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _import_stir_types() -> tuple[Optional[type], Optional[type]]:
|
|
60
|
+
"""Import STIR image/acquisition types lazily."""
|
|
61
|
+
try:
|
|
62
|
+
from stir import FloatVoxelsOnCartesianGrid as StirImageData # type: ignore
|
|
63
|
+
from stir import ProjData as StirProjData # type: ignore
|
|
64
|
+
except ImportError:
|
|
65
|
+
return None, None
|
|
66
|
+
|
|
67
|
+
return StirImageData, StirProjData
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def detect_image_backend(obj: Any) -> Optional[str]:
|
|
71
|
+
"""Return 'sirf' or 'stir' if obj is a native image type."""
|
|
72
|
+
if obj is None:
|
|
73
|
+
return None
|
|
74
|
+
|
|
75
|
+
sirf_image, _ = _import_sirf_types()
|
|
76
|
+
if sirf_image is not None and isinstance(obj, sirf_image):
|
|
77
|
+
return "sirf"
|
|
78
|
+
|
|
79
|
+
stir_image, _ = _import_stir_types()
|
|
80
|
+
if stir_image is not None and isinstance(obj, stir_image):
|
|
81
|
+
return "stir"
|
|
82
|
+
|
|
83
|
+
return None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def detect_acquisition_backend(obj: Any) -> Optional[str]:
|
|
87
|
+
"""Return 'sirf' or 'stir' if obj is a native acquisition type."""
|
|
88
|
+
if obj is None:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
_, sirf_acq = _import_sirf_types()
|
|
92
|
+
if sirf_acq is not None and isinstance(obj, sirf_acq):
|
|
93
|
+
return "sirf"
|
|
94
|
+
|
|
95
|
+
_, stir_proj = _import_stir_types()
|
|
96
|
+
if stir_proj is not None and isinstance(obj, stir_proj):
|
|
97
|
+
return "stir"
|
|
98
|
+
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def detect_backend_from_interface(
|
|
103
|
+
obj: Union[ImageDataInterface, AcquisitionDataInterface, Any],
|
|
104
|
+
) -> Optional[str]:
|
|
105
|
+
"""Detect backend by unwrapping known interface objects."""
|
|
106
|
+
native = getattr(obj, "native_object", None)
|
|
107
|
+
if native is None:
|
|
108
|
+
return None
|
|
109
|
+
|
|
110
|
+
backend = detect_image_backend(native)
|
|
111
|
+
if backend:
|
|
112
|
+
return backend
|
|
113
|
+
|
|
114
|
+
return detect_acquisition_backend(native)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _activate_backend(preferred: Optional[str]) -> Optional[str]:
|
|
118
|
+
"""Force-activate a specific backend if requested."""
|
|
119
|
+
if preferred is None:
|
|
120
|
+
return None
|
|
121
|
+
if preferred not in ("sirf", "stir"):
|
|
122
|
+
raise ValueError(
|
|
123
|
+
f"Invalid backend hint: {preferred}. Expected 'sirf' or 'stir'."
|
|
124
|
+
)
|
|
125
|
+
set_backend(preferred)
|
|
126
|
+
return preferred
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def get_backend() -> str:
|
|
130
|
+
"""Get the current backend or auto-detect.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
str: Either "sirf" or "stir"
|
|
134
|
+
|
|
135
|
+
Raises:
|
|
136
|
+
ImportError: If neither SIRF nor STIR Python is available
|
|
137
|
+
"""
|
|
138
|
+
global _backend, _backend_initialized
|
|
139
|
+
|
|
140
|
+
if _backend is not None and _backend_initialized:
|
|
141
|
+
return _backend
|
|
142
|
+
|
|
143
|
+
# Auto-detect
|
|
144
|
+
try:
|
|
145
|
+
importlib.import_module("sirf.STIR")
|
|
146
|
+
|
|
147
|
+
_backend = "sirf"
|
|
148
|
+
logger.info("Auto-detected SIRF backend")
|
|
149
|
+
except ImportError:
|
|
150
|
+
try:
|
|
151
|
+
importlib.import_module("stir")
|
|
152
|
+
importlib.import_module("stirextra")
|
|
153
|
+
|
|
154
|
+
_backend = "stir"
|
|
155
|
+
logger.info("Auto-detected STIR Python backend")
|
|
156
|
+
except ImportError:
|
|
157
|
+
raise ImportError(
|
|
158
|
+
"Neither SIRF nor STIR Python found. "
|
|
159
|
+
"Please install one of: "
|
|
160
|
+
"\n - SIRF (sirf.STIR)"
|
|
161
|
+
"\n - STIR Python (stir + stirextra)"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
_backend_initialized = True
|
|
165
|
+
return _backend
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def set_backend(backend: Literal["sirf", "stir"]) -> None:
|
|
169
|
+
"""Manually set the backend.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
backend: Either "sirf" or "stir"
|
|
173
|
+
|
|
174
|
+
Raises:
|
|
175
|
+
ValueError: If backend is not valid
|
|
176
|
+
ImportError: If requested backend is not available
|
|
177
|
+
"""
|
|
178
|
+
global _backend, _backend_initialized
|
|
179
|
+
|
|
180
|
+
if backend not in ["sirf", "stir"]:
|
|
181
|
+
raise ValueError(f"Invalid backend: {backend}. Must be 'sirf' or 'stir'")
|
|
182
|
+
|
|
183
|
+
# Verify the backend is available
|
|
184
|
+
if backend == "sirf":
|
|
185
|
+
try:
|
|
186
|
+
importlib.import_module("sirf.STIR")
|
|
187
|
+
except ImportError:
|
|
188
|
+
raise ImportError("SIRF is not available")
|
|
189
|
+
elif backend == "stir":
|
|
190
|
+
try:
|
|
191
|
+
importlib.import_module("stir")
|
|
192
|
+
importlib.import_module("stirextra")
|
|
193
|
+
except ImportError:
|
|
194
|
+
raise ImportError("STIR Python is not available")
|
|
195
|
+
|
|
196
|
+
_backend = backend
|
|
197
|
+
_backend_initialized = True
|
|
198
|
+
logger.info(f"Backend manually set to: {backend}")
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def reset_backend() -> None:
|
|
202
|
+
"""Reset backend to allow re-detection.
|
|
203
|
+
|
|
204
|
+
Useful for testing or switching backends dynamically.
|
|
205
|
+
"""
|
|
206
|
+
global _backend, _backend_initialized
|
|
207
|
+
_backend = None
|
|
208
|
+
_backend_initialized = False
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def create_image_data(
|
|
212
|
+
filepath_or_object: Optional[
|
|
213
|
+
Union[str, ImageDataInterface, "ImageData", "FloatVoxelsOnCartesianGrid"]
|
|
214
|
+
] = None,
|
|
215
|
+
) -> ImageDataInterface:
|
|
216
|
+
"""Create an image data object using the current backend.
|
|
217
|
+
|
|
218
|
+
Args:
|
|
219
|
+
filepath_or_object: Can be:
|
|
220
|
+
- str: Path to image file to load
|
|
221
|
+
- ImageDataInterface: Already wrapped object (returned as-is)
|
|
222
|
+
- Native SIRF/STIR object: Will be wrapped
|
|
223
|
+
- None: Creates empty object (SIRF only)
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
ImageDataInterface: Wrapped image object
|
|
227
|
+
|
|
228
|
+
Raises:
|
|
229
|
+
NotImplementedError: If trying to create empty image with STIR backend
|
|
230
|
+
TypeError: If input type is not supported
|
|
231
|
+
"""
|
|
232
|
+
# Already wrapped?
|
|
233
|
+
if isinstance(filepath_or_object, ImageDataInterface):
|
|
234
|
+
return filepath_or_object
|
|
235
|
+
|
|
236
|
+
backend_hint = detect_image_backend(filepath_or_object)
|
|
237
|
+
if backend_hint is None:
|
|
238
|
+
backend_hint = detect_backend_from_interface(filepath_or_object)
|
|
239
|
+
|
|
240
|
+
backend = _activate_backend(backend_hint) or get_backend()
|
|
241
|
+
|
|
242
|
+
if backend == "sirf":
|
|
243
|
+
from .sirf_backend import SirfImageData
|
|
244
|
+
|
|
245
|
+
try:
|
|
246
|
+
from sirf.STIR import ImageData
|
|
247
|
+
except ImportError:
|
|
248
|
+
ImageData = type(None)
|
|
249
|
+
|
|
250
|
+
if filepath_or_object is None:
|
|
251
|
+
return SirfImageData.create_empty()
|
|
252
|
+
elif isinstance(filepath_or_object, str):
|
|
253
|
+
return SirfImageData.read_from_file(filepath_or_object)
|
|
254
|
+
elif ImageData is not type(None) and isinstance(filepath_or_object, ImageData):
|
|
255
|
+
# Wrap native SIRF object
|
|
256
|
+
return SirfImageData(filepath_or_object)
|
|
257
|
+
else:
|
|
258
|
+
raise TypeError(
|
|
259
|
+
f"Cannot create image from type {type(filepath_or_object)}. "
|
|
260
|
+
f"Expected str, ImageDataInterface, or ImageData."
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
elif backend == "stir":
|
|
264
|
+
from .stir_backend import StirImageData
|
|
265
|
+
|
|
266
|
+
try:
|
|
267
|
+
import stir
|
|
268
|
+
|
|
269
|
+
FloatVoxelsOnCartesianGrid = stir.FloatVoxelsOnCartesianGrid
|
|
270
|
+
except ImportError:
|
|
271
|
+
FloatVoxelsOnCartesianGrid = type(None)
|
|
272
|
+
|
|
273
|
+
if filepath_or_object is None:
|
|
274
|
+
raise NotImplementedError(
|
|
275
|
+
"Creating empty STIR images requires geometry. "
|
|
276
|
+
"Please provide a filepath or use a template."
|
|
277
|
+
)
|
|
278
|
+
elif isinstance(filepath_or_object, str):
|
|
279
|
+
return StirImageData.read_from_file(filepath_or_object)
|
|
280
|
+
elif FloatVoxelsOnCartesianGrid is not type(None) and isinstance(
|
|
281
|
+
filepath_or_object, FloatVoxelsOnCartesianGrid
|
|
282
|
+
):
|
|
283
|
+
# Wrap native STIR object
|
|
284
|
+
return StirImageData(filepath_or_object)
|
|
285
|
+
else:
|
|
286
|
+
raise TypeError(
|
|
287
|
+
f"Cannot create image from type {type(filepath_or_object)}. "
|
|
288
|
+
f"Expected str, ImageDataInterface, or FloatVoxelsOnCartesianGrid."
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def create_acquisition_data(
|
|
293
|
+
filepath_or_object: Optional[
|
|
294
|
+
Union[str, AcquisitionDataInterface, "AcquisitionData", "ProjData"]
|
|
295
|
+
] = None,
|
|
296
|
+
) -> AcquisitionDataInterface:
|
|
297
|
+
"""Create an acquisition data object using the current backend.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
filepath_or_object: Can be:
|
|
301
|
+
- str: Path to acquisition file to load
|
|
302
|
+
- AcquisitionDataInterface: Already wrapped object (returned as-is)
|
|
303
|
+
- Native SIRF/STIR object: Will be wrapped
|
|
304
|
+
- None: Creates empty object (SIRF only)
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
AcquisitionDataInterface: Wrapped acquisition object
|
|
308
|
+
|
|
309
|
+
Raises:
|
|
310
|
+
NotImplementedError: If trying to create empty acquisition with STIR backend
|
|
311
|
+
TypeError: If input type is not supported
|
|
312
|
+
"""
|
|
313
|
+
# Already wrapped?
|
|
314
|
+
if isinstance(filepath_or_object, AcquisitionDataInterface):
|
|
315
|
+
return filepath_or_object
|
|
316
|
+
|
|
317
|
+
backend_hint = detect_acquisition_backend(filepath_or_object)
|
|
318
|
+
if backend_hint is None:
|
|
319
|
+
backend_hint = detect_backend_from_interface(filepath_or_object)
|
|
320
|
+
|
|
321
|
+
backend = _activate_backend(backend_hint) or get_backend()
|
|
322
|
+
|
|
323
|
+
if backend == "sirf":
|
|
324
|
+
from .sirf_backend import SirfAcquisitionData
|
|
325
|
+
|
|
326
|
+
try:
|
|
327
|
+
from sirf.STIR import AcquisitionData
|
|
328
|
+
except ImportError:
|
|
329
|
+
AcquisitionData = type(None)
|
|
330
|
+
|
|
331
|
+
if filepath_or_object is None:
|
|
332
|
+
return SirfAcquisitionData.create_empty()
|
|
333
|
+
elif isinstance(filepath_or_object, str):
|
|
334
|
+
return SirfAcquisitionData.read_from_file(filepath_or_object)
|
|
335
|
+
elif AcquisitionData is not type(None) and isinstance(
|
|
336
|
+
filepath_or_object, AcquisitionData
|
|
337
|
+
):
|
|
338
|
+
# Wrap native SIRF object
|
|
339
|
+
return SirfAcquisitionData(filepath_or_object)
|
|
340
|
+
else:
|
|
341
|
+
raise TypeError(
|
|
342
|
+
f"Cannot create acquisition from type {type(filepath_or_object)}. "
|
|
343
|
+
f"Expected str, AcquisitionDataInterface, or AcquisitionData."
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
elif backend == "stir":
|
|
347
|
+
from .stir_backend import StirAcquisitionData
|
|
348
|
+
|
|
349
|
+
try:
|
|
350
|
+
import stir
|
|
351
|
+
|
|
352
|
+
ProjData = stir.ProjData
|
|
353
|
+
except ImportError:
|
|
354
|
+
ProjData = type(None)
|
|
355
|
+
|
|
356
|
+
if filepath_or_object is None:
|
|
357
|
+
raise NotImplementedError(
|
|
358
|
+
"Creating empty STIR ProjData requires exam_info and proj_data_info. "
|
|
359
|
+
"Please provide a filepath or use "
|
|
360
|
+
"StirAcquisitionData.create_empty() directly."
|
|
361
|
+
)
|
|
362
|
+
elif isinstance(filepath_or_object, str):
|
|
363
|
+
return StirAcquisitionData.read_from_file(filepath_or_object)
|
|
364
|
+
elif ProjData is not type(None) and isinstance(filepath_or_object, ProjData):
|
|
365
|
+
# Wrap native STIR object
|
|
366
|
+
return StirAcquisitionData(filepath_or_object)
|
|
367
|
+
else:
|
|
368
|
+
raise TypeError(
|
|
369
|
+
f"Cannot create acquisition from type {type(filepath_or_object)}. "
|
|
370
|
+
f"Expected str, AcquisitionDataInterface, or ProjData."
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
def load_image_data(
|
|
375
|
+
filepath: str, backend: Optional[Literal["sirf", "stir"]] = None
|
|
376
|
+
) -> ImageDataInterface:
|
|
377
|
+
"""Load image data from file for the requested backend.
|
|
378
|
+
|
|
379
|
+
Args:
|
|
380
|
+
filepath: Path to the image header file to load.
|
|
381
|
+
backend: Optional backend override. If omitted, use auto-detected backend.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
ImageDataInterface: Wrapped backend-specific image object.
|
|
385
|
+
|
|
386
|
+
Raises:
|
|
387
|
+
ValueError: If backend is explicitly provided but invalid.
|
|
388
|
+
"""
|
|
389
|
+
backend_to_use = backend or get_backend()
|
|
390
|
+
|
|
391
|
+
if backend_to_use == "sirf":
|
|
392
|
+
from .sirf_backend import SirfImageData
|
|
393
|
+
|
|
394
|
+
return SirfImageData.read_from_file(filepath)
|
|
395
|
+
if backend_to_use == "stir":
|
|
396
|
+
from .stir_backend import StirImageData
|
|
397
|
+
|
|
398
|
+
return StirImageData.read_from_file(filepath)
|
|
399
|
+
|
|
400
|
+
raise ValueError("Invalid backend. Expected 'sirf' or 'stir'.")
|
|
401
|
+
|
|
402
|
+
|
|
403
|
+
def load_acquisition_data(
|
|
404
|
+
filepath: str, backend: Optional[Literal["sirf", "stir"]] = None
|
|
405
|
+
) -> AcquisitionDataInterface:
|
|
406
|
+
"""Load acquisition data from file for the requested backend.
|
|
407
|
+
|
|
408
|
+
Args:
|
|
409
|
+
filepath: Path to the acquisition header file to load.
|
|
410
|
+
backend: Optional backend override. If omitted, use auto-detected backend.
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
AcquisitionDataInterface: Wrapped backend-specific acquisition object.
|
|
414
|
+
|
|
415
|
+
Raises:
|
|
416
|
+
ValueError: If backend is explicitly provided but invalid.
|
|
417
|
+
"""
|
|
418
|
+
backend_to_use = backend or get_backend()
|
|
419
|
+
|
|
420
|
+
if backend_to_use == "sirf":
|
|
421
|
+
from .sirf_backend import SirfAcquisitionData
|
|
422
|
+
|
|
423
|
+
return SirfAcquisitionData.read_from_file(filepath)
|
|
424
|
+
if backend_to_use == "stir":
|
|
425
|
+
from .stir_backend import StirAcquisitionData
|
|
426
|
+
|
|
427
|
+
return StirAcquisitionData.read_from_file(filepath)
|
|
428
|
+
|
|
429
|
+
raise ValueError("Invalid backend. Expected 'sirf' or 'stir'.")
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def is_sirf_backend() -> bool:
|
|
433
|
+
"""Check if SIRF backend is active.
|
|
434
|
+
|
|
435
|
+
Returns:
|
|
436
|
+
bool: True if SIRF is the active backend
|
|
437
|
+
"""
|
|
438
|
+
return get_backend() == "sirf"
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def is_stir_backend() -> bool:
|
|
442
|
+
"""Check if STIR Python backend is active.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
bool: True if STIR is the active backend
|
|
446
|
+
"""
|
|
447
|
+
return get_backend() == "stir"
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def unwrap(obj: Union[ImageDataInterface, AcquisitionDataInterface]):
|
|
451
|
+
"""Get the native object from a wrapped backend object.
|
|
452
|
+
|
|
453
|
+
Args:
|
|
454
|
+
obj: Wrapped image or acquisition data
|
|
455
|
+
|
|
456
|
+
Returns:
|
|
457
|
+
The underlying SIRF or STIR object
|
|
458
|
+
"""
|
|
459
|
+
if hasattr(obj, "native_object"):
|
|
460
|
+
return obj.native_object
|
|
461
|
+
return obj
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
__all__ = [
|
|
465
|
+
"get_backend",
|
|
466
|
+
"set_backend",
|
|
467
|
+
"reset_backend",
|
|
468
|
+
"detect_image_backend",
|
|
469
|
+
"detect_acquisition_backend",
|
|
470
|
+
"detect_backend_from_interface",
|
|
471
|
+
"create_image_data",
|
|
472
|
+
"create_acquisition_data",
|
|
473
|
+
"load_image_data",
|
|
474
|
+
"load_acquisition_data",
|
|
475
|
+
"is_sirf_backend",
|
|
476
|
+
"is_stir_backend",
|
|
477
|
+
"unwrap",
|
|
478
|
+
"ImageDataInterface",
|
|
479
|
+
"AcquisitionDataInterface",
|
|
480
|
+
]
|