ibl-neuropixel 1.8.1__tar.gz → 1.9.1__tar.gz

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.
Files changed (71) hide show
  1. {ibl_neuropixel-1.8.1/src/ibl_neuropixel.egg-info → ibl_neuropixel-1.9.1}/PKG-INFO +49 -20
  2. ibl_neuropixel-1.9.1/README.md +89 -0
  3. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/setup.py +1 -1
  4. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1/src/ibl_neuropixel.egg-info}/PKG-INFO +49 -20
  5. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibl_neuropixel.egg-info/SOURCES.txt +3 -1
  6. ibl_neuropixel-1.9.1/src/ibldsp/plots.py +135 -0
  7. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/utils.py +135 -18
  8. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/voltage.py +155 -12
  9. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/waveform_extraction.py +1 -0
  10. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/spikeglx.py +16 -11
  11. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/integration/test_destripe.py +5 -4
  12. ibl_neuropixel-1.9.1/src/tests/unit/test_plots.py +30 -0
  13. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/unit/test_spikeglx.py +25 -2
  14. ibl_neuropixel-1.8.1/src/tests/unit/test_ibldsp.py → ibl_neuropixel-1.9.1/src/tests/unit/test_utils.py +18 -100
  15. ibl_neuropixel-1.9.1/src/tests/unit/test_voltage.py +160 -0
  16. ibl_neuropixel-1.8.1/README.md +0 -60
  17. ibl_neuropixel-1.8.1/src/ibldsp/plots.py +0 -58
  18. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/LICENSE +0 -0
  19. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/MANIFEST.in +0 -0
  20. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/setup.cfg +0 -0
  21. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibl_neuropixel.egg-info/dependency_links.txt +0 -0
  22. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibl_neuropixel.egg-info/requires.txt +0 -0
  23. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibl_neuropixel.egg-info/top_level.txt +0 -0
  24. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/__init__.py +0 -0
  25. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/cadzow.py +0 -0
  26. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/cuda_tools.py +0 -0
  27. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/destripe_gpu.py +0 -0
  28. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/filter_gpu.py +0 -0
  29. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/fourier.py +0 -0
  30. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/icsd.py +0 -0
  31. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/raw_metrics.py +0 -0
  32. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/smooth.py +0 -0
  33. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/spiketrains.py +0 -0
  34. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/waveforms.py +0 -0
  35. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/neuropixel.py +0 -0
  36. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/neurowaveforms/__init__.py +0 -0
  37. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/neurowaveforms/model.py +0 -0
  38. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/np2split/NP1_meta/_spikeglx_ephysData_g0_t0.imec0.ap.meta +0 -0
  39. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/np2split/NP21_meta/_spikeglx_ephysData_g0_t0.imec0.ap.meta +0 -0
  40. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/np2split/NP24_meta/_spikeglx_ephysData_g0_t0.imec0.ap.meta +0 -0
  41. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/np2split/_spikeglx_ephysData_g0_t0.imec0.ap.ch +0 -0
  42. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/np2split/_spikeglx_ephysData_g0_t0.imec0.ap.meta +0 -0
  43. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_376_channels.ap.meta +0 -0
  44. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_g0_t0.imec.ap.meta +0 -0
  45. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_g0_t0.imec.lf.meta +0 -0
  46. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_g0_t0.imec.wiring.json +0 -0
  47. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_short_g0_t0.imec.ap.meta +0 -0
  48. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B2_exported.imec0.ap.meta +0 -0
  49. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_catgt.ap.meta +0 -0
  50. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_g0_t0.imec1.ap.meta +0 -0
  51. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_g0_t0.imec1.lf.meta +0 -0
  52. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_g0_t0.nidq.meta +0 -0
  53. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_g0_t0.nidq.wiring.json +0 -0
  54. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_version202304.ap.meta +0 -0
  55. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.1_g0_t0.imec.ap.meta +0 -0
  56. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.1_prototype.ap.meta +0 -0
  57. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.4_1shank_g0_t0.imec.ap.meta +0 -0
  58. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.4_4shanks_appVersion20230905.ap.meta +0 -0
  59. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.4_4shanks_g0_t0.imec.ap.meta +0 -0
  60. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.4_4shanks_while_acquiring_incomplete.ap.meta +0 -0
  61. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNPultra_g0_t0.imec0.ap.meta +0 -0
  62. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/waveform_sample/test_arr_in.npy +0 -0
  63. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/waveform_sample/test_arr_peak.npy +0 -0
  64. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/waveform_sample/test_df.csv +0 -0
  65. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/waveform_sample/test_df_wavinfo.csv +0 -0
  66. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/integration/__init__.py +0 -0
  67. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/integration/csd_experiments.py +0 -0
  68. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/unit/__init__.py +0 -0
  69. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/unit/test_ephys_np2.py +0 -0
  70. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/unit/test_neuropixel.py +0 -0
  71. {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/unit/test_waveforms.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ibl-neuropixel
3
- Version: 1.8.1
3
+ Version: 1.9.1
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
@@ -41,6 +41,53 @@ Minimum Python version supported is 3.10
41
41
 
42
42
  ## Destriping
43
43
  ### Getting started
44
+
45
+ #### Compress a binary file losslessly using `mtscomp`
46
+
47
+ The mtscomp util implements fast chunked compression for neurophysiology data in a single shard.
48
+ Package repository is [here](https://github.com/int-brain-lab/mtscomp).
49
+
50
+
51
+ ```python
52
+ from pathlib import Path
53
+ import spikeglx
54
+ file_spikeglx = Path('/datadisk/neuropixel/file.imec0.ap.bin')
55
+ sr = spikeglx.Reader(file_spikeglx)
56
+ sr.compress_file()
57
+ # note: you can use sr.compress_file(keep_original=False) to also remove the orginal bin file
58
+ ```
59
+
60
+ #### Reading raw spikeglx file and manipulating arrays
61
+
62
+ The mtscomp util implements fast chunked compression for neurophysiology data in a single shard.
63
+ Package repository is [here](https://github.com/int-brain-lab/mtscomp).
64
+
65
+ ```python
66
+ from pathlib import Path
67
+ import spikeglx
68
+
69
+ import ibldsp.voltage
70
+
71
+ file_spikeglx = Path('/datadisk/Data/neuropixel/human/Pt01.imec0.ap.bin')
72
+ sr = spikeglx.Reader(file_spikeglx)
73
+
74
+ # reads in 300ms of data
75
+ raw = sr[10_300_000:10_310_000, :sr.nc - sr.nsync].T
76
+ destripe = ibldsp.voltage.destripe(raw, fs=sr.fs, neuropixel_version=1)
77
+
78
+ # display with matplotlib backend
79
+ import ibldsp.plots
80
+ ibldsp.plots.voltageshow(raw, fs=sr.fs, title='raw')
81
+ ibldsp.plots.voltageshow(destripe, fs=sr.fs, title='destripe')
82
+
83
+ # display with QT backend
84
+ from viewephys.gui import viewephys
85
+ eqc = {}
86
+ eqc['raw'] = viewephys(raw, fs=sr.fs, title='raw')
87
+ eqc['destripe'] = viewephys(destripe, fs=sr.fs, title='destripe')
88
+ ```
89
+
90
+ #### Destripe a binary file
44
91
  This relies on a fast fourier transform external library: `pip install pyfftw`.
45
92
 
46
93
  Minimal working example to destripe a neuropixel binary file.
@@ -71,22 +118,4 @@ The following describes the methods implemented in this repository.
71
118
  https://doi.org/10.6084/m9.figshare.19705522
72
119
 
73
120
  ## Contribution
74
- Contribution checklist:
75
- - run tests
76
- - ruff format
77
- - PR to main
78
-
79
-
80
- Pypi Release checklist:
81
- - Edit the version number in `setup.py`
82
- - add release notes in `release_notes.md`
83
-
84
-
85
- ```shell
86
- ruff format
87
- tag=X.Y.Z
88
- git tag -a $tag
89
- git push origin $tag
90
- ```
91
-
92
- Create new release with tag X.Y.Z (will automatically publish to PyPI)
121
+ Please see our [contribution guidelines](CONTRIBUTING.md) for details on how to contribute to this project.
@@ -0,0 +1,89 @@
1
+ # ibl-neuropixel
2
+ Collection of tools to handle Neuropixel 1.0 and 2.0 data
3
+ (documentation coming soon...)
4
+
5
+ ## Installation
6
+ Minimum Python version supported is 3.10
7
+ `pip install ibl-neuropixel`
8
+
9
+
10
+ ## Destriping
11
+ ### Getting started
12
+
13
+ #### Compress a binary file losslessly using `mtscomp`
14
+
15
+ The mtscomp util implements fast chunked compression for neurophysiology data in a single shard.
16
+ Package repository is [here](https://github.com/int-brain-lab/mtscomp).
17
+
18
+
19
+ ```python
20
+ from pathlib import Path
21
+ import spikeglx
22
+ file_spikeglx = Path('/datadisk/neuropixel/file.imec0.ap.bin')
23
+ sr = spikeglx.Reader(file_spikeglx)
24
+ sr.compress_file()
25
+ # note: you can use sr.compress_file(keep_original=False) to also remove the orginal bin file
26
+ ```
27
+
28
+ #### Reading raw spikeglx file and manipulating arrays
29
+
30
+ The mtscomp util implements fast chunked compression for neurophysiology data in a single shard.
31
+ Package repository is [here](https://github.com/int-brain-lab/mtscomp).
32
+
33
+ ```python
34
+ from pathlib import Path
35
+ import spikeglx
36
+
37
+ import ibldsp.voltage
38
+
39
+ file_spikeglx = Path('/datadisk/Data/neuropixel/human/Pt01.imec0.ap.bin')
40
+ sr = spikeglx.Reader(file_spikeglx)
41
+
42
+ # reads in 300ms of data
43
+ raw = sr[10_300_000:10_310_000, :sr.nc - sr.nsync].T
44
+ destripe = ibldsp.voltage.destripe(raw, fs=sr.fs, neuropixel_version=1)
45
+
46
+ # display with matplotlib backend
47
+ import ibldsp.plots
48
+ ibldsp.plots.voltageshow(raw, fs=sr.fs, title='raw')
49
+ ibldsp.plots.voltageshow(destripe, fs=sr.fs, title='destripe')
50
+
51
+ # display with QT backend
52
+ from viewephys.gui import viewephys
53
+ eqc = {}
54
+ eqc['raw'] = viewephys(raw, fs=sr.fs, title='raw')
55
+ eqc['destripe'] = viewephys(destripe, fs=sr.fs, title='destripe')
56
+ ```
57
+
58
+ #### Destripe a binary file
59
+ This relies on a fast fourier transform external library: `pip install pyfftw`.
60
+
61
+ Minimal working example to destripe a neuropixel binary file.
62
+ ```python
63
+ from pathlib import Path
64
+ from ibldsp.voltage import decompress_destripe_cbin
65
+ sr_file = Path('/datadisk/Data/spike_sorting/pykilosort_tests/imec_385_100s.ap.bin')
66
+ out_file = Path('/datadisk/scratch/imec_385_100s.ap.bin')
67
+
68
+ decompress_destripe_cbin(sr_file=sr_file, output_file=out_file, nprocesses=8)
69
+ ```
70
+
71
+ ### Viewer
72
+
73
+ The best way to look at the results is to use [viewephys](https://github.com/oliche/viewephys),
74
+ open an ephys viewer on the raw data.
75
+
76
+ - tick the destripe box.
77
+ - move to a desired location in the file
78
+ - ctr+P will make the gain and axis the same on both windows
79
+
80
+ ![alt text](./docs/raw_bin_viewer_destripe.png "Ephys viewer")
81
+
82
+ You can then move within the raw data file.
83
+
84
+ ### White Paper
85
+ The following describes the methods implemented in this repository.
86
+ https://doi.org/10.6084/m9.figshare.19705522
87
+
88
+ ## Contribution
89
+ Please see our [contribution guidelines](CONTRIBUTING.md) for details on how to contribute to this project.
@@ -8,7 +8,7 @@ with open("requirements.txt") as f:
8
8
 
9
9
  setuptools.setup(
10
10
  name="ibl-neuropixel",
11
- version="1.8.1",
11
+ version="1.9.1",
12
12
  author="The International Brain Laboratory",
13
13
  description="Collection of tools for Neuropixel 1.0 and 2.0 probes data",
14
14
  long_description=long_description,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ibl-neuropixel
3
- Version: 1.8.1
3
+ Version: 1.9.1
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
@@ -41,6 +41,53 @@ Minimum Python version supported is 3.10
41
41
 
42
42
  ## Destriping
43
43
  ### Getting started
44
+
45
+ #### Compress a binary file losslessly using `mtscomp`
46
+
47
+ The mtscomp util implements fast chunked compression for neurophysiology data in a single shard.
48
+ Package repository is [here](https://github.com/int-brain-lab/mtscomp).
49
+
50
+
51
+ ```python
52
+ from pathlib import Path
53
+ import spikeglx
54
+ file_spikeglx = Path('/datadisk/neuropixel/file.imec0.ap.bin')
55
+ sr = spikeglx.Reader(file_spikeglx)
56
+ sr.compress_file()
57
+ # note: you can use sr.compress_file(keep_original=False) to also remove the orginal bin file
58
+ ```
59
+
60
+ #### Reading raw spikeglx file and manipulating arrays
61
+
62
+ The mtscomp util implements fast chunked compression for neurophysiology data in a single shard.
63
+ Package repository is [here](https://github.com/int-brain-lab/mtscomp).
64
+
65
+ ```python
66
+ from pathlib import Path
67
+ import spikeglx
68
+
69
+ import ibldsp.voltage
70
+
71
+ file_spikeglx = Path('/datadisk/Data/neuropixel/human/Pt01.imec0.ap.bin')
72
+ sr = spikeglx.Reader(file_spikeglx)
73
+
74
+ # reads in 300ms of data
75
+ raw = sr[10_300_000:10_310_000, :sr.nc - sr.nsync].T
76
+ destripe = ibldsp.voltage.destripe(raw, fs=sr.fs, neuropixel_version=1)
77
+
78
+ # display with matplotlib backend
79
+ import ibldsp.plots
80
+ ibldsp.plots.voltageshow(raw, fs=sr.fs, title='raw')
81
+ ibldsp.plots.voltageshow(destripe, fs=sr.fs, title='destripe')
82
+
83
+ # display with QT backend
84
+ from viewephys.gui import viewephys
85
+ eqc = {}
86
+ eqc['raw'] = viewephys(raw, fs=sr.fs, title='raw')
87
+ eqc['destripe'] = viewephys(destripe, fs=sr.fs, title='destripe')
88
+ ```
89
+
90
+ #### Destripe a binary file
44
91
  This relies on a fast fourier transform external library: `pip install pyfftw`.
45
92
 
46
93
  Minimal working example to destripe a neuropixel binary file.
@@ -71,22 +118,4 @@ The following describes the methods implemented in this repository.
71
118
  https://doi.org/10.6084/m9.figshare.19705522
72
119
 
73
120
  ## Contribution
74
- Contribution checklist:
75
- - run tests
76
- - ruff format
77
- - PR to main
78
-
79
-
80
- Pypi Release checklist:
81
- - Edit the version number in `setup.py`
82
- - add release notes in `release_notes.md`
83
-
84
-
85
- ```shell
86
- ruff format
87
- tag=X.Y.Z
88
- git tag -a $tag
89
- git push origin $tag
90
- ```
91
-
92
- Create new release with tag X.Y.Z (will automatically publish to PyPI)
121
+ Please see our [contribution guidelines](CONTRIBUTING.md) for details on how to contribute to this project.
@@ -59,7 +59,9 @@ src/tests/integration/csd_experiments.py
59
59
  src/tests/integration/test_destripe.py
60
60
  src/tests/unit/__init__.py
61
61
  src/tests/unit/test_ephys_np2.py
62
- src/tests/unit/test_ibldsp.py
63
62
  src/tests/unit/test_neuropixel.py
63
+ src/tests/unit/test_plots.py
64
64
  src/tests/unit/test_spikeglx.py
65
+ src/tests/unit/test_utils.py
66
+ src/tests/unit/test_voltage.py
65
67
  src/tests/unit/test_waveforms.py
@@ -0,0 +1,135 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+
4
+ AP_RANGE_UV = 75
5
+ LF_RANGE_UV = 250
6
+
7
+
8
+ def show_channels_labels(
9
+ raw,
10
+ fs,
11
+ channel_labels,
12
+ xfeats,
13
+ similarity_threshold=(-0.5, 1),
14
+ psd_hf_threshold=0.02,
15
+ ):
16
+ """
17
+ Shows the features side by side a snippet of raw data
18
+ :param sr:
19
+ :return:
20
+ """
21
+ nc, ns = raw.shape
22
+ raw = raw - np.mean(raw, axis=-1)[:, np.newaxis] # removes DC offset
23
+ ns_plot = np.minimum(ns, 3000)
24
+ fig, ax = plt.subplots(
25
+ 1, 5, figsize=(18, 6), gridspec_kw={"width_ratios": [1, 1, 1, 8, 0.2]}
26
+ )
27
+ ax[0].plot(xfeats["xcor_hf"], np.arange(nc))
28
+ ax[0].plot( # plot channel below the similarity threshold as dead in black
29
+ xfeats["xcor_hf"][(iko := channel_labels == 1)], np.arange(nc)[iko], "k*"
30
+ )
31
+ ax[0].plot( # plot the values above the similarity threshold as noisy in red
32
+ xfeats["xcor_hf"][
33
+ (iko := np.where(xfeats["xcor_hf"] > similarity_threshold[1]))
34
+ ],
35
+ np.arange(nc)[iko],
36
+ "r*",
37
+ )
38
+ ax[0].plot(similarity_threshold[0] * np.ones(2), [0, nc], "k--")
39
+ ax[0].plot(similarity_threshold[1] * np.ones(2), [0, nc], "r--")
40
+ ax[0].set(
41
+ ylabel="channel #",
42
+ xlabel="high coherence",
43
+ ylim=[0, nc],
44
+ title="a) dead channel",
45
+ )
46
+ ax[1].plot(xfeats["psd_hf"], np.arange(nc))
47
+ ax[1].plot(
48
+ xfeats["psd_hf"][(iko := xfeats["psd_hf"] > psd_hf_threshold)],
49
+ np.arange(nc)[iko],
50
+ "r*",
51
+ )
52
+ ax[1].plot(psd_hf_threshold * np.array([1, 1]), [0, nc], "r--")
53
+ ax[1].set(yticklabels=[], xlabel="PSD", ylim=[0, nc], title="b) noisy channel")
54
+ ax[1].sharey(ax[0])
55
+ ax[2].plot(xfeats["xcor_lf"], np.arange(nc))
56
+ ax[2].plot(
57
+ xfeats["xcor_lf"][(iko := channel_labels == 3)], np.arange(nc)[iko], "y*"
58
+ )
59
+ ax[2].plot([-0.75, -0.75], [0, nc], "y--")
60
+ ax[2].set(yticklabels=[], xlabel="LF coherence", ylim=[0, nc], title="c) outside")
61
+ ax[2].sharey(ax[0])
62
+ voltageshow(raw[:, :ns_plot], fs, ax=ax[3], cax=ax[4])
63
+ ax[3].sharey(ax[0])
64
+ fig.tight_layout()
65
+ return fig, ax
66
+
67
+
68
+ def voltageshow(
69
+ raw,
70
+ fs,
71
+ cmap="PuOr",
72
+ ax=None,
73
+ cax=None,
74
+ cbar_label="Voltage (uV)",
75
+ scaling=1e6,
76
+ vrange=None,
77
+ **axis_kwargs,
78
+ ):
79
+ """
80
+ Visualizes electrophysiological voltage data as a heatmap.
81
+
82
+ This function displays raw voltage data as a color-coded image with appropriate
83
+ scaling based on the sampling frequency. It automatically selects voltage range
84
+ based on whether the data is low-frequency (LF) or action potential (AP) data.
85
+
86
+ Parameters
87
+ ----------
88
+ raw : numpy.ndarray
89
+ Raw voltage data array with shape (channels, samples), in Volts
90
+ fs : float
91
+ Sampling frequency in Hz, used to determine time axis scaling and voltage range.
92
+ cmap : str, optional
93
+ Matplotlib colormap name for the heatmap. Default is 'PuOr'.
94
+ ax : matplotlib.axes.Axes, optional
95
+ Axes object to plot on. If None, a new figure and axes are created.
96
+ cax : matplotlib.axes.Axes, optional
97
+ Axes object for the colorbar. If None and ax is None, a new colorbar axes is created.
98
+ cbar_label : str, optional
99
+ Label for the colorbar. Default is 'Voltage (uV)'.
100
+ vrange: float, optional
101
+ Voltage range for the colorbar. Defaults to +/- 75 uV for AP and +/- 250 uV for LF.
102
+ scaling: float, optional
103
+ Unit transform: default is 1e6: we expect Volts but plot uV.
104
+ **axis_kwargs: optional
105
+ Additional keyword arguments for the axis properties, fed to the ax.set() method.
106
+ Returns
107
+ -------
108
+ matplotlib.image.AxesImage
109
+ The image object created by imshow, which can be used for further customization.
110
+ """
111
+ if ax is None:
112
+ fig, axs = plt.subplots(1, 2, gridspec_kw={"width_ratios": [1, 0.05]})
113
+ ax, cax = axs
114
+ nc, ns = raw.shape
115
+ default_vrange = LF_RANGE_UV if fs < 2600 else AP_RANGE_UV
116
+ vrange = vrange if vrange is not None else default_vrange
117
+ im = ax.imshow(
118
+ raw * scaling,
119
+ origin="lower",
120
+ cmap=cmap,
121
+ aspect="auto",
122
+ vmin=-vrange,
123
+ vmax=vrange,
124
+ extent=[0, ns / fs, 0, nc],
125
+ )
126
+ # set the axis properties: we use defaults values that can be overridden by user-provided ones
127
+ axis_kwargs = (
128
+ dict(ylim=[0, nc], xlabel="Time (s)", ylabel="Depth (μm)") | axis_kwargs
129
+ )
130
+ ax.set(**axis_kwargs)
131
+ ax.grid(False)
132
+ if cax is not None:
133
+ plt.colorbar(im, cax=cax, shrink=0.8).ax.set(ylabel=cbar_label)
134
+
135
+ return im
@@ -89,7 +89,7 @@ def parabolic_max(x):
89
89
  # for 2D arrays, operate along the last dimension
90
90
  ns = x.shape[-1]
91
91
  axis = -1
92
- imax = np.argmax(x, axis=axis)
92
+ imax = np.nanargmax(x, axis=axis)
93
93
 
94
94
  if x.ndim == 1:
95
95
  v010 = x[np.maximum(np.minimum(imax + np.array([-1, 0, 1]), ns - 1), 0)]
@@ -268,12 +268,64 @@ def make_channel_index(geom, radius=200.0, pad_val=None):
268
268
 
269
269
  class WindowGenerator(object):
270
270
  """
271
- `wg = WindowGenerator(ns, nswin, overlap)`
271
+ A utility class for generating sliding windows for signal processing applications.
272
272
 
273
- Provide sliding windows indices generator for signal processing applications.
274
- For straightforward spectrogram / periodogram implementation, prefer scipy methods !
273
+ WindowGenerator provides various methods to iterate through windows of a signal
274
+ with configurable window size and overlap. It's particularly useful for operations
275
+ like spectrograms, filtering, or any processing that requires windowed analysis.
275
276
 
276
- Example of implementations in test_dsp.py.
277
+ Parameters
278
+ ----------
279
+ ns : int
280
+ Total number of samples in the signal to be windowed.
281
+ nswin : int
282
+ Number of samples in each window.
283
+ overlap : int
284
+ Number of samples that overlap between consecutive windows.
285
+
286
+ Attributes
287
+ ----------
288
+ ns : int
289
+ Total number of samples in the signal.
290
+ nswin : int
291
+ Number of samples in each window.
292
+ overlap : int
293
+ Number of samples that overlap between consecutive windows.
294
+ nwin : int
295
+ Total number of windows.
296
+ iw : int or None
297
+ Current window index during iteration.
298
+
299
+ Notes
300
+ -----
301
+ For straightforward spectrogram or periodogram implementation,
302
+ scipy methods are recommended over this class.
303
+
304
+ Examples
305
+ --------
306
+ # straight windowing without overlap
307
+ >>> wg = WindowGenerator(ns=1000, nwin=111)
308
+ >>> signal = np.random.randn(1000)
309
+ >>> for window_slice in wg.slice:
310
+ ... window_data = signal[window_slice]
311
+ ... # Process window_data
312
+
313
+ # windowing with overlap (ie. buffers for apodization)
314
+ >>> for win_slice, valid_slice, win_valid_slice in wg.slices_valid:
315
+ ... window = signal[win_slice]
316
+ ... # Process window
317
+ ... processed = some_function_with_edge_effect(window)
318
+ ... # Only use the valid portion for reconstruction
319
+ ... recons[valid_slice] = processed[win_valid_slice]
320
+
321
+ # splicing add a fade-in / fade-out in the overlap so that reconstruction has unit amplitude
322
+ >>> recons = np.zeros_like(signal)
323
+ >>> for win_slice, amplitude in wg.splice:
324
+ ... window = signal[win_slice]
325
+ ... # Process window
326
+ ... processed = some_function(window)
327
+ ... # The processed windows is weighted with the amplitude and added to the reconstructed signal
328
+ ... recons[win_slice] = recons[win_slice] + processed * amplitude
277
329
  """
278
330
 
279
331
  def __init__(self, ns, nswin, overlap):
@@ -289,14 +341,35 @@ class WindowGenerator(object):
289
341
  self.iw = None
290
342
 
291
343
  @property
292
- def firstlast_splicing(self):
344
+ def splice(self):
293
345
  """
294
- Generator that yields the indices as well as an amplitude function that can be used
295
- to splice the windows together.
296
- In the overlap, the amplitude function gradually transitions the amplitude from one window
297
- to the next. The amplitudes always sum to one (ie. windows are symmetrical)
346
+ Generator that yields slices and amplitude arrays for windowed signal processing with splicing.
347
+
348
+ This property provides a convenient way to iterate through all windows with their
349
+ corresponding amplitude arrays for proper signal reconstruction. The amplitude arrays
350
+ contain tapering values (from a Hann window) at the overlapping regions to ensure
351
+ unit amplitude of all samples of the original signal
352
+
353
+ Yields
354
+ ------
355
+ tuple
356
+ A tuple containing:
357
+ - slice: A Python slice object representing the current window
358
+ - amp: A numpy array containing amplitude values for proper splicing/tapering
359
+ at overlap regions
360
+
361
+ Notes
362
+ -----
363
+ This is particularly useful for overlap-add methods where windows need to be
364
+ properly weighted before being combined in the reconstruction process.
365
+ """
366
+ for first, last, amp in self.firstlast_splicing:
367
+ yield slice(first, last), amp
298
368
 
299
- :return: tuple of (first_index, last_index, amplitude_vector]
369
+ @property
370
+ def firstlast_splicing(self):
371
+ """
372
+ cf. self.splice
300
373
  """
301
374
  w = scipy.signal.windows.hann((self.overlap + 1) * 2 + 1, sym=True)[
302
375
  1 : self.overlap + 1
@@ -310,7 +383,7 @@ class WindowGenerator(object):
310
383
  yield (first, last, amp)
311
384
 
312
385
  @property
313
- def firstlast_valid(self):
386
+ def firstlast_valid(self, discard_edges=False):
314
387
  """
315
388
  Generator that yields a tuple of first, last, first_valid, last_valid index of windows
316
389
  The valid indices span up to half of the overlap
@@ -318,12 +391,18 @@ class WindowGenerator(object):
318
391
  """
319
392
  assert self.overlap % 2 == 0, "Overlap must be even"
320
393
  for first, last in self.firstlast:
321
- first_valid = 0 if first == 0 else first + self.overlap // 2
322
- last_valid = last if last == self.ns else last - self.overlap // 2
394
+ first_valid = (
395
+ 0 if first == 0 and not discard_edges else first + self.overlap // 2
396
+ )
397
+ last_valid = (
398
+ last
399
+ if last == self.ns and not discard_edges
400
+ else last - self.overlap // 2
401
+ )
323
402
  yield (first, last, first_valid, last_valid)
324
403
 
325
404
  @property
326
- def firstlast(self, return_valid=False):
405
+ def firstlast(self):
327
406
  """
328
407
  Generator that yields first and last index of windows
329
408
 
@@ -343,13 +422,51 @@ class WindowGenerator(object):
343
422
  @property
344
423
  def slice(self):
345
424
  """
346
- Generator that yields slices of windows
347
-
348
- :return: a slice of the window
425
+ Generator that yields slice objects for each window in the signal.
426
+
427
+ This property provides a convenient way to iterate through all windows
428
+ defined by the WindowGenerator parameters. Each yielded slice can be
429
+ used directly to index into the original signal array.
430
+
431
+ Yields
432
+ ------
433
+ slice
434
+ A Python slice object representing the current window, defined by
435
+ its first and last indices. The slice can be used to extract the
436
+ corresponding window from the original signal.
349
437
  """
350
438
  for first, last in self.firstlast:
351
439
  yield slice(first, last)
352
440
 
441
+ @property
442
+ def slices_valid(self):
443
+ """
444
+ Generator that yields slices for windowed signal processing with valid regions.
445
+
446
+ This method generates tuples of slice objects that can be used to extract windows
447
+ from a signal and identify the valid (non-overlapping) portions within each window.
448
+ It's particularly useful for reconstruction operations where overlapping regions
449
+ need special handling.
450
+
451
+ Yields
452
+ ------
453
+ tuple
454
+ A tuple containing three slice objects:
455
+ - slice(first, last): The full window slice
456
+ - slice(first_valid, last_valid): The valid portion of the signal in absolute indices
457
+ - slice_window_valid: The valid portion relative to the window (for use within the window)
458
+
459
+ Notes
460
+ -----
461
+ This generator relies on the firstlast_valid property which provides the
462
+ indices for both the full windows and their valid regions.
463
+ """
464
+ for first, last, first_valid, last_valid in self.firstlast_valid:
465
+ slice_window_valid = slice(
466
+ first_valid - first, None if (lv := -(last - last_valid)) == 0 else lv
467
+ )
468
+ yield slice(first, last), slice(first_valid, last_valid), slice_window_valid
469
+
353
470
  def slice_array(self, sig, axis=-1):
354
471
  """
355
472
  Provided an array or sliceable object, generator that yields