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