1use crate::types::{BduError, BduResult, Position};
14use feagi_structures::genomic::cortical_area::descriptors::CorticalAreaDimensions as Dimensions;
15
16#[cfg(feature = "parallel")]
17use rayon::prelude::*;
18
19#[derive(Debug, Clone, Default)]
21pub struct ProjectorParams {
22 pub transpose: Option<(usize, usize, usize)>,
24 pub project_last_layer_of: Option<usize>,
26}
27
28#[allow(clippy::too_many_arguments)]
55pub fn syn_projector(
56 _src_area_id: &str,
57 _dst_area_id: &str,
58 _src_neuron_id: u64,
59 src_dimensions: (usize, usize, usize),
60 dst_dimensions: (usize, usize, usize),
61 neuron_location: Position,
62 transpose: Option<(usize, usize, usize)>,
63 project_last_layer_of: Option<usize>,
64) -> BduResult<Vec<Position>> {
65 let src_dims = Dimensions::from_tuple((
67 src_dimensions.0 as u32,
68 src_dimensions.1 as u32,
69 src_dimensions.2 as u32,
70 ))?;
71 let dst_dims = Dimensions::from_tuple((
72 dst_dimensions.0 as u32,
73 dst_dimensions.1 as u32,
74 dst_dimensions.2 as u32,
75 ))?;
76
77 if !src_dims.contains(neuron_location) {
79 return Err(BduError::OutOfBounds {
80 pos: neuron_location,
81 dims: src_dimensions,
82 });
83 }
84
85 let (src_shape, dst_shape, location) = if let Some((tx, ty, tz)) = transpose {
87 apply_transpose(src_dims, dst_dims, neuron_location, (tx, ty, tz))
88 } else {
89 (
90 [
91 src_dims.width as usize,
92 src_dims.height as usize,
93 src_dims.depth as usize,
94 ],
95 [
96 dst_dims.width as usize,
97 dst_dims.height as usize,
98 dst_dims.depth as usize,
99 ],
100 [
101 neuron_location.0 as usize,
102 neuron_location.1 as usize,
103 neuron_location.2 as usize,
104 ],
105 )
106 };
107
108 let mut dst_voxels: [Vec<u32>; 3] = [Vec::new(), Vec::new(), Vec::new()];
110
111 for axis in 0..3 {
112 dst_voxels[axis] = calculate_axis_projection(
113 location[axis] as u32,
114 src_shape[axis],
115 dst_shape[axis],
116 project_last_layer_of == Some(axis),
117 )?;
118 }
119
120 if dst_voxels[0].is_empty() || dst_voxels[1].is_empty() || dst_voxels[2].is_empty() {
122 return Ok(Vec::new());
123 }
124
125 let total_combinations = dst_voxels[0].len() * dst_voxels[1].len() * dst_voxels[2].len();
128 let mut candidate_positions = Vec::with_capacity(total_combinations);
129
130 for &x in &dst_voxels[0] {
132 for &y in &dst_voxels[1] {
133 for &z in &dst_voxels[2] {
134 if x < dst_dims.width && y < dst_dims.height && z < dst_dims.depth {
136 candidate_positions.push((x, y, z));
137 }
138 }
139 }
140 }
141
142 Ok(candidate_positions)
143}
144
145fn calculate_axis_projection(
152 location: u32,
153 src_size: usize,
154 dst_size: usize,
155 force_first_layer: bool,
156) -> BduResult<Vec<u32>> {
157 let mut voxels = Vec::new();
158
159 if force_first_layer {
160 voxels.push(0);
162 return Ok(voxels);
163 }
164
165 if src_size > dst_size {
166 let ratio = src_size as f32 / dst_size as f32;
168 let target = (location as f32 / ratio) as u32;
169 if (target as usize) < dst_size {
170 voxels.push(target);
171 }
172 } else if src_size < dst_size {
173 let ratio = dst_size as f32 / src_size as f32;
176
177 for dst_vox in 0..dst_size {
178 let src_vox = (dst_vox as f32 / ratio) as u32;
179 if src_vox == location {
180 voxels.push(dst_vox as u32);
181 }
182 }
183 } else {
184 if (location as usize) < dst_size {
186 voxels.push(location);
187 }
188 }
189
190 Ok(voxels)
191}
192
193fn apply_transpose(
195 src_dims: Dimensions,
196 dst_dims: Dimensions,
197 location: Position,
198 transpose: (usize, usize, usize),
199) -> ([usize; 3], [usize; 3], [usize; 3]) {
200 let src_arr = [
201 src_dims.width as usize,
202 src_dims.height as usize,
203 src_dims.depth as usize,
204 ];
205 let dst_arr = [
206 dst_dims.width as usize,
207 dst_dims.height as usize,
208 dst_dims.depth as usize,
209 ];
210 let loc_arr = [location.0, location.1, location.2];
211
212 let src_transposed = [
213 src_arr[transpose.0],
214 src_arr[transpose.1],
215 src_arr[transpose.2],
216 ];
217 let dst_transposed = [
218 dst_arr[transpose.0],
219 dst_arr[transpose.1],
220 dst_arr[transpose.2],
221 ];
222 let loc_transposed = [
223 loc_arr[transpose.0] as usize,
224 loc_arr[transpose.1] as usize,
225 loc_arr[transpose.2] as usize,
226 ];
227
228 (src_transposed, dst_transposed, loc_transposed)
229}
230
231#[allow(clippy::too_many_arguments)]
235pub fn syn_projector_batch(
236 src_area_id: &str,
237 dst_area_id: &str,
238 neuron_ids: &[u64],
239 neuron_locations: &[Position],
240 src_dimensions: (usize, usize, usize),
241 dst_dimensions: (usize, usize, usize),
242 transpose: Option<(usize, usize, usize)>,
243 project_last_layer_of: Option<usize>,
244) -> BduResult<Vec<Vec<Position>>> {
245 if neuron_ids.len() != neuron_locations.len() {
247 return Err(BduError::Internal(format!(
248 "Neuron ID count {} doesn't match location count {}",
249 neuron_ids.len(),
250 neuron_locations.len()
251 )));
252 }
253
254 #[cfg(feature = "parallel")]
256 let results: Vec<BduResult<Vec<Position>>> = neuron_ids
257 .par_iter()
258 .zip(neuron_locations.par_iter())
259 .map(|(id, loc)| {
260 syn_projector(
261 src_area_id,
262 dst_area_id,
263 *id,
264 src_dimensions,
265 dst_dimensions,
266 *loc,
267 transpose,
268 project_last_layer_of,
269 )
270 })
271 .collect();
272
273 #[cfg(not(feature = "parallel"))]
274 let results: Vec<BduResult<Vec<Position>>> = neuron_ids
275 .iter()
276 .zip(neuron_locations.iter())
277 .map(|(id, loc)| {
278 syn_projector(
279 src_area_id,
280 dst_area_id,
281 *id,
282 src_dimensions,
283 dst_dimensions,
284 *loc,
285 transpose,
286 project_last_layer_of,
287 )
288 })
289 .collect();
290
291 results.into_iter().collect()
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn test_projection_128x128x3_to_128x128x1() {
301 let result = syn_projector(
303 "src",
304 "dst",
305 0,
306 (128, 128, 3),
307 (128, 128, 1),
308 (64, 64, 1),
309 None,
310 None,
311 );
312
313 assert!(result.is_ok());
314 let positions = result.unwrap();
315
316 assert!(!positions.is_empty());
318
319 for pos in &positions {
321 assert!(pos.0 < 128);
322 assert!(pos.1 < 128);
323 assert!(pos.2 < 1);
324 }
325 }
326
327 #[test]
328 fn test_scale_down() {
329 let result = calculate_axis_projection(64, 256, 128, false);
331 assert!(result.is_ok());
332 let voxels = result.unwrap();
333 assert_eq!(voxels.len(), 1);
334 assert_eq!(voxels[0], 32); }
336
337 #[test]
338 fn test_scale_up() {
339 let result = calculate_axis_projection(64, 128, 256, false);
341 assert!(result.is_ok());
342 let voxels = result.unwrap();
343 assert_eq!(voxels.len(), 2); }
345
346 #[test]
347 fn test_same_size() {
348 let result = calculate_axis_projection(64, 128, 128, false);
350 assert!(result.is_ok());
351 let voxels = result.unwrap();
352 assert_eq!(voxels.len(), 1);
353 assert_eq!(voxels[0], 64);
354 }
355
356 #[test]
357 fn test_force_first_layer() {
358 let result = calculate_axis_projection(99, 128, 20, true);
360 assert!(result.is_ok());
361 let voxels = result.unwrap();
362 assert_eq!(voxels.len(), 1);
363 assert_eq!(voxels[0], 0);
364 }
365
366 #[test]
367 fn test_out_of_bounds() {
368 let result = syn_projector(
369 "src",
370 "dst",
371 0,
372 (128, 128, 3),
373 (128, 128, 1),
374 (200, 0, 0), None,
376 None,
377 );
378 assert!(result.is_err());
379 }
380}