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/mri/phantoms/vial-signal.yaml b/specs/australian-imaging-service/mri/phantoms/vial-signal.yaml new file mode 100644 index 00000000..f16b1cc9 --- /dev/null +++ b/specs/australian-imaging-service/mri/phantoms/vial-signal.yaml @@ -0,0 +1,49 @@ +schema_version: '2.0' +title: Phantom vial signal metrics +version: &package_version "0.1.7" +authors: + - name: Arkiev D'Souza + email: arkiev.dsouza@sydney.edu.au + - name: Thomas G. Close + email: tom.g.close@gmail.com +docs: + description: >- + End-to-end pydra workflow for processing a GSP SPIRIT or 120E phantom MRI + session. + + 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: *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 diff --git a/tests/test_vial_signal.py b/tests/test_vial_signal.py new file mode 100644 index 00000000..534e8c15 --- /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" + / "mri" + / "phantoms" + / "vial-signal.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" / "mri" / "phantoms" / "vial-signal" + 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 = { + "all_metrics": { + "input_image": "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}"