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.
- {ibl_neuropixel-1.8.1/src/ibl_neuropixel.egg-info → ibl_neuropixel-1.9.1}/PKG-INFO +49 -20
- ibl_neuropixel-1.9.1/README.md +89 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/setup.py +1 -1
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1/src/ibl_neuropixel.egg-info}/PKG-INFO +49 -20
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibl_neuropixel.egg-info/SOURCES.txt +3 -1
- ibl_neuropixel-1.9.1/src/ibldsp/plots.py +135 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/utils.py +135 -18
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/voltage.py +155 -12
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/waveform_extraction.py +1 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/spikeglx.py +16 -11
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/integration/test_destripe.py +5 -4
- ibl_neuropixel-1.9.1/src/tests/unit/test_plots.py +30 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/unit/test_spikeglx.py +25 -2
- ibl_neuropixel-1.8.1/src/tests/unit/test_ibldsp.py → ibl_neuropixel-1.9.1/src/tests/unit/test_utils.py +18 -100
- ibl_neuropixel-1.9.1/src/tests/unit/test_voltage.py +160 -0
- ibl_neuropixel-1.8.1/README.md +0 -60
- ibl_neuropixel-1.8.1/src/ibldsp/plots.py +0 -58
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/LICENSE +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/MANIFEST.in +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/setup.cfg +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibl_neuropixel.egg-info/dependency_links.txt +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibl_neuropixel.egg-info/requires.txt +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibl_neuropixel.egg-info/top_level.txt +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/__init__.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/cadzow.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/cuda_tools.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/destripe_gpu.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/filter_gpu.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/fourier.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/icsd.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/raw_metrics.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/smooth.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/spiketrains.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/ibldsp/waveforms.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/neuropixel.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/neurowaveforms/__init__.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/neurowaveforms/model.py +0 -0
- {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
- {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
- {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
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/np2split/_spikeglx_ephysData_g0_t0.imec0.ap.ch +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/np2split/_spikeglx_ephysData_g0_t0.imec0.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_376_channels.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_g0_t0.imec.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_g0_t0.imec.lf.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_g0_t0.imec.wiring.json +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3A_short_g0_t0.imec.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B2_exported.imec0.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_catgt.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_g0_t0.imec1.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_g0_t0.imec1.lf.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_g0_t0.nidq.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_g0_t0.nidq.wiring.json +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sample3B_version202304.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.1_g0_t0.imec.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.1_prototype.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.4_1shank_g0_t0.imec.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.4_4shanks_appVersion20230905.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.4_4shanks_g0_t0.imec.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNP2.4_4shanks_while_acquiring_incomplete.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/sampleNPultra_g0_t0.imec0.ap.meta +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/waveform_sample/test_arr_in.npy +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/waveform_sample/test_arr_peak.npy +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/waveform_sample/test_df.csv +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/fixtures/waveform_sample/test_df_wavinfo.csv +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/integration/__init__.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/integration/csd_experiments.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/unit/__init__.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/unit/test_ephys_np2.py +0 -0
- {ibl_neuropixel-1.8.1 → ibl_neuropixel-1.9.1}/src/tests/unit/test_neuropixel.py +0 -0
- {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.
|
|
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
|
-
|
|
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
|
+

|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
271
|
+
A utility class for generating sliding windows for signal processing applications.
|
|
272
272
|
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
|
|
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
|
|
344
|
+
def splice(self):
|
|
293
345
|
"""
|
|
294
|
-
Generator that yields
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
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
|
-
|
|
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 =
|
|
322
|
-
|
|
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
|
|
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
|
|
347
|
-
|
|
348
|
-
|
|
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
|