diff --git a/docs/changes/newsfragments/252.change b/docs/changes/newsfragments/252.change new file mode 100644 index 000000000..70bf6bd8d --- /dev/null +++ b/docs/changes/newsfragments/252.change @@ -0,0 +1 @@ +Add ``native_t1w`` parameter to :class:`.DataladAOMICID1000`, :class:`.DataladAOMICPIOP1`, :class:`.DataladAOMICPIOP2`, enabling fetching of T1w data in subject-native space by `Synchon Mandal`_ diff --git a/docs/changes/newsfragments/252.feature b/docs/changes/newsfragments/252.feature new file mode 100644 index 000000000..57f767c22 --- /dev/null +++ b/docs/changes/newsfragments/252.feature @@ -0,0 +1 @@ +Add support for subject-native space by `Synchon Mandal`_ and `Fede Raimondo`_ diff --git a/junifer/datagrabber/aomic/id1000.py b/junifer/datagrabber/aomic/id1000.py index a955f6b8a..e8b4af1cb 100644 --- a/junifer/datagrabber/aomic/id1000.py +++ b/junifer/datagrabber/aomic/id1000.py @@ -28,6 +28,8 @@ class DataladAOMICID1000(PatternDataladDataGrabber): "probseg_WM", "DWI"} or a list of the options, optional AOMIC data types. If None, all available data types are selected. (default None). + native_t1w : bool, optional + Whether to use T1w in native space (default False). """ @@ -35,6 +37,7 @@ class DataladAOMICID1000(PatternDataladDataGrabber): self, datadir: Union[str, Path, None] = None, types: Union[str, List[str], None] = None, + native_t1w: bool = False, ) -> None: # The patterns patterns = { @@ -84,6 +87,27 @@ class DataladAOMICID1000(PatternDataladDataGrabber): "sub-{subject}_desc-preproc_dwi.nii.gz" ), } + # Use native T1w assets + self.native_t1w = False + if native_t1w: + self.native_t1w = True + patterns.update( + { + "T1w": ( + "derivatives/fmriprep/sub-{subject}/anat/" + "sub-{subject}_desc-preproc_T1w.nii.gz" + ), + "T1w_mask": ( + "derivatives/fmriprep/sub-{subject}/anat/" + "sub-{subject}_desc-brain_mask.nii.gz" + ), + "Warp": ( + "derivatives/fmriprep/sub-{subject}/anat/" + "sub-{subject}_from-MNI152NLin2009cAsym_to-T1w_" + "mode-image_xfm.h5" + ), + } + ) # Set default types if types is None: types = list(patterns.keys()) @@ -126,5 +150,8 @@ class DataladAOMICID1000(PatternDataladDataGrabber): if out.get("T1w"): out["T1w"]["mask_item"] = "T1w_mask" # Add space information - out["T1w"].update({"space": "native"}) + if self.native_t1w: + out["T1w"].update({"space": "native"}) + else: + out["T1w"].update({"space": "MNI152NLin2009cAsym"}) return out diff --git a/junifer/datagrabber/aomic/piop1.py b/junifer/datagrabber/aomic/piop1.py index 88a59005c..888d1511c 100644 --- a/junifer/datagrabber/aomic/piop1.py +++ b/junifer/datagrabber/aomic/piop1.py @@ -34,6 +34,8 @@ class DataladAOMICPIOP1(PatternDataladDataGrabber): "gstroop", "workingmemory"} or list of the options, optional AOMIC PIOP1 task sessions. If None, all available task sessions are selected (default None). + native_t1w : bool, optional + Whether to use T1w in native space (default False). """ @@ -42,6 +44,7 @@ class DataladAOMICPIOP1(PatternDataladDataGrabber): datadir: Union[str, Path, None] = None, types: Union[str, List[str], None] = None, tasks: Union[str, List[str], None] = None, + native_t1w: bool = False, ) -> None: # Declare all tasks all_tasks = [ @@ -114,6 +117,27 @@ class DataladAOMICPIOP1(PatternDataladDataGrabber): "sub-{subject}_desc-preproc_dwi.nii.gz" ), } + # Use native T1w assets + self.native_t1w = False + if native_t1w: + self.native_t1w = True + patterns.update( + { + "T1w": ( + "derivatives/fmriprep/sub-{subject}/anat/" + "sub-{subject}_desc-preproc_T1w.nii.gz" + ), + "T1w_mask": ( + "derivatives/fmriprep/sub-{subject}/anat/" + "sub-{subject}_desc-brain_mask.nii.gz" + ), + "Warp": ( + "derivatives/fmriprep/sub-{subject}/anat/" + "sub-{subject}_from-MNI152NLin2009cAsym_to-T1w_" + "mode-image_xfm.h5" + ), + } + ) # Set default types if types is None: types = list(patterns.keys()) @@ -171,7 +195,10 @@ class DataladAOMICPIOP1(PatternDataladDataGrabber): if out.get("T1w"): out["T1w"]["mask_item"] = "T1w_mask" # Add space information - out["T1w"].update({"space": "native"}) + if self.native_t1w: + out["T1w"].update({"space": "native"}) + else: + out["T1w"].update({"space": "MNI152NLin2009cAsym"}) return out def get_elements(self) -> List: diff --git a/junifer/datagrabber/aomic/piop2.py b/junifer/datagrabber/aomic/piop2.py index a973d93e2..00b55f076 100644 --- a/junifer/datagrabber/aomic/piop2.py +++ b/junifer/datagrabber/aomic/piop2.py @@ -34,6 +34,8 @@ class DataladAOMICPIOP2(PatternDataladDataGrabber): or list of the options, optional AOMIC PIOP2 task sessions. If None, all available task sessions are selected (default None). + native_t1w : bool, optional + Whether to use T1w in native space (default False). """ @@ -42,6 +44,7 @@ class DataladAOMICPIOP2(PatternDataladDataGrabber): datadir: Union[str, Path, None] = None, types: Union[str, List[str], None] = None, tasks: Union[str, List[str], None] = None, + native_t1w: bool = False, ) -> None: # Declare all tasks all_tasks = [ @@ -111,6 +114,27 @@ class DataladAOMICPIOP2(PatternDataladDataGrabber): "sub-{subject}_desc-preproc_dwi.nii.gz" ), } + # Use native T1w assets + self.native_t1w = False + if native_t1w: + self.native_t1w = True + patterns.update( + { + "T1w": ( + "derivatives/fmriprep/sub-{subject}/anat/" + "sub-{subject}_desc-preproc_T1w.nii.gz" + ), + "T1w_mask": ( + "derivatives/fmriprep/sub-{subject}/anat/" + "sub-{subject}_desc-brain_mask.nii.gz" + ), + "Warp": ( + "derivatives/fmriprep/sub-{subject}/anat/" + "sub-{subject}_from-MNI152NLin2009cAsym_to-T1w_" + "mode-image_xfm.h5" + ), + } + ) # Set default types if types is None: types = list(patterns.keys()) @@ -171,5 +195,8 @@ class DataladAOMICPIOP2(PatternDataladDataGrabber): if out.get("T1w"): out["T1w"]["mask_item"] = "T1w_mask" # Add space information - out["T1w"].update({"space": "native"}) + if self.native_t1w: + out["T1w"].update({"space": "native"}) + else: + out["T1w"].update({"space": "MNI152NLin2009cAsym"}) return out diff --git a/junifer/datagrabber/hcp1200/hcp1200.py b/junifer/datagrabber/hcp1200/hcp1200.py index 351ad2b9d..df5ec43a1 100644 --- a/junifer/datagrabber/hcp1200/hcp1200.py +++ b/junifer/datagrabber/hcp1200/hcp1200.py @@ -98,7 +98,7 @@ class HCP1200(PatternDataGrabber): ) suffix = "_hp2000_clean" if ica_fix else "" # The types of data - types = ["BOLD"] + types = ["BOLD", "T1w", "Warp"] # The patterns patterns = { "BOLD": ( @@ -106,7 +106,9 @@ class HCP1200(PatternDataGrabber): "{task}_{phase_encoding}/" "{task}_{phase_encoding}" f"{suffix}.nii.gz" - ) + ), + "T1w": "{subject}/T1w/T1w_acpc_dc_restore.nii.gz", + "Warp": "{subject}/MNINonLinear/xfms/standard2acpc_dc.nii.gz", } # The replacements replacements = ["subject", "task", "phase_encoding"] @@ -147,6 +149,12 @@ class HCP1200(PatternDataGrabber): out = super().get_item( subject=subject, task=new_task, phase_encoding=phase_encoding ) + # Add space for BOLD data type + if "BOLD" in out: + out["BOLD"].update({"space": "MNI152NLin6Asym"}) + # Add space for T1w data type + if "T1w" in out: + out["T1w"].update({"space": "native"}) return out def get_elements(self) -> List: diff --git a/junifer/datareader/default.py b/junifer/datareader/default.py index c2a486e93..6cc159e95 100644 --- a/junifer/datareader/default.py +++ b/junifer/datareader/default.py @@ -104,6 +104,10 @@ class DefaultDataReader(PipelineStepMixin, UpdateMetaMixin): params = {} # For each type of data, try to read it for type_ in input.keys(): + # Skip Warp data type + if type_ == "Warp": + continue + # Check for malformed datagrabber specification if "path" not in input[type_]: warn_with_log( diff --git a/tools/create_hcp1200_example_dataset.py b/tools/create_hcp1200_example_dataset.py index 5a3bd0637..0759cbb9b 100644 --- a/tools/create_hcp1200_example_dataset.py +++ b/tools/create_hcp1200_example_dataset.py @@ -4,8 +4,8 @@ # License: AGPL from itertools import product -from tempfile import TemporaryDirectory from pathlib import Path +from tempfile import TemporaryDirectory import datalad.api as dl @@ -15,7 +15,6 @@ DST = "git@gin.g-node.org:/juaml/datalad-example-hcp1200.git" if __name__ == "__main__": - with TemporaryDirectory() as tmpdir: # Convert str to Path tmpdir_path = Path(tmpdir) @@ -29,7 +28,30 @@ if __name__ == "__main__": # Create subject directory subdir.mkdir() - for (task, phase_encoding) in product( + # T1w data + # Set subject data directory + sub_t1w_datadir = subdir / "T1w" + # Create subject data directory + sub_t1w_datadir.mkdir(parents=True) + # Set subject data file + sub_t1w_datafile = sub_t1w_datadir / "T1w_acpc_dc_restore.nii.gz" + # Write subject data file + with open(sub_t1w_datafile, "w") as f: + f.write("placeholder") + + # Warp data + # Set subject data directory + sub_warp_datadir = subdir / "MNINonLinear" / "xfms" + # Create subject data directory + sub_warp_datadir.mkdir(parents=True) + # Set subject data file + sub_warp_datafile = sub_warp_datadir / "standard2acpc_dc.nii.gz" + # Write subject data file + with open(sub_warp_datafile, "w") as f: + f.write("placeholder") + + # BOLD data + for task, phase_encoding in product( [ "REST1", "REST2", @@ -49,32 +71,31 @@ if __name__ == "__main__": else: new_task = f"tfMRI_{task}" # Set subject data directory - sub_datadir = ( + sub_bold_datadir = ( subdir / "MNINonLinear" / "Results" / f"{new_task}_{phase_encoding}" ) # Create subject data directory - sub_datadir.mkdir(parents=True) + sub_bold_datadir.mkdir(parents=True) # Set subject data file - sub_datafile = ( - sub_datadir - / f"{new_task}_{phase_encoding}.nii.gz" + sub_bold_datafile = ( + sub_bold_datadir / f"{new_task}_{phase_encoding}.nii.gz" ) # Create subject data file - with open(sub_datafile, "w") as f: + with open(sub_bold_datafile, "w") as f: f.write("placeholder") if "REST" in task: # Set subject data file with ICA+FIX - sub_datafile = ( - sub_datadir + sub_bold_datafile = ( + sub_bold_datadir / f"{new_task}_{phase_encoding}_hp2000_clean.nii.gz" ) # Create subject data file with ICA+FIX - with open(sub_datafile, "w") as f: + with open(sub_bold_datafile, "w") as f: f.write("placeholder") # Save datalad dataset