Skip to content

Refactor of fMRIPrep to PETPrep #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 19 additions & 5 deletions fmriprep/cli/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,16 @@ def _slice_time_ref(value, parser):
raise parser.error(f'Slice time reference must be in range 0-1. Received {value}.')
return value

def _reference_frame(value, parser):
if value == 'average':
return 'average'
try:
return int(value)
except ValueError:
raise parser.error(
"Reference frame must be an integer index or 'average'."
) from None

verstr = f'PETPrep v{config.environment.version}'
currentv = Version(config.environment.version)
is_release = not any((currentv.is_devrelease, currentv.is_prerelease, currentv.is_postrelease))
Expand All @@ -158,6 +168,7 @@ def _slice_time_ref(value, parser):
IsFile = partial(_is_file, parser=parser)
PositiveInt = partial(_min_one, parser=parser)
BIDSFilter = partial(_bids_filter, parser=parser)
ReferenceFrame = partial(_reference_frame, parser=parser)

# Arguments as specified by BIDS-Apps
# required, positional arguments
Expand Down Expand Up @@ -377,12 +388,15 @@ def _slice_time_ref(value, parser):
help='Deprecated - use `--force no-bbr` instead.',
)
g_conf.add_argument(
'--dummy-scans',
required=False,
'--reference-frame',
action='store',
default=None,
type=int,
help='Number of nonsteady-state volumes. Overrides automatic detection.',
dest='reference_frame',
type=ReferenceFrame,
default='average',
help=(
"Reference frame index (0-based) for PET preprocessing. "
"Use 'average' to compute the standard averaged reference."
),
)
g_conf.add_argument(
'--random-seed',
Expand Down
5 changes: 3 additions & 2 deletions fmriprep/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,8 +556,6 @@ class workflow(_Config):
"""Initial transform for PET-to-anatomical registration."""
cifti_output = None
"""Generate HCP Grayordinates, accepts either ``'91k'`` (default) or ``'170k'``."""
dummy_scans = None
"""Set a number of initial scans to be considered nonsteady states."""
hires = None
"""Run FreeSurfer ``recon-all`` with the ``-hires`` flag."""
fs_no_resume = None
Expand Down Expand Up @@ -594,7 +592,10 @@ class workflow(_Config):
spaces = None
"""Keeps the :py:class:`~niworkflows.utils.spaces.SpatialReferences`
instance keeping standard and nonstandard spaces."""
reference_frame: int | str | None = None
"""Selected frame index for PET reference generation.

``None`` or ``'average'`` retains the current averaging behavior."""

class loggers:
"""Keep loggers easily accessible (see :py:func:`init`)."""
Expand Down
12 changes: 0 additions & 12 deletions fmriprep/interfaces/confounds.py
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,6 @@ class GatherConfoundsInputSpec(BaseInterfaceInputSpec):
std_dvars = File(exists=True, desc='file containing standardized DVARS')
fd = File(exists=True, desc='input framewise displacement')
rmsd = File(exists=True, desc='input RMS framewise displacement')
tcompcor = File(exists=True, desc='input tCompCorr')
acompcor = File(exists=True, desc='input aCompCorr')
crowncompcor = File(exists=True, desc='input crown-based regressors')
cos_basis = File(exists=True, desc='input cosine basis')
motion = File(exists=True, desc='input motion parameters')

Expand Down Expand Up @@ -379,9 +376,6 @@ def _run_interface(self, runtime):
std_dvars=self.inputs.std_dvars,
fdisp=self.inputs.fd,
rmsd=self.inputs.rmsd,
tcompcor=self.inputs.tcompcor,
acompcor=self.inputs.acompcor,
crowncompcor=self.inputs.crowncompcor,
cos_basis=self.inputs.cos_basis,
motion=self.inputs.motion,
newpath=runtime.cwd,
Expand All @@ -397,9 +391,6 @@ def _gather_confounds(
std_dvars=None,
fdisp=None,
rmsd=None,
tcompcor=None,
acompcor=None,
crowncompcor=None,
cos_basis=None,
motion=None,
newpath=None,
Expand Down Expand Up @@ -449,9 +440,6 @@ def _adjust_indices(left_df, right_df):
(dvars, 'DVARS'),
(fdisp, 'Framewise displacement'),
(rmsd, 'Framewise displacement (RMS)'),
(tcompcor, 'tCompCor'),
(acompcor, 'aCompCor'),
(crowncompcor, 'crownCompCor'),
(cos_basis, 'Cosine basis'),
(motion, 'Motion parameters'),
):
Expand Down
4 changes: 2 additions & 2 deletions fmriprep/interfaces/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,10 @@ def _generate_segment(self):
t2w_seg = f'(+ {len(self.inputs.t2w):d} T2-weighted)'

# Add list of tasks with number of runs
pet_series = self.inputs.bold or []
pet_series = self.inputs.pet or []

counts = Counter(
BIDS_NAME.search(series).groupdict().get('task_id', 'task-<none>')[5:]
(BIDS_NAME.search(series).groupdict().get('task_id') or 'task-<none>')[5:]
for series in pet_series
)

Expand Down
22 changes: 22 additions & 0 deletions fmriprep/interfaces/tests/test_reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,25 @@
)
def test_get_world_pedir(tmpdir, orientation, pe_dir, expected):
assert get_world_pedir(orientation, pe_dir) == expected


def test_subject_summary_handles_missing_task(tmp_path):
from ..reports import SubjectSummary

t1w = tmp_path / 'sub-01_T1w.nii.gz'
t1w.write_text('')
pet1 = tmp_path / 'sub-01_task-rest_run-01_pet.nii.gz'
pet1.write_text('')
pet2 = tmp_path / 'sub-01_run-01_pet.nii.gz'
pet2.write_text('')

summary = SubjectSummary(
t1w=[str(t1w)],
pet=[str(pet1), str(pet2)],
std_spaces=[],
nstd_spaces=[],
)

segment = summary._generate_segment()
assert 'Task: rest (1 run)' in segment
assert 'Task: <none> (1 run)' in segment
10 changes: 8 additions & 2 deletions fmriprep/workflows/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,14 +199,20 @@ def init_single_subject_wf(subject_id: str):
### References

"""
from niworkflows.utils.bids import DEFAULT_BIDS_QUERIES
import copy

queries = copy.deepcopy(DEFAULT_BIDS_QUERIES)
queries['t1w'].pop('datatype', None)

subject_data = collect_data(
config.execution.layout,
config.execution.bids_dir,
subject_id,
task=config.execution.task_id,
bids_filters=config.execution.bids_filters,
queries=queries
)[0]


if 'flair' in config.workflow.ignore:
subject_data['flair'] = []
if 't2w' in config.workflow.ignore:
Expand Down
3 changes: 0 additions & 3 deletions fmriprep/workflows/pet/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,7 +543,6 @@ def init_pet_wf(
('outputnode.petref', 'inputnode.petref'),
('outputnode.motion_xfm', 'inputnode.motion_xfm'),
('outputnode.petref2anat_xfm', 'inputnode.petref2anat_xfm'),
('outputnode.dummy_scans', 'inputnode.skip_vols'),
]),
(pet_native_wf, pet_confounds_wf, [
('outputnode.pet_native', 'inputnode.pet'),
Expand Down Expand Up @@ -575,7 +574,6 @@ def _last(inlist):
('mni2009c2anat_xfm', 'inputnode.std2anat_xfm'),
]),
(pet_fit_wf, carpetplot_wf, [
('outputnode.dummy_scans', 'inputnode.dummy_scans'),
('outputnode.pet_mask', 'inputnode.pet_mask'),
('outputnode.petref2anat_xfm', 'inputnode.petref2anat_xfm'),
]),
Expand All @@ -585,7 +583,6 @@ def _last(inlist):
(pet_confounds_wf, carpetplot_wf, [
('outputnode.confounds_file', 'inputnode.confounds_file'),
('outputnode.crown_mask', 'inputnode.crown_mask'),
(('outputnode.acompcor_masks', _last), 'inputnode.acompcor_mask'),
]),
]) # fmt:skip

Expand Down
Loading
Loading