Skip to main content

feagi_structures/genomic/brain_regions/
mod.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5BrainRegion data model.
6
7Represents a hierarchical grouping of cortical areas with functional significance.
8Moved from feagi-core/crates/feagi-bdu/src/models/brain_region.rs
9*/
10
11mod region_id;
12pub use region_id::RegionID;
13
14use crate::genomic::cortical_area::CorticalID;
15use crate::FeagiDataError;
16use serde::{Deserialize, Serialize};
17use std::collections::{HashMap, HashSet};
18
19/// Type of brain region (placeholder for future functional/anatomical classification)
20///
21/// Currently, no specific region types are defined. This enum serves as a placeholder
22/// for future extensions when functional or anatomical classification is implemented.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
24#[serde(rename_all = "lowercase")]
25#[derive(Default)]
26pub enum RegionType {
27    /// Generic/undefined region type (placeholder)
28    #[default]
29    Undefined,
30}
31
32impl std::fmt::Display for RegionType {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        write!(f, "undefined")
35    }
36}
37
38/// Brain region metadata (genome representation)
39///
40/// A brain region is a hierarchical grouping of cortical areas that share
41/// functional or anatomical characteristics. Regions form a tree structure
42/// where each region can contain multiple cortical areas and sub-regions.
43///
44/// # Design Notes
45///
46/// - Regions are organizational constructs (not physical entities)
47/// - Used for genome editing, visualization, and bulk operations
48/// - Serializable for genome persistence
49/// - Properties stored as HashMap for maximum flexibility
50///
51#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct BrainRegion {
53    /// Unique identifier for this region
54    pub region_id: RegionID,
55
56    /// Human-readable name (mapped to "title" in genome JSON)
57    pub name: String,
58
59    /// Functional/anatomical type
60    pub region_type: RegionType,
61
62    /// Set of cortical area IDs contained in this region
63    #[serde(default)]
64    pub cortical_areas: HashSet<CorticalID>,
65
66    /// Additional user-defined properties
67    /// Commonly used keys: description, coordinate_2d, coordinate_3d, inputs, outputs, signature
68    #[serde(default)]
69    pub properties: HashMap<String, serde_json::Value>,
70}
71
72impl BrainRegion {
73    /// Create a new brain region
74    ///
75    /// # Arguments
76    ///
77    /// * `region_id` - Unique identifier (validated RegionID)
78    /// * `name` - Human-readable name
79    /// * `region_type` - Functional type
80    ///
81    /// # Errors
82    ///
83    /// Returns error if name is empty
84    ///
85    pub fn new(
86        region_id: RegionID,
87        name: String,
88        region_type: RegionType,
89    ) -> Result<Self, FeagiDataError> {
90        if name.trim().is_empty() {
91            return Err(FeagiDataError::BadParameters(
92                "name cannot be empty".to_string(),
93            ));
94        }
95
96        Ok(Self {
97            region_id,
98            name,
99            region_type,
100            cortical_areas: HashSet::new(),
101            properties: HashMap::new(),
102        })
103    }
104
105    /// Create a region with initial cortical areas
106    pub fn with_areas(mut self, areas: impl IntoIterator<Item = CorticalID>) -> Self {
107        self.cortical_areas.extend(areas);
108        self
109    }
110
111    /// Create a region with custom properties
112    pub fn with_properties(mut self, properties: HashMap<String, serde_json::Value>) -> Self {
113        self.properties = properties;
114        self
115    }
116
117    /// Add a cortical area to this region
118    ///
119    /// Returns `true` if the area was newly added, `false` if it was already present
120    ///
121    pub fn add_area(&mut self, area_id: CorticalID) -> bool {
122        self.cortical_areas.insert(area_id)
123    }
124
125    /// Remove a cortical area from this region
126    ///
127    /// Returns `true` if the area was present and removed, `false` if it wasn't present
128    ///
129    pub fn remove_area(&mut self, area_id: &CorticalID) -> bool {
130        self.cortical_areas.remove(area_id)
131    }
132
133    /// Check if this region contains a specific cortical area
134    pub fn contains_area(&self, area_id: &CorticalID) -> bool {
135        self.cortical_areas.contains(area_id)
136    }
137
138    /// Get all cortical area IDs in this region
139    pub fn get_all_areas(&self) -> Vec<&CorticalID> {
140        self.cortical_areas.iter().collect()
141    }
142
143    /// Get the number of cortical areas in this region
144    pub fn area_count(&self) -> usize {
145        self.cortical_areas.len()
146    }
147
148    /// Clear all cortical areas from this region
149    pub fn clear_areas(&mut self) {
150        self.cortical_areas.clear();
151    }
152
153    /// Get a property value by key
154    pub fn get_property(&self, key: &str) -> Option<&serde_json::Value> {
155        self.properties.get(key)
156    }
157
158    /// Add a property to the region
159    pub fn add_property(&mut self, key: String, value: serde_json::Value) {
160        self.properties.insert(key, value);
161    }
162
163    /// Convert to dictionary representation (for serialization)
164    pub fn to_dict(&self) -> serde_json::Value {
165        // Convert CorticalIDs to their base64 string representation for JSON
166        let area_ids: Vec<String> = self
167            .cortical_areas
168            .iter()
169            .map(|id| id.as_base_64())
170            .collect();
171
172        let mut dict = serde_json::json!({
173            "id": self.region_id.to_string(),
174            "name": self.name,
175            "region_type": self.region_type.to_string(),
176            "cortical_areas": area_ids,
177        });
178
179        // Add all properties from the HashMap
180        for (key, value) in &self.properties {
181            dict[key] = value.clone();
182        }
183
184        dict
185    }
186}