Skip to content

Commit 1973e38

Browse files
committed
debugging t1w pipeline and associated unittests
1 parent d8bd45e commit 1973e38

File tree

8 files changed

+186
-76
lines changed

8 files changed

+186
-76
lines changed

.github/workflows/release.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ jobs:
2222
matrix:
2323
spec:
2424
- quality-control/phi-finder
25-
- mri/human/neuro/bidsapp/fmriprep
26-
- mri/human/neuro/bidsapp/mriqc
27-
- mri/human/neuro/bidsapp/smriprep
28-
# - mri/human/neuro/t1w/preprocess
25+
# - mri/human/neuro/bidsapp/fmriprep
26+
# - mri/human/neuro/bidsapp/mriqc
27+
# - mri/human/neuro/bidsapp/smriprep
28+
- mri/human/neuro/t1w/preprocess
2929
steps:
3030
- name: Removed unnecessary tools to free space
3131
run: |

conftest.py

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from frametree.core.utils import varname2path
99
import pytest
1010
from click.testing import CliRunner
11+
import xnat
1112
import xnat4tests
1213

1314
# Set DEBUG logging for unittests
@@ -70,13 +71,17 @@ class BidsAppTestBlueprint:
7071
/ "neuro"
7172
/ "bidsapps"
7273
)
73-
test_bids_data_dir = (
74-
Path(__file__).parent / "tests" / "data" / "mri" / "human" / "neuro" / "bidsapps"
75-
)
74+
test_data_dir = Path(__file__).parent / "tests" / "data"
75+
test_bids_data_dir = test_data_dir / "specs" / "mri" / "human" / "neuro" / "bidsapps"
7676

7777
bids_specs = [str(p.stem) for p in bids_apps_dir.glob("*.yaml")]
7878

7979

80+
@pytest.fixture(params=bids_specs)
81+
def bids_app_spec_path(request):
82+
return bids_apps_dir / (request.param + ".yaml")
83+
84+
8085
@pytest.fixture(params=bids_specs)
8186
def bids_app_blueprint(run_prefix, xnat_connect, request):
8287
bids_app_name = request.param
@@ -168,4 +173,10 @@ def upload_test_dataset_to_xnat(project_id: str, source_data_dir: Path, xnat_con
168173
xresource.upload_dir(resource_path, method="tar_file")
169174

170175
# Populate metadata from DICOM headers
171-
login.put(f"/data/experiments/{xsession.id}?pullDataFromHeaders=true")
176+
try:
177+
login.put(f"/data/experiments/{xsession.id}?pullDataFromHeaders=true")
178+
except xnat.exceptions.XNATResponseError as e:
179+
logger.warning(
180+
f"Failed to pull metadata from DICOM headers for session {xsession.id} "
181+
f"with error: {e}"
182+
)

specs/australian-imaging-service/mri/human/neuro/t1w/preprocess.yaml

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ base_image:
99
packages:
1010
# Install dependencies for DICOM->NIfTI conversion
1111
pip:
12-
pydra-mrtrix3: 3.0.4a9
13-
pydra-fsl:
12+
pydra-tasks-mrtrix3:
13+
pydra-tasks-fsl:
1414
fileformats:
15-
pydra-fastsurfer:
15+
pydra-tasks-fastsurfer:
1616
fileformats-medimage-extras:
17-
fileformats-medimage-mrtrix3: 3.0.4a12
18-
fileformats-medimage-mrtrix3-extras: 3.0.4a12
17+
fileformats-medimage-mrtrix3:
18+
fileformats-medimage-mrtrix3-extras:
1919
australianimagingservice:
2020
neurodocker:
2121
mrtrix3: 3.0.2
@@ -66,31 +66,33 @@ commands:
6666
single_parc:
6767
task: australianimagingservice.mri.human.neuro.t1w.preprocess:SingleParcellation
6868
operates_on: medimage/session
69-
inputs:
70-
- t1w
69+
sources:
70+
T1w:
71+
field: t1w
72+
help: "The T1-weighted MRI dataset to preprocess"
73+
parameters:
74+
Parcellation:
75+
field: parcellation
76+
help: "The parcellation to parcelate the cortex with"
7177
configuration: # Additional args passed to arcana.common:shell_cmd
7278
mrtrix_lut_dir: /parcellations # /opt/mrtrix3-3.0.2/share/mrtrix3/labelconvert
7379
freesurfer_home: &freesurfer_home !join ["/opt/freesurfer-", *freesurfer_version]
7480
fs_license: *freesurfer_license
75-
cache_dir: /work/pydra-cache
76-
fastsurfer_executable:
77-
- /fastsurfer/Docker/entrypoint.sh
78-
- /fastsurfer/run_fastsurfer.sh
79-
- --allow_root
81+
subjects_dir: /work/subjects
82+
in_fastsurfer_container: true
8083
all_parcs:
8184
task: australianimagingservice.mri.human.neuro.t1w.preprocess:AllParcellations
8285
operates_on: medimage/session
83-
inputs: # List the inputs that are presented to end-user in UI
84-
- t1w
86+
sources:
87+
T1w:
88+
field: t1w
89+
help: "The T1-weighted MRI dataset to preprocess"
8590
configuration: # Additional args passed to arcana.common:shell_cmd
8691
mrtrix_lut_dir: /parcellations # /opt/mrtrix3-3.0.2/share/mrtrix3/labelconvert ## should this be
87-
freesurfer_home: &freesurfer_home !join ["/opt/freesurfer-", *freesurfer_version] ## freesurfer_home: *freesurfer_home
92+
freesurfer_home: *freesurfer_home ## freesurfer_home: *freesurfer_home
8893
fs_license: *freesurfer_license
89-
cache_dir: /work/pydra-cache
90-
fastsurfer_executable:
91-
- /fastsurfer/Docker/entrypoint.sh
92-
- /fastsurfer/run_fastsurfer.sh
93-
- --allow_root
94+
subjects_dir: /work/subjects
95+
in_fastsurfer_container: true
9496
resources:
9597
neuro-parcellations: /parcellations
9698
yeo2011-parcellations: !join [*freesurfer_home, "/Yeo2011"]

src/australianimagingservice/mri/human/neuro/t1w/preprocess/all_parcs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ def AllParcellations(
7171
freesurfer_home: Directory,
7272
mrtrix_lut_dir: Directory,
7373
fs_license: File,
74-
fastsurfer_executable: str | list[str] | None = None,
74+
in_fastsurfer_container: bool = False,
7575
fastsurfer_python: str = "python3",
7676
) -> tuple[
7777
DirectoryOf[Mif],
@@ -96,7 +96,7 @@ def AllParcellations(
9696
freesurfer_home=freesurfer_home,
9797
mrtrix_lut_dir=mrtrix_lut_dir,
9898
fs_license=fs_license,
99-
fastsurfer_executable=fastsurfer_executable,
99+
in_fastsurfer_container=in_fastsurfer_container,
100100
fastsurfer_python=fastsurfer_python,
101101
subjects_dir=subjects_dir,
102102
), # pyright: ignore[reportArgumentType]

src/australianimagingservice/mri/human/neuro/t1w/preprocess/single_parc.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from fileformats.medimage import NiftiGz
1818
from fileformats.medimage_mrtrix3 import ImageFormat as Mif
1919
from pydra.environments.docker import Docker
20+
from pydra.environments.native import Native
2021
from pydra.tasks.fastsurfer.latest import Fastsurfer
2122
from .helpers import JoinTaskCatalogue, LabelSgmFix, Dependency
2223

@@ -46,42 +47,48 @@ def SingleParcellation(
4647
mrtrix_lut_dir: Directory,
4748
fs_license: File,
4849
subjects_dir: Path,
49-
fastsurfer_executable: ty.Union[str, ty.List[str], None] = None,
50+
in_fastsurfer_container: bool = False,
5051
fastsurfer_python: str = "python3",
5152
) -> tuple[Mif, Mif | None, Mif | None, Mif | None, Mif | None, Mif | None, Mif | None]:
5253

5354
# ###################
5455
# # FASTSURFER TASK #
5556
# ###################
5657

58+
if in_fastsurfer_container:
59+
fs_environment = Native()
60+
else:
61+
fs_environment = Docker(
62+
image="deepmi/fastsurfer",
63+
tag="cpu-v2.4.2",
64+
xargs=[
65+
"--user",
66+
"1000:1000",
67+
"--entrypoint",
68+
"/bin/bash",
69+
],
70+
)
71+
5772
fastsurfer = workflow.add(
5873
Fastsurfer(
5974
T1_files=t1w,
6075
fs_license=fs_license,
6176
subject_id="FS_outputs",
62-
py=fastsurfer_python,
6377
# norm_img="norm.mgz",
6478
# aparcaseg_img="aparcaseg.mgz",
6579
fsaparc=True,
6680
parallel=True,
6781
threads=24,
6882
subjects_dir=subjects_dir,
6983
),
70-
environment=Docker(
71-
image="deepmi/fastsurfer",
72-
tag="cpu-v2.4.2",
73-
xargs=[
74-
"--user",
75-
"1000:1000",
76-
"--entrypoint",
77-
"/bin/bash",
78-
"-v",
79-
f"{subjects_dir}:/mnt/pydra/{subjects_dir}:rw",
80-
],
81-
),
84+
environment=fs_environment,
8285
)
83-
if fastsurfer_executable:
84-
fastsurfer.inputs.executable = fastsurfer_executable
86+
87+
if in_fastsurfer_container:
88+
fastsurfer.inputs.py = "python3"
89+
fastsurfer.inputs.executable = "/fastsurfer/run_fastsurfer.sh"
90+
else:
91+
fastsurfer.inputs.py = fastsurfer_python
8592

8693
# #################################################
8794
# # FIVE TISSUE TYPE Generation and visualisation #
Lines changed: 17 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,28 @@
1+
import os
12
from pathlib import Path
23
from pydra2app.core import App
34
from australianimagingservice.mri.human.neuro.t1w.preprocess import AllParcellations
5+
import xnat
6+
import pytest
7+
from conftest import upload_test_dataset_to_xnat, test_data_dir
48

5-
PKG_DIR = (
6-
Path(__file__).parent / ".." / ".." / ".." / ".." / ".." / ".." / ".."
7-
).resolve()
9+
home_dir = Path("~").expanduser()
810

911

10-
def test_t1w_preprocess():
12+
@pytest.mark.skip
13+
def test_t1w_preprocess(tmp_path: Path):
14+
15+
test_data = test_data_dir / "src" / "mri" / "human" / "neuro" / "t1w" / "preprocess"
16+
1117
wf = AllParcellations(
12-
t1w=,
13-
subjects_dir=,
14-
freesurfer_home=,
15-
mrtrix_lut_dir=,
16-
fs_license=,
17-
"/opt/FastSurfer",
18-
"/opt/mrtrix3/3.0.4/share/mrtrix3/labelconvert",
19-
cache_dir="/Users/tclose/Desktop/cache-dir",
20-
"/Users/tclose/Desktop/file1.txt",
18+
t1w=test_data / "t1w.nii.gz",
19+
subjects_dir=tmp_path / "subjects",
20+
freesurfer_home=os.environ["FREESURFER_HOME"],
21+
mrtrix_lut_dir=(
22+
Path(os.environ["MRTRIX3_HOME"]) / "share" / "mrtrix3" / "labelconvert"
23+
),
24+
fs_license=Path(os.environ["FREESURFER_HOME"]).parent / "licence.txt",
2125
)
2226

2327
outputs = wf()
2428
print(outputs)
25-
26-
27-
def test_t1w_preprocess_yaml(tmp_path: Path):
28-
app = App.load(
29-
PKG_DIR
30-
/ "specs"
31-
/ "australian-imaging-service"
32-
/ "mri"
33-
/ "human"
34-
/ "neuro"
35-
/ "t1w"
36-
/ "preprocess.yaml"
37-
)
38-
39-
app.make(
40-
build_dir=tmp_path, generate_only=True, resources_dir=PKG_DIR / "resources"
41-
)

tests/test_phi_finder.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@
33
from pydra2app.core.cli import make
44
from frametree.core.utils import show_cli_trace
55
import xnat4tests
6+
import pytest
67
from pydra2app.xnat.deploy import install_cs_command, launch_cs_command
78

89
PKG_PATH = Path(__file__).parent.parent.absolute()
910

1011
runner = CliRunner()
1112

1213

14+
@pytest.mark.skip
1315
def test_phi_finder():
1416
result = runner.invoke(
1517
make,

tests/test_t1_preprocess.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import json
2+
import typing as ty
3+
from pathlib import Path
4+
from pydra2app.core.cli import make
5+
from pydra2app.xnat import XnatApp
6+
from frametree.core.utils import show_cli_trace
7+
from pydra2app.xnat.deploy import install_and_launch_xnat_cs_command
8+
from conftest import upload_test_dataset_to_xnat, test_data_dir
9+
10+
PKG_DIR = Path(__file__).parent.parent
11+
12+
SPEC_PATH = (
13+
PKG_DIR
14+
/ "specs"
15+
/ "australian-imaging-service"
16+
/ "mri"
17+
/ "human"
18+
/ "neuro"
19+
/ "t1w"
20+
/ "preprocess.yaml"
21+
)
22+
23+
RESOURCES_DIR = PKG_DIR / "resources"
24+
25+
SKIP_BUILD = False
26+
27+
28+
def test_t1_preprocess_app(
29+
run_prefix: str,
30+
xnat_connect: ty.Any,
31+
cli_runner: ty.Callable[..., ty.Any],
32+
tmp_path: Path,
33+
):
34+
35+
build_dir = tmp_path / "build"
36+
37+
build_dir.mkdir(exist_ok=True, parents=True)
38+
39+
project_id = f"{run_prefix}mrihumanneurot1wpreprocess"
40+
test_data = (
41+
test_data_dir / "specs" / "mri" / "human" / "neuro" / "t1w" / "preprocess"
42+
)
43+
upload_test_dataset_to_xnat(project_id, test_data, xnat_connect)
44+
45+
if SKIP_BUILD:
46+
build_arg = "--generate-only"
47+
else:
48+
build_arg = "--build"
49+
50+
result = cli_runner(
51+
make,
52+
[
53+
"xnat",
54+
str(SPEC_PATH),
55+
"--build-dir",
56+
str(build_dir),
57+
build_arg,
58+
"--resources-dir",
59+
str(RESOURCES_DIR),
60+
"--for-localhost",
61+
"--use-local-packages",
62+
"--raise-errors",
63+
],
64+
)
65+
66+
assert result.exit_code == 0, show_cli_trace(result)
67+
68+
image_spec = XnatApp.load(SPEC_PATH)
69+
70+
with xnat_connect() as xlogin:
71+
72+
with open(
73+
build_dir / image_spec.name / "xnat_commands" / (image_spec.name + ".json")
74+
) as f:
75+
xnat_command = json.load(f)
76+
xnat_command.name = xnat_command.label = image_spec.name + run_prefix
77+
78+
test_xsession = next(
79+
iter(xnat_connect.projects[project_id].experiments.values())
80+
)
81+
82+
inputs_json = {
83+
"T1w": test_data / "T1w",
84+
"Parcellation": "desikan",
85+
"pydra2app_flags": (
86+
"--worker serial "
87+
"--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)
88+
"--dataset-name default "
89+
"--loglevel debug "
90+
),
91+
}
92+
93+
workflow_id, status, out_str = install_and_launch_xnat_cs_command(
94+
command_json=xnat_command,
95+
project_id=project_id,
96+
session_id=test_xsession.id,
97+
inputs=inputs_json,
98+
xlogin=xnat_connect,
99+
timeout=30000,
100+
)
101+
assert status == "Complete", f"Workflow {workflow_id} failed.\n{out_str}"

0 commit comments

Comments
 (0)