diff --git a/docs/container.rst b/docs/container.rst index 8ad96729..fd10d3a4 100644 --- a/docs/container.rst +++ b/docs/container.rst @@ -4,7 +4,7 @@ Using heudiconv in a Container If heudiconv is :ref:`installed via a Docker container `, you can run the commands in the following format:: - + docker run nipy/heudiconv:latest [heudiconv options] So a user running via container would check the version with this command:: @@ -46,4 +46,3 @@ We typically recommend users make use of the following flags to Docker and Podma * ``-it`` Interactive terminal * ``--rm`` Remove the changes to the container when it completes - diff --git a/docs/custom-heuristic.rst b/docs/custom-heuristic.rst index bfef79db..54299bbb 100644 --- a/docs/custom-heuristic.rst +++ b/docs/custom-heuristic.rst @@ -316,4 +316,3 @@ Suppose you want to use the values in the field ``image_type``? It is not a num Note that this differs from testing for a string because you cannot test for any substring (e.g., 'TEST' would not work). String tests will not work on a tuple datatype. .. Note:: *image_type* is described in the `DICOM specification `_ - diff --git a/docs/index.rst b/docs/index.rst index f17be802..2f63f524 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -19,4 +19,3 @@ Contents commandline container api - diff --git a/docs/quickstart.rst b/docs/quickstart.rst index 118c77dc..f52b905f 100644 --- a/docs/quickstart.rst +++ b/docs/quickstart.rst @@ -3,7 +3,7 @@ Quickstart This tutorial is based on `Dianne Patterson's University of Arizona tutorials `_ -This guide assumes you have already :ref:`installed heudiconv and dcm2niix ` and +This guide assumes you have already :ref:`installed heudiconv and dcm2niix ` and demonstrates how to use the heudiconv tool with a provided `heuristic.py` to convert DICOMS into the BIDS data structure. .. _prepare_dataset: @@ -11,7 +11,7 @@ demonstrates how to use the heudiconv tool with a provided `heuristic.py` to con Prepare Dataset *************** -Download and unzip `sub-219_dicom.zip `_. +Download and unzip `sub-219_dicom.zip `_. We will be working from a directory called MRIS. Under the MRIS directory is the *dicom* subdirectory: Under the subject number *219* the session *itbs* is nested. Each dicom sequence folder is nested under the session:: @@ -29,7 +29,7 @@ We will be working from a directory called MRIS. Under the MRIS directory is the ├── field_mapping_21 └── restingstate_18 Nifti - └── code + └── code └── heuristic1.py Basic Conversion @@ -57,36 +57,36 @@ Run the following command:: Output ****** - + The *Nifti* directory will contain a bids-compliant subject directory:: - - + + └── sub-219 └── ses-itbs ├── anat ├── dwi ├── fmap └── func - + The following required BIDS text files are also created in the Nifti directory. Details for filling in these skeleton text files can be found under `tabular files `_ in the BIDS specification:: - + CHANGES README dataset_description.json participants.json participants.tsv task-rest_bold.json - + Validation ********** -Ensure that everything is according to spec by using `bids validator `_ +Ensure that everything is according to spec by using `bids validator `_ Click `Choose File` and then select the *Nifti* directory. There should be no errors (though there are a couple of warnings). - + .. Note:: Your files are not uploaded to the BIDS validator, so there are no privacy concerns! - -Next + +Next **** In the following sections, you will modify *heuristic.py* yourself so you can test different options and understand how to work with your own data. diff --git a/heudiconv/heuristics/reproin.py b/heudiconv/heuristics/reproin.py index 0d919aa5..26db4aea 100644 --- a/heudiconv/heuristics/reproin.py +++ b/heudiconv/heuristics/reproin.py @@ -280,8 +280,8 @@ def fix_canceled_runs(seqinfo: list[SeqInfo]) -> list[SeqInfo]: """Function that adds cancelme_ to known bad runs which were forgotten""" if not fix_accession2run: return seqinfo # nothing to do - for i, s in enumerate(seqinfo): - accession_number = s.accession_number + for i, curr_seqinfo in enumerate(seqinfo): + accession_number = curr_seqinfo.accession_number if accession_number and accession_number in fix_accession2run: lgr.info( "Considering some runs possibly marked to be " @@ -292,12 +292,12 @@ def fix_canceled_runs(seqinfo: list[SeqInfo]) -> list[SeqInfo]: # a single accession, but left as is for now badruns = fix_accession2run[accession_number] badruns_pattern = "|".join(badruns) - if re.match(badruns_pattern, s.series_id): - lgr.info("Fixing bad run {0}".format(s.series_id)) + if re.match(badruns_pattern, curr_seqinfo.series_id): + lgr.info("Fixing bad run {0}".format(curr_seqinfo.series_id)) fixedkwargs = dict() for key in series_spec_fields: - fixedkwargs[key] = "cancelme_" + getattr(s, key) - seqinfo[i] = s._replace(**fixedkwargs) + fixedkwargs[key] = "cancelme_" + getattr(curr_seqinfo, key) + seqinfo[i] = curr_seqinfo._replace(**fixedkwargs) return seqinfo @@ -341,11 +341,11 @@ def _apply_substitutions( seqinfo: list[SeqInfo], substitutions: list[tuple[str, str]], subs_scope: str ) -> None: lgr.info("Considering %s substitutions", subs_scope) - for i, s in enumerate(seqinfo): + for i, curr_seqinfo in enumerate(seqinfo): fixed_kwargs = dict() # need to replace both protocol_name series_description for key in series_spec_fields: - oldvalue = value = getattr(s, key) + oldvalue = value = getattr(curr_seqinfo, key) # replace all I need to replace for substring, replacement in substitutions: value = re.sub(substring, replacement, value) @@ -353,7 +353,7 @@ def _apply_substitutions( lgr.info(" %s: %r -> %r", key, oldvalue, value) fixed_kwargs[key] = value # namedtuples are immutable - seqinfo[i] = s._replace(**fixed_kwargs) + seqinfo[i] = curr_seqinfo._replace(**fixed_kwargs) def fix_seqinfo(seqinfo: list[SeqInfo]) -> list[SeqInfo]: @@ -402,32 +402,34 @@ def infotodict( run_label: Optional[str] = None # run- dcm_image_iod_spec: Optional[str] = None skip_derived = False - for s in seqinfo: + for curr_seqinfo in seqinfo: # XXX: skip derived sequences, we don't store them to avoid polluting # the directory, unless it is the motion corrected ones # (will get _rec-moco suffix) - if skip_derived and s.is_derived and not s.is_motion_corrected: - skipped.append(s.series_id) - lgr.debug("Ignoring derived data %s", s.series_id) + if skip_derived and curr_seqinfo.is_derived and not curr_seqinfo.is_motion_corrected: + skipped.append(curr_seqinfo.series_id) + lgr.debug("Ignoring derived data %s", curr_seqinfo.series_id) continue # possibly apply present formatting in the series_description or protocol name for f in "series_description", "protocol_name": - s = s._replace(**{f: getattr(s, f).format(**s._asdict())}) + curr_seqinfo = curr_seqinfo._replace( + **{f: getattr(curr_seqinfo, f).format(**curr_seqinfo._asdict())} + ) template = None suffix = "" # seq = [] - # figure out type of image from s.image_info -- just for checking ATM + # figure out type of image from curr_seqinfo.image_info -- just for checking ATM # since we primarily rely on encoded in the protocol name information prev_dcm_image_iod_spec = dcm_image_iod_spec - if len(s.image_type) > 2: + if len(curr_seqinfo.image_type) > 2: # https://dicom.innolitics.com/ciods/cr-image/general-image/00080008 # 0 - ORIGINAL/DERIVED # 1 - PRIMARY/SECONDARY # 3 - Image IOD specific specialization (optional) - dcm_image_iod_spec = s.image_type[2] + dcm_image_iod_spec = curr_seqinfo.image_type[2] image_type_datatype = { # Note: P and M are too generic to make a decision here, could be # for different datatypes (bold, fmap, etc) @@ -443,7 +445,7 @@ def infotodict( series_info = {} # For please lintian and its friends for sfield in series_spec_fields: - svalue = getattr(s, sfield) + svalue = getattr(curr_seqinfo, sfield) series_info = parse_series_spec(svalue) if series_info: # looks like a valid spec - we are done series_spec = svalue @@ -454,10 +456,10 @@ def infotodict( if not series_info: series_spec = None # we cannot know better lgr.warning( - "Could not determine the series name by looking at " "%s fields", + "Could not determine the series name by looking at %s fields", ", ".join(series_spec_fields), ) - skipped_unknown.append(s.series_id) + skipped_unknown.append(curr_seqinfo.series_id) continue if dcm_image_iod_spec and dcm_image_iod_spec.startswith("MIP"): @@ -476,14 +478,14 @@ def infotodict( series_spec, ) - # if s.is_derived: + # if curr_seqinfo.is_derived: # # Let's for now stash those close to original images # # TODO: we might want a separate tree for all of this!? # # so more of a parameter to the create_key # #datatype += '/derivative' # # just keep it lower case and without special characters # # XXXX what for??? - # #seq.append(s.series_description.lower()) + # #seq.append(curr_seqinfo.series_description.lower()) # prefix = os.path.join('derivatives', 'scanner') # else: # prefix = '' @@ -493,14 +495,14 @@ def infotodict( # Figure out the datatype_suffix (BIDS _suffix) # # If none was provided -- let's deduce it from the information we find: - # analyze s.protocol_name (series_id is based on it) for full name mapping etc + # analyze curr_seqinfo.protocol_name (series_id is based on it) for full name mapping etc if not datatype_suffix: if datatype == "func": if "_pace_" in series_spec: datatype_suffix = "pace" # or should it be part of seq- - elif "P" in s.image_type: + elif "P" in curr_seqinfo.image_type: datatype_suffix = "phase" - elif "M" in s.image_type: + elif "M" in curr_seqinfo.image_type: datatype_suffix = "bold" else: # assume bold by default @@ -526,7 +528,7 @@ def infotodict( # since they are complementary files produced along-side with original # ones. # - if s.series_description.endswith("_SBRef"): + if curr_seqinfo.series_description.endswith("_SBRef"): datatype_suffix = "sbref" if not datatype_suffix: @@ -550,7 +552,7 @@ def infotodict( # XXX if we have a known earlier study, we need to always # increase the run counter for phasediff because magnitudes # were not acquired - if get_study_hash([s]) == "9d148e2a05f782273f6343507733309d": + if get_study_hash([curr_seqinfo]) == "9d148e2a05f782273f6343507733309d": current_run += 1 else: raise RuntimeError( @@ -583,10 +585,10 @@ def infotodict( run_label = None # yoh: had a wrong assumption - # if s.is_motion_corrected: - # assert s.is_derived, "Motion corrected images must be 'derived'" + # if curr_seqinfo.is_motion_corrected: + # assert curr_seqinfo.is_derived, "Motion corrected images must be 'derived'" - if s.is_motion_corrected and "rec-" in series_info.get("bids", ""): + if curr_seqinfo.is_motion_corrected and "rec-" in series_info.get("bids", ""): raise NotImplementedError( "want to add _rec-moco but there is _rec- already" ) @@ -611,7 +613,7 @@ def from_series_info(name: str) -> Optional[str]: from_series_info("acq"), # But we want to add an indicator in case it was motion corrected # in the magnet. ref sample /2017/01/03/qa - None if not s.is_motion_corrected else "rec-moco", + None if not curr_seqinfo.is_motion_corrected else "rec-moco", from_series_info("dir"), series_info.get("bids"), run_label, @@ -621,7 +623,7 @@ def from_series_info(name: str) -> Optional[str]: suffix = "_".join(filter(bool, filename_suffix_parts)) # type: ignore[arg-type] # # .series_description in case of - # sdesc = s.study_description + # sdesc = curr_seqinfo.study_description # # temporary aliases for those phantoms which we already collected # # so we rename them into this # #MAPPING @@ -638,13 +640,16 @@ def from_series_info(name: str) -> Optional[str]: # https://github.com/nipy/heudiconv/issues/145 outtype: tuple[str, ...] if ( - "_Scout" in s.series_description + "_Scout" in curr_seqinfo.series_description or ( datatype == "anat" and datatype_suffix and datatype_suffix.startswith("scout") ) - or (s.series_description.lower() == s.protocol_name.lower() + "_setter") + or ( + curr_seqinfo.series_description.lower() + == curr_seqinfo.protocol_name.lower() + "_setter" + ) ): outtype = ("dicom",) else: @@ -654,7 +659,7 @@ def from_series_info(name: str) -> Optional[str]: # we wanted ordered dict for consistent demarcation of dups if template not in info: info[template] = [] - info[template].append(s.series_id) + info[template].append(curr_seqinfo.series_id) if skipped: lgr.info("Skipped %d sequences: %s" % (len(skipped), skipped)) @@ -762,7 +767,7 @@ def infotoids(seqinfos: Iterable[SeqInfo], outdir: str) -> dict[str, Optional[st # So -- use `outdir` and locator etc to see if for a given locator/subject # and possible ses+ in the sequence names, so we would provide a sequence - # So might need to go through parse_series_spec(s.protocol_name) + # So might need to go through parse_series_spec(curr_seqinfo.protocol_name) # to figure out presence of sessions. ses_markers: list[str] = []