1use cubecl::prelude::Client;
2use dspk_ops::linalg::{PcaModel, PpcaModel};
3use dspk_core::compute::{ComputeTarget, ComputeTask};
4use dspk_core::{DspError, DspResult};
5
6use crate::core::{FeatureEmbedder, SnippetBatch, WaveformSnippet};
7
8pub mod conduction;
9pub(crate) mod kernels;
10
11pub use kernels::NO_CHANNEL;
13pub mod local_svd;
14pub mod morphology;
15pub mod wavelet;
16
17pub use local_svd::{ChannelNeighbourhoods, LocalSvd, LocalSvdOptions};
18pub use conduction::{ConductionVelocityEstimate, estimate_hdemg_conduction_velocity};
19pub use morphology::{SpikeMorphology, compute_morphology};
20pub use wavelet::{WaveletFeatureEmbedder, compute_haar_dwt};
21
22pub const DEFAULT_FEATURE_COMPONENTS: usize = 4;
24
25fn feature_matrix(features: usize, spikes: usize, feature_of: impl Fn(usize) -> Vec<f32>) -> Vec<f32> {
27 let mut matrix = vec![0.0f32; features * spikes];
28 for s in 0..spikes {
29 for (f, v) in feature_of(s).into_iter().enumerate().take(features) {
30 matrix[f * spikes + s] = v;
31 }
32 }
33 matrix
34}
35
36fn per_spike(projected: &[f32], d: usize, spikes: usize) -> Vec<f32> {
38 let mut out = vec![0.0f32; spikes * d];
39 for s in 0..spikes {
40 for k in 0..d {
41 out[s * d + k] = projected[k * spikes + s];
42 }
43 }
44 out
45}
46
47pub fn extract_waveform_pca(
50 client: &Client,
51 snippets: &[WaveformSnippet],
52 num_components: usize,
53) -> Option<(PcaModel, Vec<Vec<f32>>)> {
54 let first = snippets.first()?;
55 let (samples, spikes) = (first.num_samples, snippets.len());
56 let matrix = feature_matrix(samples, spikes, |s| {
57 let w = &snippets[s].waveform;
58 if w.len() == samples { w.clone() } else { Vec::new() }
59 });
60 let pca = PcaModel::fit::<f32>(client, &matrix, samples, spikes, num_components);
61 let d = pca.num_components;
62 let flat = per_spike(&pca.project_cpu(&matrix, samples, spikes), d, spikes);
63 let rows = flat.chunks_exact(d.max(1)).map(<[f32]>::to_vec).collect();
64 Some((pca, rows))
65}
66
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69enum LinearModel {
70 Pca,
71 Ppca,
72}
73
74struct EmbedTask<'a> {
76 model: LinearModel,
77 matrix: &'a [f32],
78 features: usize,
79 spikes: usize,
80 components: usize,
81}
82
83impl ComputeTask for EmbedTask<'_> {
84 type Output = (Vec<f32>, usize);
85 fn run(self, client: Client) -> Self::Output {
86 let (projected, d) = match self.model {
87 LinearModel::Pca => {
88 let m = PcaModel::fit::<f32>(&client, self.matrix, self.features, self.spikes, self.components);
89 (m.project_cpu(self.matrix, self.features, self.spikes), m.num_components)
90 }
91 LinearModel::Ppca => {
92 let m = PpcaModel::fit::<f32>(&client, self.matrix, self.features, self.spikes, self.components);
93 (m.project_cpu(self.matrix, self.features, self.spikes), m.num_components)
94 }
95 };
96 (per_spike(&projected, d, self.spikes), d)
97 }
98}
99
100fn embed_linear(model: LinearModel, target: ComputeTarget, components: usize, batch: &SnippetBatch) -> DspResult<(Vec<f32>, usize)> {
101 let d = components.max(1);
102 if batch.num_spikes == 0 {
103 return Ok((Vec::new(), d));
104 }
105 let features = batch.num_channels * batch.num_samples;
106 let matrix = feature_matrix(features, batch.num_spikes, |s| batch.snippet_slice(s).to_vec());
107 let task = EmbedTask { model, matrix: &matrix, features, spikes: batch.num_spikes, components: d };
108 target.run(task).map_err(|e| DspError::ComputeError(e.to_string()))
109}
110
111#[derive(Debug, Clone, Copy)]
114pub struct PcaFeatureEmbedder {
115 pub num_components: usize,
116 pub target: ComputeTarget,
117}
118
119impl PcaFeatureEmbedder {
120 pub fn new(target: ComputeTarget) -> Self {
122 Self { num_components: DEFAULT_FEATURE_COMPONENTS, target }
123 }
124}
125
126impl FeatureEmbedder for PcaFeatureEmbedder {
127 fn embed(&self, batch: &SnippetBatch) -> DspResult<(Vec<f32>, usize)> {
128 embed_linear(LinearModel::Pca, self.target, self.num_components, batch)
129 }
130}
131
132#[derive(Debug, Clone, Copy)]
135pub struct PpcaFeatureEmbedder {
136 pub num_components: usize,
137 pub target: ComputeTarget,
138}
139
140impl PpcaFeatureEmbedder {
141 pub fn new(target: ComputeTarget) -> Self {
143 Self { num_components: DEFAULT_FEATURE_COMPONENTS, target }
144 }
145}
146
147impl FeatureEmbedder for PpcaFeatureEmbedder {
148 fn embed(&self, batch: &SnippetBatch) -> DspResult<(Vec<f32>, usize)> {
149 embed_linear(LinearModel::Ppca, self.target, self.num_components, batch)
150 }
151}