Skip to content

Commit bdcbf2e

Browse files
tsaloyarikoptic
authored andcommitted
Add functions for renaming complex, multi-echo, and uncombined files.
1 parent d2e47cb commit bdcbf2e

File tree

1 file changed

+128
-64
lines changed

1 file changed

+128
-64
lines changed

heudiconv/convert.py

Lines changed: 128 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -233,6 +233,95 @@ def prep_conversion(sid, dicoms, outdir, heuristic, converter, anon_sid,
233233
getattr(heuristic, 'DEFAULT_FIELDS', {}))
234234

235235

236+
def update_complex_name(fileinfo, this_prefix_basename, suffix):
237+
"""
238+
Insert `_part-<mag|phase>` entity into filename if data are from a sequence
239+
with magnitude/phase reconstruction.
240+
"""
241+
unsupported_types = ['_bold', '_phase']
242+
if suffix in unsupported_types:
243+
return this_prefix_basename
244+
245+
# Check to see if it is magnitude or phase reconstruction:
246+
if 'M' in fileinfo.get('ImageType'):
247+
mag_or_phase = 'mag'
248+
elif 'P' in fileinfo.get('ImageType'):
249+
mag_or_phase = 'phase'
250+
else:
251+
mag_or_phase = suffix
252+
253+
# Insert reconstruction label
254+
if not ('_part-%s' % mag_or_phase) in this_prefix_basename:
255+
256+
# If "_part-" is specified, prepend the 'mag_or_phase' value.
257+
if '_part-' in this_prefix_basename:
258+
raise BIDSError(
259+
"Part label for images will be automatically set, remove "
260+
"from heuristic"
261+
)
262+
263+
# If not, insert "_part-" + 'mag_or_phase' into the prefix_basename
264+
# **before** "_run", "_echo" or "_sbref", whichever appears first:
265+
for label in ['_run', '_echo', '_sbref']:
266+
if (label in this_prefix_basename):
267+
this_prefix_basename = this_prefix_basename.replace(
268+
label, "_part-%s%s" % (mag_or_phase, label)
269+
)
270+
break
271+
return this_prefix_basename
272+
273+
274+
def update_multiecho_name(fileinfo, this_prefix_basename, echo_times, suffix):
275+
"""
276+
Insert `_echo-<num>` entity into filename if data are from a multi-echo
277+
sequence.
278+
"""
279+
unsupported_types = ['_magnitude1', '_magnitude2', '_phasediff', '_phase1', '_phase2']
280+
if suffix in unsupported_types:
281+
return this_prefix_basename
282+
283+
# Get the EchoNumber from json file info. If not present, use EchoTime
284+
if 'EchoNumber' in fileinfo.keys():
285+
echo_number = fileinfo['EchoNumber']
286+
else:
287+
echo_number = echo_times.index(fileinfo['EchoTime']) + 1
288+
289+
# Now, decide where to insert it.
290+
# Insert it **before** the following string(s), whichever appears first.
291+
for imgtype in supported_multiecho:
292+
if (imgtype in this_prefix_basename):
293+
this_prefix_basename = this_prefix_basename.replace(
294+
imgtype, "_echo-%d%s" % (echo_number, imgtype)
295+
)
296+
break
297+
return this_prefix_basename
298+
299+
300+
def update_uncombined_name(fileinfo, this_prefix_basename, coil_names, suffix):
301+
"""
302+
Insert `_channel-<num>` entity into filename if data are from a sequence
303+
with "save uncombined".
304+
"""
305+
# Determine the channel number
306+
channel_number = coil_names.index(fileinfo['CoilString']) + 1
307+
308+
# Insert it **before** the following string(s), whichever appears first.
309+
for label in ['_run', '_echo', suffix]:
310+
if label == suffix:
311+
prefix_suffix = this_prefix_basename.split('_')[-1]
312+
this_prefix_basename = this_prefix_basename.replace(
313+
prefix_suffix, "_channel-%s_%s" % (channel_number, prefix_suffix)
314+
)
315+
break
316+
317+
if (label in this_prefix_basename):
318+
this_prefix_basename = this_prefix_basename.replace(
319+
label, "_channel-%s%s" % (coil_number, label)
320+
)
321+
break
322+
return this_prefix_basename
323+
324+
236325
def convert(items, converter, scaninfo_suffix, custom_callable, with_prov,
237326
bids_options, outdir, min_meta, overwrite, symlink=True, prov_file=None,
238327
dcmconfig=None):
@@ -534,14 +623,28 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam
534623
# series. To do that, the most straightforward way is to read the
535624
# echo times for all bids_files and see if they are all the same or not.
536625

537-
# Check for varying echo times
538-
echo_times = sorted(list(set(
539-
b.get('EchoTime', nan)
540-
for b in bids_metas
541-
if b
542-
)))
543-
544-
is_multiecho = len(echo_times) > 1
626+
# Collect some metadata across all images
627+
echo_times, channel_names, image_types = [], [], []
628+
for metadata in bids_metas:
629+
if not metadata:
630+
continue
631+
echo_times.append(metadata.get('EchoTime', None))
632+
channel_names.append(metadata.get('CoilString', None))
633+
image_types.append(metadata.get('ImageType', None))
634+
echo_times = [v for v in echo_times if v]
635+
echo_times = sorted(list(set(echo_times)))
636+
channel_names = [v for v in channel_names if v]
637+
channel_names = sorted(list(set(channel_names)))
638+
image_types = [v for v in image_types if v]
639+
image_types = sorted(list(set(image_types)))
640+
641+
is_multiecho = len(echo_times) > 1 # Check for varying echo times
642+
is_uncombined = len(coil_names) > 1 # Check for uncombined data
643+
644+
# Determine if data are complex (magnitude + phase)
645+
magnitude_found = ['M' in it for it in image_types]
646+
phase_found = ['P' in it for it in image_types]
647+
is_complex = magnitude_found and phase_found
545648

546649
### Loop through the bids_files, set the output name and save files
547650
for fl, suffix, bids_file, bids_meta in zip(res_files, suffixes, bids_files, bids_metas):
@@ -552,65 +655,26 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam
552655
# and we don't want to modify it for all the bids_files):
553656
this_prefix_basename = prefix_basename
554657

555-
# _sbref sequences reconstructing magnitude and phase generate
556-
# two NIfTI files IN THE SAME SERIES, so we cannot just add
557-
# the suffix, if we want to be bids compliant:
558-
if bids_meta and this_prefix_basename.endswith('_sbref') \
559-
and len(suffixes) > len(echo_times):
560-
if len(suffixes) != len(echo_times)*2:
561-
lgr.warning(
562-
"Got %d suffixes for %d echo times, which isn't "
563-
"multiple of two as if it was magnitude + phase pairs",
564-
len(suffixes), len(echo_times)
565-
)
566-
# Check to see if it is magnitude or phase reconstruction:
567-
if 'M' in bids_meta.get('ImageType'):
568-
mag_or_phase = 'magnitude'
569-
elif 'P' in bids_meta.get('ImageType'):
570-
mag_or_phase = 'phase'
571-
else:
572-
mag_or_phase = suffix
573-
574-
# Insert reconstruction label
575-
if not ("_rec-%s" % mag_or_phase) in this_prefix_basename:
576-
577-
# If "_rec-" is specified, prepend the 'mag_or_phase' value.
578-
if ('_rec-' in this_prefix_basename):
579-
raise BIDSError(
580-
"Reconstruction label for multi-echo single-band"
581-
" reference images will be automatically set, remove"
582-
" from heuristic"
583-
)
584-
585-
# If not, insert "_rec-" + 'mag_or_phase' into the prefix_basename
586-
# **before** "_run", "_echo" or "_sbref", whichever appears first:
587-
for label in ['_run', '_echo', '_sbref']:
588-
if (label in this_prefix_basename):
589-
this_prefix_basename = this_prefix_basename.replace(
590-
label, "_rec-%s%s" % (mag_or_phase, label)
591-
)
592-
break
593-
594-
# Now check if this run is multi-echo
658+
# Update name if complex data
659+
if bids_file and is_complex:
660+
this_prefix_basename = update_complex_name(
661+
bids_meta, this_prefix_basename, suffix
662+
)
663+
664+
# Update name if multi-echo
595665
# (Note: it can be _sbref and multiecho, so don't use "elif"):
596666
# For multi-echo sequences, we have to specify the echo number in
597667
# the file name:
598-
if bids_meta and is_multiecho:
599-
# Get the EchoNumber from json file info. If not present, use EchoTime
600-
if 'EchoNumber' in bids_meta:
601-
echo_number = bids_meta['EchoNumber']
602-
else:
603-
echo_number = echo_times.index(bids_meta['EchoTime']) + 1
604-
605-
supported_multiecho = ['_bold', '_phase', '_epi', '_sbref', '_T1w', '_PDT2']
606-
# Now, decide where to insert it.
607-
# Insert it **before** the following string(s), whichever appears first.
608-
for imgtype in supported_multiecho:
609-
if (imgtype in this_prefix_basename):
610-
this_prefix_basename = this_prefix_basename.replace(
611-
imgtype, "_echo-%d%s" % (echo_number, imgtype)
612-
)
613-
break
668+
if bids_file and is_multiecho:
669+
this_prefix_basename = update_multiecho_name(
670+
bids_meta, this_prefix_basename, echo_times, suffix
671+
)
672+
673+
# Update name if uncombined (channel-level) data
674+
if bids_file and is_uncombined:
675+
this_prefix_basename = update_uncombined_name(
676+
bids_meta, this_prefix_basename, channel_names
677+
)
614678

615679
# Fallback option:
616680
# If we have failed to modify this_prefix_basename, because it didn't fall

0 commit comments

Comments
 (0)