dspk_synapse/io/probe/
sensor.rs1use serde::{Deserialize, Serialize};
2use super::site::{Position3D, SensorSite};
3use dspk_core::{DspError, DspResult};
4
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
7pub struct SensorLayout {
8 pub name: String,
9 pub contacts: Vec<SensorSite>,
10}
11
12impl SensorLayout {
13 pub fn new(name: impl Into<String>, contacts: Vec<SensorSite>) -> Self {
14 Self {
15 name: name.into(),
16 contacts,
17 }
18 }
19
20 pub fn total_channels(&self) -> usize {
21 self.contacts.len()
22 }
23
24 pub fn active_channels(&self) -> usize {
25 self.contacts.iter().filter(|c| c.enabled).count()
26 }
27
28 pub fn get_site(&self, channel_id: usize) -> DspResult<&SensorSite> {
31 if let Some(site) = self.contacts.get(channel_id).filter(|c| c.channel_id == channel_id) {
32 return Ok(site);
33 }
34 self.contacts
35 .iter()
36 .find(|c| c.channel_id == channel_id)
37 .ok_or(DspError::InvalidChannel {
38 channel: channel_id,
39 total: self.contacts.len(),
40 })
41 }
42
43 pub fn get_contact(&self, channel_id: usize) -> DspResult<&SensorSite> {
44 self.get_site(channel_id)
45 }
46
47 pub fn sites(&self) -> &[SensorSite] {
48 &self.contacts
49 }
50
51 pub fn select_channels(&self, channels: &[usize]) -> Self {
54 let contacts = channels
55 .iter()
56 .enumerate()
57 .filter_map(|(i, &c)| self.get_site(c).ok().map(|s| SensorSite { channel_id: i, ..s.clone() }))
58 .collect();
59 Self { name: self.name.clone(), contacts }
60 }
61
62 pub fn to_channel_arrays(&self) -> (Vec<usize>, Vec<[f32; 2]>, Vec<usize>) {
65 let map = self.contacts.iter().map(|c| c.channel_id).collect();
66 let pos = self.contacts.iter().map(|c| [c.position.x_um, c.position.y_um]).collect();
67 let shanks = self.contacts.iter().map(|c| c.shank_id).collect();
68 (map, pos, shanks)
69 }
70
71 pub fn from_channel_arrays(
75 name: impl Into<String>,
76 channel_map: &[usize],
77 positions: &[[f32; 2]],
78 shanks: &[usize],
79 ) -> Option<Self> {
80 if positions.is_empty() {
81 return None;
82 }
83 let contacts = positions
84 .iter()
85 .enumerate()
86 .map(|(i, p)| {
87 SensorSite::new(
88 channel_map.get(i).copied().unwrap_or(i),
89 Position3D::new(p[0], p[1], 0.0),
90 shanks.get(i).copied().unwrap_or(0),
91 )
92 })
93 .collect();
94 Some(Self::new(name, contacts))
95 }
96}
97
98#[cfg(test)]
99mod tests {
100 #[test]
101 fn neuropixels_1_0_matches_probeinterface() {
102 let np = crate::io::probe::neuropixels_1_0();
103 let xy: Vec<(f32, f32)> = np.contacts[..6].iter().map(|c| (c.position.x_um, c.position.y_um)).collect();
104 assert_eq!(xy, vec![(43.0, 0.0), (11.0, 0.0), (59.0, 20.0), (27.0, 20.0), (43.0, 40.0), (11.0, 40.0)]);
105 assert_eq!(np.get_site(383).unwrap().position.y_um, 3820.0);
106 }
107
108 #[test]
109 fn select_channels_renumbers_sites() {
110 let np = crate::io::probe::neuropixels_1_0();
111 let sub = np.select_channels(&[10, 3, 200]);
112 assert_eq!(sub.contacts.len(), 3);
113 assert_eq!(sub.get_site(1).unwrap().position, np.get_site(3).unwrap().position);
114 assert_eq!(sub.get_site(2).unwrap().position, np.get_site(200).unwrap().position);
115 }
116}