Skip to content

Commit 96de779

Browse files
committed
Work on fixing issues with multi-file converters.
1 parent 3022c71 commit 96de779

File tree

2 files changed

+68
-10
lines changed

2 files changed

+68
-10
lines changed

heudiconv/convert.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -311,21 +311,28 @@ def update_multiecho_name(metadata, filename, echo_times):
311311
filename : str
312312
Updated filename with echo entity added, if appropriate.
313313
"""
314-
# Field maps separate echoes differently
314+
# Field maps separate echoes differently, so do not attempt to update any filenames with these
315+
# suffixes
315316
unsupported_types = [
316317
'_magnitude', '_magnitude1', '_magnitude2',
317318
'_phasediff', '_phase1', '_phase2', '_fieldmap'
318319
]
319320
if any(ut in filename for ut in unsupported_types):
320321
return filename
321322

322-
assert isinstance(echo_times, list), 'Argument "echo_times" must be a list.'
323+
if not isinstance(echo_times, list):
324+
raise TypeError(f'Argument "echo_times" must be a list, not a {type(echo_times)}')
323325

324-
# Get the EchoNumber from json file info. If not present, use EchoTime
326+
# Get the EchoNumber from json file info. If not present, use EchoTime.
325327
if 'EchoNumber' in metadata.keys():
326328
echo_number = metadata['EchoNumber']
327-
else:
329+
elif 'EchoTime' in metadata.keys():
328330
echo_number = echo_times.index(metadata['EchoTime']) + 1
331+
else:
332+
raise KeyError(
333+
'Either "EchoNumber" or "EchoTime" must be in metadata keys. '
334+
f'Keys detected: {metadata.keys()}'
335+
)
329336

330337
# Determine scan suffix
331338
filetype = '_' + filename.split('_')[-1]
@@ -366,7 +373,8 @@ def update_uncombined_name(metadata, filename, channel_names):
366373
if any(ut in filename for ut in unsupported_types):
367374
return filename
368375

369-
assert isinstance(channel_names, list), 'Argument "channel_names" must be a list.'
376+
if not isinstance(channel_names, list):
377+
raise TypeError(f'Argument "channel_names" must be a list, not a {type(channel_names)}')
370378

371379
# Determine the channel number
372380
channel_number = ''.join([c for c in metadata['CoilString'] if c.isdigit()])
@@ -694,9 +702,12 @@ def save_converted_files(res, item_dicoms, bids_options, outtype, prefix, outnam
694702
for metadata in bids_metas:
695703
if not metadata:
696704
continue
705+
706+
# If the field is not available, fill that entry in the set with a False.
697707
echo_times.add(metadata.get('EchoTime', False))
698708
channel_names.add(metadata.get('CoilString', False))
699709
image_types.update(metadata.get('ImageType', [False]))
710+
700711
is_multiecho = len(set(filter(bool, echo_times))) > 1 # Check for varying echo times
701712
is_uncombined = len(set(filter(bool, channel_names))) > 1 # Check for uncombined data
702713
is_complex = 'M' in image_types and 'P' in image_types # Determine if data are complex (magnitude + phase)

heudiconv/tests/test_convert.py

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"""Test functions in heudiconv.convert module.
22
"""
33
import pytest
4-
5-
from heudiconv.convert import (update_complex_name,
6-
update_multiecho_name,
7-
update_uncombined_name)
84
from heudiconv.bids import BIDSError
5+
from heudiconv.convert import (
6+
update_complex_name,
7+
update_multiecho_name,
8+
update_uncombined_name,
9+
)
910

1011

1112
def test_update_complex_name():
@@ -19,16 +20,19 @@ def test_update_complex_name():
1920
out_fn_true = 'sub-X_ses-Y_task-Z_rec-phase_run-01_sbref'
2021
out_fn_test = update_complex_name(metadata, fn, suffix)
2122
assert out_fn_test == out_fn_true
23+
2224
# Catch an unsupported type and *do not* update
2325
fn = 'sub-X_ses-Y_task-Z_run-01_phase'
2426
out_fn_test = update_complex_name(metadata, fn, suffix)
2527
assert out_fn_test == fn
28+
2629
# Data type is missing from metadata so use suffix
2730
fn = 'sub-X_ses-Y_task-Z_run-01_sbref'
2831
metadata = {'ImageType': ['ORIGINAL', 'PRIMARY', 'MB', 'TE3', 'ND', 'MOSAIC']}
2932
out_fn_true = 'sub-X_ses-Y_task-Z_rec-3_run-01_sbref'
3033
out_fn_test = update_complex_name(metadata, fn, suffix)
3134
assert out_fn_test == out_fn_true
35+
3236
# Catch existing field with value that *does not match* metadata
3337
# and raise Exception
3438
fn = 'sub-X_ses-Y_task-Z_rec-magnitude_run-01_sbref'
@@ -50,15 +54,37 @@ def test_update_multiecho_name():
5054
out_fn_true = 'sub-X_ses-Y_task-Z_run-01_echo-1_bold'
5155
out_fn_test = update_multiecho_name(metadata, fn, echo_times)
5256
assert out_fn_test == out_fn_true
57+
5358
# EchoNumber field is missing from metadata, so use echo_times
5459
metadata = {'EchoTime': 0.01}
5560
out_fn_test = update_multiecho_name(metadata, fn, echo_times)
5661
assert out_fn_test == out_fn_true
62+
5763
# Catch an unsupported type and *do not* update
5864
fn = 'sub-X_ses-Y_task-Z_run-01_phasediff'
5965
out_fn_test = update_multiecho_name(metadata, fn, echo_times)
6066
assert out_fn_test == fn
6167

68+
# EchoTime is missing, but use EchoNumber (which is the first thing it checks)
69+
fn = 'sub-X_ses-Y_task-Z_run-01_bold'
70+
out_fn_true = 'sub-X_ses-Y_task-Z_run-01_echo-1_bold'
71+
metadata = {'EchoNumber': 1}
72+
echo_times = [False, 0.02, 0.03]
73+
out_fn_test = update_multiecho_name(metadata, fn, echo_times)
74+
assert out_fn_test == out_fn_true
75+
76+
# Both EchoTime and EchoNumber are missing, which raises a KeyError
77+
fn = 'sub-X_ses-Y_task-Z_run-01_bold'
78+
metadata = {}
79+
echo_times = [False, 0.02, 0.03]
80+
with pytest.raises(KeyError):
81+
update_multiecho_name(metadata, fn, echo_times)
82+
83+
# Providing echo times as something other than a list should raise a TypeError
84+
fn = 'sub-X_ses-Y_task-Z_run-01_bold'
85+
with pytest.raises(TypeError):
86+
update_multiecho_name(metadata, fn, set(echo_times))
87+
6288

6389
def test_update_uncombined_name():
6490
"""Unit testing for heudiconv.convert.update_uncombined_name(), which updates
@@ -71,8 +97,29 @@ def test_update_uncombined_name():
7197
out_fn_true = 'sub-X_ses-Y_task-Z_run-01_ch-01_bold'
7298
out_fn_test = update_uncombined_name(metadata, fn, channel_names)
7399
assert out_fn_test == out_fn_true
74-
# CoilString field has no number in it
100+
101+
# CoilString field has no number in it, so we index the channel_names list
75102
metadata = {'CoilString': 'HEA;HEP'}
76103
out_fn_true = 'sub-X_ses-Y_task-Z_run-01_ch-04_bold'
77104
out_fn_test = update_uncombined_name(metadata, fn, channel_names)
78105
assert out_fn_test == out_fn_true
106+
107+
# Extract the number from the CoilString and use that
108+
channel_names = ['H1', 'B1', 'H3', 'HEA;HEP']
109+
metadata = {'CoilString': 'H1'}
110+
out_fn_true = 'sub-X_ses-Y_task-Z_run-01_ch-01_bold'
111+
out_fn_test = update_uncombined_name(metadata, fn, channel_names)
112+
assert out_fn_test == out_fn_true
113+
114+
# NOTE: Extracting the number does not protect against multiple coils with the same number
115+
# (but, say, different letters)
116+
# Note that this is still "ch-01"
117+
metadata = {'CoilString': 'B1'}
118+
out_fn_true = 'sub-X_ses-Y_task-Z_run-01_ch-01_bold'
119+
out_fn_test = update_uncombined_name(metadata, fn, channel_names)
120+
assert out_fn_test == out_fn_true
121+
122+
# Providing echo times as something other than a list should raise a TypeError
123+
fn = 'sub-X_ses-Y_task-Z_run-01_bold'
124+
with pytest.raises(TypeError):
125+
update_uncombined_name(metadata, fn, set(channel_names))

0 commit comments

Comments
 (0)