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_2d(src: &NDArray, config: &ROIConfig) -> Option<NDArray> {
112 if src.dims.len() < 2 {
113 return None;
114 }
115
116 let src_x = src.dims[0].size;
117 let src_y = src.dims[1].size;
118
119 let (eff_x_min, eff_x_size) = if !config.dims[0].enable {
121 (0, src_x)
122 } else if config.dims[0].auto_size {
123 let min = config.dims[0].min.min(src_x);
124 (min, src_x.saturating_sub(min))
125 } else {
126 let min = config.dims[0].min.min(src_x);
127 let size = config.dims[0].size.min(src_x - min);
128 (min, size)
129 };
130
131 let (eff_y_min, eff_y_size) = if !config.dims[1].enable {
133 (0, src_y)
134 } else if config.dims[1].auto_size {
135 let min = config.dims[1].min.min(src_y);
136 (min, src_y.saturating_sub(min))
137 } else {
138 let min = config.dims[1].min.min(src_y);
139 let size = config.dims[1].size.min(src_y - min);
140 (min, size)
141 };
142
143 let (roi_x_min, roi_y_min) = match config.autocenter {
146 AutoCenter::None => (eff_x_min, eff_y_min),
147 AutoCenter::CenterOfMass => {
148 let (cx, cy) = find_centroid_2d(&src.data, src_x, src_y);
149 let mx = cx
150 .saturating_sub(eff_x_size / 2)
151 .min(src_x.saturating_sub(eff_x_size));
152 let my = cy
153 .saturating_sub(eff_y_size / 2)
154 .min(src_y.saturating_sub(eff_y_size));
155 (mx, my)
156 }
157 AutoCenter::PeakPosition => {
158 let (px, py) = find_peak_2d(&src.data, src_x, src_y);
159 let mx = px
160 .saturating_sub(eff_x_size / 2)
161 .min(src_x.saturating_sub(eff_x_size));
162 let my = py
163 .saturating_sub(eff_y_size / 2)
164 .min(src_y.saturating_sub(eff_y_size));
165 (mx, my)
166 }
167 };
168
169 let roi_x_size = eff_x_size;
170 let roi_y_size = eff_y_size;
171
172 if roi_x_size == 0 || roi_y_size == 0 {
173 return None;
174 }
175
176 let bin_x = config.dims[0].bin.max(1);
177 let bin_y = config.dims[1].bin.max(1);
178 let out_x = roi_x_size / bin_x;
179 let out_y = roi_y_size / bin_y;
180
181 if out_x == 0 || out_y == 0 {
182 return None;
183 }
184
185 macro_rules! extract {
186 ($vec:expr, $T:ty, $zero:expr) => {{
187 let mut out = vec![$zero; out_x * out_y];
188 for oy in 0..out_y {
189 for ox in 0..out_x {
190 let mut sum = 0.0f64;
191 let mut count = 0usize;
192 for by in 0..bin_y {
193 for bx in 0..bin_x {
194 let sx = roi_x_min + ox * bin_x + bx;
195 let sy = roi_y_min + oy * bin_y + by;
196 if sx < src_x && sy < src_y {
197 sum += $vec[sy * src_x + sx] as f64;
198 count += 1;
199 }
200 }
201 }
202 let val = if count > 0 { sum / count as f64 } else { 0.0 };
203 let idx = if config.dims[0].reverse {
204 out_x - 1 - ox
205 } else {
206 ox
207 } + if config.dims[1].reverse {
208 out_y - 1 - oy
209 } else {
210 oy
211 } * out_x;
212 let scaled = if config.enable_scale {
213 val * config.scale
214 } else {
215 val
216 };
217 out[idx] = scaled as $T;
218 }
219 }
220 out
221 }};
222 }
223
224 let out_data = match &src.data {
225 NDDataBuffer::U8(v) => NDDataBuffer::U8(extract!(v, u8, 0)),
226 NDDataBuffer::U16(v) => NDDataBuffer::U16(extract!(v, u16, 0)),
227 NDDataBuffer::I8(v) => NDDataBuffer::I8(extract!(v, i8, 0)),
228 NDDataBuffer::I16(v) => NDDataBuffer::I16(extract!(v, i16, 0)),
229 NDDataBuffer::I32(v) => NDDataBuffer::I32(extract!(v, i32, 0)),
230 NDDataBuffer::U32(v) => NDDataBuffer::U32(extract!(v, u32, 0)),
231 NDDataBuffer::I64(v) => NDDataBuffer::I64(extract!(v, i64, 0)),
232 NDDataBuffer::U64(v) => NDDataBuffer::U64(extract!(v, u64, 0)),
233 NDDataBuffer::F32(v) => NDDataBuffer::F32(extract!(v, f32, 0.0)),
234 NDDataBuffer::F64(v) => NDDataBuffer::F64(extract!(v, f64, 0.0)),
235 };
236
237 let out_dims = if config.collapse_dims && out_y == 1 {
238 vec![NDDimension::new(out_x)]
239 } else {
240 vec![NDDimension::new(out_x), NDDimension::new(out_y)]
241 };
242
243 let target_type = config.data_type.unwrap_or(src.data.data_type());
245
246 let mut arr = NDArray::new(out_dims, target_type);
247 if target_type == src.data.data_type() {
248 arr.data = out_data;
249 } else {
250 let mut temp = NDArray::new(arr.dims.clone(), src.data.data_type());
252 temp.data = out_data;
253 if let Ok(converted) = ad_core_rs::color::convert_data_type(&temp, target_type) {
254 arr.data = converted.data;
255 } else {
256 arr.data = out_data_fallback(&temp.data, target_type, temp.data.len());
257 }
258 }
259
260 arr.unique_id = src.unique_id;
261 arr.timestamp = src.timestamp;
262 arr.attributes = src.attributes.clone();
263 Some(arr)
264}
265
266fn out_data_fallback(_src: &NDDataBuffer, target: NDDataType, len: usize) -> NDDataBuffer {
267 NDDataBuffer::zeros(target, len)
268}
269
270#[derive(Default, Clone, Copy)]
272pub struct ROIDimParams {
273 pub min: usize,
274 pub size: usize,
275 pub bin: usize,
276 pub reverse: usize,
277 pub enable: usize,
278 pub auto_size: usize,
279 pub max_size: usize,
280}
281
282#[derive(Default)]
284pub struct ROIParams {
285 pub dims: [ROIDimParams; 3],
286 pub enable_scale: usize,
287 pub scale: usize,
288 pub data_type: usize,
289 pub collapse_dims: usize,
290 pub name: usize,
291}
292
293pub struct ROIProcessor {
295 config: ROIConfig,
296 params: ROIParams,
297}
298
299impl ROIProcessor {
300 pub fn new(config: ROIConfig) -> Self {
301 Self {
302 config,
303 params: ROIParams::default(),
304 }
305 }
306
307 pub fn params(&self) -> &ROIParams {
309 &self.params
310 }
311}
312
313impl NDPluginProcess for ROIProcessor {
314 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
315 let mut updates = Vec::new();
317 for (i, dim_params) in self.params.dims.iter().enumerate() {
318 let dim_size = array.dims.get(i).map(|d| d.size as i32).unwrap_or(0);
319 updates.push(ParamUpdate::int32(dim_params.max_size, dim_size));
320 }
321
322 match extract_roi_2d(array, &self.config) {
323 Some(roi_arr) => ProcessResult {
324 output_arrays: vec![Arc::new(roi_arr)],
325 param_updates: updates,
326 scatter_index: None,
327 },
328 None => ProcessResult::sink(updates),
329 }
330 }
331
332 fn plugin_type(&self) -> &str {
333 "NDPluginROI"
334 }
335
336 fn register_params(
337 &mut self,
338 base: &mut PortDriverBase,
339 ) -> Result<(), asyn_rs::error::AsynError> {
340 let dim_names = ["DIM0", "DIM1", "DIM2"];
341 for (i, prefix) in dim_names.iter().enumerate() {
342 self.params.dims[i].min =
343 base.create_param(&format!("{prefix}_MIN"), ParamType::Int32)?;
344 self.params.dims[i].size =
345 base.create_param(&format!("{prefix}_SIZE"), ParamType::Int32)?;
346 self.params.dims[i].bin =
347 base.create_param(&format!("{prefix}_BIN"), ParamType::Int32)?;
348 self.params.dims[i].reverse =
349 base.create_param(&format!("{prefix}_REVERSE"), ParamType::Int32)?;
350 self.params.dims[i].enable =
351 base.create_param(&format!("{prefix}_ENABLE"), ParamType::Int32)?;
352 self.params.dims[i].auto_size =
353 base.create_param(&format!("{prefix}_AUTO_SIZE"), ParamType::Int32)?;
354 self.params.dims[i].max_size =
355 base.create_param(&format!("{prefix}_MAX_SIZE"), ParamType::Int32)?;
356
357 base.set_int32_param(self.params.dims[i].min, 0, self.config.dims[i].min as i32)?;
359 base.set_int32_param(self.params.dims[i].size, 0, self.config.dims[i].size as i32)?;
360 base.set_int32_param(self.params.dims[i].bin, 0, self.config.dims[i].bin as i32)?;
361 base.set_int32_param(
362 self.params.dims[i].reverse,
363 0,
364 self.config.dims[i].reverse as i32,
365 )?;
366 base.set_int32_param(
367 self.params.dims[i].enable,
368 0,
369 self.config.dims[i].enable as i32,
370 )?;
371 base.set_int32_param(
372 self.params.dims[i].auto_size,
373 0,
374 self.config.dims[i].auto_size as i32,
375 )?;
376 }
377 self.params.enable_scale = base.create_param("ENABLE_SCALE", ParamType::Int32)?;
378 self.params.scale = base.create_param("SCALE_VALUE", ParamType::Float64)?;
379 self.params.data_type = base.create_param("ROI_DATA_TYPE", ParamType::Int32)?;
380 self.params.collapse_dims = base.create_param("COLLAPSE_DIMS", ParamType::Int32)?;
381 self.params.name = base.create_param("NAME", ParamType::Octet)?;
382
383 base.set_int32_param(self.params.enable_scale, 0, self.config.enable_scale as i32)?;
384 base.set_float64_param(self.params.scale, 0, self.config.scale)?;
385 base.set_int32_param(self.params.data_type, 0, -1)?; base.set_int32_param(
387 self.params.collapse_dims,
388 0,
389 self.config.collapse_dims as i32,
390 )?;
391
392 Ok(())
393 }
394
395 fn on_param_change(
396 &mut self,
397 reason: usize,
398 snapshot: &PluginParamSnapshot,
399 ) -> ad_core_rs::plugin::runtime::ParamChangeResult {
400 let p = &self.params;
401 for i in 0..3 {
402 if reason == p.dims[i].min {
403 self.config.dims[i].min = snapshot.value.as_i32().max(0) as usize;
404 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
405 }
406 if reason == p.dims[i].size {
407 self.config.dims[i].size = snapshot.value.as_i32().max(0) as usize;
408 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
409 }
410 if reason == p.dims[i].bin {
411 self.config.dims[i].bin = snapshot.value.as_i32().max(1) as usize;
412 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
413 }
414 if reason == p.dims[i].reverse {
415 self.config.dims[i].reverse = snapshot.value.as_i32() != 0;
416 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
417 }
418 if reason == p.dims[i].enable {
419 self.config.dims[i].enable = snapshot.value.as_i32() != 0;
420 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
421 }
422 if reason == p.dims[i].auto_size {
423 self.config.dims[i].auto_size = snapshot.value.as_i32() != 0;
424 return ad_core_rs::plugin::runtime::ParamChangeResult::empty();
425 }
426 }
427 if reason == p.enable_scale {
428 self.config.enable_scale = snapshot.value.as_i32() != 0;
429 } else if reason == p.scale {
430 self.config.scale = snapshot.value.as_f64();
431 } else if reason == p.data_type {
432 let v = snapshot.value.as_i32();
433 self.config.data_type = if v < 0 {
434 None
435 } else {
436 NDDataType::from_ordinal(v as u8)
437 };
438 } else if reason == p.collapse_dims {
439 self.config.collapse_dims = snapshot.value.as_i32() != 0;
440 }
441 ad_core_rs::plugin::runtime::ParamChangeResult::empty()
442 }
443}
444
445pub fn create_roi_runtime(
447 port_name: &str,
448 pool: Arc<NDArrayPool>,
449 queue_size: usize,
450 ndarray_port: &str,
451 wiring: Arc<ad_core_rs::plugin::wiring::WiringRegistry>,
452) -> (
453 ad_core_rs::plugin::runtime::PluginRuntimeHandle,
454 ROIParams,
455 std::thread::JoinHandle<()>,
456) {
457 let processor = ROIProcessor::new(ROIConfig::default());
458 let (handle, jh) = ad_core_rs::plugin::runtime::create_plugin_runtime(
459 port_name,
460 processor,
461 pool,
462 queue_size,
463 ndarray_port,
464 wiring,
465 );
466 let params = {
468 let mut base =
469 asyn_rs::port::PortDriverBase::new("_scratch_", 1, asyn_rs::port::PortFlags::default());
470 let _ = ad_core_rs::params::ndarray_driver::NDArrayDriverParams::create(&mut base);
471 let _ = ad_core_rs::plugin::params::PluginBaseParams::create(&mut base);
472 let mut p = ROIParams::default();
473 let dim_names = ["DIM0", "DIM1", "DIM2"];
474 for (i, prefix) in dim_names.iter().enumerate() {
475 p.dims[i].min = base
476 .create_param(&format!("{prefix}_MIN"), asyn_rs::param::ParamType::Int32)
477 .unwrap();
478 p.dims[i].size = base
479 .create_param(&format!("{prefix}_SIZE"), asyn_rs::param::ParamType::Int32)
480 .unwrap();
481 p.dims[i].bin = base
482 .create_param(&format!("{prefix}_BIN"), asyn_rs::param::ParamType::Int32)
483 .unwrap();
484 p.dims[i].reverse = base
485 .create_param(
486 &format!("{prefix}_REVERSE"),
487 asyn_rs::param::ParamType::Int32,
488 )
489 .unwrap();
490 p.dims[i].enable = base
491 .create_param(
492 &format!("{prefix}_ENABLE"),
493 asyn_rs::param::ParamType::Int32,
494 )
495 .unwrap();
496 p.dims[i].auto_size = base
497 .create_param(
498 &format!("{prefix}_AUTO_SIZE"),
499 asyn_rs::param::ParamType::Int32,
500 )
501 .unwrap();
502 p.dims[i].max_size = base
503 .create_param(
504 &format!("{prefix}_MAX_SIZE"),
505 asyn_rs::param::ParamType::Int32,
506 )
507 .unwrap();
508 }
509 p.enable_scale = base
510 .create_param("ENABLE_SCALE", asyn_rs::param::ParamType::Int32)
511 .unwrap();
512 p.scale = base
513 .create_param("SCALE_VALUE", asyn_rs::param::ParamType::Float64)
514 .unwrap();
515 p.data_type = base
516 .create_param("ROI_DATA_TYPE", asyn_rs::param::ParamType::Int32)
517 .unwrap();
518 p.collapse_dims = base
519 .create_param("COLLAPSE_DIMS", asyn_rs::param::ParamType::Int32)
520 .unwrap();
521 p.name = base
522 .create_param("NAME", asyn_rs::param::ParamType::Octet)
523 .unwrap();
524 p
525 };
526 (handle, params, jh)
527}
528
529#[cfg(test)]
530mod tests {
531 use super::*;
532
533 fn make_4x4_u8() -> NDArray {
534 let mut arr = NDArray::new(
535 vec![NDDimension::new(4), NDDimension::new(4)],
536 NDDataType::UInt8,
537 );
538 if let NDDataBuffer::U8(ref mut v) = arr.data {
539 for i in 0..16 {
540 v[i] = i as u8;
541 }
542 }
543 arr
544 }
545
546 #[test]
547 fn test_extract_sub_region() {
548 let arr = make_4x4_u8();
549 let mut config = ROIConfig::default();
550 config.dims[0] = ROIDimConfig {
551 min: 1,
552 size: 2,
553 bin: 1,
554 reverse: false,
555 enable: true,
556 auto_size: false,
557 };
558 config.dims[1] = ROIDimConfig {
559 min: 1,
560 size: 2,
561 bin: 1,
562 reverse: false,
563 enable: true,
564 auto_size: false,
565 };
566
567 let roi = extract_roi_2d(&arr, &config).unwrap();
568 assert_eq!(roi.dims[0].size, 2);
569 assert_eq!(roi.dims[1].size, 2);
570 if let NDDataBuffer::U8(ref v) = roi.data {
571 assert_eq!(v[0], 5);
573 assert_eq!(v[1], 6);
574 assert_eq!(v[2], 9);
575 assert_eq!(v[3], 10);
576 }
577 }
578
579 #[test]
580 fn test_binning_2x2() {
581 let arr = make_4x4_u8();
582 let mut config = ROIConfig::default();
583 config.dims[0] = ROIDimConfig {
584 min: 0,
585 size: 4,
586 bin: 2,
587 reverse: false,
588 enable: true,
589 auto_size: false,
590 };
591 config.dims[1] = ROIDimConfig {
592 min: 0,
593 size: 4,
594 bin: 2,
595 reverse: false,
596 enable: true,
597 auto_size: false,
598 };
599
600 let roi = extract_roi_2d(&arr, &config).unwrap();
601 assert_eq!(roi.dims[0].size, 2);
602 assert_eq!(roi.dims[1].size, 2);
603 if let NDDataBuffer::U8(ref v) = roi.data {
604 assert_eq!(v[0], 2);
606 }
607 }
608
609 #[test]
610 fn test_reverse() {
611 let arr = make_4x4_u8();
612 let mut config = ROIConfig::default();
613 config.dims[0] = ROIDimConfig {
614 min: 0,
615 size: 4,
616 bin: 1,
617 reverse: true,
618 enable: true,
619 auto_size: false,
620 };
621 config.dims[1] = ROIDimConfig {
622 min: 0,
623 size: 1,
624 bin: 1,
625 reverse: false,
626 enable: true,
627 auto_size: false,
628 };
629
630 let roi = extract_roi_2d(&arr, &config).unwrap();
631 if let NDDataBuffer::U8(ref v) = roi.data {
632 assert_eq!(v[0], 3);
633 assert_eq!(v[1], 2);
634 assert_eq!(v[2], 1);
635 assert_eq!(v[3], 0);
636 }
637 }
638
639 #[test]
640 fn test_collapse_dims() {
641 let arr = make_4x4_u8();
642 let mut config = ROIConfig::default();
643 config.dims[0] = ROIDimConfig {
644 min: 0,
645 size: 4,
646 bin: 1,
647 reverse: false,
648 enable: true,
649 auto_size: false,
650 };
651 config.dims[1] = ROIDimConfig {
652 min: 0,
653 size: 1,
654 bin: 1,
655 reverse: false,
656 enable: true,
657 auto_size: false,
658 };
659 config.collapse_dims = true;
660
661 let roi = extract_roi_2d(&arr, &config).unwrap();
662 assert_eq!(roi.dims.len(), 1);
663 assert_eq!(roi.dims[0].size, 4);
664 }
665
666 #[test]
667 fn test_scale() {
668 let arr = make_4x4_u8();
669 let mut config = ROIConfig::default();
670 config.dims[0] = ROIDimConfig {
671 min: 0,
672 size: 2,
673 bin: 1,
674 reverse: false,
675 enable: true,
676 auto_size: false,
677 };
678 config.dims[1] = ROIDimConfig {
679 min: 0,
680 size: 1,
681 bin: 1,
682 reverse: false,
683 enable: true,
684 auto_size: false,
685 };
686 config.enable_scale = true;
687 config.scale = 2.0;
688
689 let roi = extract_roi_2d(&arr, &config).unwrap();
690 if let NDDataBuffer::U8(ref v) = roi.data {
691 assert_eq!(v[0], 0); assert_eq!(v[1], 2); }
694 }
695
696 #[test]
697 fn test_type_convert() {
698 let arr = make_4x4_u8();
699 let mut config = ROIConfig::default();
700 config.dims[0] = ROIDimConfig {
701 min: 0,
702 size: 2,
703 bin: 1,
704 reverse: false,
705 enable: true,
706 auto_size: false,
707 };
708 config.dims[1] = ROIDimConfig {
709 min: 0,
710 size: 1,
711 bin: 1,
712 reverse: false,
713 enable: true,
714 auto_size: false,
715 };
716 config.data_type = Some(NDDataType::UInt16);
717
718 let roi = extract_roi_2d(&arr, &config).unwrap();
719 assert_eq!(roi.data.data_type(), NDDataType::UInt16);
720 }
721
722 #[test]
725 fn test_roi_processor() {
726 let mut config = ROIConfig::default();
727 config.dims[0] = ROIDimConfig {
728 min: 1,
729 size: 2,
730 bin: 1,
731 reverse: false,
732 enable: true,
733 auto_size: false,
734 };
735 config.dims[1] = ROIDimConfig {
736 min: 1,
737 size: 2,
738 bin: 1,
739 reverse: false,
740 enable: true,
741 auto_size: false,
742 };
743
744 let mut proc = ROIProcessor::new(config);
745 let pool = NDArrayPool::new(1_000_000);
746
747 let arr = make_4x4_u8();
748 let result = proc.process_array(&arr, &pool);
749 assert_eq!(result.output_arrays.len(), 1);
750 assert_eq!(result.output_arrays[0].dims[0].size, 2);
751 assert_eq!(result.output_arrays[0].dims[1].size, 2);
752 }
753
754 #[test]
757 fn test_auto_size() {
758 let arr = make_4x4_u8();
760 let mut config = ROIConfig::default();
761 config.dims[0] = ROIDimConfig {
762 min: 1,
763 size: 0,
764 bin: 1,
765 reverse: false,
766 enable: true,
767 auto_size: true,
768 };
769 config.dims[1] = ROIDimConfig {
770 min: 0,
771 size: 0,
772 bin: 1,
773 reverse: false,
774 enable: true,
775 auto_size: true,
776 };
777
778 let roi = extract_roi_2d(&arr, &config).unwrap();
779 assert_eq!(roi.dims[0].size, 3); assert_eq!(roi.dims[1].size, 4); if let NDDataBuffer::U8(ref v) = roi.data {
783 assert_eq!(v[0], 1);
785 assert_eq!(v[1], 2);
786 assert_eq!(v[2], 3);
787 }
788 }
789
790 #[test]
791 fn test_dim_disable() {
792 let arr = make_4x4_u8();
794 let mut config = ROIConfig::default();
795 config.dims[0] = ROIDimConfig {
796 min: 2,
797 size: 1,
798 bin: 1,
799 reverse: false,
800 enable: false,
801 auto_size: false,
802 };
803 config.dims[1] = ROIDimConfig {
804 min: 0,
805 size: 4,
806 bin: 1,
807 reverse: false,
808 enable: true,
809 auto_size: false,
810 };
811
812 let roi = extract_roi_2d(&arr, &config).unwrap();
813 assert_eq!(roi.dims[0].size, 4);
815 assert_eq!(roi.dims[1].size, 4);
816 }
817
818 #[test]
819 fn test_autocenter_peak() {
820 let mut arr = NDArray::new(
822 vec![NDDimension::new(8), NDDimension::new(8)],
823 NDDataType::UInt8,
824 );
825 if let NDDataBuffer::U8(ref mut v) = arr.data {
826 for i in 0..64 {
827 v[i] = 1;
828 }
829 v[5 * 8 + 6] = 255;
831 }
832
833 let mut config = ROIConfig::default();
834 config.dims[0] = ROIDimConfig {
835 min: 0,
836 size: 4,
837 bin: 1,
838 reverse: false,
839 enable: true,
840 auto_size: false,
841 };
842 config.dims[1] = ROIDimConfig {
843 min: 0,
844 size: 4,
845 bin: 1,
846 reverse: false,
847 enable: true,
848 auto_size: false,
849 };
850 config.autocenter = AutoCenter::PeakPosition;
851
852 let roi = extract_roi_2d(&arr, &config).unwrap();
853 assert_eq!(roi.dims[0].size, 4);
854 assert_eq!(roi.dims[1].size, 4);
855
856 if let NDDataBuffer::U8(ref v) = roi.data {
862 assert_eq!(v[2 * 4 + 2], 255); }
864 }
865}