Skip to content

Changelog

Each version’s downloads are on GitHub Releases.

All notable changes, newest first. Versions follow Semantic Versioning; in 0.0.x any release may change the API. The crates, the Python package and dspk-sorters share one version number.

  • Kilosort4 / EMUsort: 34 % faster on the Kilosort4 sample (CUDA, RTX 2070, warm: 13.4 s → 8.9 s; EMUsort on 600 s of HD-EMG: 36.2 s → 33.3 s), with the same units (93 % of the good units match the earlier run at ≥ 0.9; agreement with upstream’s saved results unchanged).
    • The k-means++ seeding of the graph clustering scores its trials with one unit per point: coalesced reads, every point read once for all trials, the sums reduced per cube (before: one unit per trial and block of 16 points, reads 16 apart, each point read once per trial). Seeding 200 seeds of 1 416 points × 102 features: 76 → 11 ms; the two clustering stages 3.1 → 1.1 s and 2.3 → 0.7 s.
    • The global merges compute every pair’s similarity once and, after a merge, only the merged unit’s row and column; before, the pool’s whole matrix was recomputed for every unit and every merge and only one row used (1.6 → 0.6 s).
  • Explore shows the chain built in the Pipeline at once. Explore’s views kept the session view they opened with, so a chain built on the canvas (or picked there with Show, or by an agent) was drawn only after the app started again. The active view now shows in Explore: every view of its recording that drew it unprocessed follows (traces and heatmap together), and the focused one; the empty view the recording opened with leaves the session. A chain edited in the Pipeline becomes the active one while the active one holds no work.
  • Kilosort4’s drift correction (dspk_sorter_kilosort::drift), on by default as upstream (rigid, --nblocks 1; 0 turns it off, more corrects blocks along the shank): the universal- template spikes of every window give per-batch fingerprints (depth × log amplitude), aligned to each other and smoothed; the later passes are corrected on the device by kriging interpolation after whitening, one shift per channel crossing per window. On the Kilosort4 sample it matches upstream’s dshift (within ±0.5 µm, the same in 19 of 23 batches); it costs one more detection pass (3.4 s there), as upstream. Off for EMUsort, as its configs. The drift is printed and saved with the sorting (SortingOutput::drift).
  • Kilosort4’s other missing settings: --invert-sign, --artifact-threshold (windows holding a whitened value at or above it are zeroed; upstream compares raw units, here σ, so one value fits every recording), and for every sorter --bad-channels (left out of the data and the probe) and --keep-good-only (as SpikeInterface’s).
  • Kilosort4 learns its universal templates again on the drift-corrected windows, as upstream’s second detection: on the Kilosort4 sample, agreement of the good units with upstream’s saved results rises from a median 0.810 to 0.867.
  • Drift checked on a second recording (IBL Neuropixels, 100 s): the per-batch drift matches upstream Kilosort 4.1.7’s (40 of 50 batches identical, at most 0.5 µm apart, r = 0.90), sorted in 27 s against upstream’s 165 s on the same GPU.
  • Kilosort4’s template centres take their contacts from their own shank (a centre near the edge of closely spaced shanks mixed them; upstream fixed the same in v4.0.22). A shank with fewer contacts than nearest_chans is filled from the nearest of the others.

  • e-means (dspk_synapse::sorting::emeans): mini-batch k-means whose centres follow an exponential moving average of the batches’ counts and sums, the step annealed on a cosine, the batch size and step from fitted scaling laws, dead clusters revived by splitting (Bessa et al., arXiv:2611.09501; constants and details as the authors’ faiss reference). The whole run stays on the device: the epoch’s shuffle is a keyed permutation computed per point, the batch assignments and sums, the EMA update and the closing means are kernels; per batch only scalar arguments cross. Same result type as k-means. Against k-means (k-means++, 10 restarts) on wgpu: 6× faster at n 50k / k 6, 31× at n 200k / k 64, 191× at n 500k / k 256, inertia within 0.21 %. With few, well-separated clusters, random starts can merge two (the reference does the same), so small k should start from k-means++.

  • Kilosort4 / EMUsort: --template-clustering emeans learns the universal templates with e-means (k-means++ start) instead of k-means. e-means is the default (in the app too); --template-clustering kmeans gives Kilosort4’s k-means with 10 restarts.

  • kitchen-ui and kitchen-shell 0.0.3 come from crates.io (the same code as the v0.0.3 tag), so dspk-app-core can be published again.

  • The kitchen crates (git, tag v0.0.3) carry their version too, so cargo-deny no longer reads them as wildcard dependencies of dspk-app-core.

  • App: lumen’s greys in the plots. The dark plots’ background, grid, baselines and labels are lumen’s neutrals (#161616 like lumen’s image area, #222222, #2a2a2a, muted grey) instead of a blue-black; the heatmap starts from the same background; the light plots’ greys lose their blue tint. Channel colours, markers and the playhead are unchanged.

  • App: lumen’s surfaces. Every pane is one flat panel (the panel colour at the root of each panel: Inspector, Channels, Timeline, Processing, the plugin panels, Epochs, the docked Pipeline, Curation’s views), so the dock’s darker fill no longer shows through; sections are a title and their rows, without a box or a lighter fill (Inspector, Processing, Pipeline, Sorting). Wells stay only under drawn data (traces, heatmaps, plots).

  • App: the compute device in the status bar (as lumen’s): the runtime chosen in Settings, else wgpu, with the adapter wgpu opens (the discrete GPU first: “GPU · Vulkan · NVIDIA GeForce RTX 2070 …”), and “working” while a job runs. Settings › Compute names it and every adapter wgpu found.

  • App: named dsp-kitchen in its menu, window title and About (was “DSP App”).

  • App: kitchen-shell’s frame, as lumen’s. The title bar (the dsp-kitchen menu, Help, the workspaces, the recording, where the app runs, the panel toggles, the theme), the workspace’s menu row, the status bar (status line, every job with its cancel button, the frame’s numbers, CPU and memory), Settings, the search and the window frame (rounded while it floats, resized from its edge) are kitchen-shell’s (kitchen v0.0.3), shared with lumen.

  • App: every action is a command of one registry (commands.rs and the Pipeline’s own): id, title, icon, what it does, group, key, when it can run and why not, and the JSON Schema of its arguments. The menus, the keys, the search (Ctrl+K; F3 for the operations) and, next, an agent’s tools come from that one list; an entry that cannot run is greyed with the reason. Recent files and recipes are menu entries made when the menu opens.

  • App: the Pipeline’s menus (File, Edit, a menu per kind of operation, View) sit in the frame’s menu row; docked in Explore, its own bar draws the same menus.

  • App: Settings has the frame’s Appearance (theme, accent, text size) first, then Display (a theme that follows the system, dark plots), Compute, Start, Servers, Results and About. Keyboard & mouse (F1), About and Open from server show over the frame.

  • App: a job is listed in the frame’s status bar too; its cancel button stops it.

  • The search palette of our own is gone (the frame’s search replaces it).

  • Dependencies: kitchen-ui and kitchen-shell from kitchen’s v0.0.3 tag (one revision for both: kitchen-shell uses kitchen-ui by path); cargo-deny allows that git repository.

  • App: the shared neuro-kitchen look (kitchen-ui, as lumen): plain grey neutrals, lime accent, 15 px text; the workspaces’ rails, panels, views and strips are rounded cards on the chrome, with no lines between them; the window has no outline or shadow margin around it. Widgets (dropdowns, sections, rows, empty states) and the parameter editor come from kitchen-ui; section titles are capitals, and in a narrow panel a control drops under its label instead of cutting it.

  • App: the status bar shows where the app runs (“This computer”, at its left, as VS Code’s remote indicator), and this computer’s CPU and memory at its right.

  • kitchen-ui 0.0.2 (released 2026-11-12): code, diff and text views and the chat pane available; nothing in the app changed for it.

  • Dependencies: cubecl 0.11.4, flate2 1.1.11, prost 0.14.5, rustls-pki-types 1.16.0, rfd 0.18.0, toml 1.2.1 (the newest of each).

  • App: paths show only their last two parts (…/folder/file) in messages, errors, file details, recent lists and Settings.

  • App: Settings → Appearance → Accent: lime, a suggested colour or any colour, kept readable in both themes.
  • Install what you need: pip install dspk is the core; dspk[synapse] adds electrophysiology and the neural formats, dspk[app] the desktop app, dspk[all] both. import dspk.synapse without it says what to install. Breaking: pip install dspk no longer includes Synapse.
  • dspk autotune, dspk.runtime.autotune() and File → Tune this device: tune a device ahead of time, so first runs and previews don’t stop to benchmark.
  • HIP (AMD) runtime in the Linux wheels and sorter build. Compiled in, but untested: no AMD GPU.
  • dspk.app.open(path), and a dspk command from the Python package (sorters, app, autotune).
  • dspk.io.open opens NWB, SpikeGLX and mtscomp files when Synapse is installed.
  • Sorter benchmarks run the current sorter program, and the Validation pages are re-measured. The Zarr codec comparison and 24 screens of the app are in the docs.
  • Documentation: Learn (get started, concepts, Python, a course generated from the playground), the App, Operations, Deploy, GPU lessons, Validation, Extend, a Lab notebook and the Synapse plugin’s guides and sorter pages. CLI, sorter settings and pipeline-spec references are generated from the programs.
  • Release notes on GitHub come from this file.
  • App, on Linux: Open file… (Ctrl+O) and Open folder… (Ctrl+Shift+O) are separate, because the desktop’s dialog shows either files or folders; the Add actions ask which.
  • CUDA: sorters panicked at a channel count not divisible by 16 (a buffer read back with its padding).
  • Programs could hang or crash at exit in NVIDIA’s Vulkan teardown, with all work already done. The sorter and dspk programs now skip the teardown; Python and the app limit it to 10 s.
  • App: choosing a view’s tab selects that view, so the Inspector follows it.
  • Documentation site (docs/, Starlight): entry points by purpose as header tabs (Learn, App, Operations, Reference, Plugins, More), each with its own sidebar of small chapters; Synapse as the first plugin.
  • Python API reference, generated from the package’s source and stubs (no build needed): one page per module, numpy style. Every public function and class has Parameters, Returns, Raises and Examples; the examples run as doctests on the CPU. Defaults are shown with their values; pipeline specs and channel maps in JSON and TOML tabs.
  • dspk.custom: your own Python functions over a recording, window by window (measure, transform with a halo, apply, write to Zarr; the next window is read on another thread). dspk.io.ZarrWriter writes a store block by block.
  • dspk.remote: a client of dspk serve (Session: info, files, open a recording read on the server, sort / status / wait / stop / logs, fetch; mutual TLS with the enrolled identity).
  • Python dspk.synapse: clustering (kmeans, hdbscan, gmm with BIC, density_peaks, kde_merge, subsample_peaks), matching (template_similarity, suggest_merges, matching_pursuit), spatial (localize with four methods, drift).
  • Spike locations from the streaming detector (locate; dspk detect --locate, detect_recording(locate=True)): every spike’s centre of mass over its K neighbours.
  • Pipeline ops: laplacian (surface Laplacian of an electrode grid), whiten (global ZCA fitted before the run), per-sample rectify, square, sqrt, log10; also as dspk.ops.stage stages.
  • App: Add view or container (Rerun-style: tabs, horizontal, vertical, grid; traces, heatmap, raster), keyboard-driven; one Inspector for the focused view; views bound to session views and named by their source; the Pipeline’s Channels family (remap tables, map files in TOML or JSON), remaps anywhere in a view’s chain previewed as the pipeline runs them; the full operation catalog in Explore’s Add op; whitening fitted in the background; an Analytics workspace tab (placeholder).
  • Playground: chapters 08 (detection) and 09 (sorting a Neuropixels recording).
  • The Python stubs state every constant’s value, so editors and the docs show defaults, not only names.
  • A spatial stage made for another channel count is an error with the reason (was a panic).
  • IIR autotune benchmarks at most 2^20 steps; pinned passes split time into blocks by channel count. App previews never stop to autotune.
  • App: views closed or switched to another source no longer leave source and view nodes in the Pipeline; icons used by Add view and the Channels family are bundled (a test checks every icon the sources name).
  • App: a preview read by a branch computes only what it asks for (no slack growing per level).
  • dspk-synapse: one way to call every operation (.tasks/30). Names start with their verb; functions on device buffers start with execute_; data comes first with its shape, then sample_rate, then one settings struct; spike times are samples.
    • Renamed: isi_histogram, refractory_stats, acg_refractory, ccg_refractory, composite_score, sort_composite_score, firing_range → compute_*; kmeans / hdbscan → cluster_kmeans / cluster_hdbscan; kmeans_points*, hdbscan_points*, cluster_sums, bipartite_clustering, nearest_neighbours → execute_*; detect_spikes_multichannel / _with_sigma → detect_spikes_threshold / _with_sigma; extract_snippets_multichannel / extract_snippet_batch_multichannel → extract_snippets / extract_snippet_batch; compute_neo_energy_1d → compute_neo_energy; haar_dwt_multilevel_1d → compute_haar_dwt; deduplicate_spikes_spatial_gpu → deduplicate_spikes_spatial_device.
    • Settings structs: detectors take (data, channels, samples, sample_rate, &settings) with ThresholdSpikeDetector / NeoSpikeDetector / MatchedFilterSpikeDetector (refractory in ms); MatchingPursuitMatcher and GridConvolutionLocalizer are their functions’ settings; new HdbscanOptions (replaces cluster_hdbscan_allow_single), DensityPeaksOptions (replaces cluster_density_peaks_capped), KdeMergeOptions, AmplitudeCutoffOptions (replaces compute_amplitude_cutoff_with), PsthOptions, MepOptions, DriftOptions, SnippetOptions.
    • Samples, not seconds: compute_psth, compute_instantaneous_firing_rate, estimate_rigid_drift, estimate_nonrigid_drift take spike samples and sample_rate; compute_isi_violations takes the recording’s total samples.
    • Device kernels (*_kernel) are no longer public (sorting’s stay, hidden, for the sorter crates).
  • The kernel benchmarks moved from the crates’ tests/ into benchmarks/kernels (crate dspk-benchmarks; the Kilosort ones in dspk-benchmarks-sorters, GPL-3.0); every benchmark writes a JSON record.
  • dspk-app starts on the start screen; reopening the last recording is a setting (off by default).
  • Channel maps as pairs ([remap] base, pairs = [{ from, to, name }], TOML or JSON): dspk_core::ChannelMap (compose, check), RemappedSource, dspk_io::read_channel_map. Pipeline node remap: right after read it folds into the read; between processing nodes it reorders on the device (PipelineStage::ReorderChannels); before write it may drop channels (gathered on the device, per branch). Zarr outputs keep channel names.
  • dspk_ops::core::layout::execute_gather_channels.
  • Probe files: probeinterface and Kilosort 4 probe JSON read as a channel map plus geometry (.json only); SensorLayout::remap, depth_order. CLI: sort / detect --probe-file, --channel-map; dspk probe --file … --write-map ….
  • Python: dspk.synapse — io (Probe, Sorting: Phy / .sorting.zarr / NWB units), metrics (22 unit and train metrics, correlograms, PSTH, stimulus averages, sorting comparison), detection (threshold, NEO, matched filter, dedup, detect_recording), extraction (snippets), features (morphology, Haar wavelet, conduction velocity); dspk.ops.layout (channel maps, remap, gather_channels), stage.reorder_channels / stage.remap; branched pipeline specs run from Python.
  • Branched pipeline specs compute the stages all branches start with once per window.
  • dspk-app: servers in Settings (Ctrl+,), Open from server (browse, open recordings remotely, fetch), recordings on servers as dspk:// paths sorted on the server; the Pipeline canvas shows the path of a node on hover and each node’s run status; recipes as group cards; recent entries removable.
  • dspk-stream: RemoteSource / RemoteSignal (a server’s recording read like a local one); stopped or failed jobs remove their partial output; dspk serve --runtime.
  • deploy/smoke-test.sh (the server image end to end with Podman).
  • plating (the figures’ look, a synthetic recording with known components, the benchmark JSON); the playground rebuilt as a course in chapters (01 basics … 07 channels, 10 remote), each script with its committed figure.
  • dspk-app: adding plot views in Curation no longer panics.
  • Python: branched specs (one read, several writes) were refused.
  • dspk serve refuses probe-file and channel-map as job parameters (paths a client could point outside the data root).
  • dspk-stream: only enrolled clients connect (mutual TLS). A server keeps its own certificate authority (Authority: CA key mode 0600, clients.json) and lets in only clients holding a certificate it issued and has not revoked (checked at every connection); --allow-anonymous adds read-only access without one, with a warning. dspk clients enroll &lt;name> [--control] writes a client’s identity file (its certificate and key, and the authority, mode 0600); dspk clients revoke <name>, dspk clients list; dspk receive --identity &lt;file>. Changed API: Server::accept returns the connection and its Peer (name, permission); new server_config_with_clients, client_config_with, ClientIdentity, ServerTrust::from_identity.
  • dspk-stream: a client could make a server allocate without bound (a view of any width or with repeated channels, many such requests at once, unlimited connections, 64 MiB messages). Views are now checked (checked_view: channels in range and once, at most 65,536 columns and 8 Mi values), client messages are limited to 64 KiB, each client to 8 requests at once, servers to 32 clients (Server::with_max_connections, dspk serve --max-clients), idle connections close after 30 s, and a client proves its address (QUIC Retry) before any handshake work.
  • dspk serve stops on SIGTERM (container and service managers), not only Ctrl+C.
  • Supply chain: cargo-deny (deny.toml: no known vulnerability, no yanked crate, allowed licences only, crates.io only) on every pull request, weekly, and before every release.
  • dspk-stream protocol 2 (ALPN dspk-stream/2; version 1 peers are refused): the hello is answered with ServerInfo (server, the client’s name and permission, device state, files); requests (views, ListFiles, Open) are Request → Response on streams of their own; refusals come back as StreamError::Server. A server can serve the recordings under a data root (files::DataRoot: absolute paths, .., hidden files and links out of it refused; RecordingOpener, Device, serve_session), each client opening its own; Session::recording() replaces info(), with list_files and open. dspk serve --data <dir>, dspk receive --open <path>. Identity files and keys (*.dspk-identity.pem, *.key) are never listed or served.
  • Sorts on a server. A control client configures a job (Configure: sorter, recording, output, parameters, part), starts and stops it; any client reads Status and Logs. One job at a time (Stopped ⇄ Running, Error); every request is checked (known sorter, paths under the data root, a new output never overwritten, plain parameter names, none the server sets) and every control request audited (audit.jsonl, 0600). dspk-stream::jobs (Jobs, JobRunner); dspk serve --data runs dspk-cli-sorter.
  • Fetch files and directories (a .sorting.zarr) under the data root (Session::fetch, never overwriting, names from the server checked).
  • dspk remote <addr> --identity <file>: info, files, sort … [--wait], status [--follow], logs, stop, fetch. dspk clients enroll --out - writes to standard output.
  • deploy/: dspk as a server in a Podman container (targets cpu, nvidia, mesa), hardened (non-root, read-only root, no capabilities), with a systemd Quadlet unit; tested with CUDA and Vulkan on an NVIDIA GPU.

  • dspk serve --name: the host names clients use, on the self-signed certificate.

  • dspk doctor says in one line why a runtime is not usable, and warns when the selected one has no device.

  • dspk-app on crates.io (cargo install dspk-app), with its core dspk-app-core and the electrophysiology plugin dspk-app-synapse. Four workspaces on one timeline:

    • Explore: traces and heatmaps from min/max pyramids, epochs, a timeline of sources.
    • Pipeline: an infinite canvas of the session’s graph (sources, ops by family and type, views, writes), layers, recipes (reusable groups of ops), a live device preview per op.
    • Sorting: runs the sorters through dspk-cli-sorter; units, templates (multi-select) and channels; the sorter on the left, its parameters on the right.
    • Curation: correlograms, ISI, templates, amplitudes, waveforms, features and the probe as zoomable charts with axes; merge, split (lasso), label, undo, save to .sorting.zarr.
  • The session is one JSON document (current-session.json) changed only by commands, with undo and redo. Side panels are resizable and collapsible.

  • dspk-pipeline: PipelineSpec::branches, several outputs from one read of the recording.

  • The sorter program dspk-sorters is now dspk-cli-sorter, in plugins/cli/: a plugin the command line (dspk sort) runs as a separate program (GPL-3.0, never linked). Release files dspk-cli-sorter-<target> and dspk-cli-sorter-manifest.json; environment variables DSPK_CLI_SORTER_BIN and DSPK_CLI_SORTER_RELEASE_URL (were DSPK_SORTERS_*). dspk sorters install installs it.
  • Python: dspk.ops.math.channel_mad (median absolute deviation per channel, on the device).
  • dspk-pipeline: RunReport::timings (StageTimings: read wait, device, write wait, encode, disk), in the CLI output, dspk run --json and the Python reports.
  • dspk-io: ZarrWriter, a Zarr recording written from blocks of columns in order.
  • dspk-ops: core::buffer::{fill, copy}, core::layout::transpose_strided.
  • Synapse ready for publication, one crate per upstream lineage (none of these names was on crates.io before):
    • dspk-synapse-io merged into dspk-synapse (dspk_synapse::io).
    • dspk-synapse-hub → dspk-hub (hub/): generic verified downloads with data catalogs per domain (feature synapse: Kilosort4’s wTEMP.npz); dspk_hub::pull(id). The dspk-sorters installer moved into the CLI. dspk hub lists the hub catalog.
    • dspk-synapse-ml: manifests only say how to run a model; downloads come from the hub catalog (no ModelHub; DSPK_MODEL_CATALOG overrides model manifests, DSPK_HUB_CATALOG downloads). The CLI no longer depends on it.
    • Sorters: dspk-ks4 → dspk-sorter-kilosort (features ks4, emusort; templates through dspk-hub, no ML dependency; provenance text says upstream’s code was read); dspk-ms5 → dspk-sorter-ms5 (with its own isosplit6); dspk-sc2 + dspk-tdc2 → dspk-sorter-si (features sc2, tdc2; SpikeInterface’s isosplit and waveform cutting, no MS5 dependency). isosplit is duplicated into its two owners, so dspk-synapse is MIT OR Apache-2.0 with no Apache-only part.
    • dspk (umbrella): features synapse, hub.
  • Breaking (Rust): dspk-ops has one error type, OpsError / OpsResult (was FilterError for filters and dspk_core::DspResult for stored-sample unpacking); From&lt;OpsError> for DspError keeps ? working in code returning DspResult.
  • Breaking (Rust): every operation on device buffers is execute_<op>, data first: execute_filter(client, input, output, channels, samples, sample_rate, spec) (was the filter first), resample_poly[_with] → execute_resample_poly[_with], decimate → execute_decimate. Parameter names of execute_scaling / execute_clamp: len, scale, offset / min, max.
  • Vectorized kernels run on every row length: rows the vector width does not divide (385 channels, 66 234-sample zero-phase windows) used to fall back to scalar code; FIR, running median, resample_poly, CAR and CMR now store their last vector lane by lane, and the IIR’s time-major lanes are padded to the width. Still bit-identical to the scalar kernels. Autotune keys renamed, so cached choices are measured again once.
  • dspk run / dspk.pipeline.run: a writer thread takes downloaded windows through a bounded queue, so writing overlaps reading and the device; Zarr output is written chunk by chunk as the run goes instead of converted from a temporary raw file at the end.
  • Parallel, compressed writing (default on): Zarr chunks are compressed and stored on several threads, raw output frames built on several threads. New write node settings compression ({"codec": "zstd", "level": 3} default, blosc_lz4, none), chunk_samples (Zarr chunk length, default 5000, independent of the processing windows) and parallel (default true); the same in dspk_io::ZarrOptions and Python dspk.io.write_zarr(..., compression="zstd", level=None, parallel=True) (its chunk_samples default is now 5000, ZARR_CHUNK_SAMPLES). Output is float32 whatever the input’s stored type. Breaking (Rust): dspk_io::write_zarr and ZarrWriter::create take &ZarrOptions instead of a chunk length.
  • Dependencies: pyo3 / numpy 0.30, gpui-kit 0.9 (dspk-app), tonic 0.14.9, toml 1.2, maturin 1.16.
  • MountainSort 5 on 2 s × 384 channels (CUDA): 117 s → 51 s.
    • The per-channel classifiers are fitted on several threads (each with its own device stream; same arithmetic per channel; byte-identical output) and their PCA is exact through the snippets’ Gram matrix (TopComponentsOptions::dual_max_rows, MS5 classifier_dual_max_rows = 1024): classifiers 84 s → 14 s. Exact like upstream’s, so results can differ from 0.0.4’s randomized fit (one spike of 115 on the test excerpt).
    • The isosplit6 subdivision runs the two halves of each split on two threads (same labels).
    • Mountainsort5Config::parallel (default true) switches both off.
  • dspk-ops: TopComponentsOptions::dual_max_rows (default 0, off): exact PCA of few rows through their Gram matrix.
  • CLI (dspk sort, dspk-sorters): a recording with more channels than its probe has sites (SpikeGLX’s sync channel) is read on the probe’s channels, with a note, instead of refused.
  • CI runs only the checks a change can affect; documentation alone runs none.
  • CITATION.cff, issue forms, pull request template, CONTRIBUTING.md, SECURITY.md, this changelog.
  • dspk-sorters: every spike sorter (Kilosort4, EMUsort, MountainSort 5, SpyKING CIRCUS 2, Tridesclous 2) in one program, GPL-3.0 as a whole, attached to each GitHub release. dspk sort … runs it as a separate process; dspk sorters install downloads and verifies it (SHA-256 manifest); dspk-sorters licenses prints every licence.
  • dspk-pipeline is published on crates.io.
  • dspk-ops: math::execute_channel_mad (NumPy median and median absolute deviation per channel, on the device); reduce::row_kth (percentiles) and row_abs_dev_kth.
  • benchmarks/signal-operations/scipy_timing.py: time and agreement against scipy / NumPy.
  • An example pipeline spec, playground/base/pipelines/neuropixels_preprocess.json.
  • Vectorized kernels (Vector<F, N>), chosen per device by the autotuner and bit-identical to the scalar ones: FIR, resample_poly, running median, common median and common average reference, IIR pass. On CubeCL’s CPU runtime 2–10× faster (e.g. FIR 175 → 33 ms, common median reference 1561 → 239 ms on 384 × 60 000); several GPU kernels faster too.
  • Common median reference by bitonic sort on GPUs (autotuned against rank counting).
  • The dspk command line no longer links any sorter: it is MIT OR Apache-2.0.
  • Synapse split into one crate per sorter, each with its licence and notice: dspk-ks4 (GPL-3.0), dspk-ms5 (Apache-2.0), dspk-sc2, dspk-tdc2; shared parts in dspk-synapse.
  • SpyKING CIRCUS 2 and Tridesclous 2 compute their noise levels on the device.
  • A process could abort at exit after its work (“thread local panicked on drop”, upstream tracel-ai/cubecl#1981): only wgpu devices are shut down at exit now.
  • The Python sdist carries its licence files (PyPI rejected the 0.0.3 sdist).
  • Release workflow: a re-run finishes a partial crates.io publish; crates new to crates.io are skipped with a warning; PyPI receives only wheels and the sdist.

Pipeline engine v2 and dspk run; the full set of Python operations; recordings uploaded in their stored integer type and converted on the device; a faster CPU runtime (contiguous work per thread).

Python bindings (pip install dspk), a fix to the dspk-ops CPU runtime, CI and tag-driven releases with trusted publishing.

First release of the crates on crates.io.