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}