You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This page lists what the connector checks against SIMIND v8 and v9, the defects
and biases found in the engines, and the numbers recorded by the validation
suite for each engine (see the validation results at the end).
Input and output checks: engine output prefixes are capped at 47 characters; up to 99 numbered windows and exact tot/sca/pri/PENETRATE component names are loaded from matching headers; byte offsets and full-pixel payload sizes are checked; measured geometry and source/mu-map grids are checked; DICOM diagnostics, container library isolation, and validation scripts have focused coverage.
Window files: generated .win prefixes are capped at 15 characters, and a window file cannot silently follow a changed output prefix. This cap does not apply to runtime FW: a v8 raw-engine check read two requested windows from a 16-character FW filename. The connector preserves the caller's /CA selection and derives the missing order-0 scatter or primary image only when compatible output headers exist. Executor timeouts are exposed through core, native, and PyTomography runs. Window selection is resolved once per run(), before file staging, configuration saving, cleanup or execution, and the one selected path is shared by the executor, cleanup protection and _derive_primary_headers:
Execution-time precedence: explicit FW → output_dir/{FW}.win (an explicit selection overrides connector-written windows); else a current connector-written {prefix}.win from set_energy_windows(...) (matching this run's output prefix and directory) → that file is used and FW=prefix is injected into the run-local switch holder only, so persistent and caller-supplied switch objects are never mutated; else a SCATTWIN run with an existing scattwin.win → an actionable ValueError; else a clean SCATTWIN default execution with no connector-derived window component. PENETRATE and DUMMY runs never infer a window file from an existing scattwin.win; such a file is left untouched and unselected.
Selected-file validation and failure ordering: a selected window file, explicit or generated, must be a regular file before execution — a missing or non-regular selection raises FileNotFoundError rather than falling back to another file. Window resolution runs after output-prefix, scoring-routine and runtime-operator (switch and orbit) validation and before orbit staging, configuration saving, stale-output cleanup or execution, so an invalid setup fails before core-run file staging, configuration saving, cleanup or execution; an adaptor run may already have written its phantom inputs while configuring the phantom before run() is called.
Migration (breaking): a SCATTWIN run that relied on an implicit existing scattwin.win now raises. Call set_energy_windows(...) to select the connector-written window, or select the default-named file explicitly with FW="scattwin" (connector.add_runtime_switch("FW", "scattwin") or connector.run(RuntimeOperator(switches={"FW": "scattwin"}))). Default-mode engine runs can leave scattwin.win behind, so a later run with neither selection needs the same migration.
Adaptor preflight:_validate_runtime_operator, the preflight the native and PyTomography adaptors run before writing inputs, validates switches, generated-window prefix/path consistency and the orbit, but does not run the stale-default guard or the selected-file existence check; both run only inside core run(), before core-run staging, configuration saving, stale-output cleanup or execution. A preflight-passing SCATTWIN run can therefore still raise the ValueError (or FileNotFoundError) inside run(), although an adaptor run may already have written its phantom inputs while configuring the phantom before run() was called.
CA/collimator validation: unchanged and routine-independent.CA is validated as 0, 1 or 2 whatever the scoring routine, and CA remains caller-controlled: the connector validates it when supplied and injects no /CA. Validation deliberately stays before the pre-write side effects: moving it later would weaken that protection. No routine-aware validation change was made.
Analytic .inp: deliberately retained. The connector-written {prefix}.inp (only the analytic MultipleInserts phantom writes it) stays on disk after phantom reset clears IF. .inp is not an output suffix, nothing downstream reads it, and retention supports inspecting or re-running a configured analytic phantom, so it is not part of cleanup.
Smoke evidence (selection only, not quantitative validation): two SCATTWIN window smokes passed on the native macOS ARM64 SIMIND v9.0 build (2026-09-27), each a 16³ grid, four views and a mostly-zero point source with HISTORIES=0.02 and a 300 s timeout. /CA:0/FW:genwin returned finite, nonzero tot_w1 and tot_w2 from connector-written windows while a conflicting stale scattwin.win stayed byte-identical; /CA:0/FW:scattwin selected that file's single window (tot_w1 only), also byte-identical. These smoke runs check window selection, not accuracy.
Interfile parsing: rejects non-finite or fractional byte offsets and payloads ending in a partial pixel, and complete extra or short pixels.
v9 PENETRATE headers: SIMIND v9 PENETRATE .hNN headers are converted alongside their matching .bNN payloads. SIMIND v8's shared .h00 template remains supported. Unknown component 20 is exposed as b20 without assigning it an inferred physical label.
Version selection and centring:simind_version=8|9 is available on core/native/PyTomography constructors; SIMIND_VERSION supplies the default when omitted, and v8 is the fallback. SIMIND v9's manual records its image-map origin correction. Matching v8/v9 runs used byte-identical source and density arrays, while the .res center changed from I/J=9 to 9.5 for a 16³ map. Analytic point-source probes established the corresponding transverse source shift and density-map origin offsets. The connector applies the measured shifts only to a copied v9 voxel-run .smc: source Indexes 17/18 use effective PX, density Indexes 36/37 use Index 31. It rejects non-default DI, density Index 32, or OR orientations because those correction mappings have not been validated.
Measured-geometry smoke: the four 16³/eight-view geometry cases (clockwise, counter-clockwise, non-circular, anisotropic) are recorded in the measured_geometry table below. Profile: smoke (16³ voxels, 8 views, 10 photon histories per voxel, quantization_scale 0.02×default). Each case must reach a correlation above 0.5 with its reference and a centre gap of at most 1 pixel; the full-profile gate (64³, 60 views, 500 histories per voxel, correlation above 0.9) has not been run.
MPI normalization: serial, two-rank replicate and two-rank split totals are compared per build in the mpi table below. These are per-build normalization checks, not a cross-version accuracy comparison.
Smoke evidence: which raw files each SCATTWIN /CA value writes is recorded in the runtime_switches table below (ca_raw_files_*). The v8 manual lists air, tot and pri for /CA:2, but the engine also writes sca. The native macOS ARM64 v9.0 build completes /CA:2 and returns ai2/air/pri/sca/tot.
SIMIND v9.0 Linux SCATTWIN defect: the v9.0 Linux Intel build's SCATTWIN routine either aborts with heap corruption or exits normally with silently corrupted projections (scrambled per-view bin and axial centroids, correlation 0.07-0.42 with the v8 reference). Which happens depends on the number of views, the matrix size, the source content and the working-directory length, so there is no safe setting. v8 Linux, the macOS ARM v9 build and v9 Linux PENETRATE are correct. For SCATTWIN use SIMIND v8 or the macOS v9 build. The validation suite records v9-linux SCATTWIN cases that check projection data as not run.
Reviewed math: the attenuation tables use MeV and cm²/g. The loader divides input keV by 1000 for interpolation, then multiplies the mass attenuation coefficient by density to obtain cm⁻¹. At 140 keV the checked values are water 0.15454 cm⁻¹ and bone 0.287342 cm⁻¹ at densities 1.0 and 1.85 g/cm³. The bilinear hu_to_attenuation maps HU values −1000/0/+1000 to air/water/bone; mu_map_type="hu" in configure_voxel_phantom uses the Schneider2000 density conversion instead (about 1.02 g/cm³ at 0 HU and 1.61 g/cm³ at 1000 HU). Index 25 carries MBq×seconds per projection; source-map values control histories and remain separate from absolute activity. Activity and acquisition-time doubling are checked in the activity table below. An exploratory v8 water/air PENETRATE ratio was 0.6081 versus center-path Beer–Lambert 0.6188 for a 16³, 4 mm point source. Monte Carlo uncertainty was not quantified for that check; it does not establish quantitative calibration.
Reviewed geometry/reconstruction: source and attenuation images must share shape, finite positive voxel sizes, origin, and a native direction matrix where the backend exposes one. Measured-geometry SIRF/reconstruction regressions pass. In the SIRF 3.10.1/STIR 6.4 cache regression, disabling all-views caching reduced first-update residual discrepancy from 0.0222821 to 1.55e-6.
Complete workflow smokes: the same 96³ NEMA phantom at 4 mm, 568.1 MBq, four views, NN=1, and quantization_scale=0.01×DEFAULT completed with both STIR and SIRF on v8 and v9 Linux. Both backends produced finite 96³ reconstructions; projection totals were approximately 66,190 (v8) and 67,500 (v9). A reduced v8 SIRF scatter-correction run completed one refresh with scale 2019 and scatter sum 2.321×10⁵; its contrast error was 7.8% both with and without correction. These smoke runs do not establish quantitative accuracy or an improvement in reconstruction quality.
Other backend evidence: SIRF 3.10.1 uses STIR 6.4.0. PyTomography 3.4.0 with torch 2.14.1+cpu completed reduced Example 07C on both Linux engines: four views, 32³ images, NN=1, and quantization_scale=0.01×DEFAULT, producing finite nonzero reconstructions. The selected backend tests passed (SIRF geometry/reconstruction 66; STIR 3; PyTomography 31). Both supported real-engine regression runs passed 13 tests, on v8 Linux and v9 macOS ARM respectively.
Core and packaging evidence: 1,148 local core tests passed; the rebuilt core container passed 1,146 tests plus three import-isolation checks. Ruff, strict offline Sphinx, wheel/sdist resource checks, and uv lock --check --offline passed. Build tools are declared in the dev extra, and Compose forwards explicit version selection to all services.
Remaining quantitative gates: calibrated absolute detector sensitivity, sufficient-history attenuation and reconstruction comparisons with uncertainty estimates, and measured-data calibration remain outside the evidence above. Full release validation must establish those references and tolerances before making quantitative claims.
Findings
SIMIND v9 Linux SCATTWIN: see the defect description above. v8 and the macOS v9 build are correct on identical inputs; the v9_linux_scattwin_defect case in runtime_switches records the failure.
SIMIND v9 simind_mpi with three processes: a run was observed to segfault; the suite does not run it on v9. simind_mpi seeds rank r with RR=r+1, and a replicate run with a fixed RR is refused because every process would then simulate the same histories.
STIR SPECTUB mirrors x relative to the mu map (UCL/STIR#452). With the flip that the examples apply, SIRF and PyTomography agree: the recovered hot-to-background ratios differ by 0.1 % on v8 (2.194 against 2.197) and 0.2 % on v9 (2.109 against 2.113). Without the flip the ratio between the two was 1.08.
STIR SPECTUB set_up: calling it twice on the same acquisition model gives zero back-projections. This is known STIR behaviour; set up the model once.
Per-unit-activity scale: the scale that matches SPECTUB to SIMIND differs between source distributions by 7 % on v8 and 10 % on v9 (scale_per_unit_activity_image_independent). This is a known mismatch between the SIMIND and SPECTUB models; scale to a reference that has the same source distribution as the object being reconstructed. The v9 value is outside the 10 % limit and is an expected failure.
Analytic against voxel inserts: the analytic Jaszczak-like phantom gives 1-2 % fewer counts than its voxelised equivalent in the background and cold modes (total ratio 0.981-0.990 on v8 and macOS v9; hot mode agrees within 1 %). The cause is not established.
trimmed scaling is biased low at low histories: the scale is 0.548 (v8) and 0.512 (v9) of the true value on the recon OSEM iterate at a tenth of the default quantization, and 0.959 (v8) and 0.965 (v9) at 64 times those histories (normalisers_agree_at_high_histories). The examples use sum with smoothing. trimmed_scale_bias_low_histories is an expected failure on both engines.