Skip to content

Commit

Permalink
[ENH] Output and load T2* maps in seconds (#525)
Browse files Browse the repository at this point in the history
* Output and load in T2* maps in seconds.

* Print info about T2* in seconds.

* Add comments to trigger CI.

* Maybe test t2smap arg.

* Add sec2millisec and millisec2sec functions.

* Apply @jbteves' suggestions from code review

Add millisec2sec and sec2millisec to t2smap.

Co-Authored-By: Joshua Teves <jbtevespro@gmail.com>

* Add functions to API doc page.

* Fix bad import.

* Fix mistake.

Co-authored-by: Joshua Teves <jbtevespro@gmail.com>
tsalo and jbteves authored Mar 21, 2020
1 parent d2f578c commit 4ce1f38
Showing 9 changed files with 89 additions and 22 deletions.
12 changes: 10 additions & 2 deletions docs/api.rst
Original file line number Diff line number Diff line change
@@ -116,6 +116,7 @@ API

tedana.selection.manual_selection
tedana.selection.kundu_selection_v2
tedana.selection.kundu_tedpca


.. _api_gscontrol_ref:
@@ -152,10 +153,13 @@ API
:toctree: generated/
:template: function.rst

tedana.io.split_ts
tedana.io.filewrite
tedana.io.load_data
tedana.io.filewrite
tedana.io.new_nii_like
tedana.io.save_comptable
tedana.io.load_comptable
tedana.io.add_decomp_prefix
tedana.io.split_ts
tedana.io.write_split_ts
tedana.io.writefeats
tedana.io.writeresults
@@ -199,6 +203,10 @@ API

tedana.utils.andb
tedana.utils.dice
tedana.utils.get_spectrum
tedana.utils.load_image
tedana.utils.make_adaptive_mask
tedana.utils.threshold_map
tedana.utils.unmask
tedana.utils.sec2millisec
tedana.utils.millisec2sec
14 changes: 8 additions & 6 deletions docs/outputs.rst
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ tedana derivatives
Filename Content
====================== =====================================================
t2sv.nii.gz Limited estimated T2* 3D map.
Values are in seconds.
The difference between the limited and full maps
is that, for voxels affected by dropout where
only one echo contains good data, the full map
@@ -70,12 +71,13 @@ If ``verbose`` is set to True:
====================== =====================================================
Filename Content
====================== =====================================================
t2svG.nii.gz Full T2* map/time series. The difference between
the limited and full maps is that, for voxels
affected by dropout where only one echo contains
good data, the full map uses the single echo's
value while the limited map has a NaN. Only used
for optimal combination.
t2svG.nii.gz Full T2* map/time series.
Values are in seconds.
The difference between the limited and full maps is
that, for voxels affected by dropout where only one
echo contains good data, the full map uses the
single echo's value while the limited map has a NaN.
Only used for optimal combination.
s0vG.nii.gz Full S0 map/time series. Only used for optimal
combination.
hik_ts_e[echo].nii.gz High-Kappa time series for echo number ``echo``
5 changes: 3 additions & 2 deletions tedana/metrics/__init__.py
Original file line number Diff line number Diff line change
@@ -2,8 +2,9 @@
# ex: set sts=4 ts=4 sw=4 et:

from .kundu_fit import (
dependence_metrics, kundu_metrics, get_coeffs, computefeats2
dependence_metrics, kundu_metrics
)

__all__ = [
'dependence_metrics', 'kundu_metrics', 'get_coeffs', 'computefeats2']
'dependence_metrics', 'kundu_metrics'
]
3 changes: 2 additions & 1 deletion tedana/tests/test_integration.py
Original file line number Diff line number Diff line change
@@ -105,10 +105,11 @@ def test_integration_five_echo(skip_integration):
out_dir2 = '/tmp/data/five-echo/TED.five-echo-manual'
acc_comps = df.loc[df['classification'] == 'accepted'].index.values
mixing = os.path.join(out_dir, 'ica_mixing.tsv')
t2smap = os.path.join(out_dir, 't2sv.nii.gz')
args = (['-d'] + datalist + ['-e'] + [str(te) for te in echo_times] +
['--out-dir', out_dir2, '--debug', '--verbose',
'--manacc', ','.join(acc_comps.astype(str)),
'--ctab', comptable, '--mix', mixing])
'--ctab', comptable, '--mix', mixing, '--t2smap', t2smap])
tedana_cli._main(args)

# compare the generated output files
16 changes: 16 additions & 0 deletions tedana/tests/test_utils.py
Original file line number Diff line number Diff line change
@@ -207,4 +207,20 @@ def test_smoke_threshold_map():
assert utils.threshold_map(img, min_cluster_size, sided='bi') is not None


def test_sec2millisec():
"""
Ensure that sec2millisec returns 1000x the input values.
"""
assert utils.sec2millisec(5) == 5000
assert utils.sec2millisec(np.array([5])) == np.array([5000])


def test_millisec2sec():
"""
Ensure that millisec2sec returns 1/1000x the input values.
"""
assert utils.millisec2sec(5000) == 5
assert utils.millisec2sec(np.array([5000])) == np.array([5])


# TODO: "BREAK" AND UNIT TESTS
34 changes: 34 additions & 0 deletions tedana/utils.py
Original file line number Diff line number Diff line change
@@ -329,3 +329,37 @@ def threshold_map(img, min_cluster_size, threshold=None, mask=None,
clust_thresholded = clust_thresholded[mask]

return clust_thresholded


def sec2millisec(arr):
"""
Convert seconds to milliseconds.
Parameters
----------
arr : array_like
Values in seconds.
Returns
-------
array_like
Values in milliseconds.
"""
return arr * 1000


def millisec2sec(arr):
"""
Convert milliseconds to seconds.
Parameters
----------
arr : array_like
Values in milliseconds.
Returns
-------
array_like
Values in seconds.
"""
return arr / 1000.
4 changes: 2 additions & 2 deletions tedana/viz.py
Original file line number Diff line number Diff line change
@@ -9,7 +9,7 @@
matplotlib.use('AGG')
import matplotlib.pyplot as plt

from tedana import metrics
from tedana import stats
from tedana.utils import get_spectrum

LGR = logging.getLogger(__name__)
@@ -79,7 +79,7 @@ def write_comp_figs(ts, mask, comptable, mmix, ref_img, out_dir,
LGR.warning('Provided colormap is not recognized, proceeding with default')
png_cmap = 'coolwarm'
# regenerate the beta images
ts_B = metrics.get_coeffs(ts, mmix, mask)
ts_B = stats.get_coeffs(ts, mmix, mask)
ts_B = ts_B.reshape(ref_img.shape[:3] + ts_B.shape[1:])
# trim edges from ts_B array
ts_B = trim_edge_zeros(ts_B)
7 changes: 4 additions & 3 deletions tedana/workflows/t2smap.py
Original file line number Diff line number Diff line change
@@ -216,7 +216,8 @@ def t2smap_workflow(data, tes, mask=None, fitmode='all', combmode='t2s',
# anything that is 10x higher than the 99.5 %ile will be reset to 99.5 %ile
cap_t2s = stats.scoreatpercentile(t2s_limited.flatten(), 99.5,
interpolation_method='lower')
LGR.debug('Setting cap on T2* map at {:.5f}'.format(cap_t2s * 10))
cap_t2s_sec = utils.millisec2sec(cap_t2s * 10.)
LGR.debug('Setting cap on T2* map at {:.5f}s'.format(cap_t2s_sec))
t2s_limited[t2s_limited > cap_t2s * 10] = cap_t2s

LGR.info('Computing optimal combination')
@@ -231,9 +232,9 @@ def t2smap_workflow(data, tes, mask=None, fitmode='all', combmode='t2s',
s0_limited[s0_limited < 0] = 0
t2s_limited[t2s_limited < 0] = 0

io.filewrite(t2s_limited, op.join(out_dir, 't2sv.nii'), ref_img)
io.filewrite(utils.millisec2sec(t2s_limited), op.join(out_dir, 't2sv.nii'), ref_img)
io.filewrite(s0_limited, op.join(out_dir, 's0v.nii'), ref_img)
io.filewrite(t2s_full, op.join(out_dir, 't2svG.nii'), ref_img)
io.filewrite(utils.millisec2sec(t2s_full), op.join(out_dir, 't2svG.nii'), ref_img)
io.filewrite(s0_full, op.join(out_dir, 's0vG.nii'), ref_img)
io.filewrite(OCcatd, op.join(out_dir, 'ts_OC.nii'), ref_img)

16 changes: 10 additions & 6 deletions tedana/workflows/tedana.py
Original file line number Diff line number Diff line change
@@ -270,7 +270,8 @@ def tedana_workflow(data, tes, out_dir='.', mask=None,
Name of a matplotlib colormap to be used when generating figures.
Cannot be used with --no-png. Default is 'coolwarm'.
t2smap : :obj:`str`, optional
Precalculated T2* map in the same space as the input data.
Precalculated T2* map in the same space as the input data. Values in
the map must be in seconds.
mixm : :obj:`str` or None, optional
File containing mixing matrix, to be used when re-running the workflow.
If not provided, ME-PCA and ME-ICA are done. Default is None.
@@ -442,12 +443,14 @@ def tedana_workflow(data, tes, out_dir='.', mask=None,
RepLGR.info("A user-defined mask was applied to the data.")
elif t2smap and not mask:
LGR.info('Using user-defined T2* map to generate mask')
t2s_limited = utils.load_image(t2smap)
t2s_limited_sec = utils.load_image(t2smap)
t2s_limited = utils.sec2millisec(t2s_limited_sec)
t2s_full = t2s_limited.copy()
mask = (t2s_limited != 0).astype(int)
elif t2smap and mask:
LGR.info('Combining user-defined mask and T2* map to generate mask')
t2s_limited = utils.load_image(t2smap)
t2s_limited_sec = utils.load_image(t2smap)
t2s_limited = utils.sec2millisec(t2s_limited_sec)
t2s_full = t2s_limited.copy()
mask = utils.load_image(mask)
mask[t2s_limited == 0] = 0 # reduce mask based on T2* map
@@ -471,13 +474,14 @@ def tedana_workflow(data, tes, out_dir='.', mask=None,
# anything that is 10x higher than the 99.5 %ile will be reset to 99.5 %ile
cap_t2s = stats.scoreatpercentile(t2s_limited.flatten(), 99.5,
interpolation_method='lower')
LGR.debug('Setting cap on T2* map at {:.5f}'.format(cap_t2s * 10))
LGR.debug('Setting cap on T2* map at {:.5f}s'.format(
utils.millisec2sec(cap_t2s)))
t2s_limited[t2s_limited > cap_t2s * 10] = cap_t2s
io.filewrite(t2s_limited, op.join(out_dir, 't2sv.nii'), ref_img)
io.filewrite(utils.millisec2sec(t2s_limited), op.join(out_dir, 't2sv.nii'), ref_img)
io.filewrite(s0_limited, op.join(out_dir, 's0v.nii'), ref_img)

if verbose:
io.filewrite(t2s_full, op.join(out_dir, 't2svG.nii'), ref_img)
io.filewrite(utils.millisec2sec(t2s_full), op.join(out_dir, 't2svG.nii'), ref_img)
io.filewrite(s0_full, op.join(out_dir, 's0vG.nii'), ref_img)

# optimally combine data

0 comments on commit 4ce1f38

Please sign in to comment.