Create benchmark.py
Browse files- benchmark.py +66 -0
benchmark.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Benchmark SpikeCascade against an offline baseline and emit the
|
| 3 |
+
calibration contract required by the paper.
|
| 4 |
+
|
| 5 |
+
Usage:
|
| 6 |
+
python benchmark.py --data path/to/recording.npy \
|
| 7 |
+
--baseline path/to/kilosort_output.npz
|
| 8 |
+
|
| 9 |
+
The output is a single JSON object with the four calibration fields:
|
| 10 |
+
activity_fraction, stage3_fraction, latency_ms, correlation
|
| 11 |
+
plus a 'speedup_vs_full' field computed by the theorem
|
| 12 |
+
S = (1 + alpha) / (1/k + alpha), alpha = (1 - T_s/T) / (T_s/T).
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import json
|
| 17 |
+
import time
|
| 18 |
+
|
| 19 |
+
import numpy as np
|
| 20 |
+
|
| 21 |
+
from modeling_spikecascade import SpikeCascadeForSorting
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def main():
|
| 25 |
+
parser = argparse.ArgumentParser()
|
| 26 |
+
parser.add_argument("--data", required=True, help="(n_channels, n_samples) .npy")
|
| 27 |
+
parser.add_argument("--baseline", required=True, help=".npz with 'unit_ids' and 'spike_times_ms'")
|
| 28 |
+
parser.add_argument("--model", default="your-org/SpikeCascade")
|
| 29 |
+
args = parser.parse_args()
|
| 30 |
+
|
| 31 |
+
voltage = np.load(args.data).astype(np.float32)
|
| 32 |
+
baseline = np.load(args.baseline)
|
| 33 |
+
baseline_counts = {}
|
| 34 |
+
for uid in baseline["unit_ids"]:
|
| 35 |
+
baseline_counts[int(uid)] = baseline_counts.get(int(uid), 0) + 1
|
| 36 |
+
|
| 37 |
+
model = SpikeCascadeForSorting.from_pretrained(args.model)
|
| 38 |
+
|
| 39 |
+
# Warm up.
|
| 40 |
+
_ = model.infer(voltage[:, :3000])
|
| 41 |
+
|
| 42 |
+
t0 = time.perf_counter()
|
| 43 |
+
report = model.infer(voltage, baseline_spike_counts=baseline_counts)
|
| 44 |
+
wall_ms = (time.perf_counter() - t0) * 1000
|
| 45 |
+
|
| 46 |
+
# The theorem: S = (1 + alpha) / (1/k + alpha), with
|
| 47 |
+
# alpha = (1 - f) / f and f = stage3_fraction.
|
| 48 |
+
f = report.stage3_fraction
|
| 49 |
+
alpha = (1.0 - f) / f if f > 0 else float("inf")
|
| 50 |
+
k = 20.0 # conservative cost ratio (full clustering / template match)
|
| 51 |
+
speedup = (1.0 + alpha) / (1.0 / k + alpha) if f > 0 else 1.0
|
| 52 |
+
|
| 53 |
+
out = {
|
| 54 |
+
"activity_fraction": report.activity_fraction,
|
| 55 |
+
"stage3_fraction": report.stage3_fraction,
|
| 56 |
+
"latency_ms": report.latency_ms,
|
| 57 |
+
"wall_ms": wall_ms,
|
| 58 |
+
"correlation": report.correlation,
|
| 59 |
+
"speedup_vs_full": speedup,
|
| 60 |
+
"warning": report.warning,
|
| 61 |
+
}
|
| 62 |
+
print(json.dumps(out, indent=2))
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
if __name__ == "__main__":
|
| 66 |
+
main()
|