Skip to content

Commit f053c0b

Browse files
committed
Handles multi-echo data.
It handles both ME-EPI and ME-MPRAGE. For multi-echo functional data, it generates a single _events.tsv file (following BIDS spec 1.1.0).
1 parent 2ff09b1 commit f053c0b

File tree

2 files changed

+111
-2
lines changed

2 files changed

+111
-2
lines changed

heudiconv/bids.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,22 @@ def populate_bids_templates(path, defaults={}):
8888
# create a stub onsets file for each one of those
8989
suf = '_bold.json'
9090
assert fpath.endswith(suf)
91+
# specify the name of the '_events.tsv' file:
92+
if ( '_echo-' in fpath ):
93+
# multi-echo sequence: bids (1.1.0) specifies just one '_events.tsv'
94+
# file, common for all echoes. The name will not include _echo-.
95+
# So, find out the echo number:
96+
fpath_split = fpath.split('_echo-') # split fpath using '_echo-'
97+
fpath_split_2 = fpath_split[1].split('_') # split the second part of fpath_split using '_'
98+
echoNo = fpath_split_2[0] # get echo number
99+
if ( echoNo == '1' ):
100+
# we modify fpath to exclude '_echo-' + echoNo:
101+
fpath = fpath_split[0] + '_' + fpath_split_2[1]
102+
else:
103+
# for echoNo greater than 1, don't create the events file, so go to
104+
# the next for loop iteration:
105+
continue
106+
91107
events_file = fpath[:-len(suf)] + '_events.tsv'
92108
# do not touch any existing thing, it may be precious
93109
if not op.lexists(events_file):

heudiconv/convert.py

Lines changed: 95 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,9 @@ def save_converted_files(res, item_dicoms, bids, outtype, prefix, outname_bids,
441441
"""
442442
from nipype.interfaces.base import isdefined
443443

444+
prefix_dirname = op.dirname(prefix + '.ext')
445+
prefix_basename = op.basename(prefix)
446+
444447
bids_outfiles = []
445448
res_files = res.outputs.converted_files
446449

@@ -473,11 +476,101 @@ def save_converted_files(res, item_dicoms, bids, outtype, prefix, outname_bids,
473476
if len(res.outputs.bids) == len(res_files)
474477
else [None] * len(res_files))
475478

479+
### Do we have a multi-echo series? ###
480+
# Some Siemens sequences (e.g. CMRR's MB-EPI) set the label 'TE1',
481+
# 'TE2', etc. in the 'ImageType' field. However, other seqs do not
482+
# (e.g. MGH ME-MPRAGE). They do set a 'EchoNumber', but not for the
483+
# first echo. To compound the problem, the echoes are NOT in order,
484+
# so the first NIfTI file does not correspond to echo-1, etc. So, we
485+
# need to know, beforehand, whether we are dealing with a multi-echo
486+
# series. To do that, the most straightforward way is to read the
487+
# echo times for all bids_files and see if they are all the same or not.
488+
489+
# Get the echo times:
490+
echoTimes = [load_json(bids_file).get('EchoTime') for bids_file in bids_files]
491+
492+
# To see if the echo times are the same, convert it to a set and see if
493+
# only one remains:
494+
if ( len(set(echoTimes)) == 1 ): multiecho = False
495+
else : multiecho = True
496+
497+
### Loop through the bids_files, set the output name and save files ###
498+
476499
for fl, suffix, bids_file in zip(res_files, suffixes, bids_files):
477-
outname = "%s%s.%s" % (prefix, suffix, outtype)
500+
# load the json file info:
501+
fileinfo = load_json(bids_file)
502+
503+
# set the prefix basename for this specific file (we'll modify it, and
504+
# we don't want to modify it for all the bids_files):
505+
this_prefix_basename = prefix_basename
506+
507+
# _sbref sequences reconstructing magnitude and phase generate
508+
# two NIfTI files IN THE SAME SERIES, so we cannot just add
509+
# the suffix, if we want to be bids compliant:
510+
if ( bids and (this_prefix_basename[-6:] == '_sbref') ):
511+
# Check to see if it is magnitude or phase reconstruction:
512+
if ('M' in fileinfo.get('ImageType')): mag_or_phase = 'magnitude'
513+
elif ('P' in fileinfo.get('ImageType')): mag_or_phase = 'phase'
514+
else : mag_or_phase = suffix
515+
516+
# If "_rec-'mag_or_phase'" is not already there, check where to insert it:
517+
if not (("_rec-%s" % mag_or_phase) in this_prefix_basename):
518+
519+
# If "_rec-" is specified, append the 'mag_or_phase' value.
520+
if ('_rec-' in this_prefix_basename):
521+
spt = this_prefix_basename.split('_rec-',1)
522+
# grab the reconstruction type (grab whatever we have before the next "_"):
523+
spt_spt = spt[1].split('_',1)
524+
# update 'this_prefix_basename':
525+
this_prefix_basename = "%s_rec-%s-%s_%s" % (spt[0], spt_spt[0], mag_or_phase, spt_spt[1])
526+
527+
# If not, insert "_rec-" + 'mag_or_phase' into the prefix_basename
528+
# **before** "_run", "_echo" or "_sbref", whichever appears first:
529+
else:
530+
for my_str in ['_run', '_echo', '_sbref']:
531+
if (my_str in this_prefix_basename):
532+
spt = this_prefix_basename.split(my_str, 1)
533+
this_prefix_basename = "%s_rec-%s%s%s" % (spt[0], mag_or_phase, my_str, spt[1])
534+
break
535+
536+
# Now check if this run is multi-echo (Note: it can be _sbref and multiecho, so
537+
# don't use "elif"):
538+
# For multi-echo sequences, we have to specify the echo number in the file name:
539+
if ( bids and multiecho ):
540+
# Get the EchoNumber from json file info. If not present, it's echo-1
541+
echoNumber=( fileinfo.get('EchoNumber') or 1 )
542+
543+
# Now, decide where to insert it.
544+
# Insert it **before** the following string(s), whichever appears first.
545+
# (Note: If you decide to support multi-echo for other sequences (e.g.
546+
# ME-MPRAGE), add the string before which you want to add the echo number
547+
# to the list below):
548+
for my_str in ['_bold', '_sbref', '_T1w']:
549+
if (my_str in this_prefix_basename):
550+
spt = this_prefix_basename.split(my_str, 1)
551+
this_prefix_basename = "%s_echo-%s%s%s" % (spt[0], echoNumber, my_str, spt[1])
552+
break
553+
554+
# For Scout runs with multiple NIfTI images per run:
555+
if ( bids and ('scout' in this_prefix_basename.lower()) ):
556+
# in some cases (more than one slice slab), there are several
557+
# NIfTI images in the scout run, so distinguish them with "_acq-"
558+
spt = this_prefix_basename.split('_acq-Scout', 1)
559+
this_prefix_basename = "%s%s%s%s" % (spt[0], '_acq-Scout', suffix, spt[1])
560+
561+
# Fallback option:
562+
# If we have failed to modify this_prefix_basename, because it didn't fall
563+
# into any of the options above, just add the suffix at the end:
564+
if ( this_prefix_basename == prefix_basename ):
565+
this_prefix_basename = "%s%s" % (this_prefix_basename, suffix)
566+
567+
# Finally, form the outname by stitch the directory and outtype:
568+
outname = "%s/%s.%s" % (prefix_dirname, this_prefix_basename, outtype)
569+
570+
# Write the files needed:
478571
safe_copyfile(fl, outname, overwrite)
479572
if bids_file:
480-
outname_bids_file = "%s%s.json" % (prefix, suffix)
573+
outname_bids_file = "%s.json" % (outname.strip(outtype))
481574
safe_copyfile(bids_file, outname_bids_file, overwrite)
482575
bids_outfiles.append(outname_bids_file)
483576
# res_files is not a list

0 commit comments

Comments
 (0)