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