Skip to content

Commit 663ac2d

Browse files
Merge pull request #1 from smoia/fix/pidxcall
Fix lack of provided peaks when not necessary
2 parents c918f80 + 7398832 commit 663ac2d

9 files changed

Lines changed: 509 additions & 14 deletions

File tree

phys2cvr/io.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ def load_txt(fname, shape=None):
119119
'.txt': ' ',
120120
'.1d': ' ',
121121
'.par': ' ',
122+
'': ' ',
122123
}
123124

124125
mtx = np.genfromtxt(fname, delimiter=delimiter_map.get(ext))
@@ -235,10 +236,11 @@ def load_array(fname, shape=''):
235236
"""
236237
_, _, ext = utils.check_ext(EXT_ARRAY, fname, scan=True, remove=True)
237238

238-
if ext.lower() in EXT_1D:
239-
return load_txt(fname, shape=shape)
240-
if ext.lower() in EXT_MAT:
241-
return load_mat(fname, shape=shape)
239+
if ext:
240+
if ext.lower() in EXT_1D:
241+
return load_txt(fname, shape=shape)
242+
if ext.lower() in EXT_MAT:
243+
return load_mat(fname, shape=shape)
242244

243245
raise NotImplementedError(
244246
f'{fname} file extension {ext} was not found or is not supported yet'
@@ -268,7 +270,7 @@ def load_physio(fname):
268270

269271

270272
def export_regressor(
271-
regressors_matrix, ntp, outname, suffix='petco2hrf', ext='.1D', axis=-1
273+
regressors_matrix, ntp, outprefix, suffix='petco2hrf', ext='.1D', axis=-1
272274
):
273275
"""
274276
Export generated regressors for fMRI analysis.
@@ -279,7 +281,7 @@ def export_regressor(
279281
The regressors that needs to be exported, in its original sample
280282
ntp : int
281283
The number of fMRI timepoints
282-
outname : str or path
284+
outprefix : str or path
283285
Prefix of the output file - can contain a path.
284286
suffix : str, optional
285287
The suffix of the output file.
@@ -301,7 +303,7 @@ def export_regressor(
301303
regressors_demeaned = regressors_matrix - regressors_matrix.mean(
302304
axis=axis, keepdims=True
303305
)
304-
np.savetxt(f'{outname}_{suffix}{ext}', regressors_demeaned, fmt='%.6f')
306+
np.savetxt(f'{outprefix}_{suffix}{ext}', regressors_demeaned, fmt='%.6f')
305307
return regressors_demeaned
306308

307309

phys2cvr/regressors.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ def compute_petco2hrf(
100100
'Arrays with more than 2 dimensions are not supported.'
101101
)
102102

103-
if comp_endtidal:
103+
if comp_endtidal and pidx is not None:
104104
petco2 = endtidal_interpolation(co2, pidx, axis=-1)
105105

106106
# Plot PetCO2 vs CO2
@@ -111,6 +111,10 @@ def compute_petco2hrf(
111111
# Demean and export
112112
petco2 = petco2 - petco2.mean()
113113
np.savetxt(f'{outprefix}_petco2.1D', petco2, fmt='%.18f')
114+
elif comp_endtidal and pidx is None:
115+
raise ValueError(
116+
'End-tidal interpolation was requested, but peaks were not provided.'
117+
)
114118
else:
115119
LGR.info(
116120
'Skipping End Tidal interpolation of PetCO2 trace (if you provided raw CO2 '

phys2cvr/signal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def create_hrf(freq=40):
7070
u = np.arange(0, p[6] / dt + 1, 1) - p[5] / dt
7171

7272
a1 = p[0] / p[2]
73-
b1 = 1 / p[3]
73+
b1 = 1 / p[2]
7474
a2 = p[1] / p[3]
7575
b2 = 1 / p[3]
7676

phys2cvr/tests/test_io.py

Lines changed: 108 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@
22
"""Tests for io."""
33

44
import os
5+
import sys
56

67
import nibabel as nib
78
import numpy as np
9+
import peakdet as pk
10+
import pymatreader
11+
import pytest
12+
from scipy.io import savemat
813

914
from phys2cvr import io
1015

@@ -27,8 +32,78 @@ def test_load_nifti_get_mask(nifti_data, nifti_mask):
2732
assert isinstance(img, nib.nifti1.Nifti1Image)
2833

2934

35+
@pytest.mark.parametrize(
36+
'extension, delimiter',
37+
[
38+
['.csv', ','],
39+
['.csv.gz', ','],
40+
['.tsv', '\t'],
41+
['.tsv.gz', '\t'],
42+
['.txt', ' '],
43+
['.1d', ' '],
44+
['.par', ' '],
45+
['', ' '],
46+
],
47+
)
48+
def test_load_txt(extension, delimiter, testdir):
49+
"""Test load_txt."""
50+
a = np.random.randn(5)
51+
n = os.join.path(testdir, f'zoe{extension}')
52+
np.savetxt(n, a, delimiter=delimiter)
53+
b = io.load_txt(n)
54+
55+
assert (a == b).all()
56+
os.remove(n)
57+
58+
59+
def test_load_mat():
60+
"""Test load_mat."""
61+
a = np.random.randn(5)
62+
b = 'likealeaf'
63+
n = 'inthewind'
64+
65+
savemat(n, {'data': a, 'other': b})
66+
67+
c = io.load_mat(n)
68+
69+
assert (a == c).all()
70+
os.remove(n)
71+
72+
73+
def test_load_array():
74+
"""Test load_mat."""
75+
a = np.random.randn(5)
76+
b = 'likealeaf'
77+
n = 'inthewind.mat'
78+
m = 'zoe.txt'
79+
np.savetxt(m, a)
80+
81+
savemat(n, {'data': a, 'other': b})
82+
83+
d = io.load_array(n)
84+
e = io.load_array(m)
85+
86+
assert (a == d).all()
87+
assert (a == e).all()
88+
os.remove(n)
89+
os.remove(m)
90+
91+
92+
def test_load_physio():
93+
d = pk.Physio(np.random.randn(40), fs=10)
94+
d = pk.peakfind_physio(d)
95+
n = 'kaylee.phys'
96+
pk.save_physio(n, d)
97+
p = io.load_physio(n)
98+
99+
assert (p[0] == d.data).all()
100+
assert (p[1] == d.peaks).all()
101+
assert p[2] == 10
102+
os.remove(n)
103+
104+
30105
def test_export_regressor(testdir):
31-
petco2hrf_shift = np.random.rand(10)
106+
petco2hrf_shift = np.random.randn(10)
32107
ntp = 10
33108
outname = os.path.join(testdir, 'test_regressor')
34109
suffix = 'petco2hrf'
@@ -49,7 +124,7 @@ def test_export_regressor(testdir):
49124

50125
def test_export_nifti(testdir):
51126
# create some test data
52-
data = np.random.rand(10, 10, 10)
127+
data = np.random.randn(10, 10, 10)
53128
affine = np.eye(4)
54129
header = nib.Nifti1Header()
55130
img = nib.Nifti1Image(data, affine, header)
@@ -66,8 +141,39 @@ def test_export_nifti(testdir):
66141
assert np.allclose(out_img.get_fdata(), data, atol=1e-6, rtol=0)
67142
assert np.allclose(out_img.affine, affine, atol=1e-6, rtol=0)
68143
assert out_img.header.__dict__.keys() == header.__dict__.keys()
144+
os.remove(fname)
69145
# Eventually check that headers have the same content (although they don't now
70146
# for good reasons!)
71147

72148

73149
# ## Break tests
150+
def test_break_load_mat():
151+
"""Break load_mat."""
152+
sys.modules['pymatreader'] = None
153+
with pytest.raises(ImportError) as errorinfo:
154+
io.load_mat('simon')
155+
assert 'is required' in str(errorinfo.value)
156+
sys.modules['pymatreader'] = pymatreader
157+
158+
a = 'heart'
159+
n = 'ofgold'
160+
savemat(n, {'data': a})
161+
162+
with pytest.raises(EOFError) as errorinfo:
163+
io.load_mat(n)
164+
assert f'{n} does not seem' in str(errorinfo.value)
165+
os.remove(n)
166+
167+
168+
def test_break_load_xls():
169+
"""Break load_xls."""
170+
with pytest.raises(NotImplementedError) as errorinfo:
171+
io.load_xls('firefly')
172+
assert 'loading is not' in str(errorinfo.value)
173+
174+
175+
def test_break_load_array():
176+
"""Break load_xls."""
177+
with pytest.raises(NotImplementedError) as errorinfo:
178+
io.load_array('firefly.leaf')
179+
assert 'file extension' in str(errorinfo.value)

phys2cvr/tests/test_regressors.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
#!/usr/bin/env python3
2+
"""Tests for io."""
3+
4+
import os
5+
from unittest.mock import patch
6+
7+
import numpy as np
8+
import pytest
9+
10+
from phys2cvr import regressors
11+
12+
# ## Unit tests
13+
14+
15+
def test_create_legendre():
16+
L = regressors.create_legendre(3, 10)
17+
assert L.shape == (10, 4)
18+
19+
L = regressors.create_legendre(1, 5)
20+
assert np.allclose(L[:, 1], np.linspace(-1, 1, 5))
21+
22+
L = regressors.create_legendre(0, 7)
23+
assert L.shape == (7, 1)
24+
assert np.all(L[:, 0] == 1)
25+
26+
27+
@patch(
28+
'phys2cvr.regressors.endtidal_interpolation', return_value=np.array([1, 2, 3, 4])
29+
)
30+
@patch('phys2cvr.regressors.plot_two_timeseries')
31+
@patch('phys2cvr.regressors.convolve_signal', return_value=np.array([9, 8, 7, 6]))
32+
def test_compute_petco2hrf(mock_conv, mock_plot, mock_endtidal, testdir):
33+
out = os.path.join(testdir, 'Lune')
34+
co2 = np.array([1, 2, 3, 4])
35+
pidx = np.array([0, 2])
36+
r = regressors.compute_petco2hrf(co2, pidx, 1.0, out)
37+
assert np.all(r == np.array([9, 8, 7, 6]))
38+
os.remove(f'{out}_petco2.1D')
39+
40+
41+
def test_compute_petco2hrf_skip(testdir):
42+
out = os.path.join(testdir, 'Catherine')
43+
r = regressors.compute_petco2hrf(
44+
np.array([1, 2, 3]),
45+
np.array([0]),
46+
1.0,
47+
out,
48+
comp_endtidal=False,
49+
response_function=None,
50+
)
51+
assert np.allclose(r, np.array([-1, 0, 1])) # returns petco2 demeaned
52+
53+
54+
@patch('phys2cvr.regressors.x_corr', return_value=(None, 3, np.array([1, 2, 3])))
55+
@patch('phys2cvr.regressors.plot_xcorr')
56+
def test_compute_bulk_shift(mock_plotx, mock_xcorr, testdir):
57+
out = os.path.join(testdir, 'Monoco')
58+
func = np.arange(10.0)
59+
pet = np.arange(20.0)
60+
s = regressors.compute_bulk_shift(func, pet, 1.0, out)
61+
assert s == 3
62+
assert os.path.exists(f'{out}_optshift.1D')
63+
os.remove(f'{out}_optshift.1D')
64+
65+
66+
@patch('phys2cvr.regressors.export_regressor', return_value=np.zeros((5, 5)))
67+
def test_create_fine_shift_regressors(mock_export, testdir):
68+
out = os.path.join(testdir, 'Esquie')
69+
pet = np.arange(50.0)
70+
r = regressors.create_fine_shift_regressors(pet, 3, 2, 1.0, 10, 20, out)
71+
assert r.shape == (5, 5)
72+
73+
74+
@patch('phys2cvr.regressors.export_regressor', return_value=np.zeros((3, 3)))
75+
def test_create_fine_shift_regressors_padding(mock_export, testdir):
76+
out = os.path.join(testdir, 'Maelle')
77+
pet = np.arange(10.0)
78+
r = regressors.create_fine_shift_regressors(pet, 9, 4, 1.0, 5, 8, out)
79+
assert r.shape == (3, 3)
80+
81+
82+
@patch('phys2cvr.regressors.resample_signal_freqs', return_value=np.arange(20.0))
83+
@patch('phys2cvr.regressors.plot_two_timeseries')
84+
@patch('phys2cvr.regressors.export_regressor', return_value=np.arange(10.0))
85+
def test_create_physio_regressor(mock_exp, mock_plot, mock_bs, testdir):
86+
out = os.path.join(testdir, 'Sciel')
87+
func = np.arange(10.0)
88+
pet = np.arange(30.0)
89+
d, lags = regressors.create_physio_regressor(
90+
func, pet, 1.0, 1.0, out, lag_max=2, skip_xcorr=True
91+
)
92+
assert d.shape == (10,)
93+
assert lags is not None
94+
95+
96+
@patch('phys2cvr.regressors.resample_signal_freqs', return_value=np.arange(20.0))
97+
@patch('phys2cvr.regressors.export_regressor', return_value=np.arange(10.0))
98+
def test_create_physio_regressor_no_lag_max(mock_exp, mock_bs, testdir):
99+
out = os.path.join(testdir, 'Francois')
100+
func = np.arange(10.0)
101+
pet = np.arange(30.0)
102+
d, lags = regressors.create_physio_regressor(
103+
func, pet, 1.0, 1.0, out, lag_max=None, skip_xcorr=True
104+
)
105+
assert d.shape == (10,)
106+
assert lags is None
107+
os.remove(f'{out}_petco2hrf_vs_avgroi.png')
108+
109+
110+
# ## Break tests
111+
112+
113+
def test_break_compute_petco2hrf():
114+
with pytest.raises(NotImplementedError) as errorinfo:
115+
regressors.compute_petco2hrf(np.zeros((2, 2)), np.array([0]), 1.0, 'x')
116+
assert 'Arrays with more' in str(errorinfo.value)

0 commit comments

Comments
 (0)