|
17 | 17 | from nipype.pipeline import engine as pe
|
18 | 18 | from nipype.interfaces import utility as niu
|
19 | 19 |
|
| 20 | +from niworkflows.utils.connections import pop_file, listify |
| 21 | + |
| 22 | + |
20 | 23 | from ...utils.meepi import combine_meepi_source
|
21 | 24 |
|
22 | 25 | from ...interfaces import DerivativesDataSink
|
@@ -141,59 +144,66 @@ def init_func_preproc_wf(bold_file):
|
141 | 144 | from niworkflows.interfaces.utils import DictMerge
|
142 | 145 | from sdcflows.workflows.base import init_sdc_estimate_wf, fieldmap_wrangler
|
143 | 146 |
|
144 |
| - ref_file = bold_file |
145 | 147 | mem_gb = {'filesize': 1, 'resampled': 1, 'largemem': 1}
|
146 | 148 | bold_tlen = 10
|
147 |
| - multiecho = isinstance(bold_file, list) |
148 | 149 |
|
149 | 150 | # Have some options handy
|
150 |
| - layout = config.execution.layout |
151 | 151 | omp_nthreads = config.nipype.omp_nthreads
|
152 | 152 | freesurfer = config.workflow.run_reconall
|
153 | 153 | spaces = config.workflow.spaces
|
154 | 154 | output_dir = str(config.execution.output_dir)
|
155 | 155 |
|
| 156 | + # Extract BIDS entities and metadata from BOLD file(s) |
| 157 | + entities = extract_entities(bold_file) |
| 158 | + layout = config.execution.layout |
| 159 | + |
| 160 | + # Take first file as reference |
| 161 | + ref_file = pop_file(bold_file) |
| 162 | + metadata = layout.get_metadata(ref_file) |
| 163 | + |
| 164 | + echo_idxs = listify(entities.get("echo", [])) |
| 165 | + multiecho = len(echo_idxs) > 2 |
| 166 | + if len(echo_idxs) == 1: |
| 167 | + config.loggers.warning( |
| 168 | + f"Running a single echo <{ref_file}> from a seemingly multi-echo dataset." |
| 169 | + ) |
| 170 | + bold_file = ref_file # Just in case - drop the list |
| 171 | + |
| 172 | + if len(echo_idxs) == 2: |
| 173 | + raise RuntimeError( |
| 174 | + "Multi-echo processing requires at least three different echos (found two)." |
| 175 | + ) |
| 176 | + |
156 | 177 | if multiecho:
|
157 |
| - tes = [layout.get_metadata(echo)['EchoTime'] for echo in bold_file] |
158 |
| - ref_file = dict(zip(tes, bold_file))[min(tes)] |
| 178 | + # Drop echo entity for future queries, have a boolean shorthand |
| 179 | + entities.pop("echo", None) |
| 180 | + # reorder echoes from shortest to largest |
| 181 | + tes, bold_file = zip(*sorted([ |
| 182 | + (layout.get_metadata(bf)["EchoTime"], bf) for bf in bold_file |
| 183 | + ])) |
| 184 | + ref_file = bold_file[0] # Reset reference to be the shortest TE |
159 | 185 |
|
160 | 186 | if os.path.isfile(ref_file):
|
161 | 187 | bold_tlen, mem_gb = _create_mem_gb(ref_file)
|
162 | 188 |
|
163 | 189 | wf_name = _get_wf_name(ref_file)
|
164 | 190 | config.loggers.workflow.debug(
|
165 |
| - 'Creating bold processing workflow for "%s" (%.2f GB / %d TRs). ' |
| 191 | + 'Creating bold processing workflow for <%s> (%.2f GB / %d TRs). ' |
166 | 192 | 'Memory resampled/largemem=%.2f/%.2f GB.',
|
167 | 193 | ref_file, mem_gb['filesize'], bold_tlen, mem_gb['resampled'], mem_gb['largemem'])
|
168 | 194 |
|
169 |
| - sbref_file = None |
170 | 195 | # Find associated sbref, if possible
|
171 |
| - entities = layout.parse_file_entities(ref_file) |
172 | 196 | entities['suffix'] = 'sbref'
|
173 | 197 | entities['extension'] = ['nii', 'nii.gz'] # Overwrite extensions
|
174 |
| - files = layout.get(return_type='file', **entities) |
175 |
| - refbase = os.path.basename(ref_file) |
176 |
| - if 'sbref' in config.workflow.ignore: |
177 |
| - config.loggers.workflow.info("Single-band reference files ignored.") |
178 |
| - elif files and multiecho: |
179 |
| - config.loggers.workflow.warning( |
180 |
| - "Single-band reference found, but not supported in " |
181 |
| - "multi-echo workflows at this time. Ignoring.") |
182 |
| - elif files: |
183 |
| - sbref_file = files[0] |
184 |
| - sbbase = os.path.basename(sbref_file) |
185 |
| - if len(files) > 1: |
186 |
| - config.loggers.workflow.warning( |
187 |
| - "Multiple single-band reference files found for {}; using " |
188 |
| - "{}".format(refbase, sbbase)) |
189 |
| - else: |
190 |
| - config.loggers.workflow.info("Using single-band reference file %s.", |
191 |
| - sbbase) |
192 |
| - else: |
193 |
| - config.loggers.workflow.info("No single-band-reference found for %s.", |
194 |
| - refbase) |
| 198 | + sbref_files = layout.get(return_type='file', **entities) |
195 | 199 |
|
196 |
| - metadata = layout.get_metadata(ref_file) |
| 200 | + sbref_msg = f"No single-band-reference found for {os.path.basename(ref_file)}." |
| 201 | + if sbref_files and 'sbref' in config.workflow.ignore: |
| 202 | + sbref_msg = "Single-band reference file(s) found and ignored." |
| 203 | + elif sbref_files: |
| 204 | + sbref_msg = "Using single-band reference file(s) {}.".format( |
| 205 | + ','.join([os.path.basename(sbf) for sbf in sbref_files])) |
| 206 | + config.loggers.workflow.info(sbref_msg) |
197 | 207 |
|
198 | 208 | # Find fieldmaps. Options: (phase1|phase2|phasediff|epi|fieldmap|syn)
|
199 | 209 | fmaps = None
|
@@ -234,9 +244,6 @@ def init_func_preproc_wf(bold_file):
|
234 | 244 | 't1w2fsnative_xfm', 'fsnative2t1w_xfm']),
|
235 | 245 | name='inputnode')
|
236 | 246 | inputnode.inputs.bold_file = bold_file
|
237 |
| - if sbref_file is not None: |
238 |
| - from niworkflows.interfaces.images import ValidateImage |
239 |
| - val_sbref = pe.Node(ValidateImage(in_file=sbref_file), name='val_sbref') |
240 | 247 |
|
241 | 248 | outputnode = pe.Node(niu.IdentityInterface(
|
242 | 249 | fields=['bold_t1', 'bold_t1_ref', 'bold_mask_t1', 'bold_aseg_t1', 'bold_aparc_t1',
|
@@ -296,12 +303,13 @@ def init_func_preproc_wf(bold_file):
|
296 | 303 | ])
|
297 | 304 |
|
298 | 305 | # Generate a tentative boldref
|
299 |
| - bold_reference_wf = init_bold_reference_wf(omp_nthreads=omp_nthreads) |
| 306 | + bold_reference_wf = init_bold_reference_wf( |
| 307 | + omp_nthreads=omp_nthreads, |
| 308 | + bold_file=bold_file, |
| 309 | + sbref_files=sbref_files, |
| 310 | + multiecho=multiecho, |
| 311 | + ) |
300 | 312 | bold_reference_wf.inputs.inputnode.dummy_scans = config.workflow.dummy_scans
|
301 |
| - if sbref_file is not None: |
302 |
| - workflow.connect([ |
303 |
| - (val_sbref, bold_reference_wf, [('out_file', 'inputnode.sbref_file')]), |
304 |
| - ]) |
305 | 313 |
|
306 | 314 | # Top-level BOLD splitter
|
307 | 315 | bold_split = pe.Node(FSLSplit(dimension='t'), name='bold_split',
|
@@ -414,8 +422,6 @@ def init_func_preproc_wf(bold_file):
|
414 | 422 | workflow.connect([
|
415 | 423 | (inputnode, t1w_brain, [('t1w_preproc', 'in_file'),
|
416 | 424 | ('t1w_mask', 'in_mask')]),
|
417 |
| - # Generate early reference |
418 |
| - (inputnode, bold_reference_wf, [('bold_file', 'inputnode.bold_file')]), |
419 | 425 | # BOLD buffer has slice-time corrected if it was run, original otherwise
|
420 | 426 | (boldbuffer, bold_split, [('bold_file', 'in_file')]),
|
421 | 427 | # HMC
|
@@ -889,3 +895,40 @@ def _to_join(in_file, join_file):
|
889 | 895 | return in_file
|
890 | 896 | res = JoinTSVColumns(in_file=in_file, join_file=join_file).run()
|
891 | 897 | return res.outputs.out_file
|
| 898 | + |
| 899 | + |
| 900 | +def extract_entities(file_list): |
| 901 | + """ |
| 902 | + Return a dictionary of common entities given a list of files. |
| 903 | +
|
| 904 | + Examples |
| 905 | + -------- |
| 906 | + >>> extract_entities('sub-01/anat/sub-01_T1w.nii.gz') |
| 907 | + {'subject': '01', 'suffix': 'T1w', 'datatype': 'anat', 'extension': 'nii.gz'} |
| 908 | + >>> extract_entities(['sub-01/anat/sub-01_T1w.nii.gz'] * 2) |
| 909 | + {'subject': '01', 'suffix': 'T1w', 'datatype': 'anat', 'extension': 'nii.gz'} |
| 910 | + >>> extract_entities(['sub-01/anat/sub-01_run-1_T1w.nii.gz', |
| 911 | + ... 'sub-01/anat/sub-01_run-2_T1w.nii.gz']) |
| 912 | + {'subject': '01', 'run': [1, 2], 'suffix': 'T1w', 'datatype': 'anat', |
| 913 | + 'extension': 'nii.gz'} |
| 914 | +
|
| 915 | + """ |
| 916 | + from collections import defaultdict |
| 917 | + from bids.layout import parse_file_entities |
| 918 | + |
| 919 | + entities = defaultdict(list) |
| 920 | + for e, v in [ |
| 921 | + ev_pair |
| 922 | + for f in listify(file_list) |
| 923 | + for ev_pair in parse_file_entities(f).items() |
| 924 | + ]: |
| 925 | + entities[e].append(v) |
| 926 | + |
| 927 | + def _unique(inlist): |
| 928 | + inlist = sorted(set(inlist)) |
| 929 | + if len(inlist) == 1: |
| 930 | + return inlist[0] |
| 931 | + return inlist |
| 932 | + return { |
| 933 | + k: _unique(v) for k, v in entities.items() |
| 934 | + } |
0 commit comments