1use crate::genomic::cortical_area::descriptors::CorticalSubUnitIndex;
2use crate::genomic::cortical_area::descriptors::CorticalUnitIndex;
3use crate::genomic::cortical_area::io_cortical_area_configuration_flag::{
4 FrameChangeHandling, PercentageNeuronPositioning,
5};
6use crate::genomic::cortical_area::{
7 CorticalAreaType, CorticalID, IOCorticalAreaConfigurationFlag,
8};
9use crate::sensor_cortical_units;
10use paste;
11use serde_json::{Map, Value};
12use std::collections::HashMap;
13use std::fmt::{Display, Formatter};
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)] pub struct UnitTopology {
17 pub relative_position: [i32; 3],
18 pub channel_dimensions_default: [u32; 3],
19 pub channel_dimensions_min: [u32; 3],
20 pub channel_dimensions_max: [u32; 3],
21}
22
23macro_rules! default_firing_threshold_impl {
24 () => {
25 None
26 };
27 ($value:expr) => {
28 Some($value)
29 };
30}
31
32macro_rules! default_mp_charge_accumulation_impl {
33 () => {
34 None
35 };
36 ($value:expr) => {
37 Some($value)
38 };
39}
40
41macro_rules! default_sensory_hold_last_frame_impl {
42 () => {
43 None
44 };
45 ($value:expr) => {
46 Some($value)
47 };
48}
49
50macro_rules! default_firing_threshold_increment_impl {
51 () => {
52 None
53 };
54 ([$x:expr, $y:expr, $z:expr]) => {
55 Some([$x, $y, $z])
56 };
57}
58
59macro_rules! define_sensory_cortical_units_enum {
60 (
61 SensoryCorticalUnit {
62 $(
63 $(#[doc = $doc:expr])?
64 $variant_name:ident => {
65 friendly_name: $friendly_name:expr,
66 accepted_wrapped_io_data_type: $accepted_wrapped_io_data_type:expr,
67 cortical_id_unit_reference: $cortical_id_unit_reference:expr,
68 number_cortical_areas: $number_cortical_areas:expr,
69 $(default_firing_threshold: $default_firing_threshold:expr,)?
70 $(default_firing_threshold_increment: [$default_firing_threshold_increment_x:expr, $default_firing_threshold_increment_y:expr, $default_firing_threshold_increment_z:expr],)?
71 $(default_mp_charge_accumulation: $default_mp_charge_accumulation:expr,)?
72 $(default_sensory_hold_last_frame: $default_sensory_hold_last_frame:expr,)?
73 cortical_type_parameters: {
74 $($param_name:ident: $param_type:ty),* $(,)?
75 },
76 $(allowed_frame_change_handling: [$($allowed_frame:ident),* $(,)?],)? cortical_area_properties: {
78 $($cortical_sub_unit_index:tt => ($io_cortical_area_configuration_flag_expr:expr, relative_position: [$rel_x:expr, $rel_y:expr, $rel_z:expr], channel_dimensions_default: [$dim_default_x:expr, $dim_default_y:expr, $dim_default_z:expr], channel_dimensions_min: [$dim_min_x:expr, $dim_min_y:expr, $dim_min_z:expr], channel_dimensions_max: [$dim_max_x:expr, $dim_max_y:expr, $dim_max_z:expr])),* $(,)?
79 }
80 }
81 ),* $(,)?
82 }
83 ) => {
84 #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
85 pub enum SensoryCorticalUnit {
86 $(
87 $(#[doc = $doc])?
88 $variant_name,
89 )*
90 }
91
92 impl SensoryCorticalUnit {
93 $(
94 paste::paste! {
95 #[doc = "Get cortical area types array for " $friendly_name " using individual parameters."]
96 pub const fn [<get_cortical_area_types_array_for_ $variant_name:snake _with_parameters >](
97 $($param_name: $param_type),*) -> [CorticalAreaType; $number_cortical_areas] {
98 [
99 $(CorticalAreaType::BrainInput($io_cortical_area_configuration_flag_expr)),*
100 ]
101 }
102
103 #[doc = "Get cortical IDs array for " $friendly_name " using individual parameters."]
104 pub const fn [<get_cortical_ids_array_for_ $variant_name:snake _with_parameters >](
105 $($param_name: $param_type,)* cortical_unit_index: CorticalUnitIndex) -> [CorticalID; $number_cortical_areas] {
106 let cortical_unit_identifier: [u8; 3] = $cortical_id_unit_reference;
107 [
108 $(
109 $io_cortical_area_configuration_flag_expr .as_io_cortical_id(true, cortical_unit_identifier, cortical_unit_index, CorticalSubUnitIndex::from($cortical_sub_unit_index))
110 ),*
111 ]
112 }
113 }
114 )*
115
116 pub const fn get_snake_case_name(&self) -> &'static str {
117 match self {
118 $(
119 SensoryCorticalUnit::$variant_name => paste::paste!{ stringify!([<$variant_name:snake>]) },
120 )*
121 }
122 }
123
124 pub fn from_snake_case_name(name: &str) -> Option<SensoryCorticalUnit> {
133 match name {
134 $(
135 paste::paste!{ stringify!([<$variant_name:snake>]) } => Some(SensoryCorticalUnit::$variant_name),
136 )*
137 _ => None,
138 }
139 }
140
141 pub const fn list_all() -> &'static [SensoryCorticalUnit] {
144 &[
145 $(
146 SensoryCorticalUnit::$variant_name,
147 )*
148 ]
149 }
150
151 pub const fn get_friendly_name(&self) -> &'static str {
153 match self {
154 $(
155 SensoryCorticalUnit::$variant_name => $friendly_name,
156 )*
157 }
158 }
159
160 pub const fn get_cortical_id_unit_reference(&self) -> [u8; 3] {
162 match self {
163 $(
164 SensoryCorticalUnit::$variant_name => $cortical_id_unit_reference,
165 )*
166 }
167 }
168
169 pub const fn get_number_cortical_areas(&self) -> usize {
171 match self {
172 $(
173 SensoryCorticalUnit::$variant_name => $number_cortical_areas,
174 )*
175 }
176 }
177
178 pub const fn get_default_firing_threshold(&self) -> Option<f64> {
181 match self {
182 $(
183 SensoryCorticalUnit::$variant_name => {
184 default_firing_threshold_impl!($($default_firing_threshold)?)
185 }
186 )*
187 }
188 }
189
190 pub const fn get_default_firing_threshold_increment(&self) -> Option<[f64; 3]> {
193 match self {
194 $(
195 SensoryCorticalUnit::$variant_name => {
196 default_firing_threshold_increment_impl!(
197 $([$default_firing_threshold_increment_x, $default_firing_threshold_increment_y, $default_firing_threshold_increment_z])?
198 )
199 }
200 )*
201 }
202 }
203
204 pub const fn get_default_mp_charge_accumulation(&self) -> Option<bool> {
207 match self {
208 $(
209 SensoryCorticalUnit::$variant_name => {
210 default_mp_charge_accumulation_impl!($($default_mp_charge_accumulation)?)
211 }
212 )*
213 }
214 }
215
216 pub const fn get_template_sensory_hold_last_frame(&self) -> Option<bool> {
220 match self {
221 $(
222 SensoryCorticalUnit::$variant_name => {
223 default_sensory_hold_last_frame_impl!($($default_sensory_hold_last_frame)?)
224 }
225 )*
226 }
227 }
228
229 pub const fn get_accepted_wrapped_io_data_type(&self) -> &'static str { match self {
232 $(
233 SensoryCorticalUnit::$variant_name => stringify!($accepted_wrapped_io_data_type),
234 )*
235 }
236 }
237
238 pub fn get_unit_default_topology(&self) -> HashMap<CorticalSubUnitIndex, UnitTopology> {
240 match self {
241 $(
242 SensoryCorticalUnit::$variant_name => {
243 let mut topology = HashMap::new();
244 $(
245 topology.insert(
246 CorticalSubUnitIndex::from($cortical_sub_unit_index),
247 UnitTopology {
248 relative_position: [$rel_x, $rel_y, $rel_z],
249 channel_dimensions_default: [$dim_default_x, $dim_default_y, $dim_default_z],
250 channel_dimensions_min: [$dim_min_x, $dim_min_y, $dim_min_z],
251 channel_dimensions_max: [$dim_max_x, $dim_max_y, $dim_max_z],
252 }
253 );
254 )*
255 topology
256 }
257 )*
258 }
259 }
260
261
262 pub fn get_allowed_frame_change_handling(&self) -> Option<&'static [FrameChangeHandling]> { match self {
267 $(
268 SensoryCorticalUnit::$variant_name => {
269 $crate::get_allowed_frame_change_handling_impl!($($($allowed_frame),*)?)
270 }
271 )*
272 }
273 }
274
275 pub fn get_cortical_id_vector_from_index_and_serde_io_configuration_flags(&self, cortical_unit_index: CorticalUnitIndex, map: Map<String, Value>) -> Result<Vec<CorticalID>, crate::FeagiDataError> {
276 match self {
277 $(
278 SensoryCorticalUnit::$variant_name => {
279 paste::paste! {
280 let array = SensoryCorticalUnit::[<get_cortical_ids_array_for_ $variant_name:snake _with_parameters >](
281 $($param_type::try_from_serde_map(&map)?,)*
282 cortical_unit_index);
283 return Ok(array.to_vec());
284 }
285 }
286 )*
287 }
288 }
289
290
291
292 }
293
294 impl Display for SensoryCorticalUnit {
295 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
296 match self {
297 $(
298 SensoryCorticalUnit::$variant_name => write!(f, $friendly_name),
299 )*
300 }
301 }
302 }
303 };
304
305}
306sensor_cortical_units!(define_sensory_cortical_units_enum);
308
309pub const SENSORY_HOLD_LAST_FRAME_PROPERTY: &str = "sensory_hold_last_frame";
315
316pub fn resolve_sensory_hold_last_frame(
320 cortical_id: &CorticalID,
321 properties: &HashMap<String, Value>,
322) -> bool {
323 if let Some(value) = properties
324 .get(SENSORY_HOLD_LAST_FRAME_PROPERTY)
325 .and_then(Value::as_bool)
326 {
327 return value;
328 }
329 SensoryCorticalUnit::try_from_cortical_id(cortical_id)
330 .map(|unit| unit.get_default_sensory_hold_last_frame())
331 .unwrap_or(false)
332}
333
334impl SensoryCorticalUnit {
335 pub fn get_default_sensory_hold_last_frame(&self) -> bool {
342 if let Some(declared) = self.get_template_sensory_hold_last_frame() {
343 return declared;
344 }
345 matches!(
346 self.get_accepted_wrapped_io_data_type(),
347 "Percentage" | "Percentage_3D" | "SignedPercentage_4D" | "RawIMU"
348 )
349 }
350
351 pub fn try_from_cortical_id(cortical_id: &CorticalID) -> Option<SensoryCorticalUnit> {
354 let bytes = cortical_id.as_bytes();
355 if bytes[0] != b'i' {
356 return None;
357 }
358 let unit_ref = [bytes[1], bytes[2], bytes[3]];
359 Self::list_all()
360 .iter()
361 .copied()
362 .find(|unit| unit.get_cortical_id_unit_reference() == unit_ref)
363 }
364
365 pub fn try_from_legacy_subtype(subtype: &str) -> Option<CorticalID> {
368 let subtype_bytes = subtype.as_bytes();
369 if subtype_bytes.len() != 3 {
370 return None;
371 }
372 let subtype_arr = [subtype_bytes[0], subtype_bytes[1], subtype_bytes[2]];
373 for unit in Self::list_all() {
374 if unit.get_cortical_id_unit_reference() == subtype_arr {
375 return Some(unit.get_default_cortical_id_for_group(CorticalUnitIndex::from(0u16)));
376 }
377 }
378 None
379 }
380
381 pub fn get_default_cortical_id_for_group(&self, group_index: CorticalUnitIndex) -> CorticalID {
383 use crate::genomic::cortical_area::io_cortical_area_configuration_flag::{
384 FrameChangeHandling, PercentageNeuronPositioning,
385 };
386 let fh = FrameChangeHandling::Absolute;
387 let pos = PercentageNeuronPositioning::Linear;
388 match self {
389 SensoryCorticalUnit::Infrared => {
390 Self::get_cortical_ids_array_for_infrared_with_parameters(fh, pos, group_index)[0]
391 }
392 SensoryCorticalUnit::Proximity => {
393 Self::get_cortical_ids_array_for_proximity_with_parameters(fh, pos, group_index)[0]
394 }
395 SensoryCorticalUnit::Shock => {
396 Self::get_cortical_ids_array_for_shock_with_parameters(fh, pos, group_index)[0]
397 }
398 SensoryCorticalUnit::Battery => {
399 Self::get_cortical_ids_array_for_battery_with_parameters(fh, pos, group_index)[0]
400 }
401 SensoryCorticalUnit::Servo => {
402 Self::get_cortical_ids_array_for_servo_with_parameters(fh, pos, group_index)[0]
403 }
404 SensoryCorticalUnit::AnalogGPIO => {
405 Self::get_cortical_ids_array_for_analog_g_p_i_o_with_parameters(
406 fh,
407 pos,
408 group_index,
409 )[0]
410 }
411 SensoryCorticalUnit::DigitalGPIO => {
412 Self::get_cortical_ids_array_for_digital_g_p_i_o_with_parameters(group_index)[0]
413 }
414 SensoryCorticalUnit::MiscData => {
415 Self::get_cortical_ids_array_for_misc_data_with_parameters(fh, group_index)[0]
416 }
417 SensoryCorticalUnit::TextEnglishInput => {
418 Self::get_cortical_ids_array_for_text_english_input_with_parameters(fh, group_index)
419 [0]
420 }
421 SensoryCorticalUnit::CountInput => {
422 Self::get_cortical_ids_array_for_count_input_with_parameters(fh, pos, group_index)
423 [0]
424 }
425 SensoryCorticalUnit::Vision => {
426 Self::get_cortical_ids_array_for_vision_with_parameters(fh, group_index)[0]
427 }
428 SensoryCorticalUnit::DepthMap => {
429 Self::get_cortical_ids_array_for_depth_map_with_parameters(fh, group_index)[0]
430 }
431 SensoryCorticalUnit::ObjectSegmentationInput => {
432 Self::get_cortical_ids_array_for_object_segmentation_input_with_parameters(
433 fh,
434 group_index,
435 )[0]
436 }
437 SensoryCorticalUnit::SegmentedVision => {
438 Self::get_cortical_ids_array_for_segmented_vision_with_parameters(fh, group_index)
439 [0]
440 }
441 SensoryCorticalUnit::RawIMU => {
442 Self::get_cortical_ids_array_for_raw_i_m_u_with_parameters(fh, pos, group_index)[0]
445 }
446 SensoryCorticalUnit::SmartIMU => {
447 Self::get_cortical_ids_array_for_smart_i_m_u_with_parameters(fh, pos, group_index)
448 [0]
449 }
450 SensoryCorticalUnit::CartesianPosition => {
451 Self::get_cortical_ids_array_for_cartesian_position_with_parameters(
452 fh,
453 pos,
454 group_index,
455 )[0]
456 }
457 SensoryCorticalUnit::AudioInput => {
458 Self::get_cortical_ids_array_for_audio_input_with_parameters(fh, group_index)[0]
459 }
460 }
461 }
462}
463
464#[cfg(test)]
465mod sensory_hold_default_tests {
466 use super::*;
467
468 #[test]
469 fn scalar_units_hold_by_default_frame_units_do_not() {
470 for unit in [
471 SensoryCorticalUnit::RawIMU,
472 SensoryCorticalUnit::SmartIMU,
473 SensoryCorticalUnit::Servo,
474 SensoryCorticalUnit::Proximity,
475 SensoryCorticalUnit::Battery,
476 SensoryCorticalUnit::AnalogGPIO,
477 SensoryCorticalUnit::CartesianPosition,
478 SensoryCorticalUnit::MiscData,
479 ] {
480 assert!(unit.get_default_sensory_hold_last_frame(), "{unit:?}");
481 }
482 for unit in [
483 SensoryCorticalUnit::Vision,
484 SensoryCorticalUnit::SegmentedVision,
485 SensoryCorticalUnit::AudioInput,
486 SensoryCorticalUnit::DigitalGPIO,
487 SensoryCorticalUnit::TextEnglishInput,
488 SensoryCorticalUnit::DepthMap,
489 ] {
490 assert!(!unit.get_default_sensory_hold_last_frame(), "{unit:?}");
491 }
492 }
493
494 #[test]
495 fn resolve_uses_property_then_template_default() {
496 let imu = SensoryCorticalUnit::RawIMU
497 .get_default_cortical_id_for_group(CorticalUnitIndex::from(0u16));
498 let vision = SensoryCorticalUnit::Vision
499 .get_default_cortical_id_for_group(CorticalUnitIndex::from(0u16));
500 assert_eq!(
501 SensoryCorticalUnit::try_from_cortical_id(&imu),
502 Some(SensoryCorticalUnit::RawIMU)
503 );
504 let empty = HashMap::new();
505 assert!(resolve_sensory_hold_last_frame(&imu, &empty));
506 assert!(!resolve_sensory_hold_last_frame(&vision, &empty));
507 let off = HashMap::from([(
508 SENSORY_HOLD_LAST_FRAME_PROPERTY.to_string(),
509 Value::Bool(false),
510 )]);
511 assert!(!resolve_sensory_hold_last_frame(&imu, &off));
512 let on = HashMap::from([(
513 SENSORY_HOLD_LAST_FRAME_PROPERTY.to_string(),
514 Value::Bool(true),
515 )]);
516 assert!(resolve_sensory_hold_last_frame(&vision, &on));
517 let custom = CorticalID::try_from_bytes(b"cHOLD001").unwrap();
518 assert!(!resolve_sensory_hold_last_frame(&custom, &empty));
519 }
520}