diff --git a/docs/changes/latest.inc b/docs/changes/latest.inc index 34176bcf1..7da87db84 100644 --- a/docs/changes/latest.inc +++ b/docs/changes/latest.inc @@ -30,3 +30,6 @@ Bugs API changes ~~~~~~~~~~~ + +- Add ``confounds_format`` parameter to :class:`junifer.datagrabber.PatternDataGrabber` constructor for + improved handling of confounds specified via ``BOLD_confounds`` data type (:gh:`158` by `Synchon Mandal`_). \ No newline at end of file diff --git a/junifer/configs/juseless/datagrabbers/ucla.py b/junifer/configs/juseless/datagrabbers/ucla.py index 443a1257f..cca0957fd 100644 --- a/junifer/configs/juseless/datagrabbers/ucla.py +++ b/junifer/configs/juseless/datagrabbers/ucla.py @@ -107,6 +107,7 @@ class JuselessUCLA(PatternDataGrabber): datadir=datadir, patterns=patterns, replacements=replacements, + confounds_format="fmriprep", ) def get_elements(self) -> List: diff --git a/junifer/datagrabber/aomic/id1000.py b/junifer/datagrabber/aomic/id1000.py index 2266ff04b..14a50d3ce 100644 --- a/junifer/datagrabber/aomic/id1000.py +++ b/junifer/datagrabber/aomic/id1000.py @@ -86,4 +86,5 @@ class DataladAOMICID1000(PatternDataladDataGrabber): uri=uri, patterns=patterns, replacements=replacements, + confounds_format="fmriprep", ) diff --git a/junifer/datagrabber/aomic/piop1.py b/junifer/datagrabber/aomic/piop1.py index abce1404e..d38feea67 100644 --- a/junifer/datagrabber/aomic/piop1.py +++ b/junifer/datagrabber/aomic/piop1.py @@ -116,6 +116,7 @@ class DataladAOMICPIOP1(PatternDataladDataGrabber): uri=uri, patterns=patterns, replacements=replacements, + confounds_format="fmriprep", ) def get_item(self, subject: str, task: str) -> Dict: diff --git a/junifer/datagrabber/aomic/piop2.py b/junifer/datagrabber/aomic/piop2.py index 7fe2212fb..aeae58cb3 100644 --- a/junifer/datagrabber/aomic/piop2.py +++ b/junifer/datagrabber/aomic/piop2.py @@ -113,6 +113,7 @@ class DataladAOMICPIOP2(PatternDataladDataGrabber): uri=uri, patterns=patterns, replacements=replacements, + confounds_format="fmriprep", ) def get_elements(self) -> List: diff --git a/junifer/datagrabber/pattern.py b/junifer/datagrabber/pattern.py index 48a452d29..ee5ef2a55 100644 --- a/junifer/datagrabber/pattern.py +++ b/junifer/datagrabber/pattern.py @@ -7,7 +7,7 @@ import re from pathlib import Path -from typing import Dict, List, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import numpy as np @@ -17,6 +17,10 @@ from .base import BaseDataGrabber from .utils import validate_patterns, validate_replacements +# Accepted formats for confounds specification +_CONFOUNDS_FORMATS = ("fmriprep", "adhoc") + + @register_datagrabber class PatternDataGrabber(BaseDataGrabber): """Concrete implementation for data grabbing using patterns. @@ -35,6 +39,9 @@ class PatternDataGrabber(BaseDataGrabber): Replacements in the patterns for each item in the "element" tuple. datadir : str or pathlib.Path The directory where the data is / will be stored. + confounds_format : {"fmriprep", "adhoc"}, optional + The format of the confounds for the dataset (default None). + """ def __init__( @@ -43,6 +50,7 @@ class PatternDataGrabber(BaseDataGrabber): patterns: Dict[str, str], replacements: Union[List[str], str], datadir: Union[str, Path], + confounds_format: Optional[str] = None, ) -> None: # Validate patterns validate_patterns(types=types, patterns=patterns) @@ -52,6 +60,14 @@ class PatternDataGrabber(BaseDataGrabber): # Validate replacements validate_replacements(replacements=replacements, patterns=patterns) + # Validate confounds format + if confounds_format and confounds_format not in _CONFOUNDS_FORMATS: + raise_error( + "Invalid value for `confounds_format`, should be one of " + f"{_CONFOUNDS_FORMATS}." + ) + self.confounds_format = confounds_format + super().__init__(types=types, datadir=datadir) logger.debug("Initializing PatternDataGrabber") logger.debug(f"\tpatterns = {patterns}") @@ -184,7 +200,21 @@ class PatternDataGrabber(BaseDataGrabber): f"Cannot access {t_type} for {element}: " f"File {t_out} does not exist" ) + # Update path for the element out[t_type] = {"path": t_out} + # Update confounds format for BOLD_confounds + # (if found in the datagrabber) + if t_type == "BOLD_confounds": + if not self.confounds_format: + raise_error( + "`confounds_format` needs to be one of " + f"{_CONFOUNDS_FORMATS}, None provided. " + "As the datagrabber used specifies " + "'BOLD_confounds', None is invalid." + ) + # Set the format + out[t_type].update({"format": self.confounds_format}) + return out def get_elements(self) -> List: diff --git a/junifer/datagrabber/tests/test_pattern.py b/junifer/datagrabber/tests/test_pattern.py index 8d2ec5bc8..41043f6c9 100644 --- a/junifer/datagrabber/tests/test_pattern.py +++ b/junifer/datagrabber/tests/test_pattern.py @@ -259,3 +259,48 @@ def test_PatternDataGrabber(tmp_path: Path) -> None: assert out1["func"]["path"] == out2["func"]["path"] assert out1["anat"]["path"] == out2["anat"]["path"] assert out1["vbm"]["path"] != out2["vbm"]["path"] + + +def test_pattern_data_grabber_confounds_format_error_on_init() -> None: + """Test PatterDataGrabber confounds format error on initialisation.""" + with pytest.raises( + ValueError, match="Invalid value for `confounds_format`" + ): + PatternDataGrabber( + types=["func"], + patterns={"func": "func/{subject}.nii"}, + replacements=["subject"], + datadir="/tmp", + confounds_format="foobar", + ) + + +def test_pattern_data_grabber_confounds_format_error_on_fetch( + tmp_path: Path, +) -> None: + """Test PatterDataGrabber confounds format error on fetching. + + Parameters + ---------- + tmp_path : pathlib.Path + The path to the test directory. + + """ + # Create test directory path + tmpdir = tmp_path / "pattern_dg_test" + # Create final test directory + (tmpdir / "func" / "confounds").mkdir(exist_ok=True, parents=True) + # Create test confound file + (tmpdir / "func" / "confounds" / "sub-001.nii").touch() + # Initialise datagrabber + datagrabber = PatternDataGrabber( + types=["BOLD_confounds"], + patterns={"BOLD_confounds": "func/confounds/{subject}.nii"}, + replacements=["subject"], + datadir=tmpdir, + ) + # Check error on fetch + with pytest.raises( + ValueError, match="As the datagrabber used specifies 'BOLD_confounds'" + ): + datagrabber.get_item(subject="sub-001")