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_firing_threshold_increment_impl {
42 () => {
43 None
44 };
45 ([$x:expr, $y:expr, $z:expr]) => {
46 Some([$x, $y, $z])
47 };
48}
49
50macro_rules! define_sensory_cortical_units_enum {
51 (
52 SensoryCorticalUnit {
53 $(
54 $(#[doc = $doc:expr])?
55 $variant_name:ident => {
56 friendly_name: $friendly_name:expr,
57 accepted_wrapped_io_data_type: $accepted_wrapped_io_data_type:expr,
58 cortical_id_unit_reference: $cortical_id_unit_reference:expr,
59 number_cortical_areas: $number_cortical_areas:expr,
60 $(default_firing_threshold: $default_firing_threshold:expr,)?
61 $(default_firing_threshold_increment: [$default_firing_threshold_increment_x:expr, $default_firing_threshold_increment_y:expr, $default_firing_threshold_increment_z:expr],)?
62 $(default_mp_charge_accumulation: $default_mp_charge_accumulation:expr,)?
63 cortical_type_parameters: {
64 $($param_name:ident: $param_type:ty),* $(,)?
65 },
66 $(allowed_frame_change_handling: [$($allowed_frame:ident),* $(,)?],)? cortical_area_properties: {
68 $($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])),* $(,)?
69 }
70 }
71 ),* $(,)?
72 }
73 ) => {
74 #[derive(Debug, Hash, PartialEq, Eq, Clone, Copy, serde::Serialize, serde::Deserialize)]
75 pub enum SensoryCorticalUnit {
76 $(
77 $(#[doc = $doc])?
78 $variant_name,
79 )*
80 }
81
82 impl SensoryCorticalUnit {
83 $(
84 paste::paste! {
85 #[doc = "Get cortical area types array for " $friendly_name " using individual parameters."]
86 pub const fn [<get_cortical_area_types_array_for_ $variant_name:snake _with_parameters >](
87 $($param_name: $param_type),*) -> [CorticalAreaType; $number_cortical_areas] {
88 [
89 $(CorticalAreaType::BrainInput($io_cortical_area_configuration_flag_expr)),*
90 ]
91 }
92
93 #[doc = "Get cortical IDs array for " $friendly_name " using individual parameters."]
94 pub const fn [<get_cortical_ids_array_for_ $variant_name:snake _with_parameters >](
95 $($param_name: $param_type,)* cortical_unit_index: CorticalUnitIndex) -> [CorticalID; $number_cortical_areas] {
96 let cortical_unit_identifier: [u8; 3] = $cortical_id_unit_reference;
97 [
98 $(
99 $io_cortical_area_configuration_flag_expr .as_io_cortical_id(true, cortical_unit_identifier, cortical_unit_index, CorticalSubUnitIndex::from($cortical_sub_unit_index))
100 ),*
101 ]
102 }
103 }
104 )*
105
106 pub const fn get_snake_case_name(&self) -> &'static str {
107 match self {
108 $(
109 SensoryCorticalUnit::$variant_name => paste::paste!{ stringify!([<$variant_name:snake>]) },
110 )*
111 }
112 }
113
114 pub fn from_snake_case_name(name: &str) -> Option<SensoryCorticalUnit> {
123 match name {
124 $(
125 paste::paste!{ stringify!([<$variant_name:snake>]) } => Some(SensoryCorticalUnit::$variant_name),
126 )*
127 _ => None,
128 }
129 }
130
131 pub const fn list_all() -> &'static [SensoryCorticalUnit] {
134 &[
135 $(
136 SensoryCorticalUnit::$variant_name,
137 )*
138 ]
139 }
140
141 pub const fn get_friendly_name(&self) -> &'static str {
143 match self {
144 $(
145 SensoryCorticalUnit::$variant_name => $friendly_name,
146 )*
147 }
148 }
149
150 pub const fn get_cortical_id_unit_reference(&self) -> [u8; 3] {
152 match self {
153 $(
154 SensoryCorticalUnit::$variant_name => $cortical_id_unit_reference,
155 )*
156 }
157 }
158
159 pub const fn get_number_cortical_areas(&self) -> usize {
161 match self {
162 $(
163 SensoryCorticalUnit::$variant_name => $number_cortical_areas,
164 )*
165 }
166 }
167
168 pub const fn get_default_firing_threshold(&self) -> Option<f64> {
171 match self {
172 $(
173 SensoryCorticalUnit::$variant_name => {
174 default_firing_threshold_impl!($($default_firing_threshold)?)
175 }
176 )*
177 }
178 }
179
180 pub const fn get_default_firing_threshold_increment(&self) -> Option<[f64; 3]> {
183 match self {
184 $(
185 SensoryCorticalUnit::$variant_name => {
186 default_firing_threshold_increment_impl!(
187 $([$default_firing_threshold_increment_x, $default_firing_threshold_increment_y, $default_firing_threshold_increment_z])?
188 )
189 }
190 )*
191 }
192 }
193
194 pub const fn get_default_mp_charge_accumulation(&self) -> Option<bool> {
197 match self {
198 $(
199 SensoryCorticalUnit::$variant_name => {
200 default_mp_charge_accumulation_impl!($($default_mp_charge_accumulation)?)
201 }
202 )*
203 }
204 }
205
206 pub const fn get_accepted_wrapped_io_data_type(&self) -> &'static str { match self {
209 $(
210 SensoryCorticalUnit::$variant_name => stringify!($accepted_wrapped_io_data_type),
211 )*
212 }
213 }
214
215 pub fn get_unit_default_topology(&self) -> HashMap<CorticalSubUnitIndex, UnitTopology> {
217 match self {
218 $(
219 SensoryCorticalUnit::$variant_name => {
220 let mut topology = HashMap::new();
221 $(
222 topology.insert(
223 CorticalSubUnitIndex::from($cortical_sub_unit_index),
224 UnitTopology {
225 relative_position: [$rel_x, $rel_y, $rel_z],
226 channel_dimensions_default: [$dim_default_x, $dim_default_y, $dim_default_z],
227 channel_dimensions_min: [$dim_min_x, $dim_min_y, $dim_min_z],
228 channel_dimensions_max: [$dim_max_x, $dim_max_y, $dim_max_z],
229 }
230 );
231 )*
232 topology
233 }
234 )*
235 }
236 }
237
238
239 pub fn get_allowed_frame_change_handling(&self) -> Option<&'static [FrameChangeHandling]> { match self {
244 $(
245 SensoryCorticalUnit::$variant_name => {
246 $crate::get_allowed_frame_change_handling_impl!($($($allowed_frame),*)?)
247 }
248 )*
249 }
250 }
251
252 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> {
253 match self {
254 $(
255 SensoryCorticalUnit::$variant_name => {
256 paste::paste! {
257 let array = SensoryCorticalUnit::[<get_cortical_ids_array_for_ $variant_name:snake _with_parameters >](
258 $($param_type::try_from_serde_map(&map)?,)*
259 cortical_unit_index);
260 return Ok(array.to_vec());
261 }
262 }
263 )*
264 }
265 }
266
267
268
269 }
270
271 impl Display for SensoryCorticalUnit {
272 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
273 match self {
274 $(
275 SensoryCorticalUnit::$variant_name => write!(f, $friendly_name),
276 )*
277 }
278 }
279 }
280 };
281
282}
283sensor_cortical_units!(define_sensory_cortical_units_enum);
285
286pub const SENSORY_HOLD_LAST_FRAME_PROPERTY: &str = "sensory_hold_last_frame";
292
293pub fn resolve_sensory_hold_last_frame(
297 cortical_id: &CorticalID,
298 properties: &HashMap<String, Value>,
299) -> bool {
300 if let Some(value) = properties
301 .get(SENSORY_HOLD_LAST_FRAME_PROPERTY)
302 .and_then(Value::as_bool)
303 {
304 return value;
305 }
306 SensoryCorticalUnit::try_from_cortical_id(cortical_id)
307 .map(|unit| unit.get_default_sensory_hold_last_frame())
308 .unwrap_or(false)
309}
310
311impl SensoryCorticalUnit {
312 pub fn get_default_sensory_hold_last_frame(&self) -> bool {
317 matches!(
318 self.get_accepted_wrapped_io_data_type(),
319 "Percentage" | "Percentage_3D" | "SignedPercentage_4D" | "RawIMU"
320 )
321 }
322
323 pub fn try_from_cortical_id(cortical_id: &CorticalID) -> Option<SensoryCorticalUnit> {
326 let bytes = cortical_id.as_bytes();
327 if bytes[0] != b'i' {
328 return None;
329 }
330 let unit_ref = [bytes[1], bytes[2], bytes[3]];
331 Self::list_all()
332 .iter()
333 .copied()
334 .find(|unit| unit.get_cortical_id_unit_reference() == unit_ref)
335 }
336
337 pub fn try_from_legacy_subtype(subtype: &str) -> Option<CorticalID> {
340 let subtype_bytes = subtype.as_bytes();
341 if subtype_bytes.len() != 3 {
342 return None;
343 }
344 let subtype_arr = [subtype_bytes[0], subtype_bytes[1], subtype_bytes[2]];
345 for unit in Self::list_all() {
346 if unit.get_cortical_id_unit_reference() == subtype_arr {
347 return Some(unit.get_default_cortical_id_for_group(CorticalUnitIndex::from(0u16)));
348 }
349 }
350 None
351 }
352
353 pub fn get_default_cortical_id_for_group(&self, group_index: CorticalUnitIndex) -> CorticalID {
355 use crate::genomic::cortical_area::io_cortical_area_configuration_flag::{
356 FrameChangeHandling, PercentageNeuronPositioning,
357 };
358 let fh = FrameChangeHandling::Absolute;
359 let pos = PercentageNeuronPositioning::Linear;
360 match self {
361 SensoryCorticalUnit::Infrared => {
362 Self::get_cortical_ids_array_for_infrared_with_parameters(fh, pos, group_index)[0]
363 }
364 SensoryCorticalUnit::Proximity => {
365 Self::get_cortical_ids_array_for_proximity_with_parameters(fh, pos, group_index)[0]
366 }
367 SensoryCorticalUnit::Shock => {
368 Self::get_cortical_ids_array_for_shock_with_parameters(fh, pos, group_index)[0]
369 }
370 SensoryCorticalUnit::Battery => {
371 Self::get_cortical_ids_array_for_battery_with_parameters(fh, pos, group_index)[0]
372 }
373 SensoryCorticalUnit::Servo => {
374 Self::get_cortical_ids_array_for_servo_with_parameters(fh, pos, group_index)[0]
375 }
376 SensoryCorticalUnit::AnalogGPIO => {
377 Self::get_cortical_ids_array_for_analog_g_p_i_o_with_parameters(
378 fh,
379 pos,
380 group_index,
381 )[0]
382 }
383 SensoryCorticalUnit::DigitalGPIO => {
384 Self::get_cortical_ids_array_for_digital_g_p_i_o_with_parameters(group_index)[0]
385 }
386 SensoryCorticalUnit::MiscData => {
387 Self::get_cortical_ids_array_for_misc_data_with_parameters(fh, group_index)[0]
388 }
389 SensoryCorticalUnit::TextEnglishInput => {
390 Self::get_cortical_ids_array_for_text_english_input_with_parameters(fh, group_index)
391 [0]
392 }
393 SensoryCorticalUnit::CountInput => {
394 Self::get_cortical_ids_array_for_count_input_with_parameters(fh, pos, group_index)
395 [0]
396 }
397 SensoryCorticalUnit::Vision => {
398 Self::get_cortical_ids_array_for_vision_with_parameters(fh, group_index)[0]
399 }
400 SensoryCorticalUnit::DepthMap => {
401 Self::get_cortical_ids_array_for_depth_map_with_parameters(fh, group_index)[0]
402 }
403 SensoryCorticalUnit::ObjectSegmentationInput => {
404 Self::get_cortical_ids_array_for_object_segmentation_input_with_parameters(
405 fh,
406 group_index,
407 )[0]
408 }
409 SensoryCorticalUnit::SegmentedVision => {
410 Self::get_cortical_ids_array_for_segmented_vision_with_parameters(fh, group_index)
411 [0]
412 }
413 SensoryCorticalUnit::RawIMU => {
414 Self::get_cortical_ids_array_for_raw_i_m_u_with_parameters(fh, pos, group_index)[0]
417 }
418 SensoryCorticalUnit::SmartIMU => {
419 Self::get_cortical_ids_array_for_smart_i_m_u_with_parameters(fh, pos, group_index)
420 [0]
421 }
422 SensoryCorticalUnit::CartesianPosition => {
423 Self::get_cortical_ids_array_for_cartesian_position_with_parameters(
424 fh,
425 pos,
426 group_index,
427 )[0]
428 }
429 SensoryCorticalUnit::AudioInput => {
430 Self::get_cortical_ids_array_for_audio_input_with_parameters(fh, group_index)[0]
431 }
432 }
433 }
434}
435
436#[cfg(test)]
437mod sensory_hold_default_tests {
438 use super::*;
439
440 #[test]
441 fn scalar_units_hold_by_default_frame_units_do_not() {
442 for unit in [
443 SensoryCorticalUnit::RawIMU,
444 SensoryCorticalUnit::SmartIMU,
445 SensoryCorticalUnit::Servo,
446 SensoryCorticalUnit::Proximity,
447 SensoryCorticalUnit::Battery,
448 SensoryCorticalUnit::AnalogGPIO,
449 SensoryCorticalUnit::CartesianPosition,
450 ] {
451 assert!(unit.get_default_sensory_hold_last_frame(), "{unit:?}");
452 }
453 for unit in [
454 SensoryCorticalUnit::Vision,
455 SensoryCorticalUnit::SegmentedVision,
456 SensoryCorticalUnit::AudioInput,
457 SensoryCorticalUnit::DigitalGPIO,
458 SensoryCorticalUnit::MiscData,
459 SensoryCorticalUnit::DepthMap,
460 ] {
461 assert!(!unit.get_default_sensory_hold_last_frame(), "{unit:?}");
462 }
463 }
464
465 #[test]
466 fn resolve_uses_property_then_template_default() {
467 let imu = SensoryCorticalUnit::RawIMU
468 .get_default_cortical_id_for_group(CorticalUnitIndex::from(0u16));
469 let vision = SensoryCorticalUnit::Vision
470 .get_default_cortical_id_for_group(CorticalUnitIndex::from(0u16));
471 assert_eq!(
472 SensoryCorticalUnit::try_from_cortical_id(&imu),
473 Some(SensoryCorticalUnit::RawIMU)
474 );
475 let empty = HashMap::new();
476 assert!(resolve_sensory_hold_last_frame(&imu, &empty));
477 assert!(!resolve_sensory_hold_last_frame(&vision, &empty));
478 let off = HashMap::from([(
479 SENSORY_HOLD_LAST_FRAME_PROPERTY.to_string(),
480 Value::Bool(false),
481 )]);
482 assert!(!resolve_sensory_hold_last_frame(&imu, &off));
483 let on = HashMap::from([(
484 SENSORY_HOLD_LAST_FRAME_PROPERTY.to_string(),
485 Value::Bool(true),
486 )]);
487 assert!(resolve_sensory_hold_last_frame(&vision, &on));
488 let custom = CorticalID::try_from_bytes(b"cHOLD001").unwrap();
489 assert!(!resolve_sensory_hold_last_frame(&custom, &empty));
490 }
491}