1use std::sync::Arc;
2
3use ad_core::ndarray::{NDArray, NDDataBuffer, NDDataType, NDDimension};
4use ad_core::ndarray_pool::NDArrayPool;
5use ad_core::plugin::registry::{build_plugin_base_registry, ParamInfo, ParamRegistry};
6use ad_core::plugin::runtime::{NDPluginProcess, ParamUpdate, PluginParamSnapshot, PluginRuntimeHandle, ProcessResult};
7use asyn_rs::param::ParamType;
8use asyn_rs::port::PortDriverBase;
9
10#[derive(Debug, Clone)]
12pub struct ROIDimConfig {
13 pub min: usize,
14 pub size: usize,
15 pub bin: usize,
16 pub reverse: bool,
17 pub enable: bool,
18 pub auto_size: bool,
20}
21
22impl Default for ROIDimConfig {
23 fn default() -> Self {
24 Self { min: 0, size: 0, bin: 1, reverse: false, enable: true, auto_size: false }
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AutoCenter {
31 None,
32 CenterOfMass,
33 PeakPosition,
34}
35
36#[derive(Debug, Clone)]
38pub struct ROIConfig {
39 pub dims: [ROIDimConfig; 3],
40 pub data_type: Option<NDDataType>,
41 pub enable_scale: bool,
42 pub scale: f64,
43 pub collapse_dims: bool,
44 pub autocenter: AutoCenter,
45}
46
47impl Default for ROIConfig {
48 fn default() -> Self {
49 Self {
50 dims: [ROIDimConfig::default(), ROIDimConfig::default(), ROIDimConfig::default()],
51 data_type: None,
52 enable_scale: false,
53 scale: 1.0,
54 collapse_dims: false,
55 autocenter: AutoCenter::None,
56 }
57 }
58}
59
60fn find_centroid_2d(data: &NDDataBuffer, x_size: usize, y_size: usize) -> (usize, usize) {
62 let mut cx = 0.0f64;
63 let mut cy = 0.0f64;
64 let mut total = 0.0f64;
65 for iy in 0..y_size {
66 for ix in 0..x_size {
67 let val = data.get_as_f64(iy * x_size + ix).unwrap_or(0.0);
68 total += val;
69 cx += val * ix as f64;
70 cy += val * iy as f64;
71 }
72 }
73 if total > 0.0 {
74 ((cx / total) as usize, (cy / total) as usize)
75 } else {
76 (x_size / 2, y_size / 2)
77 }
78}
79
80fn find_peak_2d(data: &NDDataBuffer, x_size: usize, y_size: usize) -> (usize, usize) {
82 let mut max_val = f64::NEG_INFINITY;
83 let mut max_x = 0;
84 let mut max_y = 0;
85 for iy in 0..y_size {
86 for ix in 0..x_size {
87 let val = data.get_as_f64(iy * x_size + ix).unwrap_or(0.0);
88 if val > max_val {
89 max_val = val;
90 max_x = ix;
91 max_y = iy;
92 }
93 }
94 }
95 (max_x, max_y)
96}
97
98pub fn extract_roi_2d(src: &NDArray, config: &ROIConfig) -> Option<NDArray> {
100 if src.dims.len() < 2 {
101 return None;
102 }
103
104 let src_x = src.dims[0].size;
105 let src_y = src.dims[1].size;
106
107 let (eff_x_min, eff_x_size) = if !config.dims[0].enable {
109 (0, src_x)
110 } else if config.dims[0].auto_size {
111 let min = config.dims[0].min.min(src_x);
112 (min, src_x.saturating_sub(min))
113 } else {
114 let min = config.dims[0].min.min(src_x);
115 let size = config.dims[0].size.min(src_x - min);
116 (min, size)
117 };
118
119 let (eff_y_min, eff_y_size) = if !config.dims[1].enable {
121 (0, src_y)
122 } else if config.dims[1].auto_size {
123 let min = config.dims[1].min.min(src_y);
124 (min, src_y.saturating_sub(min))
125 } else {
126 let min = config.dims[1].min.min(src_y);
127 let size = config.dims[1].size.min(src_y - min);
128 (min, size)
129 };
130
131 let (roi_x_min, roi_y_min) = match config.autocenter {
134 AutoCenter::None => (eff_x_min, eff_y_min),
135 AutoCenter::CenterOfMass => {
136 let (cx, cy) = find_centroid_2d(&src.data, src_x, src_y);
137 let mx = cx.saturating_sub(eff_x_size / 2).min(src_x.saturating_sub(eff_x_size));
138 let my = cy.saturating_sub(eff_y_size / 2).min(src_y.saturating_sub(eff_y_size));
139 (mx, my)
140 }
141 AutoCenter::PeakPosition => {
142 let (px, py) = find_peak_2d(&src.data, src_x, src_y);
143 let mx = px.saturating_sub(eff_x_size / 2).min(src_x.saturating_sub(eff_x_size));
144 let my = py.saturating_sub(eff_y_size / 2).min(src_y.saturating_sub(eff_y_size));
145 (mx, my)
146 }
147 };
148
149 let roi_x_size = eff_x_size;
150 let roi_y_size = eff_y_size;
151
152 if roi_x_size == 0 || roi_y_size == 0 {
153 return None;
154 }
155
156 let bin_x = config.dims[0].bin.max(1);
157 let bin_y = config.dims[1].bin.max(1);
158 let out_x = roi_x_size / bin_x;
159 let out_y = roi_y_size / bin_y;
160
161 if out_x == 0 || out_y == 0 {
162 return None;
163 }
164
165 macro_rules! extract {
166 ($vec:expr, $T:ty, $zero:expr) => {{
167 let mut out = vec![$zero; out_x * out_y];
168 for oy in 0..out_y {
169 for ox in 0..out_x {
170 let mut sum = 0.0f64;
171 let mut count = 0usize;
172 for by in 0..bin_y {
173 for bx in 0..bin_x {
174 let sx = roi_x_min + ox * bin_x + bx;
175 let sy = roi_y_min + oy * bin_y + by;
176 if sx < src_x && sy < src_y {
177 sum += $vec[sy * src_x + sx] as f64;
178 count += 1;
179 }
180 }
181 }
182 let val = if count > 0 { sum / count as f64 } else { 0.0 };
183 let idx = if config.dims[0].reverse { out_x - 1 - ox } else { ox }
184 + if config.dims[1].reverse { out_y - 1 - oy } else { oy } * out_x;
185 let scaled = if config.enable_scale { val * config.scale } else { val };
186 out[idx] = scaled as $T;
187 }
188 }
189 out
190 }};
191 }
192
193 let out_data = match &src.data {
194 NDDataBuffer::U8(v) => NDDataBuffer::U8(extract!(v, u8, 0)),
195 NDDataBuffer::U16(v) => NDDataBuffer::U16(extract!(v, u16, 0)),
196 NDDataBuffer::I8(v) => NDDataBuffer::I8(extract!(v, i8, 0)),
197 NDDataBuffer::I16(v) => NDDataBuffer::I16(extract!(v, i16, 0)),
198 NDDataBuffer::I32(v) => NDDataBuffer::I32(extract!(v, i32, 0)),
199 NDDataBuffer::U32(v) => NDDataBuffer::U32(extract!(v, u32, 0)),
200 NDDataBuffer::I64(v) => NDDataBuffer::I64(extract!(v, i64, 0)),
201 NDDataBuffer::U64(v) => NDDataBuffer::U64(extract!(v, u64, 0)),
202 NDDataBuffer::F32(v) => NDDataBuffer::F32(extract!(v, f32, 0.0)),
203 NDDataBuffer::F64(v) => NDDataBuffer::F64(extract!(v, f64, 0.0)),
204 };
205
206 let out_dims = if config.collapse_dims && out_y == 1 {
207 vec![NDDimension::new(out_x)]
208 } else {
209 vec![NDDimension::new(out_x), NDDimension::new(out_y)]
210 };
211
212 let target_type = config.data_type.unwrap_or(src.data.data_type());
214
215 let mut arr = NDArray::new(out_dims, target_type);
216 if target_type == src.data.data_type() {
217 arr.data = out_data;
218 } else {
219 let mut temp = NDArray::new(arr.dims.clone(), src.data.data_type());
221 temp.data = out_data;
222 if let Ok(converted) = ad_core::color::convert_data_type(&temp, target_type) {
223 arr.data = converted.data;
224 } else {
225 arr.data = out_data_fallback(&temp.data, target_type, temp.data.len());
226 }
227 }
228
229 arr.unique_id = src.unique_id;
230 arr.timestamp = src.timestamp;
231 arr.attributes = src.attributes.clone();
232 Some(arr)
233}
234
235fn out_data_fallback(_src: &NDDataBuffer, target: NDDataType, len: usize) -> NDDataBuffer {
236 NDDataBuffer::zeros(target, len)
237}
238
239#[derive(Default, Clone, Copy)]
241pub struct ROIDimParams {
242 pub min: usize,
243 pub size: usize,
244 pub bin: usize,
245 pub reverse: usize,
246 pub enable: usize,
247 pub auto_size: usize,
248 pub max_size: usize,
249}
250
251#[derive(Default)]
253pub struct ROIParams {
254 pub dims: [ROIDimParams; 3],
255 pub enable_scale: usize,
256 pub scale: usize,
257 pub data_type: usize,
258 pub collapse_dims: usize,
259 pub name: usize,
260}
261
262pub struct ROIProcessor {
264 config: ROIConfig,
265 params: ROIParams,
266}
267
268impl ROIProcessor {
269 pub fn new(config: ROIConfig) -> Self {
270 Self { config, params: ROIParams::default() }
271 }
272
273 pub fn params(&self) -> &ROIParams {
275 &self.params
276 }
277}
278
279impl NDPluginProcess for ROIProcessor {
280 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
281 let mut updates = Vec::new();
283 for (i, dim_params) in self.params.dims.iter().enumerate() {
284 let dim_size = array.dims.get(i).map(|d| d.size as i32).unwrap_or(0);
285 updates.push(ParamUpdate::int32(dim_params.max_size, dim_size));
286 }
287
288 match extract_roi_2d(array, &self.config) {
289 Some(roi_arr) => ProcessResult {
290 output_arrays: vec![Arc::new(roi_arr)],
291 param_updates: updates,
292 },
293 None => ProcessResult::sink(updates),
294 }
295 }
296
297 fn plugin_type(&self) -> &str {
298 "NDPluginROI"
299 }
300
301 fn register_params(&mut self, base: &mut PortDriverBase) -> Result<(), asyn_rs::error::AsynError> {
302 let dim_names = ["DIM0", "DIM1", "DIM2"];
303 for (i, prefix) in dim_names.iter().enumerate() {
304 self.params.dims[i].min = base.create_param(&format!("{prefix}_MIN"), ParamType::Int32)?;
305 self.params.dims[i].size = base.create_param(&format!("{prefix}_SIZE"), ParamType::Int32)?;
306 self.params.dims[i].bin = base.create_param(&format!("{prefix}_BIN"), ParamType::Int32)?;
307 self.params.dims[i].reverse = base.create_param(&format!("{prefix}_REVERSE"), ParamType::Int32)?;
308 self.params.dims[i].enable = base.create_param(&format!("{prefix}_ENABLE"), ParamType::Int32)?;
309 self.params.dims[i].auto_size = base.create_param(&format!("{prefix}_AUTO_SIZE"), ParamType::Int32)?;
310 self.params.dims[i].max_size = base.create_param(&format!("{prefix}_MAX_SIZE"), ParamType::Int32)?;
311
312 base.set_int32_param(self.params.dims[i].min, 0, self.config.dims[i].min as i32)?;
314 base.set_int32_param(self.params.dims[i].size, 0, self.config.dims[i].size as i32)?;
315 base.set_int32_param(self.params.dims[i].bin, 0, self.config.dims[i].bin as i32)?;
316 base.set_int32_param(self.params.dims[i].reverse, 0, self.config.dims[i].reverse as i32)?;
317 base.set_int32_param(self.params.dims[i].enable, 0, self.config.dims[i].enable as i32)?;
318 base.set_int32_param(self.params.dims[i].auto_size, 0, self.config.dims[i].auto_size as i32)?;
319 }
320 self.params.enable_scale = base.create_param("ENABLE_SCALE", ParamType::Int32)?;
321 self.params.scale = base.create_param("SCALE_VALUE", ParamType::Float64)?;
322 self.params.data_type = base.create_param("ROI_DATA_TYPE", ParamType::Int32)?;
323 self.params.collapse_dims = base.create_param("COLLAPSE_DIMS", ParamType::Int32)?;
324 self.params.name = base.create_param("NAME", ParamType::Octet)?;
325
326 base.set_int32_param(self.params.enable_scale, 0, self.config.enable_scale as i32)?;
327 base.set_float64_param(self.params.scale, 0, self.config.scale)?;
328 base.set_int32_param(self.params.data_type, 0, -1)?; base.set_int32_param(self.params.collapse_dims, 0, self.config.collapse_dims as i32)?;
330
331 Ok(())
332 }
333
334 fn on_param_change(&mut self, reason: usize, snapshot: &PluginParamSnapshot) {
335 let p = &self.params;
336 for i in 0..3 {
337 if reason == p.dims[i].min {
338 self.config.dims[i].min = snapshot.value.as_i32().max(0) as usize;
339 return;
340 }
341 if reason == p.dims[i].size {
342 self.config.dims[i].size = snapshot.value.as_i32().max(0) as usize;
343 return;
344 }
345 if reason == p.dims[i].bin {
346 self.config.dims[i].bin = snapshot.value.as_i32().max(1) as usize;
347 return;
348 }
349 if reason == p.dims[i].reverse {
350 self.config.dims[i].reverse = snapshot.value.as_i32() != 0;
351 return;
352 }
353 if reason == p.dims[i].enable {
354 self.config.dims[i].enable = snapshot.value.as_i32() != 0;
355 return;
356 }
357 if reason == p.dims[i].auto_size {
358 self.config.dims[i].auto_size = snapshot.value.as_i32() != 0;
359 return;
360 }
361 }
362 if reason == p.enable_scale {
363 self.config.enable_scale = snapshot.value.as_i32() != 0;
364 } else if reason == p.scale {
365 self.config.scale = snapshot.value.as_f64();
366 } else if reason == p.data_type {
367 let v = snapshot.value.as_i32();
368 self.config.data_type = if v < 0 { None } else { NDDataType::from_ordinal(v as u8) };
369 } else if reason == p.collapse_dims {
370 self.config.collapse_dims = snapshot.value.as_i32() != 0;
371 }
372 }
373}
374
375pub fn create_roi_runtime(
377 port_name: &str,
378 pool: Arc<NDArrayPool>,
379 queue_size: usize,
380 ndarray_port: &str,
381 wiring: Arc<ad_core::plugin::wiring::WiringRegistry>,
382) -> (
383 ad_core::plugin::runtime::PluginRuntimeHandle,
384 ROIParams,
385 std::thread::JoinHandle<()>,
386) {
387 let processor = ROIProcessor::new(ROIConfig::default());
388 let (handle, jh) = ad_core::plugin::runtime::create_plugin_runtime(
389 port_name,
390 processor,
391 pool,
392 queue_size,
393 ndarray_port,
394 wiring,
395 );
396 let params = {
398 let mut base = asyn_rs::port::PortDriverBase::new("_scratch_", 1, asyn_rs::port::PortFlags::default());
399 let _ = ad_core::params::ndarray_driver::NDArrayDriverParams::create(&mut base);
400 let _ = ad_core::plugin::params::PluginBaseParams::create(&mut base);
401 let mut p = ROIParams::default();
402 let dim_names = ["DIM0", "DIM1", "DIM2"];
403 for (i, prefix) in dim_names.iter().enumerate() {
404 p.dims[i].min = base.create_param(&format!("{prefix}_MIN"), asyn_rs::param::ParamType::Int32).unwrap();
405 p.dims[i].size = base.create_param(&format!("{prefix}_SIZE"), asyn_rs::param::ParamType::Int32).unwrap();
406 p.dims[i].bin = base.create_param(&format!("{prefix}_BIN"), asyn_rs::param::ParamType::Int32).unwrap();
407 p.dims[i].reverse = base.create_param(&format!("{prefix}_REVERSE"), asyn_rs::param::ParamType::Int32).unwrap();
408 p.dims[i].enable = base.create_param(&format!("{prefix}_ENABLE"), asyn_rs::param::ParamType::Int32).unwrap();
409 p.dims[i].auto_size = base.create_param(&format!("{prefix}_AUTO_SIZE"), asyn_rs::param::ParamType::Int32).unwrap();
410 p.dims[i].max_size = base.create_param(&format!("{prefix}_MAX_SIZE"), asyn_rs::param::ParamType::Int32).unwrap();
411 }
412 p.enable_scale = base.create_param("ENABLE_SCALE", asyn_rs::param::ParamType::Int32).unwrap();
413 p.scale = base.create_param("SCALE_VALUE", asyn_rs::param::ParamType::Float64).unwrap();
414 p.data_type = base.create_param("ROI_DATA_TYPE", asyn_rs::param::ParamType::Int32).unwrap();
415 p.collapse_dims = base.create_param("COLLAPSE_DIMS", asyn_rs::param::ParamType::Int32).unwrap();
416 p.name = base.create_param("NAME", asyn_rs::param::ParamType::Octet).unwrap();
417 p
418 };
419 (handle, params, jh)
420}
421
422pub fn build_roi_registry(h: &PluginRuntimeHandle, rp: &ROIParams) -> ParamRegistry {
424 let mut map = build_plugin_base_registry(h);
425
426 let dim_suffixes = [
427 ("X", 0),
428 ("Y", 1),
429 ("Z", 2),
430 ];
431 for (suffix, i) in &dim_suffixes {
432 let d = &rp.dims[*i];
433 let dim_idx = *i;
434 map.insert(format!("Min{suffix}"), ParamInfo::int32(d.min, &format!("DIM{dim_idx}_MIN")));
436 map.insert(format!("Min{suffix}_RBV"), ParamInfo::int32(d.min, &format!("DIM{dim_idx}_MIN")));
437 map.insert(format!("Size{suffix}"), ParamInfo::int32(d.size, &format!("DIM{dim_idx}_SIZE")));
439 map.insert(format!("Size{suffix}_RBV"), ParamInfo::int32(d.size, &format!("DIM{dim_idx}_SIZE")));
440 map.insert(format!("Bin{suffix}"), ParamInfo::int32(d.bin, &format!("DIM{dim_idx}_BIN")));
442 map.insert(format!("Bin{suffix}_RBV"), ParamInfo::int32(d.bin, &format!("DIM{dim_idx}_BIN")));
443 map.insert(format!("Reverse{suffix}"), ParamInfo::int32(d.reverse, &format!("DIM{dim_idx}_REVERSE")));
445 map.insert(format!("Reverse{suffix}_RBV"), ParamInfo::int32(d.reverse, &format!("DIM{dim_idx}_REVERSE")));
446 map.insert(format!("Enable{suffix}"), ParamInfo::int32(d.enable, &format!("DIM{dim_idx}_ENABLE")));
448 map.insert(format!("Enable{suffix}_RBV"), ParamInfo::int32(d.enable, &format!("DIM{dim_idx}_ENABLE")));
449 map.insert(format!("AutoSize{suffix}"), ParamInfo::int32(d.auto_size, &format!("DIM{dim_idx}_AUTO_SIZE")));
451 map.insert(format!("AutoSize{suffix}_RBV"), ParamInfo::int32(d.auto_size, &format!("DIM{dim_idx}_AUTO_SIZE")));
452 map.insert(format!("MaxSize{suffix}_RBV"), ParamInfo::int32(d.max_size, &format!("DIM{dim_idx}_MAX_SIZE")));
454 }
455
456 map.insert("EnableScale".into(), ParamInfo::int32(rp.enable_scale, "ENABLE_SCALE"));
458 map.insert("EnableScale_RBV".into(), ParamInfo::int32(rp.enable_scale, "ENABLE_SCALE"));
459 map.insert("Scale".into(), ParamInfo::float64(rp.scale, "SCALE_VALUE"));
460 map.insert("Scale_RBV".into(), ParamInfo::float64(rp.scale, "SCALE_VALUE"));
461
462 map.insert("DataTypeOut".into(), ParamInfo::int32(rp.data_type, "ROI_DATA_TYPE"));
464 map.insert("DataTypeOut_RBV".into(), ParamInfo::int32(rp.data_type, "ROI_DATA_TYPE"));
465
466 map.insert("CollapseDims".into(), ParamInfo::int32(rp.collapse_dims, "COLLAPSE_DIMS"));
468 map.insert("CollapseDims_RBV".into(), ParamInfo::int32(rp.collapse_dims, "COLLAPSE_DIMS"));
469
470 map.insert("Name".into(), ParamInfo::string(rp.name, "NAME"));
472 map.insert("Name_RBV".into(), ParamInfo::string(rp.name, "NAME"));
473
474 map
475}
476
477#[cfg(test)]
478mod tests {
479 use super::*;
480
481 fn make_4x4_u8() -> NDArray {
482 let mut arr = NDArray::new(
483 vec![NDDimension::new(4), NDDimension::new(4)],
484 NDDataType::UInt8,
485 );
486 if let NDDataBuffer::U8(ref mut v) = arr.data {
487 for i in 0..16 { v[i] = i as u8; }
488 }
489 arr
490 }
491
492 #[test]
493 fn test_extract_sub_region() {
494 let arr = make_4x4_u8();
495 let mut config = ROIConfig::default();
496 config.dims[0] = ROIDimConfig { min: 1, size: 2, bin: 1, reverse: false, enable: true, auto_size: false };
497 config.dims[1] = ROIDimConfig { min: 1, size: 2, bin: 1, reverse: false, enable: true, auto_size: false };
498
499 let roi = extract_roi_2d(&arr, &config).unwrap();
500 assert_eq!(roi.dims[0].size, 2);
501 assert_eq!(roi.dims[1].size, 2);
502 if let NDDataBuffer::U8(ref v) = roi.data {
503 assert_eq!(v[0], 5);
505 assert_eq!(v[1], 6);
506 assert_eq!(v[2], 9);
507 assert_eq!(v[3], 10);
508 }
509 }
510
511 #[test]
512 fn test_binning_2x2() {
513 let arr = make_4x4_u8();
514 let mut config = ROIConfig::default();
515 config.dims[0] = ROIDimConfig { min: 0, size: 4, bin: 2, reverse: false, enable: true, auto_size: false };
516 config.dims[1] = ROIDimConfig { min: 0, size: 4, bin: 2, reverse: false, enable: true, auto_size: false };
517
518 let roi = extract_roi_2d(&arr, &config).unwrap();
519 assert_eq!(roi.dims[0].size, 2);
520 assert_eq!(roi.dims[1].size, 2);
521 if let NDDataBuffer::U8(ref v) = roi.data {
522 assert_eq!(v[0], 2);
524 }
525 }
526
527 #[test]
528 fn test_reverse() {
529 let arr = make_4x4_u8();
530 let mut config = ROIConfig::default();
531 config.dims[0] = ROIDimConfig { min: 0, size: 4, bin: 1, reverse: true, enable: true, auto_size: false };
532 config.dims[1] = ROIDimConfig { min: 0, size: 1, bin: 1, reverse: false, enable: true, auto_size: false };
533
534 let roi = extract_roi_2d(&arr, &config).unwrap();
535 if let NDDataBuffer::U8(ref v) = roi.data {
536 assert_eq!(v[0], 3);
537 assert_eq!(v[1], 2);
538 assert_eq!(v[2], 1);
539 assert_eq!(v[3], 0);
540 }
541 }
542
543 #[test]
544 fn test_collapse_dims() {
545 let arr = make_4x4_u8();
546 let mut config = ROIConfig::default();
547 config.dims[0] = ROIDimConfig { min: 0, size: 4, bin: 1, reverse: false, enable: true, auto_size: false };
548 config.dims[1] = ROIDimConfig { min: 0, size: 1, bin: 1, reverse: false, enable: true, auto_size: false };
549 config.collapse_dims = true;
550
551 let roi = extract_roi_2d(&arr, &config).unwrap();
552 assert_eq!(roi.dims.len(), 1);
553 assert_eq!(roi.dims[0].size, 4);
554 }
555
556 #[test]
557 fn test_scale() {
558 let arr = make_4x4_u8();
559 let mut config = ROIConfig::default();
560 config.dims[0] = ROIDimConfig { min: 0, size: 2, bin: 1, reverse: false, enable: true, auto_size: false };
561 config.dims[1] = ROIDimConfig { min: 0, size: 1, bin: 1, reverse: false, enable: true, auto_size: false };
562 config.enable_scale = true;
563 config.scale = 2.0;
564
565 let roi = extract_roi_2d(&arr, &config).unwrap();
566 if let NDDataBuffer::U8(ref v) = roi.data {
567 assert_eq!(v[0], 0); assert_eq!(v[1], 2); }
570 }
571
572 #[test]
573 fn test_type_convert() {
574 let arr = make_4x4_u8();
575 let mut config = ROIConfig::default();
576 config.dims[0] = ROIDimConfig { min: 0, size: 2, bin: 1, reverse: false, enable: true, auto_size: false };
577 config.dims[1] = ROIDimConfig { min: 0, size: 1, bin: 1, reverse: false, enable: true, auto_size: false };
578 config.data_type = Some(NDDataType::UInt16);
579
580 let roi = extract_roi_2d(&arr, &config).unwrap();
581 assert_eq!(roi.data.data_type(), NDDataType::UInt16);
582 }
583
584 #[test]
587 fn test_roi_processor() {
588 let mut config = ROIConfig::default();
589 config.dims[0] = ROIDimConfig { min: 1, size: 2, bin: 1, reverse: false, enable: true, auto_size: false };
590 config.dims[1] = ROIDimConfig { min: 1, size: 2, bin: 1, reverse: false, enable: true, auto_size: false };
591
592 let mut proc = ROIProcessor::new(config);
593 let pool = NDArrayPool::new(1_000_000);
594
595 let arr = make_4x4_u8();
596 let result = proc.process_array(&arr, &pool);
597 assert_eq!(result.output_arrays.len(), 1);
598 assert_eq!(result.output_arrays[0].dims[0].size, 2);
599 assert_eq!(result.output_arrays[0].dims[1].size, 2);
600 }
601
602 #[test]
605 fn test_auto_size() {
606 let arr = make_4x4_u8();
608 let mut config = ROIConfig::default();
609 config.dims[0] = ROIDimConfig { min: 1, size: 0, bin: 1, reverse: false, enable: true, auto_size: true };
610 config.dims[1] = ROIDimConfig { min: 0, size: 0, bin: 1, reverse: false, enable: true, auto_size: true };
611
612 let roi = extract_roi_2d(&arr, &config).unwrap();
613 assert_eq!(roi.dims[0].size, 3); assert_eq!(roi.dims[1].size, 4); if let NDDataBuffer::U8(ref v) = roi.data {
617 assert_eq!(v[0], 1);
619 assert_eq!(v[1], 2);
620 assert_eq!(v[2], 3);
621 }
622 }
623
624 #[test]
625 fn test_dim_disable() {
626 let arr = make_4x4_u8();
628 let mut config = ROIConfig::default();
629 config.dims[0] = ROIDimConfig { min: 2, size: 1, bin: 1, reverse: false, enable: false, auto_size: false };
630 config.dims[1] = ROIDimConfig { min: 0, size: 4, bin: 1, reverse: false, enable: true, auto_size: false };
631
632 let roi = extract_roi_2d(&arr, &config).unwrap();
633 assert_eq!(roi.dims[0].size, 4);
635 assert_eq!(roi.dims[1].size, 4);
636 }
637
638 #[test]
639 fn test_autocenter_peak() {
640 let mut arr = NDArray::new(
642 vec![NDDimension::new(8), NDDimension::new(8)],
643 NDDataType::UInt8,
644 );
645 if let NDDataBuffer::U8(ref mut v) = arr.data {
646 for i in 0..64 { v[i] = 1; }
647 v[5 * 8 + 6] = 255;
649 }
650
651 let mut config = ROIConfig::default();
652 config.dims[0] = ROIDimConfig { min: 0, size: 4, bin: 1, reverse: false, enable: true, auto_size: false };
653 config.dims[1] = ROIDimConfig { min: 0, size: 4, bin: 1, reverse: false, enable: true, auto_size: false };
654 config.autocenter = AutoCenter::PeakPosition;
655
656 let roi = extract_roi_2d(&arr, &config).unwrap();
657 assert_eq!(roi.dims[0].size, 4);
658 assert_eq!(roi.dims[1].size, 4);
659
660 if let NDDataBuffer::U8(ref v) = roi.data {
666 assert_eq!(v[2 * 4 + 2], 255); }
668 }
669}