lfpack 0.2.0__tar.gz → 0.3.0__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.
- {lfpack-0.2.0 → lfpack-0.3.0}/.gitignore +1 -0
- lfpack-0.3.0/CHANGELOG.md +38 -0
- lfpack-0.3.0/CLAUDE.md +81 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/PKG-INFO +16 -4
- {lfpack-0.2.0 → lfpack-0.3.0}/README.md +15 -3
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/_quarto.yml +6 -1
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/explanation/pipeline.qmd +3 -1
- lfpack-0.3.0/docs/figures/bwm_saturation_detection.png +0 -0
- lfpack-0.3.0/docs/figures/viewephys_recording_selector.jpg +0 -0
- lfpack-0.3.0/docs/how-to/bwm-dataset.qmd +253 -0
- lfpack-0.3.0/docs/objects.json +1 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/LFPackReader.qmd +29 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/_sidebar.yml +4 -0
- lfpack-0.3.0/docs/reference/compress.qmd +29 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/compress_bin_to_h5.qmd +25 -19
- lfpack-0.3.0/docs/reference/compress_to_h5.qmd +76 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/index.qmd +10 -1
- lfpack-0.3.0/docs/reference/merge_h5.qmd +31 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/run_cadzow_checkpoint.qmd +2 -0
- lfpack-0.3.0/docs/reference/subset_h5.qmd +33 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/pyproject.toml +1 -1
- {lfpack-0.2.0 → lfpack-0.3.0}/src/lfpack/__init__.py +1 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/src/lfpack/_core.py +347 -27
- {lfpack-0.2.0 → lfpack-0.3.0}/tests/test_lfpack.py +269 -3
- lfpack-0.2.0/CHANGELOG.md +0 -17
- lfpack-0.2.0/CLAUDE.md +0 -77
- lfpack-0.2.0/docs/how-to/bwm-dataset.qmd +0 -157
- lfpack-0.2.0/docs/objects.json +0 -1
- lfpack-0.2.0/docs/reference/compress.qmd +0 -21
- lfpack-0.2.0/docs/reference/compress_to_h5.qmd +0 -56
- {lfpack-0.2.0 → lfpack-0.3.0}/.githooks/pre-commit +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/.github/workflows/docs.yml +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/.github/workflows/publish_to_pypi.yaml +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/.github/workflows/tests.yml +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/CONTRIBUTING.md +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/.gitignore +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/README.txt +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/explanation/gallery.qmd +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/density_lfp_dab512bd.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/favicon.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_1a276285-8b0e-4cc9-9f0a-a3a002978724.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_1e104bf4-7a24-4624-a5b2-c2c8289c0de7.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_6638cfb3-3831-4fc2-9327-194b76cf22e1.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_749cb2b7-e57e-4453-a794-f6230e4d0226.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_d7ec0892-0a6c-4f4f-9d8f-72083692af5c.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_da8dfec1-d265-44e8-84ce-6ae9c109b8bd.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_dab512bd-a02d-4c1f-8dbc-9155a163efc0.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_dc7e9403-19f7-409f-9240-05ee57cb7aea.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_e8f9fba4-d151-4b00-bee7-447f0f3e752c.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_eebcaf65-7fa4-4118-869d-a084e84530e2.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-02_lfp_density_voltage_fe380793-8035-414e-b000-09bfe5ece92a.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-15_lfp_compression_aggregate.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/lfp_binned_comparison.jpg +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/logo.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/viewephys_screenshot.jpg +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/viewephys_screenshot.png +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/how-to/binned-reads.qmd +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/how-to/multi-recording.qmd +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/index.qmd +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/LFPCompressed.qmd +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/compress_pipeline.qmd +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/decompress.qmd +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/hdf5-layout.qmd +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/references.bib +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/docs/tutorials/first-compression.qmd +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/tests/__init__.py +0 -0
- {lfpack-0.2.0 → lfpack-0.3.0}/uv.lock +0 -0
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.3.0] - 2026-07-25
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `subset_h5` — copy a subset of recordings out of a multi-recording HDF5 archive
|
|
14
|
+
(inverse of `merge_h5`), for carving a smaller release (e.g. BWM) out of a larger
|
|
15
|
+
superset (e.g. ephys-atlas) without re-compression.
|
|
16
|
+
- Bad-channel labels (0=good, 1=dead, 2=noisy, 3=outside brain) persisted through
|
|
17
|
+
compression and exposed via `LFPackReader.channels` / `channels_full`.
|
|
18
|
+
- **Saturation detection and muting**: ADC-clipped spans are detected on the raw LFP
|
|
19
|
+
band, stored as a per-recording interval table, and muted (cosine-tapered zero) on
|
|
20
|
+
the decimated output after Cadzow denoising. Exposed via `LFPackReader.saturation`
|
|
21
|
+
(raw-rate sample indices, recording-aligned) and `.saturation_summary`.
|
|
22
|
+
`.saturation_mask` is a property, indexed like the reader itself (`sr.saturation_mask[a:b]`);
|
|
23
|
+
`.saturation_times()` converts samples to session-clock seconds (matching `times`).
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
- `compress` no longer decompresses low-SNR chunks to exact zero; new `floor_k=64`
|
|
27
|
+
survival floor keeps the dominant mode's largest coefficients. Closes #2.
|
|
28
|
+
|
|
29
|
+
## [0.2.0] - 2026-06-30
|
|
30
|
+
|
|
31
|
+
### Added
|
|
32
|
+
- `LFPackReader.channels` and `LFPackReader.channels_full` — per-channel probe geometry
|
|
33
|
+
and brain location annotations (`x/y/z` MNI coordinates, `atlas_id`, `acronym`).
|
|
34
|
+
Brain location fields are optional; the properties work on any existing file.
|
|
35
|
+
`channels` aggregates over binned-channel groups (mean for coordinates, mode for
|
|
36
|
+
brain region); `channels_full` always returns the raw per-electrode data.
|
|
37
|
+
- `compress_to_h5` accepts an optional `channels` dict to embed brain location
|
|
38
|
+
annotations at compression time.
|
lfpack-0.3.0/CLAUDE.md
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What this project is
|
|
6
|
+
|
|
7
|
+
**lfpack** is a lossy LFP (local-field-potential) codec for Neuropixels recordings. It compresses raw `.cbin` LFP binaries into HDF5 files at >100× compression with RMSE < 25 µV, using an 8-stage pipeline followed by adaptive SVD + wavelet-packet thresholding.
|
|
8
|
+
|
|
9
|
+
## Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
# Dev setup
|
|
13
|
+
uv sync --group dev
|
|
14
|
+
git config core.hooksPath .githooks # installs ruff pre-commit hook
|
|
15
|
+
|
|
16
|
+
# Tests
|
|
17
|
+
uv run pytest # all tests
|
|
18
|
+
uv run pytest tests/test_lfpack.py::TestLfpack::test_compress_output_shapes # single test
|
|
19
|
+
|
|
20
|
+
# Lint / format
|
|
21
|
+
uv run ruff check src/ tests/
|
|
22
|
+
uv run ruff format src/ tests/
|
|
23
|
+
|
|
24
|
+
# Docs — regenerate API reference from docstrings, then preview
|
|
25
|
+
uv run quartodoc build --config docs/_quarto.yml
|
|
26
|
+
quarto preview docs/
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Architecture
|
|
30
|
+
|
|
31
|
+
Nearly all code lives in `src/lfpack/_core.py` (~1000 lines). The public API is re-exported from `src/lfpack/__init__.py`.
|
|
32
|
+
|
|
33
|
+
### Compression pipeline (`compress_bin_to_h5`)
|
|
34
|
+
|
|
35
|
+
0. Saturation detection — flag ADC-clipped samples on the raw LFP band *before* any step below obscures them (`ibldsp.voltage.saturation_cbin`, parallel over `n_jobs`). Detection is **amplitude-only** for LFP (`v_per_sec=None`): the derivative criterion is tuned for the 30 kHz AP band and mislabels normal LFP dynamics as saturation, so it is disabled here. Stored as an insertion-level interval table (see HDF5 layout) and used to mute the clipped stretches; toggle with `detect_saturation` / `saturation_kwargs`.
|
|
36
|
+
1. Bad-channel detection (`ibldsp.voltage.detect_bad_channels_cbin`) — labels saved to the scale-`00` `meta` as a `labels` int8 attr; read via `LFPackReader.channels`/`channels_full` under the `labels` key.
|
|
37
|
+
2. Dephasing — sample-shift correction (NP1 only, via `ibldsp.fourier.fshift`)
|
|
38
|
+
3. Highpass filter — 0.5 Hz zero-phase 3rd-order Butterworth (keeps delta/infra-slow; ibldsp warmup padding scales with the corner). The true recording start/end are cosine-apodized (~3 filter time-constants) before this filter so the zero-phase transient cannot ring against the data-boundary step (interior chunk edges are handled by the warmup padding).
|
|
39
|
+
4. Bad-channel interpolation — distance-weighted neighbors
|
|
40
|
+
5. CAR — median subtraction across channels
|
|
41
|
+
6. Decimation — 2500 → 250 Hz (Q=10)
|
|
42
|
+
7. Cadzow denoising — spatial rank reduction (`ibldsp.cadzow`)
|
|
43
|
+
8. **Adaptive SVD + wavelet-packet thresholding** — the actual codec
|
|
44
|
+
|
|
45
|
+
Steps 6–7 are checkpointed to a `.npy` file so the expensive decimation can be skipped on resume. Steps 7–8 are parallelised with `joblib`. Muting (step 0): a raw-rate boolean saturation mask is passed to `resample_denoise_lfp_cbin` as `saturation_file`; each worker downsamples its slice of the mask onto the decimated grid (block-max over each Q-sample block), cosine-tapers it (`mute_window_samples`, now in **decimated** output samples) and multiplies the **decimated output *after* Cadzow** by it. This *late* muting replaced the earlier pre-filter approach: muting the raw traces before the highpass/CAR/decimate/Cadzow did not keep the span clean — those stages leaked energy back into it (residual noise, spurious CSD sources/sinks); a single re-mute of the final output is cleaner and cheaper. Muting is skipped when resuming from an existing checkpoint (data already decimated); the interval table is still written.
|
|
46
|
+
|
|
47
|
+
### Codec (`compress` / `decompress`)
|
|
48
|
+
|
|
49
|
+
`compress(data, epsilon=150, alpha=28)` → `LFPCompressed` dataclass:
|
|
50
|
+
- **SVD rank selection**: keep singular values > `epsilon × sigma_noise`
|
|
51
|
+
- **Wavelet-packet thresholding**: db4, level 5; per-component threshold `alpha × sigma_noise / sv[k]`
|
|
52
|
+
- Sparse Vh stored as `(vh_indices, vh_values)` in HDF5; `U_scaled` with shuffle+gzip
|
|
53
|
+
|
|
54
|
+
Guard bands: 64-sample Cadzow halos, 128-sample SVD/WP overlap to prevent edge transients.
|
|
55
|
+
|
|
56
|
+
### HDF5 layout (multi-recording, pyramidal)
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
<file>.h5
|
|
60
|
+
└─ <recording>/
|
|
61
|
+
├─ saturation # (n_intervals, 2) int64 [start_sample, stop_sample] at raw LFP fs;
|
|
62
|
+
│ # written once per recording (scale-independent). attrs: fs, ns_total,
|
|
63
|
+
│ # n_saturated_samples, saturated_fraction, detection params, muted
|
|
64
|
+
└─ <scale_2digit>/
|
|
65
|
+
├─ meta # nc, ns_total, fs, fs_sync, t0_sync, epsilon, alpha, geometry, …
|
|
66
|
+
└─ chunks/
|
|
67
|
+
└─ <i>/ # U_scaled, vh_indices, vh_values + attrs
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
The `saturation` node sits at recording level (not under a scale) because it describes the raw recording, not a codec pyramid level; `merge_h5` copies it automatically. Legacy flat layout (meta at root) is still readable for backwards compatibility.
|
|
71
|
+
|
|
72
|
+
### `LFPackReader`
|
|
73
|
+
|
|
74
|
+
Drop-in replacement for `spikeglx.Reader`. Wraps an HDF5 file and decompresses chunks on demand. Supports multi-recording files via `recording=` kwarg and pyramidal scales via `scale=` kwarg. Saturation access: `.saturation` (interval DataFrame with raw samples + seconds), `.saturation_summary` (fraction/count/muted from attrs), `saturation_mask(first, last)` (boolean at the reader's decimated rate, interval edges rounded outward). All three degrade gracefully to empty/default on files written without saturation detection.
|
|
75
|
+
|
|
76
|
+
## Ecosystem interconnections
|
|
77
|
+
|
|
78
|
+
lfpack sits in a tight three-way dependency with two sibling IBL packages:
|
|
79
|
+
|
|
80
|
+
- **ibl-neuropixel** (`/Users/olivier/PycharmProjects/ephys-atlas/ibl-neuropixel`) — provides the low-level Neuropixels binary I/O (`spikeglx.Reader`), destriping, dephasing (`ibldsp.fourier.fshift`), decimation (`ibldsp.voltage.resample_denoise_lfp_cbin`), bad-channel detection (`ibldsp.voltage.detect_bad_channels_cbin`), saturation detection (`ibldsp.voltage.saturation_cbin` / `saturation` / `saturation_samples_to_intervals`), and Cadzow denoising (`ibldsp.cadzow`). lfpack wraps these directly; changes to ibldsp can break the pipeline. `resample_denoise_lfp_cbin` gained a `saturation_file` / `mute_window_samples` parameter (added for this codec) to late-mute saturated stretches on the decimated output after Cadzow.
|
|
81
|
+
- **viewephys** (`/Users/olivier/PycharmProjects/ephys-atlas/viewephys`) — Qt-based interactive viewer for raw Neuropixels traces. Since [PR #49](https://github.com/int-brain-lab/viewephys/pull/49) it has a native lfpack backend (`LFPackDataModel` / `LFPackBinViewer`, optional `viewephys[lfpack]` extra): `viewephys -f file.h5` opens a `.h5` file directly, with automatic brain-region colouring from embedded `atlas_id`/`acronym` annotations, a searchable multi-recording selector, and a CSD step. No manual transpose or `BrainRegions` wiring needed.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lfpack
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: LFP codec — lossy compression of local-field-potential recordings via adaptive SVD and wavelet-packet thresholding
|
|
5
5
|
Project-URL: Homepage, https://github.com/int-brain-lab/lfpack
|
|
6
6
|
Project-URL: Bug Tracker, https://github.com/int-brain-lab/lfpack/issues
|
|
@@ -19,9 +19,6 @@ Description-Content-Type: text/markdown
|
|
|
19
19
|
|
|
20
20
|
# lfpack — LFP codec for Neuropixels recordings
|
|
21
21
|
|
|
22
|
-
> **IBL Brain-Wide Map LFP dataset** — 699 recordings, 384 channels, session-clock aligned.
|
|
23
|
-
> [How to access →](https://int-brain-lab.github.io/lfpack/how-to/bwm-dataset.html)
|
|
24
|
-
|
|
25
22
|
<p align="center">
|
|
26
23
|
<img src="docs/figures/logo.png" alt="lfpack logo" width="300"/>
|
|
27
24
|
</p>
|
|
@@ -30,10 +27,25 @@ Lossy codec for local-field-potential (LFP) recordings from Neuropixels probes.
|
|
|
30
27
|
Achieves **>100× compression** with median RMSE < 25 µV via an 8-stage pipeline
|
|
31
28
|
(bad-channel detection → dephasing → highpass → interpolation → CAR → decimation → Cadzow → adaptive SVD + wavelet-packet thresholding).
|
|
32
29
|
|
|
30
|
+
> **IBL Brain-Wide Map LFP dataset** — 699 recordings, 384 channels, session-clock aligned.
|
|
31
|
+
> [How to access →](https://int-brain-lab.github.io/lfpack/how-to/bwm-dataset.html)
|
|
32
|
+
|
|
33
33
|
```bash
|
|
34
34
|
pip install lfpack
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
## Visualise with viewephys
|
|
38
|
+
|
|
39
|
+
[`viewephys`](https://github.com/int-brain-lab/viewephys) opens lfpack `.h5` files natively —
|
|
40
|
+
`viewephys -f recording.h5` gets you a browsable, brain-region-coloured view with no
|
|
41
|
+
manual decompression step:
|
|
42
|
+
|
|
43
|
+
<p align="center">
|
|
44
|
+
<img src="docs/figures/viewephys_screenshot.jpg" alt="viewephys opened directly on a lfpack HDF5 file, showing brain-region-coloured LFP traces" width="700"/>
|
|
45
|
+
</p>
|
|
46
|
+
|
|
47
|
+
See the [BWM how-to](https://int-brain-lab.github.io/lfpack/how-to/bwm-dataset.html#visualise-with-viewephys) for details.
|
|
48
|
+
|
|
37
49
|
## Documentation
|
|
38
50
|
|
|
39
51
|
Full documentation is at **https://int-brain-lab.github.io/lfpack/**.
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
# lfpack — LFP codec for Neuropixels recordings
|
|
2
2
|
|
|
3
|
-
> **IBL Brain-Wide Map LFP dataset** — 699 recordings, 384 channels, session-clock aligned.
|
|
4
|
-
> [How to access →](https://int-brain-lab.github.io/lfpack/how-to/bwm-dataset.html)
|
|
5
|
-
|
|
6
3
|
<p align="center">
|
|
7
4
|
<img src="docs/figures/logo.png" alt="lfpack logo" width="300"/>
|
|
8
5
|
</p>
|
|
@@ -11,10 +8,25 @@ Lossy codec for local-field-potential (LFP) recordings from Neuropixels probes.
|
|
|
11
8
|
Achieves **>100× compression** with median RMSE < 25 µV via an 8-stage pipeline
|
|
12
9
|
(bad-channel detection → dephasing → highpass → interpolation → CAR → decimation → Cadzow → adaptive SVD + wavelet-packet thresholding).
|
|
13
10
|
|
|
11
|
+
> **IBL Brain-Wide Map LFP dataset** — 699 recordings, 384 channels, session-clock aligned.
|
|
12
|
+
> [How to access →](https://int-brain-lab.github.io/lfpack/how-to/bwm-dataset.html)
|
|
13
|
+
|
|
14
14
|
```bash
|
|
15
15
|
pip install lfpack
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
+
## Visualise with viewephys
|
|
19
|
+
|
|
20
|
+
[`viewephys`](https://github.com/int-brain-lab/viewephys) opens lfpack `.h5` files natively —
|
|
21
|
+
`viewephys -f recording.h5` gets you a browsable, brain-region-coloured view with no
|
|
22
|
+
manual decompression step:
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<img src="docs/figures/viewephys_screenshot.jpg" alt="viewephys opened directly on a lfpack HDF5 file, showing brain-region-coloured LFP traces" width="700"/>
|
|
26
|
+
</p>
|
|
27
|
+
|
|
28
|
+
See the [BWM how-to](https://int-brain-lab.github.io/lfpack/how-to/bwm-dataset.html#visualise-with-viewephys) for details.
|
|
29
|
+
|
|
18
30
|
## Documentation
|
|
19
31
|
|
|
20
32
|
Full documentation is at **https://int-brain-lab.github.io/lfpack/**.
|
|
@@ -65,4 +65,9 @@ quartodoc:
|
|
|
65
65
|
- title: "Reader"
|
|
66
66
|
desc: "Random-access decompression."
|
|
67
67
|
contents:
|
|
68
|
-
- LFPackReader
|
|
68
|
+
- LFPackReader
|
|
69
|
+
- title: "Multi-recording archives"
|
|
70
|
+
desc: "Combine or subset recordings across HDF5 files without re-compression."
|
|
71
|
+
contents:
|
|
72
|
+
- merge_h5
|
|
73
|
+
- subset_h5
|
|
@@ -139,7 +139,9 @@ This ensures dominant spatial modes receive finer temporal detail than weak mode
|
|
|
139
139
|
|
|
140
140
|
Non-zero coefficients are stored as sparse index/value pairs in HDF5.
|
|
141
141
|
|
|
142
|
-
**
|
|
142
|
+
**Survival floor.** Because each row of $V_r^\top$ has unit norm, its wavelet-packet coefficients have a fixed magnitude ceiling ($\max_j|w_{kj}| \approx 0.2\text{–}0.4$). On low-SNR chunks — where even the dominant mode is weak, $s_0/\sigma_\text{noise} \lesssim \alpha/\max_j|w_{0j}| \approx 140$ — the threshold exceeds every coefficient and the whole chunk reconstructs to exact zero, silently discarding real low-amplitude LFP. To prevent this the dominant mode ($k=0$) always keeps its `floor_k` largest coefficients. The floor is a no-op on high-SNR recordings (their dominant row keeps far more than `floor_k`, so their output is bit-for-bit unchanged) and on saturation-muted spans, which enter as all-zero and still reconstruct to zero ($s_0 = 0 \Rightarrow$ `U_scaled` $= 0$).
|
|
143
|
+
|
|
144
|
+
**Parameters:** `epsilon` — SVD noise threshold multiplier (default `150`); `alpha` — WP threshold multiplier (default `28`); `floor_k` — minimum WP coefficients kept on the dominant mode (default `64`)
|
|
143
145
|
|
|
144
146
|
### Guard bands
|
|
145
147
|
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "How to access the IBL Brain-Wide Map LFP dataset"
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
The IBL Brain-Wide Map (BWM) LFP dataset is distributed as a 16GB `.h5` file
|
|
6
|
+
containing 699 individual recordings of 384 channels each.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
::: {.callout-important}
|
|
10
|
+
## Before you start
|
|
11
|
+
|
|
12
|
+
- **Align with trial events via `sr.times`** — each recording stores a sync-corrected
|
|
13
|
+
session clock; always use `sr.times` rather than a manual `np.arange / sr.fs` when
|
|
14
|
+
matching LFP samples to spike times or trial events.
|
|
15
|
+
See [Session-clock times](#session-clock-times).
|
|
16
|
+
|
|
17
|
+
- **Use binned reads for brainwide sweeps** — looping over all 699 recordings at full
|
|
18
|
+
384-channel resolution is memory-intensive. Pass `bin_channels=4` to reduce each
|
|
19
|
+
recording to 96 spatial bins with acceptable loss of depth coverage.
|
|
20
|
+
See [Binned-channel reads](#binned-channel-reads).
|
|
21
|
+
:::
|
|
22
|
+
|
|
23
|
+
## Visualise with viewephys
|
|
24
|
+
|
|
25
|
+
`viewephys` has a native lfpack backend — point it at the `.h5` file directly,
|
|
26
|
+
no manual transpose or `BrainRegions` wiring needed:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
viewephys -f lf_compressed_all_bwm.h5
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The `.h5` extension is auto-detected and opens the lfpack-aware viewer, which:
|
|
33
|
+
|
|
34
|
+
- shows a searchable recording selector (type any `pid` substring) when the
|
|
35
|
+
file holds multiple recordings, preserving window size, position and zoom
|
|
36
|
+
when you switch
|
|
37
|
+
- colours the depth axis by brain region automatically, reading the
|
|
38
|
+
`atlas_id` / `acronym` annotations embedded in the file — no separate
|
|
39
|
+
`iblatlas` call required
|
|
40
|
+
- adds a **CSD** (current-source density) step alongside the raw signal
|
|
41
|
+
|
|
42
|
+
{fig-alt="Ephys Bin Viewer window showing the searchable recording dropdown and raw/CSD checkboxes for an lfpack HDF5 file" .lightbox}
|
|
43
|
+
|
|
44
|
+
The same class is usable from Python:
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
from viewephys.gui import LFPackBinViewer
|
|
48
|
+
from viewephys.viewer.qt import create_app
|
|
49
|
+
|
|
50
|
+
app = create_app()
|
|
51
|
+
win = LFPackBinViewer("lf_compressed_all_bwm.h5")
|
|
52
|
+
app.exec()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
{fig-alt="viewephys density view showing 384-channel LFP data with brain region colour bar and a searchable recording selector" .lightbox}
|
|
56
|
+
|
|
57
|
+
See [viewephys PR #49](https://github.com/int-brain-lab/viewephys/pull/49) for implementation details.
|
|
58
|
+
|
|
59
|
+
## Prerequisites
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv pip install "viewephys[lfpack]" one-api
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Download
|
|
66
|
+
|
|
67
|
+
From inside the `ibl-ai-agent` repository:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run python scripts/download_datasets.py --lfp
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
This downloads `lf_compressed_all_bwm.h5` (~14 GB) into `reports/datasets/bwm_lfp/`
|
|
74
|
+
and writes the path into `data_locations.local.yaml` automatically.
|
|
75
|
+
|
|
76
|
+
Or, from Python (no AWS credentials needed — uses the ONE public S3 helper):
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from one.remote.aws import s3_download_file
|
|
80
|
+
|
|
81
|
+
s3_download_file(
|
|
82
|
+
source="resources/ibl-agent-data/lf_compressed_all_bwm.h5",
|
|
83
|
+
destination="lf_compressed_all_bwm.h5",
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`s3_download_file` defaults to the IBL public bucket, shows a tqdm progress bar,
|
|
88
|
+
and skips the download if the local file already has the correct size.
|
|
89
|
+
|
|
90
|
+
## Session-clock times
|
|
91
|
+
|
|
92
|
+
`sr.times` returns a `(ns,)` array of session-clock timestamps (seconds) for every
|
|
93
|
+
LFP sample, derived from the sync signal stored during compression.
|
|
94
|
+
Use it to align LFP traces with trial events or spike times:
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
from lfpack import LFPackReader
|
|
98
|
+
import numpy as np
|
|
99
|
+
|
|
100
|
+
file_lfpack = "lf_compressed_all_bwm.h5"
|
|
101
|
+
pid = 'dab512bd-a02d-4c1f-8dbc-9155a163efc0'
|
|
102
|
+
|
|
103
|
+
sr = LFPackReader(file_lfpack, recording=pid)
|
|
104
|
+
|
|
105
|
+
n = int(10 * sr.fs)
|
|
106
|
+
traces = sr[:n, :] # (n_samples, nc) — first 10 s
|
|
107
|
+
t = sr.times[:n] # (n_samples,) — session-clock seconds
|
|
108
|
+
|
|
109
|
+
# Align with trial events loaded via ONE
|
|
110
|
+
# stim_on = trials['stimOn_times'] # already in session-clock seconds
|
|
111
|
+
# idx = np.searchsorted(t, stim_on) # nearest LFP sample for each stimulus onset
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## List recordings
|
|
115
|
+
|
|
116
|
+
Each file contains one entry per BWM probe. Recording identifiers are named after the probe identifier UUID `pid`.
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
from lfpack import LFPackReader
|
|
120
|
+
|
|
121
|
+
recordings = LFPackReader.recordings("lf_compressed_all_bwm.h5")
|
|
122
|
+
print(f"{len(recordings)} recordings")
|
|
123
|
+
print(recordings[:3])
|
|
124
|
+
# 699 recordings
|
|
125
|
+
# ['00a824c0-e060-495f-9ebc-79c82fef4c67', '00a96dee-1e8b-44cc-9cc3-aca704d2b594', '00c425fd-ec3e-4cd2-b8af-c0bc0c4bdd44']
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Read a single recording
|
|
129
|
+
|
|
130
|
+
```python
|
|
131
|
+
from lfpack import LFPackReader
|
|
132
|
+
|
|
133
|
+
file_lfpack = "lf_compressed_all_bwm.h5"
|
|
134
|
+
pid = 'dab512bd-a02d-4c1f-8dbc-9155a163efc0'
|
|
135
|
+
|
|
136
|
+
sr = LFPackReader(file_lfpack, recording=pid)
|
|
137
|
+
|
|
138
|
+
# First 10 seconds — shape (2500, nc), float32, volts
|
|
139
|
+
traces = sr[: int(10 * sr.fs)].T
|
|
140
|
+
|
|
141
|
+
print(f"Duration: {sr.ns / sr.fs:.1f} s")
|
|
142
|
+
print(f"Channels: {sr.nc}")
|
|
143
|
+
print(f"Sample rate: {sr.fs} Hz") # 250 Hz (decimated from 2500 Hz)
|
|
144
|
+
print(f"Shape: {traces.shape}")
|
|
145
|
+
|
|
146
|
+
# Duration: 3668.9 s
|
|
147
|
+
# Channels: 384
|
|
148
|
+
# Sample rate: 250.00252518761928 Hz
|
|
149
|
+
# Shape: (384, 2500)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
`LFPackReader` is a drop-in for `spikeglx.Reader`.
|
|
153
|
+
Slicing decompresses only the requested chunks — the full file is never loaded into memory.
|
|
154
|
+
|
|
155
|
+
## Quality Control - Saturation
|
|
156
|
+
|
|
157
|
+
Each recording carries an insertion-level table of ADC-saturated (clipped) spans,
|
|
158
|
+
detected on the raw LFP band before any filtering. The saturated stretches are muted
|
|
159
|
+
(zeroed) in the decompressed output, so it's worth checking whether a window of
|
|
160
|
+
interest overlaps one before trusting the amplitude.
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from lfpack import LFPackReader
|
|
164
|
+
|
|
165
|
+
sr = LFPackReader("lf_compressed_all_bwm.h5", recording=pid)
|
|
166
|
+
|
|
167
|
+
# Interval table: raw-rate sample indices, recording-aligned
|
|
168
|
+
sr.saturation
|
|
169
|
+
# start_sample stop_sample
|
|
170
|
+
# 0 123456 125000
|
|
171
|
+
|
|
172
|
+
# Recording-level summary (fraction/count/whether muting was applied)
|
|
173
|
+
sr.saturation_summary
|
|
174
|
+
# {'saturated_fraction': 0.0052, 'n_intervals': 3, 'total_saturated_sec': 12.9, 'muted': True, ...}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
`saturation_mask` gives a boolean array at the reader's own — decimated — sampling rate,
|
|
178
|
+
indexed exactly like the reader itself rather than called with a window:
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
n = int(10 * sr.fs)
|
|
182
|
+
traces = sr[:n, :] # (n, nc)
|
|
183
|
+
mask = sr.saturation_mask[:n] # (n,) bool, True where saturated
|
|
184
|
+
|
|
185
|
+
traces_clean = traces[~mask]
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
For session-clock timestamps of saturated intervals (e.g. to overlay against trial events),
|
|
189
|
+
use `saturation_times` rather than converting `sr.saturation`'s samples to seconds by hand —
|
|
190
|
+
it applies the same sync correction as [`sr.times`](#session-clock-times):
|
|
191
|
+
|
|
192
|
+
```python
|
|
193
|
+
sr.saturation_times()
|
|
194
|
+
# start_sample stop_sample start_time stop_time
|
|
195
|
+
# 0 123456 125000 1234.382 1235.000
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
All four (`saturation`, `saturation_summary`, `saturation_mask`, `saturation_times`) degrade
|
|
199
|
+
gracefully to empty/default values on recordings without saturation detection, so the same
|
|
200
|
+
code works across the whole dataset without a try/except.
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
{.lightbox}
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
## Channel brain locations
|
|
207
|
+
|
|
208
|
+
Each recording stores per-channel brain location annotations embedded in the file —
|
|
209
|
+
no network call needed. Access them via `sr.channels`:
|
|
210
|
+
|
|
211
|
+
```python
|
|
212
|
+
from lfpack import LFPackReader
|
|
213
|
+
|
|
214
|
+
sr = LFPackReader("lf_compressed_all_bwm.h5", recording=pid)
|
|
215
|
+
ch = sr.channels
|
|
216
|
+
|
|
217
|
+
print(ch["acronym"][:5]) # ['VISp', 'VISp', 'CA1', 'CA1', 'DG']
|
|
218
|
+
print(ch["atlas_id"][:5]) # Allen CCF structure IDs, int32
|
|
219
|
+
print(ch["x"][:5]) # mediolateral MNI coordinates, metres
|
|
220
|
+
print(ch["y"][:5]) # anteroposterior
|
|
221
|
+
print(ch["z"][:5]) # dorsoventral
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
When using `bin_channels`, `channels` automatically aggregates: coordinates are
|
|
225
|
+
averaged and brain region is the within-group mode:
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
sr = LFPackReader("lf_compressed_all_bwm.h5", recording=pid, bin_channels=4)
|
|
229
|
+
ch = sr.channels # shapes (96,) — one entry per spatial bin
|
|
230
|
+
ch_full = sr.channels_full # shapes (384,) — always raw per-electrode
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Binned-channel reads
|
|
234
|
+
|
|
235
|
+
Adjacent channels are spatially correlated at LFP frequencies.
|
|
236
|
+
Passing `bin_channels` sums neighbouring channels during decompression, reducing
|
|
237
|
+
memory use. A factor of 4 takes 384 channels down to 96 while keeping depth resolution useful for LFP.
|
|
238
|
+
|
|
239
|
+
```python
|
|
240
|
+
from lfpack import LFPackReader
|
|
241
|
+
|
|
242
|
+
file_lfpack = "lf_compressed_all_bwm.h5"
|
|
243
|
+
pid = 'dab512bd-a02d-4c1f-8dbc-9155a163efc0'
|
|
244
|
+
|
|
245
|
+
sr = LFPackReader(file_lfpack, recording=pid, bin_channels=4)
|
|
246
|
+
|
|
247
|
+
traces = sr[:, :] # (ns, 96) — full recording, 96 spatial bins
|
|
248
|
+
print(sr.nc) # 96
|
|
249
|
+
print(sr.geometry["y"]) # mean depth of each 4-channel group
|
|
250
|
+
print(sr.geometry_full['binned_channel_index']) # spatial mapping of the original 384 channels into the 96 binned ones
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
{fig-alt="Side-by-side LFP density plots at full and 4x binned channel resolution" .lightbox}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"project": "lfpack", "version": "0.0.9999", "count": 54, "items": [{"name": "lfpack.compress_bin_to_h5", "domain": "py", "role": "function", "priority": "1", "uri": "reference/compress_bin_to_h5.html#lfpack.compress_bin_to_h5", "dispname": "-"}, {"name": "lfpack._core.compress_bin_to_h5", "domain": "py", "role": "function", "priority": "1", "uri": "reference/compress_bin_to_h5.html#lfpack.compress_bin_to_h5", "dispname": "lfpack.compress_bin_to_h5"}, {"name": "lfpack.compress_to_h5", "domain": "py", "role": "function", "priority": "1", "uri": "reference/compress_to_h5.html#lfpack.compress_to_h5", "dispname": "-"}, {"name": "lfpack._core.compress_to_h5", "domain": "py", "role": "function", "priority": "1", "uri": "reference/compress_to_h5.html#lfpack.compress_to_h5", "dispname": "lfpack.compress_to_h5"}, {"name": "lfpack.compress_pipeline", "domain": "py", "role": "function", "priority": "1", "uri": "reference/compress_pipeline.html#lfpack.compress_pipeline", "dispname": "-"}, {"name": "lfpack._core.compress_pipeline", "domain": "py", "role": "function", "priority": "1", "uri": "reference/compress_pipeline.html#lfpack.compress_pipeline", "dispname": "lfpack.compress_pipeline"}, {"name": "lfpack.compress", "domain": "py", "role": "function", "priority": "1", "uri": "reference/compress.html#lfpack.compress", "dispname": "-"}, {"name": "lfpack._core.compress", "domain": "py", "role": "function", "priority": "1", "uri": "reference/compress.html#lfpack.compress", "dispname": "lfpack.compress"}, {"name": "lfpack.decompress", "domain": "py", "role": "function", "priority": "1", "uri": "reference/decompress.html#lfpack.decompress", "dispname": "-"}, {"name": "lfpack._core.decompress", "domain": "py", "role": "function", "priority": "1", "uri": "reference/decompress.html#lfpack.decompress", "dispname": "lfpack.decompress"}, {"name": "lfpack.run_cadzow_checkpoint", "domain": "py", "role": "function", "priority": "1", "uri": "reference/run_cadzow_checkpoint.html#lfpack.run_cadzow_checkpoint", "dispname": "-"}, {"name": "lfpack._core.run_cadzow_checkpoint", "domain": "py", "role": "function", "priority": "1", "uri": "reference/run_cadzow_checkpoint.html#lfpack.run_cadzow_checkpoint", "dispname": "lfpack.run_cadzow_checkpoint"}, {"name": "lfpack.LFPCompressed", "domain": "py", "role": "class", "priority": "1", "uri": "reference/LFPCompressed.html#lfpack.LFPCompressed", "dispname": "-"}, {"name": "lfpack._core.LFPCompressed", "domain": "py", "role": "class", "priority": "1", "uri": "reference/LFPCompressed.html#lfpack.LFPCompressed", "dispname": "lfpack.LFPCompressed"}, {"name": "lfpack.LFPackReader.bin_channels", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.bin_channels", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.bin_channels", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.bin_channels", "dispname": "lfpack.LFPackReader.bin_channels"}, {"name": "lfpack.LFPackReader.channels", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.channels", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.channels", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.channels", "dispname": "lfpack.LFPackReader.channels"}, {"name": "lfpack.LFPackReader.channels_full", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.channels_full", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.channels_full", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.channels_full", "dispname": "lfpack.LFPackReader.channels_full"}, {"name": "lfpack.LFPackReader.fs", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.fs", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.fs", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.fs", "dispname": "lfpack.LFPackReader.fs"}, {"name": "lfpack.LFPackReader.geometry", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.geometry", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.geometry", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.geometry", "dispname": "lfpack.LFPackReader.geometry"}, {"name": "lfpack.LFPackReader.geometry_full", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.geometry_full", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.geometry_full", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.geometry_full", "dispname": "lfpack.LFPackReader.geometry_full"}, {"name": "lfpack.LFPackReader.nc", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.nc", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.nc", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.nc", "dispname": "lfpack.LFPackReader.nc"}, {"name": "lfpack.LFPackReader.read", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.read", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.read", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.read", "dispname": "lfpack.LFPackReader.read"}, {"name": "lfpack.LFPackReader.read_samples", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.read_samples", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.read_samples", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.read_samples", "dispname": "lfpack.LFPackReader.read_samples"}, {"name": "lfpack.LFPackReader.recordings", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.recordings", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.recordings", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.recordings", "dispname": "lfpack.LFPackReader.recordings"}, {"name": "lfpack.LFPackReader.saturation", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.saturation", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.saturation", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.saturation", "dispname": "lfpack.LFPackReader.saturation"}, {"name": "lfpack.LFPackReader.saturation_mask", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.saturation_mask", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.saturation_mask", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.saturation_mask", "dispname": "lfpack.LFPackReader.saturation_mask"}, {"name": "lfpack.LFPackReader.saturation_summary", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.saturation_summary", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.saturation_summary", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.saturation_summary", "dispname": "lfpack.LFPackReader.saturation_summary"}, {"name": "lfpack.LFPackReader.saturation_times", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.saturation_times", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.saturation_times", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.saturation_times", "dispname": "lfpack.LFPackReader.saturation_times"}, {"name": "lfpack.LFPackReader.scales", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.scales", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.scales", "domain": "py", "role": "function", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.scales", "dispname": "lfpack.LFPackReader.scales"}, {"name": "lfpack.LFPackReader.t0", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.t0", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.t0", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.t0", "dispname": "lfpack.LFPackReader.t0"}, {"name": "lfpack.LFPackReader.times", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.times", "dispname": "-"}, {"name": "lfpack._core.LFPackReader.times", "domain": "py", "role": "attribute", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader.times", "dispname": "lfpack.LFPackReader.times"}, {"name": "lfpack.LFPackReader", "domain": "py", "role": "class", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader", "dispname": "-"}, {"name": "lfpack._core.LFPackReader", "domain": "py", "role": "class", "priority": "1", "uri": "reference/LFPackReader.html#lfpack.LFPackReader", "dispname": "lfpack.LFPackReader"}, {"name": "lfpack.merge_h5", "domain": "py", "role": "function", "priority": "1", "uri": "reference/merge_h5.html#lfpack.merge_h5", "dispname": "-"}, {"name": "lfpack._core.merge_h5", "domain": "py", "role": "function", "priority": "1", "uri": "reference/merge_h5.html#lfpack.merge_h5", "dispname": "lfpack.merge_h5"}, {"name": "lfpack.subset_h5", "domain": "py", "role": "function", "priority": "1", "uri": "reference/subset_h5.html#lfpack.subset_h5", "dispname": "-"}, {"name": "lfpack._core.subset_h5", "domain": "py", "role": "function", "priority": "1", "uri": "reference/subset_h5.html#lfpack.subset_h5", "dispname": "lfpack.subset_h5"}]}
|
|
@@ -40,9 +40,17 @@ contains exactly one recording the key is auto-detected; otherwise pass recordin
|
|
|
40
40
|
| Name | Description |
|
|
41
41
|
| --- | --- |
|
|
42
42
|
| [bin_channels](#lfpack.LFPackReader.bin_channels) | Number of adjacent channels summed on every read (1 = no binning). |
|
|
43
|
+
| [channels](#lfpack.LFPackReader.channels) | Per-channel info aggregated over bin groups when ``bin_channels > 1``. |
|
|
44
|
+
| [channels_full](#lfpack.LFPackReader.channels_full) | Full per-electrode channel info, independent of ``bin_channels``. |
|
|
45
|
+
| [fs](#lfpack.LFPackReader.fs) | LFP sample rate in Hz, sync-corrected when sync data is present. |
|
|
43
46
|
| [geometry](#lfpack.LFPackReader.geometry) | Probe geometry averaged over each bin group. |
|
|
44
47
|
| [geometry_full](#lfpack.LFPackReader.geometry_full) | Full per-electrode probe geometry, independent of ``bin_channels``. |
|
|
45
48
|
| [nc](#lfpack.LFPackReader.nc) | Number of output channels (raw nc // bin_channels). |
|
|
49
|
+
| [saturation](#lfpack.LFPackReader.saturation) | Insertion-level table of ADC-saturated intervals. |
|
|
50
|
+
| [saturation_mask](#lfpack.LFPackReader.saturation_mask) | Boolean saturation mask for the full recording, at this reader's sampling rate. |
|
|
51
|
+
| [saturation_summary](#lfpack.LFPackReader.saturation_summary) | Summary of saturation over the whole recording, read from dataset attrs. |
|
|
52
|
+
| [t0](#lfpack.LFPackReader.t0) | Session-clock time in seconds at LFP sample 0. NaN when no sync data. |
|
|
53
|
+
| [times](#lfpack.LFPackReader.times) | Session-clock timestamps in seconds for every LFP sample. |
|
|
46
54
|
|
|
47
55
|
## Methods
|
|
48
56
|
|
|
@@ -51,6 +59,7 @@ contains exactly one recording the key is auto-detected; otherwise pass recordin
|
|
|
51
59
|
| [read](#lfpack.LFPackReader.read) | Decompress and return a sample range. |
|
|
52
60
|
| [read_samples](#lfpack.LFPackReader.read_samples) | Read and decompress a sample range with optional spatial binning. |
|
|
53
61
|
| [recordings](#lfpack.LFPackReader.recordings) | List recording keys at the root of an H5 file written by compress_to_h5. |
|
|
62
|
+
| [saturation_times](#lfpack.LFPackReader.saturation_times) | Saturation interval table with session-clock start/stop times. |
|
|
54
63
|
| [scales](#lfpack.LFPackReader.scales) | List scale indices available for a recording. |
|
|
55
64
|
|
|
56
65
|
### read { #lfpack.LFPackReader.read }
|
|
@@ -130,6 +139,26 @@ List recording keys at the root of an H5 file written by compress_to_h5.
|
|
|
130
139
|
|--------|-------------|---------------|
|
|
131
140
|
| | list of str | |
|
|
132
141
|
|
|
142
|
+
### saturation_times { #lfpack.LFPackReader.saturation_times }
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
LFPackReader.saturation_times()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Saturation interval table with session-clock start/stop times.
|
|
149
|
+
|
|
150
|
+
Converts ``start_sample``/``stop_sample`` (raw-rate, recording-aligned) to
|
|
151
|
+
``start_time``/``stop_time`` (session-clock seconds, matching ``times``) via the
|
|
152
|
+
same sample-rate ratio and ``t0``/``fs`` used everywhere else in the reader —
|
|
153
|
+
never divide the raw samples by a raw sample rate by hand.
|
|
154
|
+
|
|
155
|
+
#### Returns {.doc-section .doc-section-returns}
|
|
156
|
+
|
|
157
|
+
| Name | Type | Description |
|
|
158
|
+
|--------|----------------------------------------------------------------------------------|---------------|
|
|
159
|
+
| | pandas.DataFrame with columns ``start_sample``, ``stop_sample``, ``start_time``, | |
|
|
160
|
+
| | ``stop_time``. | |
|
|
161
|
+
|
|
133
162
|
### scales { #lfpack.LFPackReader.scales }
|
|
134
163
|
|
|
135
164
|
```python
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# compress { #lfpack.compress }
|
|
2
|
+
|
|
3
|
+
```python
|
|
4
|
+
compress(data, epsilon=150.0, alpha=28.0, floor_k=64)
|
|
5
|
+
```
|
|
6
|
+
|
|
7
|
+
Compress an LFP snippet using adaptive SVD and wavelet-packet thresholding.
|
|
8
|
+
|
|
9
|
+
## Parameters {.doc-section .doc-section-parameters}
|
|
10
|
+
|
|
11
|
+
| Name | Type | Description | Default |
|
|
12
|
+
|---------|---------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|
|
|
13
|
+
| data | ndarray of shape (nc, ns) | LFP data matrix, float32 or float64. Rows are channels, columns are time. | _required_ |
|
|
14
|
+
| epsilon | float | SVD threshold multiplier. rank = #{k : sv[k] > epsilon × sigma_noise}. Default 150. | `150.0` |
|
|
15
|
+
| alpha | float | WP threshold multiplier per component: tau_k = alpha × sigma_noise / sv[k]. Set to 0 to skip wavelet-packet stage. Default 28. | `28.0` |
|
|
16
|
+
| floor_k | int | Survival floor on the dominant mode: the top retained row (``k == 0``) keeps at least its ``floor_k`` largest-magnitude WP coefficients. This guarantees the reconstruction is never identically zero. Without it, low-SNR chunks (``sv[0] / sigma_noise`` below ~``alpha / max\|coeff\|``, empirically ~140) have every coefficient thresholded and decompress to exact zero — silently destroying real low-amplitude LFP. High-SNR recordings are unaffected: their dominant row keeps far more than ``floor_k`` coefficients, so the floor never triggers and the output is bit-for-bit identical to ``floor_k = 0``. Set to 0 to disable. Default 64 (kills the zeroing on affected recordings while leaving the benchmark set unchanged). | `64` |
|
|
17
|
+
|
|
18
|
+
## Returns {.doc-section .doc-section-returns}
|
|
19
|
+
|
|
20
|
+
| Name | Type | Description |
|
|
21
|
+
|--------|---------------|---------------|
|
|
22
|
+
| | LFPCompressed | |
|
|
23
|
+
|
|
24
|
+
## Notes {.doc-section .doc-section-notes}
|
|
25
|
+
|
|
26
|
+
The floor never resurrects saturation-muted spans: an all-zero input gives
|
|
27
|
+
``sv = 0`` so ``U_scaled = 0`` and the output is exactly zero whatever ``floor_k``
|
|
28
|
+
is. Zero output over a muted span is correct; the floor only targets the
|
|
29
|
+
pathological all-zero from over-thresholding genuine low-amplitude LFP.
|