ibl-neuropixel 1.9.1__py3-none-any.whl → 1.9.2__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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ibl-neuropixel
3
- Version: 1.9.1
3
+ Version: 1.9.2
4
4
  Summary: Collection of tools for Neuropixel 1.0 and 2.0 probes data
5
5
  Home-page: https://github.com/int-brain-lab/ibl-neuropixel
6
6
  Author: The International Brain Laboratory
@@ -1,8 +1,8 @@
1
1
  neuropixel.py,sha256=P7sIBAtGIqKReK7OqMBqdwPaTeHjhHMyfyBRL_AvuQY,37987
2
2
  spikeglx.py,sha256=4TPXnFGhJahClxr4fA9HwTeiiHBQS9ZEfkWl6t20q2s,41068
3
- ibl_neuropixel-1.9.1.dist-info/licenses/LICENSE,sha256=JJCjBeS78UPiX7TZpE-FnMjNNpCyrFb4s8VDGG2wD10,1087
3
+ ibl_neuropixel-1.9.2.dist-info/licenses/LICENSE,sha256=JJCjBeS78UPiX7TZpE-FnMjNNpCyrFb4s8VDGG2wD10,1087
4
4
  ibldsp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- ibldsp/cadzow.py,sha256=pAtxDxBwoNhoxFNc2R5WLwUrmKsq4rQuaglRNgW2Lj8,7251
5
+ ibldsp/cadzow.py,sha256=YVQdF1HAZEhdanGKSBt3FWUSd66_VCxN8y2eQy8q9Fc,7307
6
6
  ibldsp/cuda_tools.py,sha256=6LpVhYOCuOXEEg8kJ3aOCE4hzA1Yq1dojsbbBQmQCF4,2387
7
7
  ibldsp/destripe_gpu.py,sha256=I5jzFocpsYw36kMMd533YThbrQaZix5e1sHqsUjHvO4,2824
8
8
  ibldsp/filter_gpu.py,sha256=DPrPBLRXeCh_6BcJWJnPFaxS9Q6kX4nPENZg-c2q5rc,5789
@@ -12,10 +12,10 @@ ibldsp/plots.py,sha256=XmYC4yca_seZYNEmC5hE5wBiJAl_fi_KU00DbNcM6jI,4577
12
12
  ibldsp/raw_metrics.py,sha256=Ie4b7unuFc-XiFc9-tpTsUkph29G-20NvM7iJ25jAPI,5198
13
13
  ibldsp/smooth.py,sha256=m_mByXHG_JyFErnYsZ27gXjcqpfwCEuWa6eOb9eFuyg,8033
14
14
  ibldsp/spiketrains.py,sha256=lYP1PD4l6T-4KhFu8ZXlbnUUnEQLOriGxN1szacolPY,6878
15
- ibldsp/utils.py,sha256=uvEPw1adkppiGXuYBkM_fuuX5owq7LRmA6vm438rrYc,17959
16
- ibldsp/voltage.py,sha256=Iias93xAvxfRDrzgZT-aw-w4xfWtykx2zWLhI2CxzVI,45408
15
+ ibldsp/utils.py,sha256=Ku1pdymbiCyQU5iZX8Akfq47YXM4xegW6G3_aomh6WA,18580
16
+ ibldsp/voltage.py,sha256=Pb1ZYlr8av2XDUspZaSUtF2wOoTy92fnrsxnW9sTIFA,47708
17
17
  ibldsp/waveform_extraction.py,sha256=yKrldgHqpwQ_Dq6xdoSCceKkfrL9FUXnpwKJUM3R41M,26570
18
- ibldsp/waveforms.py,sha256=5OBLYuM902WS_9WGDDmiTh4BpYWGe7-bQYTMxc2mYII,35166
18
+ ibldsp/waveforms.py,sha256=XKWO0sSEhZR1mBsXCdGpVU3ga96HX3CViXIgpl3bml8,35280
19
19
  neurowaveforms/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  neurowaveforms/model.py,sha256=YOPWMMNNS_Op5TyK4Br1i9_Ni41jLSqHie5r1vb5VjY,6729
21
21
  tests/integration/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -29,7 +29,7 @@ tests/unit/test_spikeglx.py,sha256=9PrSOPGrYAAQEeJPAOmqc3Rhgia6ftv-zihVWXglhqw,3
29
29
  tests/unit/test_utils.py,sha256=37XQDUqcABYrrsdX17kX54H4e5jld7GOn1ISxtgoa5U,21859
30
30
  tests/unit/test_voltage.py,sha256=Nr6KqNGn2yOGPJYnvVzxdM5IiEHvK2FicDR_7fzvTHQ,6228
31
31
  tests/unit/test_waveforms.py,sha256=VnFvUi1pteROwwbC5Ebp2lqSxF3a8a7eXHpD8OUeuTg,16237
32
- ibl_neuropixel-1.9.1.dist-info/METADATA,sha256=RaS1xeg11qze-sAmPqKVqdKOgUcSk-5l01HNrkX9kIw,3746
33
- ibl_neuropixel-1.9.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
- ibl_neuropixel-1.9.1.dist-info/top_level.txt,sha256=WtVcEUptnwU6BT72cgGmrWYFGM9d9qCEqe3LwR9FIw4,48
35
- ibl_neuropixel-1.9.1.dist-info/RECORD,,
32
+ ibl_neuropixel-1.9.2.dist-info/METADATA,sha256=4RuLzmWxYJjEyYJKA_1Szi0T0Dn5-VRjD7o4BaY-mHQ,3746
33
+ ibl_neuropixel-1.9.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ ibl_neuropixel-1.9.2.dist-info/top_level.txt,sha256=WtVcEUptnwU6BT72cgGmrWYFGM9d9qCEqe3LwR9FIw4,48
35
+ ibl_neuropixel-1.9.2.dist-info/RECORD,,
ibldsp/cadzow.py CHANGED
@@ -17,6 +17,8 @@ for N-spatial dimensions.
17
17
  }
18
18
  """
19
19
 
20
+ from functools import lru_cache
21
+
20
22
  import numpy as np
21
23
  import scipy.fft
22
24
  from iblutil.numerical import ismember2d
@@ -46,6 +48,7 @@ def traj_matrix_indices(n):
46
48
  return itraj
47
49
 
48
50
 
51
+ @lru_cache(maxsize=24)
49
52
  def trajectory(x, y, dtype=np.complex128):
50
53
  """
51
54
  Computes the 2 spatial dimensions block-Toeplitz indices from x and y trace coordinates.
ibldsp/utils.py CHANGED
@@ -383,21 +383,34 @@ class WindowGenerator(object):
383
383
  yield (first, last, amp)
384
384
 
385
385
  @property
386
- def firstlast_valid(self, discard_edges=False):
386
+ def first_last_valid_noedge(self):
387
387
  """
388
388
  Generator that yields a tuple of first, last, first_valid, last_valid index of windows
389
- The valid indices span up to half of the overlap
389
+ The valid indices span up to half of the overlap.
390
+ The first and last windows have respectively the beginning and end of the signal discarded.
390
391
  :return:
391
392
  """
393
+ return self._firstlast_valid(discard_edges=True)
394
+
395
+ @property
396
+ def firstlast_valid(self):
397
+ """
398
+ Generator that yields a tuple of first, last, first_valid, last_valid index of windows
399
+ The valid indices span up to half of the overlap.
400
+ The first and last windows have the full beginning and end of the signal respectively.
401
+ To discard the beginning and end edges, use firstlast_valid_noedge instead.
402
+ :return:
403
+ """
404
+ return self._firstlast_valid(self)
405
+
406
+ def _firstlast_valid(self, discard_edges=False):
392
407
  assert self.overlap % 2 == 0, "Overlap must be even"
393
408
  for first, last in self.firstlast:
394
409
  first_valid = (
395
- 0 if first == 0 and not discard_edges else first + self.overlap // 2
410
+ 0 if first == 0 and discard_edges else first + self.overlap // 2
396
411
  )
397
412
  last_valid = (
398
- last
399
- if last == self.ns and not discard_edges
400
- else last - self.overlap // 2
413
+ last if last == self.ns and discard_edges else last - self.overlap // 2
401
414
  )
402
415
  yield (first, last, first_valid, last_valid)
403
416
 
ibldsp/voltage.py CHANGED
@@ -790,21 +790,59 @@ def decompress_destripe_cbin(
790
790
 
791
791
 
792
792
  def detect_bad_channels(
793
- raw, fs, similarity_threshold=(-0.5, 1), psd_hf_threshold=None, display=False
793
+ raw,
794
+ fs,
795
+ similarity_threshold=(-0.5, 1),
796
+ psd_hf_threshold=None,
797
+ display=False,
798
+ outside_threshold=-0.75,
794
799
  ):
795
800
  """
796
- Bad channels detection for Neuropixel probes
797
- Labels channels
798
- 0: all clear
799
- 1: dead low coherence / amplitude
800
- 2: noisy
801
- 3: outside of the brain
802
- :param raw: [nc, ns]
803
- :param fs: sampling frequency
804
- :param similarity_threshold:
805
- :param psd_hf_threshold:
806
- :param display: optinal (False) will show a plot of features alongside a raw data snippet
807
- :return: labels (numpy vector [nc]), xfeats: dictionary of features [nc]
801
+ Detect bad channels in Neuropixel probe recordings based on signal quality metrics.
802
+
803
+ This function analyzes raw electrophysiology data to identify and label problematic channels
804
+ using multiple criteria including cross-correlation with neighboring channels, power spectral
805
+ density analysis, and spatial coherence patterns. Channels are classified into four categories:
806
+ good (0), dead (1), noisy (2), or outside the brain (3).
807
+
808
+ Parameters
809
+ ----------
810
+ raw : numpy.ndarray
811
+ Raw voltage traces array with shape (nc, ns), where nc is the number of channels
812
+ and ns is the number of samples.
813
+ fs : float
814
+ Sampling frequency in Hz.
815
+ similarity_threshold : tuple of float, optional
816
+ Two-element tuple (lower, upper) defining the acceptable range for high-frequency
817
+ cross-correlation values. Channels outside this range are flagged as dead (below lower)
818
+ or noisy (above upper). Defaults to (-0.5, 1).
819
+ psd_hf_threshold : float, optional
820
+ Threshold for high-frequency power spectral density to identify noisy channels.
821
+ If None, defaults to 0.02 for AP band (fs > 2600 Hz) or 1.4 for LF band (fs <= 2600 Hz).
822
+ Units are µV²/Hz.
823
+ display : bool, optional
824
+ If True, displays a diagnostic plot showing channel features and a raw data snippet.
825
+ Defaults to False.
826
+ outside_threshold : float or str, optional
827
+ Threshold for low-frequency cross-correlation to identify channels outside the brain.
828
+ Can be a float value (default -0.75) or 'adaptive' for automatic threshold detection
829
+ based on signal gradient analysis.
830
+
831
+ Returns
832
+ -------
833
+ ichannels : numpy.ndarray
834
+ Integer array of shape (nc,) containing channel labels:
835
+ - 0: good channel
836
+ - 1: dead channel (low coherence/amplitude)
837
+ - 2: noisy channel (high noise or excessive correlation)
838
+ - 3: outside of the brain
839
+ xfeats : dict
840
+ Dictionary containing computed features for each channel:
841
+ - 'ind': channel indices
842
+ - 'rms_raw': RMS amplitude of raw signal
843
+ - 'xcor_hf': detrended high-frequency cross-correlation
844
+ - 'xcor_lf': low-frequency cross-correlation component
845
+ - 'psd_hf': mean power spectral density in high-frequency band
808
846
  """
809
847
 
810
848
  def rneighbours(raw, n=1): # noqa
@@ -897,21 +935,28 @@ def detect_bad_channels(
897
935
  )[0]
898
936
  # the channels outside of the brains are the contiguous channels below the threshold on the trend coherency
899
937
 
900
- signal_noisy = xfeats["xcor_lf"]
901
- # Filter signal
902
- window_size = 25 # Choose based on desired smoothing (e.g., 25 samples)
903
- kernel = np.ones(window_size) / window_size
904
- # Apply convolution
905
- signal_filtered = np.convolve(signal_noisy, kernel, mode="same")
906
-
907
- diff_x = np.diff(signal_filtered)
908
- indx = np.where(diff_x < -0.02)[0] # hardcoded threshold
909
- if indx.size > 0:
910
- indx_threshold = np.floor(np.median(indx)).astype(int)
911
- threshold = signal_noisy[indx_threshold]
912
- ioutside = np.where(signal_noisy < threshold)[0]
938
+ # deal with channels outside of the brain
939
+ if outside_threshold == "adaptive":
940
+ signal_noisy = xfeats["xcor_lf"]
941
+ # Filter signal
942
+ window_size = 25 # Choose based on desired smoothing (e.g., 25 samples)
943
+ kernel = np.ones(window_size) / window_size
944
+ # Apply convolution
945
+ signal_filtered = np.convolve(signal_noisy, kernel, mode="same")
946
+
947
+ diff_x = np.diff(signal_filtered)
948
+ indx = np.where(diff_x < -0.02)[0] # hardcoded threshold
949
+ if indx.size > 0:
950
+ indx_threshold = np.floor(np.median(indx)).astype(int)
951
+ threshold = signal_noisy[indx_threshold]
952
+ ioutside = np.where(signal_noisy < threshold)[0]
953
+ else:
954
+ ioutside = np.array([])
913
955
  else:
914
- ioutside = np.array([])
956
+ assert np.isreal(outside_threshold) and np.isscalar(outside_threshold), (
957
+ "outside_threshold must be a real number or 'adaptive' for adaptive threshold"
958
+ )
959
+ ioutside = np.where(xfeats["xcor_lf"] < outside_threshold)[0]
915
960
 
916
961
  if ioutside.size > 0 and ioutside[-1] == (nc - 1):
917
962
  a = np.cumsum(np.r_[0, np.diff(ioutside) - 1])
ibldsp/waveforms.py CHANGED
@@ -297,7 +297,7 @@ def plot_wiggle(
297
297
  return ax
298
298
 
299
299
 
300
- def double_wiggle(wav, fs=1, ax=None, colors=None, **kwargs):
300
+ def double_wiggle(wav, fs=1, ax=None, colors=None, title=None, **kwargs):
301
301
  """
302
302
  Double trouble: this wiggle colours both the negative and the postive values
303
303
  :param wav: (nchannels, nsamples)
@@ -308,6 +308,7 @@ def double_wiggle(wav, fs=1, ax=None, colors=None, **kwargs):
308
308
  :param fill_sign: -1 for negative (default for spikes), 1 for positive
309
309
  :param plot_kwargs: kwargs for the line plot
310
310
  :param fill_kwargs: kwargs for the fill
311
+ :param title: title for the axis (optional)
311
312
  :return:
312
313
  """
313
314
  if colors is None:
@@ -334,6 +335,8 @@ def double_wiggle(wav, fs=1, ax=None, colors=None, **kwargs):
334
335
  fill_kwargs={"color": colors[1]},
335
336
  **kwargs,
336
337
  )
338
+ if title is not None:
339
+ ax.set(title=title)
337
340
  return ax
338
341
 
339
342