Skip to content

refactor: simplify around connector-first SIMIND adaptors#1

Merged
samdporter merged 14 commits intomainfrom
codex/simplify-repo
Mar 5, 2026
Merged

refactor: simplify around connector-first SIMIND adaptors#1
samdporter merged 14 commits intomainfrom
codex/simplify-repo

Conversation

@samdporter
Copy link
Copy Markdown
Owner

@samdporter samdporter commented Mar 5, 2026

Summary

  • collapse the legacy simulator stack into a connector-first core
  • route STIR and SIRF adaptors through SimindPythonConnector
  • keep reconstruction responsibilities in target libraries
  • remove PyTomography adaptor wrappers around package system-matrix helpers
  • refresh README/docs to user-facing language aligned with current architecture

Validation

  • Docker pytest marker suites for python, stir, sirf, and pytomography
  • container import-isolation checks
  • Docker adaptor examples: 07A/07B/07C
  • SIMIND geometry diagnostics: test_geometry_isolation_forward_projection.py and test_osem_geometry_diagnostics.py
  • style checks in Docker python container: ruff check and ruff format

Summary by Sourcery

Refactor the SIMIND integration around a connector-first core, routing all adaptors through the NumPy-based SimindPythonConnector and retiring the legacy simulator stack.

Enhancements:

  • Reimplement STIR and SIRF adaptors as thin BaseConnector implementations that delegate simulation to SimindPythonConnector and expose native STIR/SIRF output types.
  • Extend SimindPythonConnector with voxel phantom configuration and energy window utilities, centralising SIMIND input geometry, quantisation and window-file handling.
  • Simplify the core layer by introducing a minimal SimindExecutor subprocess runner and removing the legacy simulator/components backend abstraction.
  • Refine the PyTomography adaptor to delegate simulation to SimindPythonConnector while keeping only tensor IO and projection access, avoiding wrapping PyTomography reconstruction helpers.
  • Adjust example scripts to use the connector-first API and updated projection/visualisation semantics across STIR, SIRF, and PyTomography workflows.
  • Tighten geometry handling and voxel-size validation in adaptors and connector utilities, and relax certain geometry diagnostics thresholds to be robust to numerical noise.
  • Update the public core and package exports to reflect the connector-first surface area, dropping SimindSimulator and NativeBackendConnector from the public API.
  • Tune default Example.yaml scanner configuration for a denser projection set in example runs.

Build:

  • Adjust SIRF, STIR, and PyTomography Dockerfiles to copy the workspace with non-root ownership for better container ergonomics.

Documentation:

  • Rewrite README and usage snippets to present SimindPythonConnector as the primary entry point, clarify adaptor-specific dependencies, and align language with the connector-first architecture.
  • Refresh documentation pages to focus on connector/adaptor usage and updated backend guidance.

Tests:

  • Update adaptor, connector, and base-connector tests to target the new BaseConnector-only hierarchy and connector-first behaviour.
  • Add unit tests for SimindPythonConnector voxel phantom configuration and energy-window file creation, and simplify PyTomography adaptor tests now that system-matrix helpers live in examples.

Chores:

  • Remove deprecated simulator, backend adapter, and related core components and tests that are superseded by the connector-first design.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Mar 5, 2026

Reviewer's Guide

Refactors the SIMIND integration to a connector-first architecture by routing STIR/SIRF/PyTomography adaptors through SimindPythonConnector, inlining voxel-phantom and energy-window setup into the connector core, removing the legacy SimindSimulator/native backend layer, and updating examples, tests, Dockerfiles, configs, and docs to match the new adaptor-focused API.

Sequence diagram for STIR adaptor run via SimindPythonConnector

sequenceDiagram
    actor User
    participant StirSimindAdaptor
    participant SimindPythonConnector
    participant SimulationConfig
    participant RuntimeSwitches
    participant SimindExecutor
    participant SIMIND
    participant STIR

    User->>StirSimindAdaptor: __init__(config_source, output_dir, ...)
    StirSimindAdaptor->>SimindPythonConnector: __init__(config_source, output_dir, ...)
    SimindPythonConnector->>SimulationConfig: load or clone config
    SimindPythonConnector->>RuntimeSwitches: initialize

    User->>StirSimindAdaptor: set_source(stir_image)
    StirSimindAdaptor-->>StirSimindAdaptor: store _source

    User->>StirSimindAdaptor: set_mu_map(stir_mu_map)
    StirSimindAdaptor-->>StirSimindAdaptor: store _mu_map

    User->>StirSimindAdaptor: set_energy_windows(lowers, uppers, orders)
    StirSimindAdaptor->>SimindPythonConnector: set_energy_windows(lowers, uppers, orders)

    User->>StirSimindAdaptor: add_runtime_switch("NN", photon_multiplier)
    StirSimindAdaptor->>SimindPythonConnector: add_runtime_switch("NN", photon_multiplier)
    SimindPythonConnector->>RuntimeSwitches: set_switch("NN", photon_multiplier)

    User->>StirSimindAdaptor: run(runtime_operator)
    StirSimindAdaptor-->>StirSimindAdaptor: _validate_inputs()
    StirSimindAdaptor-->>StirSimindAdaptor: _extract_voxel_size_mm(_source)
    StirSimindAdaptor-->>StirSimindAdaptor: convert source, mu_map to numpy

    StirSimindAdaptor->>SimindPythonConnector: configure_voxel_phantom(source_array, mu_array, voxel_size_mm, scoring_routine)
    SimindPythonConnector->>SimulationConfig: set_flag, set_value, set_data_file
    SimindPythonConnector->>RuntimeSwitches: set_switch("PX", vox_cm)
    SimindPythonConnector-->>StirSimindAdaptor: source_path, density_path

    StirSimindAdaptor->>SimindPythonConnector: run(runtime_operator)
    SimindPythonConnector->>SimindExecutor: run_simulation(output_prefix, orbit_file, runtime_switches)
    SimindExecutor->>SIMIND: invoke subprocess simind output_prefix ...
    SIMIND-->>SimindExecutor: projection files
    SimindExecutor-->>SimindPythonConnector: return

    SimindPythonConnector-->>SimindPythonConnector: load_interfile_array(...)
    SimindPythonConnector-->>StirSimindAdaptor: raw_outputs Dict

    StirSimindAdaptor-->>StirSimindAdaptor: wrap headers as stir.ProjData
    StirSimindAdaptor-->>User: outputs Dict~str, ProjData~
Loading

Class diagram for updated connector and adaptor types

classDiagram
    class BaseConnector {
        <<abstract>>
        +add_config_value(index int, value Any) void
        +add_runtime_switch(switch str, value Any) void
        +set_runtime_switches(switches Mapping) void
        +run(*args Any, **kwargs Any) Any
        +get_outputs() Dict~str, Any~
        +get_config() SimulationConfig
    }

    class SimulationConfig {
        +set_value(index int, value Any) void
        +set_flag(index int, value bool) void
        +set_data_file(index int, prefix str) void
        +get_value(key Any) Any
        +get_flag(index int) bool
    }

    class RuntimeSwitches {
        +set_switch(key str, value Any) void
        +get_all() Dict~str, Any~
    }

    class SimindExecutor {
        +logger
        +__init__() void
        +run_simulation(output_prefix str, orbit_file Path, runtime_switches Dict) void
    }

    class SimindPythonConnector {
        +config SimulationConfig
        +output_dir Path
        +output_prefix str
        +quantization_scale float
        +runtime_switches RuntimeSwitches
        +executor SimindExecutor
        +add_config_value(index int, value Any) void
        +add_runtime_switch(switch str, value Any) void
        +configure_voxel_phantom(source ndarray, mu_map ndarray, voxel_size_mm float, scoring_routine ScoringRoutine) tuple~Path, Path~
        +set_energy_windows(lower_bounds Any, upper_bounds Any, scatter_orders Any) void
        +run(runtime_operator RuntimeOperator) Dict~str, ProjectionResult~
        +get_config() SimulationConfig
    }

    class StirSimindAdaptor {
        +python_connector SimindPythonConnector
        +_scoring_routine ScoringRoutine
        +_source Any
        +_mu_map Any
        +_outputs Dict~str, Any~
        +__init__(config_source ConfigSource, output_dir str, output_prefix str, photon_multiplier int, quantization_scale float, scoring_routine ScoringRoutine) void
        +set_source(source Any) void
        +set_mu_map(mu_map Any) void
        +set_energy_windows(lower_bounds Any, upper_bounds Any, scatter_orders Any) void
        +add_config_value(index int, value Any) void
        +add_runtime_switch(switch str, value Any) void
        +run(runtime_operator RuntimeOperator) Dict~str, Any~
        +get_outputs() Dict~str, Any~
        +get_total_output(window int) Any
        +get_scatter_output(window int) Any
        +get_primary_output(window int) Any
        +get_air_output(window int) Any
        +get_penetrate_output(component PenetrateOutputType) Any
        +list_available_outputs() list~str~
        +get_scoring_routine() ScoringRoutine
        +get_config() SimulationConfig
        -_get_component(prefix str, window int) Any
        -_extract_voxel_size_mm(image Any) float
        -_validate_inputs() void
    }

    class SirfSimindAdaptor {
        +python_connector SimindPythonConnector
        +_scoring_routine ScoringRoutine
        +_source Any
        +_mu_map Any
        +_outputs Dict~str, Any~
        +__init__(config_source ConfigSource, output_dir str, output_prefix str, photon_multiplier int, quantization_scale float, scoring_routine ScoringRoutine) void
        +set_source(source Any) void
        +set_mu_map(mu_map Any) void
        +set_energy_windows(lower_bounds Any, upper_bounds Any, scatter_orders Any) void
        +add_config_value(index int, value Any) void
        +add_runtime_switch(switch str, value Any) void
        +run(runtime_operator RuntimeOperator) Dict~str, Any~
        +get_outputs() Dict~str, Any~
        +get_total_output(window int) Any
        +get_scatter_output(window int) Any
        +get_primary_output(window int) Any
        +get_air_output(window int) Any
        +get_penetrate_output(component PenetrateOutputType) Any
        +list_available_outputs() list~str~
        +get_scoring_routine() ScoringRoutine
        +get_config() SimulationConfig
        -_get_component(prefix str, window int) Any
        -_extract_voxel_size_mm(image Any) float
        -_validate_inputs() void
    }

    class PyTomographySimindAdaptor {
        +python_connector SimindPythonConnector
        +output_dir Path
        +output_prefix str
        +voxel_size_mm float
        +_scoring_routine ScoringRoutine
        +_source torch.Tensor
        +_mu_map torch.Tensor
        +_energy_windows tuple
        +_outputs Dict~str, torch.Tensor~
        +_output_header_paths Dict~str, Path~
        +__init__(config_source ConfigSource, output_dir PathLike, output_prefix str, voxel_size_mm float, photon_multiplier int, quantization_scale float, scoring_routine ScoringRoutine) void
        +set_source(source torch.Tensor) void
        +set_mu_map(mu_map torch.Tensor) void
        +set_energy_windows(lower_bounds list~float~, upper_bounds list~float~, scatter_orders list~int~) void
        +add_config_value(index int, value Any) void
        +add_runtime_switch(switch str, value Any) void
        +run(runtime_operator RuntimeOperator) Dict~str, torch.Tensor~
        +get_outputs() Dict~str, torch.Tensor~
        +get_output_header_path(key str) Path
        +get_total_output(window int) torch.Tensor
        +get_scatter_output(window int) torch.Tensor
        +get_primary_output(window int) torch.Tensor
        +get_air_output(window int) torch.Tensor
        +list_available_outputs() list~str~
        -_get_component(prefix str, window int) torch.Tensor
        -_validate_inputs() void
        -_validate_tensor(value torch.Tensor, name str) torch.Tensor
        +from_simind_image_axes(value torch.Tensor) torch.Tensor
        +to_simind_image_axes(value torch.Tensor) torch.Tensor
    }

    BaseConnector <|.. SimindPythonConnector
    BaseConnector <|-- StirSimindAdaptor
    BaseConnector <|-- SirfSimindAdaptor
    BaseConnector <|-- PyTomographySimindAdaptor

    SimindPythonConnector o-- SimulationConfig
    SimindPythonConnector o-- RuntimeSwitches
    SimindPythonConnector o-- SimindExecutor

    StirSimindAdaptor o-- SimindPythonConnector
    SirfSimindAdaptor o-- SimindPythonConnector
    PyTomographySimindAdaptor o-- SimindPythonConnector
Loading

File-Level Changes

Change Details Files
Reimplement STIR and SIRF adaptors on top of SimindPythonConnector instead of the legacy NativeBackendConnector/SimindSimulator stack.
  • Replace NativeBackendConnector inheritance with BaseConnector in STIR/SIRF adaptors and store an internal SimindPythonConnector instance.
  • Expose set_source/set_mu_map plus energy-window and config/runtime switch methods that forward directly to SimindPythonConnector.
  • Convert STIR/SIRF input objects to NumPy via get_array, derive voxel size from backend-specific metadata, and call configure_voxel_phantom on the connector.
  • Convert SimindPythonConnector ProjectionResult outputs back to native STIR/SIRF types (ProjData/AcquisitionData) and provide convenience accessors for components and PENETRATE outputs.
  • Add validation for presence of STIR/SIRF packages and for source/mu_map shape consistency and voxel-size readability.
sirf_simind_connection/connectors/stir_adaptor.py
sirf_simind_connection/connectors/sirf_adaptor.py
Promote voxel phantom and window-file configuration into SimindPythonConnector as first-class methods and adjust examples/tests accordingly.
  • Introduce configure_voxel_phantom on SimindPythonConnector to configure SIMIND geometry, scoring routine, and source/density files from 3D NumPy arrays, including scaling by MAX_SOURCE, quantization_scale, and Schneider attenuation_to_density.
  • Introduce set_energy_windows on SimindPythonConnector that writes the SIMIND .win file via create_window_file.
  • Refactor helper examples to use SimindPythonConnector.configure_voxel_phantom and set_energy_windows directly instead of standalone helpers.
  • Add unit tests for configure_voxel_phantom (file creation, scaling, validation) and set_energy_windows (window file content).
sirf_simind_connection/connectors/python_connector.py
examples/_python_connector_helpers.py
examples/01_basic_simulation.py
examples/02_runtime_switch_comparison.py
examples/03_multi_window.py
examples/05_scattwin_vs_penetrate_comparison.py
tests/test_python_connector.py
Simplify the PyTomography adaptor to a connector-first design and remove wrapped system-matrix helpers, pushing reconstruction concerns into PyTomography itself and examples.
  • Store a ScoringRoutine on the adaptor and pass it to configure_voxel_phantom, validating voxel_size_mm upfront.
  • Replace manual geometry, source/density file, and window file management with calls to SimindPythonConnector.configure_voxel_phantom and set_energy_windows using tensors converted to SIMIND axis order.
  • Delete adaptor-level get_pytomography_metadata and build_system_matrix helpers and associated geometry-configuration/write-window helpers.
  • Move SPECTSystemMatrix and transform construction into the 07C example with a dedicated _build_system_matrix helper that uses pytomography.io.SPECT.simind utilities.
  • Update tests to assert that the adaptor no longer exposes system-matrix helper methods and still preserves projection shapes.
sirf_simind_connection/connectors/pytomography_adaptor.py
examples/07C_pytomography_adaptor_osem.py
tests/test_pytomography_adaptor.py
Collapse the legacy simulator/core stack in favor of a minimal SimindExecutor and connector-first core API, and adjust package exports and tests.
  • Add SimindExecutor as a focused subprocess runner that builds simind/mpirun commands from runtime switches (including MPI support) and raises SimulationError on failure.
  • Switch SimindPythonConnector to use SimindExecutor from core.executor rather than the old core.components/SimindExecutor stack.
  • Remove legacy core modules related to backends, components, file managers, output processing, and SimindSimulator and delete their tests.
  • Simplify sirf_simind_connection.core to export only SimulationConfig and ScoringRoutine with a connector-first docstring and update package-level lazy import/export logic accordingly.
  • Update connector base to drop NativeBackendConnector, leaving only BaseConnector and adapting tests to validate adaptor inheritance from BaseConnector.
sirf_simind_connection/core/executor.py
sirf_simind_connection/connectors/python_connector.py
sirf_simind_connection/core/__init__.py
sirf_simind_connection/connectors/base.py
sirf_simind_connection/__init__.py
sirf_simind_connection/connectors/__init__.py
tests/test_connectors_base.py
sirf_simind_connection/core/backend_adapter.py
sirf_simind_connection/core/components.py
sirf_simind_connection/core/file_managers.py
sirf_simind_connection/core/output_processor.py
sirf_simind_connection/core/simulator.py
tests/test_backend_adapter.py
tests/test_components.py
tests/test_file_managers.py
tests/test_simind_simulator.py
Update documentation, examples, configs, Dockerfiles, and geometry tests to match the connector-first architecture and new adaptor semantics.
  • Rewrite README to describe SimindPythonConnector and adaptors as primary API surfaces, clarify optional dependencies per adaptor, and replace SimindSimulator usage with connector-first examples.
  • Adjust example projection-plane plotting functions and slice orientation for STIR/SIRF/PyTomography OSEM examples to reflect consistent axis semantics.
  • Increase the default number of projections in Example.yaml and tweak geometry diagnostics tests to allow small numerical tolerance when comparing baseline vs flipped/alternative geometries.
  • Change Dockerfiles to copy the repo with explicit ownership for the working user and ensure editable installs include examples/dev extras.
  • Refresh multiple Sphinx docs pages (api, backends, geometry, installation, intro, testing, usage) to align with the new architecture (content changes not fully shown in diff).
README.md
examples/04_custom_config.py
examples/07A_stir_adaptor_osem.py
examples/07B_sirf_adaptor_osem.py
examples/07C_pytomography_adaptor_osem.py
sirf_simind_connection/configs/Example.yaml
tests/test_geometry_isolation_forward_projection.py
docker/pytomography/Dockerfile
docker/sirf/Dockerfile
docker/stir/Dockerfile
docs/api.rst
docs/backends.rst
docs/geometry.rst
docs/installation.rst
docs/intro.rst
docs/testing.rst
docs/usage.rst

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 security issue, 4 other issues, and left some high level feedback:

Security issues:

  • Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. (link)

General comments:

  • The STIR and SIRF adaptors now have near-identical logic (source/mu handling, energy window plumbing, output accessors, voxel-size extraction); consider factoring this into a shared helper/base class to reduce duplication and keep future behavior changes consistent across backends.
  • The STIR adaptor’s _extract_voxel_size_mm falls back to get_grid_spacing()[3] and the SIRF adaptor only supports voxel_sizes()[2]; it might be worth aligning these conventions and documenting/validating expected units/axis ordering to avoid subtle geometry mismatches between backends.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The STIR and SIRF adaptors now have near-identical logic (source/mu handling, energy window plumbing, output accessors, voxel-size extraction); consider factoring this into a shared helper/base class to reduce duplication and keep future behavior changes consistent across backends.
- The STIR adaptor’s `_extract_voxel_size_mm` falls back to `get_grid_spacing()[3]` and the SIRF adaptor only supports `voxel_sizes()[2]`; it might be worth aligning these conventions and documenting/validating expected units/axis ordering to avoid subtle geometry mismatches between backends.

## Individual Comments

### Comment 1
<location path="sirf_simind_connection/connectors/stir_adaptor.py" line_range="153-155" />
<code_context>
+            if len(voxel_sizes) >= 3:
+                return float(voxel_sizes[2])
+        if hasattr(image, "get_grid_spacing"):
+            spacing = image.get_grid_spacing()
+            try:
+                return float(spacing[3])
+            except Exception as exc:  # pragma: no cover - backend-specific
+                raise ValueError(
</code_context>
<issue_to_address>
**issue (bug_risk):** Potential off‑by‑one when reading voxel spacing from get_grid_spacing()

With a 3-element spacing tuple, indexing `spacing[3]` will always raise IndexError and force the error path, so `get_grid_spacing()` can never succeed. If you intended to use the z-spacing, this should be `spacing[2]`, or the expected spacing format should be clarified in documentation.
</issue_to_address>

### Comment 2
<location path="tests/test_pytomography_adaptor.py" line_range="61-70" />
<code_context>
-    monkeypatch.setattr(adapter_mod, "AcquisitionDataInterface", DummyInterface)
-
-
-@pytest.mark.unit
-def test_backend_adapter_prefers_first_backend(monkeypatch):
-    """First wrapped object sets backend preference."""
</code_context>
<issue_to_address>
**suggestion (testing):** Add positive tests for new PyTomographySimindAdaptor geometry + connector wiring, not only interface-removal checks.

The current test only checks removal of `build_system_matrix` / `get_pytomography_metadata`, so it doesn’t validate the new adaptor–connector behavior.

Please add a positive test that, via a monkeypatched `SimindPythonConnector`, verifies that:
- a small `(x, y, z)` source and mu-map tensor passed through the adaptor reaches `configure_voxel_phantom` in SIMIND `(z, y, x)` order,
- `voxel_size_mm`, energy windows, and `scoring_routine` are forwarded correctly via `set_energy_windows` / `run`, and
- `get_total_output` / `get_scatter_output` / `get_penetrate_output` simply return the connector’s outputs.

This will exercise the new connector-first integration and axis handling end-to-end, not just the removed methods.
</issue_to_address>

### Comment 3
<location path="tests/test_python_connector.py" line_range="218-227" />
<code_context>
-    monkeypatch.setattr(adapter_mod, "AcquisitionDataInterface", DummyInterface)
-
-
-@pytest.mark.unit
-def test_backend_adapter_prefers_first_backend(monkeypatch):
-    """First wrapped object sets backend preference."""
</code_context>
<issue_to_address>
**suggestion (testing):** Extend SimindPythonConnector.configure_voxel_phantom tests to cover validation and attenuation edge cases.

These tests are a solid start. To better cover `configure_voxel_phantom`, consider adding cases for:

1. Non-3D inputs: pass a 2D or 4D `source_array`/`mu_map_array` and assert `ValueError` with the "must both be 3D arrays" message.
2. Non-positive `voxel_size_mm`: call with 0 or negative and assert `ValueError("voxel_size_mm must be > 0")`.
3. Attenuation disabled: with the attenuation flag off and a nonzero `mu_map`, assert `density_u16` in the written file is all zeros.
4. Enum `scoring_routine`: pass a `ScoringRoutine` enum and assert `cfg.get_value(84) == routine.value`.

These additions would strengthen regression coverage for this helper.
</issue_to_address>

### Comment 4
<location path="tests/test_connectors_base.py" line_range="14-23" />
<code_context>
-    monkeypatch.setattr(adapter_mod, "AcquisitionDataInterface", DummyInterface)
-
-
-@pytest.mark.unit
-def test_backend_adapter_prefers_first_backend(monkeypatch):
-    """First wrapped object sets backend preference."""
</code_context>
<issue_to_address>
**suggestion (testing):** Add behavioural tests for the new STIR/SIRF adaptor implementations (validation, voxel size extraction, and runtime path).

Currently this file only checks the inheritance hierarchy and that `quantization_scale` stays an `__init__` parameter. The refactor added substantial behaviour in `SirfSimindAdaptor` / `StirSimindAdaptor` (input validation, conversion to/from NumPy, voxel size extraction, and output handling) that isn’t covered.

Please add targeted behavioural tests using small dummy STIR/SIRF-like objects and a monkeypatched `SimindPythonConnector` that records `configure_voxel_phantom` calls. Suggested assertions:
- `run()` raises `ValueError` when source or mu_map is missing, or shapes differ.
- `run()` forwards float32 arrays with the expected shape and `scoring_routine` into `configure_voxel_phantom`.
- `_extract_voxel_size_mm` uses `voxel_sizes()` or `get_grid_spacing()` and raises `ValueError` when neither is present.
- `get_penetrate_output` / `_get_component` raise `KeyError` listing available keys when a component is missing.

This will exercise the new adaptor behaviour rather than just its signature.
</issue_to_address>

### Comment 5
<location path="sirf_simind_connection/core/executor.py" line_range="62" />
<code_context>
            subprocess.run(command, check=True)
</code_context>
<issue_to_address>
**security (python.lang.security.audit.dangerous-subprocess-use-audit):** Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.

*Source: opengrep*
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread sirf_simind_connection/connectors/stir_adaptor.py Outdated
Comment thread tests/test_pytomography_adaptor.py
Comment thread tests/test_python_connector.py
Comment thread tests/test_connectors_base.py
Comment thread sirf_simind_connection/core/executor.py Outdated
@samdporter samdporter merged commit 41fa5d8 into main Mar 5, 2026
5 of 6 checks passed
@samdporter samdporter deleted the codex/simplify-repo branch March 5, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants