From 7c3968d2dce2cb714a27c2bce4eacecd0ab34aec Mon Sep 17 00:00:00 2001 From: Ruben van de Ven Date: Wed, 2 Jul 2025 11:49:49 +0200 Subject: [PATCH] Implement pillow distortion correction --- src/bin/render_lines_gui.rs | 26 ++++++++++++++-- src/trap/filters.rs | 61 +++++++++++++++++-------------------- src/trap/laser.rs | 4 +-- 3 files changed, 54 insertions(+), 37 deletions(-) diff --git a/src/bin/render_lines_gui.rs b/src/bin/render_lines_gui.rs index 4ae155b..a06f703 100644 --- a/src/bin/render_lines_gui.rs +++ b/src/bin/render_lines_gui.rs @@ -727,8 +727,10 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { } + // Pincushion / Pillow / Barrel distortion. Generally, only needed for the x-axis + if ui - .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, 0.0..=2.).text("Pincushion x")) + .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, -0.5..=0.5).text("Pincushion x")) .changed() { let factor = stream_config.config.filters.pincushion.k_x; @@ -738,7 +740,17 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { } if ui - .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, 0.0..=2.).text("Pincushion y")) + .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x2, -0.2..=0.2).text("Higher order pincushion x")) + .changed() + { + let factor = stream_config.config.filters.pincushion.k_x2; + stream_config.stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.pincushion.k_x2 = factor; + }).unwrap(); + } + + if ui + .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, -0.5..=0.5).text("Pincushion y")) .changed() { let factor = stream_config.config.filters.pincushion.k_y; @@ -747,6 +759,16 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) { }).unwrap(); } + if ui + .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y2, -0.2..=0.2).text("Higher order pincushion y")) + .changed() + { + let factor = stream_config.config.filters.pincushion.k_y2; + stream_config.stream.send(move |laser_model: &mut LaserModel| { + laser_model.config.filters.pincushion.k_y2 = factor; + }).unwrap(); + } + if ui .checkbox(&mut stream_config.config.filters.crop.enabled ,"Crop") diff --git a/src/trap/filters.rs b/src/trap/filters.rs index 313f3d5..da39e39 100644 --- a/src/trap/filters.rs +++ b/src/trap/filters.rs @@ -34,7 +34,9 @@ pub struct ScaleFilter { #[derive(Serialize, Deserialize, Clone, Debug)] pub struct PincushionFilter { pub k_x: f32, - pub k_y: f32 + pub k_x2: f32, + pub k_y: f32, + pub k_y2: f32, } // #[derive(Serialize, Deserialize, Clone, Debug)] @@ -95,7 +97,7 @@ impl Default for PointFilters { homography: HomographyFilter::default(), dim: DimFilter{intensity: 0.5}, scale: ScaleFilter { factor: 1. }, - pincushion: PincushionFilter{k_x: 1., k_y: 1.}, + pincushion: PincushionFilter{k_x: 0.,k_x2: 0., k_y: 0., k_y2: 0.}, crop: CropFilter{ enabled: true }, } } @@ -316,42 +318,35 @@ impl Filter for PincushionFilter { // formula by Ilya Sinenko: https://hackernoon.com/galvo-scanner-pillow-and-barrel-distortions-correction - // x_angle = atan(x_cord / (sqrt(pow(d,2)+pow(y_cord,2))+e)) + // who states: x_angle = atan(x_cord / (sqrt(pow(d,2)+pow(y_cord,2))+e)) + // let x = (p[0] / (p[1].abs() + self.k_x )).atan(); + // let new_position = [x, p[1]]; - let x = (p[0] / (p[1].abs() + self.k_x )).atan(); - return [x, p[1]]; + // Adapted from https://www.geeks3d.com/20140213/glsl-shader-library-fish-eye-and-dome-and-barrel-distortion-post-processing-filters/2/ + // let mut radius = (p[0].powi(2) + p[1].powi(2)).sqrt(); + // let new_position = if radius > 0. { + // let theta = p[1].atan2(p[0]); + // let radius_x = radius.powf(self.k_x); + // let radius_y = radius.powf(self.k_y); + // let x = radius_x * theta.cos(); + // let y = radius_y * theta.sin(); + // [x, y] + // } else { + // p + // }; - let mut radius = (p[0].powi(2) + p[1].powi(2)).sqrt(); - let new_position = if radius > 0. { - let theta = p[1].atan2(p[0]); - let radius_x = radius.powf(self.k_x); - let radius_y = radius.powf(self.k_y); - let x = radius_x * theta.cos(); - let y = radius_y * theta.sin(); - [x, y] - } else { - p - }; + // Apply Brown-Conrady model of distortion - // // Convert to polar coords: - // // float radius = length(v); - // if (radius > 0) - // { - // float theta = atan(v.y,v.x); - - // // Distort: - // radius = pow(radius, BarrelPower); + // https://en.wikipedia.org/wiki/Distortion_(optics)#Software_correction + // calculate radius (though we might be able to get away, just using p[1] and p[0] + // as we calculate axes independently) + let radius = (p[0].powi(2) + p[1].powi(2)).sqrt(); - // // Convert back to Cartesian: - // v.x = radius * cos(theta); - // v.y = radius * sin(theta); - // p.xy = v.xy * p.w; - // } - // let new_position = [ - // p[0] * (1. + self.k_x * p[0].powi(2)), - // p[1] * (1. + self.k_y * p[1].powi(2)) - // ]; + let new_position = [ + p[0] * (1. + self.k_x * radius.powi(2)+ self.k_x2 * radius.powi(4)), + p[1] * (1. + self.k_y * radius.powi(2)+ self.k_y2 * radius.powi(4)) + ]; laser::Point { position: new_position, diff --git a/src/trap/laser.rs b/src/trap/laser.rs index 1394a4c..33197d5 100644 --- a/src/trap/laser.rs +++ b/src/trap/laser.rs @@ -164,7 +164,7 @@ impl StreamSource{ color: [0., 0., 0.], weight: 0, }); - for j in (0..=0xFFF).step_by(0xFFF / 50) { + for j in (0..=0xFFF).step_by(0xFFF / 10) { points.push(laser::Point{ position:[i as f32, j as f32], color: [1.,1.,1.], @@ -180,7 +180,7 @@ impl StreamSource{ color: [0., 0., 0.], weight: 0, }); - for j in (0..=0xFFF).step_by(0xFFF / 50) { + for j in (0..=0xFFF).step_by(0xFFF / 10) { points.push(laser::Point{ position:[j as f32, i as f32], color: [1.,1.,1.],