Download streaming.py from zeechimp/SpikeCascade: direct link, hf CLI and curl.
- Browser
- Download file 2.44 kB
-
https://huggingface.co/zeechimp/SpikeCascade/resolve/main/streaming.py
- Command line
-
hf download hf://zeechimp/SpikeCascade/streaming.py
-
curl -L -o streaming.py https://huggingface.co/zeechimp/SpikeCascade/resolve/main/streaming.py
2.44 kB
| """ | |
| Online wrapper around SpikeCascadeForSorting. Consumes 50 ms chunks | |
| from a SpikeGLX-like stream and returns per-chunk CascadeReports. | |
| This is the class a closed-loop experiment would use: the loop runs | |
| at ~1 kHz (one chunk per millisecond of wall clock), and the latency | |
| of each `process_chunk` call is the closed-loop latency. | |
| """ | |
| import time | |
| from collections import deque | |
| from typing import Deque, Optional | |
| import numpy as np | |
| from modeling_spikecascade import SpikeCascadeForSorting, CascadeReport | |
| class StreamingSorter: | |
| def __init__( | |
| self, | |
| model: SpikeCascadeForSorting, | |
| chunk_ms: float = 50.0, | |
| overlap_ms: float = 2.0, | |
| ): | |
| self.model = model | |
| self.sample_rate = model.config.sample_rate | |
| self.chunk_samples = int(chunk_ms / 1000 * self.sample_rate) | |
| self.overlap_samples = int(overlap_ms / 1000 * self.sample_rate) | |
| self.buffer: Deque[np.ndarray] = deque() | |
| self.reports: list[CascadeReport] = [] | |
| def push(self, chunk: np.ndarray): | |
| """chunk: (n_channels, n_samples).""" | |
| assert chunk.shape[1] == self.chunk_samples | |
| self.buffer.append(chunk) | |
| def step(self) -> Optional[CascadeReport]: | |
| """ | |
| Pop one chunk, run the cascade, and return the report. | |
| Returns None if the buffer is empty. | |
| """ | |
| if not self.buffer: | |
| return None | |
| chunk = self.buffer.popleft() | |
| report = self.model.infer(chunk, sample_rate=self.sample_rate) | |
| self.reports.append(report) | |
| return report | |
| def run(self, n_steps: int) -> list[CascadeReport]: | |
| out = [] | |
| for _ in range(n_steps): | |
| r = self.step() | |
| if r is None: | |
| break | |
| out.append(r) | |
| return out | |
| def summary(self) -> dict: | |
| if not self.reports: | |
| return {} | |
| return { | |
| "n_chunks": len(self.reports), | |
| "mean_activity_fraction": float(np.mean([r.activity_fraction for r in self.reports])), | |
| "mean_stage3_fraction": float(np.mean([r.stage3_fraction for r in self.reports])), | |
| "mean_latency_ms": float(np.mean([r.latency_ms for r in self.reports])), | |
| "mean_correlation": float( | |
| np.nanmean([r.correlation for r in self.reports]) | |
| ), | |
| "zero_spike_chunks": sum(1 for r in self.reports if r.activity_fraction == 0.0), | |
| } |