radiens-qc
radiens-qc turns a Radiens recording into an answerable question: is this data good enough to use?
It computes standardized signal quality metrics, grades electrode health, records where the detector fired, and exports self-contained report packages for instant visualization.
Installation
or with uv:
Requires Python 3.12+ and access to a Radiens server to compute reports. Saved reports can be reloaded, analyzed, and plotted completely offline.
Quick start
Compute & save a report
from pathlib import Path
from radiens_core import VidereClient
from radiens_qc import compute_report, save_report
vc = VidereClient()
report = compute_report(vc, Path("/data/rec01.xdat"), source="exp/day3")
print(report.median("snr")) # 6.2
print(report.integrity_counts()) # {"ok": 30, "noisy": 1, "dead": 1}
print(report.event_counts()) # {"present": 24, "absent": 8}
save_report(report, Path("reports")) # -> reports/rec01/
Note:
compute_reportaccepts a localPathor an existingDatasetMetadataobject.
Generate plots
FIGURES names every figure the package draws. To render them all, say so once
-- only what the selected figures need is fetched:
from pathlib import Path
from radiens_qc import FIGURES, fetch_figure_sources, render, save_figures
sources = fetch_figure_sources(
vc, dataset, FIGURES, report=report, crossings_spikes_id=crossings_id, units_spikes_id=units_id
)
save_figures(render(FIGURES, sources), Path("figures")) # figures/<level>/<name>.png
The events and units levels read spike sorts you run, so you decide which sort and whether an earlier one will do. Crossings need a detect-only scrape that detects every site independently, run after the report set the threshold; units need a clustered sort:
from radiens_core.models import SpikeSorterLaunchParams, SpikeSorterSubCommand
from radiens_qc import PER_CHANNEL_NEIGHBORS
crossings_id = vc.spike_sort(
SpikeSorterLaunchParams(
dsource_id=dataset.id,
sub_cmd=SpikeSorterSubCommand.DETECT_ONLY,
max_num_site_neighbors=PER_CHANNEL_NEIGHBORS,
)
)
units_id = vc.spike_sort(SpikeSorterLaunchParams(dsource_id=dataset.id, neighbor_radius_um=150.0))
Each reader checks the fileset it is given and raises if it cannot support the figure: crossings detected with neighbors or at another threshold, or units from a detect-only scrape. Leave an ID out and that level is skipped.
select narrows that by level or by name, and render returns
{spec: Figure} if you would rather place the figures yourself:
from radiens_qc import FigureSources, select
for spec, fig in render(select(levels=["channel"]), FigureSources(report=report)).items():
print(spec.name, spec.caption)
A report read off disk supports the channel and recording levels; the
raw-signal, events and units levels describe the recording rather than the
measurement, so they need a live dataset. render skips a figure whose source
it was not given, which is what makes a saved report directory a valid thing to
draw from.
Each figure is also a plain function returning one matplotlib Figure that it
owns entirely, so you choose the size and where it goes:
from radiens_qc import plot_channel_profiles, plot_site_maps, plot_stability
plot_site_maps(report) # SNR, noise, event rate, amplitude on the probe
plot_channel_profiles(report) # the same four, one bar row per site
plot_stability(report) # each metric over time against its own start
Every one takes the object it draws: plot_raw_traces and plot_raw_spectra
take what fetch_raw_signal returns, plot_event_raster what fetch_events
returns, and plot_unit_footprints, plot_unit_yield, plot_unit_raster and
plot_unit_inventory what fetch_units returns.
Unit amplitude is measured from spike waveforms, which are the largest payload here and so are fetched only when you ask for them:
from radiens_qc import fetch_unit_waveforms, fetch_units, plot_unit_yield
units = fetch_units(vc, dataset, units_id)
units = units.with_waveforms(fetch_unit_waveforms(vc, dataset, units))
plot_unit_yield(units)
Given units without them, the figures that report amplitude say so on that panel
instead of drawing it; the rest are unaffected. fetch_figure_sources handles
this for you, fetching waveforms when a selected figure reports amplitude.
Saved report structure
save_report(report, out_dir) creates <out_dir>/<base_name>/. It writes data
only -- figures are drawn from a report, not stored beside one:
| File | Description |
|---|---|
meta.json |
Provenance, thresholds, channel counts, and whole-recording readings |
channels.csv |
Channel geometry, metric values, integrity tier, and event state |
matrices.npz |
Full per-metric matrices, enough to redraw every figure without a server |
License
See LICENSE.