simind-python-connector 1.1.1__py3-none-any.whl → 1.2.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 +2 -11
- simind_python_connector/_version.py +3 -0
- simind_python_connector/backends/__init__.py +34 -31
- simind_python_connector/backends/base.py +7 -52
- simind_python_connector/backends/sirf_backend.py +19 -23
- simind_python_connector/backends/stir_backend.py +74 -46
- simind_python_connector/builders/__init__.py +2 -2
- simind_python_connector/builders/acquisition_builder.py +8 -25
- simind_python_connector/builders/image_builder.py +4 -4
- simind_python_connector/configs/__init__.py +1 -2
- simind_python_connector/connectors/__init__.py +2 -2
- simind_python_connector/connectors/_spacing.py +115 -11
- simind_python_connector/connectors/base.py +35 -2
- simind_python_connector/connectors/python_connector.py +451 -106
- simind_python_connector/connectors/pytomography_adaptor.py +62 -29
- simind_python_connector/connectors/sirf_adaptor.py +47 -16
- simind_python_connector/connectors/stir_adaptor.py +50 -16
- simind_python_connector/converters/attenuation.py +1 -10
- simind_python_connector/converters/simind_to_stir.py +92 -60
- simind_python_connector/core/config.py +420 -108
- simind_python_connector/core/executor.py +268 -31
- simind_python_connector/core/types.py +21 -8
- simind_python_connector/data/__init__.py +5 -3
- simind_python_connector/utils/__init__.py +10 -20
- simind_python_connector/utils/backend_access.py +4 -4
- simind_python_connector/utils/geometry_phantom.py +42 -0
- simind_python_connector/utils/import_helpers.py +37 -10
- simind_python_connector/utils/interfile_numpy.py +26 -5
- simind_python_connector/utils/interfile_parser.py +9 -10
- simind_python_connector/utils/simind_utils.py +31 -8
- simind_python_connector/utils/sirf_stir_utils.py +11 -11
- simind_python_connector/utils/stir_utils.py +12 -18
- {simind_python_connector-1.1.1.dist-info → simind_python_connector-1.2.0.dist-info}/METADATA +30 -12
- simind_python_connector-1.2.0.dist-info/RECORD +49 -0
- simind_python_connector-1.1.1.dist-info/RECORD +0 -47
- {simind_python_connector-1.1.1.dist-info → simind_python_connector-1.2.0.dist-info}/WHEEL +0 -0
- {simind_python_connector-1.1.1.dist-info → simind_python_connector-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {simind_python_connector-1.1.1.dist-info → simind_python_connector-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -3,18 +3,9 @@ simind-python-connector connector/adaptor API.
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import importlib
|
|
6
|
-
from importlib import metadata as _meta
|
|
7
6
|
from typing import Any
|
|
8
7
|
|
|
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.1.1"
|
|
8
|
+
from ._version import __version__ as __version__
|
|
18
9
|
|
|
19
10
|
|
|
20
11
|
def __getattr__(name: str) -> Any:
|
|
@@ -58,9 +49,9 @@ __all__ = [
|
|
|
58
49
|
"ProjectionResult",
|
|
59
50
|
"PyTomographySimindAdaptor",
|
|
60
51
|
"RuntimeOperator",
|
|
61
|
-
"SirfSimindAdaptor",
|
|
62
52
|
"SimindPythonConnector",
|
|
63
53
|
"SimulationConfig",
|
|
54
|
+
"SirfSimindAdaptor",
|
|
64
55
|
"StirSimindAdaptor",
|
|
65
56
|
"builders",
|
|
66
57
|
"configs",
|
|
@@ -20,8 +20,9 @@ Usage:
|
|
|
20
20
|
import importlib
|
|
21
21
|
import logging
|
|
22
22
|
import os
|
|
23
|
-
from typing import TYPE_CHECKING, Any, Literal,
|
|
23
|
+
from typing import TYPE_CHECKING, Any, Literal, Union
|
|
24
24
|
|
|
25
|
+
from ..utils.import_helpers import _import_stir_extra
|
|
25
26
|
from .base import AcquisitionDataInterface, ImageDataInterface
|
|
26
27
|
|
|
27
28
|
|
|
@@ -43,11 +44,11 @@ if TYPE_CHECKING:
|
|
|
43
44
|
FloatVoxelsOnCartesianGrid = Any # type: ignore[assignment]
|
|
44
45
|
|
|
45
46
|
# Global backend state
|
|
46
|
-
_backend:
|
|
47
|
+
_backend: str | None = None
|
|
47
48
|
_backend_initialized = False
|
|
48
49
|
|
|
49
50
|
|
|
50
|
-
def _import_sirf_types() -> tuple[
|
|
51
|
+
def _import_sirf_types() -> tuple[type | None, type | None]:
|
|
51
52
|
"""Import SIRF image/acquisition types lazily."""
|
|
52
53
|
try:
|
|
53
54
|
from sirf.STIR import AcquisitionData as SirfAcquisitionData # type: ignore
|
|
@@ -57,7 +58,7 @@ def _import_sirf_types() -> tuple[Optional[type], Optional[type]]:
|
|
|
57
58
|
return SirfImageData, SirfAcquisitionData
|
|
58
59
|
|
|
59
60
|
|
|
60
|
-
def _import_stir_types() -> tuple[
|
|
61
|
+
def _import_stir_types() -> tuple[type | None, type | None]:
|
|
61
62
|
"""Import STIR image/acquisition types lazily."""
|
|
62
63
|
try:
|
|
63
64
|
from stir import FloatVoxelsOnCartesianGrid as StirImageData # type: ignore
|
|
@@ -68,7 +69,7 @@ def _import_stir_types() -> tuple[Optional[type], Optional[type]]:
|
|
|
68
69
|
return StirImageData, StirProjData
|
|
69
70
|
|
|
70
71
|
|
|
71
|
-
def detect_image_backend(obj: Any) ->
|
|
72
|
+
def detect_image_backend(obj: Any) -> str | None:
|
|
72
73
|
"""Return 'sirf' or 'stir' if obj is a native image type."""
|
|
73
74
|
if obj is None:
|
|
74
75
|
return None
|
|
@@ -84,7 +85,7 @@ def detect_image_backend(obj: Any) -> Optional[str]:
|
|
|
84
85
|
return None
|
|
85
86
|
|
|
86
87
|
|
|
87
|
-
def detect_acquisition_backend(obj: Any) ->
|
|
88
|
+
def detect_acquisition_backend(obj: Any) -> str | None:
|
|
88
89
|
"""Return 'sirf' or 'stir' if obj is a native acquisition type."""
|
|
89
90
|
if obj is None:
|
|
90
91
|
return None
|
|
@@ -101,8 +102,8 @@ def detect_acquisition_backend(obj: Any) -> Optional[str]:
|
|
|
101
102
|
|
|
102
103
|
|
|
103
104
|
def detect_backend_from_interface(
|
|
104
|
-
obj:
|
|
105
|
-
) ->
|
|
105
|
+
obj: ImageDataInterface | AcquisitionDataInterface | Any,
|
|
106
|
+
) -> str | None:
|
|
106
107
|
"""Detect backend by unwrapping known interface objects."""
|
|
107
108
|
native = getattr(obj, "native_object", None)
|
|
108
109
|
if native is None:
|
|
@@ -115,7 +116,7 @@ def detect_backend_from_interface(
|
|
|
115
116
|
return detect_acquisition_backend(native)
|
|
116
117
|
|
|
117
118
|
|
|
118
|
-
def _activate_backend(preferred:
|
|
119
|
+
def _activate_backend(preferred: str | None) -> str | None:
|
|
119
120
|
"""Force-activate a specific backend if requested."""
|
|
120
121
|
if preferred is None:
|
|
121
122
|
return None
|
|
@@ -150,7 +151,7 @@ def get_backend() -> str:
|
|
|
150
151
|
except ImportError:
|
|
151
152
|
try:
|
|
152
153
|
importlib.import_module("stir")
|
|
153
|
-
|
|
154
|
+
_import_stir_extra()
|
|
154
155
|
|
|
155
156
|
_backend = "stir"
|
|
156
157
|
logger.info("Auto-detected STIR Python backend")
|
|
@@ -159,7 +160,7 @@ def get_backend() -> str:
|
|
|
159
160
|
"Neither SIRF nor STIR Python found. "
|
|
160
161
|
"Please install one of: "
|
|
161
162
|
"\n - SIRF (sirf.STIR)"
|
|
162
|
-
"\n - STIR Python (stir +
|
|
163
|
+
"\n - STIR Python (stir + stir.extra)"
|
|
163
164
|
)
|
|
164
165
|
|
|
165
166
|
_backend_initialized = True
|
|
@@ -190,7 +191,7 @@ def set_backend(backend: Literal["sirf", "stir"]) -> None:
|
|
|
190
191
|
elif backend == "stir":
|
|
191
192
|
try:
|
|
192
193
|
importlib.import_module("stir")
|
|
193
|
-
|
|
194
|
+
_import_stir_extra()
|
|
194
195
|
except ImportError:
|
|
195
196
|
raise ImportError("STIR Python is not available")
|
|
196
197
|
|
|
@@ -210,9 +211,10 @@ def reset_backend() -> None:
|
|
|
210
211
|
|
|
211
212
|
|
|
212
213
|
def create_image_data(
|
|
213
|
-
filepath_or_object:
|
|
214
|
-
|
|
215
|
-
]
|
|
214
|
+
filepath_or_object: Union[
|
|
215
|
+
str, ImageDataInterface, "ImageData", "FloatVoxelsOnCartesianGrid"
|
|
216
|
+
]
|
|
217
|
+
| None = None,
|
|
216
218
|
) -> ImageDataInterface:
|
|
217
219
|
"""Create an image data object using the current backend.
|
|
218
220
|
|
|
@@ -294,9 +296,10 @@ def create_image_data(
|
|
|
294
296
|
|
|
295
297
|
|
|
296
298
|
def create_acquisition_data(
|
|
297
|
-
filepath_or_object:
|
|
298
|
-
|
|
299
|
-
]
|
|
299
|
+
filepath_or_object: Union[
|
|
300
|
+
str, AcquisitionDataInterface, "AcquisitionData", "ProjData"
|
|
301
|
+
]
|
|
302
|
+
| None = None,
|
|
300
303
|
) -> AcquisitionDataInterface:
|
|
301
304
|
"""Create an acquisition data object using the current backend.
|
|
302
305
|
|
|
@@ -379,7 +382,7 @@ def create_acquisition_data(
|
|
|
379
382
|
|
|
380
383
|
|
|
381
384
|
def load_image_data(
|
|
382
|
-
filepath: str, backend:
|
|
385
|
+
filepath: str, backend: Literal["sirf", "stir"] | None = None
|
|
383
386
|
) -> ImageDataInterface:
|
|
384
387
|
"""Load image data from file for the requested backend.
|
|
385
388
|
|
|
@@ -408,7 +411,7 @@ def load_image_data(
|
|
|
408
411
|
|
|
409
412
|
|
|
410
413
|
def load_acquisition_data(
|
|
411
|
-
filepath: str, backend:
|
|
414
|
+
filepath: str, backend: Literal["sirf", "stir"] | None = None
|
|
412
415
|
) -> AcquisitionDataInterface:
|
|
413
416
|
"""Load acquisition data from file for the requested backend.
|
|
414
417
|
|
|
@@ -454,7 +457,7 @@ def is_stir_backend() -> bool:
|
|
|
454
457
|
return get_backend() == "stir"
|
|
455
458
|
|
|
456
459
|
|
|
457
|
-
def unwrap(obj:
|
|
460
|
+
def unwrap(obj: ImageDataInterface | AcquisitionDataInterface):
|
|
458
461
|
"""Get the native object from a wrapped backend object.
|
|
459
462
|
|
|
460
463
|
Args:
|
|
@@ -469,19 +472,19 @@ def unwrap(obj: Union[ImageDataInterface, AcquisitionDataInterface]):
|
|
|
469
472
|
|
|
470
473
|
|
|
471
474
|
__all__ = [
|
|
472
|
-
"
|
|
473
|
-
"
|
|
474
|
-
"
|
|
475
|
-
"
|
|
475
|
+
"AcquisitionDataInterface",
|
|
476
|
+
"ImageDataInterface",
|
|
477
|
+
"create_acquisition_data",
|
|
478
|
+
"create_image_data",
|
|
476
479
|
"detect_acquisition_backend",
|
|
477
480
|
"detect_backend_from_interface",
|
|
478
|
-
"
|
|
479
|
-
"
|
|
480
|
-
"load_image_data",
|
|
481
|
-
"load_acquisition_data",
|
|
481
|
+
"detect_image_backend",
|
|
482
|
+
"get_backend",
|
|
482
483
|
"is_sirf_backend",
|
|
483
484
|
"is_stir_backend",
|
|
485
|
+
"load_acquisition_data",
|
|
486
|
+
"load_image_data",
|
|
487
|
+
"reset_backend",
|
|
488
|
+
"set_backend",
|
|
484
489
|
"unwrap",
|
|
485
|
-
"ImageDataInterface",
|
|
486
|
-
"AcquisitionDataInterface",
|
|
487
490
|
]
|
|
@@ -6,7 +6,6 @@ for working with both SIRF and STIR Python libraries.
|
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
|
-
from typing import Tuple, Union
|
|
10
9
|
|
|
11
10
|
import numpy as np
|
|
12
11
|
|
|
@@ -28,7 +27,6 @@ class ImageDataInterface(ABC):
|
|
|
28
27
|
Returns:
|
|
29
28
|
ImageDataInterface: Wrapped image object
|
|
30
29
|
"""
|
|
31
|
-
pass
|
|
32
30
|
|
|
33
31
|
@abstractmethod
|
|
34
32
|
def write(self, filepath: str) -> None:
|
|
@@ -37,16 +35,14 @@ class ImageDataInterface(ABC):
|
|
|
37
35
|
Args:
|
|
38
36
|
filepath: Output file path
|
|
39
37
|
"""
|
|
40
|
-
pass
|
|
41
38
|
|
|
42
39
|
@abstractmethod
|
|
43
|
-
def fill(self, data:
|
|
40
|
+
def fill(self, data: np.ndarray | float) -> None:
|
|
44
41
|
"""Fill image with data.
|
|
45
42
|
|
|
46
43
|
Args:
|
|
47
44
|
data: Numpy array or scalar value
|
|
48
45
|
"""
|
|
49
|
-
pass
|
|
50
46
|
|
|
51
47
|
@abstractmethod
|
|
52
48
|
def clone(self) -> "ImageDataInterface":
|
|
@@ -55,7 +51,6 @@ class ImageDataInterface(ABC):
|
|
|
55
51
|
Returns:
|
|
56
52
|
ImageDataInterface: Cloned image object
|
|
57
53
|
"""
|
|
58
|
-
pass
|
|
59
54
|
|
|
60
55
|
@abstractmethod
|
|
61
56
|
def get_uniform_copy(self, value: float) -> "ImageDataInterface":
|
|
@@ -67,25 +62,22 @@ class ImageDataInterface(ABC):
|
|
|
67
62
|
Returns:
|
|
68
63
|
ImageDataInterface: New uniform image
|
|
69
64
|
"""
|
|
70
|
-
pass
|
|
71
65
|
|
|
72
66
|
@abstractmethod
|
|
73
|
-
def dimensions(self) ->
|
|
67
|
+
def dimensions(self) -> tuple[int, int, int]:
|
|
74
68
|
"""Get image dimensions.
|
|
75
69
|
|
|
76
70
|
Returns:
|
|
77
71
|
Tuple[int, int, int]: (z, y, x) dimensions
|
|
78
72
|
"""
|
|
79
|
-
pass
|
|
80
73
|
|
|
81
74
|
@abstractmethod
|
|
82
|
-
def voxel_sizes(self) ->
|
|
75
|
+
def voxel_sizes(self) -> tuple[float, float, float]:
|
|
83
76
|
"""Get voxel sizes in mm.
|
|
84
77
|
|
|
85
78
|
Returns:
|
|
86
79
|
Tuple[float, float, float]: (z, y, x) voxel sizes
|
|
87
80
|
"""
|
|
88
|
-
pass
|
|
89
81
|
|
|
90
82
|
@abstractmethod
|
|
91
83
|
def as_array(self) -> np.ndarray:
|
|
@@ -94,7 +86,6 @@ class ImageDataInterface(ABC):
|
|
|
94
86
|
Returns:
|
|
95
87
|
np.ndarray: Image data as numpy array
|
|
96
88
|
"""
|
|
97
|
-
pass
|
|
98
89
|
|
|
99
90
|
@abstractmethod
|
|
100
91
|
def sum(self) -> float:
|
|
@@ -103,7 +94,6 @@ class ImageDataInterface(ABC):
|
|
|
103
94
|
Returns:
|
|
104
95
|
float: Sum of all voxel values
|
|
105
96
|
"""
|
|
106
|
-
pass
|
|
107
97
|
|
|
108
98
|
@abstractmethod
|
|
109
99
|
def max(self) -> float:
|
|
@@ -112,7 +102,6 @@ class ImageDataInterface(ABC):
|
|
|
112
102
|
Returns:
|
|
113
103
|
float: Maximum voxel value
|
|
114
104
|
"""
|
|
115
|
-
pass
|
|
116
105
|
|
|
117
106
|
@abstractmethod
|
|
118
107
|
def maximum(self, value: float) -> None:
|
|
@@ -121,7 +110,6 @@ class ImageDataInterface(ABC):
|
|
|
121
110
|
Args:
|
|
122
111
|
value: Minimum value to clip to
|
|
123
112
|
"""
|
|
124
|
-
pass
|
|
125
113
|
|
|
126
114
|
@property
|
|
127
115
|
@abstractmethod
|
|
@@ -131,53 +119,43 @@ class ImageDataInterface(ABC):
|
|
|
131
119
|
Returns either SIRF ``ImageData`` or STIR
|
|
132
120
|
``FloatVoxelsOnCartesianGrid``.
|
|
133
121
|
"""
|
|
134
|
-
pass
|
|
135
122
|
|
|
136
123
|
# Arithmetic operations
|
|
137
124
|
@abstractmethod
|
|
138
125
|
def __add__(self, other):
|
|
139
126
|
"""Add two images or image and scalar."""
|
|
140
|
-
pass
|
|
141
127
|
|
|
142
128
|
@abstractmethod
|
|
143
129
|
def __sub__(self, other):
|
|
144
130
|
"""Subtract two images or image and scalar."""
|
|
145
|
-
pass
|
|
146
131
|
|
|
147
132
|
@abstractmethod
|
|
148
133
|
def __mul__(self, other):
|
|
149
134
|
"""Multiply image by scalar or element-wise."""
|
|
150
|
-
pass
|
|
151
135
|
|
|
152
136
|
@abstractmethod
|
|
153
137
|
def __truediv__(self, other):
|
|
154
138
|
"""Divide image by scalar or element-wise."""
|
|
155
|
-
pass
|
|
156
139
|
|
|
157
140
|
@abstractmethod
|
|
158
141
|
def __radd__(self, other):
|
|
159
142
|
"""Right-hand addition."""
|
|
160
|
-
pass
|
|
161
143
|
|
|
162
144
|
@abstractmethod
|
|
163
145
|
def __rsub__(self, other):
|
|
164
146
|
"""Right-hand subtraction."""
|
|
165
|
-
pass
|
|
166
147
|
|
|
167
148
|
@abstractmethod
|
|
168
149
|
def __rmul__(self, other):
|
|
169
150
|
"""Right-hand multiplication."""
|
|
170
|
-
pass
|
|
171
151
|
|
|
172
152
|
@abstractmethod
|
|
173
153
|
def __rtruediv__(self, other):
|
|
174
154
|
"""Right-hand division."""
|
|
175
|
-
pass
|
|
176
155
|
|
|
177
156
|
@abstractmethod
|
|
178
157
|
def __neg__(self):
|
|
179
158
|
"""Negate image."""
|
|
180
|
-
pass
|
|
181
159
|
|
|
182
160
|
@abstractmethod
|
|
183
161
|
def sapyb(
|
|
@@ -195,7 +173,6 @@ class ImageDataInterface(ABC):
|
|
|
195
173
|
Returns:
|
|
196
174
|
ImageDataInterface: self after modification (for chaining)
|
|
197
175
|
"""
|
|
198
|
-
pass
|
|
199
176
|
|
|
200
177
|
|
|
201
178
|
class AcquisitionDataInterface(ABC):
|
|
@@ -212,7 +189,6 @@ class AcquisitionDataInterface(ABC):
|
|
|
212
189
|
Returns:
|
|
213
190
|
AcquisitionDataInterface: Wrapped acquisition object
|
|
214
191
|
"""
|
|
215
|
-
pass
|
|
216
192
|
|
|
217
193
|
@abstractmethod
|
|
218
194
|
def write(self, filepath: str) -> None:
|
|
@@ -221,16 +197,14 @@ class AcquisitionDataInterface(ABC):
|
|
|
221
197
|
Args:
|
|
222
198
|
filepath: Output file path
|
|
223
199
|
"""
|
|
224
|
-
pass
|
|
225
200
|
|
|
226
201
|
@abstractmethod
|
|
227
|
-
def fill(self, data:
|
|
202
|
+
def fill(self, data: np.ndarray | float) -> None:
|
|
228
203
|
"""Fill acquisition data.
|
|
229
204
|
|
|
230
205
|
Args:
|
|
231
206
|
data: Numpy array or scalar value
|
|
232
207
|
"""
|
|
233
|
-
pass
|
|
234
208
|
|
|
235
209
|
@abstractmethod
|
|
236
210
|
def clone(self) -> "AcquisitionDataInterface":
|
|
@@ -239,7 +213,6 @@ class AcquisitionDataInterface(ABC):
|
|
|
239
213
|
Returns:
|
|
240
214
|
AcquisitionDataInterface: Cloned object
|
|
241
215
|
"""
|
|
242
|
-
pass
|
|
243
216
|
|
|
244
217
|
@abstractmethod
|
|
245
218
|
def get_uniform_copy(self, value: float) -> "AcquisitionDataInterface":
|
|
@@ -251,19 +224,17 @@ class AcquisitionDataInterface(ABC):
|
|
|
251
224
|
Returns:
|
|
252
225
|
AcquisitionDataInterface: New uniform acquisition data
|
|
253
226
|
"""
|
|
254
|
-
pass
|
|
255
227
|
|
|
256
228
|
@abstractmethod
|
|
257
|
-
def dimensions(self) ->
|
|
229
|
+
def dimensions(self) -> tuple:
|
|
258
230
|
"""Get acquisition data dimensions.
|
|
259
231
|
|
|
260
232
|
Returns:
|
|
261
233
|
Tuple: Dimensions (varies by format)
|
|
262
234
|
"""
|
|
263
|
-
pass
|
|
264
235
|
|
|
265
236
|
@property
|
|
266
|
-
def shape(self) ->
|
|
237
|
+
def shape(self) -> tuple:
|
|
267
238
|
"""Get acquisition data shape (alias for dimensions()).
|
|
268
239
|
|
|
269
240
|
Returns:
|
|
@@ -278,7 +249,6 @@ class AcquisitionDataInterface(ABC):
|
|
|
278
249
|
Returns:
|
|
279
250
|
np.ndarray: Acquisition data as numpy array
|
|
280
251
|
"""
|
|
281
|
-
pass
|
|
282
252
|
|
|
283
253
|
@abstractmethod
|
|
284
254
|
def sum(self) -> float:
|
|
@@ -287,7 +257,6 @@ class AcquisitionDataInterface(ABC):
|
|
|
287
257
|
Returns:
|
|
288
258
|
float: Sum of all counts
|
|
289
259
|
"""
|
|
290
|
-
pass
|
|
291
260
|
|
|
292
261
|
@abstractmethod
|
|
293
262
|
def max(self) -> float:
|
|
@@ -296,7 +265,6 @@ class AcquisitionDataInterface(ABC):
|
|
|
296
265
|
Returns:
|
|
297
266
|
float: Maximum bin value
|
|
298
267
|
"""
|
|
299
|
-
pass
|
|
300
268
|
|
|
301
269
|
@abstractmethod
|
|
302
270
|
def get_info(self) -> str:
|
|
@@ -305,68 +273,56 @@ class AcquisitionDataInterface(ABC):
|
|
|
305
273
|
Returns:
|
|
306
274
|
str: Metadata string
|
|
307
275
|
"""
|
|
308
|
-
pass
|
|
309
276
|
|
|
310
277
|
@abstractmethod
|
|
311
|
-
def get_energy_window_bounds(self) ->
|
|
278
|
+
def get_energy_window_bounds(self) -> tuple[float, float]:
|
|
312
279
|
"""Get the energy window bounds for the acquisition.
|
|
313
280
|
|
|
314
281
|
Returns:
|
|
315
282
|
Tuple[float, float]: (lower_threshold, upper_threshold) in keV
|
|
316
283
|
"""
|
|
317
|
-
pass
|
|
318
284
|
|
|
319
285
|
@property
|
|
320
286
|
@abstractmethod
|
|
321
287
|
def native_object(self):
|
|
322
288
|
"""Get the underlying native object (AcquisitionData or ProjData)."""
|
|
323
|
-
pass
|
|
324
289
|
|
|
325
290
|
# Arithmetic operations
|
|
326
291
|
@abstractmethod
|
|
327
292
|
def __add__(self, other):
|
|
328
293
|
"""Add two acquisition data or acquisition data and scalar."""
|
|
329
|
-
pass
|
|
330
294
|
|
|
331
295
|
@abstractmethod
|
|
332
296
|
def __sub__(self, other):
|
|
333
297
|
"""Subtract two acquisition data or acquisition data and scalar."""
|
|
334
|
-
pass
|
|
335
298
|
|
|
336
299
|
@abstractmethod
|
|
337
300
|
def __mul__(self, other):
|
|
338
301
|
"""Multiply acquisition data by scalar or element-wise."""
|
|
339
|
-
pass
|
|
340
302
|
|
|
341
303
|
@abstractmethod
|
|
342
304
|
def __truediv__(self, other):
|
|
343
305
|
"""Divide acquisition data by scalar or element-wise."""
|
|
344
|
-
pass
|
|
345
306
|
|
|
346
307
|
@abstractmethod
|
|
347
308
|
def __radd__(self, other):
|
|
348
309
|
"""Right-hand addition."""
|
|
349
|
-
pass
|
|
350
310
|
|
|
351
311
|
@abstractmethod
|
|
352
312
|
def __rsub__(self, other):
|
|
353
313
|
"""Right-hand subtraction."""
|
|
354
|
-
pass
|
|
355
314
|
|
|
356
315
|
@abstractmethod
|
|
357
316
|
def __rmul__(self, other):
|
|
358
317
|
"""Right-hand multiplication."""
|
|
359
|
-
pass
|
|
360
318
|
|
|
361
319
|
@abstractmethod
|
|
362
320
|
def __rtruediv__(self, other):
|
|
363
321
|
"""Right-hand division."""
|
|
364
|
-
pass
|
|
365
322
|
|
|
366
323
|
@abstractmethod
|
|
367
324
|
def __neg__(self):
|
|
368
325
|
"""Negate acquisition data."""
|
|
369
|
-
pass
|
|
370
326
|
|
|
371
327
|
@abstractmethod
|
|
372
328
|
def sapyb(
|
|
@@ -384,4 +340,3 @@ class AcquisitionDataInterface(ABC):
|
|
|
384
340
|
Returns:
|
|
385
341
|
AcquisitionDataInterface: self after modification (for chaining)
|
|
386
342
|
"""
|
|
387
|
-
pass
|
|
@@ -5,7 +5,7 @@ Provides wrappers around sirf.STIR.ImageData and sirf.STIR.AcquisitionData
|
|
|
5
5
|
that conform to the backend interface.
|
|
6
6
|
"""
|
|
7
7
|
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import Union
|
|
9
9
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
@@ -60,21 +60,17 @@ class SirfImageData(ImageDataInterface):
|
|
|
60
60
|
"""Create uniform copy."""
|
|
61
61
|
return SirfImageData(self._obj.get_uniform_copy(value))
|
|
62
62
|
|
|
63
|
-
def dimensions(self) ->
|
|
63
|
+
def dimensions(self) -> tuple[int, int, int]:
|
|
64
64
|
"""Get dimensions as (z, y, x)."""
|
|
65
65
|
return self._obj.dimensions()
|
|
66
66
|
|
|
67
|
-
def voxel_sizes(self) ->
|
|
67
|
+
def voxel_sizes(self) -> tuple[float, float, float]:
|
|
68
68
|
"""Get voxel sizes as (z, y, x) in mm."""
|
|
69
69
|
return self._obj.voxel_sizes()
|
|
70
70
|
|
|
71
71
|
def as_array(self) -> np.ndarray:
|
|
72
72
|
"""Convert to numpy array."""
|
|
73
|
-
|
|
74
|
-
if hasattr(self._obj, "asarray"):
|
|
75
|
-
return self._obj.asarray()
|
|
76
|
-
else:
|
|
77
|
-
return self._obj.as_array()
|
|
73
|
+
return self._obj.as_array()
|
|
78
74
|
|
|
79
75
|
def sum(self) -> float:
|
|
80
76
|
"""Sum all values."""
|
|
@@ -124,16 +120,18 @@ class SirfImageData(ImageDataInterface):
|
|
|
124
120
|
return SirfImageData(self._obj / other)
|
|
125
121
|
|
|
126
122
|
def __radd__(self, other):
|
|
127
|
-
return
|
|
123
|
+
return self + other
|
|
128
124
|
|
|
129
125
|
def __rsub__(self, other):
|
|
130
|
-
return
|
|
126
|
+
return -self + other
|
|
131
127
|
|
|
132
128
|
def __rmul__(self, other):
|
|
133
|
-
return
|
|
129
|
+
return self * other
|
|
134
130
|
|
|
135
131
|
def __rtruediv__(self, other):
|
|
136
|
-
|
|
132
|
+
if isinstance(other, ImageData):
|
|
133
|
+
return SirfImageData(other) / self
|
|
134
|
+
return self.get_uniform_copy(float(other)) / self
|
|
137
135
|
|
|
138
136
|
def __neg__(self):
|
|
139
137
|
return SirfImageData(-self._obj)
|
|
@@ -197,17 +195,13 @@ class SirfAcquisitionData(AcquisitionDataInterface):
|
|
|
197
195
|
"""Create uniform copy."""
|
|
198
196
|
return SirfAcquisitionData(self._obj.get_uniform_copy(value))
|
|
199
197
|
|
|
200
|
-
def dimensions(self) ->
|
|
198
|
+
def dimensions(self) -> tuple:
|
|
201
199
|
"""Get dimensions."""
|
|
202
200
|
return self._obj.dimensions()
|
|
203
201
|
|
|
204
202
|
def as_array(self) -> np.ndarray:
|
|
205
203
|
"""Convert to numpy array."""
|
|
206
|
-
|
|
207
|
-
if hasattr(self._obj, "asarray"):
|
|
208
|
-
return self._obj.asarray()
|
|
209
|
-
else:
|
|
210
|
-
return self._obj.as_array()
|
|
204
|
+
return self._obj.as_array()
|
|
211
205
|
|
|
212
206
|
def sum(self) -> float:
|
|
213
207
|
"""Sum all values."""
|
|
@@ -223,7 +217,7 @@ class SirfAcquisitionData(AcquisitionDataInterface):
|
|
|
223
217
|
"""Get metadata info."""
|
|
224
218
|
return self._obj.get_info()
|
|
225
219
|
|
|
226
|
-
def get_energy_window_bounds(self) ->
|
|
220
|
+
def get_energy_window_bounds(self) -> tuple[float, float]:
|
|
227
221
|
"""Get the energy window bounds for the acquisition.
|
|
228
222
|
|
|
229
223
|
Returns:
|
|
@@ -277,16 +271,18 @@ class SirfAcquisitionData(AcquisitionDataInterface):
|
|
|
277
271
|
return SirfAcquisitionData(self._obj / other)
|
|
278
272
|
|
|
279
273
|
def __radd__(self, other):
|
|
280
|
-
return
|
|
274
|
+
return self + other
|
|
281
275
|
|
|
282
276
|
def __rsub__(self, other):
|
|
283
|
-
return
|
|
277
|
+
return -self + other
|
|
284
278
|
|
|
285
279
|
def __rmul__(self, other):
|
|
286
|
-
return
|
|
280
|
+
return self * other
|
|
287
281
|
|
|
288
282
|
def __rtruediv__(self, other):
|
|
289
|
-
|
|
283
|
+
if isinstance(other, AcquisitionData):
|
|
284
|
+
return SirfAcquisitionData(other) / self
|
|
285
|
+
return self.get_uniform_copy(float(other)) / self
|
|
290
286
|
|
|
291
287
|
def __neg__(self):
|
|
292
288
|
return SirfAcquisitionData(-self._obj)
|