|
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 |
| 20 | +from niworkflows.utils.connections import pop_file, listify |
| 21 | + |
21 | 22 |
|
22 | 23 | from ...utils.meepi import combine_meepi_source |
23 | 24 |
|
@@ -152,23 +153,30 @@ def init_func_preproc_wf(bold_file): |
152 | 153 | spaces = config.workflow.spaces |
153 | 154 | output_dir = str(config.execution.output_dir) |
154 | 155 |
|
155 | | - # Extract BIDS entities and metadata from bold reference file |
156 | | - ref_file = pop_file(bold_file) |
| 156 | + # Extract BIDS entities and metadata from BOLD file(s) |
| 157 | + entities = extract_entities(bold_file) |
157 | 158 | layout = config.execution.layout |
158 | | - entities = layout.parse_file_entities(ref_file) |
| 159 | + |
| 160 | + # Take first file as reference |
| 161 | + ref_file = pop_file(bold_file) |
159 | 162 | metadata = layout.get_metadata(ref_file) |
160 | 163 |
|
161 | | - # Drop echo entity for future queries, have a boolean shorthand |
162 | | - multiecho = bool(entities.pop("echo", None)) |
163 | | - if multiecho and (isinstance(bold_file, str) or len(bold_file) == 1): |
164 | | - multiecho = False |
| 164 | + echo_idxs = listify(entities.get("echo", [])) |
| 165 | + multiecho = len(echo_idxs) > 2 |
| 166 | + if len(echo_idxs) == 1: |
165 | 167 | config.loggers.warning( |
166 | | - f"Only one echo <{ref_file}> found in a seemingly multi-echo dataset. " |
167 | | - "Falling back to single-echo mode." |
| 168 | + f"Running a single echo <{ref_file}> from a seemingly multi-echo dataset." |
168 | 169 | ) |
169 | 170 | bold_file = ref_file # Just in case - drop the list |
170 | 171 |
|
| 172 | + if len(echo_idxs) == 2: |
| 173 | + raise RuntimeError( |
| 174 | + "Multi-echo processing requires at least three different echos (found two)." |
| 175 | + ) |
| 176 | + |
171 | 177 | if multiecho: |
| 178 | + # Drop echo entity for future queries, have a boolean shorthand |
| 179 | + entities.pop("echo", None) |
172 | 180 | # reorder echoes from shortest to largest |
173 | 181 | tes, bold_file = zip(*sorted([ |
174 | 182 | (layout.get_metadata(bf)["EchoTime"], bf) for bf in bold_file |
@@ -887,3 +895,40 @@ def _to_join(in_file, join_file): |
887 | 895 | return in_file |
888 | 896 | res = JoinTSVColumns(in_file=in_file, join_file=join_file).run() |
889 | 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