Add M9-M14: UI redesign, plugins, macros, 3D viz, binary/segmentation ops, Metal GPU, scientific LUTs
M9: TailwindCSS + shadcn/ui frontend redesign
M10: Dynamic plugin system with shared library loading
M11: Macro recording, playback, and batch processing
M12: 3D volume visualization, marching cubes, STL/OBJ export, rivol:// protocol
M13: Additional GPU shaders, enhanced auto-threshold, merge channels
M14: Binary image processing (EDT, watershed, skeleton, connected components,
voronoi), segmentation & analysis (particles, colocalization, find maxima),
math/noise/filter/transform ops (60+ total), scientific LUTs (Fire, Ice,
Spectrum, Jet, Phase, HiLo + .lut file I/O), Apple Metal GPU optimization
(metal-only feature, Apple Silicon detection, lower dispatch threshold),
native Metal compute crate (ri-metal with MSL shaders via objc2-metal),
7 new WGSL GPU shaders (bilateral, variance, mean, math_ops, outline,
minmax_filter, affine transform). 25 crates, 162 tests, 75+ IPC commands.
Co-Authored-By: Claude Opus 4.6 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
1889715f3c
commit
0fd39d7fd3
@@ -0,0 +1,72 @@
|
||||
use ri_core::TypedBuffer;
|
||||
|
||||
/// Mean (box) filter: replace each pixel with the average of its neighborhood.
|
||||
pub fn mean_filter(buffer: &TypedBuffer, w: u32, h: u32, radius: u32) -> TypedBuffer {
|
||||
let w = w as usize;
|
||||
let h = h as usize;
|
||||
let r = radius as i32;
|
||||
|
||||
let input: Vec<f32> = match buffer {
|
||||
TypedBuffer::U8(d) => d.iter().map(|&v| v as f32).collect(),
|
||||
TypedBuffer::U16(d) => d.iter().map(|&v| v as f32).collect(),
|
||||
TypedBuffer::F32(d) => d.clone(),
|
||||
};
|
||||
|
||||
let mut output = vec![0.0f32; w * h];
|
||||
|
||||
for y in 0..h {
|
||||
for x in 0..w {
|
||||
let mut sum = 0.0f64;
|
||||
let mut count = 0u32;
|
||||
|
||||
for dy in -r..=r {
|
||||
for dx in -r..=r {
|
||||
let nx = x as i32 + dx;
|
||||
let ny = y as i32 + dy;
|
||||
if nx >= 0 && nx < w as i32 && ny >= 0 && ny < h as i32 {
|
||||
sum += input[ny as usize * w + nx as usize] as f64;
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
output[y * w + x] = (sum / count as f64) as f32;
|
||||
}
|
||||
}
|
||||
|
||||
match buffer {
|
||||
TypedBuffer::U8(_) => TypedBuffer::U8(
|
||||
output.iter().map(|&v| v.round().clamp(0.0, 255.0) as u8).collect(),
|
||||
),
|
||||
TypedBuffer::U16(_) => TypedBuffer::U16(
|
||||
output.iter().map(|&v| v.round().clamp(0.0, 65535.0) as u16).collect(),
|
||||
),
|
||||
TypedBuffer::F32(_) => TypedBuffer::F32(output),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn mean_filter_smooths() {
|
||||
// 5x5 image with a spike in center
|
||||
let mut data = vec![0u8; 25];
|
||||
data[12] = 100; // center
|
||||
let buf = TypedBuffer::U8(data);
|
||||
let result = mean_filter(&buf, 5, 5, 1);
|
||||
let s = result.as_u8_slice().unwrap();
|
||||
// Center should be reduced (averaged with 8 zero neighbors + itself)
|
||||
assert!(s[12] < 100);
|
||||
assert!(s[12] > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_image_unchanged() {
|
||||
let buf = TypedBuffer::U8(vec![50; 25]);
|
||||
let result = mean_filter(&buf, 5, 5, 1);
|
||||
let s = result.as_u8_slice().unwrap();
|
||||
assert!(s.iter().all(|&v| v == 50));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user