simind-python-connector 2.0.1__py3-none-any.whl → 2.0.2__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 +1 -1
- simind_python_connector/connectors/python_connector.py +2 -2
- simind_python_connector/recon/corrections.py +56 -14
- simind_python_connector/recon/osem.py +5 -1
- simind_python_connector/utils/projection_metrics.py +153 -0
- {simind_python_connector-2.0.1.dist-info → simind_python_connector-2.0.2.dist-info}/METADATA +32 -51
- {simind_python_connector-2.0.1.dist-info → simind_python_connector-2.0.2.dist-info}/RECORD +10 -9
- {simind_python_connector-2.0.1.dist-info → simind_python_connector-2.0.2.dist-info}/WHEEL +0 -0
- {simind_python_connector-2.0.1.dist-info → simind_python_connector-2.0.2.dist-info}/licenses/LICENSE +0 -0
- {simind_python_connector-2.0.1.dist-info → simind_python_connector-2.0.2.dist-info}/top_level.txt +0 -0
|
@@ -853,8 +853,8 @@ class SimindPythonConnector(BaseConnector):
|
|
|
853
853
|
):
|
|
854
854
|
warnings.warn(
|
|
855
855
|
"SIMIND v9.0 on Linux can abort in SCATTWIN or silently return "
|
|
856
|
-
"corrupted projections. Use SIMIND v8
|
|
857
|
-
"
|
|
856
|
+
"corrupted projections. Use SIMIND v8; v9 support is "
|
|
857
|
+
"experimental and unvalidated in this release (planned for 2.1.0).",
|
|
858
858
|
RuntimeWarning,
|
|
859
859
|
stacklevel=2,
|
|
860
860
|
)
|
|
@@ -23,9 +23,13 @@ class ScatterCorrection:
|
|
|
23
23
|
output, which holds phantom scatter only.
|
|
24
24
|
|
|
25
25
|
With measured, this is Dewaraja et al. 2017's pixel-wise model: the
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
measured data times the scatter-to-total ratio of SIMIND's run. It does not
|
|
27
|
+
depend on the projector's scalar scale. With smoothing, SIMIND's scatter
|
|
28
|
+
and total are smoothed with the same Gaussian before the ratio, and the
|
|
29
|
+
scaled result is smoothed again. smoothing_fwhm_bins=3.0 and
|
|
30
|
+
smooth_across_views=True match the paper. The final smoothing is retained
|
|
31
|
+
because the measured noise enters the estimate. The ratio is zero only
|
|
32
|
+
where the smoothed total is not positive; signed scatter is not clipped.
|
|
29
33
|
smooth_across_views wraps around the view axis, so it assumes the views
|
|
30
34
|
cover 360 degrees.
|
|
31
35
|
"""
|
|
@@ -79,6 +83,26 @@ class ScatterCorrection:
|
|
|
79
83
|
f"scatter shape {scatter_array.shape}"
|
|
80
84
|
)
|
|
81
85
|
total_array = total.as_array()
|
|
86
|
+
if total_array.shape != scatter_array.shape:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
f"total shape {total_array.shape} does not match "
|
|
89
|
+
f"scatter shape {scatter_array.shape}"
|
|
90
|
+
)
|
|
91
|
+
# Smooth the MC scatter and its total before forming the fraction. In
|
|
92
|
+
# the fixed-primary, noisy-scatter control used to check this order,
|
|
93
|
+
# pre-smoothing reduced the ratio noise in the summed estimate. The
|
|
94
|
+
# direction and size of a noisy ratio's error depend on the covariance
|
|
95
|
+
# between numerator and denominator, so this is an observation about
|
|
96
|
+
# that control, not a general theorem about Monte Carlo ratios, and it
|
|
97
|
+
# does not account for the unresolved pixel-wise drift. The final
|
|
98
|
+
# smoothing in estimate() is retained.
|
|
99
|
+
if self.smoothing_fwhm_bins is not None:
|
|
100
|
+
scatter_array = _smooth_array(
|
|
101
|
+
scatter_array, self.smoothing_fwhm_bins, self.smooth_across_views
|
|
102
|
+
)
|
|
103
|
+
total_array = _smooth_array(
|
|
104
|
+
total_array, self.smoothing_fwhm_bins, self.smooth_across_views
|
|
105
|
+
)
|
|
82
106
|
fraction = np.divide(
|
|
83
107
|
scatter_array,
|
|
84
108
|
total_array,
|
|
@@ -90,27 +114,45 @@ class ScatterCorrection:
|
|
|
90
114
|
return out
|
|
91
115
|
|
|
92
116
|
|
|
93
|
-
def
|
|
94
|
-
|
|
117
|
+
def _smooth_array(
|
|
118
|
+
array: Any, fwhm_bins: float, across_views: bool = False
|
|
119
|
+
) -> np.ndarray:
|
|
120
|
+
"""Gaussian-smooth a projection array, keeping its total.
|
|
95
121
|
|
|
96
|
-
|
|
122
|
+
The total is restored by rescaling only when the input sum stands clear
|
|
123
|
+
of the rounding floor of that sum, so a zero-sum signed field keeps its
|
|
124
|
+
smoothed values instead of being multiplied by an arbitrary factor.
|
|
97
125
|
"""
|
|
98
126
|
from scipy.ndimage import gaussian_filter
|
|
99
127
|
|
|
100
|
-
|
|
101
|
-
sigma = [0.0] *
|
|
102
|
-
mode = ["reflect"] *
|
|
128
|
+
values = np.asarray(array, dtype=np.float64)
|
|
129
|
+
sigma = [0.0] * values.ndim
|
|
130
|
+
mode = ["reflect"] * values.ndim
|
|
103
131
|
# Projection arrays end in (axial, view, bin); SIRF may add a TOF axis first.
|
|
104
132
|
sigma[-3] = sigma[-1] = fwhm_bins / (2.0 * np.sqrt(2.0 * np.log(2.0)))
|
|
105
133
|
if across_views:
|
|
106
134
|
sigma[-2] = sigma[-1]
|
|
107
135
|
mode[-2] = "wrap"
|
|
108
|
-
smoothed = gaussian_filter(
|
|
109
|
-
total
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
smoothed = gaussian_filter(values, sigma, mode=mode)
|
|
137
|
+
# Keep the original total, but only when it stands clear of the rounding
|
|
138
|
+
# floor of the sum. A cancelling signed field sums to about zero, and so
|
|
139
|
+
# does its smoothed version: their ratio is then an arbitrary factor (and
|
|
140
|
+
# zero erases the field), so such a field is left exactly as smoothed.
|
|
141
|
+
original_sum = values.sum()
|
|
142
|
+
if abs(original_sum) > 1e-9 * np.abs(values).sum():
|
|
143
|
+
total = smoothed.sum()
|
|
144
|
+
if total != 0.0:
|
|
145
|
+
smoothed *= original_sum / total
|
|
146
|
+
return smoothed.astype(np.float32)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _smooth_views(data: Any, fwhm_bins: float, across_views: bool = False) -> Any:
|
|
150
|
+
"""Gaussian-smooth each view along its axial and bin axes, keeping the total.
|
|
151
|
+
|
|
152
|
+
With across_views, also smooth along the view axis, wrapping round.
|
|
153
|
+
"""
|
|
112
154
|
out = data.clone()
|
|
113
|
-
out.fill(
|
|
155
|
+
out.fill(_smooth_array(data.as_array(), fwhm_bins, across_views))
|
|
114
156
|
return out
|
|
115
157
|
|
|
116
158
|
|
|
@@ -28,7 +28,8 @@ def run_osem_with_corrections(
|
|
|
28
28
|
Each pass builds a fresh model, objective and reconstructor, continuing
|
|
29
29
|
from the preceding pass's image. initial_image is cloned and not modified.
|
|
30
30
|
updater.update_now is called between passes at completed subiterations;
|
|
31
|
-
updater.schedule is not used.
|
|
31
|
+
updater.schedule is not used. Intermediate subiteration images are not
|
|
32
|
+
written to disk; only the returned image is produced.
|
|
32
33
|
"""
|
|
33
34
|
# Each pass rebuilds a reconstructor that restarts at subset 0, so a
|
|
34
35
|
# remainder would use some subsets more often and others never.
|
|
@@ -51,6 +52,9 @@ def run_osem_with_corrections(
|
|
|
51
52
|
reconstructor.set_objective_function(objective)
|
|
52
53
|
reconstructor.set_num_subsets(num_subsets)
|
|
53
54
|
reconstructor.set_num_subiterations(subiterations_per_update)
|
|
55
|
+
# STIR otherwise writes one Interfile image per subiteration into the
|
|
56
|
+
# process working directory. Callers only need the returned image.
|
|
57
|
+
reconstructor.disable_output()
|
|
54
58
|
reconstructor.set_input(acquisition_data)
|
|
55
59
|
reconstructor.set_up(image)
|
|
56
60
|
reconstructor.reconstruct(image)
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Small projection-metric helpers used by the worked examples.
|
|
2
|
+
|
|
3
|
+
They are deliberately plain NumPy: the examples need a shared count scale, a
|
|
4
|
+
repeatability measure, a triple-energy-window estimate and a quadrature check,
|
|
5
|
+
not a reconstruction framework.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import numpy as np
|
|
11
|
+
|
|
12
|
+
from simind_python_connector.core.types import MAX_SOURCE
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def projection_view0(projection: np.ndarray) -> np.ndarray:
|
|
16
|
+
"""Return the first view of a 3-D or 4-D projection array."""
|
|
17
|
+
if projection.ndim == 4:
|
|
18
|
+
return projection[0, :, 0, :]
|
|
19
|
+
if projection.ndim == 3:
|
|
20
|
+
return projection[0, :, :]
|
|
21
|
+
raise ValueError(
|
|
22
|
+
f"Expected projection with 3 or 4 dimensions, got shape {projection.shape}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def require_output_keys(outputs: dict[str, object], *keys: str) -> dict[str, object]:
|
|
27
|
+
"""Return only the named outputs, or say which ones the run did not write."""
|
|
28
|
+
missing = [key for key in keys if key not in outputs]
|
|
29
|
+
if missing:
|
|
30
|
+
raise KeyError(
|
|
31
|
+
f"SIMIND did not write {', '.join(missing)}; it wrote "
|
|
32
|
+
f"{', '.join(sorted(outputs))}"
|
|
33
|
+
)
|
|
34
|
+
return {key: outputs[key] for key in keys}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def source_weights(source: np.ndarray, quantization_scale: float) -> np.ndarray:
|
|
38
|
+
"""Return the integer photon-history weights the connector writes."""
|
|
39
|
+
source = np.asarray(source, dtype=float)
|
|
40
|
+
if source.size == 0:
|
|
41
|
+
raise ValueError("source must not be empty")
|
|
42
|
+
if not 0.0 < quantization_scale <= 1.0:
|
|
43
|
+
raise ValueError("quantization_scale must be in (0, 1]")
|
|
44
|
+
maximum = float(source.max())
|
|
45
|
+
if maximum == 0.0:
|
|
46
|
+
return np.zeros(source.shape, dtype=np.int64)
|
|
47
|
+
return np.clip(
|
|
48
|
+
np.round(source / maximum * (MAX_SOURCE * quantization_scale)),
|
|
49
|
+
0,
|
|
50
|
+
MAX_SOURCE,
|
|
51
|
+
).astype(np.int64)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def source_weight_sum(source: np.ndarray, quantization_scale: float) -> int:
|
|
55
|
+
"""Number of photon histories per projection before the ``NN`` multiplier."""
|
|
56
|
+
return int(source_weights(source, quantization_scale).sum())
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def zero_history_fraction(source: np.ndarray, quantization_scale: float) -> float:
|
|
60
|
+
"""Fraction of the source weight that integer rounding sends to zero."""
|
|
61
|
+
source = np.asarray(source, dtype=float)
|
|
62
|
+
total = float(source.sum())
|
|
63
|
+
if total == 0.0:
|
|
64
|
+
return 0.0
|
|
65
|
+
weights = source_weights(source, quantization_scale)
|
|
66
|
+
return float(source[weights == 0].sum() / total)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def symmetric_limits(*images: np.ndarray) -> tuple[float, float]:
|
|
70
|
+
"""A zero-centred colour scale that fits every image."""
|
|
71
|
+
if not images:
|
|
72
|
+
raise ValueError("symmetric_limits needs at least one image")
|
|
73
|
+
limit = max(float(np.max(np.abs(image))) for image in images)
|
|
74
|
+
if limit == 0.0:
|
|
75
|
+
return 0.0, 0.0
|
|
76
|
+
return -limit, limit
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def normalised_rms_difference(a: np.ndarray, b: np.ndarray) -> float:
|
|
80
|
+
"""RMS difference between two images, relative to their mean magnitude."""
|
|
81
|
+
a = np.asarray(a, dtype=float)
|
|
82
|
+
b = np.asarray(b, dtype=float)
|
|
83
|
+
if a.shape != b.shape:
|
|
84
|
+
raise ValueError(f"shape mismatch: {a.shape} and {b.shape}")
|
|
85
|
+
scale = float(np.mean(np.abs(a) + np.abs(b)) / 2.0)
|
|
86
|
+
if scale == 0.0:
|
|
87
|
+
return 0.0
|
|
88
|
+
return float(np.sqrt(np.mean((a - b) ** 2)) / scale)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def tew_scatter_estimate(
|
|
92
|
+
lower: np.ndarray,
|
|
93
|
+
upper: np.ndarray,
|
|
94
|
+
*,
|
|
95
|
+
lower_width_kev: float,
|
|
96
|
+
photopeak_width_kev: float,
|
|
97
|
+
upper_width_kev: float,
|
|
98
|
+
) -> np.ndarray:
|
|
99
|
+
"""Triple-energy-window scatter estimate for one photopeak.
|
|
100
|
+
|
|
101
|
+
Each side window is scaled to the photopeak width and half of the total is
|
|
102
|
+
assigned to the peak, which assumes the scatter spectrum is linear across
|
|
103
|
+
the three windows. That is an approximation, not a measurement.
|
|
104
|
+
"""
|
|
105
|
+
widths = (lower_width_kev, photopeak_width_kev, upper_width_kev)
|
|
106
|
+
if any(width <= 0 for width in widths):
|
|
107
|
+
raise ValueError(f"window widths must be positive, got {widths}")
|
|
108
|
+
return np.asarray(lower) * (
|
|
109
|
+
photopeak_width_kev / (2.0 * lower_width_kev)
|
|
110
|
+
) + np.asarray(upper) * (photopeak_width_kev / (2.0 * upper_width_kev))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def tew_correction(
|
|
114
|
+
photopeak: np.ndarray,
|
|
115
|
+
lower: np.ndarray,
|
|
116
|
+
upper: np.ndarray,
|
|
117
|
+
*,
|
|
118
|
+
lower_width_kev: float,
|
|
119
|
+
photopeak_width_kev: float,
|
|
120
|
+
upper_width_kev: float,
|
|
121
|
+
) -> tuple[np.ndarray, np.ndarray, float, float]:
|
|
122
|
+
"""Return (scatter estimate, corrected, clipped count fraction, clipped pixels).
|
|
123
|
+
|
|
124
|
+
The corrected image is the photopeak minus the estimate, clipped at zero.
|
|
125
|
+
The count fraction is the part of the photopeak that clipping removed;
|
|
126
|
+
the pixel fraction is the part of the image that the estimate exceeded.
|
|
127
|
+
"""
|
|
128
|
+
photopeak = np.asarray(photopeak)
|
|
129
|
+
estimate = tew_scatter_estimate(
|
|
130
|
+
lower,
|
|
131
|
+
upper,
|
|
132
|
+
lower_width_kev=lower_width_kev,
|
|
133
|
+
photopeak_width_kev=photopeak_width_kev,
|
|
134
|
+
upper_width_kev=upper_width_kev,
|
|
135
|
+
)
|
|
136
|
+
unclipped = photopeak - estimate
|
|
137
|
+
corrected = np.clip(unclipped, a_min=0.0, a_max=None)
|
|
138
|
+
peak_total = float(np.clip(photopeak, 0.0, None).sum())
|
|
139
|
+
clipped_counts = float((corrected - unclipped).sum())
|
|
140
|
+
clipped_count_fraction = clipped_counts / peak_total if peak_total else 0.0
|
|
141
|
+
clipped_pixel_fraction = float(np.mean(unclipped < 0.0))
|
|
142
|
+
return estimate, corrected, clipped_count_fraction, clipped_pixel_fraction
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def count_preservation_error(
|
|
146
|
+
outputs: dict[str, object], total_key: str, *component_keys: str
|
|
147
|
+
) -> float:
|
|
148
|
+
"""Relative difference between a total and the sum of its components."""
|
|
149
|
+
total = float(outputs[total_key].projection.sum())
|
|
150
|
+
components = sum(float(outputs[key].projection.sum()) for key in component_keys)
|
|
151
|
+
if total == 0.0:
|
|
152
|
+
return 0.0 if components == 0.0 else float("inf")
|
|
153
|
+
return (components - total) / total
|
{simind_python_connector-2.0.1.dist-info → simind_python_connector-2.0.2.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simind-python-connector
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
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>
|
|
@@ -33,8 +33,8 @@ Requires-Dist: scipy; extra == "dev"
|
|
|
33
33
|
Requires-Dist: ruff; extra == "dev"
|
|
34
34
|
Requires-Dist: sphinx; extra == "dev"
|
|
35
35
|
Requires-Dist: sphinx-rtd-theme; extra == "dev"
|
|
36
|
+
Requires-Dist: Pillow; extra == "dev"
|
|
36
37
|
Requires-Dist: sphinx-autodoc-typehints; extra == "dev"
|
|
37
|
-
Requires-Dist: sphinx-gallery<0.23,>=0.22; extra == "dev"
|
|
38
38
|
Requires-Dist: pooch>=1.8; extra == "dev"
|
|
39
39
|
Requires-Dist: myst-parser; extra == "dev"
|
|
40
40
|
Requires-Dist: pre-commit; extra == "dev"
|
|
@@ -58,15 +58,12 @@ SIMIND project and Lund University.
|
|
|
58
58
|
|
|
59
59
|
## Start here
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
[documentation](https://simind-python-connector.readthedocs.io/) explains
|
|
68
|
-
installation, geometry, normalisation, backend options, and the
|
|
69
|
-
[observed SIMIND output-component mapping](docs/simind_validation.md).
|
|
61
|
+
The [workflow tutorial](docs/tutorial.rst) walks through a core simulation,
|
|
62
|
+
known-activity NEMA phantom, reconstruction, scatter correction, and
|
|
63
|
+
measured-data calibration; the [example pages](docs/example_pages/index.rst)
|
|
64
|
+
collect the tutorials and figures. The
|
|
65
|
+
[documentation](https://simind-python-connector.readthedocs.io/) covers
|
|
66
|
+
installation, geometry, normalisation and backends.
|
|
70
67
|
|
|
71
68
|
Install the package and example plotting dependencies:
|
|
72
69
|
|
|
@@ -79,15 +76,15 @@ Select a SIMIND executable and its matching data directory. The binary and
|
|
|
79
76
|
`simind` on `PATH`; set `SMC_DIR` when required by your installation.
|
|
80
77
|
|
|
81
78
|
```bash
|
|
82
|
-
export SIMIND_BIN=/path/to/simind-
|
|
83
|
-
export PATH=/path/to/simind-
|
|
84
|
-
export SMC_DIR=/path/to/simind-
|
|
85
|
-
export SIMIND_VERSION=
|
|
79
|
+
export SIMIND_BIN=/path/to/simind-v8/simind
|
|
80
|
+
export PATH=/path/to/simind-v8:$PATH
|
|
81
|
+
export SMC_DIR=/path/to/simind-v8/smc_dir/
|
|
82
|
+
export SIMIND_VERSION=8
|
|
86
83
|
```
|
|
87
84
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
SIMIND v8 is the supported engine. A constructor's `simind_version` overrides
|
|
86
|
+
`SIMIND_VERSION`; v9 selection is experimental. See
|
|
87
|
+
[installation](docs/installation.rst).
|
|
91
88
|
|
|
92
89
|
Use the installed connector directly:
|
|
93
90
|
|
|
@@ -96,7 +93,7 @@ import numpy as np
|
|
|
96
93
|
from simind_python_connector import SimindPythonConnector, configs
|
|
97
94
|
|
|
98
95
|
connector = SimindPythonConnector(
|
|
99
|
-
configs.get("Example.yaml"), "output/basic", "case01", simind_version=
|
|
96
|
+
configs.get("Example.yaml"), "output/basic", "case01", simind_version=8
|
|
100
97
|
)
|
|
101
98
|
source = np.zeros((32, 32, 32), dtype=np.float32)
|
|
102
99
|
source[12:20, 12:20, 12:20] = 1
|
|
@@ -107,42 +104,26 @@ outputs = connector.run()
|
|
|
107
104
|
print(outputs["tot_w1"].projection.shape)
|
|
108
105
|
```
|
|
109
106
|
|
|
110
|
-
Run a complete core simulation example from a source checkout:
|
|
111
|
-
|
|
112
|
-
```bash
|
|
113
|
-
python examples/getting_started/plot_01_basic_simulation.py
|
|
114
|
-
```
|
|
115
|
-
|
|
116
107
|
The core connector does not require STIR, SIRF, or PyTomography. Install only
|
|
117
|
-
the optional backend
|
|
118
|
-
[installation](docs/installation.rst)
|
|
119
|
-
phantomgen package; the install command is in the tutorial.
|
|
108
|
+
the optional backend your workflow uses; the NEMA examples also need
|
|
109
|
+
phantomgen. See [installation](docs/installation.rst) and the tutorial.
|
|
120
110
|
|
|
121
111
|
## Example map
|
|
122
112
|
|
|
123
|
-
- `examples/getting_started/
|
|
124
|
-
|
|
125
|
-
- `examples/backends
|
|
126
|
-
|
|
127
|
-
- `examples/
|
|
128
|
-
|
|
129
|
-
- `examples/
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
The real-data pages are rendered case studies on restricted Y-90 input, not
|
|
138
|
-
reproducible tutorials. See [datasets](docs/datasets.rst) for the availability
|
|
139
|
-
statement.
|
|
140
|
-
|
|
141
|
-
Lower `quantization_scale`, fewer projections, or a mostly-zero source map can
|
|
142
|
-
make smoke runs faster. These settings reduce history statistics; they do not
|
|
143
|
-
set projection counts. For quantitative output, use known activity and
|
|
144
|
-
acquisition time or normalise against a measured or analytic reference. Keep
|
|
145
|
-
`NN >= 1`.
|
|
113
|
+
- `examples/getting_started/` and `examples/phantoms/`: core simulations,
|
|
114
|
+
windows, configurations, scoring, density conversion and phantoms.
|
|
115
|
+
- `examples/backends/`: STIR, SIRF and PyTomography adaptors, plus the DICOM
|
|
116
|
+
projection recipe in `docs/example_pages/dicom_recipe.rst`.
|
|
117
|
+
- `examples/mc_scatter/` and `examples/cil/`: Monte Carlo scatter and
|
|
118
|
+
residual correction, including CIL.
|
|
119
|
+
- `examples/real_data/` and `docs/example_pages/y90_*.rst`: restricted Y-90
|
|
120
|
+
CT conversion and effective attenuation energy, from data that are not
|
|
121
|
+
redistributed; see [datasets](docs/datasets.rst).
|
|
122
|
+
|
|
123
|
+
Lower `quantization_scale`, fewer projections, or a mostly-zero source map make
|
|
124
|
+
smoke runs faster without changing projection counts. For quantitative output,
|
|
125
|
+
use known activity and acquisition time or normalise against a measured or
|
|
126
|
+
analytic reference. Keep `NN >= 1`.
|
|
146
127
|
|
|
147
128
|
## License
|
|
148
129
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
simind_python_connector/__init__.py,sha256=
|
|
1
|
+
simind_python_connector/__init__.py,sha256=ISwXiFuVtdtlhjmW38_xOGAXKqmByMDxAEGdTYKmzFs,1730
|
|
2
2
|
simind_python_connector/datasets.py,sha256=sOocwL2sHvjn03qaYEZXk--r9Nr92G8bWBVWMUIgSxc,2228
|
|
3
3
|
simind_python_connector/normalisation.py,sha256=9cLibos1BYCTUSuanHVUTmzjaqP9N0lL-Of8Y7GEpB0,3527
|
|
4
4
|
simind_python_connector/phantoms.py,sha256=IU12ET2bPzMtLo3LNXVl98HQHkIXgyNFtGnQ8JlXr0E,13513
|
|
@@ -15,7 +15,7 @@ simind_python_connector/configs/input.smc,sha256=oAY2JpwS6IBWZxM-rHdtqWNF4shrZTQ
|
|
|
15
15
|
simind_python_connector/connectors/__init__.py,sha256=_pw86l4iOGONER1JJvKHFafj6bXBzHwSzo_D1GHSLkM,959
|
|
16
16
|
simind_python_connector/connectors/_native_adaptor.py,sha256=TgQCInvYWcSKXaXyIZrAdUlauVi8BZl0ChT5JbzlVWc,19685
|
|
17
17
|
simind_python_connector/connectors/base.py,sha256=XGGF7W7b5mr3yb2efTDOqvXXc6b8Y35KVhAMGHScLBI,1249
|
|
18
|
-
simind_python_connector/connectors/python_connector.py,sha256=
|
|
18
|
+
simind_python_connector/connectors/python_connector.py,sha256=Bi63nkmOphI_yEexmtK_bjKV6n04ZuImbinyhKoNhv8,58817
|
|
19
19
|
simind_python_connector/connectors/pytomography_adaptor.py,sha256=L-v5wk3904svETRbg53oWpC28tyRLruGWODlZR50ytE,13521
|
|
20
20
|
simind_python_connector/connectors/sirf_adaptor.py,sha256=xgAcbLtKTsDBzZPZdmnv_xYEZ4WRtQiH5ua7FBnSR8E,1274
|
|
21
21
|
simind_python_connector/connectors/stir_adaptor.py,sha256=umvI8z8i0ims82-o2rvmFH4SfKGVFxaHn9cHOra5eOw,1439
|
|
@@ -32,16 +32,17 @@ simind_python_connector/data/bone.atn,sha256=ROadKIOQPqJ7HGAxl43UnN_YjZ8keyTIV8H
|
|
|
32
32
|
simind_python_connector/data/h2o.atn,sha256=UplHE9XSSxcsx_cji-5ZtRXcYQYgeyVMzAp_FMOS3cg,1860
|
|
33
33
|
simind_python_connector/recon/__init__.py,sha256=T5TnLOsAbrWv6GNcwrARKJLswAl3KzwVAkkVU-JvF3U,1010
|
|
34
34
|
simind_python_connector/recon/cil.py,sha256=TuemfbLBTUYj1v-odl1RXOCnGCIozyp2lxFnfOsoJ5w,7000
|
|
35
|
-
simind_python_connector/recon/corrections.py,sha256=
|
|
35
|
+
simind_python_connector/recon/corrections.py,sha256=B_QgDPFqWbwqsY77OQwiKN5HHHGflhpf7YczuFT3HAM,7813
|
|
36
36
|
simind_python_connector/recon/diagnostics.py,sha256=BbTavxVEP2U7yA5zCuDonnqA92RarO_4L1gUt4gykN4,1588
|
|
37
|
-
simind_python_connector/recon/osem.py,sha256=
|
|
37
|
+
simind_python_connector/recon/osem.py,sha256=LGCXWW2OtczOAws2U53sd_OIeWzLVa03MQZg5OvQcDo,2676
|
|
38
38
|
simind_python_connector/recon/projector.py,sha256=emCDxcciKL4a96e5Lgd9g3U4g5cvWb3hWygpGEWcHa8,4960
|
|
39
39
|
simind_python_connector/recon/updates.py,sha256=IeseWjD052Jt5G312gyMPnkkG2PQaZMGonA2ymzJ6_M,2914
|
|
40
40
|
simind_python_connector/utils/__init__.py,sha256=lcqGs6Fg36If1zyeUph0vMC_bQCwbFJFRw76RGcAio8,2112
|
|
41
41
|
simind_python_connector/utils/interfile.py,sha256=toasBMC63qRh6HkSgmUQWrCvcYUyZK2tUf4WAqtdIkI,24665
|
|
42
|
+
simind_python_connector/utils/projection_metrics.py,sha256=xzVXJ_KweIGUwsjfeke2S_27W1L3VCt5qVtqB_9ZXZ8,5786
|
|
42
43
|
simind_python_connector/utils/simind_utils.py,sha256=cIfpF5LQdFLZAWsqso0mYLGBPkIUB3H9YQdgt9UDb-o,3815
|
|
43
|
-
simind_python_connector-2.0.
|
|
44
|
-
simind_python_connector-2.0.
|
|
45
|
-
simind_python_connector-2.0.
|
|
46
|
-
simind_python_connector-2.0.
|
|
47
|
-
simind_python_connector-2.0.
|
|
44
|
+
simind_python_connector-2.0.2.dist-info/licenses/LICENSE,sha256=cCWAxo3f4AsMBTc9HmCzz4RiWcEW1m5hsTXSlMzKikE,10874
|
|
45
|
+
simind_python_connector-2.0.2.dist-info/METADATA,sha256=BHvC9nSM-10jtGkQ6W23kNbK9ZQ4tcRjmzBtjzYQcWc,5449
|
|
46
|
+
simind_python_connector-2.0.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
47
|
+
simind_python_connector-2.0.2.dist-info/top_level.txt,sha256=9JxsbTd91-eq8ppiYYspHX1tpNz1Mk81LC9pJ53SkWA,24
|
|
48
|
+
simind_python_connector-2.0.2.dist-info/RECORD,,
|
|
File without changes
|
{simind_python_connector-2.0.1.dist-info → simind_python_connector-2.0.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{simind_python_connector-2.0.1.dist-info → simind_python_connector-2.0.2.dist-info}/top_level.txt
RENAMED
|
File without changes
|