/ proof-of-concept / audio-spike-sorting
Sign in
proof-of-concept / audio-spike-sorting
audio-spike-sorting / src / bench / execute.ts
81 lines · 3.1 KBBlameHistoryRaw
1/**
2 * Runs one algorithm on one dataset through numbl. The algorithm is a
3 * MATLAB-syntax file defining
4 *
5 * function [times, labels] = anyname(x, fs, training)
6 *
7 * where x is the recording (column vector), fs the sampling rate, and
8 * training a struct with fields:
9 * snips {1 x nTypes} cell, each [snipLen x nSnips] (columns are snippets)
10 * names {1 x nTypes} cell of type names
11 * pre samples before the event onset within each snippet
12 * It must return event times in seconds and 1-based type labels. This
13 * module is pure of the DOM so the node test and the web worker share it.
14 */
15import { executeCode, RTV } from 'numbl';
16import type { RuntimeValue } from 'numbl';
17import type { Detection } from './types.ts';
18import type { TrainingArrays } from './training.ts';
19import { extractFunctionName } from './source.ts';
21export interface ExecuteOutcome {
22 detections: Detection[];
23 logs: string[];
24 elapsedMs: number;
27export function runAlgorithm(
28 source: string,
29 x: Float32Array | Float64Array,
30 fs: number,
31 training: TrainingArrays,
32): ExecuteOutcome {
33 const fnName = extractFunctionName(source);
34 if (!fnName) throw new Error('algorithm source must define a function [times, labels] = name(x, fs, training)');
36 const xd = x instanceof Float64Array ? x : Float64Array.from(x);
37 const xv = RTV.tensor(xd, [xd.length, 1]);
38 const trainingStruct = RTV.struct({
39 snips: RTV.cell(
40 training.snips.map((s) => RTV.tensor(s.data, [s.snipLen, Math.max(s.count, 0)])),
41 [1, training.snips.length],
42 ),
43 names: RTV.cell(training.names.map((n) => RTV.char(n)), [1, training.names.length]),
44 pre: RTV.num(training.pre),
45 });
47 const logs: string[] = [];
48 const t0 = (typeof performance !== 'undefined' ? performance : Date).now();
49 const result = executeCode(
50 `[times__, labels__] = ${fnName}(x, fs, training);\ntimes__ = times__(:); labels__ = labels__(:);`,
51 {
52 initialVariableValues: { x: xv, fs: RTV.num(fs), training: trainingStruct },
53 optimization: '1',
54 displayResults: false,
55 onOutput: (text: string) => logs.push(text),
56 },
57 [{ name: `${fnName}.m`, source }],
58 'main.m',
59 );
60 const elapsedMs = (typeof performance !== 'undefined' ? performance : Date).now() - t0;
62 const times = toVector(result.variableValues.times__);
63 const labels = toVector(result.variableValues.labels__);
64 if (times.length !== labels.length) {
65 throw new Error(`times (${times.length}) and labels (${labels.length}) differ in length`);
66 }
67 const detections: Detection[] = [];
68 for (let i = 0; i < times.length; i++) {
69 detections.push({ t: times[i], typeIndex: Math.round(labels[i]) - 1 });
70 }
71 detections.sort((a, b) => a.t - b.t);
72 return { detections, logs, elapsedMs };
75function toVector(v: RuntimeValue | undefined): number[] {
76 if (v === undefined || v === null) throw new Error('algorithm did not return times/labels');
77 if (typeof v === 'number') return [v];
78 const t = v as { data?: Float64Array };
79 if (t.data instanceof Float64Array) return Array.from(t.data);
80 throw new Error('times/labels must be numeric vectors');
↑↓ move↵openescclose