1use crate::error::{IoError, IoResult};
7use scirs2_core::ndarray::Array1;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct CalibrationCurve {
14 pub input_points: Vec<f32>,
16 pub output_points: Vec<f32>,
18}
19
20impl CalibrationCurve {
21 pub fn new(input_points: Vec<f32>, output_points: Vec<f32>) -> IoResult<Self> {
23 if input_points.len() != output_points.len() {
24 return Err(IoError::InvalidConfig(
25 "Input and output points must have same length".to_string(),
26 ));
27 }
28 if input_points.len() < 2 {
29 return Err(IoError::InvalidConfig(
30 "Need at least 2 calibration points".to_string(),
31 ));
32 }
33
34 Ok(Self {
35 input_points,
36 output_points,
37 })
38 }
39
40 pub fn apply(&self, value: f32) -> f32 {
42 let mut lower_idx = 0;
44 let mut upper_idx = self.input_points.len() - 1;
45
46 for (i, &input) in self.input_points.iter().enumerate() {
47 if value >= input {
48 lower_idx = i;
49 }
50 if value <= input && i < upper_idx {
51 upper_idx = i;
52 break;
53 }
54 }
55
56 if lower_idx == upper_idx {
57 return self.output_points[lower_idx];
58 }
59
60 let x0 = self.input_points[lower_idx];
62 let x1 = self.input_points[upper_idx];
63 let y0 = self.output_points[lower_idx];
64 let y1 = self.output_points[upper_idx];
65
66 if (x1 - x0).abs() < 1e-10 {
67 return y0;
68 }
69
70 let t = (value - x0) / (x1 - x0);
71 y0 + t * (y1 - y0)
72 }
73}
74
75#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct CalibrationParams {
78 pub offset: f32,
80 pub scale: f32,
82 pub temp_coefficient: Option<f32>,
84 pub curve: Option<CalibrationCurve>,
86 pub custom: HashMap<String, f32>,
88}
89
90impl Default for CalibrationParams {
91 fn default() -> Self {
92 Self {
93 offset: 0.0,
94 scale: 1.0,
95 temp_coefficient: None,
96 curve: None,
97 custom: HashMap::new(),
98 }
99 }
100}
101
102impl CalibrationParams {
103 pub fn identity() -> Self {
105 Self::default()
106 }
107
108 pub fn from_offset_scale(offset: f32, scale: f32) -> Self {
110 Self {
111 offset,
112 scale,
113 ..Default::default()
114 }
115 }
116
117 pub fn calibrate(&self, raw_value: f32, temperature: Option<f32>) -> f32 {
119 let mut value = raw_value;
120
121 value = (value - self.offset) * self.scale;
123
124 if let (Some(temp_coef), Some(temp)) = (self.temp_coefficient, temperature) {
126 value *= 1.0 + temp_coef * (temp - 25.0); }
128
129 if let Some(ref curve) = self.curve {
131 value = curve.apply(value);
132 }
133
134 value
135 }
136
137 pub fn calibrate_array(&self, raw_values: &[f32], temperature: Option<f32>) -> Array1<f32> {
139 Array1::from_vec(
140 raw_values
141 .iter()
142 .map(|&v| self.calibrate(v, temperature))
143 .collect(),
144 )
145 }
146}
147
148pub struct MultiPointCalibrator {
150 reference: Vec<f32>,
152 measured: Vec<f32>,
154}
155
156impl MultiPointCalibrator {
157 pub fn new() -> Self {
159 Self {
160 reference: Vec::new(),
161 measured: Vec::new(),
162 }
163 }
164
165 pub fn add_point(&mut self, reference: f32, measured: f32) {
167 self.reference.push(reference);
168 self.measured.push(measured);
169 }
170
171 pub fn compute_calibration(&self) -> IoResult<CalibrationParams> {
173 if self.reference.len() < 2 {
174 return Err(IoError::InvalidConfig(
175 "Need at least 2 calibration points".to_string(),
176 ));
177 }
178
179 let ref_mean: f32 = self.reference.iter().sum::<f32>() / self.reference.len() as f32;
181 let meas_mean: f32 = self.measured.iter().sum::<f32>() / self.measured.len() as f32;
182
183 let mut covariance = 0.0f32;
185 let mut variance = 0.0f32;
186
187 for (&r, &m) in self.reference.iter().zip(self.measured.iter()) {
188 let r_diff = r - ref_mean;
189 let m_diff = m - meas_mean;
190 covariance += r_diff * m_diff;
191 variance += m_diff * m_diff;
192 }
193
194 if variance.abs() < 1e-10 {
195 return Err(IoError::InvalidConfig(
196 "Measured values have zero variance".to_string(),
197 ));
198 }
199
200 let scale = covariance / variance;
206 let offset = meas_mean - ref_mean / scale;
207
208 Ok(CalibrationParams::from_offset_scale(offset, scale))
209 }
210
211 pub fn num_points(&self) -> usize {
213 self.reference.len()
214 }
215
216 pub fn clear(&mut self) {
218 self.reference.clear();
219 self.measured.clear();
220 }
221}
222
223impl Default for MultiPointCalibrator {
224 fn default() -> Self {
225 Self::new()
226 }
227}
228
229pub struct CalibrationManager {
231 calibrations: HashMap<String, CalibrationParams>,
232}
233
234impl CalibrationManager {
235 pub fn new() -> Self {
237 Self {
238 calibrations: HashMap::new(),
239 }
240 }
241
242 pub fn add_calibration(&mut self, sensor_id: String, params: CalibrationParams) {
244 self.calibrations.insert(sensor_id, params);
245 }
246
247 pub fn get_calibration(&self, sensor_id: &str) -> Option<&CalibrationParams> {
249 self.calibrations.get(sensor_id)
250 }
251
252 pub fn remove_calibration(&mut self, sensor_id: &str) -> Option<CalibrationParams> {
254 self.calibrations.remove(sensor_id)
255 }
256
257 pub fn calibrate(&self, sensor_id: &str, raw_value: f32, temperature: Option<f32>) -> f32 {
259 self.calibrations
260 .get(sensor_id)
261 .map(|cal| cal.calibrate(raw_value, temperature))
262 .unwrap_or(raw_value) }
264
265 pub fn calibrate_array(
267 &self,
268 sensor_id: &str,
269 raw_values: &[f32],
270 temperature: Option<f32>,
271 ) -> Array1<f32> {
272 self.calibrations
273 .get(sensor_id)
274 .map(|cal| cal.calibrate_array(raw_values, temperature))
275 .unwrap_or_else(|| Array1::from_vec(raw_values.to_vec()))
276 }
277
278 pub fn num_sensors(&self) -> usize {
280 self.calibrations.len()
281 }
282
283 pub fn to_json(&self) -> IoResult<String> {
285 serde_json::to_string_pretty(&self.calibrations).map_err(IoError::JsonError)
286 }
287
288 pub fn from_json(json: &str) -> IoResult<Self> {
290 let calibrations: HashMap<String, CalibrationParams> =
291 serde_json::from_str(json).map_err(IoError::JsonError)?;
292 Ok(Self { calibrations })
293 }
294}
295
296impl Default for CalibrationManager {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302pub struct AutoCalibrator {
304 reference: Vec<f32>,
306 samples: Vec<Vec<f32>>,
308 num_samples: usize,
310}
311
312impl AutoCalibrator {
313 pub fn new(reference: Vec<f32>, num_samples: usize) -> Self {
315 Self {
316 reference,
317 samples: Vec::new(),
318 num_samples,
319 }
320 }
321
322 pub fn add_sample(&mut self, sample: Vec<f32>) -> IoResult<()> {
324 if sample.len() != self.reference.len() {
325 return Err(IoError::InvalidConfig("Sample length mismatch".to_string()));
326 }
327 self.samples.push(sample);
328 Ok(())
329 }
330
331 pub fn is_ready(&self) -> bool {
333 self.samples.len() >= self.num_samples
334 }
335
336 pub fn compute_calibration(&self) -> IoResult<CalibrationParams> {
338 if !self.is_ready() {
339 return Err(IoError::InvalidConfig(
340 "Not enough samples collected".to_string(),
341 ));
342 }
343
344 let mut averaged = vec![0.0f32; self.reference.len()];
346 for sample in &self.samples {
347 for (avg, &val) in averaged.iter_mut().zip(sample.iter()) {
348 *avg += val;
349 }
350 }
351 for avg in &mut averaged {
352 *avg /= self.samples.len() as f32;
353 }
354
355 let mut calibrator = MultiPointCalibrator::new();
357 for (&ref_val, &meas_val) in self.reference.iter().zip(averaged.iter()) {
358 calibrator.add_point(ref_val, meas_val);
359 }
360
361 calibrator.compute_calibration()
362 }
363
364 pub fn num_collected(&self) -> usize {
366 self.samples.len()
367 }
368
369 pub fn reset(&mut self) {
371 self.samples.clear();
372 }
373}
374
375#[cfg(test)]
376mod tests {
377 use super::*;
378
379 #[test]
380 fn test_calibration_params_basic() {
381 let params = CalibrationParams::from_offset_scale(10.0, 2.0);
382
383 let calibrated = params.calibrate(30.0, None);
386 assert_eq!(calibrated, 40.0);
387 }
388
389 #[test]
390 fn test_calibration_curve() {
391 let curve = CalibrationCurve::new(vec![0.0, 1.0, 2.0], vec![0.0, 10.0, 30.0]).unwrap();
392
393 assert_eq!(curve.apply(0.0), 0.0);
395 assert_eq!(curve.apply(1.0), 10.0);
396 assert_eq!(curve.apply(0.5), 5.0);
397 assert_eq!(curve.apply(1.5), 20.0);
398 }
399
400 #[test]
401 fn test_multipoint_calibrator() {
402 let mut calibrator = MultiPointCalibrator::new();
403
404 calibrator.add_point(2.0, 1.0); calibrator.add_point(4.0, 2.0); calibrator.add_point(6.0, 3.0); let params = calibrator.compute_calibration().unwrap();
411
412 let calibrated = params.calibrate(1.0, None);
414 assert!((calibrated - 2.0).abs() < 0.2);
415 }
416
417 #[test]
418 fn test_calibration_manager() {
419 let mut manager = CalibrationManager::new();
420
421 let params1 = CalibrationParams::from_offset_scale(0.0, 2.0);
422 let params2 = CalibrationParams::from_offset_scale(10.0, 1.0);
423
424 manager.add_calibration("sensor1".to_string(), params1);
425 manager.add_calibration("sensor2".to_string(), params2);
426
427 assert_eq!(manager.num_sensors(), 2);
428
429 let cal1 = manager.calibrate("sensor1", 5.0, None);
430 assert_eq!(cal1, 10.0); let cal2 = manager.calibrate("sensor2", 20.0, None);
433 assert_eq!(cal2, 10.0); }
435
436 #[test]
437 fn test_calibration_json_roundtrip() {
438 let mut manager = CalibrationManager::new();
439 manager.add_calibration(
440 "test_sensor".to_string(),
441 CalibrationParams::from_offset_scale(1.0, 2.0),
442 );
443
444 let json = manager.to_json().unwrap();
445 let loaded = CalibrationManager::from_json(&json).unwrap();
446
447 assert_eq!(loaded.num_sensors(), 1);
448 let cal = loaded.calibrate("test_sensor", 5.0, None);
449 assert_eq!(cal, 8.0); }
451
452 #[test]
453 fn test_auto_calibrator() {
454 let reference = vec![1.0, 2.0, 3.0, 4.0, 5.0];
455 let mut calibrator = AutoCalibrator::new(reference.clone(), 3);
456
457 assert!(!calibrator.is_ready());
458
459 calibrator
462 .add_sample(vec![2.0, 4.0, 6.0, 8.0, 10.0])
463 .unwrap();
464 calibrator
465 .add_sample(vec![2.0, 4.0, 6.0, 8.0, 10.0])
466 .unwrap();
467 calibrator
468 .add_sample(vec![2.0, 4.0, 6.0, 8.0, 10.0])
469 .unwrap();
470
471 assert!(calibrator.is_ready());
472
473 let params = calibrator.compute_calibration().unwrap();
474
475 let calibrated = params.calibrate(2.0, None);
477 assert!((calibrated - 1.0).abs() < 0.2);
478 }
479}