Skip to content

Commit e3bf4e8

Browse files
committed
Working version of build_tensor
1 parent 235169c commit e3bf4e8

File tree

2 files changed

+227
-36
lines changed

2 files changed

+227
-36
lines changed

pynapple/process/warping.py

Lines changed: 77 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def wrapper(*args, **kwargs):
2525
"time_unit": (str,),
2626
"align": (str,),
2727
"padding_value": (Number,),
28+
"num_bin" : (int,)
2829
}
2930
for param, param_type in parameters_type.items():
3031
if param in kwargs:
@@ -39,25 +40,35 @@ def wrapper(*args, **kwargs):
3940
return wrapper
4041

4142

42-
def _build_tensor_from_tsgroup(input, ep, binsize, align, padding_value):
43+
def _build_tensor_from_tsgroup(input, ep, binsize, align, padding_value, time_unit):
4344
# Determine size of tensor
45+
binsize = float(nap.TsIndex.format_timestamps(np.array([binsize]), time_unit)[0])
4446
n_t = int(np.max(np.ceil((ep.end + binsize - ep.start) / binsize)))
45-
output = np.ones(shape=(len(input), len(ep), n_t)) * padding_value
4647
count = input.count(bin_size=binsize, ep=ep)
4748

49+
if len(count.shape) == 1:
50+
output = np.ones(shape=(1, len(ep), n_t)) * padding_value
51+
else:
52+
output = np.ones(shape=(count.shape[1], len(ep), n_t)) * padding_value
53+
54+
n_ep = np.zeros(len(ep), dtype="int") # To trim to the minimum length
55+
4856
if align == "start":
4957
for i in range(len(ep)):
5058
tmp = count.get(ep.start[i], ep.end[i]).values
59+
n_ep[i] = tmp.shape[0]
5160
output[:, i, 0 : tmp.shape[0]] = np.transpose(tmp)
52-
if np.all(np.isnan(output[:, :, -1])):
53-
output = output[:, :, 0:-1]
61+
output = output[:, :, 0 : np.max(n_ep)]
5462

5563
if align == "end":
5664
for i in range(len(ep)):
5765
tmp = count.get(ep.start[i], ep.end[i]).values
66+
n_ep[i] = tmp.shape[0]
5867
output[:, i, -tmp.shape[0] :] = np.transpose(tmp)
59-
if np.all(np.isnan(output[:, :, 0])):
60-
output = output[:, :, 1:]
68+
output = output[:, :, -np.max(n_ep) :]
69+
70+
if len(count.shape) == 1: # Removing first axis if Ts.
71+
output = output[0]
6172

6273
return output
6374

@@ -100,7 +111,7 @@ def build_tensor(
100111
101112
Parameters
102113
----------
103-
input : Tsd, TsdFrame, TsdTensor or TsGroup
114+
input : Ts, Tsd, TsdFrame, TsdTensor or TsGroup
104115
Input to slice and align to the trials within the `ep` parameter.
105116
ep : IntervalSet
106117
Epochs holding the trials. Each interval can be of unequal size.
@@ -121,65 +132,96 @@ def build_tensor(
121132
RuntimeError
122133
If `time_unit` not in ["s", "ms", "us"]
123134
124-
125135
Examples
126136
--------
127-
128-
137+
>>> import pynapple as nap
138+
>>> import numpy as np
139+
>>> group = nap.TsGroup({0:nap.Ts(t=np.arange(0, 100))})
140+
>>> ep = nap.IntervalSet(start=np.arange(20, 100, 20), end=np.arange(20, 100, 20) + np.arange(2, 10, 2))
141+
>>> print(ep)
142+
index start end
143+
0 20 22
144+
1 40 44
145+
2 60 66
146+
3 80 88
147+
shape: (4, 2), time unit: sec.
148+
149+
Create a trial-based tensor by counting events within 1 second bin for each interval of `ep`.
150+
151+
>>> tensor = nap.build_tensor(group, ep, binsize=1)
152+
>>> tensor
153+
array([[[ 1., 1., nan, nan, nan, nan, nan, nan],
154+
[ 1., 1., 1., 1., nan, nan, nan, nan],
155+
[ 1., 1., 1., 1., 1., 1., nan, nan],
156+
[ 1., 1., 1., 1., 1., 1., 1., 1.]]])
157+
158+
By default, the time series are aligned to the start of the epochs. The parameter `align` control this behavior.
159+
160+
>>> tensor = nap.build_tensor(group, ep, binsize=1, align="end")
161+
>>> tensor
162+
array([[[nan, nan, nan, nan, nan, nan, 1., 1.],
163+
[nan, nan, nan, nan, 1., 1., 1., 1.],
164+
[nan, nan, 1., 1., 1., 1., 1., 1.],
165+
[ 1., 1., 1., 1., 1., 1., 1., 1.]]])
166+
167+
This function works for any time series.
168+
169+
>>> tsdframe = nap.TsdFrame(t=np.arange(100), d=np.arange(200).reshape(2,100).T)
170+
>>> tensor = nap.build_tensor(tsdframe, ep)
171+
>>> tensor
172+
array([[[ 20., 21., 22., nan, nan, nan, nan, nan, nan],
173+
[ 40., 41., 42., 43., 44., nan, nan, nan, nan],
174+
[ 60., 61., 62., 63., 64., 65., 66., nan, nan],
175+
[ 80., 81., 82., 83., 84., 85., 86., 87., 88.]],
176+
[[120., 121., 122., nan, nan, nan, nan, nan, nan],
177+
[140., 141., 142., 143., 144., nan, nan, nan, nan],
178+
[160., 161., 162., 163., 164., 165., 166., nan, nan],
179+
[180., 181., 182., 183., 184., 185., 186., 187., 188.]]])
129180
130181
"""
131182
if time_unit not in ["s", "ms", "us"]:
132183
raise RuntimeError("time_unit should be 's', 'ms' or 'us'")
133184
if align not in ["start", "end"]:
134185
raise RuntimeError("align should be 'start' or 'end'")
135186

136-
if isinstance(input, nap.TsGroup):
187+
if isinstance(input, (nap.TsGroup, nap.Ts)):
137188
if not isinstance(binsize, Number):
138-
raise RuntimeError("When input is a TsGroup, binsize should be specified")
139-
return _build_tensor_from_tsgroup(input, ep, binsize, align, padding_value)
140-
141-
if isinstance(input, (nap.Tsd, nap.TsdFrame, nap.TsdTensor)):
189+
raise RuntimeError(
190+
"When input is a TsGroup or Ts object, binsize should be specified"
191+
)
192+
return _build_tensor_from_tsgroup(
193+
input, ep, binsize, align, padding_value, time_unit
194+
)
195+
else:
142196
return _build_tensor_from_tsd(input, ep, align, padding_value)
143197

144198

145199
@_validate_warping_inputs
146-
def warp_tensor(input, ep, num_bin=None, align="start"):
200+
def warp_tensor(input, ep, num_bin=None):
147201
"""
148-
Return time-warped trial-based tensor from an IntervalSet object.
202+
Return linearly time-warped trial-based tensor from an IntervalSet object.
149203
150-
- If `input` is a `TsGroup`, returns a numpy array of shape (number of group element, number of trial, number of time bins). The `binsize` parameter determines the number of time bins.
204+
- If `input` is a `TsGroup`, returns a numpy array of shape (number of group element, number of trial, `num_bin`).
151205
152-
- If `input` is `Tsd`, `TsdFrame` or `TsdTensor`, returns a numpy array of shape (shape of time series, number of trial, number of time points).
206+
- If `input` is `Tsd`, `TsdFrame` or `TsdTensor`, returns a numpy array of shape (shape of time series, number of trial, `num_bin`).
153207
154208
155209
Parameters
156210
----------
157-
input : Tsd, TsdFrame, TsdTensor or TsGroup
158-
Returns a numpy array.
211+
input : Ts, Tsd, TsdFrame, TsdTensor or TsGroup
212+
Input object
159213
ep : IntervalSet
160214
Epochs holding the trials. Each interval can be of unequal size.
161-
binsize : Number, optional
162-
align: str, optional
163-
How to align the time series ('start' [default], 'end')
164-
padding_value: Number, optional
165-
How to pad the array if unequal intervals. Default is np.nan.
166-
time_unit : str, optional
167-
Time units of the binsize parameter ('s' [default], 'ms', 'us').
215+
num_bin : int
168216
169217
Returns
170218
-------
171219
numpy.ndarray
172220
173-
Raises
174-
------
175-
RuntimeError
176-
If `time_unit` not in ["s", "ms", "us"]
177-
178-
179221
Examples
180222
--------
181223
182224
183225
184226
"""
185-
pass
227+

tests/test_warping.py

Lines changed: 150 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ def test_build_tensor_runtime_error():
103103
)
104104

105105
with pytest.raises(
106-
RuntimeError, match=r"When input is a TsGroup, binsize should be specified"
106+
RuntimeError,
107+
match=r"When input is a TsGroup or Ts object, binsize should be specified",
107108
):
108109
nap.build_tensor(group, ep)
109110

@@ -140,3 +141,151 @@ def test_build_tensor_with_group():
140141

141142
tensor = nap.build_tensor(group, ep, binsize=1, align="end")
142143
np.testing.assert_array_almost_equal(tensor, np.flip(expected, axis=2))
144+
145+
tensor = nap.build_tensor(group, ep, binsize=1, time_unit="s")
146+
np.testing.assert_array_almost_equal(tensor, expected)
147+
148+
tensor = nap.build_tensor(group, ep, binsize=1e3, time_unit="ms")
149+
np.testing.assert_array_almost_equal(tensor, expected)
150+
151+
tensor = nap.build_tensor(group, ep, binsize=1e6, time_unit="us")
152+
np.testing.assert_array_almost_equal(tensor, expected)
153+
154+
tensor = nap.build_tensor(group, ep, binsize=1, align="start", padding_value=-1)
155+
expected[np.isnan(expected)] = -1
156+
np.testing.assert_array_almost_equal(tensor, expected)
157+
158+
159+
def test_build_tensor_with_ts():
160+
ts = nap.Ts(t=np.arange(0, 100))
161+
ep = nap.IntervalSet(
162+
start=np.arange(0, 100, 20), end=np.arange(0, 100, 20) + np.arange(0, 10, 2)
163+
)
164+
165+
expected = np.ones((len(ep), 8)) * np.nan
166+
for i, k in zip(range(len(ep)), range(2, 10, 2)):
167+
expected[i, 0:k] = 1
168+
169+
tensor = nap.build_tensor(ts, ep, binsize=1)
170+
np.testing.assert_array_almost_equal(tensor, expected)
171+
172+
tensor = nap.build_tensor(ts, ep, binsize=1, align="start")
173+
np.testing.assert_array_almost_equal(tensor, expected)
174+
175+
tensor = nap.build_tensor(ts, ep, binsize=1, align="end")
176+
np.testing.assert_array_almost_equal(tensor, np.flip(expected, axis=1))
177+
178+
tensor = nap.build_tensor(ts, ep, binsize=1, time_unit="s")
179+
np.testing.assert_array_almost_equal(tensor, expected)
180+
181+
tensor = nap.build_tensor(ts, ep, binsize=1e3, time_unit="ms")
182+
np.testing.assert_array_almost_equal(tensor, expected)
183+
184+
tensor = nap.build_tensor(ts, ep, binsize=1e6, time_unit="us")
185+
np.testing.assert_array_almost_equal(tensor, expected)
186+
187+
tensor = nap.build_tensor(ts, ep, binsize=1, align="start", padding_value=-1)
188+
expected[np.isnan(expected)] = -1
189+
np.testing.assert_array_almost_equal(tensor, expected)
190+
191+
192+
def test_build_tensor_with_tsd():
193+
tsd = nap.Tsd(t=np.arange(100), d=np.arange(100))
194+
ep = nap.IntervalSet(
195+
start=np.arange(0, 100, 20), end=np.arange(0, 100, 20) + np.arange(0, 10, 2)
196+
)
197+
198+
expected = np.ones((len(ep), 9)) * np.nan
199+
for i, k in zip(range(len(ep)), range(2, 10, 2)):
200+
expected[i, 0 : k + 1] = np.arange(k * 10, k * 10 + k + 1)
201+
202+
tensor = nap.build_tensor(tsd, ep)
203+
np.testing.assert_array_almost_equal(tensor, expected)
204+
205+
tensor = nap.build_tensor(tsd, ep, align="start")
206+
np.testing.assert_array_almost_equal(tensor, expected)
207+
208+
tensor = nap.build_tensor(tsd, ep, align="start", padding_value=-1)
209+
expected[np.isnan(expected)] = -1
210+
np.testing.assert_array_almost_equal(tensor, expected)
211+
212+
expected = np.ones((len(ep), 9)) * np.nan
213+
for i, k in zip(range(len(ep)), range(2, 10, 2)):
214+
expected[i, -k - 1 :] = np.arange(k * 10, k * 10 + k + 1)
215+
216+
tensor = nap.build_tensor(tsd, ep, align="end")
217+
np.testing.assert_array_almost_equal(tensor, expected)
218+
219+
tensor = nap.build_tensor(tsd, ep, align="end", padding_value=-1)
220+
expected[np.isnan(expected)] = -1
221+
np.testing.assert_array_almost_equal(tensor, expected)
222+
223+
224+
def test_build_tensor_with_tsdframe():
225+
tsdframe = nap.TsdFrame(t=np.arange(100), d=np.tile(np.arange(100)[:, None], 3))
226+
ep = nap.IntervalSet(
227+
start=np.arange(0, 100, 20), end=np.arange(0, 100, 20) + np.arange(0, 10, 2)
228+
)
229+
230+
expected = np.ones((len(ep), 9)) * np.nan
231+
for i, k in zip(range(len(ep)), range(2, 10, 2)):
232+
expected[i, 0 : k + 1] = np.arange(k * 10, k * 10 + k + 1)
233+
expected = np.repeat(expected[None, :], 3, axis=0)
234+
235+
tensor = nap.build_tensor(tsdframe, ep)
236+
np.testing.assert_array_almost_equal(tensor, expected)
237+
238+
tensor = nap.build_tensor(tsdframe, ep, align="start")
239+
np.testing.assert_array_almost_equal(tensor, expected)
240+
241+
tensor = nap.build_tensor(tsdframe, ep, align="start", padding_value=-1)
242+
expected[np.isnan(expected)] = -1
243+
np.testing.assert_array_almost_equal(tensor, expected)
244+
245+
expected = np.ones((len(ep), 9)) * np.nan
246+
for i, k in zip(range(len(ep)), range(2, 10, 2)):
247+
expected[i, -k - 1 :] = np.arange(k * 10, k * 10 + k + 1)
248+
expected = np.repeat(expected[None, :], 3, axis=0)
249+
250+
tensor = nap.build_tensor(tsdframe, ep, align="end")
251+
np.testing.assert_array_almost_equal(tensor, expected)
252+
253+
tensor = nap.build_tensor(tsdframe, ep, align="end", padding_value=-1)
254+
expected[np.isnan(expected)] = -1
255+
np.testing.assert_array_almost_equal(tensor, expected)
256+
257+
258+
def test_build_tensor_with_tsdtensor():
259+
tsdtensor = nap.TsdTensor(
260+
t=np.arange(100), d=np.tile(np.arange(100)[:, None, None], (1, 2, 3))
261+
)
262+
ep = nap.IntervalSet(
263+
start=np.arange(0, 100, 20), end=np.arange(0, 100, 20) + np.arange(0, 10, 2)
264+
)
265+
266+
expected = np.ones((len(ep), 9)) * np.nan
267+
for i, k in zip(range(len(ep)), range(2, 10, 2)):
268+
expected[i, 0 : k + 1] = np.arange(k * 10, k * 10 + k + 1)
269+
expected = np.tile(expected[None, None, :, :], (2, 3, 1, 1))
270+
271+
tensor = nap.build_tensor(tsdtensor, ep)
272+
np.testing.assert_array_almost_equal(tensor, expected)
273+
274+
tensor = nap.build_tensor(tsdtensor, ep, align="start")
275+
np.testing.assert_array_almost_equal(tensor, expected)
276+
277+
tensor = nap.build_tensor(tsdtensor, ep, align="start", padding_value=-1)
278+
expected[np.isnan(expected)] = -1
279+
np.testing.assert_array_almost_equal(tensor, expected)
280+
281+
expected = np.ones((len(ep), 9)) * np.nan
282+
for i, k in zip(range(len(ep)), range(2, 10, 2)):
283+
expected[i, -k - 1 :] = np.arange(k * 10, k * 10 + k + 1)
284+
expected = np.tile(expected[None, None, :, :], (2, 3, 1, 1))
285+
286+
tensor = nap.build_tensor(tsdtensor, ep, align="end")
287+
np.testing.assert_array_almost_equal(tensor, expected)
288+
289+
tensor = nap.build_tensor(tsdtensor, ep, align="end", padding_value=-1)
290+
expected[np.isnan(expected)] = -1
291+
np.testing.assert_array_almost_equal(tensor, expected)

0 commit comments

Comments
 (0)