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.
Files changed (67) hide show
  1. {lfpack-0.2.0 → lfpack-0.3.0}/.gitignore +1 -0
  2. lfpack-0.3.0/CHANGELOG.md +38 -0
  3. lfpack-0.3.0/CLAUDE.md +81 -0
  4. {lfpack-0.2.0 → lfpack-0.3.0}/PKG-INFO +16 -4
  5. {lfpack-0.2.0 → lfpack-0.3.0}/README.md +15 -3
  6. {lfpack-0.2.0 → lfpack-0.3.0}/docs/_quarto.yml +6 -1
  7. {lfpack-0.2.0 → lfpack-0.3.0}/docs/explanation/pipeline.qmd +3 -1
  8. lfpack-0.3.0/docs/figures/bwm_saturation_detection.png +0 -0
  9. lfpack-0.3.0/docs/figures/viewephys_recording_selector.jpg +0 -0
  10. lfpack-0.3.0/docs/how-to/bwm-dataset.qmd +253 -0
  11. lfpack-0.3.0/docs/objects.json +1 -0
  12. {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/LFPackReader.qmd +29 -0
  13. {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/_sidebar.yml +4 -0
  14. lfpack-0.3.0/docs/reference/compress.qmd +29 -0
  15. {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/compress_bin_to_h5.qmd +25 -19
  16. lfpack-0.3.0/docs/reference/compress_to_h5.qmd +76 -0
  17. {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/index.qmd +10 -1
  18. lfpack-0.3.0/docs/reference/merge_h5.qmd +31 -0
  19. {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/run_cadzow_checkpoint.qmd +2 -0
  20. lfpack-0.3.0/docs/reference/subset_h5.qmd +33 -0
  21. {lfpack-0.2.0 → lfpack-0.3.0}/pyproject.toml +1 -1
  22. {lfpack-0.2.0 → lfpack-0.3.0}/src/lfpack/__init__.py +1 -0
  23. {lfpack-0.2.0 → lfpack-0.3.0}/src/lfpack/_core.py +347 -27
  24. {lfpack-0.2.0 → lfpack-0.3.0}/tests/test_lfpack.py +269 -3
  25. lfpack-0.2.0/CHANGELOG.md +0 -17
  26. lfpack-0.2.0/CLAUDE.md +0 -77
  27. lfpack-0.2.0/docs/how-to/bwm-dataset.qmd +0 -157
  28. lfpack-0.2.0/docs/objects.json +0 -1
  29. lfpack-0.2.0/docs/reference/compress.qmd +0 -21
  30. lfpack-0.2.0/docs/reference/compress_to_h5.qmd +0 -56
  31. {lfpack-0.2.0 → lfpack-0.3.0}/.githooks/pre-commit +0 -0
  32. {lfpack-0.2.0 → lfpack-0.3.0}/.github/workflows/docs.yml +0 -0
  33. {lfpack-0.2.0 → lfpack-0.3.0}/.github/workflows/publish_to_pypi.yaml +0 -0
  34. {lfpack-0.2.0 → lfpack-0.3.0}/.github/workflows/tests.yml +0 -0
  35. {lfpack-0.2.0 → lfpack-0.3.0}/CONTRIBUTING.md +0 -0
  36. {lfpack-0.2.0 → lfpack-0.3.0}/docs/.gitignore +0 -0
  37. {lfpack-0.2.0 → lfpack-0.3.0}/docs/README.txt +0 -0
  38. {lfpack-0.2.0 → lfpack-0.3.0}/docs/explanation/gallery.qmd +0 -0
  39. {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/density_lfp_dab512bd.png +0 -0
  40. {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/favicon.png +0 -0
  41. {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
  42. {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
  43. {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
  44. {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
  45. {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
  46. {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
  47. {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
  48. {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
  49. {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
  50. {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
  51. {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
  52. {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/gallery/2026-06-15_lfp_compression_aggregate.png +0 -0
  53. {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/lfp_binned_comparison.jpg +0 -0
  54. {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/logo.png +0 -0
  55. {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/viewephys_screenshot.jpg +0 -0
  56. {lfpack-0.2.0 → lfpack-0.3.0}/docs/figures/viewephys_screenshot.png +0 -0
  57. {lfpack-0.2.0 → lfpack-0.3.0}/docs/how-to/binned-reads.qmd +0 -0
  58. {lfpack-0.2.0 → lfpack-0.3.0}/docs/how-to/multi-recording.qmd +0 -0
  59. {lfpack-0.2.0 → lfpack-0.3.0}/docs/index.qmd +0 -0
  60. {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/LFPCompressed.qmd +0 -0
  61. {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/compress_pipeline.qmd +0 -0
  62. {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/decompress.qmd +0 -0
  63. {lfpack-0.2.0 → lfpack-0.3.0}/docs/reference/hdf5-layout.qmd +0 -0
  64. {lfpack-0.2.0 → lfpack-0.3.0}/docs/references.bib +0 -0
  65. {lfpack-0.2.0 → lfpack-0.3.0}/docs/tutorials/first-compression.qmd +0 -0
  66. {lfpack-0.2.0 → lfpack-0.3.0}/tests/__init__.py +0 -0
  67. {lfpack-0.2.0 → lfpack-0.3.0}/uv.lock +0 -0
@@ -1,4 +1,5 @@
1
1
  TODO.md
2
+ .DS_Store
2
3
 
3
4
  # Byte-compiled / optimized / DLL files
4
5
  __pycache__/
@@ -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.2.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
- **Parameters:** `epsilon` — SVD noise threshold multiplier (default `150`); `alpha` — WP threshold multiplier (default `28`)
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
 
@@ -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
+ ![Recording selector (top right) and raw/CSD checkboxes after opening a multi-recording lfpack file — the dataset info panel confirms the native "Neuropixels LFP (lfpack)" backend](../figures/viewephys_recording_selector.jpg){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
+ ![viewephys opened directly on a BWM lfpack file — brain regions colour the depth axis automatically and the searchable selector switches between the file's 699 recordings](../figures/viewephys_screenshot.jpg){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
+ ![Saturation detection across the 699-recording release: per-recording saturated-fraction distribution (left) and the 15 most-affected recordings (right).](../figures/bwm_saturation_detection.png){.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
+ ![Full-resolution (384 ch) vs binned ×4 (96 ch) LFP density for the same 4-second window](../figures/lfp_binned_comparison.jpg){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
@@ -16,5 +16,9 @@ website:
16
16
  - contents:
17
17
  - reference/LFPackReader.qmd
18
18
  section: Reader
19
+ - contents:
20
+ - reference/merge_h5.qmd
21
+ - reference/subset_h5.qmd
22
+ section: Multi-recording archives
19
23
  id: reference
20
24
  - id: dummy-sidebar
@@ -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.