diff --git a/niworkflows/interfaces/bids.py b/niworkflows/interfaces/bids.py index 84c50f10f6e..27277e4ddf3 100644 --- a/niworkflows/interfaces/bids.py +++ b/niworkflows/interfaces/bids.py @@ -260,16 +260,29 @@ class BIDSDataGrabber(SimpleInterface): input_spec = _BIDSDataGrabberInputSpec output_spec = _BIDSDataGrabberOutputSpec - _require_funcs = True - def __init__(self, *args, **kwargs): - anat_only = kwargs.pop('anat_only', None) - anat_derivatives = kwargs.pop('anat_derivatives', None) - require_t1w = kwargs.pop('require_t1w', True) + _image_types = {'asl', 'bold', 'dwi', 'flair', 'fmap', 'pet', 'roi', 'sbref', 't1w', 't2w'} + + def __init__( + self, + anat_only=False, + anat_derivatives=None, + *args, + **kwargs, + ): + default_required = {'t1w', 'bold'} + + for imtype in self._image_types: + kwarg = f'require_{imtype}' + val = kwargs.pop(kwarg, True if imtype in default_required else False) + setattr(self, f'_{kwarg}', val) + + self._require_t1w = self._require_t1w and anat_derivatives is None + if anat_only: + self._require_bold = False + self._require_pet = False + super().__init__(*args, **kwargs) - if anat_only is not None: - self._require_funcs = not anat_only - self._require_t1w = require_t1w and anat_derivatives is None def _run_interface(self, runtime): bids_dict = self.inputs.subject_data @@ -277,19 +290,12 @@ def _run_interface(self, runtime): self._results['out_dict'] = bids_dict self._results.update(bids_dict) - if self._require_t1w and not bids_dict['t1w']: - raise FileNotFoundError( - f'No T1w images found for subject sub-{self.inputs.subject_id}' - ) - - if self._require_funcs and not bids_dict['bold']: - raise FileNotFoundError( - f'No functional images found for subject sub-{self.inputs.subject_id}' - ) - - for imtype in ['bold', 't2w', 'flair', 'fmap', 'sbref', 'roi', 'pet', 'asl']: + for imtype in self._image_types: if not bids_dict[imtype]: - LOGGER.info('No "%s" images found for sub-%s', imtype, self.inputs.subject_id) + msg = f'No "{imtype}" images found for sub-{self.inputs.subject_id}' + if getattr(self, f'_require_{imtype}', False): # only raise if image is required + raise FileNotFoundError(msg) + LOGGER.info(msg) return runtime diff --git a/niworkflows/interfaces/tests/test_bids.py b/niworkflows/interfaces/tests/test_bids.py index 5da592f8ece..9d6b16ef15a 100644 --- a/niworkflows/interfaces/tests/test_bids.py +++ b/niworkflows/interfaces/tests/test_bids.py @@ -798,11 +798,61 @@ def test_fsdir_min_version(tmp_path, min_version): def test_BIDSDataGrabber(): x = bintfs.BIDSDataGrabber(anat_only=True) assert x._require_t1w is True - assert x._require_funcs is False + assert x._require_bold is False x = bintfs.BIDSDataGrabber(anat_only=False, require_t1w=False) assert x._require_t1w is False - assert x._require_funcs is True + assert x._require_bold is True x = bintfs.BIDSDataGrabber(anat_derivatives='derivatives') assert x._require_t1w is False + + +def test_require_bold_pet_behavior(): + import pytest + + from niworkflows.interfaces.bids import BIDSDataGrabber + + subject_data_pet = { + 't1w': ['t1.nii'], + 'bold': [], + 'pet': ['pet.nii'], + 't2w': [], + 'flair': [], + 'fmap': [], + 'sbref': [], + 'roi': [], + 'asl': [], + 'dwi': [], + } + + subject_data_bold = { + 't1w': ['t1.nii'], + 'bold': ['bold.nii'], + 'pet': [], + 't2w': [], + 'flair': [], + 'fmap': [], + 'sbref': [], + 'roi': [], + 'asl': [], + 'dwi': [], + } + + # PET present, functional not required + grabber_pet = BIDSDataGrabber( + subject_data=subject_data_pet, subject_id='01', require_pet=True, require_bold=False + ) + assert grabber_pet._require_bold is False + assert grabber_pet._require_pet is True + grabber_pet.run() # Should succeed without bold data + + # PET absent, functional required by default + grabber_func = BIDSDataGrabber(subject_data=subject_data_bold, subject_id='01') + assert grabber_func._require_bold is True + assert grabber_func.run().outputs.bold == 'bold.nii' + + # Fail when bold is required but missing + grabber_fail = BIDSDataGrabber(subject_data=subject_data_pet, subject_id='01') + with pytest.raises(FileNotFoundError, match='No "bold" images found'): + grabber_fail.run() diff --git a/niworkflows/utils/bids.py b/niworkflows/utils/bids.py index 573d7483389..5d9fab3d3c2 100644 --- a/niworkflows/utils/bids.py +++ b/niworkflows/utils/bids.py @@ -34,7 +34,11 @@ from packaging.version import Version DEFAULT_BIDS_QUERIES = { - 'bold': {'datatype': 'func', 'suffix': 'bold', 'part': ['mag', None]}, + 'bold': { + 'datatype': 'func', + 'suffix': 'bold', + 'part': ['mag', None], + }, 'dwi': {'suffix': 'dwi'}, 'flair': {'datatype': 'anat', 'suffix': 'FLAIR', 'part': ['mag', None]}, 'fmap': {'datatype': 'fmap'}, @@ -272,7 +276,7 @@ def collect_data( del layout_get_kwargs[entity] if task: - queries['bold']['task'] = task + queries['bold']['task'] = queries['pet']['task'] = task if echo: queries['bold']['echo'] = echo