Skip to content

Commit e21a9ba

Browse files
committed
Add functions for renaming complex, multi-echo, and uncombined files.
1 parent fdea77c commit e21a9ba

File tree

1 file changed

+129
-56
lines changed

1 file changed

+129
-56
lines changed

heudiconv/convert.py

Lines changed: 129 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,95 @@ def prep_conversion(sid, dicoms, outdir, heuristic, converter, anon_sid,
230230
getattr(heuristic, 'DEFAULT_FIELDS', {}))
231231

232232

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

530-
# Check for varying echo times
531-
echo_times = sorted(list(set(
532-
load_json(b).get('EchoTime', None)
533-
for b in bids_files
534-
if b
535-
)))
536-
537-
is_multiecho = len(echo_times) > 1
619+
# Collect some metadata across all images
620+
echo_times, channel_names, image_types = [], [], []
621+
for b in bids_files:
622+
if not b:
623+
continue
624+
metadata = load_json(b)
625+
echo_times.append(metadata.get('EchoTime', None))
626+
channel_names.append(metadata.get('CoilString', None))
627+
image_types.append(metadata.get('ImageType', None))
628+
echo_times = [v for v in echo_times if v]
629+
echo_times = sorted(list(set(echo_times)))
630+
channel_names = [v for v in channel_names if v]
631+
channel_names = sorted(list(set(channel_names)))
632+
image_types = [v for v in image_types if v]
633+
image_types = sorted(list(set(image_types)))
634+
635+
is_multiecho = len(echo_times) > 1 # Check for varying echo times
636+
is_uncombined = len(coil_names) > 1 # Check for uncombined data
637+
638+
# Determine if data are complex (magnitude + phase)
639+
magnitude_found = ['M' in it for it in image_types]
640+
phase_found = ['P' in it for it in image_types]
641+
is_complex = magnitude_found and phase_found
538642

539643
### Loop through the bids_files, set the output name and save files
540644
for fl, suffix, bids_file in zip(res_files, suffixes, bids_files):
541645

542646
# TODO: monitor conversion duration
543647
if bids_file:
544648
fileinfo = load_json(bids_file)
649+
print(suffix)
545650

546651
# set the prefix basename for this specific file (we'll modify it,
547652
# and we don't want to modify it for all the bids_files):
548653
this_prefix_basename = prefix_basename
549654

550-
# _sbref sequences reconstructing magnitude and phase generate
551-
# two NIfTI files IN THE SAME SERIES, so we cannot just add
552-
# the suffix, if we want to be bids compliant:
553-
if bids_file and this_prefix_basename.endswith('_sbref'):
554-
# Check to see if it is magnitude or phase reconstruction:
555-
if 'M' in fileinfo.get('ImageType'):
556-
mag_or_phase = 'magnitude'
557-
elif 'P' in fileinfo.get('ImageType'):
558-
mag_or_phase = 'phase'
559-
else:
560-
mag_or_phase = suffix
561-
562-
# Insert reconstruction label
563-
if not ("_rec-%s" % mag_or_phase) in this_prefix_basename:
564-
565-
# If "_rec-" is specified, prepend the 'mag_or_phase' value.
566-
if ('_rec-' in this_prefix_basename):
567-
raise BIDSError(
568-
"Reconstruction label for multi-echo single-band"
569-
" reference images will be automatically set, remove"
570-
" from heuristic"
571-
)
572-
573-
# If not, insert "_rec-" + 'mag_or_phase' into the prefix_basename
574-
# **before** "_run", "_echo" or "_sbref", whichever appears first:
575-
for label in ['_run', '_echo', '_sbref']:
576-
if (label in this_prefix_basename):
577-
this_prefix_basename = this_prefix_basename.replace(
578-
label, "_rec-%s%s" % (mag_or_phase, label)
579-
)
580-
break
581-
582-
# Now check if this run is multi-echo
655+
# Update name if complex data
656+
if bids_file and is_complex:
657+
this_prefix_basename = update_complex_name(
658+
fileinfo, this_prefix_basename, suffix
659+
)
660+
661+
# Update name if multi-echo
583662
# (Note: it can be _sbref and multiecho, so don't use "elif"):
584663
# For multi-echo sequences, we have to specify the echo number in
585664
# the file name:
586665
if bids_file and is_multiecho:
587-
# Get the EchoNumber from json file info. If not present, use EchoTime
588-
if 'EchoNumber' in fileinfo.keys():
589-
echo_number = fileinfo['EchoNumber']
590-
else:
591-
echo_number = echo_times.index(fileinfo['EchoTime']) + 1
592-
593-
supported_multiecho = ['_bold', '_phase', '_epi', '_sbref', '_T1w', '_PDT2']
594-
# Now, decide where to insert it.
595-
# Insert it **before** the following string(s), whichever appears first.
596-
for imgtype in supported_multiecho:
597-
if (imgtype in this_prefix_basename):
598-
this_prefix_basename = this_prefix_basename.replace(
599-
imgtype, "_echo-%d%s" % (echo_number, imgtype)
600-
)
601-
break
666+
this_prefix_basename = update_multiecho_name(
667+
fileinfo, this_prefix_basename, echo_times, suffix
668+
)
669+
670+
# Update name if uncombined (channel-level) data
671+
if bids_file and is_uncombined:
672+
this_prefix_basename = update_uncombined_name(
673+
fileinfo, this_prefix_basename, channel_names
674+
)
602675

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

0 commit comments

Comments
 (0)