Test patterns, and glitchy pillow distortion

This commit is contained in:
Ruben van de Ven 2025-07-02 10:54:43 +02:00
parent 188420c4ce
commit b25fc93997
3 changed files with 268 additions and 39 deletions

View file

@ -12,7 +12,7 @@ use nannou_laser::DacId;
use nannou_laser::{self as laser}; use nannou_laser::{self as laser};
use serde_json::Result; use serde_json::Result;
use trap_rust::trap::filters::PointFilters; use trap_rust::trap::filters::PointFilters;
use trap_rust::trap::laser::{LaserPoints, TMP_DESK_CLUBMAX}; use trap_rust::trap::laser::{LaserPoints, StreamSource, STREAM_SOURCES, TMP_DESK_CLUBMAX};
use trap_rust::trap::tracks::CoordinateSpace; use trap_rust::trap::tracks::CoordinateSpace;
use trap_rust::trap::{laser::{python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{RenderableLines}}; use trap_rust::trap::{laser::{python_cv_h_into_mat3, LaserModel, TMP_PYTHON_LASER_H, DacConfig}, tracks::{RenderableLines}};
use zmq::Socket; use zmq::Socket;
@ -20,6 +20,8 @@ use std::sync::{mpsc, Arc};
use std::time::{Instant, Duration}; use std::time::{Instant, Duration};
use std::collections::HashMap; use std::collections::HashMap;
// use egui_dropdown::DropDownBox;
fn main() { fn main() {
nannou::app(model).update(update).run(); nannou::app(model).update(update).run();
@ -196,8 +198,7 @@ fn get_dac_configs() -> DacConfigMap{
DacId::Helios { id: 926298163 }, DacId::Helios { id: 926298163 },
DacConfig{ DacConfig{
name: "Helios#1".into(), name: "Helios#1".into(),
homography: python_cv_h_into_mat3(TMP_PYTHON_LASER_H), .. DacConfig::default()
filters: PointFilters::default(),
} }
); );
dac_configs.insert( dac_configs.insert(
@ -213,8 +214,7 @@ fn get_dac_configs() -> DacConfigMap{
}, },
DacConfig{ DacConfig{
name: "ED - 192.168.8.101".into(), name: "ED - 192.168.8.101".into(),
homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX), .. DacConfig::default()
filters: PointFilters::default(),
} }
); );
dac_configs.insert( dac_configs.insert(
@ -230,8 +230,8 @@ fn get_dac_configs() -> DacConfigMap{
}, },
DacConfig{ DacConfig{
name: "ED - 192.168.9.101".into(), name: "ED - 192.168.9.101".into(),
homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX), .. DacConfig::default()
filters: PointFilters::default(), // filters: PointFilters::default(),
} }
); );
dac_configs.insert( dac_configs.insert(
@ -247,8 +247,7 @@ fn get_dac_configs() -> DacConfigMap{
}, },
DacConfig{ DacConfig{
name: "Emulator".into(), name: "Emulator".into(),
homography: python_cv_h_into_mat3(TMP_DESK_CLUBMAX), .. DacConfig::default()
filters: PointFilters::default(),
} }
); );
dac_configs dac_configs
@ -357,8 +356,35 @@ fn model(app: &App) -> GuiModel {
fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){ fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){
let points: LaserPoints = (&model.current_lines).into(); let current_points: LaserPoints = (&model.current_lines).into();
// let points = LaserPoints { points: vec!(
// laser::Point{
// position:[ 9.4, 7.2],
// color: [1.,1.,0.],
// weight: 0,
// },
// laser::Point{
// position:[ 12.4, 7.2],
// color: [1.,1.,0.],
// weight: 0,
// },
// laser::Point{
// position:[ 12.4, 4.2],
// color: [1.,1.,0.],
// weight: 0,
// },
// laser::Point{
// position:[ 9.4, 4.2],
// color: [1.,1.,0.],
// weight: 0,
// },
// ), space: CoordinateSpace::World };
let space = &model.current_lines.space; let space = &model.current_lines.space;
// check which source should be used, and get points accordingly.
// potentially ignoring the points coming from the stream
let points = model.config.source.get_shape(current_points);
let pointno = points.points.len(); let pointno = points.points.len();
let new_points = model.config.filters.apply(&points); let new_points = model.config.filters.apply(&points);
@ -367,6 +393,11 @@ fn laser_frame_producer(model: &mut LaserModel, frame: &mut laser::Frame){
println!("Cropped Points {} (was: {})", new_laser_points.len(), pointno); println!("Cropped Points {} (was: {})", new_laser_points.len(), pointno);
} }
// on reconnect gives Unknown
// dbg!(&model.config);
// dbg!(&points.points[0]);
// dbg!(&new_laser_points[0]);
frame.add_lines(new_laser_points); frame.add_lines(new_laser_points);
return; return;
} }
@ -428,9 +459,17 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
// TODO: keeps looping on disconnect. // TODO: keeps looping on disconnect.
println!("attempting to restart stream with DAC {:?}", dac.id()); println!("attempting to restart stream with DAC {:?}", dac.id());
let dac_id = dac.id(); let dac_id = dac.id();
let config = match model.per_laser_config.contains_key(&dac.id()) {
true => &model.per_laser_config[&dac.id()],
false => {
println!("Found unknown DAC, try to register it in get_dac_configs()");
dbg!(&dac.id());
&DacConfig::default()
},
};
match model match model
.laser_api .laser_api
.new_frame_stream(model.laser_model.clone(), laser_frame_producer) .new_frame_stream(model.laser_model.with_config(config), laser_frame_producer)
.detected_dac(dac) .detected_dac(dac)
.build() .build()
{ {
@ -600,6 +639,73 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
let stream_config: &mut StreamConfig = laser_streams.get_mut(&selected_stream_value).expect("Selected stream not found in configs"); let stream_config: &mut StreamConfig = laser_streams.get_mut(&selected_stream_value).expect("Selected stream not found in configs");
let source = &mut stream_config.config.source;
// for source_option in STREAM_SOURCES {
// if ui.radio_value(source, source_option.clone(), format!("{:?}", &source_option)).changed() {
// println!("Clicked!")
// };
// }
egui::ComboBox::from_label("Source")
.selected_text(format!("{source:?}"))
.show_ui(ui, |ui| {
for source_option in STREAM_SOURCES {
if ui.selectable_value(source, source_option.clone(), format!("{:?}", &source_option)).clicked() {
// let source = source_option;
stream_config.stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.source = source_option;
}).unwrap();
};
}
// ui.selectable_value(source, StreamSource::CurrentLines, "Zmq Stream");
// ui.selectable_value(source, StreamSource::Rectangle, "Rectangle");
// ui.selectable_value(source, StreamSource::Grid, "Grid");
});
// ui.radio_value(source, StreamSource::Rectangle, "Rectangle");
// ui.radio_value(source, StreamSource::Grid, "Grid");
// if ui.add(DropDownBox::from_iter(
// vec!(StreamSource::CurrentLines, StreamSource::Rectangle, StreamSource::Grid(5)),
// "test_dropbox",
// &mut stream_config.config.source,
// |ui, text| ui.selectable_label(false, text)
// )).changed() {
// println!("Changed source! {:?}", stream_config.config.source);
// let source = stream_config.config.source;
// stream_config.stream.send(move |laser_model: &mut LaserModel| {
// laser_model.config.source = source;
// }).unwrap();
// }
// if ui.add(
// egui::ComboBox::from_label("Source")
// .selected_text(format!("{source:?}"))
// // .show_ui(ui, |ui| {
// // ui.selectable_value(source, StreamSource::CurrentLines, "Zmq Stream");
// // ui.selectable_value(source, StreamSource::Rectangle, "Rectangle");
// // ui.selectable_value(source, StreamSource::Grid(5), "Grid");
// // })
// ).changed()
// {
// let source = stream_config.config.source;
// stream_config.stream.send(move |laser_model: &mut LaserModel| {
// laser_model.config.source = source;
// }).unwrap();
// }
if ui
.add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming"))
.changed()
{
let factor = stream_config.config.filters.dim.intensity;
stream_config.stream.send(move |laser_model: &mut LaserModel| {
laser_model.config.filters.dim.intensity = factor;
}).unwrap();
}
if ui if ui
.add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming")) .add(egui::Slider::new(&mut stream_config.config.filters.dim.intensity, 0.0..=1.).text("Dimming"))
.changed() .changed()
@ -622,7 +728,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
if ui if ui
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, -1.0..=1.).text("Pincushion x")) .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_x, 0.0..=2.).text("Pincushion x"))
.changed() .changed()
{ {
let factor = stream_config.config.filters.pincushion.k_x; let factor = stream_config.config.filters.pincushion.k_x;
@ -632,7 +738,7 @@ fn update(_app: &App, model: &mut GuiModel, update: Update) {
} }
if ui if ui
.add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, -1.0..=1.).text("Pincushion y")) .add(egui::Slider::new(&mut stream_config.config.filters.pincushion.k_y, 0.0..=2.).text("Pincushion y"))
.changed() .changed()
{ {
let factor = stream_config.config.filters.pincushion.k_y; let factor = stream_config.config.filters.pincushion.k_y;

View file

@ -11,45 +11,45 @@ pub trait Filter {
fn apply(&self, points: &LaserPoints) -> LaserPoints; fn apply(&self, points: &LaserPoints) -> LaserPoints;
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct HomographyFilter { pub struct HomographyFilter {
pub homography_matrix: Mat3 pub homography_matrix: Mat3
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct CropFilter { pub struct CropFilter {
pub enabled: bool pub enabled: bool
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DimFilter { pub struct DimFilter {
pub intensity: f32 pub intensity: f32
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ScaleFilter { pub struct ScaleFilter {
pub factor: f32 pub factor: f32
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PincushionFilter { pub struct PincushionFilter {
pub k_x: f32, pub k_x: f32,
pub k_y: f32 pub k_y: f32
} }
#[derive(Serialize, Deserialize, Clone)] // #[derive(Serialize, Deserialize, Clone, Debug)]
// TODO consider moving to struct? // // TODO consider moving to struct?
pub enum PointFilter { // pub enum PointFilter {
Homography(HomographyFilter), // Homography(HomographyFilter),
Dim(DimFilter), // Dim(DimFilter),
Scale(ScaleFilter), // Scale(ScaleFilter),
Pincushion(PincushionFilter), // Pincushion(PincushionFilter),
Crop(CropFilter), // Crop(CropFilter),
} // }
pub struct PointFilterList(Vec<PointFilter>); // deprecated // pub struct PointFilterList(Vec<PointFilter>); // deprecated
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PointFilters{ pub struct PointFilters{
pub dim: DimFilter, pub dim: DimFilter,
pub homography: HomographyFilter, pub homography: HomographyFilter,
@ -95,7 +95,7 @@ impl Default for PointFilters {
homography: HomographyFilter::default(), homography: HomographyFilter::default(),
dim: DimFilter{intensity: 0.5}, dim: DimFilter{intensity: 0.5},
scale: ScaleFilter { factor: 1. }, scale: ScaleFilter { factor: 1. },
pincushion: PincushionFilter{k_x: 0., k_y: 0.}, pincushion: PincushionFilter{k_x: 1., k_y: 1.},
crop: CropFilter{ enabled: true }, crop: CropFilter{ enabled: true },
} }
} }
@ -313,10 +313,37 @@ impl Filter for PincushionFilter {
let projected_positions: Vec<laser::Point> = points.points.iter().map(|point| { let projected_positions: Vec<laser::Point> = points.points.iter().map(|point| {
let p = point.position; let p = point.position;
let new_position = [ let mut radius = (p[0].powi(2) + p[1].powi(2)).sqrt();
p[0] * (1. + self.k_x * p[0].powi(2)), let new_position = if radius > 0. {
p[1] * (1. + self.k_y * p[1].powi(2)) 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
};
// // Convert to polar coords:
// // float radius = length(v);
// if (radius > 0)
// {
// float theta = atan(v.y,v.x);
// // Distort:
// radius = pow(radius, BarrelPower);
// // 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))
// ];
laser::Point { laser::Point {
position: new_position, position: new_position,

View file

@ -2,10 +2,11 @@ use bevy::prelude::*;
use nannou_laser as laser; use nannou_laser as laser;
use std::time::Instant; use std::time::Instant;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::trap::{filters::{PointFilter, PointFilters}, tracks::CoordinateSpace}; use crate::trap::{filters::{PointFilters}, tracks::CoordinateSpace};
use super::tracks::{RenderableLines}; use super::tracks::{RenderableLines};
pub struct LaserPoints{ pub struct LaserPoints{
pub points: Vec<laser::Point>, pub points: Vec<laser::Point>,
pub space: CoordinateSpace pub space: CoordinateSpace
@ -81,15 +82,29 @@ pub struct LaserTimer {
} }
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DacConfig{ pub struct DacConfig{
// #[serde(with = "DacIdSerializable")] // #[serde(with = "DacIdSerializable")]
// id: DacId, // id: DacId,
pub name: String, pub name: String,
pub homography: Mat3, // pub homography: Mat3,
pub filters: PointFilters pub source: StreamSource,
pub filters: PointFilters,
} }
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum StreamSource {
CurrentLines,
Rectangle,
Grid, // lines
Circle, // segments
Spiral,
}
// usefull to create pull downs with an iterator
pub const STREAM_SOURCES: [StreamSource; 5] = [StreamSource::CurrentLines, StreamSource::Rectangle, StreamSource::Grid, StreamSource::Circle, StreamSource::Spiral];
const LASER_H: Mat3 = python_cv_h_into_mat3(TMP_PYTHON_LASER_H); const LASER_H: Mat3 = python_cv_h_into_mat3(TMP_PYTHON_LASER_H);
const LASER_H_CM: Mat3 = python_cv_h_into_mat3(TMP_DESK_CLUBMAX); const LASER_H_CM: Mat3 = python_cv_h_into_mat3(TMP_DESK_CLUBMAX);
@ -98,6 +113,87 @@ impl Default for DacConfig{
fn default() -> DacConfig{ fn default() -> DacConfig{
//DacConfig { name: "Unknown".into(), homography: Mat3::IDENTITY } //DacConfig { name: "Unknown".into(), homography: Mat3::IDENTITY }
// DacConfig { name: "Unknown".into(), homography: LASER_H_CM } // DacConfig { name: "Unknown".into(), homography: LASER_H_CM }
DacConfig { name: "Unknown".into(), homography: LASER_H, filters: PointFilters::default().with_homography(LASER_H) } DacConfig { name: "Unknown".into(), source: StreamSource::CurrentLines, filters: PointFilters::default().with_homography(LASER_H) }
}
}
impl Default for LaserPoints {
fn default() -> LaserPoints {
LaserPoints { points: Vec::new(), space: CoordinateSpace::World }
}
}
// the different shapes that override the provided lines if needed
impl StreamSource{
pub fn get_shape(&self, current_lines: LaserPoints) -> LaserPoints {
match self {
Self::CurrentLines => current_lines,
Self::Rectangle => LaserPoints { points: vec!(
laser::Point{
position:[0xFFF as f32, 0xFFF as f32],
color: [1.,1.,1.],
weight: 0,
},
laser::Point{
position:[0xFFF as f32, 0.0],
color: [1.,1.,1.],
weight: 0,
},
laser::Point{
position:[0.0, 0.0],
color: [1.,1.,1.],
weight: 0,
},
laser::Point{
position:[0.0, 0xFFF as f32],
color: [1.,1.,1.],
weight: 0,
},
laser::Point{
position:[0xFFF as f32, 0xFFF as f32],
color: [1.,1.,1.],
weight: 0,
},
), space: CoordinateSpace::Laser },
Self::Grid => {
let mut points = Vec::new();
for i in (0..=0xFFF).step_by(0xFFF / 5) {
points.push(laser::Point{
position:[i as f32, 0.],
color: [0., 0., 0.],
weight: 0,
});
for j in (0..=0xFFF).step_by(0xFFF / 50) {
points.push(laser::Point{
position:[i as f32, j as f32],
color: [1.,1.,1.],
weight: 0,
});
}
points.push(points[points.len()-1].blanked());
}
for i in (0..=0xFFF).step_by(0xFFF / 5) {
points.push(laser::Point{
position:[0., i as f32],
color: [0., 0., 0.],
weight: 0,
});
for j in (0..=0xFFF).step_by(0xFFF / 50) {
points.push(laser::Point{
position:[j as f32, i as f32],
color: [1.,1.,1.],
weight: 0,
});
}
points.push(points[points.len()-1].blanked());
}
LaserPoints { points, space: CoordinateSpace::Laser }
},
_ => LaserPoints::default(),
}
} }
} }