diff --git a/dcm2bids/acquisition.py b/dcm2bids/acquisition.py index cda538cc..8ed77c4b 100644 --- a/dcm2bids/acquisition.py +++ b/dcm2bids/acquisition.py @@ -26,8 +26,8 @@ def __init__( participant, dataType, modalityLabel, - indexSidecar=None, customLabels="", + id=None, srcSidecar=None, sidecarChanges=None, intendedFor=None, @@ -38,8 +38,8 @@ def __init__( self._modalityLabel = "" self._customLabels = "" + self._id = "" self._intendedFor = None - self._indexSidecar = None self.participant = participant self.dataType = dataType @@ -57,6 +57,11 @@ def __init__( else: self.intendedFor = intendedFor + if id is None: + self.id = None + else: + self.id = id + self.dstFile = '' def __eq__(self, other): @@ -79,6 +84,18 @@ def modalityLabel(self, modalityLabel): """ Prepend '_' if necessary""" self._modalityLabel = self.prepend(modalityLabel) + @property + def id(self): + """ + Returns: + A string '_' + """ + return self._id + + @id.setter + def id(self, value): + self._id = value + @property def customLabels(self): """ @@ -191,23 +208,7 @@ def intendedFor(self, value): else: self._intendedFor = [value] - @property - def indexSidecar(self): - """ - Returns: - A int '_' - """ - return self._indexSidecar - - @indexSidecar.setter - def indexSidecar(self, value): - """ - Returns: - A int '_' - """ - self._indexSidecar = value - - def dstSidecarData(self, descriptions, intendedForList): + def dstSidecarData(self, intendedForList): """ """ data = self.srcSidecar.origData @@ -218,12 +219,14 @@ def dstSidecarData(self, descriptions, intendedForList): intendedValue = [] for index in self.intendedFor: - intendedValue = intendedValue + intendedForList[index] - - if len(intendedValue) == 1: - data["IntendedFor"] = intendedValue[0] - else: - data["IntendedFor"] = intendedValue + if index in intendedForList: + intendedValue = intendedValue + [intendedForList[index]] + else: + logging.warning(f"No id found for IntendedFor value '{index}'.") + logging.warning(f"No sidecar changes for field IntendedFor will be made for json file {self.dstFile}.json with this id.") + logging.warning("Check: https://unfmontreal.github.io/Dcm2Bids/docs/how-to/create-config-file/#id-and-intendedFor.\n") + + data["IntendedFor"] = [item for sublist in intendedValue for item in sublist] # sidecarChanges for key, value in self.sidecarChanges.items(): diff --git a/dcm2bids/cli/dcm2bids.py b/dcm2bids/cli/dcm2bids.py index f0ee9a5a..e3c193ee 100644 --- a/dcm2bids/cli/dcm2bids.py +++ b/dcm2bids/cli/dcm2bids.py @@ -8,10 +8,10 @@ import argparse from dcm2bids.dcm2bids_gen import Dcm2BidsGen -from dcm2bids.utils.tools import check_latest from dcm2bids.utils.utils import DEFAULT from dcm2bids.version import __version__ + def _build_arg_parser(): p = argparse.ArgumentParser(description=__doc__, epilog=DEFAULT.doc, formatter_class=argparse.RawTextHelpFormatter) @@ -71,9 +71,6 @@ def main(): parser = _build_arg_parser() args = parser.parse_args() - check_latest() - check_latest("dcm2niix") - app = Dcm2BidsGen(**vars(args)) return app.run() diff --git a/dcm2bids/dcm2bids_gen.py b/dcm2bids/dcm2bids_gen.py index 94b97951..a44a2800 100644 --- a/dcm2bids/dcm2bids_gen.py +++ b/dcm2bids/dcm2bids_gen.py @@ -4,7 +4,6 @@ Reorganising NIfTI files from dcm2niix into the Brain Imaging Data Structure """ -import argparse import logging import os from pathlib import Path @@ -22,6 +21,7 @@ from dcm2bids.utils.tools import check_latest, dcm2niix_version from dcm2bids.version import __version__ + class Dcm2BidsGen(object): """ Object to handle dcm2bids execution steps @@ -75,7 +75,6 @@ def __init__( self.logger.info("BIDS directory: %s", os.path.realpath(output_dir)) self.logger.info("Validate BIDS: %s", self.bids_validate) - @property def dicomDirs(self): """List of DICOMs directories""" @@ -133,7 +132,7 @@ def run(self): self.logger.info("moving acquisitions into BIDS folder") - intendedForList = [[] for i in range(len(parser.descriptions))] + intendedForList = {} for acq in parser.acquisitions: acq.setDstFile() intendedForList = self.move(acq, intendedForList) @@ -147,12 +146,11 @@ def run(self): except: self.logger.info("The bids-validator does not seem to work properly. " "The bids-validator may not been installed on your computer. " - f"Please check: https://github.com/bids-standard/bids-validator#quickstart.") + "Please check: https://github.com/bids-standard/bids-validator#quickstart.") def move(self, acquisition, intendedForList): """Move an acquisition to BIDS format""" for srcFile in glob(acquisition.srcRoot + ".*"): - ext = Path(srcFile).suffixes ext = [curr_ext for curr_ext in ext if curr_ext in ['.nii', '.gz', '.json', @@ -173,8 +171,15 @@ def move(self, acquisition, intendedForList): self.logger.info("Use --clobber option to overwrite") continue + # Populate intendedFor + if '.nii' in ext: + if acquisition.id in intendedForList: + intendedForList[acquisition.id].append(acquisition.dstIntendedFor + "".join(ext)) + else: + intendedForList[acquisition.id] = [acquisition.dstIntendedFor + "".join(ext)] + # it's an anat nifti file and the user using a deface script - if (self.config.get("defaceTpl") and acquisition.dataType == "func" and ".nii" in ext): + if (self.config.get("defaceTpl") and acquisition.dataType == "anat" and ".nii" in ext): try: os.remove(dstFile) except FileNotFoundError: @@ -185,11 +190,8 @@ def move(self, acquisition, intendedForList): cmd = [w.replace('dstFile', dstFile) for w in defaceTpl] run_shell_command(cmd) - intendedForList[acquisition.indexSidecar].append(acquisition.dstIntendedFor + "".join(ext)) - elif ".json" in ext: - data = acquisition.dstSidecarData(self.config["descriptions"], - intendedForList) + data = acquisition.dstSidecarData(intendedForList) save_json(dstFile, data) os.remove(srcFile) @@ -197,8 +199,4 @@ def move(self, acquisition, intendedForList): else: os.rename(srcFile, dstFile) - intendedFile = acquisition.dstIntendedFor + ".nii.gz" - if intendedFile not in intendedForList[acquisition.indexSidecar]: - intendedForList[acquisition.indexSidecar].append(intendedFile) - - return intendedForList \ No newline at end of file + return intendedForList diff --git a/dcm2bids/participant.py b/dcm2bids/participant.py index bf6a5827..61f27f0b 100644 --- a/dcm2bids/participant.py +++ b/dcm2bids/participant.py @@ -5,7 +5,6 @@ from os.path import join as opj from dcm2bids.utils.utils import DEFAULT -from dcm2bids.version import __version__ class Participant(object): @@ -92,4 +91,4 @@ def hasSession(self): Returns: Boolean """ - return self.session.strip() != DEFAULT.session \ No newline at end of file + return self.session.strip() != DEFAULT.session diff --git a/dcm2bids/sidecar.py b/dcm2bids/sidecar.py index d2a58d6a..688ce240 100644 --- a/dcm2bids/sidecar.py +++ b/dcm2bids/sidecar.py @@ -226,7 +226,6 @@ def build_acquisitions(self, participant): desc = valid_descriptions[0] acq = Acquisition(participant, srcSidecar=sidecar, **desc) - acq.indexSidecar = self.descriptions.index(desc) acq.setDstFile() if acq.intendedFor != [None]: @@ -245,7 +244,6 @@ def build_acquisitions(self, participant): self.logger.warning("Several Pairing <- %s", sidecarName) for desc in valid_descriptions: acq = Acquisition(participant, - indexSidecar=self.descriptions.index(desc), **desc) self.logger.warning(" -> %s", acq.suffix) diff --git a/dcm2bids/utils/utils.py b/dcm2bids/utils/utils.py index 2f1d03e4..6461338c 100644 --- a/dcm2bids/utils/utils.py +++ b/dcm2bids/utils/utils.py @@ -14,6 +14,7 @@ class DEFAULT(object): doc = "Documentation at https://unfmontreal.github.io/Dcm2Bids/" link_bids_validator = "https://github.com/bids-standard/bids-validator#quickstart" + link_doc_intended_for = "https://unfmontreal.github.io/Dcm2Bids/docs/tutorial/first-steps/#populating-the-config-file" # cli dcm2bids cliSession = "" diff --git a/docs/how-to/create-config-file.md b/docs/how-to/create-config-file.md index 07cbea90..9023449a 100644 --- a/docs/how-to/create-config-file.md +++ b/docs/how-to/create-config-file.md @@ -17,6 +17,7 @@ } }, { + "id": "task-rest", "dataType": "func", "modalityLabel": "bold", "customLabels": "task-rest", @@ -28,12 +29,13 @@ { "dataType": "fmap", "modalityLabel": "fmap", - "intendedFor": 1, + "intendedFor": "task_rest", "criteria": { "ProtocolName": "*field_mapping*" } }, { + "id": "id_task_learning", "dataType": "func", "modalityLabel": "bold", "customLabels": "task-learning", @@ -50,7 +52,7 @@ "criteria": { "SeriesDescription": "fmap_task-learning" }, - "IntendedFor": 2, + "IntendedFor": "id_task_learning", "sidecarChanges": { "TaskName": "learning" } @@ -146,15 +148,14 @@ WARNING:dcm2bids.structure:✅ Filename was reordered according to BIDS entity t Optional field to change or add information in a sidecar. -## intendedFor +## id and intendedFor -Optional field to add an `IntendedFor` entry in the sidecar of a fieldmap. Just -put the index or a list of indices of the description(s) that's intended for. +Optional field to add an `IntendedFor` entry in the sidecar of a fieldmap. +You will need to set an id to the corresponding description and put the same id in the `IntendedFor` field. -Python index begins at `0` so in the example, **`1`** means it is intended for -`task-rest_bold` and **`2`** is intended for `task-learning` which will be -renamed to only `learning` because of the -`"sidecarChanges": { "TaskName": "learning" }` field. +Fo example, **`task_rest`** means it is intended for `task-rest_bold` +and **`id_task_learning`** is intended for `task-learning` which will be +renamed to only `learning` because of the `"sidecarChanges": { "TaskName": "learning" }` field. ## Multiple config files diff --git a/docs/tutorial/first-steps.md b/docs/tutorial/first-steps.md index 31295505..43aff9e7 100644 --- a/docs/tutorial/first-steps.md +++ b/docs/tutorial/first-steps.md @@ -671,7 +671,7 @@ good unique identifier. === "Command" ```sh - cat code/dcm2bids_config.json + cat tmp_dcm2bids/helper/004_In_DCM2NIIX_regression_test_20180918114023.json ``` === "Output" @@ -807,11 +807,11 @@ task name: "modalityLabel": "bold", "customLabels": "task-rest", "criteria": { - "SeriesDescription": "Axial EPI-FMRI (Interleaved I to S)*", - "sidecarChanges": { + "SeriesDescription": "Axial EPI-FMRI (Interleaved I to S)*" + }, + "sidecarChanges": { "TaskName": "rest" } - } } ] } @@ -887,6 +887,7 @@ file with the appropriate info. { "descriptions": [ { + "id": "id_task-rest", "dataType": "func", "modalityLabel": "bold", "customLabels": "task-rest", @@ -904,7 +905,7 @@ file with the appropriate info. "criteria": { "SeriesDescription": "EPI PE=AP*" }, - "intendedFor": 0 + "intendedFor": "id_task-rest" }, { "dataType": "fmap", @@ -913,7 +914,7 @@ file with the appropriate info. "criteria": { "SeriesDescription": "EPI PE=PA*" }, - "intendedFor": 0 + "intendedFor": "id_task-rest" } ] } diff --git a/tests/data/config_test.json b/tests/data/config_test.json index 7220ba8d..fda7280b 100644 --- a/tests/data/config_test.json +++ b/tests/data/config_test.json @@ -12,6 +12,7 @@ } }, { + "id": "func_task-rest", "dataType": "func", "modalityLabel": "bold", "customLabels": "task-rest", @@ -21,6 +22,7 @@ } }, { + "id": "T1", "dataType": "anat", "modalityLabel": "T1w", "criteria": { @@ -28,6 +30,7 @@ } }, { + "id": "id_dwi", "dataType": "dwi", "modalityLabel": "dwi", "criteria": { @@ -42,7 +45,7 @@ "EchoNumber": 1, "EchoTime": 0.00492 }, - "intendedFor": [3,2] + "intendedFor": ["id_dwi", "T1", "dummy"] }, { "dataType": "fmap", @@ -53,7 +56,7 @@ "EchoTime": 0.00738, "ImageType": ["ORIGINAL", "PRIMARY", "M", "ND"] }, - "IntendedFor": 3 + "IntendedFor": "id_dwi" }, { "dataType": "dwi", @@ -61,7 +64,8 @@ "customLabels": "desc-fa00", "criteria": { "SeriesDescription": "DTI_FA" - } + }, + "IntendedFor": ["id_dwi", "T1"] }, { "dataType": "dwi", diff --git a/tests/data/sidecars/003_MPRAGE_20100603125600.nii b/tests/data/sidecars/003_MPRAGE_20100603125600.nii new file mode 100644 index 00000000..f6a892b5 --- /dev/null +++ b/tests/data/sidecars/003_MPRAGE_20100603125600.nii @@ -0,0 +1 @@ +DUMMY_FILE_FOR_TEST \ No newline at end of file diff --git a/tests/data/sidecars/004_DTI_20100603125600.nii.gz b/tests/data/sidecars/004_DTI_20100603125600.nii.gz new file mode 100644 index 00000000..f6a892b5 --- /dev/null +++ b/tests/data/sidecars/004_DTI_20100603125600.nii.gz @@ -0,0 +1 @@ +DUMMY_FILE_FOR_TEST \ No newline at end of file diff --git a/tests/test_dcm2bids.py b/tests/test_dcm2bids.py index 07799d08..f958d060 100644 --- a/tests/test_dcm2bids.py +++ b/tests/test_dcm2bids.py @@ -62,12 +62,12 @@ def test_dcm2bids(): fmapFile = os.path.join(bidsDir.name, "sub-01", "fmap", "sub-01_echo-492_fmap.json") data = load_json(fmapFile) assert data["IntendedFor"] == [os.path.join("dwi", "sub-01_dwi.nii.gz"), - os.path.join("anat", "sub-01_T1w.nii.gz")] + os.path.join("anat", "sub-01_T1w.nii")] fmapFile = os.path.join(bidsDir.name, "sub-01", "fmap", "sub-01_echo-738_fmap.json") data = load_json(fmapFile) fmapMtime = os.stat(fmapFile).st_mtime - assert data["IntendedFor"] == os.path.join("dwi", "sub-01_dwi.nii.gz") + assert data["IntendedFor"] == [os.path.join("dwi", "sub-01_dwi.nii.gz")] data = load_json( os.path.join(