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,96 @@
|
|
|
1
|
+
"""Minimal SIMIND process runner used by connector-first APIs."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import subprocess
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Dict, Optional
|
|
9
|
+
|
|
10
|
+
from .types import SimulationError
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class SimindExecutor:
|
|
14
|
+
"""Run SIMIND as a subprocess with optional MPI switch support."""
|
|
15
|
+
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self.logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
def run_simulation(
|
|
20
|
+
self,
|
|
21
|
+
output_prefix: str,
|
|
22
|
+
orbit_file: Optional[Path] = None,
|
|
23
|
+
runtime_switches: Optional[Dict] = None,
|
|
24
|
+
) -> None:
|
|
25
|
+
mp_value = None
|
|
26
|
+
if runtime_switches:
|
|
27
|
+
for key, value in runtime_switches.items():
|
|
28
|
+
if str(key).lower() == "mp":
|
|
29
|
+
mp_value = value
|
|
30
|
+
break
|
|
31
|
+
|
|
32
|
+
validated_output_prefix = self._validate_cli_token(output_prefix)
|
|
33
|
+
validated_orbit_name = (
|
|
34
|
+
self._validate_cli_token(orbit_file.name) if orbit_file else None
|
|
35
|
+
)
|
|
36
|
+
validated_mp_value = (
|
|
37
|
+
self._validate_cli_token(mp_value) if mp_value is not None else None
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
validated_switch_blob = None
|
|
41
|
+
if runtime_switches:
|
|
42
|
+
switch_parts = []
|
|
43
|
+
for key, value in runtime_switches.items():
|
|
44
|
+
if str(key).upper() == "MP":
|
|
45
|
+
switch_parts.append(f"/{key}")
|
|
46
|
+
else:
|
|
47
|
+
switch_parts.append(f"/{key}:{value}")
|
|
48
|
+
if switch_parts:
|
|
49
|
+
validated_switch_blob = self._validate_cli_token("".join(switch_parts))
|
|
50
|
+
|
|
51
|
+
if validated_mp_value is not None:
|
|
52
|
+
command = [
|
|
53
|
+
"mpirun",
|
|
54
|
+
"-np",
|
|
55
|
+
validated_mp_value,
|
|
56
|
+
"simind",
|
|
57
|
+
validated_output_prefix,
|
|
58
|
+
validated_output_prefix,
|
|
59
|
+
]
|
|
60
|
+
if validated_orbit_name is not None:
|
|
61
|
+
command.append(validated_orbit_name)
|
|
62
|
+
command.append("-p")
|
|
63
|
+
if validated_switch_blob is not None:
|
|
64
|
+
command.append(validated_switch_blob)
|
|
65
|
+
else:
|
|
66
|
+
command = ["simind", validated_output_prefix, validated_output_prefix]
|
|
67
|
+
if validated_orbit_name is not None:
|
|
68
|
+
command.append(validated_orbit_name)
|
|
69
|
+
if validated_switch_blob is not None:
|
|
70
|
+
command.append(validated_switch_blob)
|
|
71
|
+
|
|
72
|
+
self.logger.info("Running SIMIND: %s", " ".join(command))
|
|
73
|
+
try:
|
|
74
|
+
subprocess.run(command, check=True, shell=False)
|
|
75
|
+
except OSError as exc:
|
|
76
|
+
raise SimulationError(f"Unable to execute SIMIND command: {exc}") from exc
|
|
77
|
+
except subprocess.CalledProcessError as exc:
|
|
78
|
+
raise SimulationError(f"SIMIND execution failed: {exc}") from exc
|
|
79
|
+
|
|
80
|
+
@staticmethod
|
|
81
|
+
def _validate_cli_token(value: object) -> str:
|
|
82
|
+
"""Validate command tokens before subprocess invocation.
|
|
83
|
+
|
|
84
|
+
This executor always runs with ``shell=False``, and each token is
|
|
85
|
+
validated to reject empty values, NUL bytes, and whitespace.
|
|
86
|
+
"""
|
|
87
|
+
token = str(value)
|
|
88
|
+
if not token:
|
|
89
|
+
raise SimulationError("Encountered empty command token for SIMIND call.")
|
|
90
|
+
if "\x00" in token:
|
|
91
|
+
raise SimulationError("SIMIND command token contains NUL byte.")
|
|
92
|
+
if any(char.isspace() for char in token):
|
|
93
|
+
raise SimulationError(
|
|
94
|
+
f"SIMIND command token contains whitespace: {token!r}"
|
|
95
|
+
)
|
|
96
|
+
return token
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Type definitions, enums, exceptions, and constants for SIMIND simulation.
|
|
3
|
+
|
|
4
|
+
This module contains types that don't depend on SIRF, allowing them to be
|
|
5
|
+
imported without triggering SIRF dependencies.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from enum import Enum
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# =============================================================================
|
|
12
|
+
# EXCEPTIONS
|
|
13
|
+
# =============================================================================
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class SimindError(Exception):
|
|
17
|
+
"""Base exception for SIMIND simulation errors."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ValidationError(SimindError):
|
|
21
|
+
"""Raised when validation fails."""
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class SimulationError(SimindError):
|
|
25
|
+
"""Raised when simulation execution fails."""
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OutputError(SimindError):
|
|
29
|
+
"""Raised when output processing fails."""
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# =============================================================================
|
|
33
|
+
# ENUMS
|
|
34
|
+
# =============================================================================
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class RotationDirection(Enum):
|
|
38
|
+
"""Rotation direction for SPECT acquisition."""
|
|
39
|
+
|
|
40
|
+
CCW = "ccw"
|
|
41
|
+
CW = "cw"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ScatterType(Enum):
|
|
45
|
+
"""SIMIND scatter output types."""
|
|
46
|
+
|
|
47
|
+
TOTAL = "tot"
|
|
48
|
+
SCATTER = "sca"
|
|
49
|
+
PRIMARY = "pri"
|
|
50
|
+
AIR = "air"
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class ScoringRoutine(Enum):
|
|
54
|
+
"""Enum for different SIMIND scoring routines."""
|
|
55
|
+
|
|
56
|
+
DUMMY = 0
|
|
57
|
+
SCATTWIN = 1
|
|
58
|
+
LIST_MODE = 2
|
|
59
|
+
FORCED_COLLIMATION = 3
|
|
60
|
+
PENETRATE = 4
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PenetrateOutputType(Enum):
|
|
64
|
+
"""Enum for different penetrate routine output components."""
|
|
65
|
+
|
|
66
|
+
def __new__(cls, value: int, slug: str, description: str):
|
|
67
|
+
obj = object.__new__(cls)
|
|
68
|
+
obj._value_ = value
|
|
69
|
+
obj.slug = slug
|
|
70
|
+
obj.description = description
|
|
71
|
+
return obj
|
|
72
|
+
|
|
73
|
+
# Without backscatter (*.b01-*.b09)
|
|
74
|
+
ALL_INTERACTIONS = (
|
|
75
|
+
1,
|
|
76
|
+
"all_interactions",
|
|
77
|
+
"All type of interactions",
|
|
78
|
+
)
|
|
79
|
+
GEOM_COLL_PRIMARY_ATT = (
|
|
80
|
+
2,
|
|
81
|
+
"geom_coll_primary",
|
|
82
|
+
"Geometrically collimated primary attenuated photons",
|
|
83
|
+
)
|
|
84
|
+
SEPTAL_PENETRATION_PRIMARY_ATT = (
|
|
85
|
+
3,
|
|
86
|
+
"septal_pen_primary",
|
|
87
|
+
"Septal penetration from primary attenuated photons",
|
|
88
|
+
)
|
|
89
|
+
COLL_SCATTER_PRIMARY_ATT = (
|
|
90
|
+
4,
|
|
91
|
+
"coll_scatter_primary",
|
|
92
|
+
"Collimator scatter from primary attenuated photons",
|
|
93
|
+
)
|
|
94
|
+
COLL_XRAY_PRIMARY_ATT = (
|
|
95
|
+
5,
|
|
96
|
+
"coll_xray_primary",
|
|
97
|
+
"X-rays from collimator (primary attenuated photons)",
|
|
98
|
+
)
|
|
99
|
+
GEOM_COLL_SCATTERED = (
|
|
100
|
+
6,
|
|
101
|
+
"geom_coll_scattered",
|
|
102
|
+
"Geometrically collimated scattered photons",
|
|
103
|
+
)
|
|
104
|
+
SEPTAL_PENETRATION_SCATTERED = (
|
|
105
|
+
7,
|
|
106
|
+
"septal_pen_scattered",
|
|
107
|
+
"Septal penetration from scattered photons",
|
|
108
|
+
)
|
|
109
|
+
COLL_SCATTER_SCATTERED = (
|
|
110
|
+
8,
|
|
111
|
+
"coll_scatter_scattered",
|
|
112
|
+
"Collimator scatter from scattered photons",
|
|
113
|
+
)
|
|
114
|
+
COLL_XRAY_SCATTERED = (
|
|
115
|
+
9,
|
|
116
|
+
"coll_xray_scattered",
|
|
117
|
+
"X-rays from collimator (scattered photons)",
|
|
118
|
+
)
|
|
119
|
+
# With backscatter (*.b10-*.b17)
|
|
120
|
+
GEOM_COLL_PRIMARY_ATT_BACK = (
|
|
121
|
+
10,
|
|
122
|
+
"geom_coll_primary_back",
|
|
123
|
+
"Geometrically collimated primary attenuated photons (with backscatter)",
|
|
124
|
+
)
|
|
125
|
+
SEPTAL_PENETRATION_PRIMARY_ATT_BACK = (
|
|
126
|
+
11,
|
|
127
|
+
"septal_pen_primary_back",
|
|
128
|
+
"Septal penetration from primary attenuated photons (with backscatter)",
|
|
129
|
+
)
|
|
130
|
+
COLL_SCATTER_PRIMARY_ATT_BACK = (
|
|
131
|
+
12,
|
|
132
|
+
"coll_scatter_primary_back",
|
|
133
|
+
"Collimator scatter from primary attenuated photons (with backscatter)",
|
|
134
|
+
)
|
|
135
|
+
COLL_XRAY_PRIMARY_ATT_BACK = (
|
|
136
|
+
13,
|
|
137
|
+
"coll_xray_primary_back",
|
|
138
|
+
"X-rays from collimator, primary attenuated photons (with backscatter)",
|
|
139
|
+
)
|
|
140
|
+
GEOM_COLL_SCATTERED_BACK = (
|
|
141
|
+
14,
|
|
142
|
+
"geom_coll_scattered_back",
|
|
143
|
+
"Geometrically collimated scattered photons (with backscatter)",
|
|
144
|
+
)
|
|
145
|
+
SEPTAL_PENETRATION_SCATTERED_BACK = (
|
|
146
|
+
15,
|
|
147
|
+
"septal_pen_scattered_back",
|
|
148
|
+
"Septal penetration from scattered photons (with backscatter)",
|
|
149
|
+
)
|
|
150
|
+
COLL_SCATTER_SCATTERED_BACK = (
|
|
151
|
+
16,
|
|
152
|
+
"coll_scatter_scattered_back",
|
|
153
|
+
"Collimator scatter from scattered photons (with backscatter)",
|
|
154
|
+
)
|
|
155
|
+
COLL_XRAY_SCATTERED_BACK = (
|
|
156
|
+
17,
|
|
157
|
+
"coll_xray_scattered_back",
|
|
158
|
+
"X-rays from collimator, scattered photons (with backscatter)",
|
|
159
|
+
)
|
|
160
|
+
ALL_UNSCATTERED_UNATTENUATED = (
|
|
161
|
+
18,
|
|
162
|
+
"unscattered_unattenuated",
|
|
163
|
+
"Photons without scattering and attenuation in phantom",
|
|
164
|
+
)
|
|
165
|
+
ALL_UNSCATTERED_UNATTENUATED_GEOM_COLL = (
|
|
166
|
+
19,
|
|
167
|
+
"unscattered_unattenuated_geom_coll",
|
|
168
|
+
"Photons without scattering/attenuation, geometrically collimated",
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
# =============================================================================
|
|
173
|
+
# CONSTANTS
|
|
174
|
+
# =============================================================================
|
|
175
|
+
|
|
176
|
+
SIMIND_VOXEL_UNIT_CONVERSION = 10 # mm to cm
|
|
177
|
+
# Maximum normalised value of source image
|
|
178
|
+
# You would have expected this to be 65535, but it is not
|
|
179
|
+
# I have no understanding why, but it is the case
|
|
180
|
+
# 500 seems a reasonable value that improves precision
|
|
181
|
+
# whilst not exceeding the maximum value (weird things happen)
|
|
182
|
+
MAX_SOURCE = 500
|
|
183
|
+
ORBIT_FILE_EXTENSION = ".cor"
|
|
184
|
+
OUTPUT_EXTENSIONS = [".h00", ".a00", ".hs"]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
__all__ = [
|
|
188
|
+
# Exceptions
|
|
189
|
+
"SimindError",
|
|
190
|
+
"ValidationError",
|
|
191
|
+
"SimulationError",
|
|
192
|
+
"OutputError",
|
|
193
|
+
# Enums
|
|
194
|
+
"RotationDirection",
|
|
195
|
+
"ScatterType",
|
|
196
|
+
"ScoringRoutine",
|
|
197
|
+
"PenetrateOutputType",
|
|
198
|
+
# Constants
|
|
199
|
+
"SIMIND_VOXEL_UNIT_CONVERSION",
|
|
200
|
+
"MAX_SOURCE",
|
|
201
|
+
"ORBIT_FILE_EXTENSION",
|
|
202
|
+
"OUTPUT_EXTENSIONS",
|
|
203
|
+
]
|
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Air_0": {
|
|
3
|
+
"density (mg/cm3)": 1.21,
|
|
4
|
+
"HU_lo (HU)": -1050.0,
|
|
5
|
+
"HU_hi (HU)": -950.0
|
|
6
|
+
},
|
|
7
|
+
"Lung_1": {
|
|
8
|
+
"density (mg/cm3)": 102.695,
|
|
9
|
+
"HU_lo (HU)": -950.0,
|
|
10
|
+
"HU_hi (HU)": -852.884
|
|
11
|
+
},
|
|
12
|
+
"Lung_2": {
|
|
13
|
+
"density (mg/cm3)": 202.695,
|
|
14
|
+
"HU_lo (HU)": -852.884,
|
|
15
|
+
"HU_hi (HU)": -755.769
|
|
16
|
+
},
|
|
17
|
+
"Lung_3": {
|
|
18
|
+
"density (mg/cm3)": 302.695,
|
|
19
|
+
"HU_lo (HU)": -755.769,
|
|
20
|
+
"HU_hi (HU)": -658.653
|
|
21
|
+
},
|
|
22
|
+
"Lung_4": {
|
|
23
|
+
"density (mg/cm3)": 402.695,
|
|
24
|
+
"HU_lo (HU)": -658.653,
|
|
25
|
+
"HU_hi (HU)": -561.538
|
|
26
|
+
},
|
|
27
|
+
"Lung_5": {
|
|
28
|
+
"density (mg/cm3)": 502.695,
|
|
29
|
+
"HU_lo (HU)": -561.538,
|
|
30
|
+
"HU_hi (HU)": -464.422
|
|
31
|
+
},
|
|
32
|
+
"Lung_6": {
|
|
33
|
+
"density (mg/cm3)": 602.695,
|
|
34
|
+
"HU_lo (HU)": -464.422,
|
|
35
|
+
"HU_hi (HU)": -367.306
|
|
36
|
+
},
|
|
37
|
+
"Lung_7": {
|
|
38
|
+
"density (mg/cm3)": 702.695,
|
|
39
|
+
"HU_lo (HU)": -367.306,
|
|
40
|
+
"HU_hi (HU)": -270.191
|
|
41
|
+
},
|
|
42
|
+
"Lung_8": {
|
|
43
|
+
"density (mg/cm3)": 802.695,
|
|
44
|
+
"HU_lo (HU)": -270.191,
|
|
45
|
+
"HU_hi (HU)": -173.075
|
|
46
|
+
},
|
|
47
|
+
"Lung_9": {
|
|
48
|
+
"density (mg/cm3)": 880.021,
|
|
49
|
+
"HU_lo (HU)": -173.075,
|
|
50
|
+
"HU_hi (HU)": -120.0
|
|
51
|
+
},
|
|
52
|
+
"AT_AG_SI1_10": {
|
|
53
|
+
"density (mg/cm3)": 926.911,
|
|
54
|
+
"HU_lo (HU)": -120.0,
|
|
55
|
+
"HU_hi (HU)": -82.0
|
|
56
|
+
},
|
|
57
|
+
"AT_AG_SI2_11": {
|
|
58
|
+
"density (mg/cm3)": 957.382,
|
|
59
|
+
"HU_lo (HU)": -82.0,
|
|
60
|
+
"HU_hi (HU)": -52.0
|
|
61
|
+
},
|
|
62
|
+
"AT_AG_SI3_12": {
|
|
63
|
+
"density (mg/cm3)": 984.277,
|
|
64
|
+
"HU_lo (HU)": -52.0,
|
|
65
|
+
"HU_hi (HU)": -22.0
|
|
66
|
+
},
|
|
67
|
+
"AT_AG_SI4_13": {
|
|
68
|
+
"density (mg/cm3)": 1011.1699999999998,
|
|
69
|
+
"HU_lo (HU)": -22.0,
|
|
70
|
+
"HU_hi (HU)": 8.0
|
|
71
|
+
},
|
|
72
|
+
"AT_AG_SI5_14": {
|
|
73
|
+
"density (mg/cm3)": 1029.55,
|
|
74
|
+
"HU_lo (HU)": 8.0,
|
|
75
|
+
"HU_hi (HU)": 19.0
|
|
76
|
+
},
|
|
77
|
+
"SoftTissus_15": {
|
|
78
|
+
"density (mg/cm3)": 1061.6000000000001,
|
|
79
|
+
"HU_lo (HU)": 19.0,
|
|
80
|
+
"HU_hi (HU)": 80.0
|
|
81
|
+
},
|
|
82
|
+
"ConnectiveTissue_16": {
|
|
83
|
+
"density (mg/cm3)": 1119.8999999999999,
|
|
84
|
+
"HU_lo (HU)": 80.0,
|
|
85
|
+
"HU_hi (HU)": 120.0
|
|
86
|
+
},
|
|
87
|
+
"Marrow_Bone01_17": {
|
|
88
|
+
"density (mg/cm3)": 1111.15,
|
|
89
|
+
"HU_lo (HU)": 120.0,
|
|
90
|
+
"HU_hi (HU)": 200.0
|
|
91
|
+
},
|
|
92
|
+
"Marrow_Bone02_18": {
|
|
93
|
+
"density (mg/cm3)": 1164.4699999999998,
|
|
94
|
+
"HU_lo (HU)": 200.0,
|
|
95
|
+
"HU_hi (HU)": 300.0
|
|
96
|
+
},
|
|
97
|
+
"Marrow_Bone03_19": {
|
|
98
|
+
"density (mg/cm3)": 1223.71,
|
|
99
|
+
"HU_lo (HU)": 300.0,
|
|
100
|
+
"HU_hi (HU)": 400.0
|
|
101
|
+
},
|
|
102
|
+
"Marrow_Bone04_20": {
|
|
103
|
+
"density (mg/cm3)": 1282.95,
|
|
104
|
+
"HU_lo (HU)": 400.0,
|
|
105
|
+
"HU_hi (HU)": 500.0
|
|
106
|
+
},
|
|
107
|
+
"Marrow_Bone05_21": {
|
|
108
|
+
"density (mg/cm3)": 1342.19,
|
|
109
|
+
"HU_lo (HU)": 500.0,
|
|
110
|
+
"HU_hi (HU)": 600.0
|
|
111
|
+
},
|
|
112
|
+
"Marrow_Bone06_22": {
|
|
113
|
+
"density (mg/cm3)": 1401.42,
|
|
114
|
+
"HU_lo (HU)": 600.0,
|
|
115
|
+
"HU_hi (HU)": 700.0
|
|
116
|
+
},
|
|
117
|
+
"Marrow_Bone07_23": {
|
|
118
|
+
"density (mg/cm3)": 1460.66,
|
|
119
|
+
"HU_lo (HU)": 700.0,
|
|
120
|
+
"HU_hi (HU)": 800.0
|
|
121
|
+
},
|
|
122
|
+
"Marrow_Bone08_24": {
|
|
123
|
+
"density (mg/cm3)": 1519.9,
|
|
124
|
+
"HU_lo (HU)": 800.0,
|
|
125
|
+
"HU_hi (HU)": 900.0
|
|
126
|
+
},
|
|
127
|
+
"Marrow_Bone09_25": {
|
|
128
|
+
"density (mg/cm3)": 1579.14,
|
|
129
|
+
"HU_lo (HU)": 900.0,
|
|
130
|
+
"HU_hi (HU)": 1000.0
|
|
131
|
+
},
|
|
132
|
+
"Marrow_Bone10_26": {
|
|
133
|
+
"density (mg/cm3)": 1638.3799999999999,
|
|
134
|
+
"HU_lo (HU)": 1000.0,
|
|
135
|
+
"HU_hi (HU)": 1100.0
|
|
136
|
+
},
|
|
137
|
+
"Marrow_Bone11_27": {
|
|
138
|
+
"density (mg/cm3)": 1697.62,
|
|
139
|
+
"HU_lo (HU)": 1100.0,
|
|
140
|
+
"HU_hi (HU)": 1200.0
|
|
141
|
+
},
|
|
142
|
+
"Marrow_Bone12_28": {
|
|
143
|
+
"density (mg/cm3)": 1756.8600000000001,
|
|
144
|
+
"HU_lo (HU)": 1200.0,
|
|
145
|
+
"HU_hi (HU)": 1300.0
|
|
146
|
+
},
|
|
147
|
+
"Marrow_Bone13_29": {
|
|
148
|
+
"density (mg/cm3)": 1816.1000000000001,
|
|
149
|
+
"HU_lo (HU)": 1300.0,
|
|
150
|
+
"HU_hi (HU)": 1400.0
|
|
151
|
+
},
|
|
152
|
+
"Marrow_Bone14_30": {
|
|
153
|
+
"density (mg/cm3)": 1875.34,
|
|
154
|
+
"HU_lo (HU)": 1400.0,
|
|
155
|
+
"HU_hi (HU)": 1500.0
|
|
156
|
+
},
|
|
157
|
+
"Marrow_Bone15_31": {
|
|
158
|
+
"density (mg/cm3)": 1946.43,
|
|
159
|
+
"HU_lo (HU)": 1500.0,
|
|
160
|
+
"HU_hi (HU)": 1640.0
|
|
161
|
+
},
|
|
162
|
+
"AmalgamTooth_32": {
|
|
163
|
+
"density (mg/cm3)": 2038.08,
|
|
164
|
+
"HU_lo (HU)": 1640.0,
|
|
165
|
+
"HU_hi (HU)": 1807.5
|
|
166
|
+
},
|
|
167
|
+
"AmalgamTooth_33": {
|
|
168
|
+
"density (mg/cm3)": 2138.08,
|
|
169
|
+
"HU_lo (HU)": 1807.5,
|
|
170
|
+
"HU_hi (HU)": 1975.01
|
|
171
|
+
},
|
|
172
|
+
"AmalgamTooth_34": {
|
|
173
|
+
"density (mg/cm3)": 2238.08,
|
|
174
|
+
"HU_lo (HU)": 1975.01,
|
|
175
|
+
"HU_hi (HU)": 2142.51
|
|
176
|
+
},
|
|
177
|
+
"AmalgamTooth_35": {
|
|
178
|
+
"density (mg/cm3)": 2335.09,
|
|
179
|
+
"HU_lo (HU)": 2142.51,
|
|
180
|
+
"HU_hi (HU)": 2300.0
|
|
181
|
+
},
|
|
182
|
+
"MetallImplants_36": {
|
|
183
|
+
"density (mg/cm3)": 2432.1000000000004,
|
|
184
|
+
"HU_lo (HU)": 2300.0,
|
|
185
|
+
"HU_hi (HU)": 2467.5
|
|
186
|
+
},
|
|
187
|
+
"MetallImplants_37": {
|
|
188
|
+
"density (mg/cm3)": 2532.1,
|
|
189
|
+
"HU_lo (HU)": 2467.5,
|
|
190
|
+
"HU_hi (HU)": 2635.01
|
|
191
|
+
},
|
|
192
|
+
"MetallImplants_38": {
|
|
193
|
+
"density (mg/cm3)": 2632.1,
|
|
194
|
+
"HU_lo (HU)": 2635.01,
|
|
195
|
+
"HU_hi (HU)": 2802.51
|
|
196
|
+
},
|
|
197
|
+
"MetallImplants_39": {
|
|
198
|
+
"density (mg/cm3)": 2732.1,
|
|
199
|
+
"HU_lo (HU)": 2802.51,
|
|
200
|
+
"HU_hi (HU)": 2970.02
|
|
201
|
+
},
|
|
202
|
+
"MetallImplants_40": {
|
|
203
|
+
"density (mg/cm3)": 2791.0499999999997,
|
|
204
|
+
"HU_lo (HU)": 2970.02,
|
|
205
|
+
"HU_hi (HU)": 3000.0
|
|
206
|
+
},
|
|
207
|
+
"MetallImplants_41": {
|
|
208
|
+
"density (mg/cm3)": 2900.0,
|
|
209
|
+
"HU_lo (HU)": 3000.0,
|
|
210
|
+
"HU_hi (HU)": 4000.0
|
|
211
|
+
},
|
|
212
|
+
"MetallImplants_42": {
|
|
213
|
+
"density (mg/cm3)": 2900.0,
|
|
214
|
+
"HU_lo (HU)": 4000.0,
|
|
215
|
+
"HU_hi (HU)": 4000.0
|
|
216
|
+
},
|
|
217
|
+
"MetallImplants_43": {
|
|
218
|
+
"density (mg/cm3)": 2900.0,
|
|
219
|
+
"HU_lo (HU)": 4000.0,
|
|
220
|
+
"HU_hi (HU)": 4001.0
|
|
221
|
+
}
|
|
222
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Access to the built-in attenuation tables bundled with the package.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from importlib import resources as _res
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
import numpy as _np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = ["data_path", "load_table"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def data_path(filename: str) -> Path:
|
|
15
|
+
"""Return a path to a packaged data file."""
|
|
16
|
+
return _res.files(__package__).joinpath(filename)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def load_table(filename: str) -> _np.ndarray:
|
|
20
|
+
"""
|
|
21
|
+
Load an attenuation table (energy [keV], μ [cm⁻¹]).
|
|
22
|
+
Example filenames: 'bone.atn', 'h2o.atn'.
|
|
23
|
+
"""
|
|
24
|
+
with _res.as_file(data_path(filename)) as fp:
|
|
25
|
+
return _np.loadtxt(fp, dtype=float)
|