1use std::sync::Arc;
2
3use ad_core_rs::ndarray::{NDArray, NDDataBuffer, NDDataType, NDDimension};
4use ad_core_rs::ndarray_pool::NDArrayPool;
5use ad_core_rs::plugin::runtime::{
6 NDPluginProcess, ParamUpdate, PluginParamSnapshot, ProcessResult,
7};
8use asyn_rs::param::ParamType;
9use asyn_rs::port::PortDriverBase;
10
11#[derive(Debug, Clone)]
13pub struct ROIDimConfig {
14 pub min: usize,
15 pub size: usize,
16 pub bin: usize,
17 pub reverse: bool,
18 pub enable: bool,
19 pub auto_size: bool,
21}
22
23impl Default for ROIDimConfig {
24 fn default() -> Self {
25 Self {
26 min: 0,
27 size: 0,
28 bin: 1,
29 reverse: false,
30 enable: true,
31 auto_size: false,
32 }
33 }
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub enum AutoCenter {
39 None,
40 CenterOfMass,
41 PeakPosition,
42}
43
44#[derive(Debug, Clone)]
46pub struct ROIConfig {
47 pub dims: [ROIDimConfig; 3],
48 pub data_type: Option<NDDataType>,
49 pub enable_scale: bool,
50 pub scale: f64,
51 pub collapse_dims: bool,
52 pub autocenter: AutoCenter,
53}
54
55impl Default for ROIConfig {
56 fn default() -> Self {
57 Self {
58 dims: [
59 ROIDimConfig::default(),
60 ROIDimConfig::default(),
61 ROIDimConfig::default(),
62 ],
63 data_type: None,
64 enable_scale: false,
65 scale: 1.0,
66 collapse_dims: false,
67 autocenter: AutoCenter::None,
68 }
69 }
70}
71
72fn find_centroid_2d(data: &NDDataBuffer, x_size: usize, y_size: usize) -> (usize, usize) {
74 let mut cx = 0.0f64;
75 let mut cy = 0.0f64;
76 let mut total = 0.0f64;
77 for iy in 0..y_size {
78 for ix in 0..x_size {
79 let val = data.get_as_f64(iy * x_size + ix).unwrap_or(0.0);
80 total += val;
81 cx += val * ix as f64;
82 cy += val * iy as f64;
83 }
84 }
85 if total > 0.0 {
86 ((cx / total) as usize, (cy / total) as usize)
87 } else {
88 (x_size / 2, y_size / 2)
89 }
90}
91
92fn find_peak_2d(data: &NDDataBuffer, x_size: usize, y_size: usize) -> (usize, usize) {
94 let mut max_val = f64::NEG_INFINITY;
95 let mut max_x = 0;
96 let mut max_y = 0;
97 for iy in 0..y_size {
98 for ix in 0..x_size {
99 let val = data.get_as_f64(iy * x_size + ix).unwrap_or(0.0);
100 if val > max_val {
101 max_val = val;
102 max_x = ix;
103 max_y = iy;
104 }
105 }
106 }
107 (max_x, max_y)
108}
109
110pub fn extract_roi(src: &NDArray, config: &ROIConfig) -> Option<NDArray> {
118 use ad_core_rs::color::NDColorMode;
119 if src.dims.len() >= 3 {
120 let info = src.info();
121 if matches!(
122 info.color_mode,
123 NDColorMode::RGB1 | NDColorMode::RGB2 | NDColorMode::RGB3
124 ) {
125 return extract_roi_3d(src, config);
126 }
127 }
128 extract_roi_2d(src, config)
129}
130
131pub fn extract_roi_3d(src: &NDArray, config: &ROIConfig) -> Option<NDArray> {
139 let info = src.info();
140 let (src_x, src_y, src_c) = (info.x_size, info.y_size, info.color_size.max(1));
141 if src_x == 0 || src_y == 0 || src_c == 0 {
142 return None;
143 }
144
145 let resolve = |cfg: &ROIDimConfig, dim_size: usize| -> (usize, usize) {
147 if !cfg.enable || dim_size == 0 {
148 return (0, dim_size);
149 }
150 let offset = cfg.min.min(dim_size - 1);
151 let size = if cfg.auto_size { dim_size } else { cfg.size };
152 let size = size.max(1).min(dim_size - offset);
153 (offset, size)
154 };
155 let (x_min, x_roi) = resolve(&config.dims[0], src_x);
156 let (y_min, y_roi) = resolve(&config.dims[1], src_y);
157 let (c_min, c_roi) = resolve(&config.dims[2], src_c);
158
159 let bin_x = config.dims[0].bin.max(1).min(x_roi);
160 let bin_y = config.dims[1].bin.max(1).min(y_roi);
161 let bin_c = config.dims[2].bin.max(1).min(c_roi);
162 let (out_x, out_y, out_c) = (x_roi / bin_x, y_roi / bin_y, c_roi / bin_c);
163 if out_x == 0 || out_y == 0 || out_c == 0 {
164 return None;
165 }
166
167 let (sxs, sys, scs) = (
169 info.x_stride.max(1),
170 info.y_stride.max(1),
171 info.color_stride.max(1),
172 );
173 let (dims, dxs, dys, dcs) = match info.color_mode {
175 ad_core_rs::color::NDColorMode::RGB1 => (
176 vec![
177 NDDimension::new(out_c),
178 NDDimension::new(out_x),
179 NDDimension::new(out_y),
180 ],
181 out_c,
182 out_x * out_c,
183 1usize,
184 ),
185 ad_core_rs::color::NDColorMode::RGB2 => (
186 vec![
187 NDDimension::new(out_x),
188 NDDimension::new(out_c),
189 NDDimension::new(out_y),
190 ],
191 1usize,
192 out_x * out_c,
193 out_x,
194 ),
195 _ => (
196 vec![
197 NDDimension::new(out_x),
198 NDDimension::new(out_y),
199 NDDimension::new(out_c),
200 ],
201 1usize,
202 out_x,
203 out_x * out_y,
204 ),
205 };
206 let total = out_x * out_y * out_c;
207
208 macro_rules! extract3d {
209 ($vec:expr, $T:ty, $zero:expr) => {{
210 let mut out = vec![$zero; total];
211 for oc in 0..out_c {
212 for oy in 0..out_y {
213 for ox in 0..out_x {
214 let mut sum = 0.0f64;
215 for bc in 0..bin_c {
216 for by in 0..bin_y {
217 for bx in 0..bin_x {
218 let sx = x_min + ox * bin_x + bx;
219 let sy = y_min + oy * bin_y + by;
220 let sc = c_min + oc * bin_c + bc;
221 sum += $vec[sy * sys + sx * sxs + sc * scs] as f64;
222 }
223 }
224 }
225 let dx = if config.dims[0].reverse {
226 out_x - 1 - ox
227 } else {
228 ox
229 };
230 let dy = if config.dims[1].reverse {
231 out_y - 1 - oy
232 } else {
233 oy
234 };
235 let dc = if config.dims[2].reverse {
236 out_c - 1 - oc
237 } else {
238 oc
239 };
240 let scaled = if config.enable_scale && config.scale != 0.0 {
241 sum / config.scale
242 } else {
243 sum
244 };
245 out[dy * dys + dx * dxs + dc * dcs] = scaled as $T;
246 }
247 }
248 }
249 out
250 }};
251 }
252
253 let out_data = match &src.data {
254 NDDataBuffer::U8(v) => NDDataBuffer::U8(extract3d!(v, u8, 0)),
255 NDDataBuffer::U16(v) => NDDataBuffer::U16(extract3d!(v, u16, 0)),
256 NDDataBuffer::I8(v) => NDDataBuffer::I8(extract3d!(v, i8, 0)),
257 NDDataBuffer::I16(v) => NDDataBuffer::I16(extract3d!(v, i16, 0)),
258 NDDataBuffer::I32(v) => NDDataBuffer::I32(extract3d!(v, i32, 0)),
259 NDDataBuffer::U32(v) => NDDataBuffer::U32(extract3d!(v, u32, 0)),
260 NDDataBuffer::I64(v) => NDDataBuffer::I64(extract3d!(v, i64, 0)),
261 NDDataBuffer::U64(v) => NDDataBuffer::U64(extract3d!(v, u64, 0)),
262 NDDataBuffer::F32(v) => NDDataBuffer::F32(extract3d!(v, f32, 0.0)),
263 NDDataBuffer::F64(v) => NDDataBuffer::F64(extract3d!(v, f64, 0.0)),
264 };
265
266 let mut arr = NDArray::new(dims, src.data.data_type());
267 arr.data = out_data;
268 arr.unique_id = src.unique_id;
269 arr.timestamp = src.timestamp;
270 arr.time_stamp = src.time_stamp;
271 arr.attributes = src.attributes.clone();
272 Some(arr)
273}
274
275pub fn extract_roi_2d(src: &NDArray, config: &ROIConfig) -> Option<NDArray> {
277 if src.dims.len() < 2 {
278 return None;
279 }
280
281 let src_x = src.dims[0].size;
282 let src_y = src.dims[1].size;
283
284 let resolve = |cfg: &ROIDimConfig, dim_size: usize| -> (usize, usize) {
290 if !cfg.enable || dim_size == 0 {
291 return (0, dim_size);
292 }
293 let offset = cfg.min.min(dim_size - 1);
295 let size = if cfg.auto_size { dim_size } else { cfg.size };
296 let size = size.max(1).min(dim_size - offset);
298 (offset, size)
299 };
300 let (eff_x_min, eff_x_size) = resolve(&config.dims[0], src_x);
301 let (eff_y_min, eff_y_size) = resolve(&config.dims[1], src_y);
302
303 let (roi_x_min, roi_y_min) = match config.autocenter {
306 AutoCenter::None => (eff_x_min, eff_y_min),
307 AutoCenter::CenterOfMass => {
308 let (cx, cy) = find_centroid_2d(&src.data, src_x, src_y);
309 let mx = cx
310 .saturating_sub(eff_x_size / 2)
311 .min(src_x.saturating_sub(eff_x_size));
312 let my = cy
313 .saturating_sub(eff_y_size / 2)
314 .min(src_y.saturating_sub(eff_y_size));
315 (mx, my)
316 }
317 AutoCenter::PeakPosition => {
318 let (px, py) = find_peak_2d(&src.data, src_x, src_y);
319 let mx = px
320 .saturating_sub(eff_x_size / 2)
321 .min(src_x.saturating_sub(eff_x_size));
322 let my = py
323 .saturating_sub(eff_y_size / 2)
324 .min(src_y.saturating_sub(eff_y_size));
325 (mx, my)
326 }
327 };
328
329 let roi_x_size = eff_x_size;
330 let roi_y_size = eff_y_size;
331
332 if roi_x_size == 0 || roi_y_size == 0 {
333 return None;
334 }
335
336 let bin_x = config.dims[0].bin.max(1).min(roi_x_size);
340 let bin_y = config.dims[1].bin.max(1).min(roi_y_size);
341 let out_x = roi_x_size / bin_x;
342 let out_y = roi_y_size / bin_y;
343
344 if out_x == 0 || out_y == 0 {
345 return None;
346 }
347
348 macro_rules! extract {
349 ($vec:expr, $T:ty, $zero:expr) => {{
350 let mut out = vec![$zero; out_x * out_y];
351 for oy in 0..out_y {
352 for ox in 0..out_x {
353 let mut sum = 0.0f64;
354 let mut _count = 0usize;
355 for by in 0..bin_y {
356 for bx in 0..bin_x {
357 let sx = roi_x_min + ox * bin_x + bx;
358 let sy = roi_y_min + oy * bin_y + by;
359 if sx < src_x && sy < src_y {
360 sum += $vec[sy * src_x + sx] as f64;
361 _count += 1;
362 }
363 }
364 }
365 let val = sum;
367 let idx = if config.dims[0].reverse {
368 out_x - 1 - ox
369 } else {
370 ox
371 } + if config.dims[1].reverse {
372 out_y - 1 - oy
373 } else {
374 oy
375 } * out_x;
376 let scaled = if config.enable_scale && config.scale != 0.0 {
377 val / config.scale
378 } else {
379 val
380 };
381 out[idx] = scaled as $T;
382 }
383 }
384 out
385 }};
386 }
387
388 let out_data = match &src.data {
389 NDDataBuffer::U8(v) => NDDataBuffer::U8(extract!(v, u8, 0)),
390 NDDataBuffer::U16(v) => NDDataBuffer::U16(extract!(v, u16, 0)),
391 NDDataBuffer::I8(v) => NDDataBuffer::I8(extract!(v, i8, 0)),
392 NDDataBuffer::I16(v) => NDDataBuffer::I16(extract!(v, i16, 0)),
393 NDDataBuffer::I32(v) => NDDataBuffer::I32(extract!(v, i32, 0)),
394 NDDataBuffer::U32(v) => NDDataBuffer::U32(extract!(v, u32, 0)),
395 NDDataBuffer::I64(v) => NDDataBuffer::I64(extract!(v, i64, 0)),
396 NDDataBuffer::U64(v) => NDDataBuffer::U64(extract!(v, u64, 0)),
397 NDDataBuffer::F32(v) => NDDataBuffer::F32(extract!(v, f32, 0.0)),
398 NDDataBuffer::F64(v) => NDDataBuffer::F64(extract!(v, f64, 0.0)),
399 };
400
401 let out_dims = if config.collapse_dims {
402 let all_dims = vec![NDDimension::new(out_x), NDDimension::new(out_y)];
403 let filtered: Vec<NDDimension> = all_dims.into_iter().filter(|d| d.size > 1).collect();
404 if filtered.is_empty() {
405 vec![NDDimension::new(out_x)]
406 } else {
407 filtered
408 }
409 } else {
410 vec![NDDimension::new(out_x), NDDimension::new(out_y)]
411 };
412
413 let target_type = config.data_type.unwrap_or(src.data.data_type());
415
416 let mut arr = NDArray::new(out_dims, target_type);
417 if target_type == src.data.data_type() {
418 arr.data = out_data;
419 } else {
420 let mut temp = NDArray::new(arr.dims.clone(), src.data.data_type());
422 temp.data = out_data;
423 match ad_core_rs::color::convert_data_type(&temp, target_type) {
424 Ok(converted) => arr.data = converted.data,
425 Err(e) => {
426 tracing::warn!(
430 error = %e,
431 from = ?src.data.data_type(),
432 to = ?target_type,
433 "ROI output data-type conversion failed; dropping frame"
434 );
435 return None;
436 }
437 }
438 }
439
440 arr.unique_id = src.unique_id;
441 arr.timestamp = src.timestamp;
442 arr.attributes = src.attributes.clone();
443 Some(arr)
444}
445
446#[derive(Default, Clone, Copy)]
448pub struct ROIDimParams {
449 pub min: usize,
450 pub size: usize,
451 pub bin: usize,
452 pub reverse: usize,
453 pub enable: usize,
454 pub auto_size: usize,
455 pub max_size: usize,
456}
457
458#[derive(Default)]
460pub struct ROIParams {
461 pub dims: [ROIDimParams; 3],
462 pub enable_scale: usize,
463 pub scale: usize,
464 pub data_type: usize,
465 pub collapse_dims: usize,
466 pub name: usize,
467}
468
469pub struct ROIProcessor {
471 config: ROIConfig,
472 params: ROIParams,
473}
474
475impl ROIProcessor {
476 pub fn new(config: ROIConfig) -> Self {
477 Self {
478 config,
479 params: ROIParams::default(),
480 }
481 }
482
483 pub fn params(&self) -> &ROIParams {
485 &self.params
486 }
487}
488
489impl NDPluginProcess for ROIProcessor {
490 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
491 let mut updates = Vec::new();
493 for (i, dim_params) in self.params.dims.iter().enumerate() {
494 let dim_size = array.dims.get(i).map(|d| d.size as i32).unwrap_or(0);
495 updates.push(ParamUpdate::int32(dim_params.max_size, dim_size));
496 }
497
498 match extract_roi(array, &self.config) {
499 Some(roi_arr) => ProcessResult {
500 output_arrays: vec![Arc::new(roi_arr)],
501 param_updates: updates,
502 scatter_index: None,
503 },
504 None => ProcessResult::sink(updates),
505 }
506 }
507
508 fn plugin_type(&self) -> &str {
509 "NDPluginROI"
510 }
511
512 fn register_params(
513 &mut self,
514 base: &mut PortDriverBase,
515 ) -> Result<(), asyn_rs::error::AsynError> {
516 let dim_names = ["DIM0", "DIM1", "DIM2"];
517 for (i, prefix) in dim_names.iter().enumerate() {
518 self.params.dims[i].min =
519 base.create_param(&format!("{prefix}_MIN"), ParamType::Int32)?;
520 self.params.dims[i].size =
521 base.create_param(&format!("{prefix}_SIZE"), ParamType::Int32)?;
522 self.params.dims[i].bin =
523 base.create_param(&format!("{prefix}_BIN"), ParamType::Int32)?;
524 self.params.dims[i].reverse =
525 base.create_param(&format!("{prefix}_REVERSE"), ParamType::Int32)?;
526 self.params.dims[i].enable =
527 base.create_param(&format!("{prefix}_ENABLE"), ParamType::Int32)?;
528 self.params.dims[i].auto_size =
529 base.create_param(&format!("{prefix}_AUTO_SIZE"), ParamType::Int32)?;
530 self.params.dims[i].max_size =
531 base.create_param(&format!("{prefix}_MAX_SIZE"), ParamType::Int32)?;
532
533 base.set_int32_param(self.params.dims[i].min, 0, self.config.dims[i].min as i32)?;
535 base.set_int32_param(self.params.dims[i].size, 0, self.config.dims[i].size as i32)?;
536 base.set_int32_param(self.params.dims[i].bin, 0, self.config.dims[i].bin as i32)?;
537 base.set_int32_param(
538 self.params.dims[i].reverse,
539 0,
540 self.config.dims[i].reverse as i32,
541 )?;
542 base.set_int32_param(
543 self.params.dims[i].enable,
544 0,
545 self.config.dims[i].enable as i32,
546 )?;
547 base.set_int32_param(
548 self.params.dims[i].auto_size,
549 0,
550 self.config.dims[i].auto_size as i32,
551 )?;
552 }
553 self.params.enable_scale = base.create_param("ENABLE_SCALE", ParamType::Int32)?;
554 self.params.scale = base.create_param("SCALE_VALUE", ParamType::Float64)?;
555 self.params.data_type = base.create_param("ROI_DATA_TYPE", ParamType::Int32)?;
556 self.params.collapse_dims = base.create_param("COLLAPSE_DIMS", ParamType::Int32)?;
557 self.params.name = base.create_param("NAME", ParamType::Octet)?;
558
559 base.set_int32_param(self.params.enable_scale, 0, self.config.enable_scale as i32)?;
560 base.set_float64_param(self.params.scale, 0, self.config.scale)?;
561 base.set_int32_param(self.params.data_type, 0, -1)?; base.set_int32_param(
563 self.params.collapse_dims,
564 0,
565 self.config.collapse_dims as i32,
566 )?;
567
568 Ok(())
569 }
570
571 fn on_param_change(
572 &mut self,
573 reason: usize,
574 snapshot: &PluginParamSnapshot,
575 ) -> ad_core_rs::plugin::runtime::ParamChangeResult {
576 let p = &self.params;
577 for i in 0..3 {
578 if reason == p.dims[i].min {
579 self.config.dims[i].min = snapshot.value.as_i32().max(0) as usize;
580 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
581 }
582 if reason == p.dims[i].size {
583 self.config.dims[i].size = snapshot.value.as_i32().max(0) as usize;
584 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
585 }
586 if reason == p.dims[i].bin {
587 self.config.dims[i].bin = snapshot.value.as_i32().max(1) as usize;
588 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
589 }
590 if reason == p.dims[i].reverse {
591 self.config.dims[i].reverse = snapshot.value.as_i32() != 0;
592 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
593 }
594 if reason == p.dims[i].enable {
595 self.config.dims[i].enable = snapshot.value.as_i32() != 0;
596 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
597 }
598 if reason == p.dims[i].auto_size {
599 self.config.dims[i].auto_size = snapshot.value.as_i32() != 0;
600 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
601 }
602 }
603 if reason == p.enable_scale {
604 self.config.enable_scale = snapshot.value.as_i32() != 0;
605 } else if reason == p.scale {
606 self.config.scale = snapshot.value.as_f64();
607 } else if reason == p.data_type {
608 let v = snapshot.value.as_i32();
609 self.config.data_type = if v < 0 {
610 None
611 } else {
612 NDDataType::from_ordinal(v as u8)
613 };
614 } else if reason == p.collapse_dims {
615 self.config.collapse_dims = snapshot.value.as_i32() != 0;
616 }
617 ad_core_rs::plugin::runtime::ParamChangeResult::empty()
618 }
619}
620
621pub fn create_roi_runtime(
623 port_name: &str,
624 pool: Arc<NDArrayPool>,
625 queue_size: usize,
626 ndarray_port: &str,
627 wiring: Arc<ad_core_rs::plugin::wiring::WiringRegistry>,
628) -> (
629 ad_core_rs::plugin::runtime::PluginRuntimeHandle,
630 ROIParams,
631 std::thread::JoinHandle<()>,
632) {
633 let processor = ROIProcessor::new(ROIConfig::default());
634 let (handle, jh) = ad_core_rs::plugin::runtime::create_plugin_runtime(
635 port_name,
636 processor,
637 pool,
638 queue_size,
639 ndarray_port,
640 wiring,
641 );
642 let params = {
644 let mut base =
645 asyn_rs::port::PortDriverBase::new("_scratch_", 1, asyn_rs::port::PortFlags::default());
646 let _ = ad_core_rs::params::ndarray_driver::NDArrayDriverParams::create(&mut base);
647 let _ = ad_core_rs::plugin::params::PluginBaseParams::create(&mut base);
648 let mut p = ROIParams::default();
649 let dim_names = ["DIM0", "DIM1", "DIM2"];
650 for (i, prefix) in dim_names.iter().enumerate() {
651 p.dims[i].min = base
652 .create_param(&format!("{prefix}_MIN"), asyn_rs::param::ParamType::Int32)
653 .unwrap();
654 p.dims[i].size = base
655 .create_param(&format!("{prefix}_SIZE"), asyn_rs::param::ParamType::Int32)
656 .unwrap();
657 p.dims[i].bin = base
658 .create_param(&format!("{prefix}_BIN"), asyn_rs::param::ParamType::Int32)
659 .unwrap();
660 p.dims[i].reverse = base
661 .create_param(
662 &format!("{prefix}_REVERSE"),
663 asyn_rs::param::ParamType::Int32,
664 )
665 .unwrap();
666 p.dims[i].enable = base
667 .create_param(
668 &format!("{prefix}_ENABLE"),
669 asyn_rs::param::ParamType::Int32,
670 )
671 .unwrap();
672 p.dims[i].auto_size = base
673 .create_param(
674 &format!("{prefix}_AUTO_SIZE"),
675 asyn_rs::param::ParamType::Int32,
676 )
677 .unwrap();
678 p.dims[i].max_size = base
679 .create_param(
680 &format!("{prefix}_MAX_SIZE"),
681 asyn_rs::param::ParamType::Int32,
682 )
683 .unwrap();
684 }
685 p.enable_scale = base
686 .create_param("ENABLE_SCALE", asyn_rs::param::ParamType::Int32)
687 .unwrap();
688 p.scale = base
689 .create_param("SCALE_VALUE", asyn_rs::param::ParamType::Float64)
690 .unwrap();
691 p.data_type = base
692 .create_param("ROI_DATA_TYPE", asyn_rs::param::ParamType::Int32)
693 .unwrap();
694 p.collapse_dims = base
695 .create_param("COLLAPSE_DIMS", asyn_rs::param::ParamType::Int32)
696 .unwrap();
697 p.name = base
698 .create_param("NAME", asyn_rs::param::ParamType::Octet)
699 .unwrap();
700 p
701 };
702 (handle, params, jh)
703}
704
705#[cfg(test)]
706mod tests {
707 use super::*;
708
709 fn make_4x4_u8() -> NDArray {
710 let mut arr = NDArray::new(
711 vec![NDDimension::new(4), NDDimension::new(4)],
712 NDDataType::UInt8,
713 );
714 if let NDDataBuffer::U8(ref mut v) = arr.data {
715 for i in 0..16 {
716 v[i] = i as u8;
717 }
718 }
719 arr
720 }
721
722 #[test]
723 fn test_extract_sub_region() {
724 let arr = make_4x4_u8();
725 let mut config = ROIConfig::default();
726 config.dims[0] = ROIDimConfig {
727 min: 1,
728 size: 2,
729 bin: 1,
730 reverse: false,
731 enable: true,
732 auto_size: false,
733 };
734 config.dims[1] = ROIDimConfig {
735 min: 1,
736 size: 2,
737 bin: 1,
738 reverse: false,
739 enable: true,
740 auto_size: false,
741 };
742
743 let roi = extract_roi_2d(&arr, &config).unwrap();
744 assert_eq!(roi.dims[0].size, 2);
745 assert_eq!(roi.dims[1].size, 2);
746 if let NDDataBuffer::U8(ref v) = roi.data {
747 assert_eq!(v[0], 5);
749 assert_eq!(v[1], 6);
750 assert_eq!(v[2], 9);
751 assert_eq!(v[3], 10);
752 }
753 }
754
755 #[test]
756 fn test_binning_2x2() {
757 let arr = make_4x4_u8();
758 let mut config = ROIConfig::default();
759 config.dims[0] = ROIDimConfig {
760 min: 0,
761 size: 4,
762 bin: 2,
763 reverse: false,
764 enable: true,
765 auto_size: false,
766 };
767 config.dims[1] = ROIDimConfig {
768 min: 0,
769 size: 4,
770 bin: 2,
771 reverse: false,
772 enable: true,
773 auto_size: false,
774 };
775
776 let roi = extract_roi_2d(&arr, &config).unwrap();
777 assert_eq!(roi.dims[0].size, 2);
778 assert_eq!(roi.dims[1].size, 2);
779 if let NDDataBuffer::U8(ref v) = roi.data {
780 assert_eq!(v[0], 10);
782 }
783 }
784
785 #[test]
786 fn test_reverse() {
787 let arr = make_4x4_u8();
788 let mut config = ROIConfig::default();
789 config.dims[0] = ROIDimConfig {
790 min: 0,
791 size: 4,
792 bin: 1,
793 reverse: true,
794 enable: true,
795 auto_size: false,
796 };
797 config.dims[1] = ROIDimConfig {
798 min: 0,
799 size: 1,
800 bin: 1,
801 reverse: false,
802 enable: true,
803 auto_size: false,
804 };
805
806 let roi = extract_roi_2d(&arr, &config).unwrap();
807 if let NDDataBuffer::U8(ref v) = roi.data {
808 assert_eq!(v[0], 3);
809 assert_eq!(v[1], 2);
810 assert_eq!(v[2], 1);
811 assert_eq!(v[3], 0);
812 }
813 }
814
815 #[test]
816 fn test_collapse_dims() {
817 let arr = make_4x4_u8();
818 let mut config = ROIConfig::default();
819 config.dims[0] = ROIDimConfig {
820 min: 0,
821 size: 4,
822 bin: 1,
823 reverse: false,
824 enable: true,
825 auto_size: false,
826 };
827 config.dims[1] = ROIDimConfig {
828 min: 0,
829 size: 1,
830 bin: 1,
831 reverse: false,
832 enable: true,
833 auto_size: false,
834 };
835 config.collapse_dims = true;
836
837 let roi = extract_roi_2d(&arr, &config).unwrap();
838 assert_eq!(roi.dims.len(), 1);
839 assert_eq!(roi.dims[0].size, 4);
840 }
841
842 #[test]
843 fn test_scale() {
844 let arr = make_4x4_u8();
845 let mut config = ROIConfig::default();
846 config.dims[0] = ROIDimConfig {
847 min: 0,
848 size: 2,
849 bin: 1,
850 reverse: false,
851 enable: true,
852 auto_size: false,
853 };
854 config.dims[1] = ROIDimConfig {
855 min: 0,
856 size: 1,
857 bin: 1,
858 reverse: false,
859 enable: true,
860 auto_size: false,
861 };
862 config.enable_scale = true;
863 config.scale = 2.0;
864
865 let roi = extract_roi_2d(&arr, &config).unwrap();
866 if let NDDataBuffer::U8(ref v) = roi.data {
867 assert_eq!(v[0], 0); assert_eq!(v[1], 0); }
871 }
872
873 #[test]
874 fn test_type_convert() {
875 let arr = make_4x4_u8();
876 let mut config = ROIConfig::default();
877 config.dims[0] = ROIDimConfig {
878 min: 0,
879 size: 2,
880 bin: 1,
881 reverse: false,
882 enable: true,
883 auto_size: false,
884 };
885 config.dims[1] = ROIDimConfig {
886 min: 0,
887 size: 1,
888 bin: 1,
889 reverse: false,
890 enable: true,
891 auto_size: false,
892 };
893 config.data_type = Some(NDDataType::UInt16);
894
895 let roi = extract_roi_2d(&arr, &config).unwrap();
896 assert_eq!(roi.data.data_type(), NDDataType::UInt16);
897 }
898
899 #[test]
902 fn test_roi_processor() {
903 let mut config = ROIConfig::default();
904 config.dims[0] = ROIDimConfig {
905 min: 1,
906 size: 2,
907 bin: 1,
908 reverse: false,
909 enable: true,
910 auto_size: false,
911 };
912 config.dims[1] = ROIDimConfig {
913 min: 1,
914 size: 2,
915 bin: 1,
916 reverse: false,
917 enable: true,
918 auto_size: false,
919 };
920
921 let mut proc = ROIProcessor::new(config);
922 let pool = NDArrayPool::new(1_000_000);
923
924 let arr = make_4x4_u8();
925 let result = proc.process_array(&arr, &pool);
926 assert_eq!(result.output_arrays.len(), 1);
927 assert_eq!(result.output_arrays[0].dims[0].size, 2);
928 assert_eq!(result.output_arrays[0].dims[1].size, 2);
929 }
930
931 #[test]
934 fn test_auto_size() {
935 let arr = make_4x4_u8();
937 let mut config = ROIConfig::default();
938 config.dims[0] = ROIDimConfig {
939 min: 1,
940 size: 0,
941 bin: 1,
942 reverse: false,
943 enable: true,
944 auto_size: true,
945 };
946 config.dims[1] = ROIDimConfig {
947 min: 0,
948 size: 0,
949 bin: 1,
950 reverse: false,
951 enable: true,
952 auto_size: true,
953 };
954
955 let roi = extract_roi_2d(&arr, &config).unwrap();
956 assert_eq!(roi.dims[0].size, 3);
960 assert_eq!(roi.dims[1].size, 4);
961 }
962
963 #[test]
964 fn test_dim_disable() {
965 let arr = make_4x4_u8();
967 let mut config = ROIConfig::default();
968 config.dims[0] = ROIDimConfig {
969 min: 2,
970 size: 1,
971 bin: 1,
972 reverse: false,
973 enable: false,
974 auto_size: false,
975 };
976 config.dims[1] = ROIDimConfig {
977 min: 0,
978 size: 4,
979 bin: 1,
980 reverse: false,
981 enable: true,
982 auto_size: false,
983 };
984
985 let roi = extract_roi_2d(&arr, &config).unwrap();
986 assert_eq!(roi.dims[0].size, 4);
988 assert_eq!(roi.dims[1].size, 4);
989 }
990
991 #[test]
992 fn test_autocenter_peak() {
993 let mut arr = NDArray::new(
995 vec![NDDimension::new(8), NDDimension::new(8)],
996 NDDataType::UInt8,
997 );
998 if let NDDataBuffer::U8(ref mut v) = arr.data {
999 for i in 0..64 {
1000 v[i] = 1;
1001 }
1002 v[5 * 8 + 6] = 255;
1004 }
1005
1006 let mut config = ROIConfig::default();
1007 config.dims[0] = ROIDimConfig {
1008 min: 0,
1009 size: 4,
1010 bin: 1,
1011 reverse: false,
1012 enable: true,
1013 auto_size: false,
1014 };
1015 config.dims[1] = ROIDimConfig {
1016 min: 0,
1017 size: 4,
1018 bin: 1,
1019 reverse: false,
1020 enable: true,
1021 auto_size: false,
1022 };
1023 config.autocenter = AutoCenter::PeakPosition;
1024
1025 let roi = extract_roi_2d(&arr, &config).unwrap();
1026 assert_eq!(roi.dims[0].size, 4);
1027 assert_eq!(roi.dims[1].size, 4);
1028
1029 if let NDDataBuffer::U8(ref v) = roi.data {
1035 assert_eq!(v[2 * 4 + 2], 255); }
1037 }
1038
1039 #[test]
1040 fn test_offset_clamp_to_last_column() {
1041 let arr = make_4x4_u8();
1045 let mut config = ROIConfig::default();
1046 config.dims[0] = ROIDimConfig {
1048 min: 4,
1049 size: 10,
1050 bin: 1,
1051 reverse: false,
1052 enable: true,
1053 auto_size: false,
1054 };
1055 config.dims[1] = ROIDimConfig {
1056 min: 0,
1057 size: 1,
1058 bin: 1,
1059 reverse: false,
1060 enable: true,
1061 auto_size: false,
1062 };
1063 let roi = extract_roi_2d(&arr, &config).unwrap();
1064 assert_eq!(roi.dims[0].size, 1);
1066 if let NDDataBuffer::U8(ref v) = roi.data {
1067 assert_eq!(v[0], 3); }
1069 }
1070
1071 #[test]
1072 fn test_bin_larger_than_roi_clamps() {
1073 let arr = make_4x4_u8();
1077 let mut config = ROIConfig::default();
1078 config.dims[0] = ROIDimConfig {
1079 min: 0,
1080 size: 2,
1081 bin: 99, reverse: false,
1083 enable: true,
1084 auto_size: false,
1085 };
1086 config.dims[1] = ROIDimConfig {
1087 min: 0,
1088 size: 1,
1089 bin: 1,
1090 reverse: false,
1091 enable: true,
1092 auto_size: false,
1093 };
1094 let roi = extract_roi_2d(&arr, &config).unwrap();
1095 assert_eq!(roi.dims[0].size, 1);
1097 if let NDDataBuffer::U8(ref v) = roi.data {
1098 assert_eq!(v[0], 1);
1100 }
1101 }
1102
1103 fn make_rgb1_2x2() -> NDArray {
1105 use ad_core_rs::attributes::{NDAttrSource, NDAttrValue, NDAttribute};
1106 let mut arr = NDArray::new(
1107 vec![
1108 NDDimension::new(3),
1109 NDDimension::new(2),
1110 NDDimension::new(2),
1111 ],
1112 NDDataType::UInt8,
1113 );
1114 arr.attributes.add(NDAttribute::new_static(
1115 "ColorMode",
1116 "",
1117 NDAttrSource::Driver,
1118 NDAttrValue::Int32(ad_core_rs::color::NDColorMode::RGB1 as i32),
1119 ));
1120 if let NDDataBuffer::U8(ref mut v) = arr.data {
1121 for y in 0..2 {
1122 for x in 0..2 {
1123 for c in 0..3 {
1124 v[y * 6 + x * 3 + c] = (100 * y + 10 * x + c) as u8;
1125 }
1126 }
1127 }
1128 }
1129 arr
1130 }
1131
1132 #[test]
1133 fn test_roi_3d_rgb1_x_subregion() {
1134 let arr = make_rgb1_2x2();
1136 let mut config = ROIConfig::default();
1137 config.dims[0] = ROIDimConfig {
1139 min: 1,
1140 size: 1,
1141 bin: 1,
1142 reverse: false,
1143 enable: true,
1144 auto_size: false,
1145 };
1146 config.dims[1] = ROIDimConfig {
1147 min: 0,
1148 size: 2,
1149 bin: 1,
1150 reverse: false,
1151 enable: true,
1152 auto_size: false,
1153 };
1154 config.dims[2] = ROIDimConfig {
1155 min: 0,
1156 size: 3,
1157 bin: 1,
1158 reverse: false,
1159 enable: true,
1160 auto_size: false,
1161 };
1162 let roi = extract_roi(&arr, &config).unwrap();
1163 assert_eq!(roi.dims[0].size, 3);
1165 assert_eq!(roi.dims[1].size, 1);
1166 assert_eq!(roi.dims[2].size, 2);
1167 if let NDDataBuffer::U8(ref v) = roi.data {
1168 assert_eq!(&v[0..3], &[10, 11, 12]);
1170 assert_eq!(&v[3..6], &[110, 111, 112]);
1172 } else {
1173 panic!("not u8");
1174 }
1175 }
1176}