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, 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)]
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)]
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, 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, Serialize, Deserialize)]
356pub enum JSONDecoderProperties {
357    CartesianPlane(ImageFrameProperties),
358    MiscData(MiscDataDimensions),
359    Percentage(
360        NeuronDepth,
361        PercentageNeuronPositioning,
362        bool,
363        PercentageChannelDimensionality,
364    ),
365    GazeProperties(NeuronDepth, NeuronDepth, PercentageNeuronPositioning), // eccentricity z depth, modularity z depth
366    ImageFilteringSettings(
367        NeuronDepth,
368        NeuronDepth,
369        NeuronDepth,
370        PercentageNeuronPositioning,
371    ), // brightness z depth, contrast z depth, diff z depth
372}
373
374impl JSONDecoderProperties {
375    pub fn to_box_decoder(
376        &self,
377        number_channels: CorticalChannelCount,
378        cortical_ids: &[CorticalID],
379    ) -> Result<Box<dyn NeuronVoxelXYZPDecoder + Sync + Send>, FeagiDataError> {
380        match self {
381            JSONDecoderProperties::CartesianPlane(image_frame_properties) => {
382                if cortical_ids.len() != 1 {
383                    return Err(FeagiDataError::InternalError(
384                        "Expected one cortical id!".to_string(),
385                    ));
386                }
387                crate::neuron_voxel_coding::xyzp::decoders::CartesianPlaneNeuronVoxelXYZPDecoder::new_box(
388                    *cortical_ids.first().unwrap(),
389                    image_frame_properties,
390                    number_channels,
391                )
392            }
393            JSONDecoderProperties::MiscData(misc_data_dimensions) => {
394                if cortical_ids.len() != 1 {
395                    return Err(FeagiDataError::InternalError(
396                        "Expected one cortical id!".to_string(),
397                    ));
398                }
399                MiscDataNeuronVoxelXYZPDecoder::new_box(
400                    *cortical_ids.first().unwrap(), // Eccentricity
401                    *misc_data_dimensions,
402                    number_channels,
403                )
404            }
405            JSONDecoderProperties::Percentage(
406                neuron_depth,
407                percentage_neuron_positioning,
408                is_signed,
409                dimension_count,
410            ) => {
411                if cortical_ids.len() != 1 {
412                    return Err(FeagiDataError::InternalError(
413                        "Expected one cortical id!".to_string(),
414                    ));
415                }
416                PercentageNeuronVoxelXYZPDecoder::new_box(
417                    *cortical_ids.first().unwrap(), // Eccentricity
418                    *neuron_depth,
419                    number_channels,
420                    *percentage_neuron_positioning,
421                    *is_signed,
422                    *dimension_count,
423                )
424            }
425            JSONDecoderProperties::GazeProperties(
426                eccentricity_neuron_depth,
427                modularity_neuron_depth,
428                percentage_neuron_positioning,
429            ) => {
430                if cortical_ids.len() != 2 {
431                    return Err(FeagiDataError::InternalError(
432                        "Expected two cortical ids!".to_string(),
433                    ));
434                }
435                GazePropertiesNeuronVoxelXYZPDecoder::new_box(
436                    *cortical_ids.first().unwrap(), // Eccentricity
437                    *cortical_ids.get(1).unwrap(),  // Modularity
438                    *eccentricity_neuron_depth,
439                    *modularity_neuron_depth,
440                    number_channels,
441                    *percentage_neuron_positioning,
442                )
443            }
444            JSONDecoderProperties::ImageFilteringSettings(
445                brightness_z_depth,
446                contrast_z_depth,
447                diff_z_depth,
448                percentage_neuron_positioning,
449            ) => {
450                if cortical_ids.len() != 3 {
451                    return Err(FeagiDataError::InternalError(
452                        "Expected three cortical ids for ImageFilteringSettings!".to_string(),
453                    ));
454                }
455                ImageFilteringSettingsNeuronVoxelXYZPDecoder::new_box(
456                    *cortical_ids.first().unwrap(), // Brightness
457                    *cortical_ids.get(1).unwrap(),  // Contrast
458                    *cortical_ids.get(2).unwrap(),  // Diff threshold
459                    *brightness_z_depth,
460                    *contrast_z_depth,
461                    *diff_z_depth,
462                    number_channels,
463                    *percentage_neuron_positioning,
464                )
465            }
466        }
467    }
468
469    pub fn default_wrapped_value(&self) -> Result<WrappedIOData, FeagiDataError> {
470        match self {
471            JSONDecoderProperties::CartesianPlane(image_frame_properties) => {
472                Ok(WrappedIOData::ImageFrame(
473                    ImageFrame::new_from_image_frame_properties(image_frame_properties)?,
474                ))
475            }
476            JSONDecoderProperties::MiscData(misc_data_dimensions) => Ok(WrappedIOData::MiscData(
477                MiscData::new(misc_data_dimensions)?,
478            )),
479            JSONDecoderProperties::Percentage(
480                _neuron_depth,
481                _percentage_neuron_positioning,
482                is_signed,
483                number_dimensions,
484            ) => match number_dimensions {
485                PercentageChannelDimensionality::D1 => {
486                    if *is_signed {
487                        Ok(WrappedIOData::SignedPercentage(
488                            SignedPercentage::new_from_m1_1_unchecked(0.0),
489                        ))
490                    } else {
491                        Ok(WrappedIOData::Percentage(Percentage::new_zero()))
492                    }
493                }
494                PercentageChannelDimensionality::D2 => {
495                    if *is_signed {
496                        Ok(WrappedIOData::SignedPercentage_2D(
497                            SignedPercentage2D::new_zero(),
498                        ))
499                    } else {
500                        Ok(WrappedIOData::Percentage_2D(Percentage2D::new_zero()))
501                    }
502                }
503                PercentageChannelDimensionality::D3 => {
504                    if *is_signed {
505                        Ok(WrappedIOData::SignedPercentage_3D(
506                            SignedPercentage3D::new_zero(),
507                        ))
508                    } else {
509                        Ok(WrappedIOData::Percentage_3D(Percentage3D::new_zero()))
510                    }
511                }
512                PercentageChannelDimensionality::D4 => {
513                    if *is_signed {
514                        Ok(WrappedIOData::SignedPercentage_4D(
515                            SignedPercentage4D::new_zero(),
516                        ))
517                    } else {
518                        Ok(WrappedIOData::Percentage_4D(Percentage4D::new_zero()))
519                    }
520                }
521            },
522            JSONDecoderProperties::GazeProperties(
523                _eccentricity,
524                _modularity,
525                _percentage_neuron_positioning,
526            ) => Ok(WrappedIOData::GazeProperties(
527                GazeProperties::create_default_centered(),
528            )),
529            JSONDecoderProperties::ImageFilteringSettings(
530                _brightness,
531                _contrast,
532                _diff,
533                _percentage_neuron_positioning,
534            ) => Ok(WrappedIOData::ImageFilteringSettings(
535                ImageFilteringSettings::default(),
536            )),
537        }
538    }
539}
540
541//endregion
542
543/// Custom Metadata to allow defining hardware properties per channel
544//region Device Properties
545/// A Dictionary structure that allows developers to tag custom information to
546/// device groupings (channels).
547pub type JSONDeviceProperties = HashMap<String, JSONDevicePropertyValue>;
548
549/// User defined key for custom properties per channel, which can be useful in describing hardware
550
551#[derive(Serialize, Deserialize)]
552#[serde(tag = "type", content = "value")]
553#[derive(Debug, Clone)]
554pub enum JSONDevicePropertyValue {
555    String(String),
556    Integer(i32),
557    Float(f32),
558    Dictionary(JSONDeviceProperties),
559}
560
561//endregion