Skip to content

Commit 8f4bd9d

Browse files
authored
52 pipeline should halt if bad t1ws are present or more generally if not bids valid (#54)
* added checks and error messages for too many dimension t1w's * fixed logic for excluded subjects * fixed bug where missing t1w's wouldn't halt pipeline * format with black * added validator check * added tests for no t1w and invalid bids dir A t1w file not found error would get caught by a try except that's now been fixed by tracking all not found t1w errors and halting the pipeline while alerting the user. Added full bids validator check using pip installable bids deno validator too.
1 parent 5c65a98 commit 8f4bd9d

File tree

6 files changed

+160
-6
lines changed

6 files changed

+160
-6
lines changed

data/participants.tsv

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
participant_id height weight age gender
2-
sub-01 178 58 28 male
2+
sub-01 178 58 28 male

petdeface/petdeface.py

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,12 @@
3131
from mideface import ApplyMideface
3232
from mideface import Mideface
3333
from pet import WeightedAverage
34+
from utils import run_validator
3435
except ModuleNotFoundError:
3536
from .mideface import ApplyMideface
3637
from .mideface import Mideface
3738
from .pet import WeightedAverage
39+
from .utils import run_validator
3840

3941

4042
# collect version from pyproject.toml
@@ -207,6 +209,10 @@ def deface(args: Union[dict, argparse.Namespace]) -> None:
207209
else:
208210
args = args
209211

212+
# first check to see if the dataset is bids valid
213+
if not args.skip_bids_validator:
214+
run_validator(args.bids_dir)
215+
210216
if not check_valid_fs_license() and not locate_freesurfer_license().exists():
211217
raise Exception("You need a valid FreeSurfer license to proceed!")
212218

@@ -255,6 +261,8 @@ def deface(args: Union[dict, argparse.Namespace]) -> None:
255261

256262
petdeface_wf = Workflow(name="petdeface_wf", base_dir=output_dir)
257263

264+
missing_file_errors = []
265+
258266
for subject_id in subjects:
259267
try:
260268
single_subject_wf = init_single_subject_wf(
@@ -265,12 +273,20 @@ def deface(args: Union[dict, argparse.Namespace]) -> None:
265273
session_label=args.session_label,
266274
session_label_exclude=args.session_label_exclude,
267275
)
268-
except FileNotFoundError:
276+
except FileNotFoundError as error:
269277
single_subject_wf = None
270-
278+
missing_file_errors.append(str(error))
271279
if single_subject_wf:
272280
petdeface_wf.add_nodes([single_subject_wf])
273281

282+
if (
283+
missing_file_errors
284+
): # todo add conditional later for cases where a template t1w is used
285+
raise FileNotFoundError(
286+
"The following subjects are missing t1w images:\n"
287+
+ "\n".join(missing_file_errors)
288+
)
289+
274290
try:
275291
petdeface_wf.write_graph("petdeface.dot", graph2use="colored", simple_form=True)
276292
except OSError as Err:
@@ -748,7 +764,7 @@ def __init__(
748764
anat_only=False,
749765
subject="",
750766
n_procs=2,
751-
skip_bids_validator=True,
767+
skip_bids_validator=False,
752768
remove_existing=True,
753769
placement="adjacent",
754770
preview_pics=True,

petdeface/utils.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import subprocess
2+
from pathlib import Path
3+
import json
4+
import sys
5+
6+
7+
class InvalidBIDSDataset(Exception):
8+
def __init__(self, message, errors):
9+
super().__init__(message)
10+
self.errors = errors
11+
print(f"{message}\n{errors}")
12+
13+
14+
def deno_validator_installed():
15+
get_help = subprocess.run(
16+
"bids-validator-deno --help", shell=True, capture_output=True
17+
)
18+
if get_help.returncode == 0:
19+
return True
20+
else:
21+
return False
22+
23+
24+
def run_validator(bids_path):
25+
bids_path = Path(bids_path)
26+
if bids_path.exists():
27+
pass
28+
else:
29+
raise FileNotFoundError(bids_path)
30+
if deno_validator_installed():
31+
command = f"bids-validator-deno {bids_path.resolve()} --ignoreWarnings --json --no-color"
32+
run_validator = subprocess.run(command, shell=True, capture_output=True)
33+
json_output = json.loads(run_validator.stdout.decode("utf-8"))
34+
# since we've ignored warnings any issue in issue is an error
35+
issues = json_output.get("issues").get("issues")
36+
formatted_errors = ""
37+
for issue in issues:
38+
formatted_errors += "\n" + json.dumps(issue, indent=4)
39+
if formatted_errors != "":
40+
raise InvalidBIDSDataset(
41+
message=f"Dataset at {bids_path} is invalid, see:",
42+
errors=formatted_errors,
43+
)
44+
45+
else:
46+
raise Exception(
47+
f"bids-validator-deno not found"
48+
+ "\nskip validation with --skip_bids_validator"
49+
+ "\nor install with pip install bids-validator-deno"
50+
)

poetry.lock

Lines changed: 34 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "petdeface"
3-
version = "0.2.2"
3+
version = "0.2.3"
44
description = "A nipype PET and MR defacing pipeline for BIDS datasets utilizing FreeSurfer's MiDeFace."
55
authors = ["Martin Nørgaard <[email protected]>", "Anthony Galassi <[email protected]>", "Murat Bilgel <[email protected]>"]
66
license = "MIT"
@@ -19,6 +19,9 @@ python = ">=3.10, <4.0"
1919
setuptools = "^68.1.2"
2020
petutils = "^0.0.1"
2121
niworkflows = "^1.11.0"
22+
niftifixer = {git = "https://github.com/openneuropet/nifti_fixer.git"}
23+
bids-validator-deno = "^2.0.5"
24+
2225

2326
[tool.poetry.group.dev.dependencies]
2427
black = "^23.7.0"

tests/test_dir_layouts.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import shutil
44
import bids
55
from petdeface.petdeface import PetDeface
6+
from petdeface.utils import InvalidBIDSDataset
67
from os import cpu_count
78
from bids.layout import BIDSLayout
89
import subprocess
@@ -34,6 +35,7 @@ def test_anat_in_first_session_folder():
3435
/ "derivatives"
3536
/ "petdeface",
3637
n_procs=nthreads,
38+
preview_pics=False,
3739
)
3840
petdeface.run()
3941

@@ -80,6 +82,7 @@ def test_anat_in_each_session_folder():
8082
/ "derivatives"
8183
/ "petdeface",
8284
n_procs=nthreads,
85+
preview_pics=False,
8386
)
8487
petdeface.run()
8588

@@ -117,5 +120,54 @@ def test_anat_in_subject_folder():
117120
/ "derivatives"
118121
/ "petdeface",
119122
n_procs=nthreads,
123+
preview_pics=False,
120124
)
121125
petdeface.run()
126+
127+
def test_no_anat():
128+
# create a temporary directory to copy the existing dataset into
129+
with tempfile.TemporaryDirectory() as tmpdir:
130+
shutil.copytree(data_dir, Path(tmpdir) / "no_anat")
131+
132+
subject_folder = Path(tmpdir) / "no_anat" / "sub-01"
133+
# next we delete the anat fold in the subject folder
134+
shutil.rmtree(subject_folder / "ses-baseline" / "anat")
135+
136+
# run petdeface on the copied dataset
137+
petdeface = PetDeface(
138+
Path(tmpdir) / "no_anat",
139+
output_dir=Path(tmpdir)
140+
/ "no_anat_defaced"
141+
/ "derivatives"
142+
/ "petdeface",
143+
n_procs=nthreads,
144+
)
145+
146+
# now we want to assert that this pipeline crashes and print the error
147+
with pytest.raises(FileNotFoundError) as exc_info:
148+
petdeface.run()
149+
150+
def test_invalid_bids():
151+
with tempfile.TemporaryDirectory() as tmpdir:
152+
shutil.copytree(data_dir, Path(tmpdir) / "invalid")
153+
# rename the files in the pet folder to a different subject id
154+
subject_folder = Path(tmpdir) / "invalid" / "sub-01"
155+
pet_folder = subject_folder / "ses-baseline" / "pet"
156+
for file in pet_folder.glob("sub-01_*"):
157+
shutil.move(
158+
file,
159+
pet_folder / file.name.replace("sub-01", "sub-01-bestsubject")
160+
)
161+
162+
# run petdeface on the invalid dataset
163+
petdeface = PetDeface(
164+
Path(tmpdir) / "invalid",
165+
output_dir=Path(tmpdir) / "invalid_defaced" / "derivatives" / "petdeface",
166+
n_procs=nthreads,
167+
)
168+
169+
# Run it and see what error gets raised
170+
with pytest.raises(InvalidBIDSDataset) as exc_info:
171+
petdeface.run()
172+
assert "Dataset at" in str(exc_info.value)
173+

0 commit comments

Comments
 (0)