1use std::sync::Arc;
2
3#[cfg(feature = "parallel")]
4use crate::par_util;
5#[cfg(feature = "parallel")]
6use rayon::prelude::*;
7
8use ad_core_rs::ndarray::{NDArray, NDDataBuffer, NDDataType};
9use ad_core_rs::ndarray_pool::NDArrayPool;
10use ad_core_rs::plugin::runtime::{NDPluginProcess, ProcessResult};
11
12#[derive(Debug, Clone)]
25pub struct FilterConfig {
26 pub num_filter: usize,
28 pub auto_reset: bool,
30 pub filter_callbacks: usize,
32 pub oc: [f64; 4],
34 pub fc: [f64; 4],
36 pub rc: [f64; 2],
38 pub o_offset: f64,
40 pub o_scale: f64,
42 pub f_offset: f64,
44 pub f_scale: f64,
46}
47
48impl Default for FilterConfig {
49 fn default() -> Self {
50 Self {
51 num_filter: 1,
52 auto_reset: false,
53 filter_callbacks: 0,
54 oc: [1.0, 0.0, 0.0, 0.0], fc: [1.0, 0.0, 0.0, 0.0],
56 rc: [1.0, 0.0],
57 o_offset: 0.0,
58 o_scale: 1.0,
59 f_offset: 0.0,
60 f_scale: 1.0,
61 }
62 }
63}
64
65#[derive(Debug, Clone)]
67pub struct ProcessConfig {
68 pub enable_background: bool,
69 pub enable_flat_field: bool,
70 pub enable_offset_scale: bool,
71 pub offset: f64,
72 pub scale: f64,
73 pub enable_low_clip: bool,
74 pub low_clip_thresh: f64,
75 pub low_clip_value: f64,
76 pub enable_high_clip: bool,
77 pub high_clip_thresh: f64,
78 pub high_clip_value: f64,
79 pub scale_flat_field: f64,
80 pub enable_filter: bool,
81 pub filter: FilterConfig,
82 pub output_type: Option<NDDataType>,
83 pub save_background: bool,
85 pub save_flat_field: bool,
87 pub valid_background: bool,
89 pub valid_flat_field: bool,
91}
92
93impl Default for ProcessConfig {
94 fn default() -> Self {
95 Self {
96 enable_background: false,
97 enable_flat_field: false,
98 enable_offset_scale: false,
99 offset: 0.0,
100 scale: 1.0,
101 enable_low_clip: false,
102 low_clip_thresh: 0.0,
103 low_clip_value: 0.0,
104 enable_high_clip: false,
105 high_clip_thresh: 100.0,
106 high_clip_value: 100.0,
107 scale_flat_field: 255.0,
108 enable_filter: false,
109 filter: FilterConfig::default(),
110 output_type: None,
111 save_background: false,
112 save_flat_field: false,
113 valid_background: false,
114 valid_flat_field: false,
115 }
116 }
117}
118
119pub struct ProcessState {
121 pub config: ProcessConfig,
122 pub background: Option<Vec<f64>>,
123 pub flat_field: Option<Vec<f64>>,
124 pub filter_state: Option<Vec<f64>>,
126 pub filter_state_prev: Option<Vec<Vec<f64>>>,
128 pub output_state: Option<Vec<f64>>,
130 pub output_state_prev: Option<Vec<f64>>,
132 pub num_filtered: usize,
134}
135
136impl ProcessState {
137 pub fn new(config: ProcessConfig) -> Self {
138 Self {
139 config,
140 background: None,
141 flat_field: None,
142 filter_state: None,
143 filter_state_prev: None,
144 output_state: None,
145 output_state_prev: None,
146 num_filtered: 0,
147 }
148 }
149
150 pub fn save_background(&mut self, array: &NDArray) {
152 let n = array.data.len();
153 let mut bg = vec![0.0f64; n];
154 for i in 0..n {
155 bg[i] = array.data.get_as_f64(i).unwrap_or(0.0);
156 }
157 self.background = Some(bg);
158 self.config.valid_background = true;
159 }
160
161 pub fn save_flat_field(&mut self, array: &NDArray) {
163 let n = array.data.len();
164 let mut ff = vec![0.0f64; n];
165 for i in 0..n {
166 ff[i] = array.data.get_as_f64(i).unwrap_or(0.0);
167 }
168 self.flat_field = Some(ff);
169 self.config.valid_flat_field = true;
170 }
171
172 pub fn auto_offset_scale(&mut self, array: &NDArray) {
175 let n = array.data.len();
176 if n == 0 {
177 return;
178 }
179 let mut min_val = f64::MAX;
180 let mut max_val = f64::MIN;
181 for i in 0..n {
182 let v = array.data.get_as_f64(i).unwrap_or(0.0);
183 if v < min_val {
184 min_val = v;
185 }
186 if v > max_val {
187 max_val = v;
188 }
189 }
190 let range = max_val - min_val;
191 if range > 0.0 {
192 let out_max = match self.config.output_type.unwrap_or(array.data.data_type()) {
193 NDDataType::Int8 => 127.0,
194 NDDataType::UInt8 => 255.0,
195 NDDataType::Int16 => 32767.0,
196 NDDataType::UInt16 => 65535.0,
197 NDDataType::Int32 => 2_147_483_647.0,
198 NDDataType::UInt32 => 4_294_967_295.0,
199 _ => 65535.0,
200 };
201 self.config.scale = out_max / range;
202 self.config.offset = -min_val * self.config.scale;
203 }
204 }
205
206 pub fn apply_filter_type(&mut self, filter_type: i32) {
208 let fc = &mut self.config.filter;
209 match filter_type {
210 0 => {
211 let n = fc.num_filter.max(1) as f64;
215 fc.fc = [1.0 / n, (n - 1.0) / n, 0.0, 0.0];
216 fc.oc = [1.0, 0.0, 0.0, 0.0];
217 fc.rc = [1.0, 0.0];
218 fc.f_offset = 0.0;
219 fc.f_scale = 1.0;
220 fc.o_offset = 0.0;
221 fc.o_scale = 1.0;
222 }
223 1 => {
224 let n = fc.num_filter.max(1) as f64;
227 fc.fc = [1.0, 1.0, 0.0, 0.0];
228 fc.oc = [1.0 / n, 0.0, 0.0, 0.0];
229 fc.rc = [1.0, 0.0];
230 fc.f_offset = 0.0;
231 fc.f_scale = 1.0;
232 fc.o_offset = 0.0;
233 fc.o_scale = 1.0;
234 }
235 2 => {
236 fc.fc = [1.0, 1.0, 0.0, 0.0];
238 fc.oc = [1.0, 0.0, 0.0, 0.0];
239 fc.rc = [1.0, 0.0];
240 fc.f_offset = 0.0;
241 fc.f_scale = 1.0;
242 fc.o_offset = 0.0;
243 fc.o_scale = 1.0;
244 }
245 3 => {
246 fc.fc = [1.0, 0.0, 0.0, 0.0];
248 fc.oc = [1.0, -1.0, 0.0, 0.0];
249 fc.rc = [1.0, 0.0];
250 fc.f_offset = 0.0;
251 fc.f_scale = 1.0;
252 fc.o_offset = 0.0;
253 fc.o_scale = 1.0;
254 }
255 4 => {
256 let n = fc.num_filter.max(1) as f64;
258 fc.fc = [1.0 / n, (n - 1.0) / n, 0.0, 0.0];
259 fc.oc = [-1.0, 0.0, 0.0, 0.0];
260 fc.rc = [1.0, 0.0];
261 fc.f_offset = 0.0;
262 fc.f_scale = 1.0;
263 fc.o_offset = 0.0;
264 fc.o_scale = 1.0;
265 }
266 5 => {
267 fc.fc = [1.0, 0.0, 0.0, 0.0];
269 fc.oc = [1.0, 0.0, 0.0, 0.0];
270 fc.rc = [1.0, 0.0];
271 fc.f_offset = 0.0;
272 fc.f_scale = 1.0;
273 fc.o_offset = 0.0;
274 fc.o_scale = 1.0;
275 }
276 _ => {} }
278 }
279
280 pub fn reset_filter(&mut self) {
282 self.filter_state = None;
283 self.filter_state_prev = None;
284 self.output_state = None;
285 self.output_state_prev = None;
286 self.num_filtered = 0;
287 }
288
289 pub fn process(&mut self, src: &NDArray) -> NDArray {
291 let n = src.data.len();
292 let mut values = vec![0.0f64; n];
293 for i in 0..n {
294 values[i] = src.data.get_as_f64(i).unwrap_or(0.0);
295 }
296
297 if self.config.save_background {
299 self.save_background(src);
300 self.config.save_background = false;
301 }
302 if self.config.save_flat_field {
303 self.save_flat_field(src);
304 self.config.save_flat_field = false;
305 }
306
307 let needs_element_ops = self.config.enable_background
310 || self.config.enable_flat_field
311 || self.config.enable_offset_scale
312 || self.config.enable_low_clip
313 || self.config.enable_high_clip;
314
315 if needs_element_ops {
316 let bg = if self.config.enable_background {
317 self.background.as_ref()
318 } else {
319 None
320 };
321 let (ff, ff_scale) = if self.config.enable_flat_field {
322 if let Some(ref ff) = self.flat_field {
323 let scale = if self.config.scale_flat_field > 0.0 {
324 self.config.scale_flat_field
325 } else {
326 ff.iter().sum::<f64>() / ff.len().max(1) as f64
327 };
328 (Some(ff.as_slice()), scale)
329 } else {
330 (None, 0.0)
331 }
332 } else {
333 (None, 0.0)
334 };
335 let do_offset_scale = self.config.enable_offset_scale;
336 let scale = self.config.scale;
337 let offset = self.config.offset;
338 let do_low_clip = self.config.enable_low_clip;
339 let low_clip_thresh = self.config.low_clip_thresh;
340 let low_clip_value = self.config.low_clip_value;
341 let do_high_clip = self.config.enable_high_clip;
342 let high_clip_thresh = self.config.high_clip_thresh;
343 let high_clip_value = self.config.high_clip_value;
344
345 let apply_stages = |i: usize, v: &mut f64| {
346 if let Some(bg) = bg {
348 if i < bg.len() {
349 *v -= bg[i];
350 }
351 }
352 if let Some(ff) = ff {
354 if i < ff.len() && ff[i] != 0.0 {
355 *v = *v * ff_scale / ff[i];
356 }
357 }
358 if do_offset_scale {
360 *v = *v * scale + offset;
361 }
362 if do_low_clip && *v < low_clip_thresh {
364 *v = low_clip_value;
365 }
366 if do_high_clip && *v > high_clip_thresh {
367 *v = high_clip_value;
368 }
369 };
370
371 #[cfg(feature = "parallel")]
372 let use_parallel = par_util::should_parallelize(n);
373 #[cfg(not(feature = "parallel"))]
374 let use_parallel = false;
375
376 if use_parallel {
377 #[cfg(feature = "parallel")]
378 par_util::thread_pool().install(|| {
379 values.par_iter_mut().enumerate().for_each(|(i, v)| {
380 apply_stages(i, v);
381 });
382 });
383 } else {
384 for (i, v) in values.iter_mut().enumerate() {
385 apply_stages(i, v);
386 }
387 }
388 }
389
390 if self.config.enable_filter {
392 let fc = &self.config.filter;
393 let is_first_frame = self.filter_state.is_none();
394
395 if is_first_frame {
396 let rc1 = fc.rc[0];
398 let rc2 = fc.rc[1];
399
400 let mut f_new = vec![0.0f64; n];
401 let f_prev = self.filter_state.as_ref();
403 for i in 0..n {
404 let fp = f_prev.map_or(0.0, |p| p[i]);
405 f_new[i] = rc1 * values[i] + rc2 * fp;
406 }
407
408 let mut o_new = vec![0.0f64; n];
410 for i in 0..n {
411 o_new[i] = fc.oc[0] * f_new[i];
412 o_new[i] = fc.o_offset + fc.o_scale * o_new[i];
413 }
414
415 self.filter_state_prev = Some(vec![vec![0.0; n], vec![0.0; n]]);
417 self.output_state_prev = Some(vec![0.0; n]);
418 self.output_state = Some(o_new.clone());
419 self.filter_state = Some(f_new);
420 self.num_filtered = 1;
421
422 values = o_new;
423 } else {
424 let f_prev = self.filter_state.as_ref().unwrap(); let f_prev_history = self.filter_state_prev.as_ref().unwrap();
427 let f_prev2 = &f_prev_history[0]; let f_prev3 = &f_prev_history[1]; let o_prev = self.output_state.as_ref().unwrap(); let o_prev2 = self.output_state_prev.as_ref().unwrap(); let f_offset = fc.f_offset;
434 let f_scale = fc.f_scale;
435 let o_offset = fc.o_offset;
436 let o_scale = fc.o_scale;
437 let fc_coeffs = fc.fc;
438 let oc_coeffs = fc.oc;
439
440 let mut f_new = vec![0.0f64; n];
441 let mut o_new = vec![0.0f64; n];
442
443 for i in 0..n {
444 f_new[i] = fc_coeffs[0] * values[i]
446 + fc_coeffs[1] * f_prev[i]
447 + fc_coeffs[2] * (f_prev2[i] - f_offset)
448 + fc_coeffs[3] * (f_prev3[i] - f_offset);
449 f_new[i] = f_offset + f_scale * f_new[i];
451
452 o_new[i] = oc_coeffs[0] * f_new[i]
454 + oc_coeffs[1] * f_prev[i]
455 + oc_coeffs[2] * (o_prev[i] - o_offset)
456 + oc_coeffs[3] * (o_prev2[i] - o_offset);
457 o_new[i] = o_offset + o_scale * o_new[i];
459 }
460
461 let old_f_prev = f_prev.clone();
463 self.filter_state_prev = Some(vec![old_f_prev, f_prev2.clone()]);
464 self.output_state_prev = Some(o_prev.clone());
466 self.output_state = Some(o_new.clone());
467 self.filter_state = Some(f_new);
468
469 self.num_filtered += 1;
470
471 if fc.auto_reset && fc.num_filter > 0 && self.num_filtered >= fc.num_filter {
473 self.reset_filter();
474 }
475
476 values = o_new;
477 }
478 }
479
480 let out_type = self.config.output_type.unwrap_or(src.data.data_type());
482 let mut out_data = NDDataBuffer::zeros(out_type, n);
483 for i in 0..n {
484 out_data.set_from_f64(i, values[i]);
485 }
486
487 let mut arr = NDArray::new(src.dims.clone(), out_type);
488 arr.data = out_data;
489 arr.unique_id = src.unique_id;
490 arr.timestamp = src.timestamp;
491 arr.attributes = src.attributes.clone();
492 arr
493 }
494}
495
496#[derive(Default)]
500struct ProcParamIndices {
501 data_type: Option<usize>,
502 save_background: Option<usize>,
503 enable_background: Option<usize>,
504 valid_background: Option<usize>,
505 save_flat_field: Option<usize>,
506 enable_flat_field: Option<usize>,
507 valid_flat_field: Option<usize>,
508 scale_flat_field: Option<usize>,
509 enable_offset_scale: Option<usize>,
510 auto_offset_scale: Option<usize>,
511 offset: Option<usize>,
512 scale: Option<usize>,
513 enable_low_clip: Option<usize>,
514 low_clip_thresh: Option<usize>,
515 low_clip_value: Option<usize>,
516 enable_high_clip: Option<usize>,
517 high_clip_thresh: Option<usize>,
518 high_clip_value: Option<usize>,
519 enable_filter: Option<usize>,
520 filter_type: Option<usize>,
521 reset_filter: Option<usize>,
522 auto_reset_filter: Option<usize>,
523 filter_callbacks: Option<usize>,
524 num_filter: Option<usize>,
525 num_filtered: Option<usize>,
526 o_offset: Option<usize>,
527 o_scale: Option<usize>,
528 oc: [Option<usize>; 4],
529 f_offset: Option<usize>,
530 f_scale: Option<usize>,
531 fc: [Option<usize>; 4],
532 r_offset: Option<usize>,
533 rc: [Option<usize>; 2],
534}
535
536pub struct ProcessProcessor {
538 state: ProcessState,
539 params: ProcParamIndices,
540}
541
542impl ProcessProcessor {
543 pub fn new(config: ProcessConfig) -> Self {
544 Self {
545 state: ProcessState::new(config),
546 params: ProcParamIndices::default(),
547 }
548 }
549
550 pub fn state(&self) -> &ProcessState {
551 &self.state
552 }
553
554 pub fn state_mut(&mut self) -> &mut ProcessState {
555 &mut self.state
556 }
557}
558
559impl NDPluginProcess for ProcessProcessor {
560 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
561 use ad_core_rs::plugin::runtime::ParamUpdate;
562
563 let out = self.state.process(array);
564 let mut result = ProcessResult::arrays(vec![Arc::new(out)]);
565
566 if let Some(idx) = self.params.valid_background {
568 result.param_updates.push(ParamUpdate::int32(
569 idx,
570 if self.state.config.valid_background {
571 1
572 } else {
573 0
574 },
575 ));
576 }
577 if let Some(idx) = self.params.valid_flat_field {
578 result.param_updates.push(ParamUpdate::int32(
579 idx,
580 if self.state.config.valid_flat_field {
581 1
582 } else {
583 0
584 },
585 ));
586 }
587 if let Some(idx) = self.params.num_filtered {
588 result
589 .param_updates
590 .push(ParamUpdate::int32(idx, self.state.num_filtered as i32));
591 }
592 if let Some(idx) = self.params.save_background {
594 result.param_updates.push(ParamUpdate::int32(idx, 0));
595 }
596 if let Some(idx) = self.params.save_flat_field {
597 result.param_updates.push(ParamUpdate::int32(idx, 0));
598 }
599
600 result
601 }
602
603 fn plugin_type(&self) -> &str {
604 "NDPluginProcess"
605 }
606
607 fn register_params(
608 &mut self,
609 base: &mut asyn_rs::port::PortDriverBase,
610 ) -> asyn_rs::error::AsynResult<()> {
611 use asyn_rs::param::ParamType;
612 base.create_param("PROCESS_DATA_TYPE", ParamType::Int32)?;
613 base.create_param("SAVE_BACKGROUND", ParamType::Int32)?;
614 base.create_param("ENABLE_BACKGROUND", ParamType::Int32)?;
615 base.create_param("VALID_BACKGROUND", ParamType::Int32)?;
616 base.create_param("SAVE_FLAT_FIELD", ParamType::Int32)?;
617 base.create_param("ENABLE_FLAT_FIELD", ParamType::Int32)?;
618 base.create_param("VALID_FLAT_FIELD", ParamType::Int32)?;
619 base.create_param("SCALE_FLAT_FIELD", ParamType::Float64)?;
620 base.create_param("ENABLE_OFFSET_SCALE", ParamType::Int32)?;
621 base.create_param("AUTO_OFFSET_SCALE", ParamType::Int32)?;
622 base.create_param("OFFSET", ParamType::Float64)?;
623 base.create_param("SCALE", ParamType::Float64)?;
624 base.create_param("ENABLE_LOW_CLIP", ParamType::Int32)?;
625 base.create_param("LOW_CLIP_THRESH", ParamType::Float64)?;
626 base.create_param("LOW_CLIP_VALUE", ParamType::Float64)?;
627 base.create_param("ENABLE_HIGH_CLIP", ParamType::Int32)?;
628 base.create_param("HIGH_CLIP_THRESH", ParamType::Float64)?;
629 base.create_param("HIGH_CLIP_VALUE", ParamType::Float64)?;
630 base.create_param("ENABLE_FILTER", ParamType::Int32)?;
631 base.create_param("FILTER_TYPE", ParamType::Int32)?;
632 base.create_param("RESET_FILTER", ParamType::Int32)?;
633 base.create_param("AUTO_RESET_FILTER", ParamType::Int32)?;
634 base.create_param("FILTER_CALLBACKS", ParamType::Int32)?;
635 base.create_param("NUM_FILTER", ParamType::Int32)?;
636 base.create_param("NUM_FILTERED", ParamType::Int32)?;
637 base.create_param("FILTER_OOFFSET", ParamType::Float64)?;
638 base.create_param("FILTER_OSCALE", ParamType::Float64)?;
639 base.create_param("FILTER_OC1", ParamType::Float64)?;
640 base.create_param("FILTER_OC2", ParamType::Float64)?;
641 base.create_param("FILTER_OC3", ParamType::Float64)?;
642 base.create_param("FILTER_OC4", ParamType::Float64)?;
643 base.create_param("FILTER_FOFFSET", ParamType::Float64)?;
644 base.create_param("FILTER_FSCALE", ParamType::Float64)?;
645 base.create_param("FILTER_FC1", ParamType::Float64)?;
646 base.create_param("FILTER_FC2", ParamType::Float64)?;
647 base.create_param("FILTER_FC3", ParamType::Float64)?;
648 base.create_param("FILTER_FC4", ParamType::Float64)?;
649 base.create_param("FILTER_ROFFSET", ParamType::Float64)?;
650 base.create_param("FILTER_RC1", ParamType::Float64)?;
651 base.create_param("FILTER_RC2", ParamType::Float64)?;
652
653 self.params.data_type = base.find_param("PROCESS_DATA_TYPE");
655 self.params.save_background = base.find_param("SAVE_BACKGROUND");
656 self.params.enable_background = base.find_param("ENABLE_BACKGROUND");
657 self.params.valid_background = base.find_param("VALID_BACKGROUND");
658 self.params.save_flat_field = base.find_param("SAVE_FLAT_FIELD");
659 self.params.enable_flat_field = base.find_param("ENABLE_FLAT_FIELD");
660 self.params.valid_flat_field = base.find_param("VALID_FLAT_FIELD");
661 self.params.scale_flat_field = base.find_param("SCALE_FLAT_FIELD");
662 self.params.enable_offset_scale = base.find_param("ENABLE_OFFSET_SCALE");
663 self.params.auto_offset_scale = base.find_param("AUTO_OFFSET_SCALE");
664 self.params.offset = base.find_param("OFFSET");
665 self.params.scale = base.find_param("SCALE");
666 self.params.enable_low_clip = base.find_param("ENABLE_LOW_CLIP");
667 self.params.low_clip_thresh = base.find_param("LOW_CLIP_THRESH");
668 self.params.low_clip_value = base.find_param("LOW_CLIP_VALUE");
669 self.params.enable_high_clip = base.find_param("ENABLE_HIGH_CLIP");
670 self.params.high_clip_thresh = base.find_param("HIGH_CLIP_THRESH");
671 self.params.high_clip_value = base.find_param("HIGH_CLIP_VALUE");
672 self.params.enable_filter = base.find_param("ENABLE_FILTER");
673 self.params.filter_type = base.find_param("FILTER_TYPE");
674 self.params.reset_filter = base.find_param("RESET_FILTER");
675 self.params.auto_reset_filter = base.find_param("AUTO_RESET_FILTER");
676 self.params.filter_callbacks = base.find_param("FILTER_CALLBACKS");
677 self.params.num_filter = base.find_param("NUM_FILTER");
678 self.params.num_filtered = base.find_param("NUM_FILTERED");
679 self.params.o_offset = base.find_param("FILTER_OOFFSET");
680 self.params.o_scale = base.find_param("FILTER_OSCALE");
681 self.params.oc[0] = base.find_param("FILTER_OC1");
682 self.params.oc[1] = base.find_param("FILTER_OC2");
683 self.params.oc[2] = base.find_param("FILTER_OC3");
684 self.params.oc[3] = base.find_param("FILTER_OC4");
685 self.params.f_offset = base.find_param("FILTER_FOFFSET");
686 self.params.f_scale = base.find_param("FILTER_FSCALE");
687 self.params.fc[0] = base.find_param("FILTER_FC1");
688 self.params.fc[1] = base.find_param("FILTER_FC2");
689 self.params.fc[2] = base.find_param("FILTER_FC3");
690 self.params.fc[3] = base.find_param("FILTER_FC4");
691 self.params.r_offset = base.find_param("FILTER_ROFFSET");
692 self.params.rc[0] = base.find_param("FILTER_RC1");
693 self.params.rc[1] = base.find_param("FILTER_RC2");
694 Ok(())
695 }
696
697 fn on_param_change(
698 &mut self,
699 reason: usize,
700 params: &ad_core_rs::plugin::runtime::PluginParamSnapshot,
701 ) -> ad_core_rs::plugin::runtime::ParamChangeResult {
702 use ad_core_rs::plugin::runtime::{ParamChangeResult, ParamUpdate};
703
704 let s = &mut self.state;
705 let p = &self.params;
706 let mut updates = Vec::new();
707
708 if Some(reason) == p.data_type {
709 let v = params.value.as_i32();
710 s.config.output_type = if v < 0 {
711 None } else {
713 NDDataType::from_ordinal(v as u8)
714 };
715 } else if Some(reason) == p.save_background {
716 if params.value.as_i32() != 0 {
717 s.config.save_background = true;
718 }
719 } else if Some(reason) == p.enable_background {
720 s.config.enable_background = params.value.as_i32() != 0;
721 } else if Some(reason) == p.save_flat_field {
722 if params.value.as_i32() != 0 {
723 s.config.save_flat_field = true;
724 }
725 } else if Some(reason) == p.enable_flat_field {
726 s.config.enable_flat_field = params.value.as_i32() != 0;
727 } else if Some(reason) == p.scale_flat_field {
728 s.config.scale_flat_field = params.value.as_f64();
729 } else if Some(reason) == p.enable_offset_scale {
730 s.config.enable_offset_scale = params.value.as_i32() != 0;
731 } else if Some(reason) == p.auto_offset_scale {
732 if params.value.as_i32() != 0 {
733 if let Some(ref arr) = self.state.background {
734 let _ = arr; }
737 }
741 } else if Some(reason) == p.offset {
742 s.config.offset = params.value.as_f64();
743 } else if Some(reason) == p.scale {
744 s.config.scale = params.value.as_f64();
745 } else if Some(reason) == p.enable_low_clip {
746 s.config.enable_low_clip = params.value.as_i32() != 0;
747 } else if Some(reason) == p.low_clip_thresh {
748 s.config.low_clip_thresh = params.value.as_f64();
749 } else if Some(reason) == p.low_clip_value {
750 s.config.low_clip_value = params.value.as_f64();
751 } else if Some(reason) == p.enable_high_clip {
752 s.config.enable_high_clip = params.value.as_i32() != 0;
753 } else if Some(reason) == p.high_clip_thresh {
754 s.config.high_clip_thresh = params.value.as_f64();
755 } else if Some(reason) == p.high_clip_value {
756 s.config.high_clip_value = params.value.as_f64();
757 } else if Some(reason) == p.enable_filter {
758 s.config.enable_filter = params.value.as_i32() != 0;
759 } else if Some(reason) == p.filter_type {
760 s.apply_filter_type(params.value.as_i32());
761 s.reset_filter();
762 let fc = &s.config.filter;
764 for (i, idx) in p.fc.iter().enumerate() {
765 if let Some(idx) = *idx {
766 updates.push(ParamUpdate::float64(idx, fc.fc[i]));
767 }
768 }
769 for (i, idx) in p.oc.iter().enumerate() {
770 if let Some(idx) = *idx {
771 updates.push(ParamUpdate::float64(idx, fc.oc[i]));
772 }
773 }
774 for (i, idx) in p.rc.iter().enumerate() {
775 if let Some(idx) = *idx {
776 updates.push(ParamUpdate::float64(idx, fc.rc[i]));
777 }
778 }
779 if let Some(idx) = p.f_offset {
780 updates.push(ParamUpdate::float64(idx, fc.f_offset));
781 }
782 if let Some(idx) = p.f_scale {
783 updates.push(ParamUpdate::float64(idx, fc.f_scale));
784 }
785 if let Some(idx) = p.o_offset {
786 updates.push(ParamUpdate::float64(idx, fc.o_offset));
787 }
788 if let Some(idx) = p.o_scale {
789 updates.push(ParamUpdate::float64(idx, fc.o_scale));
790 }
791 } else if Some(reason) == p.reset_filter {
792 if params.value.as_i32() != 0 {
793 s.reset_filter();
794 if let Some(idx) = p.num_filtered {
795 updates.push(ParamUpdate::int32(idx, 0));
796 }
797 }
798 } else if Some(reason) == p.auto_reset_filter {
799 s.config.filter.auto_reset = params.value.as_i32() != 0;
800 } else if Some(reason) == p.filter_callbacks {
801 s.config.filter.filter_callbacks = params.value.as_i32().max(0) as usize;
802 } else if Some(reason) == p.num_filter {
803 s.config.filter.num_filter = params.value.as_i32().max(1) as usize;
804 } else if Some(reason) == p.o_offset {
805 s.config.filter.o_offset = params.value.as_f64();
806 } else if Some(reason) == p.o_scale {
807 s.config.filter.o_scale = params.value.as_f64();
808 } else if Some(reason) == p.f_offset {
809 s.config.filter.f_offset = params.value.as_f64();
810 } else if Some(reason) == p.f_scale {
811 s.config.filter.f_scale = params.value.as_f64();
812 } else if Some(reason) == p.r_offset {
813 } else {
815 for i in 0..4 {
817 if Some(reason) == p.oc[i] {
818 s.config.filter.oc[i] = params.value.as_f64();
819 return ParamChangeResult::updates(vec![]);
820 }
821 if Some(reason) == p.fc[i] {
822 s.config.filter.fc[i] = params.value.as_f64();
823 return ParamChangeResult::updates(vec![]);
824 }
825 }
826 for i in 0..2 {
827 if Some(reason) == p.rc[i] {
828 s.config.filter.rc[i] = params.value.as_f64();
829 return ParamChangeResult::updates(vec![]);
830 }
831 }
832 }
833
834 ParamChangeResult::updates(updates)
835 }
836}
837
838#[cfg(test)]
839mod tests {
840 use super::*;
841 use ad_core_rs::ndarray::{NDDataBuffer, NDDimension};
842
843 fn make_array(vals: &[u8]) -> NDArray {
844 let mut arr = NDArray::new(vec![NDDimension::new(vals.len())], NDDataType::UInt8);
845 if let NDDataBuffer::U8(ref mut v) = arr.data {
846 v.copy_from_slice(vals);
847 }
848 arr
849 }
850
851 fn make_f64_array(vals: &[f64]) -> NDArray {
852 let mut arr = NDArray::new(vec![NDDimension::new(vals.len())], NDDataType::Float64);
853 if let NDDataBuffer::F64(ref mut v) = arr.data {
854 v.copy_from_slice(vals);
855 }
856 arr
857 }
858
859 #[test]
860 fn test_background_subtraction() {
861 let bg_arr = make_array(&[10, 20, 30]);
862 let input = make_array(&[15, 25, 35]);
863
864 let mut state = ProcessState::new(ProcessConfig {
865 enable_background: true,
866 ..Default::default()
867 });
868 state.save_background(&bg_arr);
869
870 let result = state.process(&input);
871 if let NDDataBuffer::U8(ref v) = result.data {
872 assert_eq!(v[0], 5);
873 assert_eq!(v[1], 5);
874 assert_eq!(v[2], 5);
875 }
876 }
877
878 #[test]
879 fn test_flat_field() {
880 let ff_arr = make_array(&[100, 200, 50]);
881 let input = make_array(&[100, 200, 50]);
882
883 let mut state = ProcessState::new(ProcessConfig {
884 enable_flat_field: true,
885 scale_flat_field: 0.0, ..Default::default()
887 });
888 state.save_flat_field(&ff_arr);
889
890 let result = state.process(&input);
891 if let NDDataBuffer::U8(ref v) = result.data {
893 assert!((v[0] as f64 - 116.67).abs() < 1.0);
895 assert!((v[1] as f64 - 116.67).abs() < 1.0);
896 assert!((v[2] as f64 - 116.67).abs() < 1.0);
897 }
898 }
899
900 #[test]
901 fn test_offset_scale() {
902 let input = make_array(&[10, 20, 30]);
903 let mut state = ProcessState::new(ProcessConfig {
904 enable_offset_scale: true,
905 scale: 2.0,
906 offset: 5.0,
907 ..Default::default()
908 });
909
910 let result = state.process(&input);
911 if let NDDataBuffer::U8(ref v) = result.data {
912 assert_eq!(v[0], 25); assert_eq!(v[1], 45); assert_eq!(v[2], 65); }
916 }
917
918 #[test]
919 fn test_clipping() {
920 let input = make_array(&[5, 50, 200]);
921 let mut state = ProcessState::new(ProcessConfig {
922 enable_low_clip: true,
923 low_clip_thresh: 10.0,
924 low_clip_value: 10.0,
925 enable_high_clip: true,
926 high_clip_thresh: 100.0,
927 high_clip_value: 100.0,
928 ..Default::default()
929 });
930
931 let result = state.process(&input);
932 if let NDDataBuffer::U8(ref v) = result.data {
933 assert_eq!(v[0], 10); assert_eq!(v[1], 50); assert_eq!(v[2], 100); }
937 }
938
939 #[test]
940 fn test_recursive_filter() {
941 let input1 = make_array(&[100, 100, 100]);
944 let input2 = make_array(&[0, 0, 0]);
945
946 let mut state = ProcessState::new(ProcessConfig {
947 enable_filter: true,
948 filter: FilterConfig {
949 fc: [0.5, 0.5, 0.0, 0.0],
950 oc: [1.0, 0.0, 0.0, 0.0],
951 ..Default::default()
952 },
953 ..Default::default()
954 });
955
956 let _ = state.process(&input1); let result = state.process(&input2); if let NDDataBuffer::U8(ref v) = result.data {
959 assert_eq!(v[0], 50);
960 assert_eq!(v[1], 50);
961 }
962 }
963
964 #[test]
965 fn test_output_type_conversion() {
966 let input = make_array(&[10, 20, 30]);
967 let mut state = ProcessState::new(ProcessConfig {
968 output_type: Some(NDDataType::Float64),
969 ..Default::default()
970 });
971
972 let result = state.process(&input);
973 assert_eq!(result.data.data_type(), NDDataType::Float64);
974 }
975
976 #[test]
979 fn test_process_processor() {
980 let mut proc = ProcessProcessor::new(ProcessConfig {
981 enable_offset_scale: true,
982 scale: 2.0,
983 offset: 1.0,
984 ..Default::default()
985 });
986 let pool = NDArrayPool::new(1_000_000);
987
988 let input = make_array(&[10, 20, 30]);
989 let result = proc.process_array(&input, &pool);
990 assert_eq!(result.output_arrays.len(), 1);
991 if let NDDataBuffer::U8(ref v) = result.output_arrays[0].data {
992 assert_eq!(v[0], 21); }
994 }
995
996 #[test]
999 fn test_4tap_filter_averaging() {
1000 let mut state = ProcessState::new(ProcessConfig {
1003 enable_filter: true,
1004 filter: FilterConfig {
1005 fc: [0.25, 0.75, 0.0, 0.0],
1006 oc: [1.0, 0.0, 0.0, 0.0],
1007 ..Default::default()
1008 },
1009 output_type: Some(NDDataType::Float64),
1010 ..Default::default()
1011 });
1012
1013 let r1 = state.process(&make_f64_array(&[100.0]));
1015 let v1 = r1.data.get_as_f64(0).unwrap();
1016 assert!((v1 - 100.0).abs() < 1e-9, "frame 1: got {v1}");
1017
1018 let r2 = state.process(&make_f64_array(&[100.0]));
1020 let v2 = r2.data.get_as_f64(0).unwrap();
1021 assert!((v2 - 100.0).abs() < 1e-9, "frame 2: got {v2}");
1022
1023 let r3 = state.process(&make_f64_array(&[0.0]));
1025 let v3 = r3.data.get_as_f64(0).unwrap();
1026 assert!((v3 - 75.0).abs() < 1e-9, "frame 3: got {v3}");
1027
1028 let r4 = state.process(&make_f64_array(&[0.0]));
1030 let v4 = r4.data.get_as_f64(0).unwrap();
1031 assert!((v4 - 56.25).abs() < 1e-9, "frame 4: got {v4}");
1032 }
1033
1034 #[test]
1035 fn test_4tap_filter_all_taps() {
1036 let mut state = ProcessState::new(ProcessConfig {
1038 enable_filter: true,
1039 filter: FilterConfig {
1040 fc: [0.5, 0.3, 0.1, 0.1],
1041 oc: [0.7, 0.2, 0.05, 0.05],
1042 rc: [1.0, 0.0],
1043 f_offset: 0.0,
1044 f_scale: 1.0,
1045 o_offset: 0.0,
1046 o_scale: 1.0,
1047 ..Default::default()
1048 },
1049 output_type: Some(NDDataType::Float64),
1050 ..Default::default()
1051 });
1052
1053 let _ = state.process(&make_f64_array(&[10.0]));
1055
1056 let r1 = state.process(&make_f64_array(&[20.0]));
1060 let v1 = r1.data.get_as_f64(0).unwrap();
1061 assert!((v1 - 11.45).abs() < 1e-9, "frame 1: got {v1}");
1062
1063 let r2 = state.process(&make_f64_array(&[30.0]));
1067 let v2 = r2.data.get_as_f64(0).unwrap();
1068 assert!((v2 - 17.4525).abs() < 1e-9, "frame 2: got {v2}");
1069 }
1070
1071 #[test]
1072 fn test_save_background_one_shot() {
1073 let mut state = ProcessState::new(ProcessConfig {
1074 save_background: true,
1075 ..Default::default()
1076 });
1077
1078 assert!(!state.config.valid_background);
1079 assert!(state.background.is_none());
1080
1081 let input = make_array(&[10, 20, 30]);
1083 let _ = state.process(&input);
1084
1085 assert!(
1086 !state.config.save_background,
1087 "save_background should be cleared"
1088 );
1089 assert!(
1090 state.config.valid_background,
1091 "valid_background should be set"
1092 );
1093 assert!(state.background.is_some());
1094
1095 let bg = state.background.as_ref().unwrap();
1096 assert_eq!(bg.len(), 3);
1097 assert!((bg[0] - 10.0).abs() < 1e-9);
1098 assert!((bg[1] - 20.0).abs() < 1e-9);
1099 assert!((bg[2] - 30.0).abs() < 1e-9);
1100
1101 let input2 = make_array(&[40, 50, 60]);
1103 let _ = state.process(&input2);
1104
1105 assert!(
1106 !state.config.save_background,
1107 "save_background stays cleared"
1108 );
1109 let bg2 = state.background.as_ref().unwrap();
1111 assert!((bg2[0] - 10.0).abs() < 1e-9);
1112 }
1113
1114 #[test]
1115 fn test_save_flat_field_one_shot() {
1116 let mut state = ProcessState::new(ProcessConfig {
1117 save_flat_field: true,
1118 ..Default::default()
1119 });
1120
1121 assert!(!state.config.valid_flat_field);
1122 assert!(state.flat_field.is_none());
1123
1124 let input = make_array(&[50, 100, 150]);
1125 let _ = state.process(&input);
1126
1127 assert!(
1128 !state.config.save_flat_field,
1129 "save_flat_field should be cleared"
1130 );
1131 assert!(
1132 state.config.valid_flat_field,
1133 "valid_flat_field should be set"
1134 );
1135 assert!(state.flat_field.is_some());
1136
1137 let ff = state.flat_field.as_ref().unwrap();
1138 assert_eq!(ff.len(), 3);
1139 assert!((ff[0] - 50.0).abs() < 1e-9);
1140 assert!((ff[1] - 100.0).abs() < 1e-9);
1141 assert!((ff[2] - 150.0).abs() < 1e-9);
1142 }
1143
1144 #[test]
1145 fn test_auto_reset_when_num_filter_reached() {
1146 let mut state = ProcessState::new(ProcessConfig {
1147 enable_filter: true,
1148 filter: FilterConfig {
1149 num_filter: 3,
1150 auto_reset: true,
1151 fc: [0.5, 0.5, 0.0, 0.0],
1152 oc: [1.0, 0.0, 0.0, 0.0],
1153 ..Default::default()
1154 },
1155 output_type: Some(NDDataType::Float64),
1156 ..Default::default()
1157 });
1158
1159 let _ = state.process(&make_f64_array(&[100.0]));
1161 assert_eq!(state.num_filtered, 1);
1162
1163 let _ = state.process(&make_f64_array(&[100.0]));
1165 assert_eq!(state.num_filtered, 2);
1166
1167 let _ = state.process(&make_f64_array(&[100.0]));
1169 assert_eq!(state.num_filtered, 0, "auto_reset should have fired");
1170 assert!(
1171 state.filter_state.is_none(),
1172 "filter state should be cleared"
1173 );
1174 assert!(
1175 state.output_state.is_none(),
1176 "output state should be cleared"
1177 );
1178
1179 let _ = state.process(&make_f64_array(&[200.0]));
1181 assert_eq!(state.num_filtered, 1, "fresh start after reset");
1182 }
1183
1184 #[test]
1185 fn test_filter_with_offset_scale() {
1186 let mut state = ProcessState::new(ProcessConfig {
1188 enable_filter: true,
1189 filter: FilterConfig {
1190 fc: [1.0, 0.0, 0.0, 0.0],
1191 oc: [1.0, 0.0, 0.0, 0.0],
1192 f_offset: 10.0,
1193 f_scale: 2.0,
1194 o_offset: 5.0,
1195 o_scale: 3.0,
1196 ..Default::default()
1197 },
1198 output_type: Some(NDDataType::Float64),
1199 ..Default::default()
1200 });
1201
1202 let r0 = state.process(&make_f64_array(&[50.0]));
1205 let v0 = r0.data.get_as_f64(0).unwrap();
1206 assert!((v0 - 155.0).abs() < 1e-9, "frame 0: got {v0}");
1207
1208 let r1 = state.process(&make_f64_array(&[20.0]));
1212 let v1 = r1.data.get_as_f64(0).unwrap();
1213 assert!((v1 - 155.0).abs() < 1e-9, "frame 1: got {v1}");
1214 }
1215
1216 #[test]
1217 fn test_reset_filter_manual() {
1218 let mut state = ProcessState::new(ProcessConfig {
1219 enable_filter: true,
1220 filter: FilterConfig {
1221 fc: [0.5, 0.5, 0.0, 0.0],
1222 oc: [1.0, 0.0, 0.0, 0.0],
1223 ..Default::default()
1224 },
1225 output_type: Some(NDDataType::Float64),
1226 ..Default::default()
1227 });
1228
1229 let _ = state.process(&make_f64_array(&[100.0]));
1231 let _ = state.process(&make_f64_array(&[100.0]));
1232 assert!(state.filter_state.is_some());
1233 assert_eq!(state.num_filtered, 2);
1234
1235 state.reset_filter();
1237 assert!(state.filter_state.is_none());
1238 assert!(state.output_state.is_none());
1239 assert_eq!(state.num_filtered, 0);
1240
1241 let r = state.process(&make_f64_array(&[200.0]));
1243 let v = r.data.get_as_f64(0).unwrap();
1244 assert!(
1245 (v - 200.0).abs() < 1e-9,
1246 "after reset, first frame: got {v}"
1247 );
1248 assert_eq!(state.num_filtered, 1);
1249 }
1250}