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)]
27pub struct FilterConfig {
28 pub num_filter: usize,
30 pub auto_reset: bool,
32 pub filter_callbacks: usize,
34 pub oc: [f64; 4],
36 pub fc: [f64; 4],
38 pub rc: [f64; 2],
40 pub r_offset: f64,
42 pub o_offset: f64,
44 pub o_scale: f64,
46 pub f_offset: f64,
48 pub f_scale: f64,
50}
51
52impl Default for FilterConfig {
53 fn default() -> Self {
54 Self {
55 num_filter: 1,
56 auto_reset: false,
57 filter_callbacks: 0,
58 oc: [1.0, 0.0, 0.0, 0.0], fc: [1.0, 0.0, 0.0, 0.0],
60 rc: [1.0, 0.0],
61 r_offset: 0.0,
62 o_offset: 0.0,
63 o_scale: 1.0,
64 f_offset: 0.0,
65 f_scale: 1.0,
66 }
67 }
68}
69
70#[derive(Debug, Clone)]
72pub struct ProcessConfig {
73 pub enable_background: bool,
74 pub enable_flat_field: bool,
75 pub enable_offset_scale: bool,
76 pub offset: f64,
77 pub scale: f64,
78 pub enable_low_clip: bool,
79 pub low_clip_thresh: f64,
80 pub low_clip_value: f64,
81 pub enable_high_clip: bool,
82 pub high_clip_thresh: f64,
83 pub high_clip_value: f64,
84 pub scale_flat_field: f64,
85 pub enable_filter: bool,
86 pub filter: FilterConfig,
87 pub output_type: Option<NDDataType>,
88 pub save_background: bool,
90 pub save_flat_field: bool,
92 pub valid_background: bool,
94 pub valid_flat_field: bool,
96}
97
98impl Default for ProcessConfig {
99 fn default() -> Self {
100 Self {
101 enable_background: false,
102 enable_flat_field: false,
103 enable_offset_scale: false,
104 offset: 0.0,
105 scale: 1.0,
106 enable_low_clip: false,
107 low_clip_thresh: 0.0,
108 low_clip_value: 0.0,
109 enable_high_clip: false,
110 high_clip_thresh: 100.0,
111 high_clip_value: 100.0,
112 scale_flat_field: 255.0,
113 enable_filter: false,
114 filter: FilterConfig::default(),
115 output_type: None,
116 save_background: false,
117 save_flat_field: false,
118 valid_background: false,
119 valid_flat_field: false,
120 }
121 }
122}
123
124pub struct ProcessState {
128 pub config: ProcessConfig,
129 pub background: Option<Vec<f64>>,
130 pub flat_field: Option<Vec<f64>>,
131 pub filter_state: Option<Vec<f64>>,
133 pub num_filtered: usize,
135}
136
137impl ProcessState {
138 pub fn new(config: ProcessConfig) -> Self {
139 Self {
140 config,
141 background: None,
142 flat_field: None,
143 filter_state: None,
144 num_filtered: 0,
145 }
146 }
147
148 pub fn save_background(&mut self, array: &NDArray) {
150 let n = array.data.len();
151 let mut bg = vec![0.0f64; n];
152 for i in 0..n {
153 bg[i] = array.data.get_as_f64(i).unwrap_or(0.0);
154 }
155 self.background = Some(bg);
156 self.config.valid_background = true;
157 }
158
159 pub fn save_flat_field(&mut self, array: &NDArray) {
161 let n = array.data.len();
162 let mut ff = vec![0.0f64; n];
163 for i in 0..n {
164 ff[i] = array.data.get_as_f64(i).unwrap_or(0.0);
165 }
166 self.flat_field = Some(ff);
167 self.config.valid_flat_field = true;
168 }
169
170 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 bytes_per_elem = match self.config.output_type.unwrap_or(array.data.data_type()) {
194 NDDataType::Int8 | NDDataType::UInt8 => 1,
195 NDDataType::Int16 | NDDataType::UInt16 => 2,
196 NDDataType::Int32 | NDDataType::UInt32 => 4,
197 NDDataType::Int64 | NDDataType::UInt64 => 8,
198 NDDataType::Float32 => 4,
199 NDDataType::Float64 => 8,
200 };
201 let max_scale = 2.0f64.powi(bytes_per_elem * 8) - 1.0;
202 self.config.scale = max_scale / range;
204 self.config.offset = -min_val;
205 self.config.enable_offset_scale = true;
207 self.config.enable_low_clip = true;
208 self.config.low_clip_thresh = 0.0;
209 self.config.enable_high_clip = true;
210 self.config.high_clip_thresh = max_scale;
211 }
212 }
213
214 pub fn apply_filter_type(&mut self, filter_type: i32) {
222 let fc = &mut self.config.filter;
223 match filter_type {
224 0 => {
225 fc.fc = [1.0, -1.0, 0.0, 1.0];
239 fc.oc = [1.0, 0.0, 0.0, 0.0];
240 fc.rc = [0.0, 1.0]; fc.r_offset = 0.0;
242 fc.f_offset = 0.0;
243 fc.f_scale = 1.0;
244 fc.o_offset = 0.0;
245 fc.o_scale = 1.0;
246 }
247 1 => {
248 fc.fc = [1.0, 0.0, 1.0, 0.0];
254 fc.oc = [0.0, 1.0, 0.0, 0.0];
255 fc.rc = [0.0, 1.0]; fc.r_offset = 0.0;
257 fc.f_offset = 0.0;
258 fc.f_scale = 1.0;
259 fc.o_offset = 0.0;
260 fc.o_scale = 1.0;
261 }
262 2 => {
263 fc.fc = [1.0, 0.0, 1.0, 0.0];
265 fc.oc = [1.0, 0.0, 0.0, 0.0];
266 fc.rc = [0.0, 1.0];
267 fc.r_offset = 0.0;
268 fc.f_offset = 0.0;
269 fc.f_scale = 1.0;
270 fc.o_offset = 0.0;
271 fc.o_scale = 1.0;
272 }
273 3 => {
274 fc.fc = [0.0, 0.0, 1.0, 0.0];
278 fc.oc = [-1.0, 0.0, 1.0, 0.0];
279 fc.rc = [0.0, 1.0];
280 fc.r_offset = 0.0;
281 fc.f_offset = 0.0;
282 fc.f_scale = 1.0;
283 fc.o_offset = 0.0;
284 fc.o_scale = 1.0;
285 }
286 4 => {
287 fc.fc = [1.0, -1.0, 0.0, 1.0];
290 fc.oc = [-1.0, 0.0, 1.0, 0.0];
291 fc.rc = [0.0, 1.0];
292 fc.r_offset = 0.0;
293 fc.f_offset = 0.0;
294 fc.f_scale = 1.0;
295 fc.o_offset = 0.0;
296 fc.o_scale = 1.0;
297 }
298 5 => {
299 fc.fc = [0.0, 0.0, 1.0, 0.0];
301 fc.oc = [1.0, 0.0, 0.0, 0.0];
302 fc.rc = [0.0, 1.0];
303 fc.r_offset = 0.0;
304 fc.f_offset = 0.0;
305 fc.f_scale = 1.0;
306 fc.o_offset = 0.0;
307 fc.o_scale = 1.0;
308 }
309 _ => {} }
311 }
312
313 pub fn reset_filter(&mut self) {
315 self.filter_state = None;
316 self.num_filtered = 0;
317 }
318
319 pub fn process(&mut self, src: &NDArray) -> NDArray {
321 let n = src.data.len();
322 let mut values = vec![0.0f64; n];
323 for i in 0..n {
324 values[i] = src.data.get_as_f64(i).unwrap_or(0.0);
325 }
326
327 if self.config.save_background {
329 self.save_background(src);
330 self.config.save_background = false;
331 }
332 if self.config.save_flat_field {
333 self.save_flat_field(src);
334 self.config.save_flat_field = false;
335 }
336
337 let needs_element_ops = self.config.enable_background
340 || self.config.enable_flat_field
341 || self.config.enable_offset_scale
342 || self.config.enable_low_clip
343 || self.config.enable_high_clip;
344
345 if needs_element_ops {
346 let bg = if self.config.enable_background {
347 self.background.as_ref()
348 } else {
349 None
350 };
351 let (ff, ff_scale) = if self.config.enable_flat_field {
352 if let Some(ref ff) = self.flat_field {
353 let scale = if self.config.scale_flat_field > 0.0 {
354 self.config.scale_flat_field
355 } else {
356 ff.iter().sum::<f64>() / ff.len().max(1) as f64
357 };
358 (Some(ff.as_slice()), scale)
359 } else {
360 (None, 0.0)
361 }
362 } else {
363 (None, 0.0)
364 };
365 let do_offset_scale = self.config.enable_offset_scale;
366 let scale = self.config.scale;
367 let offset = self.config.offset;
368 let do_low_clip = self.config.enable_low_clip;
369 let low_clip_thresh = self.config.low_clip_thresh;
370 let low_clip_value = self.config.low_clip_value;
371 let do_high_clip = self.config.enable_high_clip;
372 let high_clip_thresh = self.config.high_clip_thresh;
373 let high_clip_value = self.config.high_clip_value;
374
375 let apply_stages = |i: usize, v: &mut f64| {
376 if let Some(bg) = bg {
378 if i < bg.len() {
379 *v -= bg[i];
380 }
381 }
382 if let Some(ff) = ff {
384 if i < ff.len() && ff[i] != 0.0 {
385 *v = *v * ff_scale / ff[i];
386 }
387 }
388 if do_offset_scale {
390 *v = (*v + offset) * scale;
391 }
392 if do_low_clip && *v < low_clip_thresh {
394 *v = low_clip_value;
395 }
396 if do_high_clip && *v > high_clip_thresh {
397 *v = high_clip_value;
398 }
399 };
400
401 #[cfg(feature = "parallel")]
402 let use_parallel = par_util::should_parallelize(n);
403 #[cfg(not(feature = "parallel"))]
404 let use_parallel = false;
405
406 if use_parallel {
407 #[cfg(feature = "parallel")]
408 par_util::thread_pool().install(|| {
409 values.par_iter_mut().enumerate().for_each(|(i, v)| {
410 apply_stages(i, v);
411 });
412 });
413 } else {
414 for (i, v) in values.iter_mut().enumerate() {
415 apply_stages(i, v);
416 }
417 }
418 }
419
420 if self.config.enable_filter {
422 let fc = &self.config.filter;
423
424 if let Some(ref f) = self.filter_state {
426 if f.len() != n {
427 self.filter_state = None;
428 }
429 }
430
431 let mut reset_filter = self.filter_state.is_none();
432 if self.num_filtered >= fc.num_filter && fc.auto_reset {
433 reset_filter = true;
434 }
435
436 if self.filter_state.is_none() {
438 self.filter_state = Some(values.clone());
439 }
440
441 let filter = self.filter_state.as_mut().unwrap();
442
443 if reset_filter {
444 let r_offset = fc.r_offset;
446 let rc1 = fc.rc[0];
447 let rc2 = fc.rc[1];
448 for i in 0..n {
449 let new_filter = r_offset + rc1 * filter[i] + rc2 * values[i];
450 filter[i] = new_filter;
451 }
452 self.num_filtered = 0;
453 }
454
455 if self.num_filtered < fc.num_filter {
457 self.num_filtered += 1;
458 }
459
460 let nf = self.num_filtered as f64;
462 let o1 = fc.o_scale * (fc.oc[0] + fc.oc[1] / nf);
463 let o2 = fc.o_scale * (fc.oc[2] + fc.oc[3] / nf);
464 let f1 = fc.f_scale * (fc.fc[0] + fc.fc[1] / nf);
465 let f2 = fc.f_scale * (fc.fc[2] + fc.fc[3] / nf);
466 let o_offset = fc.o_offset;
467 let f_offset = fc.f_offset;
468
469 for i in 0..n {
472 let new_data = o_offset + o1 * filter[i] + o2 * values[i];
473 let new_filter = f_offset + f1 * filter[i] + f2 * values[i];
474 values[i] = new_data;
475 filter[i] = new_filter;
476 }
477
478 if fc.filter_callbacks > 0 && self.num_filtered != fc.num_filter {
480 return src.clone();
483 }
484 }
485
486 let out_type = self.config.output_type.unwrap_or(src.data.data_type());
488 let mut out_data = NDDataBuffer::zeros(out_type, n);
489 for i in 0..n {
490 out_data.set_from_f64(i, values[i]);
491 }
492
493 let mut arr = NDArray::new(src.dims.clone(), out_type);
494 arr.data = out_data;
495 arr.unique_id = src.unique_id;
496 arr.timestamp = src.timestamp;
497 arr.attributes = src.attributes.clone();
498 arr
499 }
500}
501
502#[derive(Default)]
506struct ProcParamIndices {
507 data_type: Option<usize>,
508 save_background: Option<usize>,
509 enable_background: Option<usize>,
510 valid_background: Option<usize>,
511 save_flat_field: Option<usize>,
512 enable_flat_field: Option<usize>,
513 valid_flat_field: Option<usize>,
514 scale_flat_field: Option<usize>,
515 enable_offset_scale: Option<usize>,
516 auto_offset_scale: Option<usize>,
517 offset: Option<usize>,
518 scale: Option<usize>,
519 enable_low_clip: Option<usize>,
520 low_clip_thresh: Option<usize>,
521 low_clip_value: Option<usize>,
522 enable_high_clip: Option<usize>,
523 high_clip_thresh: Option<usize>,
524 high_clip_value: Option<usize>,
525 enable_filter: Option<usize>,
526 filter_type: Option<usize>,
527 reset_filter: Option<usize>,
528 auto_reset_filter: Option<usize>,
529 filter_callbacks: Option<usize>,
530 num_filter: Option<usize>,
531 num_filtered: Option<usize>,
532 o_offset: Option<usize>,
533 o_scale: Option<usize>,
534 oc: [Option<usize>; 4],
535 f_offset: Option<usize>,
536 f_scale: Option<usize>,
537 fc: [Option<usize>; 4],
538 r_offset: Option<usize>,
539 rc: [Option<usize>; 2],
540}
541
542pub struct ProcessProcessor {
544 state: ProcessState,
545 params: ProcParamIndices,
546}
547
548impl ProcessProcessor {
549 pub fn new(config: ProcessConfig) -> Self {
550 Self {
551 state: ProcessState::new(config),
552 params: ProcParamIndices::default(),
553 }
554 }
555
556 pub fn state(&self) -> &ProcessState {
557 &self.state
558 }
559
560 pub fn state_mut(&mut self) -> &mut ProcessState {
561 &mut self.state
562 }
563}
564
565impl NDPluginProcess for ProcessProcessor {
566 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
567 use ad_core_rs::plugin::runtime::ParamUpdate;
568
569 let out = self.state.process(array);
570 let mut result = ProcessResult::arrays(vec![Arc::new(out)]);
571
572 if let Some(idx) = self.params.valid_background {
574 result.param_updates.push(ParamUpdate::int32(
575 idx,
576 if self.state.config.valid_background {
577 1
578 } else {
579 0
580 },
581 ));
582 }
583 if let Some(idx) = self.params.valid_flat_field {
584 result.param_updates.push(ParamUpdate::int32(
585 idx,
586 if self.state.config.valid_flat_field {
587 1
588 } else {
589 0
590 },
591 ));
592 }
593 if let Some(idx) = self.params.num_filtered {
594 result
595 .param_updates
596 .push(ParamUpdate::int32(idx, self.state.num_filtered as i32));
597 }
598 if let Some(idx) = self.params.save_background {
600 result.param_updates.push(ParamUpdate::int32(idx, 0));
601 }
602 if let Some(idx) = self.params.save_flat_field {
603 result.param_updates.push(ParamUpdate::int32(idx, 0));
604 }
605
606 result
607 }
608
609 fn plugin_type(&self) -> &str {
610 "NDPluginProcess"
611 }
612
613 fn register_params(
614 &mut self,
615 base: &mut asyn_rs::port::PortDriverBase,
616 ) -> asyn_rs::error::AsynResult<()> {
617 use asyn_rs::param::ParamType;
618 base.create_param("PROCESS_DATA_TYPE", ParamType::Int32)?;
619 base.create_param("SAVE_BACKGROUND", ParamType::Int32)?;
620 base.create_param("ENABLE_BACKGROUND", ParamType::Int32)?;
621 base.create_param("VALID_BACKGROUND", ParamType::Int32)?;
622 base.create_param("SAVE_FLAT_FIELD", ParamType::Int32)?;
623 base.create_param("ENABLE_FLAT_FIELD", ParamType::Int32)?;
624 base.create_param("VALID_FLAT_FIELD", ParamType::Int32)?;
625 base.create_param("SCALE_FLAT_FIELD", ParamType::Float64)?;
626 base.create_param("ENABLE_OFFSET_SCALE", ParamType::Int32)?;
627 base.create_param("AUTO_OFFSET_SCALE", ParamType::Int32)?;
628 base.create_param("OFFSET", ParamType::Float64)?;
629 base.create_param("SCALE", ParamType::Float64)?;
630 base.create_param("ENABLE_LOW_CLIP", ParamType::Int32)?;
631 base.create_param("LOW_CLIP_THRESH", ParamType::Float64)?;
632 base.create_param("LOW_CLIP_VALUE", ParamType::Float64)?;
633 base.create_param("ENABLE_HIGH_CLIP", ParamType::Int32)?;
634 base.create_param("HIGH_CLIP_THRESH", ParamType::Float64)?;
635 base.create_param("HIGH_CLIP_VALUE", ParamType::Float64)?;
636 base.create_param("ENABLE_FILTER", ParamType::Int32)?;
637 base.create_param("FILTER_TYPE", ParamType::Int32)?;
638 base.create_param("RESET_FILTER", ParamType::Int32)?;
639 base.create_param("AUTO_RESET_FILTER", ParamType::Int32)?;
640 base.create_param("FILTER_CALLBACKS", ParamType::Int32)?;
641 base.create_param("NUM_FILTER", ParamType::Int32)?;
642 base.create_param("NUM_FILTERED", ParamType::Int32)?;
643 base.create_param("FILTER_OOFFSET", ParamType::Float64)?;
644 base.create_param("FILTER_OSCALE", ParamType::Float64)?;
645 base.create_param("FILTER_OC1", ParamType::Float64)?;
646 base.create_param("FILTER_OC2", ParamType::Float64)?;
647 base.create_param("FILTER_OC3", ParamType::Float64)?;
648 base.create_param("FILTER_OC4", ParamType::Float64)?;
649 base.create_param("FILTER_FOFFSET", ParamType::Float64)?;
650 base.create_param("FILTER_FSCALE", ParamType::Float64)?;
651 base.create_param("FILTER_FC1", ParamType::Float64)?;
652 base.create_param("FILTER_FC2", ParamType::Float64)?;
653 base.create_param("FILTER_FC3", ParamType::Float64)?;
654 base.create_param("FILTER_FC4", ParamType::Float64)?;
655 base.create_param("FILTER_ROFFSET", ParamType::Float64)?;
656 base.create_param("FILTER_RC1", ParamType::Float64)?;
657 base.create_param("FILTER_RC2", ParamType::Float64)?;
658
659 self.params.data_type = base.find_param("PROCESS_DATA_TYPE");
661 self.params.save_background = base.find_param("SAVE_BACKGROUND");
662 self.params.enable_background = base.find_param("ENABLE_BACKGROUND");
663 self.params.valid_background = base.find_param("VALID_BACKGROUND");
664 self.params.save_flat_field = base.find_param("SAVE_FLAT_FIELD");
665 self.params.enable_flat_field = base.find_param("ENABLE_FLAT_FIELD");
666 self.params.valid_flat_field = base.find_param("VALID_FLAT_FIELD");
667 self.params.scale_flat_field = base.find_param("SCALE_FLAT_FIELD");
668 self.params.enable_offset_scale = base.find_param("ENABLE_OFFSET_SCALE");
669 self.params.auto_offset_scale = base.find_param("AUTO_OFFSET_SCALE");
670 self.params.offset = base.find_param("OFFSET");
671 self.params.scale = base.find_param("SCALE");
672 self.params.enable_low_clip = base.find_param("ENABLE_LOW_CLIP");
673 self.params.low_clip_thresh = base.find_param("LOW_CLIP_THRESH");
674 self.params.low_clip_value = base.find_param("LOW_CLIP_VALUE");
675 self.params.enable_high_clip = base.find_param("ENABLE_HIGH_CLIP");
676 self.params.high_clip_thresh = base.find_param("HIGH_CLIP_THRESH");
677 self.params.high_clip_value = base.find_param("HIGH_CLIP_VALUE");
678 self.params.enable_filter = base.find_param("ENABLE_FILTER");
679 self.params.filter_type = base.find_param("FILTER_TYPE");
680 self.params.reset_filter = base.find_param("RESET_FILTER");
681 self.params.auto_reset_filter = base.find_param("AUTO_RESET_FILTER");
682 self.params.filter_callbacks = base.find_param("FILTER_CALLBACKS");
683 self.params.num_filter = base.find_param("NUM_FILTER");
684 self.params.num_filtered = base.find_param("NUM_FILTERED");
685 self.params.o_offset = base.find_param("FILTER_OOFFSET");
686 self.params.o_scale = base.find_param("FILTER_OSCALE");
687 self.params.oc[0] = base.find_param("FILTER_OC1");
688 self.params.oc[1] = base.find_param("FILTER_OC2");
689 self.params.oc[2] = base.find_param("FILTER_OC3");
690 self.params.oc[3] = base.find_param("FILTER_OC4");
691 self.params.f_offset = base.find_param("FILTER_FOFFSET");
692 self.params.f_scale = base.find_param("FILTER_FSCALE");
693 self.params.fc[0] = base.find_param("FILTER_FC1");
694 self.params.fc[1] = base.find_param("FILTER_FC2");
695 self.params.fc[2] = base.find_param("FILTER_FC3");
696 self.params.fc[3] = base.find_param("FILTER_FC4");
697 self.params.r_offset = base.find_param("FILTER_ROFFSET");
698 self.params.rc[0] = base.find_param("FILTER_RC1");
699 self.params.rc[1] = base.find_param("FILTER_RC2");
700 Ok(())
701 }
702
703 fn on_param_change(
704 &mut self,
705 reason: usize,
706 params: &ad_core_rs::plugin::runtime::PluginParamSnapshot,
707 ) -> ad_core_rs::plugin::runtime::ParamChangeResult {
708 use ad_core_rs::plugin::runtime::{ParamChangeResult, ParamUpdate};
709
710 let s = &mut self.state;
711 let p = &self.params;
712 let mut updates = Vec::new();
713
714 if Some(reason) == p.data_type {
715 let v = params.value.as_i32();
716 s.config.output_type = if v < 0 {
717 None } else {
719 NDDataType::from_ordinal(v as u8)
720 };
721 } else if Some(reason) == p.save_background {
722 if params.value.as_i32() != 0 {
723 s.config.save_background = true;
724 }
725 } else if Some(reason) == p.enable_background {
726 s.config.enable_background = params.value.as_i32() != 0;
727 } else if Some(reason) == p.save_flat_field {
728 if params.value.as_i32() != 0 {
729 s.config.save_flat_field = true;
730 }
731 } else if Some(reason) == p.enable_flat_field {
732 s.config.enable_flat_field = params.value.as_i32() != 0;
733 } else if Some(reason) == p.scale_flat_field {
734 s.config.scale_flat_field = params.value.as_f64();
735 } else if Some(reason) == p.enable_offset_scale {
736 s.config.enable_offset_scale = params.value.as_i32() != 0;
737 } else if Some(reason) == p.auto_offset_scale {
738 if params.value.as_i32() != 0 {
739 if let Some(ref arr) = self.state.background {
740 let _ = arr; }
743 }
747 } else if Some(reason) == p.offset {
748 s.config.offset = params.value.as_f64();
749 } else if Some(reason) == p.scale {
750 s.config.scale = params.value.as_f64();
751 } else if Some(reason) == p.enable_low_clip {
752 s.config.enable_low_clip = params.value.as_i32() != 0;
753 } else if Some(reason) == p.low_clip_thresh {
754 s.config.low_clip_thresh = params.value.as_f64();
755 } else if Some(reason) == p.low_clip_value {
756 s.config.low_clip_value = params.value.as_f64();
757 } else if Some(reason) == p.enable_high_clip {
758 s.config.enable_high_clip = params.value.as_i32() != 0;
759 } else if Some(reason) == p.high_clip_thresh {
760 s.config.high_clip_thresh = params.value.as_f64();
761 } else if Some(reason) == p.high_clip_value {
762 s.config.high_clip_value = params.value.as_f64();
763 } else if Some(reason) == p.enable_filter {
764 s.config.enable_filter = params.value.as_i32() != 0;
765 } else if Some(reason) == p.filter_type {
766 s.apply_filter_type(params.value.as_i32());
767 s.reset_filter();
768 let fc = &s.config.filter;
770 for (i, idx) in p.fc.iter().enumerate() {
771 if let Some(idx) = *idx {
772 updates.push(ParamUpdate::float64(idx, fc.fc[i]));
773 }
774 }
775 for (i, idx) in p.oc.iter().enumerate() {
776 if let Some(idx) = *idx {
777 updates.push(ParamUpdate::float64(idx, fc.oc[i]));
778 }
779 }
780 for (i, idx) in p.rc.iter().enumerate() {
781 if let Some(idx) = *idx {
782 updates.push(ParamUpdate::float64(idx, fc.rc[i]));
783 }
784 }
785 if let Some(idx) = p.f_offset {
786 updates.push(ParamUpdate::float64(idx, fc.f_offset));
787 }
788 if let Some(idx) = p.f_scale {
789 updates.push(ParamUpdate::float64(idx, fc.f_scale));
790 }
791 if let Some(idx) = p.o_offset {
792 updates.push(ParamUpdate::float64(idx, fc.o_offset));
793 }
794 if let Some(idx) = p.o_scale {
795 updates.push(ParamUpdate::float64(idx, fc.o_scale));
796 }
797 } else if Some(reason) == p.reset_filter {
798 if params.value.as_i32() != 0 {
799 s.reset_filter();
800 if let Some(idx) = p.num_filtered {
801 updates.push(ParamUpdate::int32(idx, 0));
802 }
803 }
804 } else if Some(reason) == p.auto_reset_filter {
805 s.config.filter.auto_reset = params.value.as_i32() != 0;
806 } else if Some(reason) == p.filter_callbacks {
807 s.config.filter.filter_callbacks = params.value.as_i32().max(0) as usize;
808 } else if Some(reason) == p.num_filter {
809 s.config.filter.num_filter = params.value.as_i32().max(1) as usize;
810 } else if Some(reason) == p.o_offset {
811 s.config.filter.o_offset = params.value.as_f64();
812 } else if Some(reason) == p.o_scale {
813 s.config.filter.o_scale = params.value.as_f64();
814 } else if Some(reason) == p.f_offset {
815 s.config.filter.f_offset = params.value.as_f64();
816 } else if Some(reason) == p.f_scale {
817 s.config.filter.f_scale = params.value.as_f64();
818 } else if Some(reason) == p.r_offset {
819 s.config.filter.r_offset = params.value.as_f64();
820 } else {
821 for i in 0..4 {
823 if Some(reason) == p.oc[i] {
824 s.config.filter.oc[i] = params.value.as_f64();
825 return ParamChangeResult::updates(vec![]);
826 }
827 if Some(reason) == p.fc[i] {
828 s.config.filter.fc[i] = params.value.as_f64();
829 return ParamChangeResult::updates(vec![]);
830 }
831 }
832 for i in 0..2 {
833 if Some(reason) == p.rc[i] {
834 s.config.filter.rc[i] = params.value.as_f64();
835 return ParamChangeResult::updates(vec![]);
836 }
837 }
838 }
839
840 ParamChangeResult::updates(updates)
841 }
842}
843
844#[cfg(test)]
845mod tests {
846 use super::*;
847 use ad_core_rs::ndarray::{NDDataBuffer, NDDimension};
848
849 fn make_array(vals: &[u8]) -> NDArray {
850 let mut arr = NDArray::new(vec![NDDimension::new(vals.len())], NDDataType::UInt8);
851 if let NDDataBuffer::U8(ref mut v) = arr.data {
852 v.copy_from_slice(vals);
853 }
854 arr
855 }
856
857 fn make_f64_array(vals: &[f64]) -> NDArray {
858 let mut arr = NDArray::new(vec![NDDimension::new(vals.len())], NDDataType::Float64);
859 if let NDDataBuffer::F64(ref mut v) = arr.data {
860 v.copy_from_slice(vals);
861 }
862 arr
863 }
864
865 #[test]
866 fn test_background_subtraction() {
867 let bg_arr = make_array(&[10, 20, 30]);
868 let input = make_array(&[15, 25, 35]);
869
870 let mut state = ProcessState::new(ProcessConfig {
871 enable_background: true,
872 ..Default::default()
873 });
874 state.save_background(&bg_arr);
875
876 let result = state.process(&input);
877 if let NDDataBuffer::U8(ref v) = result.data {
878 assert_eq!(v[0], 5);
879 assert_eq!(v[1], 5);
880 assert_eq!(v[2], 5);
881 }
882 }
883
884 #[test]
885 fn test_flat_field() {
886 let ff_arr = make_array(&[100, 200, 50]);
887 let input = make_array(&[100, 200, 50]);
888
889 let mut state = ProcessState::new(ProcessConfig {
890 enable_flat_field: true,
891 scale_flat_field: 0.0, ..Default::default()
893 });
894 state.save_flat_field(&ff_arr);
895
896 let result = state.process(&input);
897 if let NDDataBuffer::U8(ref v) = result.data {
899 assert!((v[0] as f64 - 116.67).abs() < 1.0);
901 assert!((v[1] as f64 - 116.67).abs() < 1.0);
902 assert!((v[2] as f64 - 116.67).abs() < 1.0);
903 }
904 }
905
906 #[test]
907 fn test_offset_scale() {
908 let input = make_array(&[10, 20, 30]);
909 let mut state = ProcessState::new(ProcessConfig {
910 enable_offset_scale: true,
911 scale: 2.0,
912 offset: 5.0,
913 ..Default::default()
914 });
915
916 let result = state.process(&input);
917 if let NDDataBuffer::U8(ref v) = result.data {
918 assert_eq!(v[0], 30); assert_eq!(v[1], 50); assert_eq!(v[2], 70); }
923 }
924
925 #[test]
926 fn test_clipping() {
927 let input = make_array(&[5, 50, 200]);
928 let mut state = ProcessState::new(ProcessConfig {
929 enable_low_clip: true,
930 low_clip_thresh: 10.0,
931 low_clip_value: 10.0,
932 enable_high_clip: true,
933 high_clip_thresh: 100.0,
934 high_clip_value: 100.0,
935 ..Default::default()
936 });
937
938 let result = state.process(&input);
939 if let NDDataBuffer::U8(ref v) = result.data {
940 assert_eq!(v[0], 10); assert_eq!(v[1], 50); assert_eq!(v[2], 100); }
944 }
945
946 #[test]
947 fn test_recursive_filter() {
948 let input1 = make_array(&[100, 100, 100]);
954 let input2 = make_array(&[0, 0, 0]);
955
956 let mut state = ProcessState::new(ProcessConfig {
957 enable_filter: true,
958 filter: FilterConfig {
959 num_filter: 10,
960 fc: [0.5, 0.0, 0.5, 0.0], oc: [1.0, 0.0, 0.0, 0.0], rc: [0.0, 1.0], ..Default::default()
964 },
965 ..Default::default()
966 });
967
968 let _ = state.process(&input1);
972
973 let result = state.process(&input2);
977 if let NDDataBuffer::U8(ref v) = result.data {
978 assert_eq!(v[0], 100);
980 assert_eq!(v[1], 100);
981 }
982 }
983
984 #[test]
985 fn test_output_type_conversion() {
986 let input = make_array(&[10, 20, 30]);
987 let mut state = ProcessState::new(ProcessConfig {
988 output_type: Some(NDDataType::Float64),
989 ..Default::default()
990 });
991
992 let result = state.process(&input);
993 assert_eq!(result.data.data_type(), NDDataType::Float64);
994 }
995
996 #[test]
999 fn test_process_processor() {
1000 let mut proc = ProcessProcessor::new(ProcessConfig {
1001 enable_offset_scale: true,
1002 scale: 2.0,
1003 offset: 1.0,
1004 ..Default::default()
1005 });
1006 let pool = NDArrayPool::new(1_000_000);
1007
1008 let input = make_array(&[10, 20, 30]);
1009 let result = proc.process_array(&input, &pool);
1010 assert_eq!(result.output_arrays.len(), 1);
1011 if let NDDataBuffer::U8(ref v) = result.output_arrays[0].data {
1012 assert_eq!(v[0], 22); }
1014 }
1015
1016 #[test]
1019 fn test_filter_sum_preset() {
1020 let mut state = ProcessState::new(ProcessConfig {
1023 enable_filter: true,
1024 filter: FilterConfig {
1025 num_filter: 10,
1026 fc: [1.0, 0.0, 1.0, 0.0],
1027 oc: [1.0, 0.0, 0.0, 0.0],
1028 rc: [0.0, 1.0],
1029 ..Default::default()
1030 },
1031 output_type: Some(NDDataType::Float64),
1032 ..Default::default()
1033 });
1034
1035 let r0 = state.process(&make_f64_array(&[100.0]));
1044 let v0 = r0.data.get_as_f64(0).unwrap();
1045 assert!((v0 - 100.0).abs() < 1e-9, "frame 0: got {v0}");
1046
1047 let r1 = state.process(&make_f64_array(&[100.0]));
1054 let v1 = r1.data.get_as_f64(0).unwrap();
1055 assert!((v1 - 200.0).abs() < 1e-9, "frame 1: got {v1}");
1056 }
1057
1058 #[test]
1059 fn test_filter_average_preset() {
1060 let mut state = ProcessState::new(ProcessConfig {
1063 enable_filter: true,
1064 filter: FilterConfig {
1065 num_filter: 10,
1066 fc: [1.0, 0.0, 1.0, 0.0],
1067 oc: [0.0, 1.0, 0.0, 0.0],
1068 rc: [0.0, 1.0],
1069 ..Default::default()
1070 },
1071 output_type: Some(NDDataType::Float64),
1072 ..Default::default()
1073 });
1074
1075 let r0 = state.process(&make_f64_array(&[100.0]));
1080 let v0 = r0.data.get_as_f64(0).unwrap();
1081 assert!((v0 - 100.0).abs() < 1e-9, "frame 0: got {v0}");
1082
1083 let r1 = state.process(&make_f64_array(&[200.0]));
1088 let v1 = r1.data.get_as_f64(0).unwrap();
1089 assert!((v1 - 100.0).abs() < 1e-9, "frame 1: got {v1}");
1090
1091 let r2 = state.process(&make_f64_array(&[300.0]));
1106 let v2 = r2.data.get_as_f64(0).unwrap();
1107 let expected = 400.0 / 3.0; assert!((v2 - expected).abs() < 1e-9, "frame 2: got {v2}");
1109 }
1110
1111 #[test]
1112 fn test_filter_recursive_ave() {
1113 let mut state = ProcessState::new(ProcessConfig {
1118 enable_filter: true,
1119 filter: FilterConfig {
1120 num_filter: 10,
1121 fc: [1.0, -1.0, 0.0, 1.0],
1122 oc: [1.0, 0.0, 0.0, 0.0],
1123 rc: [0.0, 1.0],
1124 ..Default::default()
1125 },
1126 output_type: Some(NDDataType::Float64),
1127 ..Default::default()
1128 });
1129
1130 let r0 = state.process(&make_f64_array(&[100.0]));
1136 let v0 = r0.data.get_as_f64(0).unwrap();
1137 assert!((v0 - 100.0).abs() < 1e-9, "frame 0: got {v0}");
1138
1139 let r1 = state.process(&make_f64_array(&[200.0]));
1144 let v1 = r1.data.get_as_f64(0).unwrap();
1145 assert!((v1 - 100.0).abs() < 1e-9, "frame 1: got {v1}");
1146
1147 let r2 = state.process(&make_f64_array(&[300.0]));
1152 let v2 = r2.data.get_as_f64(0).unwrap();
1153 assert!((v2 - 150.0).abs() < 1e-9, "frame 2: got {v2}");
1154 }
1155
1156 #[test]
1157 fn test_save_background_one_shot() {
1158 let mut state = ProcessState::new(ProcessConfig {
1159 save_background: true,
1160 ..Default::default()
1161 });
1162
1163 assert!(!state.config.valid_background);
1164 assert!(state.background.is_none());
1165
1166 let input = make_array(&[10, 20, 30]);
1168 let _ = state.process(&input);
1169
1170 assert!(
1171 !state.config.save_background,
1172 "save_background should be cleared"
1173 );
1174 assert!(
1175 state.config.valid_background,
1176 "valid_background should be set"
1177 );
1178 assert!(state.background.is_some());
1179
1180 let bg = state.background.as_ref().unwrap();
1181 assert_eq!(bg.len(), 3);
1182 assert!((bg[0] - 10.0).abs() < 1e-9);
1183 assert!((bg[1] - 20.0).abs() < 1e-9);
1184 assert!((bg[2] - 30.0).abs() < 1e-9);
1185
1186 let input2 = make_array(&[40, 50, 60]);
1188 let _ = state.process(&input2);
1189
1190 assert!(
1191 !state.config.save_background,
1192 "save_background stays cleared"
1193 );
1194 let bg2 = state.background.as_ref().unwrap();
1196 assert!((bg2[0] - 10.0).abs() < 1e-9);
1197 }
1198
1199 #[test]
1200 fn test_save_flat_field_one_shot() {
1201 let mut state = ProcessState::new(ProcessConfig {
1202 save_flat_field: true,
1203 ..Default::default()
1204 });
1205
1206 assert!(!state.config.valid_flat_field);
1207 assert!(state.flat_field.is_none());
1208
1209 let input = make_array(&[50, 100, 150]);
1210 let _ = state.process(&input);
1211
1212 assert!(
1213 !state.config.save_flat_field,
1214 "save_flat_field should be cleared"
1215 );
1216 assert!(
1217 state.config.valid_flat_field,
1218 "valid_flat_field should be set"
1219 );
1220 assert!(state.flat_field.is_some());
1221
1222 let ff = state.flat_field.as_ref().unwrap();
1223 assert_eq!(ff.len(), 3);
1224 assert!((ff[0] - 50.0).abs() < 1e-9);
1225 assert!((ff[1] - 100.0).abs() < 1e-9);
1226 assert!((ff[2] - 150.0).abs() < 1e-9);
1227 }
1228
1229 #[test]
1230 fn test_auto_reset_when_num_filter_reached() {
1231 let mut state = ProcessState::new(ProcessConfig {
1233 enable_filter: true,
1234 filter: FilterConfig {
1235 num_filter: 3,
1236 auto_reset: true,
1237 fc: [1.0, 0.0, 1.0, 0.0], oc: [1.0, 0.0, 0.0, 0.0],
1239 rc: [0.0, 1.0],
1240 ..Default::default()
1241 },
1242 output_type: Some(NDDataType::Float64),
1243 ..Default::default()
1244 });
1245
1246 let _ = state.process(&make_f64_array(&[100.0]));
1248 assert_eq!(state.num_filtered, 1);
1249
1250 let _ = state.process(&make_f64_array(&[100.0]));
1252 assert_eq!(state.num_filtered, 2);
1253
1254 let _ = state.process(&make_f64_array(&[100.0]));
1256 assert_eq!(state.num_filtered, 3);
1257
1258 let _ = state.process(&make_f64_array(&[200.0]));
1260 assert_eq!(state.num_filtered, 1, "fresh start after auto reset");
1262 }
1263
1264 #[test]
1265 fn test_filter_with_offset_scale() {
1266 let mut state = ProcessState::new(ProcessConfig {
1270 enable_filter: true,
1271 filter: FilterConfig {
1272 num_filter: 10,
1273 fc: [0.0, 0.0, 1.0, 0.0], oc: [1.0, 0.0, 0.0, 0.0], rc: [0.0, 1.0],
1276 f_offset: 10.0,
1277 f_scale: 2.0,
1278 o_offset: 5.0,
1279 o_scale: 3.0,
1280 ..Default::default()
1281 },
1282 output_type: Some(NDDataType::Float64),
1283 ..Default::default()
1284 });
1285
1286 let r0 = state.process(&make_f64_array(&[50.0]));
1291 let v0 = r0.data.get_as_f64(0).unwrap();
1292 assert!((v0 - 155.0).abs() < 1e-9, "frame 0: got {v0}");
1293
1294 let r1 = state.process(&make_f64_array(&[20.0]));
1299 let v1 = r1.data.get_as_f64(0).unwrap();
1300 assert!((v1 - 335.0).abs() < 1e-9, "frame 1: got {v1}");
1301 }
1302
1303 #[test]
1304 fn test_reset_filter_manual() {
1305 let mut state = ProcessState::new(ProcessConfig {
1306 enable_filter: true,
1307 filter: FilterConfig {
1308 num_filter: 10,
1309 fc: [1.0, 0.0, 1.0, 0.0],
1310 oc: [1.0, 0.0, 0.0, 0.0],
1311 rc: [0.0, 1.0],
1312 ..Default::default()
1313 },
1314 output_type: Some(NDDataType::Float64),
1315 ..Default::default()
1316 });
1317
1318 let _ = state.process(&make_f64_array(&[100.0]));
1320 let _ = state.process(&make_f64_array(&[100.0]));
1321 assert!(state.filter_state.is_some());
1322 assert_eq!(state.num_filtered, 2);
1323
1324 state.reset_filter();
1326 assert!(state.filter_state.is_none());
1327 assert_eq!(state.num_filtered, 0);
1328
1329 let _ = state.process(&make_f64_array(&[200.0]));
1331 assert_eq!(state.num_filtered, 1);
1332 }
1333}