Skip to content

Commit 9585cbb

Browse files
committed
feat(get_trt): Allow estimated and fallback TotalReadoutTime
1 parent 0d86ff4 commit 9585cbb

File tree

2 files changed

+60
-1
lines changed

2 files changed

+60
-1
lines changed

sdcflows/interfaces/epi.py

+4
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
class _GetReadoutTimeInputSpec(BaseInterfaceInputSpec):
3838
in_file = File(exists=True, desc="EPI image corresponding to the metadata")
3939
metadata = traits.Dict(mandatory=True, desc="metadata corresponding to the inputs")
40+
use_estimate = traits.Bool(False, desc='Use "Estimated*" fields to calculate TotalReadoutTime')
41+
fallback = traits.Float(desc="A fallback value, in seconds.")
4042

4143

4244
class _GetReadoutTimeOutputSpec(TraitedSpec):
@@ -57,6 +59,8 @@ def _run_interface(self, runtime):
5759
self._results["readout_time"] = get_trt(
5860
self.inputs.metadata,
5961
self.inputs.in_file if isdefined(self.inputs.in_file) else None,
62+
use_estimate=self.inputs.use_estimate,
63+
fallback=self.inputs.fallback or None,
6064
)
6165
self._results["pe_direction"] = self.inputs.metadata["PhaseEncodingDirection"]
6266
self._results["pe_dir_fsl"] = (

sdcflows/utils/epimanip.py

+56-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,13 @@
2626
"""
2727

2828

29-
def get_trt(in_meta, in_file=None):
29+
def get_trt(
30+
in_meta,
31+
in_file=None,
32+
*,
33+
use_estimate: bool = False,
34+
fallback: float | None = None,
35+
):
3036
r"""
3137
Obtain the *total readout time* :math:`T_\text{ro}` from available metadata.
3238
@@ -43,6 +49,26 @@ def get_trt(in_meta, in_file=None):
4349
>>> nb.Nifti1Image(np.zeros((90, 90, 60)), None, None).to_filename(
4450
... tmpdir.join('epi.nii.gz').strpath)
4551
52+
Parameters
53+
----------
54+
in_meta: :class:`dict`
55+
BIDS metadata dictionary.
56+
in_file: :class:`str`, optional
57+
Path to the EPI file. Used to determine the number of voxels along the
58+
phase-encoding direction.
59+
use_estimate: :class:`bool`, optional
60+
Whether to use "Estimated*" fields to calculate the total readout time.
61+
These are generated by dcm2niix when authoritative metadata is not available
62+
but heuristic methods permit an estimation.
63+
fallback: :class:`float`, optional
64+
A fallback value, in seconds, to use when the total readout time cannot be
65+
calculated. This should only be used in situations where the field is to be
66+
determined from displacement fields, as in SyN-SDC.
67+
A recommended "plausible" value would be 0.03125, to minimize the impact of
68+
floating-point errors in the calculations.
69+
70+
Examples
71+
--------
4672
4773
>>> meta = {'TotalReadoutTime': 0.05251}
4874
>>> get_trt(meta)
@@ -159,6 +185,23 @@ def get_trt(in_meta, in_file=None):
159185
Traceback (most recent call last):
160186
ValueError:
161187
188+
dcm2niix may provide "EstimatedTotalReadoutTime" or "EstimatedEffectiveEchoSpacing"
189+
fields when converting Philips data. In order to use these fields, pass
190+
``use_estimate=True``:
191+
192+
>>> get_trt({'EstimatedTotalReadoutTime': 0.05251}, use_estimate=True)
193+
0.05251
194+
>>> meta = {'EstimatedEffectiveEchoSpacing': 0.00059,
195+
... 'PhaseEncodingDirection': 'j-'}
196+
>>> f"{get_trt(meta, in_file='epi.nii.gz'):g}"
197+
'0.05251'
198+
199+
Finally, if a fallback value is provided, it will be used when the total readout
200+
time cannot be calculated by any method:
201+
202+
>>> get_trt({}, fallback=0.03125)
203+
0.03125
204+
162205
.. testcleanup::
163206
164207
>>> os.chdir(cwd)
@@ -194,6 +237,13 @@ def get_trt(in_meta, in_file=None):
194237
raise ValueError(f"'{trt}'")
195238

196239
return trt
240+
elif use_estimate and "EstimatedTotalReadoutTime" in in_meta:
241+
trt = in_meta.get("EstimatedTotalReadoutTime")
242+
if not trt:
243+
raise ValueError(f"'{trt}'")
244+
245+
return trt
246+
197247

198248
# npe = N voxels PE direction
199249
pe_index = "ijk".index(in_meta["PhaseEncodingDirection"][0])
@@ -204,6 +254,8 @@ def get_trt(in_meta, in_file=None):
204254
if ees:
205255
# Effective echo spacing means that acceleration factors have been accounted for.
206256
return ees * (npe - 1)
257+
elif use_estimate and "EstimatedEffectiveEchoSpacing" in in_meta:
258+
return in_meta.get("EstimatedEffectiveEchoSpacing") * (npe - 1)
207259

208260
try:
209261
echospacing = in_meta["EchoSpacing"]
@@ -231,6 +283,9 @@ def get_trt(in_meta, in_file=None):
231283
ees = wfs / (wfs_hz * (epifactor + 1))
232284
return ees * (npe - 1)
233285

286+
if fallback:
287+
return fallback
288+
234289
raise ValueError("Unknown total-readout time specification")
235290

236291

0 commit comments

Comments
 (0)