1use dspk_ops::math::peak_to_peak;
2use dspk_ops::math::RunningMoments;
3use crate::io::templates::{DenseTemplates, TemplateAxisOrder};
4use serde::{Deserialize, Serialize};
5use super::snippets::WaveformSnippet;
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
9pub enum UnitQualityLabel {
10 #[serde(alias = "Good", alias = "good", alias = "singleunit")]
12 SingleUnit,
13 #[serde(alias = "Mua", alias = "mua", alias = "multiunit")]
15 MultiUnit,
16 #[serde(alias = "noise")]
18 Noise,
19 #[default]
21 #[serde(alias = "unsorted")]
22 Unsorted,
23}
24
25#[allow(non_upper_case_globals)]
26impl UnitQualityLabel {
27 pub const Good: Self = Self::SingleUnit;
29 pub const Mua: Self = Self::MultiUnit;
31
32 pub fn as_str(self) -> &'static str {
33 match self {
34 Self::SingleUnit => "good",
35 Self::MultiUnit => "mua",
36 Self::Noise => "noise",
37 Self::Unsorted => "unsorted",
38 }
39 }
40
41 pub fn parse(s: &str) -> Self {
42 match s.trim().to_ascii_lowercase().as_str() {
43 "good" | "singleunit" | "sua" => Self::SingleUnit,
44 "mua" | "multiunit" => Self::MultiUnit,
45 "noise" => Self::Noise,
46 _ => Self::Unsorted,
47 }
48 }
49}
50
51#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
57pub struct WaveformTemplate {
58 pub channel_ids: Vec<usize>,
60 pub num_channels: usize,
61 pub num_samples: usize,
62 pub count: usize,
64 pub trough_index: usize,
66 pub mean: Vec<f32>,
67 pub std: Vec<f32>,
68 pub se: Vec<f32>,
70}
71
72impl WaveformTemplate {
73 pub fn new(channel_ids: Vec<usize>, num_samples: usize, mean: Vec<f32>, std: Vec<f32>) -> Self {
75 Self::with_count(channel_ids, num_samples, 1, mean, std)
76 }
77
78 pub fn with_count(
80 channel_ids: Vec<usize>,
81 num_samples: usize,
82 count: usize,
83 mean: Vec<f32>,
84 std: Vec<f32>,
85 ) -> Self {
86 let num_channels = channel_ids.len();
87 assert_eq!(mean.len(), num_channels * num_samples, "mean shape");
88 assert_eq!(std.len(), num_channels * num_samples, "std shape");
89 let trough_index = mean
90 .iter()
91 .enumerate()
92 .min_by(|a, b| a.1.total_cmp(b.1))
93 .map_or(0, |(i, _)| i % num_samples.max(1));
94 let denom = (count.max(1) as f32).sqrt();
95 let se = std.iter().map(|&s| s / denom).collect();
96 Self {
97 channel_ids,
98 num_channels,
99 num_samples,
100 count: count.max(1),
101 trough_index,
102 mean,
103 std,
104 se,
105 }
106 }
107
108 pub fn row(&self, r: usize) -> &[f32] {
110 &self.mean[r * self.num_samples..(r + 1) * self.num_samples]
111 }
112
113 pub fn std_row(&self, r: usize) -> &[f32] {
115 &self.std[r * self.num_samples..(r + 1) * self.num_samples]
116 }
117
118 pub fn se_row(&self, r: usize) -> &[f32] {
120 &self.se[r * self.num_samples..(r + 1) * self.num_samples]
121 }
122
123 pub fn channel_row(&self, channel: usize) -> Option<&[f32]> {
125 self.channel_ids.iter().position(|&c| c == channel).map(|r| self.row(r))
126 }
127
128 pub fn best_channel(&self) -> Option<usize> {
130 (0..self.num_channels)
131 .max_by(|&a, &b| peak_to_peak(self.row(a)).total_cmp(&peak_to_peak(self.row(b))))
132 .and_then(|r| self.channel_ids.get(r).copied())
133 }
134
135 pub fn amplitude(&self) -> f32 {
137 (0..self.num_channels).map(|r| peak_to_peak(self.row(r))).fold(0.0, f32::max)
138 }
139}
140
141pub fn pack_templates<'a>(
146 units: impl IntoIterator<Item = (usize, Option<&'a WaveformTemplate>)>,
147 num_channels: usize,
148 order: TemplateAxisOrder,
149) -> Option<([usize; 3], Vec<f32>, Vec<f32>, Vec<f32>)> {
150 let list: Vec<(usize, Option<&WaveformTemplate>)> = units.into_iter().collect();
151 let count = list.iter().map(|&(slot, _)| slot + 1).max().unwrap_or(0);
152 let samples = list.iter().filter_map(|(_, t)| t.map(|w| w.num_samples)).max().unwrap_or(0);
153 let channels = num_channels.max(
154 list.iter()
155 .filter_map(|(_, t)| *t)
156 .flat_map(|t| t.channel_ids.iter().copied())
157 .max()
158 .map_or(0, |c| c + 1),
159 );
160 if count == 0 || samples == 0 || channels == 0 {
161 return None;
162 }
163 let total = count * samples * channels;
164 let mut mean = vec![0.0f32; total];
165 let mut std = vec![0.0f32; total];
166 let mut se = vec![0.0f32; total];
167
168 for (slot, tpl) in list {
169 let Some(t) = tpl else { continue };
170 for (r, &ch) in t.channel_ids.iter().enumerate() {
171 if ch < channels {
172 let n_s = t.num_samples.min(samples);
173 let m_row = t.row(r);
174 let sd_row = t.std_row(r);
175 let se_row = t.se_row(r);
176 for s in 0..n_s {
177 let idx = match order {
178 TemplateAxisOrder::SamplesChannels => (slot * samples + s) * channels + ch,
179 TemplateAxisOrder::ChannelsSamples => (slot * channels + ch) * samples + s,
180 };
181 mean[idx] = m_row[s];
182 std[idx] = sd_row[s];
183 se[idx] = se_row[s];
184 }
185 }
186 }
187 }
188 let shape = match order {
189 TemplateAxisOrder::SamplesChannels => [count, samples, channels],
190 TemplateAxisOrder::ChannelsSamples => [count, channels, samples],
191 };
192 Some((shape, mean, std, se))
193}
194
195pub fn unpack_template(
197 slot: usize,
198 shape: [usize; 3],
199 order: TemplateAxisOrder,
200 mean_buf: &[f32],
201 std_buf: Option<&[f32]>,
202 se_buf: Option<&[f32]>,
203 spike_count: usize,
204) -> Option<WaveformTemplate> {
205 let (count, channels, samples) = match order {
206 TemplateAxisOrder::SamplesChannels => (shape[0], shape[2], shape[1]),
207 TemplateAxisOrder::ChannelsSamples => (shape[0], shape[1], shape[2]),
208 };
209 if slot >= count || channels == 0 || samples == 0 {
210 return None;
211 }
212 let at = |buf: &[f32], c: usize, s: usize| match order {
213 TemplateAxisOrder::SamplesChannels => buf[(slot * samples + s) * channels + c],
214 TemplateAxisOrder::ChannelsSamples => buf[(slot * channels + c) * samples + s],
215 };
216 let n = channels * samples;
217 let mut mean = Vec::with_capacity(n);
218 for c in 0..channels {
219 for s in 0..samples {
220 mean.push(at(mean_buf, c, s));
221 }
222 }
223 let std = match std_buf {
224 Some(sd) if sd.len() == mean_buf.len() => {
225 let mut v = Vec::with_capacity(n);
226 for c in 0..channels {
227 for s in 0..samples {
228 v.push(at(sd, c, s));
229 }
230 }
231 v
232 }
233 _ => vec![1.0; n],
234 };
235 let mut w = WaveformTemplate::with_count((0..channels).collect(), samples, spike_count.max(1), mean, std);
236 if let Some(se) = se_buf.filter(|s| s.len() == mean_buf.len()) {
237 let mut v = Vec::with_capacity(n);
238 for c in 0..channels {
239 for s in 0..samples {
240 v.push(at(se, c, s));
241 }
242 }
243 w.se = v;
244 }
245 Some(w)
246}
247
248pub fn dense_waveform(dense: &DenseTemplates, t: usize) -> WaveformTemplate {
250 let channels: Vec<usize> = (0..dense.channels).filter(|&c| dense.trace(t, c).iter().any(|v| *v != 0.0)).collect();
251 let mean: Vec<f32> = channels.iter().flat_map(|&c| dense.trace(t, c)).collect();
252 let std = vec![0.0; mean.len()];
253 WaveformTemplate::new(channels, dense.samples, mean, std)
254}
255
256pub const TEMPLATE_STD_DDOF: u64 = 0;
259
260pub fn compute_mean_template(snippets: &[WaveformSnippet]) -> Option<WaveformTemplate> {
264 let first = snippets.first()?;
265 let n_s = first.num_samples;
266 let total_elements = first.num_channels() * n_s;
267 let used: Vec<&WaveformSnippet> = snippets
268 .iter()
269 .filter(|s| s.waveform.len() == total_elements && s.channel_ids == first.channel_ids)
270 .collect();
271 if used.is_empty() {
272 return None;
273 }
274 let mut moments = RunningMoments::new(total_elements);
275 used.iter().for_each(|s| moments.push(&s.waveform));
276 let mean = moments.mean().iter().map(|&m| m as f32).collect();
277 let std = moments.std(TEMPLATE_STD_DDOF).into_iter().map(|s| s as f32).collect();
278 Some(WaveformTemplate::with_count(first.channel_ids.clone(), n_s, used.len(), mean, std))
279}
280
281pub struct UnitTemplateAccumulator {
287 client: cubecl::prelude::Client,
288 n_units: usize,
289 channels: usize,
290 n_before: usize,
291 width: usize,
292 counts: Vec<u32>,
293 mean: cubecl::server::Handle,
294 m2: cubecl::server::Handle,
295}
296
297impl UnitTemplateAccumulator {
298 pub fn new(client: &cubecl::prelude::Client, n_units: usize, channels: usize, n_before: usize, width: usize) -> Self {
301 let len = n_units * channels * width;
302 Self {
303 client: client.clone(),
304 n_units,
305 channels,
306 n_before,
307 width,
308 counts: vec![0; n_units],
309 mean: dspk_ops::core::buffer::zeros::<f32>(client, len),
310 m2: dspk_ops::core::buffer::zeros::<f32>(client, len),
311 }
312 }
313
314 pub fn add(&mut self, data: &cubecl::server::Handle, samples: usize, peak_samples: &[u32], labels: &[i32]) {
323 use cubecl::prelude::*;
324 use dspk_ops::core::buffer;
325
326 assert_eq!(peak_samples.len(), labels.len(), "one label per peak");
327 let units = self.n_units;
328 let mut offsets = vec![0u32; units + 1];
330 for &l in labels {
331 if l >= 0 && (l as usize) < units {
332 offsets[l as usize + 1] += 1;
333 }
334 }
335 for u in 0..units {
336 offsets[u + 1] += offsets[u];
337 }
338 if offsets[units] == 0 || self.channels == 0 || self.width == 0 {
339 return;
340 }
341 let mut cursor = offsets.clone();
342 let mut order = vec![0u32; offsets[units] as usize];
343 for (i, &l) in labels.iter().enumerate() {
344 if l >= 0 && (l as usize) < units {
345 order[cursor[l as usize] as usize] = i as u32;
346 cursor[l as usize] += 1;
347 }
348 }
349 let client = &self.client;
350 let total = units * self.channels * self.width;
351 let geom = dspk_core::compute::LaunchGeometry::elementwise(client, total);
352 unsafe {
354 super::kernels::accumulate_unit_templates_kernel::launch::<f32>(
355 client,
356 geom.cube_count,
357 geom.cube_dim,
358 BufferArg::from_raw_parts(data.clone(), self.channels * samples),
359 BufferArg::from_raw_parts(buffer::upload(client, peak_samples), peak_samples.len()),
360 BufferArg::from_raw_parts(buffer::upload(client, &order), order.len()),
361 BufferArg::from_raw_parts(buffer::upload(client, &offsets), offsets.len()),
362 BufferArg::from_raw_parts(buffer::upload(client, &self.counts), units),
363 BufferArg::from_raw_parts(self.mean.clone(), total),
364 BufferArg::from_raw_parts(self.m2.clone(), total),
365 units as u32,
366 self.channels as u32,
367 samples as u32,
368 self.n_before as u32,
369 self.width as u32,
370 );
371 }
372 for u in 0..units {
373 self.counts[u] += offsets[u + 1] - offsets[u];
374 }
375 }
376
377 pub fn counts(&self) -> &[u32] {
379 &self.counts
380 }
381
382 pub fn device_mean(&self) -> &cubecl::server::Handle {
384 &self.mean
385 }
386
387 pub fn templates(&self) -> Vec<Option<WaveformTemplate>> {
390 use dspk_ops::core::buffer;
391
392 let total = self.n_units * self.channels * self.width;
393 let mean = buffer::download_prefix::<f32>(&self.client, self.mean.clone(), total);
394 let m2 = buffer::download_prefix::<f32>(&self.client, self.m2.clone(), total);
395 let per = self.channels * self.width;
396 (0..self.n_units)
397 .map(|u| {
398 let n = self.counts[u] as u64;
399 (n > 0).then(|| {
400 let denom = n.saturating_sub(TEMPLATE_STD_DDOF).max(1) as f32;
401 let std = m2[u * per..(u + 1) * per].iter().map(|&v| (v.max(0.0) / denom).sqrt()).collect();
402 WaveformTemplate::with_count((0..self.channels).collect(), self.width, n as usize, mean[u * per..(u + 1) * per].to_vec(), std)
403 })
404 })
405 .collect()
406 }
407}
408
409#[cfg(test)]
410mod unit_template_tests {
411 use super::*;
412 use dspk_ops::core::buffer;
413 use dspk_core::compute::ComputeTarget;
414
415 fn recording() -> (Vec<f32>, usize, Vec<u32>, Vec<i32>, [Vec<f32>; 2]) {
418 let (channels, samples, width, n_before) = (3usize, 40_000usize, 20usize, 5usize);
419 let shapes = [
420 (0..channels * width).map(|i| { let (c, t) = (i / width, i % width); -(10.0 - 3.0 * c as f32) * (-0.5 * ((t as f32 - 5.0) / 2.0).powi(2)).exp() }).collect::<Vec<f32>>(),
421 (0..channels * width).map(|i| { let (c, t) = (i / width, i % width); 4.0 * (c as f32 + 1.0) * (-0.5 * ((t as f32 - 8.0) / 3.0).powi(2)).exp() }).collect::<Vec<f32>>(),
422 ];
423 let mut data: Vec<f32> = (0..channels * samples).map(|i| ((i * 7919 % 101) as f32 - 50.0) / 500.0).collect();
424 let (mut peaks, mut labels) = (Vec::new(), Vec::new());
425 for k in 0..300 {
426 let t0 = 100 + k * 120;
427 let u = k % 3; let shape: Vec<f32> = if u < 2 { shapes[u].clone() } else { shapes[0].iter().map(|v| v * 5.0).collect() };
429 for c in 0..channels {
430 for t in 0..width {
431 data[c * samples + t0 + t] += shape[c * width + t];
432 }
433 }
434 peaks.push((t0 + n_before) as u32);
435 labels.push(if u < 2 { u as i32 } else { -1 });
436 }
437 (data, samples, peaks, labels, shapes)
438 }
439
440 #[test]
441 fn templates_recover_the_units_and_ignore_unassigned_spikes() {
442 let Ok(target) = ComputeTarget::from_env() else { return };
443 let client = target.client().unwrap();
444 let (data, samples, peaks, labels, shapes) = recording();
445 let handle = buffer::upload(&client, &data);
446 let mut acc = UnitTemplateAccumulator::new(&client, 2, 3, 5, 20);
447 acc.add(&handle, samples, &peaks, &labels);
448 assert_eq!(acc.counts(), &[100, 100]);
449 let t = acc.templates();
450 for u in 0..2 {
451 let tmpl = t[u].as_ref().unwrap();
452 assert_eq!((tmpl.count, tmpl.channel_ids.len(), tmpl.num_samples), (100, 3, 20));
453 let err = tmpl.mean.iter().zip(&shapes[u]).map(|(a, b)| (a - b).abs()).fold(0.0f32, f32::max);
454 assert!(err < 0.05, "unit {u}: max error {err}");
455 assert!(tmpl.std.iter().all(|&s| s < 0.2), "unit {u}: std is the noise only");
456 }
457 }
458
459 #[test]
461 fn windows_add_up_to_the_whole() {
462 let Ok(target) = ComputeTarget::from_env() else { return };
463 let client = target.client().unwrap();
464 let (data, samples, peaks, labels, _) = recording();
465 let handle = buffer::upload(&client, &data);
466 let mut whole = UnitTemplateAccumulator::new(&client, 2, 3, 5, 20);
467 whole.add(&handle, samples, &peaks, &labels);
468 let mut parts = UnitTemplateAccumulator::new(&client, 2, 3, 5, 20);
469 let half = peaks.len() / 2;
470 parts.add(&handle, samples, &peaks[..half], &labels[..half]);
471 parts.add(&handle, samples, &peaks[half..], &labels[half..]);
472 let (a, b) = (whole.templates(), parts.templates());
473 for u in 0..2 {
474 let (a, b) = (a[u].as_ref().unwrap(), b[u].as_ref().unwrap());
475 assert_eq!(a.count, b.count);
476 assert!(a.mean.iter().zip(&b.mean).all(|(x, y)| (x - y).abs() < 1e-5));
477 assert!(a.std.iter().zip(&b.std).all(|(x, y)| (x - y).abs() < 1e-4));
478 }
479 let empty = UnitTemplateAccumulator::new(&client, 3, 3, 5, 20);
481 assert!(empty.templates().iter().all(Option::is_none));
482 }
483}