1use std::path::PathBuf;
4use std::time::Instant;
5
6use serde::Serialize;
7
8use crate::core::SdkError;
9use crate::sdk::base::{CorticalTopology, TopologyCache};
10use crate::sdk::motor::perception::config::PerceptionDecoderConfig;
11use crate::sdk::motor::traits::MotorDecoder;
12use crate::sdk::types::{
13 ColorChannelLayout, ColorSpace, CorticalChannelCount, CorticalChannelIndex, CorticalUnitIndex,
14 FrameChangeHandling, ImageFrame, ImageFrameProperties, ImageXYResolution, MiscData,
15 MiscDataDimensions, MotorCorticalUnit,
16};
17use feagi_sensorimotor::caching::MotorDeviceCache;
18use feagi_sensorimotor::data_types::text_token::decode_token_id_from_misc_data_with_depth;
19use feagi_structures::neuron_voxels::xyzp::CorticalMappedXYZPNeuronVoxels;
20use std::sync::Mutex;
21
22#[derive(Debug, Clone, Serialize)]
24pub struct OsegFrame {
25 pub width: u32,
26 pub height: u32,
27 pub depth: u32,
28 pub channels: u32,
29 pub x: Vec<u32>,
30 pub y: Vec<u32>,
31 pub z: Vec<u32>,
32 pub p: Vec<f32>,
33}
34
35#[derive(Debug, Clone, Serialize)]
37pub struct OimgFrame {
38 pub width: u32,
39 pub height: u32,
40 pub depth: u32,
41 pub channels: u32,
42 pub x: Vec<u32>,
43 pub y: Vec<u32>,
44 pub z: Vec<u32>,
45 pub p: Vec<u32>,
46}
47
48#[derive(Debug, Clone, Serialize)]
50pub struct PerceptionFrame {
51 pub timestamp_ms: u64,
52 pub source_width: Option<u32>,
53 pub source_height: Option<u32>,
54 pub oseg: Option<OsegFrame>,
55 pub oimg: Option<OimgFrame>,
56 pub oten_text: Option<String>,
57 pub oten_token_id: Option<u32>,
58}
59
60pub struct PerceptionDecoder {
62 config: PerceptionDecoderConfig,
63 motor_cache: Mutex<MotorDeviceCache>,
64 oseg_topology: CorticalTopology,
65 oimg_topology: CorticalTopology,
66 oten_topology: CorticalTopology,
67 #[cfg(feature = "sdk-text")]
68 tokenizer: Option<tokenizers::Tokenizer>,
69}
70
71impl PerceptionDecoder {
72 pub async fn new(
74 config: PerceptionDecoderConfig,
75 topology_cache: &TopologyCache,
76 tokenizer_path: Option<PathBuf>,
77 ) -> Result<Self, SdkError> {
78 let unit = CorticalUnitIndex::from(config.cortical_unit_id);
79 let frame = FrameChangeHandling::Absolute;
80
81 let oseg_id =
82 MotorCorticalUnit::get_cortical_ids_array_for_object_segmentation_with_parameters(
83 frame, unit,
84 )[0];
85 let oimg_id =
86 MotorCorticalUnit::get_cortical_ids_array_for_simple_vision_output_with_parameters(
87 frame, unit,
88 )[0];
89 let oten_id =
90 MotorCorticalUnit::get_cortical_ids_array_for_text_english_output_with_parameters(
91 frame, unit,
92 )[0];
93
94 let oseg_topology = topology_cache.get_topology(&oseg_id).await?;
95 let oimg_topology = topology_cache.get_topology(&oimg_id).await?;
96 let oten_topology = topology_cache.get_topology(&oten_id).await?;
97
98 let mut motor_cache = MotorDeviceCache::new();
99 register_oseg(
100 &mut motor_cache,
101 unit,
102 oseg_topology,
103 FrameChangeHandling::Absolute,
104 )?;
105 register_oimg(
106 &mut motor_cache,
107 unit,
108 oimg_topology,
109 FrameChangeHandling::Absolute,
110 )?;
111 register_oten(
112 &mut motor_cache,
113 unit,
114 oten_topology,
115 FrameChangeHandling::Absolute,
116 )?;
117
118 #[cfg(feature = "sdk-text")]
119 let tokenizer = match tokenizer_path {
120 Some(path) => Some(
121 tokenizers::Tokenizer::from_file(path)
122 .map_err(|e| SdkError::Other(format!("Tokenizer load failed: {e}")))?,
123 ),
124 None => None,
125 };
126
127 Ok(Self {
128 config,
129 motor_cache: Mutex::new(motor_cache),
130 oseg_topology,
131 oimg_topology,
132 oten_topology,
133 #[cfg(feature = "sdk-text")]
134 tokenizer,
135 })
136 }
137
138 pub fn available_areas(&self) -> (bool, bool, bool) {
140 (
141 self.oseg_topology.width > 0,
142 self.oimg_topology.width > 0,
143 self.oten_topology.width > 0,
144 )
145 }
146
147 fn decode_oseg(&self, unit: CorticalUnitIndex) -> Result<Option<OsegFrame>, SdkError> {
148 let channel = CorticalChannelIndex::from(0u32);
150 let wrapped = self
151 .motor_cache
152 .lock()
153 .map_err(|_| SdkError::Other("OSEG cache lock poisoned".to_string()))?
154 .object_segmentation_read_postprocessed_cache_value(unit, channel)
155 .map_err(|e| SdkError::Other(format!("OSEG read failed: {e}")))?;
156 let misc: MiscData = wrapped;
157 let dims = misc.get_dimensions();
158
159 let mut x = Vec::new();
160 let mut y = Vec::new();
161 let mut z = Vec::new();
162 let mut p = Vec::new();
163 for ((xi, yi, zi), val) in misc.get_internal_data().indexed_iter() {
164 if val.abs() <= f32::EPSILON {
165 continue;
166 }
167 x.push(xi as u32);
168 y.push(yi as u32);
169 z.push(zi as u32);
170 p.push(*val);
171 }
172
173 Ok(Some(OsegFrame {
174 width: dims.width,
175 height: dims.height,
176 depth: dims.depth,
177 channels: self.oseg_topology.channels.max(1),
178 x,
179 y,
180 z,
181 p,
182 }))
183 }
184
185 fn decode_oimg(&self, unit: CorticalUnitIndex) -> Result<Option<OimgFrame>, SdkError> {
186 let channel = CorticalChannelIndex::from(0u32);
188 let wrapped = self
189 .motor_cache
190 .lock()
191 .map_err(|_| SdkError::Other("OIMG cache lock poisoned".to_string()))?
192 .simple_vision_output_read_postprocessed_cache_value(unit, channel)
193 .map_err(|e| SdkError::Other(format!("OIMG read failed: {e}")))?;
194 let image: ImageFrame = wrapped;
195
196 let resolution = image.get_image_frame_properties().get_image_resolution();
197 let mut x = Vec::new();
198 let mut y = Vec::new();
199 let mut z = Vec::new();
200 let mut p = Vec::new();
201
202 let height = resolution.height;
203 for ((row, col, channel), val) in image.get_internal_data().indexed_iter() {
204 if *val == 0 {
205 continue;
206 }
207 x.push(col as u32);
208 y.push(height - 1 - (row as u32));
209 z.push(channel as u32);
210 p.push(*val as u32);
211 }
212
213 Ok(Some(OimgFrame {
214 width: resolution.width,
215 height: resolution.height,
216 depth: image
217 .get_image_frame_properties()
218 .get_color_channel_layout() as u32,
219 channels: self.oimg_topology.channels.max(1),
220 x,
221 y,
222 z,
223 p,
224 }))
225 }
226
227 fn decode_oten(
228 &self,
229 unit: CorticalUnitIndex,
230 ) -> Result<(Option<u32>, Option<String>), SdkError> {
231 let channel = CorticalChannelIndex::from(0u32);
232 let wrapped = self
233 .motor_cache
234 .lock()
235 .map_err(|_| SdkError::Other("OTEN cache lock poisoned".to_string()))?
236 .text_english_output_read_postprocessed_cache_value(unit, channel)
237 .map_err(|e| SdkError::Other(format!("OTEN read failed: {e}")))?;
238 let misc: MiscData = wrapped;
239 let token_id = decode_token_id_from_misc_data_with_depth(&misc, self.oten_topology.depth)
240 .map_err(|e| SdkError::Other(format!("OTEN token decode failed: {e}")))?;
241
242 #[cfg(feature = "sdk-text")]
243 let text = if let (Some(token_id), Some(tokenizer)) = (token_id, &self.tokenizer) {
244 tokenizer.decode(&[token_id], true).ok()
245 } else {
246 None
247 };
248
249 #[cfg(not(feature = "sdk-text"))]
250 let text: Option<String> = None;
251
252 Ok((token_id, text))
253 }
254}
255
256impl MotorDecoder for PerceptionDecoder {
257 type Input = CorticalMappedXYZPNeuronVoxels;
258 type Output = PerceptionFrame;
259
260 fn decode(&self, input: &Self::Input) -> Result<Self::Output, SdkError> {
261 {
262 let mut cache = self
263 .motor_cache
264 .lock()
265 .map_err(|_| SdkError::Other("Motor cache lock poisoned".to_string()))?;
266 cache
267 .ingest_neuron_data_and_run_callbacks(input.clone(), Instant::now())
268 .map_err(|e| SdkError::Other(format!("Motor cache ingest failed: {e}")))?;
269 }
270
271 let unit = CorticalUnitIndex::from(self.config.cortical_unit_id);
272 let oseg = self.decode_oseg(unit)?;
273 let oimg = self.decode_oimg(unit)?;
274 let (oten_token_id, oten_text) = self.decode_oten(unit)?;
275
276 let timestamp_ms = std::time::SystemTime::now()
277 .duration_since(std::time::UNIX_EPOCH)
278 .map_err(|e| SdkError::Other(format!("Timestamp error: {e}")))?
279 .as_millis() as u64;
280
281 Ok(PerceptionFrame {
282 timestamp_ms,
283 source_width: oimg.as_ref().map(|f| f.width),
284 source_height: oimg.as_ref().map(|f| f.height),
285 oseg,
286 oimg,
287 oten_text,
288 oten_token_id,
289 })
290 }
291}
292
293fn register_oseg(
294 motor_cache: &mut MotorDeviceCache,
295 unit: CorticalUnitIndex,
296 topology: CorticalTopology,
297 frame: FrameChangeHandling,
298) -> Result<(), SdkError> {
299 let channels = CorticalChannelCount::new(topology.channels)
300 .map_err(|e| SdkError::Other(format!("Invalid OSEG channel count: {e}")))?;
301 let dims = MiscDataDimensions::new(topology.width, topology.height, topology.depth)
302 .map_err(|e| SdkError::Other(format!("Invalid OSEG dimensions: {e}")))?;
303 motor_cache
304 .object_segmentation_register(unit, channels, frame, dims)
305 .map_err(|e| SdkError::Other(format!("OSEG register failed: {e}")))
306}
307
308fn register_oimg(
309 motor_cache: &mut MotorDeviceCache,
310 unit: CorticalUnitIndex,
311 topology: CorticalTopology,
312 frame: FrameChangeHandling,
313) -> Result<(), SdkError> {
314 let channels = CorticalChannelCount::new(topology.channels)
315 .map_err(|e| SdkError::Other(format!("Invalid OIMG channel count: {e}")))?;
316 let resolution = ImageXYResolution::new(topology.width, topology.height)
317 .map_err(|e| SdkError::Other(format!("Invalid OIMG resolution: {e}")))?;
318 let layout = match topology.depth {
319 1 => ColorChannelLayout::GrayScale,
320 3 => ColorChannelLayout::RGB,
321 _ => {
322 return Err(SdkError::Other(format!(
323 "Unsupported OIMG depth: {}",
324 topology.depth
325 )))
326 }
327 };
328 let props = ImageFrameProperties::new(resolution, ColorSpace::Gamma, layout)
330 .map_err(|e| SdkError::Other(format!("OIMG properties error: {e}")))?;
331 motor_cache
332 .simple_vision_output_register(unit, channels, frame, props)
333 .map_err(|e| SdkError::Other(format!("OIMG register failed: {e}")))
334}
335
336fn register_oten(
337 motor_cache: &mut MotorDeviceCache,
338 unit: CorticalUnitIndex,
339 topology: CorticalTopology,
340 frame: FrameChangeHandling,
341) -> Result<(), SdkError> {
342 let channels = CorticalChannelCount::new(topology.channels)
343 .map_err(|e| SdkError::Other(format!("Invalid OTEN channel count: {e}")))?;
344 let dims = MiscDataDimensions::new(topology.width, topology.height, topology.depth)
345 .map_err(|e| SdkError::Other(format!("Invalid OTEN dimensions: {e}")))?;
346 motor_cache
347 .text_english_output_register(unit, channels, frame, dims)
348 .map_err(|e| SdkError::Other(format!("OTEN register failed: {e}")))
349}