From 83828d08500908f59471b6490354ee911fcbf322 Mon Sep 17 00:00:00 2001 From: "Thomas G. Close" Date: Thu, 11 Jun 2026 11:15:33 +1000 Subject: [PATCH 1/4] add new gsp spirit phantom --- .../quality-control/phantoms/gsp-spirit.yaml | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 specs/australian-imaging-service/quality-control/phantoms/gsp-spirit.yaml diff --git a/specs/australian-imaging-service/quality-control/phantoms/gsp-spirit.yaml b/specs/australian-imaging-service/quality-control/phantoms/gsp-spirit.yaml new file mode 100644 index 00000000..256b5447 --- /dev/null +++ b/specs/australian-imaging-service/quality-control/phantoms/gsp-spirit.yaml @@ -0,0 +1,30 @@ +schema_version: '2.0' +title: GSP-Spirit-Analysis +version: &package_version "0.1.0" +authors: + - name: Arkiev D'Souza + email: arkiev@mq.edu.au + - name: Thomas G. Close + email: tom.g.close@gmail.com +docs: + description: >- + Pydra workflow for processing a single GSP SPIRIT phantom MRI session. + + Registers the phantom scan to the GSP SPIRIT template using iterative ANTs + SyN registration with an orientation search, extracts per-vial signal + statistics for all contrast images, and generates publication-quality plots. + info_url: https://github.com/Australian-Imaging-Service/phantomkit +packages: + pip: + phantomkit: *package_version + pydra: 1.0a7 + neurodocker: + dcm2niix: v1.0.20240202 + mrtrix3: 3.0.2 + ants: 2.6.2 +commands: + gsp_spirit: + operates_on: medimage/session + task: + type: python + function: phantomkit.protocols.gsp_spirit:GspSpiritAnalysis From 6b7a4bf2b18f3b5e6f5de8fa948bb45a303b6ae6 Mon Sep 17 00:00:00 2001 From: "Thomas G. Close" Date: Thu, 11 Jun 2026 11:15:33 +1000 Subject: [PATCH 2/4] renamed pipeline to vial_signal --- .../quality-control/phi-finder.yaml | 0 .../phantoms/vial_signals.yaml} | 10 +- tests/test_vial_signal.py | 99 +++++++++++++++++++ 3 files changed, 103 insertions(+), 6 deletions(-) rename specs/australian-imaging-service/{ => generic}/quality-control/phi-finder.yaml (100%) rename specs/australian-imaging-service/{quality-control/phantoms/gsp-spirit.yaml => mri/phantoms/vial_signals.yaml} (81%) create mode 100644 tests/test_vial_signal.py diff --git a/specs/australian-imaging-service/quality-control/phi-finder.yaml b/specs/australian-imaging-service/generic/quality-control/phi-finder.yaml similarity index 100% rename from specs/australian-imaging-service/quality-control/phi-finder.yaml rename to specs/australian-imaging-service/generic/quality-control/phi-finder.yaml diff --git a/specs/australian-imaging-service/quality-control/phantoms/gsp-spirit.yaml b/specs/australian-imaging-service/mri/phantoms/vial_signals.yaml similarity index 81% rename from specs/australian-imaging-service/quality-control/phantoms/gsp-spirit.yaml rename to specs/australian-imaging-service/mri/phantoms/vial_signals.yaml index 256b5447..ca9413fb 100644 --- a/specs/australian-imaging-service/quality-control/phantoms/gsp-spirit.yaml +++ b/specs/australian-imaging-service/mri/phantoms/vial_signals.yaml @@ -1,6 +1,6 @@ schema_version: '2.0' -title: GSP-Spirit-Analysis -version: &package_version "0.1.0" +title: Phantom vial signal metrics +version: &package_version "0.1.7" authors: - name: Arkiev D'Souza email: arkiev@mq.edu.au @@ -23,8 +23,6 @@ packages: mrtrix3: 3.0.2 ants: 2.6.2 commands: - gsp_spirit: + all_metrics: operates_on: medimage/session - task: - type: python - function: phantomkit.protocols.gsp_spirit:GspSpiritAnalysis + task: phantomkit.protocols.vial_signal:VialSignalAnalysis diff --git a/tests/test_vial_signal.py b/tests/test_vial_signal.py new file mode 100644 index 00000000..5280deb4 --- /dev/null +++ b/tests/test_vial_signal.py @@ -0,0 +1,99 @@ +import json +import typing as ty +from pathlib import Path +from pydra2app.core.cli import make +from pydra2app.xnat import XnatApp +from frametree.core.utils import show_cli_trace +from pydra2app.xnat.deploy import install_and_launch_xnat_cs_command +from conftest import upload_test_dataset_to_xnat, test_data_dir + +PKG_DIR = Path(__file__).parent.parent + +SPEC_PATH = ( + PKG_DIR + / "specs" + / "australian-imaging-service" + / "quality-control" + / "phantoms" + / "gsp-spirit.yaml" +) + +SKIP_BUILD = False + + +def test_vial_signal_app( + run_prefix: str, + xnat_connect: ty.Any, + cli_runner: ty.Callable[..., ty.Any], + tmp_path: Path, +): + + build_dir = tmp_path / "build" + + build_dir.mkdir(exist_ok=True, parents=True) + + project_id = f"{run_prefix}qualitycontrolgspspirit" + + test_data = test_data_dir / "specs" / "quality-control" / "phantoms" / "gsp-spirit" + upload_test_dataset_to_xnat(project_id, test_data, xnat_connect) + + if SKIP_BUILD: + build_arg = "--generate-only" + else: + build_arg = "--build" + + result = cli_runner( + make, + [ + "xnat", + str(SPEC_PATH), + "--build-dir", + str(build_dir), + build_arg, + "--for-localhost", + "--use-local-packages", + "--raise-errors", + ], + ) + + assert result.exit_code == 0, show_cli_trace(result) + + image_spec = XnatApp.load(SPEC_PATH) + + command_inputs = { + "gsp_spirit": { + "in_file": "foo_bar", + } + } + + with xnat_connect() as xlogin: + + for command_obj in image_spec.commands: + with open(build_dir / "xnat_commands" / (command_obj.name + ".json")) as f: + command_json = json.load(f) + command_json["name"] = command_json["label"] = ( + image_spec.name + command_obj.name + run_prefix + ) + + test_xsession = next(iter(xlogin.projects[project_id].experiments.values())) + + inputs_json = command_inputs[command_obj.name] + inputs_json["pydra2app_flags"] = ( + "--worker debug " + "--work /work " # NB: work dir moved inside container due to file-locking issue on some mounted volumes (see https://github.com/tox-dev/py-filelock/issues/147) + "--dataset-name default " + "--logger frametree debug " + "--logger frametree-xnat debug " + "--logger pydra2app debug " + "--logger pydra2app-xnat debug " + ) + + workflow_id, status, out_str = install_and_launch_xnat_cs_command( + command_json=command_json, + project_id=project_id, + session_id=test_xsession.id, + inputs=inputs_json, + xlogin=xlogin, + timeout=30000, + ) + assert status == "Complete", f"Workflow {workflow_id} failed.\n{out_str}" From 1a8a0dd65fc69cde33596478e69511533059a501 Mon Sep 17 00:00:00 2001 From: "Thomas G. Close" Date: Thu, 11 Jun 2026 11:15:33 +1000 Subject: [PATCH 3/4] added vial-signal pipeline --- .../phantoms/{vial_signals.yaml => vial-signal.yaml} | 8 ++++---- tests/test_vial_signal.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) rename specs/australian-imaging-service/mri/phantoms/{vial_signals.yaml => vial-signal.yaml} (85%) diff --git a/specs/australian-imaging-service/mri/phantoms/vial_signals.yaml b/specs/australian-imaging-service/mri/phantoms/vial-signal.yaml similarity index 85% rename from specs/australian-imaging-service/mri/phantoms/vial_signals.yaml rename to specs/australian-imaging-service/mri/phantoms/vial-signal.yaml index ca9413fb..0289ccaf 100644 --- a/specs/australian-imaging-service/mri/phantoms/vial_signals.yaml +++ b/specs/australian-imaging-service/mri/phantoms/vial-signal.yaml @@ -16,13 +16,13 @@ docs: info_url: https://github.com/Australian-Imaging-Service/phantomkit packages: pip: - phantomkit: *package_version - pydra: 1.0a7 + phantomkit: + pydra: 1.0a8 neurodocker: dcm2niix: v1.0.20240202 - mrtrix3: 3.0.2 + mrtrix3: 3.0.4 ants: 2.6.2 commands: all_metrics: operates_on: medimage/session - task: phantomkit.protocols.vial_signal:VialSignalAnalysis + task: phantomkit.analyses.vial_signal:VialSignalAnalysis diff --git a/tests/test_vial_signal.py b/tests/test_vial_signal.py index 5280deb4..534e8c15 100644 --- a/tests/test_vial_signal.py +++ b/tests/test_vial_signal.py @@ -13,9 +13,9 @@ PKG_DIR / "specs" / "australian-imaging-service" - / "quality-control" + / "mri" / "phantoms" - / "gsp-spirit.yaml" + / "vial-signal.yaml" ) SKIP_BUILD = False @@ -34,7 +34,7 @@ def test_vial_signal_app( project_id = f"{run_prefix}qualitycontrolgspspirit" - test_data = test_data_dir / "specs" / "quality-control" / "phantoms" / "gsp-spirit" + test_data = test_data_dir / "specs" / "mri" / "phantoms" / "vial-signal" upload_test_dataset_to_xnat(project_id, test_data, xnat_connect) if SKIP_BUILD: @@ -61,8 +61,8 @@ def test_vial_signal_app( image_spec = XnatApp.load(SPEC_PATH) command_inputs = { - "gsp_spirit": { - "in_file": "foo_bar", + "all_metrics": { + "input_image": "foo_bar", } } From a0cb8e0a801925bc5e8fe3516e7f56634a8450eb Mon Sep 17 00:00:00 2001 From: arkiev Date: Thu, 11 Jun 2026 11:15:33 +1000 Subject: [PATCH 4/4] update to match single-workflow PR --- .../mri/phantoms/vial-signal.yaml | 35 +++++++++++++++---- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/specs/australian-imaging-service/mri/phantoms/vial-signal.yaml b/specs/australian-imaging-service/mri/phantoms/vial-signal.yaml index 0289ccaf..f16b1cc9 100644 --- a/specs/australian-imaging-service/mri/phantoms/vial-signal.yaml +++ b/specs/australian-imaging-service/mri/phantoms/vial-signal.yaml @@ -3,26 +3,47 @@ title: Phantom vial signal metrics version: &package_version "0.1.7" authors: - name: Arkiev D'Souza - email: arkiev@mq.edu.au + email: arkiev.dsouza@sydney.edu.au - name: Thomas G. Close email: tom.g.close@gmail.com docs: description: >- - Pydra workflow for processing a single GSP SPIRIT phantom MRI session. + End-to-end pydra workflow for processing a GSP SPIRIT or 120E phantom MRI + session. - Registers the phantom scan to the GSP SPIRIT template using iterative ANTs - SyN registration with an orientation search, extracts per-vial signal - statistics for all contrast images, and generates publication-quality plots. + Supports three processing stages: + + Stage 1 — DWI preprocessing: automatic series classification and + phase-encoding correction mode selection (rpe_none, rpe_pair, rpe_all, + rpe_split), FSL dwifslpreproc, DWI bias correction, tensor fitting to + produce ADC and FA maps, and T1-to-DWI coregistration via FLIRT. + + Stage 2 — Phantom QC in DWI space: iterative ANTs rigid registration of + the T1 (in DWI space) to the phantom template, inverse transform of vial + segmentations into DWI space, and per-vial ADC and FA metric extraction. + + Stage 3 — Native contrast QC: registration of the T1 to the phantom + template, inverse transform of vial segmentations, and per-vial metric + extraction across all contrast images (T1, IR, TE). Generates + publication-quality scatter plots and parametric map plots. + + All stages are invoked from a single command pointing at a DICOM session + directory; series classification and preprocessing mode selection are fully + automatic. info_url: https://github.com/Australian-Imaging-Service/phantomkit packages: pip: - phantomkit: - pydra: 1.0a8 + phantomkit: *package_version + pydra: 1.0a9 neurodocker: dcm2niix: v1.0.20240202 mrtrix3: 3.0.4 ants: 2.6.2 + fsl: 6.0.7.9 commands: all_metrics: operates_on: medimage/session task: phantomkit.analyses.vial_signal:VialSignalAnalysis + pipeline: + operates_on: medimage/session + task: phantomkit.pipeline:run_pipeline \ No newline at end of file