Skip to main content

feagi_sensorimotor/configuration/
jsonable.rs

1use crate::data_pipeline::PipelineStageProperties;
2use crate::data_types::descriptors::{
3    ImageFrameProperties, MiscDataDimensions, PercentageChannelDimensionality,
4    SegmentedImageFrameProperties,
5};
6use crate::data_types::{
7    GazeProperties, ImageFilteringSettings, ImageFrame, MiscData, Percentage, Percentage2D,
8    Percentage3D, Percentage4D, SegmentedImageFrame, SignedPercentage, SignedPercentage2D,
9    SignedPercentage3D, SignedPercentage4D,
10};
11use crate::feedbacks::FeedbackRegistrar;
12use crate::neuron_voxel_coding::xyzp::decoders::{
13    GazePropertiesNeuronVoxelXYZPDecoder, ImageFilteringSettingsNeuronVoxelXYZPDecoder,
14    MiscDataNeuronVoxelXYZPDecoder, PercentageNeuronVoxelXYZPDecoder,
15};
16use crate::neuron_voxel_coding::xyzp::encoders::{
17    BooleanNeuronVoxelXYZPEncoder, CartesianPlaneNeuronVoxelXYZPEncoder,
18    MiscDataNeuronVoxelXYZPEncoder, PercentageNeuronVoxelXYZPEncoder,
19    SegmentedImageFrameNeuronVoxelXYZPEncoder,
20};
21use crate::neuron_voxel_coding::xyzp::{NeuronVoxelXYZPDecoder, NeuronVoxelXYZPEncoder};
22use crate::wrapped_io_data::WrappedIOData;
23use feagi_structures::genomic::cortical_area::descriptors::{
24    CorticalChannelCount, CorticalChannelIndex, CorticalUnitIndex, NeuronDepth,
25};
26use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::PercentageNeuronPositioning;
27use feagi_structures::genomic::cortical_area::CorticalID;
28use feagi_structures::genomic::{MotorCorticalUnit, SensoryCorticalUnit};
29use feagi_structures::FeagiDataError;
30use serde::{Deserialize, Serialize};
31use std::collections::HashMap;
32
33/// Top level JSON representation of registered devices and feedbacks
34
35#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)]
36pub struct JSONInputOutputDefinition {
37    input_units_and_encoder_properties:
38        HashMap<SensoryCorticalUnit, Vec<(JSONUnitDefinition, JSONEncoderProperties)>>,
39    output_units_and_decoder_properties:
40        HashMap<MotorCorticalUnit, Vec<(JSONUnitDefinition, JSONDecoderProperties)>>,
41    feedbacks: FeedbackRegistrar,
42}
43
44impl JSONInputOutputDefinition {
45    pub fn new() -> JSONInputOutputDefinition {
46        JSONInputOutputDefinition {
47            input_units_and_encoder_properties: HashMap::new(),
48            output_units_and_decoder_properties: HashMap::new(),
49            feedbacks: FeedbackRegistrar::new(),
50        }
51    }
52
53    pub fn get_input_units_and_encoder_properties(
54        &self,
55    ) -> &HashMap<SensoryCorticalUnit, Vec<(JSONUnitDefinition, JSONEncoderProperties)>> {
56        &self.input_units_and_encoder_properties
57    }
58
59    pub fn get_output_units_and_decoder_properties(
60        &self,
61    ) -> &HashMap<MotorCorticalUnit, Vec<(JSONUnitDefinition, JSONDecoderProperties)>> {
62        &self.output_units_and_decoder_properties
63    }
64
65    pub fn verify_valid_structure(&self) -> Result<(), FeagiDataError> {
66        for units_and_encoders in self.input_units_and_encoder_properties.values() {
67            let mut unit_indexes: Vec<CorticalUnitIndex> = Vec::new();
68            for unit in units_and_encoders {
69                unit.0.verify_valid_structure()?;
70                if unit_indexes.contains(&unit.0.cortical_unit_index) {
71                    return Err(FeagiDataError::DeserializationError(
72                        "Duplicate cortical unit indexes found!".into(),
73                    ));
74                }
75                unit_indexes.push(unit.0.cortical_unit_index);
76            }
77        }
78        for units_and_decoders in self.output_units_and_decoder_properties.values() {
79            let mut unit_indexes: Vec<CorticalUnitIndex> = Vec::new();
80            for unit in units_and_decoders {
81                unit.0.verify_valid_structure()?;
82                if unit_indexes.contains(&unit.0.cortical_unit_index) {
83                    return Err(FeagiDataError::DeserializationError(
84                        "Duplicate cortical unit indexes found!".into(),
85                    ));
86                }
87                unit_indexes.push(unit.0.cortical_unit_index);
88            }
89        }
90
91        Ok(())
92    }
93
94    pub fn insert_motor(
95        &mut self,
96        motor: MotorCorticalUnit,
97        unit_definition: JSONUnitDefinition,
98        decoder_properties: JSONDecoderProperties,
99    ) {
100        if let std::collections::hash_map::Entry::Vacant(entry) =
101            self.output_units_and_decoder_properties.entry(motor)
102        {
103            entry.insert(vec![(unit_definition, decoder_properties)]);
104            return;
105        }
106        let vec = self
107            .output_units_and_decoder_properties
108            .get_mut(&motor)
109            .unwrap();
110        vec.push((unit_definition, decoder_properties));
111    }
112
113    pub fn insert_sensor(
114        &mut self,
115        sensor: SensoryCorticalUnit,
116        unit_definition: JSONUnitDefinition,
117        encoder_properties: JSONEncoderProperties,
118    ) {
119        if let std::collections::hash_map::Entry::Vacant(entry) =
120            self.input_units_and_encoder_properties.entry(sensor)
121        {
122            entry.insert(vec![(unit_definition, encoder_properties)]);
123            return;
124        }
125        let vec = self
126            .input_units_and_encoder_properties
127            .get_mut(&sensor)
128            .unwrap();
129        vec.push((unit_definition, encoder_properties));
130    }
131
132    pub(crate) fn get_feedbacks(&self) -> &FeedbackRegistrar {
133        &self.feedbacks
134    }
135    pub(crate) fn set_feedbacks(&mut self, feedbacks: FeedbackRegistrar) {
136        self.feedbacks = feedbacks;
137    }
138}
139
140impl Default for JSONInputOutputDefinition {
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146/// Defines a cortical unit. Does not include a COder Property directly since the type can vary
147/// between input and output
148#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
149pub struct JSONUnitDefinition {
150    pub(crate) friendly_name: Option<String>,
151    pub(crate) cortical_unit_index: CorticalUnitIndex,
152    pub(crate) io_configuration_flags: serde_json::Map<String, serde_json::Value>, // Due to the diversity contained here, this MUST be a generic dictionary
153    pub(crate) device_grouping: Vec<JSONDeviceGrouping>,
154}
155
156impl JSONUnitDefinition {
157    pub fn verify_valid_structure(&self) -> Result<(), FeagiDataError> {
158        if self.device_grouping.is_empty() {
159            return Err(FeagiDataError::DeserializationError(
160                "Cannot have a cortical unit of 0 device grouping!".to_string(),
161            ));
162        }
163        let number_channels = self.device_grouping.len() as u32;
164        for device_grouping in &self.device_grouping {
165            if let Some(channel_override) = device_grouping.channel_index_override {
166                if *channel_override > number_channels {
167                    return Err(FeagiDataError::DeserializationError(
168                        "Device has invalid channel override!".to_string(),
169                    ));
170                }
171            }
172
173            let _stages = &device_grouping.pipeline_stages;
174            // TODO check stage compatibility
175        }
176        Ok(())
177    }
178
179    pub fn get_channel_count(&self) -> Result<CorticalChannelCount, FeagiDataError> {
180        CorticalChannelCount::new(self.device_grouping.len() as u32)
181    }
182}
183
184/// Defines a cortical unit's channel implementations
185#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
186pub struct JSONDeviceGrouping {
187    pub(crate) friendly_name: Option<String>,
188    pub(crate) device_properties: JSONDeviceProperties,
189    pub(crate) channel_index_override: Option<CorticalChannelIndex>,
190    pub(crate) pipeline_stages: Vec<PipelineStageProperties>,
191}
192
193/// Middleman for Encoders and Decoders
194//region Coder Properties
195
196#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197pub enum JSONEncoderProperties {
198    Boolean,
199    CartesianPlane(ImageFrameProperties),
200    MiscData(MiscDataDimensions),
201    Percentage(
202        NeuronDepth,
203        PercentageNeuronPositioning,
204        bool,
205        PercentageChannelDimensionality,
206    ),
207    SegmentedImageFrame(SegmentedImageFrameProperties),
208}
209
210impl JSONEncoderProperties {
211    pub fn to_box_encoder(
212        &self,
213        number_channels: CorticalChannelCount,
214        cortical_ids: &[CorticalID],
215    ) -> Result<Box<dyn NeuronVoxelXYZPEncoder + Sync + Send>, FeagiDataError> {
216        match self {
217            JSONEncoderProperties::Boolean => {
218                if cortical_ids.len() != 1 {
219                    return Err(FeagiDataError::InternalError(
220                        "Expected one cortical id!".to_string(),
221                    ));
222                }
223                BooleanNeuronVoxelXYZPEncoder::new_box(
224                    *cortical_ids.first().unwrap(),
225                    number_channels,
226                )
227            }
228            JSONEncoderProperties::CartesianPlane(image_frame) => {
229                if cortical_ids.len() != 1 {
230                    return Err(FeagiDataError::InternalError(
231                        "Expected one cortical id!".to_string(),
232                    ));
233                }
234                CartesianPlaneNeuronVoxelXYZPEncoder::new_box(
235                    *cortical_ids.first().unwrap(),
236                    image_frame,
237                    number_channels,
238                )
239            }
240            JSONEncoderProperties::MiscData(misc_data_dimensions) => {
241                if cortical_ids.len() != 1 {
242                    return Err(FeagiDataError::InternalError(
243                        "Expected one cortical id!".to_string(),
244                    ));
245                }
246                MiscDataNeuronVoxelXYZPEncoder::new_box(
247                    *cortical_ids.first().unwrap(),
248                    *misc_data_dimensions,
249                    number_channels,
250                )
251            }
252            JSONEncoderProperties::Percentage(
253                neuron_depth,
254                percentage,
255                is_signed,
256                number_dimensions,
257            ) => {
258                if cortical_ids.len() != 1 {
259                    return Err(FeagiDataError::InternalError(
260                        "Expected one cortical id!".to_string(),
261                    ));
262                }
263                PercentageNeuronVoxelXYZPEncoder::new_box(
264                    *cortical_ids.first().unwrap(),
265                    *neuron_depth,
266                    number_channels,
267                    *percentage,
268                    *is_signed,
269                    *number_dimensions,
270                )
271            }
272            JSONEncoderProperties::SegmentedImageFrame(segmented_properties) => {
273                if cortical_ids.len() != 9 {
274                    return Err(FeagiDataError::InternalError(
275                        "Expected nine cortical ids!".to_string(),
276                    ));
277                }
278                let cortical_ids: [CorticalID; 9] = (*cortical_ids).try_into().map_err(|_| {
279                    FeagiDataError::InternalError("Unable to get cortical ids!".to_string())
280                })?;
281                SegmentedImageFrameNeuronVoxelXYZPEncoder::new_box(
282                    cortical_ids,
283                    *segmented_properties,
284                    number_channels,
285                )
286            }
287        }
288    }
289
290    pub fn default_wrapped_value(&self) -> Result<WrappedIOData, FeagiDataError> {
291        match self {
292            JSONEncoderProperties::Boolean => Ok(WrappedIOData::Boolean(false)),
293            JSONEncoderProperties::CartesianPlane(image_frame_properties) => {
294                Ok(WrappedIOData::ImageFrame(
295                    ImageFrame::new_from_image_frame_properties(image_frame_properties)?,
296                ))
297            }
298            JSONEncoderProperties::MiscData(misc_data_dimensions) => Ok(WrappedIOData::MiscData(
299                MiscData::new(misc_data_dimensions)?,
300            )),
301            JSONEncoderProperties::Percentage(
302                _neuron_depth,
303                _percentage,
304                is_signed,
305                number_dimensions,
306            ) => match number_dimensions {
307                PercentageChannelDimensionality::D1 => {
308                    if *is_signed {
309                        Ok(WrappedIOData::SignedPercentage(
310                            SignedPercentage::new_from_m1_1_unchecked(0.0),
311                        ))
312                    } else {
313                        Ok(WrappedIOData::Percentage(Percentage::new_zero()))
314                    }
315                }
316                PercentageChannelDimensionality::D2 => {
317                    if *is_signed {
318                        Ok(WrappedIOData::SignedPercentage_2D(
319                            SignedPercentage2D::new_zero(),
320                        ))
321                    } else {
322                        Ok(WrappedIOData::Percentage_2D(Percentage2D::new_zero()))
323                    }
324                }
325                PercentageChannelDimensionality::D3 => {
326                    if *is_signed {
327                        Ok(WrappedIOData::SignedPercentage_3D(
328                            SignedPercentage3D::new_zero(),
329                        ))
330                    } else {
331                        Ok(WrappedIOData::Percentage_3D(Percentage3D::new_zero()))
332                    }
333                }
334                PercentageChannelDimensionality::D4 => {
335                    if *is_signed {
336                        Ok(WrappedIOData::SignedPercentage_4D(
337                            SignedPercentage4D::new_zero(),
338                        ))
339                    } else {
340                        Ok(WrappedIOData::Percentage_4D(Percentage4D::new_zero()))
341                    }
342                }
343            },
344            JSONEncoderProperties::SegmentedImageFrame(segmented_properties) => {
345                Ok(WrappedIOData::SegmentedImageFrame(
346                    SegmentedImageFrame::from_segmented_image_frame_properties(
347                        segmented_properties,
348                    )?,
349                ))
350            }
351        }
352    }
353}
354
355#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
356pub enum JSONDecoderProperties {
357    CartesianPlane(ImageFrameProperties),
358    MiscData(MiscDataDimensions),
359    Percentage(
360        NeuronDepth,
361        PercentageNeuronPositioning,
362        bool,
363        PercentageChannelDimensionality,
364    ),
365    PositionalServo(NeuronDepth, PercentageNeuronPositioning), // z depth for both absolute and incremental
366    GazeProperties(NeuronDepth, NeuronDepth, PercentageNeuronPositioning), // eccentricity z depth, modularity z depth
367    ImageFilteringSettings(
368        NeuronDepth,
369        NeuronDepth,
370        NeuronDepth,
371        PercentageNeuronPositioning,
372    ), // brightness z depth, contrast z depth, diff z depth
373}
374
375impl JSONDecoderProperties {
376    pub fn to_box_decoder(
377        &self,
378        number_channels: CorticalChannelCount,
379        cortical_ids: &[CorticalID],
380    ) -> Result<Box<dyn NeuronVoxelXYZPDecoder + Sync + Send>, FeagiDataError> {
381        match self {
382            JSONDecoderProperties::CartesianPlane(image_frame_properties) => {
383                if cortical_ids.len() != 1 {
384                    return Err(FeagiDataError::InternalError(
385                        "Expected one cortical id!".to_string(),
386                    ));
387                }
388                crate::neuron_voxel_coding::xyzp::decoders::CartesianPlaneNeuronVoxelXYZPDecoder::new_box(
389                    *cortical_ids.first().unwrap(),
390                    image_frame_properties,
391                    number_channels,
392                )
393            }
394            JSONDecoderProperties::MiscData(misc_data_dimensions) => {
395                if cortical_ids.len() != 1 {
396                    return Err(FeagiDataError::InternalError(
397                        "Expected one cortical id!".to_string(),
398                    ));
399                }
400                MiscDataNeuronVoxelXYZPDecoder::new_box(
401                    *cortical_ids.first().unwrap(), // Eccentricity
402                    *misc_data_dimensions,
403                    number_channels,
404                )
405            }
406            JSONDecoderProperties::Percentage(
407                neuron_depth,
408                percentage_neuron_positioning,
409                is_signed,
410                dimension_count,
411            ) => {
412                if cortical_ids.len() != 1 {
413                    return Err(FeagiDataError::InternalError(
414                        "Expected one cortical id!".to_string(),
415                    ));
416                }
417                PercentageNeuronVoxelXYZPDecoder::new_box(
418                    *cortical_ids.first().unwrap(), // Eccentricity
419                    *neuron_depth,
420                    number_channels,
421                    *percentage_neuron_positioning,
422                    *is_signed,
423                    *dimension_count,
424                )
425            }
426            JSONDecoderProperties::PositionalServo(neuron_depth, percentage_neuron_positioning) => {
427                if cortical_ids.len() != 2 {
428                    return Err(FeagiDataError::InternalError(
429                        "Expected two cortical ids for PositionalServo!".to_string(),
430                    ));
431                }
432                crate::neuron_voxel_coding::xyzp::decoders::PositionalServoNeuronVoxelXYZPDecoder::new_box(
433                    *cortical_ids.first().unwrap(), // Absolute
434                    *cortical_ids.get(1).unwrap(),  // Incremental
435                    *neuron_depth,
436                    number_channels,
437                    *percentage_neuron_positioning,
438                )
439            }
440            JSONDecoderProperties::GazeProperties(
441                eccentricity_neuron_depth,
442                modularity_neuron_depth,
443                percentage_neuron_positioning,
444            ) => {
445                if cortical_ids.len() != 2 {
446                    return Err(FeagiDataError::InternalError(
447                        "Expected two cortical ids!".to_string(),
448                    ));
449                }
450                GazePropertiesNeuronVoxelXYZPDecoder::new_box(
451                    *cortical_ids.first().unwrap(), // Eccentricity
452                    *cortical_ids.get(1).unwrap(),  // Modularity
453                    *eccentricity_neuron_depth,
454                    *modularity_neuron_depth,
455                    number_channels,
456                    *percentage_neuron_positioning,
457                )
458            }
459            JSONDecoderProperties::ImageFilteringSettings(
460                brightness_z_depth,
461                contrast_z_depth,
462                diff_z_depth,
463                percentage_neuron_positioning,
464            ) => {
465                if cortical_ids.len() != 3 {
466                    return Err(FeagiDataError::InternalError(
467                        "Expected three cortical ids for ImageFilteringSettings!".to_string(),
468                    ));
469                }
470                ImageFilteringSettingsNeuronVoxelXYZPDecoder::new_box(
471                    *cortical_ids.first().unwrap(), // Brightness
472                    *cortical_ids.get(1).unwrap(),  // Contrast
473                    *cortical_ids.get(2).unwrap(),  // Diff threshold
474                    *brightness_z_depth,
475                    *contrast_z_depth,
476                    *diff_z_depth,
477                    number_channels,
478                    *percentage_neuron_positioning,
479                )
480            }
481        }
482    }
483
484    pub fn default_wrapped_value(&self) -> Result<WrappedIOData, FeagiDataError> {
485        match self {
486            JSONDecoderProperties::CartesianPlane(image_frame_properties) => {
487                Ok(WrappedIOData::ImageFrame(
488                    ImageFrame::new_from_image_frame_properties(image_frame_properties)?,
489                ))
490            }
491            JSONDecoderProperties::MiscData(misc_data_dimensions) => Ok(WrappedIOData::MiscData(
492                MiscData::new(misc_data_dimensions)?,
493            )),
494            JSONDecoderProperties::Percentage(
495                _neuron_depth,
496                _percentage_neuron_positioning,
497                is_signed,
498                number_dimensions,
499            ) => match number_dimensions {
500                PercentageChannelDimensionality::D1 => {
501                    if *is_signed {
502                        Ok(WrappedIOData::SignedPercentage(
503                            SignedPercentage::new_from_m1_1_unchecked(0.0),
504                        ))
505                    } else {
506                        Ok(WrappedIOData::Percentage(Percentage::new_zero()))
507                    }
508                }
509                PercentageChannelDimensionality::D2 => {
510                    if *is_signed {
511                        Ok(WrappedIOData::SignedPercentage_2D(
512                            SignedPercentage2D::new_zero(),
513                        ))
514                    } else {
515                        Ok(WrappedIOData::Percentage_2D(Percentage2D::new_zero()))
516                    }
517                }
518                PercentageChannelDimensionality::D3 => {
519                    if *is_signed {
520                        Ok(WrappedIOData::SignedPercentage_3D(
521                            SignedPercentage3D::new_zero(),
522                        ))
523                    } else {
524                        Ok(WrappedIOData::Percentage_3D(Percentage3D::new_zero()))
525                    }
526                }
527                PercentageChannelDimensionality::D4 => {
528                    if *is_signed {
529                        Ok(WrappedIOData::SignedPercentage_4D(
530                            SignedPercentage4D::new_zero(),
531                        ))
532                    } else {
533                        Ok(WrappedIOData::Percentage_4D(Percentage4D::new_zero()))
534                    }
535                }
536            },
537            JSONDecoderProperties::GazeProperties(
538                _eccentricity,
539                _modularity,
540                _percentage_neuron_positioning,
541            ) => Ok(WrappedIOData::GazeProperties(
542                GazeProperties::create_default_centered(),
543            )),
544            JSONDecoderProperties::PositionalServo(
545                _neuron_depth,
546                _percentage_neuron_positioning,
547            ) => Ok(WrappedIOData::Percentage(Percentage::new_zero())),
548            JSONDecoderProperties::ImageFilteringSettings(
549                _brightness,
550                _contrast,
551                _diff,
552                _percentage_neuron_positioning,
553            ) => Ok(WrappedIOData::ImageFilteringSettings(
554                ImageFilteringSettings::default(),
555            )),
556        }
557    }
558}
559
560//endregion
561
562/// Custom Metadata to allow defining hardware properties per channel
563//region Device Properties
564/// A Dictionary structure that allows developers to tag custom information to
565/// device groupings (channels).
566pub type JSONDeviceProperties = HashMap<String, JSONDevicePropertyValue>;
567
568/// User defined key for custom properties per channel, which can be useful in describing hardware
569
570#[derive(Serialize, Deserialize)]
571#[serde(tag = "type", content = "value")]
572#[derive(Debug, Clone, PartialEq)]
573pub enum JSONDevicePropertyValue {
574    String(String),
575    Integer(i32),
576    Float(f32),
577    Dictionary(JSONDeviceProperties),
578}
579
580//endregion