diff --git a/heudiconv/convert.py b/heudiconv/convert.py index 64846bf6..7b1c0b7d 100644 --- a/heudiconv/convert.py +++ b/heudiconv/convert.py @@ -518,6 +518,8 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam bids_files = (sorted(res.outputs.bids) if len(res.outputs.bids) == len(res_files) else [None] * len(res_files)) + # preload since will be used in multiple spots + bids_metas = [load_json(b) for b in bids_files if b] ### Do we have a multi-echo series? ### # Some Siemens sequences (e.g. CMRR's MB-EPI) set the label 'TE1', @@ -531,19 +533,17 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam # Check for varying echo times echo_times = sorted(list(set( - load_json(b).get('EchoTime', nan) - for b in bids_files + b.get('EchoTime', nan) + for b in bids_metas if b ))) is_multiecho = len(echo_times) > 1 ### Loop through the bids_files, set the output name and save files - for fl, suffix, bids_file in zip(res_files, suffixes, bids_files): + for fl, suffix, bids_file, bids_meta in zip(res_files, suffixes, bids_files, bids_metas): # TODO: monitor conversion duration - if bids_file: - fileinfo = load_json(bids_file) # set the prefix basename for this specific file (we'll modify it, # and we don't want to modify it for all the bids_files): @@ -552,11 +552,18 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam # _sbref sequences reconstructing magnitude and phase generate # two NIfTI files IN THE SAME SERIES, so we cannot just add # the suffix, if we want to be bids compliant: - if bids_file and this_prefix_basename.endswith('_sbref'): + if bids_meta and this_prefix_basename.endswith('_sbref') \ + and len(suffixes) > len(echo_times): + if len(suffixes) != len(echo_times)*2: + lgr.warning( + "Got %d suffixes for %d echo times, which isn't " + "multiple of two as if it was magnitude + phase pairs", + len(suffixes), len(echo_times) + ) # Check to see if it is magnitude or phase reconstruction: - if 'M' in fileinfo.get('ImageType'): + if 'M' in bids_meta.get('ImageType'): mag_or_phase = 'magnitude' - elif 'P' in fileinfo.get('ImageType'): + elif 'P' in bids_meta.get('ImageType'): mag_or_phase = 'phase' else: mag_or_phase = suffix @@ -585,12 +592,12 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam # (Note: it can be _sbref and multiecho, so don't use "elif"): # For multi-echo sequences, we have to specify the echo number in # the file name: - if bids_file and is_multiecho: + if bids_meta and is_multiecho: # Get the EchoNumber from json file info. If not present, use EchoTime - if 'EchoNumber' in fileinfo.keys(): - echo_number = fileinfo['EchoNumber'] + if 'EchoNumber' in bids_meta: + echo_number = bids_meta['EchoNumber'] else: - echo_number = echo_times.index(fileinfo['EchoTime']) + 1 + echo_number = echo_times.index(bids_meta['EchoTime']) + 1 supported_multiecho = ['_bold', '_phase', '_epi', '_sbref', '_T1w', '_PDT2'] # Now, decide where to insert it. diff --git a/heudiconv/external/dlad.py b/heudiconv/external/dlad.py index 53317338..68a0e758 100644 --- a/heudiconv/external/dlad.py +++ b/heudiconv/external/dlad.py @@ -10,7 +10,7 @@ lgr = logging.getLogger(__name__) -MIN_VERSION = '0.12.2' +MIN_VERSION = '0.12.4' def prepare_datalad(studydir, outdir, sid, session, seqinfo, dicoms, bids): @@ -177,4 +177,4 @@ def mark_sensitive(ds, path_glob): init=dict([('distribution-restrictions', 'sensitive')]), recursive=True) if inspect.isgenerator(res): - res = list(res) \ No newline at end of file + res = list(res) diff --git a/heudiconv/heuristics/reproin.py b/heudiconv/heuristics/reproin.py index a6388248..c5a76914 100644 --- a/heudiconv/heuristics/reproin.py +++ b/heudiconv/heuristics/reproin.py @@ -514,10 +514,10 @@ def infotodict(seqinfo): # 3 - Image IOD specific specialization (optional) dcm_image_iod_spec = s.image_type[2] image_type_seqtype = { - 'P': 'fmap', # phase + # Note: P and M are too generic to make a decision here, could be + # for different seqtypes (bold, fmap, etc) 'FMRI': 'func', 'MPR': 'anat', - # 'M': 'func', "magnitude" -- can be for scout, anat, bold, fmap 'DIFFUSION': 'dwi', 'MIP_SAG': 'anat', # angiography 'MIP_COR': 'anat', # angiography @@ -570,29 +570,55 @@ def infotodict(seqinfo): # prefix = '' prefix = '' + # + # Figure out the seqtype_label (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 - if seqtype == 'func' and not seqtype_label: - if '_pace_' in series_spec: - seqtype_label = 'pace' # or should it be part of seq- - else: - # assume bold by default - seqtype_label = 'bold' - - if seqtype == 'fmap' and not seqtype_label: - if not dcm_image_iod_spec: - raise ValueError("Do not know image data type yet to make decision") - seqtype_label = { - # might want explicit {file_index} ? - # _epi for pepolar fieldmaps, see - # https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#case-4-multiple-phase-encoded-directions-pepolar - 'M': 'epi' if 'dir' in series_info else 'magnitude', - 'P': 'phasediff', - 'DIFFUSION': 'epi', # according to KODI those DWI are the EPIs we need - }[dcm_image_iod_spec] - - # label for dwi as well - if seqtype == 'dwi' and not seqtype_label: - seqtype_label = 'dwi' + if not seqtype_label: + if seqtype == 'func': + if '_pace_' in series_spec: + seqtype_label = 'pace' # or should it be part of seq- + elif 'P' in s.image_type: + seqtype_label = 'phase' + elif 'M' in s.image_type: + seqtype_label = 'bold' + else: + # assume bold by default + seqtype_label = 'bold' + elif seqtype == 'fmap': + # TODO: support phase1 phase2 like in "Case 2: Two phase images ..." + if not dcm_image_iod_spec: + raise ValueError("Do not know image data type yet to make decision") + seqtype_label = { + # might want explicit {file_index} ? + # _epi for pepolar fieldmaps, see + # https://bids-specification.readthedocs.io/en/stable/04-modality-specific-files/01-magnetic-resonance-imaging-data.html#case-4-multiple-phase-encoded-directions-pepolar + 'M': 'epi' if 'dir' in series_info else 'magnitude', + 'P': 'phasediff', + 'DIFFUSION': 'epi', # according to KODI those DWI are the EPIs we need + }[dcm_image_iod_spec] + elif seqtype == 'dwi': + # label for dwi as well + seqtype_label = 'dwi' + + # + # Even if seqtype_label was provided, for some data we might need to override, + # since they are complementary files produced along-side with original + # ones. + # + if s.series_description.endswith('_SBRef'): + seqtype_label = 'sbref' + + if not seqtype_label: + # Might be provided by the bids ending within series_spec, we would + # just want to check if that the last element is not _key-value pair + bids_ending = series_info.get('bids', None) + if not bids_ending \ + or "-" in bids_ending.split('_')[-1]: + lgr.warning( + "We ended up with an empty label/suffix for %r", + series_spec) run = series_info.get('run') if run is not None: diff --git a/heudiconv/info.py b/heudiconv/info.py index 4f5fc596..ccf36817 100644 --- a/heudiconv/info.py +++ b/heudiconv/info.py @@ -42,7 +42,7 @@ EXTRA_REQUIRES = { 'tests': TESTS_REQUIRES, 'extras': [], # Requires patched version ATM ['dcmstack'], - 'datalad': ['datalad >=0.12.2'] + 'datalad': ['datalad >=0.12.3'] } # Flatten the lists