ibl-neuropixel 1.9.3__py3-none-any.whl → 1.10.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,10 +1,13 @@
1
- import numpy as np
2
- import tempfile
3
1
  from pathlib import Path
2
+ import tempfile
4
3
  import unittest
4
+ import unittest.mock
5
5
 
6
+ import numpy as np
6
7
  import pandas as pd
8
+ import scipy.signal
7
9
 
10
+ import neuropixel
8
11
  import spikeglx
9
12
  import ibldsp.voltage
10
13
  import ibldsp.fourier
@@ -14,24 +17,27 @@ import ibldsp.cadzow
14
17
 
15
18
  class TestDestripe(unittest.TestCase):
16
19
  def test_destripe_parameters(self):
17
- import inspect
20
+ # ibldsp.voltage.apply_spatial_filter(x, k_filter, **kwargs)
21
+ x = np.random.randn(100, 1000)
18
22
 
19
- _, _, spatial_fcn = ibldsp.voltage._get_destripe_parameters(
23
+ # K-filter = True calls the spatial filter function
24
+ _, k_kwargs = ibldsp.voltage._get_destripe_parameters(
20
25
  30_000, None, None, k_filter=True
21
26
  )
22
- assert "kfilt" in inspect.getsource(spatial_fcn)
23
- _, _, spatial_fcn = ibldsp.voltage._get_destripe_parameters(
24
- 2_500, None, None, k_filter=False
25
- )
26
- assert "car" in inspect.getsource(spatial_fcn)
27
- _, _, spatial_fcn = ibldsp.voltage._get_destripe_parameters(
28
- 2_500, None, None, k_filter=None
29
- )
30
- assert "dat: dat" in inspect.getsource(spatial_fcn)
31
- _, _, spatial_fcn = ibldsp.voltage._get_destripe_parameters(
32
- 2_500, None, None, k_filter=lambda dat: 3 * dat
33
- )
34
- assert "lambda dat: 3 * dat" in inspect.getsource(spatial_fcn)
27
+ with unittest.mock.patch("ibldsp.voltage.kfilt") as mock_fcn_spatial_filter:
28
+ ibldsp.voltage.apply_spatial_filter(x, k_filter=True, **k_kwargs)
29
+ # Assert the function was called
30
+ mock_fcn_spatial_filter.assert_called_once()
31
+
32
+ # K-filter = False calls the CAR function
33
+ with unittest.mock.patch("ibldsp.voltage.car") as mock_fcn_spatial_filter:
34
+ ibldsp.voltage.apply_spatial_filter(x, k_filter=False, **k_kwargs)
35
+ # Assert the function was called
36
+ mock_fcn_spatial_filter.assert_called_once()
37
+
38
+ # K-filter = None does not apply any filtering
39
+ xx = ibldsp.voltage.apply_spatial_filter(x, k_filter=None, **k_kwargs)
40
+ np.testing.assert_array_equal(xx, x)
35
41
 
36
42
  def test_fk(self):
37
43
  """
@@ -93,9 +99,9 @@ class TestSaturation(unittest.TestCase):
93
99
  sat = ibldsp.utils.fcn_cosine([0, 100])(
94
100
  np.arange(nsat)
95
101
  ) - ibldsp.utils.fcn_cosine([150, 250])(np.arange(nsat))
102
+ n_expected_samples = np.sum(sat > 0.96)
96
103
  range_volt = 0.0012
97
104
  sat = (sat / s2v * 0.0012).astype(np.int16)
98
-
99
105
  with tempfile.TemporaryDirectory() as temp_dir:
100
106
  file_bin = Path(temp_dir) / "binary.bin"
101
107
  data = np.memmap(file_bin, dtype=np.int16, mode="w+", shape=(ns, nc))
@@ -108,7 +114,10 @@ class TestSaturation(unittest.TestCase):
108
114
  _sr, max_voltage=range_volt, n_jobs=1
109
115
  )
110
116
  df_sat = pd.read_parquet(file_saturation)
111
- assert np.sum(df_sat["stop_sample"] - df_sat["start_sample"]) == 67
117
+ assert (
118
+ np.sum(df_sat["stop_sample"] - df_sat["start_sample"])
119
+ == n_expected_samples
120
+ )
112
121
 
113
122
  def test_saturation(self):
114
123
  np.random.seed(7654)
@@ -148,3 +157,134 @@ class TestSaturation(unittest.TestCase):
148
157
  saturation[45852:45865] = True
149
158
  df_sat = ibldsp.voltage.saturation_samples_to_intervals(saturation)
150
159
  self.assertEqual(81, np.sum(df_sat["stop_sample"] - df_sat["start_sample"]))
160
+
161
+
162
+ class TestLFP(unittest.TestCase):
163
+ def test_rsamp_cbin(self):
164
+ """
165
+ Resamples a binary file by a factor of 5
166
+ :return:
167
+ """
168
+ ns = int(125.83948 * 2500) + 3
169
+ nc = 12
170
+ fs = 2500
171
+ resamp_factor_q = 5
172
+ with tempfile.TemporaryDirectory() as temp_dir:
173
+ testfile = Path(temp_dir).joinpath("test.dat")
174
+ out_file = Path(temp_dir).joinpath("test_rs.dat")
175
+ # testfile = Path.home().joinpath('lfp', 'test.dat')
176
+ # out_file = Path.home().joinpath('lfp', 'test_rs.dat')
177
+ testfile.parent.mkdir(exist_ok=True, parents=True)
178
+ d = np.zeros((ns, nc), dtype=np.float32)
179
+ for ic in range(nc):
180
+ freq = 10 + ic * 4
181
+ print(freq)
182
+ d[:, ic] = np.sin(2 * np.pi * freq * np.arange(ns) / fs) * 1000
183
+
184
+ with open(testfile, "wb+") as f:
185
+ d.tofile(f)
186
+
187
+ sr = spikeglx.Reader(testfile, ns=ns, nc=nc, fs=2500, dtype=np.float32)
188
+ ibldsp.voltage.resample_denoise_lfp_cbin(
189
+ sr, output=out_file, dtype=np.float32
190
+ )
191
+ za = spikeglx.Reader(out_file, ns=ns // 5, nc=nc, fs=500, dtype=np.float32)
192
+
193
+ diff = d[0:-1:resamp_factor_q, :] - za[:]
194
+ np.testing.assert_array_less(np.abs(diff[1024:-1024] / 1000), 1e-3)
195
+
196
+
197
+ class TestDetectBadChannels(unittest.TestCase):
198
+ @staticmethod
199
+ def a_little_spike(nsw=121, nc=1):
200
+ # creates a kind of waveform that resembles a spike
201
+ wav = np.zeros(nsw)
202
+ wav[0] = 1
203
+ wav[5] = -0.1
204
+ wav[10] = -0.3
205
+ wav[15] = -0.1
206
+ sos = scipy.signal.butter(N=3, Wn=0.15, output="sos")
207
+ spike = scipy.signal.sosfilt(sos, wav)
208
+ spike = -spike / np.max(spike)
209
+ if nc > 1:
210
+ spike = spike[:, np.newaxis] * scipy.signal.hamming(nc)[np.newaxis, :]
211
+ return spike
212
+
213
+ @staticmethod
214
+ def make_synthetic_data(
215
+ ns=10000, nc=384, nss=121, ncs=21, nspikes=1200, tr=None, sample=None
216
+ ):
217
+ if tr is None:
218
+ tr = np.random.randint(np.ceil(ncs / 2), nc - np.ceil(ncs / 2), nspikes)
219
+ if sample is None:
220
+ sample = np.random.randint(np.ceil(nss / 2), ns - np.ceil(nss / 2), nspikes)
221
+ h = neuropixel.trace_header(1)
222
+ icsmid = int(np.floor(ncs / 2))
223
+ issmid = int(np.floor(nss / 2))
224
+ template = TestDetectBadChannels.a_little_spike(121)
225
+ data = np.zeros((ns, nc))
226
+ for m in np.arange(tr.size):
227
+ itr = np.arange(tr[m] - icsmid, tr[m] + icsmid + 1)
228
+ iss = np.arange(sample[m] - issmid, sample[m] + issmid + 1)
229
+ offset = np.abs(
230
+ h["x"][itr[icsmid]]
231
+ + 1j * h["y"][itr[icsmid]]
232
+ - h["x"][itr]
233
+ - 1j * h["y"][itr]
234
+ )
235
+ ampfac = 1 / (offset + 10) ** 1.3
236
+ ampfac = ampfac / np.max(ampfac)
237
+ tmp = template[:, np.newaxis] * ampfac[np.newaxis, :]
238
+ data[slice(iss[0], iss[-1] + 1), slice(itr[0], itr[-1] + 1)] += tmp
239
+ return data
240
+
241
+ @staticmethod
242
+ def synthetic_with_bad_channels():
243
+ np.random.seed(12345)
244
+ ns, nc, fs = (30000, 384, 30000)
245
+ data = TestDetectBadChannels.make_synthetic_data(ns=ns, nc=nc) * 1e-6 * 50
246
+
247
+ st = np.round(
248
+ np.cumsum(-np.log(np.random.rand(int(ns / fs * 50 * 1.5))) / 50) * fs
249
+ )
250
+ st = st[st < ns].astype(np.int32)
251
+ stripes = np.zeros(ns)
252
+ stripes[st] = 1
253
+ stripes = (
254
+ scipy.signal.convolve(stripes, ibldsp.utils.ricker(1200, 40), "same")
255
+ * 1e-6
256
+ * 2500
257
+ )
258
+
259
+ data = data + stripes[:, np.newaxis]
260
+ noise = np.random.randn(*data.shape) * 1e-6 * 10
261
+
262
+ channels = {
263
+ "idead": [29, 36, 39, 40, 191],
264
+ "inoisy": [133, 235],
265
+ "ioutside": np.arange(275, 384),
266
+ }
267
+
268
+ data[:, channels["idead"]] = data[:, channels["idead"]] / 20
269
+ noise[:, channels["inoisy"]] = noise[:, channels["inoisy"]] * 200
270
+ data[:, channels["idead"]] = data[:, channels["idead"]] / 20
271
+ data[:, channels["ioutside"]] = 0
272
+ data += noise
273
+ return data, channels
274
+
275
+ def test_channel_detections(self):
276
+ """
277
+ This test creates a synthetic dataset with voltage stripes and 3 types of bad channels
278
+ 1) dead channels or low amplitude
279
+ 2) noisy
280
+ 3) out of the brain
281
+ """
282
+ data, channels = self.synthetic_with_bad_channels()
283
+ labels, xfeats = ibldsp.voltage.detect_bad_channels(data.T, fs=30000)
284
+ assert np.all(np.where(labels == 1)[0] == np.array(channels["idead"]))
285
+ assert np.all(np.where(labels == 2)[0] == np.array(channels["inoisy"]))
286
+ assert np.all(np.where(labels == 3)[0] == np.array(channels["ioutside"]))
287
+ # from easyqc.gui import viewseis
288
+ # eqc = viewseis(data, si=1 / 30000 * 1e3, h=h, title='synth', taxis=0)
289
+ # from ibllib.plots.figures import ephys_bad_channels
290
+ # ephys_bad_channels(data.T, 30000, labels, xfeats)