use anyhow::{Context, Result};
pub fn resample_surface(
data: &[f32],
from_mesh: &str,
to_mesh: &str,
subjects_dir: Option<&str>,
k: usize,
) -> Result<Vec<f32>> {
let from_size = crate::fsaverage::fsaverage_size(from_mesh)
.ok_or_else(|| anyhow::anyhow!("Unknown mesh: {}", from_mesh))?;
let to_size = crate::fsaverage::fsaverage_size(to_mesh)
.ok_or_else(|| anyhow::anyhow!("Unknown mesh: {}", to_mesh))?;
if data.len() != 2 * from_size {
anyhow::bail!(
"Data length {} doesn't match {} (expected {})",
data.len(), from_mesh, 2 * from_size
);
}
if from_mesh == to_mesh {
return Ok(data.to_vec());
}
let from_brain = crate::fsaverage::load_fsaverage(from_mesh, "pial", "sulcal", subjects_dir)
.with_context(|| format!("Failed to load {} mesh", from_mesh))?;
let to_brain = crate::fsaverage::load_fsaverage(to_mesh, "pial", "sulcal", subjects_dir)
.with_context(|| format!("Failed to load {} mesh", to_mesh))?;
let mut result = Vec::with_capacity(2 * to_size);
for (hemi_idx, (from_hemi, to_hemi)) in [
(&from_brain.left, &to_brain.left),
(&from_brain.right, &to_brain.right),
].iter().enumerate() {
let data_offset = hemi_idx * from_size;
let hemi_data = &data[data_offset..data_offset + from_size];
let from_coords = &from_hemi.mesh.coords;
let to_coords = &to_hemi.mesh.coords;
let resampled = resample_hemisphere(
hemi_data,
from_coords,
from_hemi.mesh.n_vertices,
to_coords,
to_hemi.mesh.n_vertices,
k,
);
result.extend_from_slice(&resampled);
}
Ok(result)
}
fn resample_hemisphere(
data: &[f32],
from_coords: &[f32], n_from: usize,
to_coords: &[f32], n_to: usize,
k: usize,
) -> Vec<f32> {
let mut result = Vec::with_capacity(n_to);
for ti in 0..n_to {
let tx = to_coords[ti * 3];
let ty = to_coords[ti * 3 + 1];
let tz = to_coords[ti * 3 + 2];
let mut dists: Vec<(usize, f32)> = (0..n_from)
.map(|fi| {
let dx = from_coords[fi * 3] - tx;
let dy = from_coords[fi * 3 + 1] - ty;
let dz = from_coords[fi * 3 + 2] - tz;
(fi, dx * dx + dy * dy + dz * dz)
})
.collect();
dists.select_nth_unstable_by(k.min(n_from) - 1, |a, b| {
a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)
});
let nearest = &dists[..k.min(n_from)];
let mut weight_sum = 0.0f32;
let mut value_sum = 0.0f32;
for &(fi, dist_sq) in nearest {
if fi < data.len() {
let dist = dist_sq.sqrt().max(1e-12);
let w = 1.0 / dist;
weight_sum += w;
value_sum += w * data[fi];
}
}
result.push(if weight_sum > 0.0 { value_sum / weight_sum } else { 0.0 });
}
result
}
pub struct ResamplingMap {
pub mappings: Vec<Vec<(usize, f32)>>,
pub from_mesh: String,
pub to_mesh: String,
}
impl ResamplingMap {
pub fn apply(&self, data: &[f32]) -> Vec<f32> {
self.mappings.iter().map(|neighbors| {
let mut weight_sum = 0.0f32;
let mut value_sum = 0.0f32;
for &(idx, w) in neighbors {
if idx < data.len() {
weight_sum += w;
value_sum += w * data[idx];
}
}
if weight_sum > 0.0 { value_sum / weight_sum } else { 0.0 }
}).collect()
}
}
pub fn compute_resampling_map(
from_mesh: &str,
to_mesh: &str,
subjects_dir: Option<&str>,
k: usize,
) -> Result<ResamplingMap> {
let from_size = crate::fsaverage::fsaverage_size(from_mesh)
.ok_or_else(|| anyhow::anyhow!("Unknown mesh: {}", from_mesh))?;
let to_size = crate::fsaverage::fsaverage_size(to_mesh)
.ok_or_else(|| anyhow::anyhow!("Unknown mesh: {}", to_mesh))?;
let from_brain = crate::fsaverage::load_fsaverage(from_mesh, "pial", "sulcal", subjects_dir)?;
let to_brain = crate::fsaverage::load_fsaverage(to_mesh, "pial", "sulcal", subjects_dir)?;
let mut mappings = Vec::with_capacity(2 * to_size);
for (hemi_idx, (from_hemi, to_hemi)) in [
(&from_brain.left, &to_brain.left),
(&from_brain.right, &to_brain.right),
].iter().enumerate() {
let offset = hemi_idx * from_size;
let from_coords = &from_hemi.mesh.coords;
let to_coords = &to_hemi.mesh.coords;
for ti in 0..to_hemi.mesh.n_vertices {
let tx = to_coords[ti * 3];
let ty = to_coords[ti * 3 + 1];
let tz = to_coords[ti * 3 + 2];
let mut dists: Vec<(usize, f32)> = (0..from_hemi.mesh.n_vertices)
.map(|fi| {
let dx = from_coords[fi * 3] - tx;
let dy = from_coords[fi * 3 + 1] - ty;
let dz = from_coords[fi * 3 + 2] - tz;
(fi + offset, dx * dx + dy * dy + dz * dz)
})
.collect();
let kk = k.min(from_hemi.mesh.n_vertices);
dists.select_nth_unstable_by(kk - 1, |a, b| {
a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal)
});
let neighbors: Vec<(usize, f32)> = dists[..kk]
.iter()
.map(|&(idx, dist_sq)| {
let w = 1.0 / dist_sq.sqrt().max(1e-12);
(idx, w)
})
.collect();
mappings.push(neighbors);
}
}
Ok(ResamplingMap {
mappings,
from_mesh: from_mesh.to_string(),
to_mesh: to_mesh.to_string(),
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_resample_identity() {
let data = vec![1.0f32; 10];
let resampled = resample_hemisphere(&data, &[0.0; 30], 10, &[0.0; 30], 10, 3);
assert_eq!(resampled.len(), 10);
}
#[test]
fn test_resample_simple() {
let data = vec![1.0, 2.0, 3.0];
let from_coords = vec![0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 2.0, 0.0, 0.0];
let to_coords = vec![0.5, 0.0, 0.0, 1.5, 0.0, 0.0];
let result = resample_hemisphere(&data, &from_coords, 3, &to_coords, 2, 2);
assert_eq!(result.len(), 2);
assert!((result[0] - 1.5).abs() < 0.1, "got {}", result[0]);
assert!((result[1] - 2.5).abs() < 0.1, "got {}", result[1]);
}
#[test]
fn test_resampling_map_apply() {
let map = ResamplingMap {
mappings: vec![
vec![(0, 1.0), (1, 1.0)], vec![(2, 1.0)], ],
from_mesh: "test".to_string(),
to_mesh: "test".to_string(),
};
let data = vec![2.0, 4.0, 6.0];
let result = map.apply(&data);
assert_eq!(result.len(), 2);
assert!((result[0] - 3.0).abs() < 1e-5); assert!((result[1] - 6.0).abs() < 1e-5);
}
}