vision_calibration_pipeline/laserline_device/
problem.rs1use crate::Error;
4use serde::{Deserialize, Serialize};
5use vision_calibration_core::ScheimpflugParams;
6use vision_calibration_linear::prelude::*;
7use vision_calibration_optim::{
8 BackendSolveOptions, LaserlineDataset, LaserlineEstimate, LaserlineResidualType,
9 LaserlineSolveOptions, LaserlineStats,
10};
11
12use crate::session::{InvalidationPolicy, ProblemType};
13
14use super::state::LaserlineDeviceState;
15
16#[derive(Debug)]
18pub struct LaserlineDeviceProblem;
19
20pub type LaserlineDeviceInput = LaserlineDataset;
22
23#[derive(Debug, Clone, Default, Serialize, Deserialize)]
25#[non_exhaustive]
26pub struct LaserlineDeviceConfig {
27 pub init: LaserlineDeviceInitConfig,
29 pub solver: LaserlineDeviceSolverConfig,
31 pub optimize: LaserlineDeviceOptimizeConfig,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
37#[non_exhaustive]
38pub struct LaserlineDeviceInitConfig {
39 pub iterations: usize,
41 pub fix_k3: bool,
43 pub fix_tangential: bool,
45 pub zero_skew: bool,
47 pub sensor_init: ScheimpflugParams,
49}
50
51impl Default for LaserlineDeviceInitConfig {
52 fn default() -> Self {
53 Self {
54 iterations: 2,
55 fix_k3: true,
56 fix_tangential: false,
57 zero_skew: true,
58 sensor_init: ScheimpflugParams::default(),
59 }
60 }
61}
62
63#[derive(Debug, Clone, Serialize, Deserialize)]
65#[non_exhaustive]
66pub struct LaserlineDeviceSolverConfig {
67 pub max_iters: usize,
69 pub verbosity: usize,
71}
72
73impl Default for LaserlineDeviceSolverConfig {
74 fn default() -> Self {
75 Self {
76 max_iters: 50,
77 verbosity: 0,
78 }
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
84#[non_exhaustive]
85pub struct LaserlineDeviceOptimizeConfig {
86 pub calib_loss: vision_calibration_optim::RobustLoss,
88 pub laser_loss: vision_calibration_optim::RobustLoss,
90 pub calib_weight: f64,
92 pub laser_weight: f64,
94 pub fix_intrinsics: bool,
96 pub fix_distortion: bool,
98 pub fix_k3: bool,
100 pub fix_sensor: bool,
102 pub fix_poses: Vec<usize>,
104 pub fix_plane: bool,
106 pub laser_residual_type: LaserlineResidualType,
108}
109
110impl Default for LaserlineDeviceOptimizeConfig {
111 fn default() -> Self {
112 Self {
113 calib_loss: vision_calibration_optim::RobustLoss::Huber { scale: 1.0 },
114 laser_loss: vision_calibration_optim::RobustLoss::Huber { scale: 0.01 },
115 calib_weight: 1.0,
116 laser_weight: 1.0,
117 fix_intrinsics: false,
118 fix_distortion: false,
119 fix_k3: true,
120 fix_sensor: true,
121 fix_poses: vec![0],
122 fix_plane: false,
123 laser_residual_type: LaserlineResidualType::LineDistNormalized,
124 }
125 }
126}
127
128impl LaserlineDeviceConfig {
129 pub fn init_opts(&self) -> IterativeIntrinsicsOptions {
131 IterativeIntrinsicsOptions {
132 iterations: self.init.iterations,
133 distortion_opts: DistortionFitOptions {
134 fix_k3: self.init.fix_k3,
135 fix_tangential: self.init.fix_tangential,
136 iters: 8,
137 },
138 zero_skew: self.init.zero_skew,
139 }
140 }
141
142 pub fn solve_opts(&self) -> LaserlineSolveOptions {
144 LaserlineSolveOptions {
145 calib_loss: self.optimize.calib_loss,
146 calib_weight: self.optimize.calib_weight,
147 laser_loss: self.optimize.laser_loss,
148 laser_weight: self.optimize.laser_weight,
149 fix_intrinsics: self.optimize.fix_intrinsics,
150 fix_distortion: self.optimize.fix_distortion,
151 fix_k3: self.optimize.fix_k3,
152 fix_sensor: self.optimize.fix_sensor,
153 fix_poses: self.optimize.fix_poses.clone(),
154 fix_plane: self.optimize.fix_plane,
155 laser_residual_type: self.optimize.laser_residual_type,
156 }
157 }
158
159 pub fn backend_opts(&self) -> BackendSolveOptions {
161 BackendSolveOptions {
162 max_iters: self.solver.max_iters,
163 verbosity: self.solver.verbosity,
164 ..Default::default()
165 }
166 }
167}
168
169#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct LaserlineDeviceOutput {
172 pub estimate: LaserlineEstimate,
174 pub stats: LaserlineStats,
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
180#[non_exhaustive]
181pub struct LaserlineDeviceExport {
182 pub estimate: LaserlineEstimate,
184 pub stats: LaserlineStats,
186 pub mean_reproj_error: f64,
188 pub per_cam_reproj_errors: Vec<f64>,
190}
191
192impl ProblemType for LaserlineDeviceProblem {
193 type Config = LaserlineDeviceConfig;
194 type Input = LaserlineDeviceInput;
195 type State = LaserlineDeviceState;
196 type Output = LaserlineDeviceOutput;
197 type Export = LaserlineDeviceExport;
198
199 fn name() -> &'static str {
200 "laserline_device_v1"
201 }
202
203 fn validate_input(input: &Self::Input) -> Result<(), Error> {
204 if input.len() < 3 {
205 return Err(Error::InsufficientData {
206 need: 3,
207 got: input.len(),
208 });
209 }
210
211 for (i, view) in input.iter().enumerate() {
212 if view.obs.len() < 4 {
213 return Err(Error::invalid_input(format!(
214 "view {} has too few points (need >= 4 for homography, got {})",
215 i,
216 view.obs.len()
217 )));
218 }
219 view.meta
220 .validate()
221 .map_err(|e| Error::invalid_input(format!("view {}: {}", i, e)))?;
222 }
223
224 Ok(())
225 }
226
227 fn validate_config(config: &Self::Config) -> Result<(), Error> {
228 if config.solver.max_iters == 0 {
229 return Err(Error::invalid_input("max_iters must be positive"));
230 }
231 if config.init.iterations == 0 {
232 return Err(Error::invalid_input("init_iterations must be positive"));
233 }
234 if config.optimize.calib_weight <= 0.0 {
235 return Err(Error::invalid_input("calib_weight must be positive"));
236 }
237 if config.optimize.laser_weight <= 0.0 {
238 return Err(Error::invalid_input("laser_weight must be positive"));
239 }
240 Ok(())
241 }
242
243 fn on_input_change() -> InvalidationPolicy {
244 InvalidationPolicy::CLEAR_COMPUTED
245 }
246
247 fn export(output: &Self::Output, _config: &Self::Config) -> Result<Self::Export, Error> {
248 Ok(LaserlineDeviceExport {
249 estimate: output.estimate.clone(),
250 stats: output.stats.clone(),
251 mean_reproj_error: output.stats.mean_reproj_error,
252 per_cam_reproj_errors: vec![output.stats.mean_reproj_error],
253 })
254 }
255}