1use std::sync::Arc;
2
3use crate::par_util;
6use parking_lot::Mutex;
7#[cfg(feature = "parallel")]
8use rayon::prelude::*;
9
10use ad_core_rs::color::{self, NDBayerPattern, NDColorMode};
11use ad_core_rs::ndarray::{NDArray, NDDataBuffer, NDDataType, NDDimension};
12use ad_core_rs::ndarray_pool::NDArrayPool;
13use ad_core_rs::plugin::runtime::{NDPluginProcess, ProcessResult};
14
15pub fn bayer_to_rgb1(src: &NDArray, pattern: NDBayerPattern) -> Option<NDArray> {
17 if src.dims.len() != 2 {
18 return None;
19 }
20 let w = src.dims[0].size;
21 let h = src.dims[1].size;
22
23 let offset_x = src.dims[0].offset;
25 let offset_y = src.dims[1].offset;
26
27 let n = w * h;
29 let src_vals: Vec<f64> = (0..n)
30 .map(|i| src.data.get_as_f64(i).unwrap_or(0.0))
31 .collect();
32 let get_val = |x: usize, y: usize| -> f64 { src_vals[y * w + x] };
33
34 let mut r = vec![0.0f64; n];
35 let mut g = vec![0.0f64; n];
36 let mut b = vec![0.0f64; n];
37
38 let (mut r_row_even, mut r_col_even) = match pattern {
40 NDBayerPattern::RGGB => (true, true),
41 NDBayerPattern::GBRG => (true, false),
42 NDBayerPattern::GRBG => (false, true),
43 NDBayerPattern::BGGR => (false, false),
44 };
45 if offset_x % 2 != 0 {
46 r_col_even = !r_col_even;
47 }
48 if offset_y % 2 != 0 {
49 r_row_even = !r_row_even;
50 }
51
52 let demosaic_row = |y: usize, r_row: &mut [f64], g_row: &mut [f64], b_row: &mut [f64]| {
60 let even_row = (y % 2 == 0) == r_row_even;
61 for x in 0..w {
62 let val = get_val(x, y);
63 let even_col = (x % 2 == 0) == r_col_even;
64 let interior = x > 0 && x + 1 < w && y > 0 && y + 1 < h;
65
66 match (even_row, even_col) {
67 (true, true) => {
68 r_row[x] = val;
71 if interior {
72 g_row[x] = (get_val(x - 1, y)
73 + get_val(x + 1, y)
74 + get_val(x, y - 1)
75 + get_val(x, y + 1))
76 / 4.0;
77 b_row[x] = (get_val(x - 1, y - 1)
78 + get_val(x + 1, y - 1)
79 + get_val(x - 1, y + 1)
80 + get_val(x + 1, y + 1))
81 / 4.0;
82 }
83 }
84 (true, false) | (false, true) => {
85 g_row[x] = val;
87 if interior {
88 if even_row {
89 r_row[x] = (get_val(x - 1, y) + get_val(x + 1, y)) / 2.0;
92 b_row[x] = (get_val(x, y - 1) + get_val(x, y + 1)) / 2.0;
93 } else {
94 b_row[x] = (get_val(x - 1, y) + get_val(x + 1, y)) / 2.0;
97 r_row[x] = (get_val(x, y - 1) + get_val(x, y + 1)) / 2.0;
98 }
99 }
100 }
101 (false, false) => {
102 b_row[x] = val;
105 if interior {
106 g_row[x] = (get_val(x - 1, y)
107 + get_val(x + 1, y)
108 + get_val(x, y - 1)
109 + get_val(x, y + 1))
110 / 4.0;
111 r_row[x] = (get_val(x - 1, y - 1)
112 + get_val(x + 1, y - 1)
113 + get_val(x - 1, y + 1)
114 + get_val(x + 1, y + 1))
115 / 4.0;
116 }
117 }
118 }
119 }
120 };
121
122 let use_parallel = par_util::should_parallelize(n);
123
124 if use_parallel {
125 #[cfg(feature = "parallel")]
126 {
127 let r_rows: Vec<&mut [f64]> = r.chunks_mut(w).collect();
129 let g_rows: Vec<&mut [f64]> = g.chunks_mut(w).collect();
130 let b_rows: Vec<&mut [f64]> = b.chunks_mut(w).collect();
131
132 par_util::thread_pool().install(|| {
133 r_rows
134 .into_par_iter()
135 .zip(g_rows.into_par_iter())
136 .zip(b_rows.into_par_iter())
137 .enumerate()
138 .for_each(|(y, ((r_row, g_row), b_row))| {
139 demosaic_row(y, r_row, g_row, b_row);
140 });
141 });
142 }
143 } else {
144 for y in 0..h {
145 let row_start = y * w;
146 let row_end = row_start + w;
147 demosaic_row(
148 y,
149 &mut r[row_start..row_end],
150 &mut g[row_start..row_end],
151 &mut b[row_start..row_end],
152 );
153 }
154 }
155
156 let out_data = match src.data.data_type() {
158 NDDataType::UInt8 => {
159 let mut out = vec![0u8; n * 3];
160 for i in 0..n {
161 out[i * 3] = r[i].clamp(0.0, 255.0) as u8;
162 out[i * 3 + 1] = g[i].clamp(0.0, 255.0) as u8;
163 out[i * 3 + 2] = b[i].clamp(0.0, 255.0) as u8;
164 }
165 NDDataBuffer::U8(out)
166 }
167 NDDataType::UInt16 => {
168 let mut out = vec![0u16; n * 3];
169 for i in 0..n {
170 out[i * 3] = r[i].clamp(0.0, 65535.0) as u16;
171 out[i * 3 + 1] = g[i].clamp(0.0, 65535.0) as u16;
172 out[i * 3 + 2] = b[i].clamp(0.0, 65535.0) as u16;
173 }
174 NDDataBuffer::U16(out)
175 }
176 _ => return None,
177 };
178
179 let dims = vec![
180 NDDimension::new(3),
181 NDDimension::new(w),
182 NDDimension::new(h),
183 ];
184 let mut arr = NDArray::new(dims, src.data.data_type());
185 arr.data = out_data;
186 arr.unique_id = src.unique_id;
187 arr.timestamp = src.timestamp;
188 arr.attributes = src.attributes.clone();
189 Some(arr)
190}
191
192const RAINBOW_COLOR_MAP: [[u8; 3]; 256] = [
198 [0, 0, 0],
199 [0, 0, 4],
200 [0, 0, 8],
201 [0, 0, 12],
202 [0, 0, 16],
203 [0, 0, 20],
204 [0, 0, 24],
205 [0, 0, 28],
206 [0, 0, 32],
207 [0, 0, 36],
208 [0, 0, 40],
209 [0, 0, 45],
210 [0, 0, 49],
211 [0, 0, 53],
212 [0, 0, 57],
213 [0, 0, 61],
214 [0, 0, 65],
215 [0, 0, 69],
216 [0, 0, 73],
217 [0, 0, 77],
218 [0, 0, 81],
219 [0, 0, 85],
220 [0, 0, 89],
221 [0, 0, 93],
222 [0, 0, 97],
223 [0, 0, 101],
224 [0, 0, 105],
225 [0, 0, 109],
226 [0, 0, 113],
227 [0, 0, 117],
228 [0, 0, 121],
229 [0, 0, 125],
230 [0, 0, 130],
231 [0, 0, 134],
232 [0, 0, 138],
233 [0, 0, 142],
234 [0, 0, 146],
235 [0, 0, 150],
236 [0, 0, 154],
237 [0, 0, 158],
238 [0, 0, 162],
239 [0, 0, 166],
240 [0, 0, 170],
241 [0, 0, 174],
242 [0, 0, 178],
243 [0, 0, 182],
244 [0, 0, 186],
245 [0, 0, 190],
246 [0, 0, 194],
247 [0, 0, 198],
248 [0, 0, 202],
249 [0, 0, 206],
250 [0, 0, 210],
251 [0, 0, 215],
252 [0, 0, 219],
253 [0, 0, 223],
254 [0, 0, 227],
255 [0, 0, 231],
256 [0, 0, 235],
257 [0, 0, 239],
258 [0, 0, 243],
259 [0, 0, 247],
260 [0, 0, 251],
261 [0, 0, 255],
262 [0, 4, 255],
263 [0, 8, 255],
264 [0, 12, 255],
265 [0, 16, 255],
266 [0, 20, 255],
267 [0, 24, 255],
268 [0, 28, 255],
269 [0, 32, 255],
270 [0, 36, 255],
271 [0, 40, 255],
272 [0, 44, 255],
273 [0, 48, 255],
274 [0, 52, 255],
275 [0, 56, 255],
276 [0, 60, 255],
277 [0, 64, 255],
278 [0, 68, 255],
279 [0, 72, 255],
280 [0, 76, 255],
281 [0, 80, 255],
282 [0, 84, 255],
283 [0, 88, 255],
284 [0, 92, 255],
285 [0, 96, 255],
286 [0, 100, 255],
287 [0, 104, 255],
288 [0, 108, 255],
289 [0, 112, 255],
290 [0, 116, 255],
291 [0, 120, 255],
292 [0, 124, 255],
293 [0, 128, 255],
294 [0, 131, 255],
295 [0, 135, 255],
296 [0, 139, 255],
297 [0, 143, 255],
298 [0, 147, 255],
299 [0, 151, 255],
300 [0, 155, 255],
301 [0, 159, 255],
302 [0, 163, 255],
303 [0, 167, 255],
304 [0, 171, 255],
305 [0, 175, 255],
306 [0, 179, 255],
307 [0, 183, 255],
308 [0, 187, 255],
309 [0, 191, 255],
310 [0, 195, 255],
311 [0, 199, 255],
312 [0, 203, 255],
313 [0, 207, 255],
314 [0, 211, 255],
315 [0, 215, 255],
316 [0, 219, 255],
317 [0, 223, 255],
318 [0, 227, 255],
319 [0, 231, 255],
320 [0, 235, 255],
321 [0, 239, 255],
322 [0, 243, 255],
323 [0, 247, 255],
324 [0, 251, 255],
325 [0, 255, 255],
326 [4, 255, 251],
327 [8, 255, 247],
328 [12, 255, 243],
329 [16, 255, 239],
330 [20, 255, 235],
331 [24, 255, 231],
332 [28, 255, 227],
333 [32, 255, 223],
334 [36, 255, 219],
335 [40, 255, 215],
336 [44, 255, 211],
337 [48, 255, 207],
338 [52, 255, 203],
339 [56, 255, 199],
340 [60, 255, 195],
341 [64, 255, 191],
342 [68, 255, 187],
343 [72, 255, 183],
344 [76, 255, 179],
345 [80, 255, 175],
346 [84, 255, 171],
347 [88, 255, 167],
348 [92, 255, 163],
349 [96, 255, 159],
350 [100, 255, 155],
351 [104, 255, 151],
352 [108, 255, 147],
353 [112, 255, 143],
354 [116, 255, 139],
355 [120, 255, 135],
356 [124, 255, 131],
357 [128, 255, 128],
358 [131, 255, 124],
359 [135, 255, 120],
360 [139, 255, 116],
361 [143, 255, 112],
362 [147, 255, 108],
363 [151, 255, 104],
364 [155, 255, 100],
365 [159, 255, 96],
366 [163, 255, 92],
367 [167, 255, 88],
368 [171, 255, 84],
369 [175, 255, 80],
370 [179, 255, 76],
371 [183, 255, 72],
372 [187, 255, 68],
373 [191, 255, 64],
374 [195, 255, 60],
375 [199, 255, 56],
376 [203, 255, 52],
377 [207, 255, 48],
378 [211, 255, 44],
379 [215, 255, 40],
380 [219, 255, 36],
381 [223, 255, 32],
382 [227, 255, 28],
383 [231, 255, 24],
384 [235, 255, 20],
385 [239, 255, 16],
386 [243, 255, 12],
387 [247, 255, 8],
388 [251, 255, 4],
389 [255, 255, 0],
390 [255, 251, 0],
391 [255, 247, 0],
392 [255, 243, 0],
393 [255, 239, 0],
394 [255, 235, 0],
395 [255, 231, 0],
396 [255, 227, 0],
397 [255, 223, 0],
398 [255, 219, 0],
399 [255, 215, 0],
400 [255, 211, 0],
401 [255, 207, 0],
402 [255, 203, 0],
403 [255, 199, 0],
404 [255, 195, 0],
405 [255, 191, 0],
406 [255, 187, 0],
407 [255, 183, 0],
408 [255, 179, 0],
409 [255, 175, 0],
410 [255, 171, 0],
411 [255, 167, 0],
412 [255, 163, 0],
413 [255, 159, 0],
414 [255, 155, 0],
415 [255, 151, 0],
416 [255, 147, 0],
417 [255, 143, 0],
418 [255, 139, 0],
419 [255, 135, 0],
420 [255, 131, 0],
421 [255, 128, 0],
422 [255, 124, 0],
423 [255, 120, 0],
424 [255, 116, 0],
425 [255, 112, 0],
426 [255, 108, 0],
427 [255, 104, 0],
428 [255, 100, 0],
429 [255, 96, 0],
430 [255, 92, 0],
431 [255, 88, 0],
432 [255, 84, 0],
433 [255, 80, 0],
434 [255, 76, 0],
435 [255, 72, 0],
436 [255, 68, 0],
437 [255, 64, 0],
438 [255, 60, 0],
439 [255, 56, 0],
440 [255, 52, 0],
441 [255, 48, 0],
442 [255, 44, 0],
443 [255, 40, 0],
444 [255, 36, 0],
445 [255, 32, 0],
446 [255, 28, 0],
447 [255, 24, 0],
448 [255, 20, 0],
449 [255, 16, 0],
450 [255, 12, 0],
451 [255, 8, 0],
452 [255, 4, 0],
453 [255, 255, 255],
454];
455
456const IRON_COLOR_MAP: [[u8; 3]; 256] = [
462 [0, 0, 0],
463 [1, 0, 1],
464 [3, 1, 3],
465 [4, 1, 5],
466 [6, 2, 6],
467 [7, 2, 8],
468 [9, 3, 10],
469 [10, 3, 11],
470 [12, 4, 13],
471 [13, 4, 15],
472 [15, 5, 17],
473 [16, 5, 18],
474 [18, 6, 20],
475 [19, 6, 22],
476 [21, 7, 23],
477 [22, 7, 25],
478 [24, 8, 27],
479 [25, 8, 28],
480 [27, 9, 30],
481 [28, 9, 32],
482 [30, 10, 34],
483 [31, 10, 35],
484 [33, 11, 37],
485 [34, 11, 39],
486 [36, 12, 40],
487 [37, 12, 42],
488 [39, 13, 44],
489 [40, 13, 45],
490 [42, 14, 47],
491 [43, 14, 49],
492 [45, 15, 51],
493 [46, 15, 52],
494 [48, 16, 54],
495 [49, 16, 56],
496 [51, 17, 57],
497 [52, 17, 59],
498 [54, 18, 61],
499 [55, 18, 62],
500 [57, 19, 64],
501 [58, 19, 66],
502 [60, 20, 68],
503 [61, 20, 69],
504 [63, 21, 71],
505 [64, 21, 73],
506 [66, 22, 74],
507 [67, 22, 76],
508 [69, 23, 78],
509 [70, 23, 79],
510 [72, 24, 81],
511 [73, 24, 83],
512 [75, 25, 85],
513 [76, 25, 86],
514 [78, 26, 88],
515 [79, 26, 90],
516 [81, 27, 91],
517 [82, 27, 93],
518 [84, 28, 95],
519 [85, 28, 96],
520 [87, 29, 98],
521 [88, 29, 100],
522 [90, 30, 102],
523 [91, 30, 103],
524 [93, 31, 105],
525 [94, 31, 107],
526 [96, 32, 108],
527 [97, 32, 110],
528 [99, 33, 112],
529 [100, 33, 113],
530 [102, 34, 115],
531 [103, 34, 117],
532 [105, 35, 119],
533 [106, 35, 120],
534 [108, 36, 120],
535 [109, 36, 119],
536 [111, 37, 118],
537 [112, 37, 117],
538 [114, 38, 116],
539 [115, 38, 115],
540 [117, 39, 114],
541 [118, 39, 113],
542 [120, 40, 112],
543 [121, 40, 111],
544 [123, 41, 110],
545 [124, 41, 109],
546 [126, 42, 108],
547 [127, 42, 107],
548 [129, 43, 106],
549 [130, 43, 105],
550 [132, 44, 104],
551 [133, 44, 103],
552 [135, 45, 102],
553 [136, 45, 101],
554 [138, 46, 100],
555 [139, 46, 99],
556 [141, 47, 98],
557 [142, 47, 97],
558 [144, 48, 96],
559 [145, 48, 95],
560 [147, 49, 94],
561 [148, 49, 93],
562 [150, 50, 92],
563 [151, 50, 91],
564 [153, 51, 90],
565 [154, 51, 89],
566 [156, 52, 88],
567 [157, 52, 87],
568 [159, 53, 86],
569 [160, 53, 85],
570 [162, 54, 84],
571 [163, 54, 83],
572 [165, 55, 82],
573 [166, 55, 81],
574 [168, 56, 80],
575 [169, 56, 79],
576 [171, 57, 78],
577 [172, 57, 77],
578 [174, 58, 76],
579 [175, 58, 75],
580 [177, 59, 74],
581 [178, 59, 73],
582 [180, 60, 72],
583 [181, 60, 71],
584 [183, 61, 70],
585 [184, 61, 69],
586 [186, 62, 68],
587 [187, 62, 67],
588 [189, 63, 66],
589 [190, 63, 65],
590 [192, 64, 64],
591 [192, 65, 64],
592 [192, 67, 64],
593 [192, 68, 64],
594 [192, 70, 64],
595 [192, 71, 64],
596 [192, 73, 64],
597 [192, 74, 64],
598 [192, 76, 64],
599 [192, 77, 64],
600 [192, 79, 64],
601 [192, 80, 64],
602 [192, 82, 64],
603 [192, 83, 64],
604 [192, 85, 64],
605 [192, 86, 64],
606 [192, 88, 64],
607 [192, 89, 64],
608 [192, 91, 64],
609 [192, 92, 64],
610 [192, 94, 64],
611 [192, 95, 64],
612 [192, 97, 64],
613 [192, 98, 64],
614 [192, 100, 64],
615 [192, 101, 64],
616 [192, 103, 64],
617 [192, 104, 64],
618 [192, 106, 64],
619 [192, 107, 64],
620 [192, 109, 64],
621 [192, 110, 64],
622 [192, 112, 64],
623 [192, 113, 64],
624 [192, 115, 64],
625 [192, 116, 64],
626 [192, 118, 64],
627 [192, 119, 64],
628 [192, 121, 64],
629 [192, 122, 64],
630 [192, 124, 64],
631 [192, 125, 64],
632 [192, 127, 64],
633 [192, 128, 64],
634 [192, 130, 64],
635 [192, 131, 64],
636 [192, 133, 64],
637 [192, 134, 64],
638 [192, 136, 64],
639 [192, 137, 64],
640 [192, 139, 64],
641 [192, 140, 64],
642 [192, 142, 64],
643 [192, 143, 64],
644 [192, 145, 64],
645 [192, 146, 64],
646 [192, 148, 64],
647 [192, 149, 64],
648 [192, 151, 64],
649 [192, 152, 64],
650 [192, 154, 64],
651 [192, 155, 64],
652 [192, 157, 64],
653 [192, 158, 64],
654 [192, 160, 64],
655 [192, 161, 64],
656 [192, 163, 64],
657 [192, 164, 64],
658 [192, 166, 64],
659 [192, 167, 64],
660 [192, 169, 64],
661 [192, 170, 64],
662 [192, 172, 64],
663 [192, 173, 64],
664 [192, 175, 64],
665 [192, 176, 64],
666 [192, 178, 64],
667 [192, 179, 64],
668 [192, 181, 64],
669 [192, 182, 64],
670 [192, 184, 64],
671 [192, 185, 64],
672 [192, 187, 64],
673 [192, 188, 64],
674 [192, 190, 64],
675 [192, 191, 64],
676 [192, 193, 64],
677 [192, 194, 64],
678 [192, 196, 64],
679 [192, 197, 64],
680 [192, 199, 64],
681 [192, 200, 64],
682 [192, 202, 64],
683 [192, 203, 64],
684 [192, 205, 64],
685 [192, 206, 64],
686 [192, 208, 64],
687 [192, 209, 64],
688 [192, 211, 64],
689 [192, 212, 64],
690 [192, 214, 64],
691 [192, 215, 64],
692 [192, 217, 64],
693 [192, 218, 64],
694 [192, 220, 64],
695 [192, 221, 64],
696 [192, 223, 64],
697 [192, 224, 64],
698 [192, 226, 64],
699 [192, 227, 64],
700 [192, 229, 64],
701 [192, 230, 64],
702 [192, 232, 64],
703 [192, 233, 64],
704 [192, 235, 64],
705 [192, 236, 64],
706 [192, 238, 64],
707 [192, 239, 64],
708 [192, 241, 64],
709 [192, 242, 64],
710 [192, 244, 64],
711 [192, 245, 64],
712 [192, 247, 64],
713 [192, 248, 64],
714 [192, 250, 64],
715 [192, 251, 64],
716 [192, 253, 64],
717 [255, 255, 255],
718];
719
720fn false_color_lut(false_color: i32) -> Option<&'static [[u8; 3]; 256]> {
725 match false_color {
726 1 => Some(&RAINBOW_COLOR_MAP),
727 2 => Some(&IRON_COLOR_MAP),
728 _ => None,
729 }
730}
731
732fn false_color_mono_to_rgb1(src: &NDArray, false_color: i32) -> Option<NDArray> {
749 if src.dims.len() != 2 {
750 return None;
751 }
752 let lut = false_color_lut(false_color)?;
753
754 let w = src.dims[0].size;
755 let h = src.dims[1].size;
756 let n = w * h;
757
758 let (data, data_type) = match &src.data {
760 NDDataBuffer::U8(src_slice) => {
761 let mut out = vec![0u8; n * 3];
762 for i in 0..n {
763 let [r, g, b] = lut[src_slice[i] as usize];
764 out[i * 3] = r;
765 out[i * 3 + 1] = g;
766 out[i * 3 + 2] = b;
767 }
768 (NDDataBuffer::U8(out), NDDataType::UInt8)
769 }
770 NDDataBuffer::I8(src_slice) => {
771 let mut out = vec![0i8; n * 3];
772 for i in 0..n {
773 let [r, g, b] = lut[src_slice[i] as u8 as usize];
774 out[i * 3] = r as i8;
775 out[i * 3 + 1] = g as i8;
776 out[i * 3 + 2] = b as i8;
777 }
778 (NDDataBuffer::I8(out), NDDataType::Int8)
779 }
780 _ => return None,
781 };
782
783 let dims = vec![
784 NDDimension::new(3),
785 NDDimension::new(w),
786 NDDimension::new(h),
787 ];
788 let mut arr = NDArray::new(dims, data_type);
789 arr.data = data;
790 arr.unique_id = src.unique_id;
791 arr.timestamp = src.timestamp;
792 arr.attributes = src.attributes.clone();
793 Some(arr)
794}
795
796#[derive(Debug, Clone)]
798pub struct ColorConvertConfig {
799 pub target_mode: NDColorMode,
800 pub bayer_pattern: NDBayerPattern,
801 pub false_color: i32,
803}
804
805pub struct ColorConvertProcessor {
807 config: Mutex<ColorConvertConfig>,
808 color_mode_out_idx: Option<usize>,
809 false_color_idx: Option<usize>,
810}
811
812impl ColorConvertProcessor {
813 pub fn new(config: ColorConvertConfig) -> Self {
814 Self {
815 config: Mutex::new(config),
816 color_mode_out_idx: None,
817 false_color_idx: None,
818 }
819 }
820}
821
822impl NDPluginProcess for ColorConvertProcessor {
823 fn register_params(
824 &mut self,
825 base: &mut asyn_rs::port::PortDriverBase,
826 ) -> asyn_rs::error::AsynResult<()> {
827 use asyn_rs::param::ParamType;
828 base.create_param("COLOR_MODE_OUT", ParamType::Int32)?;
829 base.create_param("FALSE_COLOR", ParamType::Int32)?;
830 self.color_mode_out_idx = base.find_param("COLOR_MODE_OUT");
831 self.false_color_idx = base.find_param("FALSE_COLOR");
832 Ok(())
833 }
834
835 fn on_param_change(
836 &self,
837 reason: usize,
838 params: &ad_core_rs::plugin::runtime::PluginParamSnapshot,
839 ) -> ad_core_rs::plugin::runtime::ParamChangeResult {
840 if Some(reason) == self.color_mode_out_idx {
841 self.config.lock().target_mode = NDColorMode::from_i32(params.value.as_i32());
842 } else if Some(reason) == self.false_color_idx {
843 self.config.lock().false_color = params.value.as_i32();
844 }
845 ad_core_rs::plugin::runtime::ParamChangeResult::updates(vec![])
846 }
847
848 fn process_array(&self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
849 let src_mode = array.info().color_mode;
854 let (target, false_color, bayer_pattern) = {
855 let config = self.config.lock();
856 (config.target_mode, config.false_color, config.bayer_pattern)
857 };
858
859 let passthrough = || ProcessResult::arrays(vec![Arc::new(array.clone())]);
865
866 if src_mode == target {
867 return passthrough();
868 }
869
870 let rgb1 = match src_mode {
872 NDColorMode::RGB1 => Some(array.clone()),
873 NDColorMode::Mono => {
874 if false_color != 0 {
875 false_color_mono_to_rgb1(array, false_color)
876 .or_else(|| color::mono_to_rgb1(array).ok())
877 } else {
878 color::mono_to_rgb1(array).ok()
879 }
880 }
881 NDColorMode::Bayer => bayer_to_rgb1(array, bayer_pattern),
882 NDColorMode::RGB2 | NDColorMode::RGB3 => {
883 color::convert_rgb_layout(array, src_mode, NDColorMode::RGB1).ok()
884 }
885 NDColorMode::YUV444 => color::yuv444_to_rgb1(array).ok(),
886 NDColorMode::YUV422 => color::yuv422_to_rgb1(array).ok(),
887 NDColorMode::YUV411 => color::yuv411_to_rgb1(array).ok(),
888 };
889
890 let rgb1 = match rgb1 {
891 Some(r) => r,
892 None => return passthrough(),
893 };
894
895 let result = match target {
897 NDColorMode::RGB1 => Some(rgb1),
898 NDColorMode::Mono => color::rgb1_to_mono(&rgb1).ok(),
899 NDColorMode::Bayer => None,
900 NDColorMode::RGB2 | NDColorMode::RGB3 => {
901 color::convert_rgb_layout(&rgb1, NDColorMode::RGB1, target).ok()
902 }
903 NDColorMode::YUV444 => color::rgb1_to_yuv444(&rgb1).ok(),
904 NDColorMode::YUV422 => color::rgb1_to_yuv422(&rgb1).ok(),
905 NDColorMode::YUV411 => color::rgb1_to_yuv411(&rgb1).ok(),
906 };
907
908 match result {
909 Some(mut out) => {
910 let color_mode_val = match target {
912 NDColorMode::Mono => 0i32,
913 NDColorMode::Bayer => 1,
914 NDColorMode::RGB1 => 2,
915 NDColorMode::RGB2 => 3,
916 NDColorMode::RGB3 => 4,
917 NDColorMode::YUV444 => 5,
918 NDColorMode::YUV422 => 6,
919 NDColorMode::YUV411 => 7,
920 };
921 use ad_core_rs::attributes::{NDAttrSource, NDAttrValue, NDAttribute};
922 out.attributes.add(NDAttribute::new_static(
923 "ColorMode",
924 "Color Mode",
925 NDAttrSource::Driver,
926 NDAttrValue::Int32(color_mode_val),
927 ));
928 ProcessResult::arrays(vec![Arc::new(out)])
929 }
930 None => passthrough(),
931 }
932 }
933
934 fn plugin_type(&self) -> &str {
935 "NDPluginColorConvert"
936 }
937}
938
939#[cfg(test)]
940mod tests {
941 use super::*;
942
943 #[test]
944 fn test_bayer_to_rgb1_basic() {
945 let mut arr = NDArray::new(
947 vec![NDDimension::new(4), NDDimension::new(4)],
948 NDDataType::UInt8,
949 );
950 if let NDDataBuffer::U8(ref mut v) = arr.data {
951 for i in 0..16 {
953 v[i] = 128;
954 }
955 }
956
957 let rgb = bayer_to_rgb1(&arr, NDBayerPattern::RGGB).unwrap();
958 assert_eq!(rgb.dims.len(), 3);
959 assert_eq!(rgb.dims[0].size, 3); assert_eq!(rgb.dims[1].size, 4); assert_eq!(rgb.dims[2].size, 4); }
963
964 fn rgb1_pixel(arr: &NDArray, w: usize, x: usize, y: usize) -> [u8; 3] {
966 let i = (y * w + x) * 3;
967 if let NDDataBuffer::U8(ref v) = arr.data {
968 [v[i], v[i + 1], v[i + 2]]
969 } else {
970 panic!("expected UInt8 RGB1 output");
971 }
972 }
973
974 #[test]
975 fn test_adp4_bayer_border_keeps_native_channel_only() {
976 let mut arr = NDArray::new(
981 vec![NDDimension::new(4), NDDimension::new(4)],
982 NDDataType::UInt8,
983 );
984 if let NDDataBuffer::U8(ref mut v) = arr.data {
985 v.iter_mut().for_each(|p| *p = 100);
986 }
987 let rgb = bayer_to_rgb1(&arr, NDBayerPattern::RGGB).unwrap();
988
989 assert_eq!(rgb1_pixel(&rgb, 4, 0, 0), [100, 0, 0]); assert_eq!(rgb1_pixel(&rgb, 4, 1, 0), [0, 100, 0]); assert_eq!(rgb1_pixel(&rgb, 4, 0, 1), [0, 100, 0]); assert_eq!(rgb1_pixel(&rgb, 4, 3, 3), [0, 0, 100]); assert_eq!(rgb1_pixel(&rgb, 4, 1, 1), [100, 100, 100]); assert_eq!(rgb1_pixel(&rgb, 4, 1, 2), [100, 100, 100]); assert_eq!(rgb1_pixel(&rgb, 4, 2, 2), [100, 100, 100]); }
1000
1001 #[test]
1002 fn test_adp4_bayer_interior_uses_fixed_quarter_divisor() {
1003 let mut arr = NDArray::new(
1009 vec![NDDimension::new(3), NDDimension::new(3)],
1010 NDDataType::UInt8,
1011 );
1012 if let NDDataBuffer::U8(ref mut v) = arr.data {
1013 v.copy_from_slice(&[200, 40, 200, 80, 128, 80, 200, 40, 200]);
1015 }
1016 let rgb = bayer_to_rgb1(&arr, NDBayerPattern::RGGB).unwrap();
1017
1018 assert_eq!(rgb1_pixel(&rgb, 3, 1, 1), [200, 60, 128]);
1020 assert_eq!(rgb1_pixel(&rgb, 3, 0, 0), [200, 0, 0]); assert_eq!(rgb1_pixel(&rgb, 3, 1, 0), [0, 40, 0]); }
1024
1025 #[test]
1026 fn test_color_convert_processor_bayer() {
1027 let config = ColorConvertConfig {
1028 target_mode: NDColorMode::RGB1,
1029 bayer_pattern: NDBayerPattern::RGGB,
1030 false_color: 0,
1031 };
1032 let proc = ColorConvertProcessor::new(config);
1033 let pool = NDArrayPool::new(1_000_000);
1034
1035 let mut arr = NDArray::new(
1036 vec![NDDimension::new(4), NDDimension::new(4)],
1037 NDDataType::UInt8,
1038 );
1039 if let NDDataBuffer::U8(ref mut v) = arr.data {
1040 for i in 0..16 {
1041 v[i] = 128;
1042 }
1043 }
1044
1045 let result = proc.process_array(&arr, &pool);
1046 assert_eq!(result.output_arrays.len(), 1);
1047 assert_eq!(result.output_arrays[0].dims[0].size, 3); }
1049
1050 #[test]
1051 fn test_false_color_conversion() {
1052 let config = ColorConvertConfig {
1053 target_mode: NDColorMode::RGB1,
1054 bayer_pattern: NDBayerPattern::RGGB,
1055 false_color: 1,
1056 };
1057 let proc = ColorConvertProcessor::new(config);
1058 let pool = NDArrayPool::new(1_000_000);
1059
1060 let mut arr = NDArray::new(
1062 vec![NDDimension::new(4), NDDimension::new(4)],
1063 NDDataType::UInt8,
1064 );
1065 if let NDDataBuffer::U8(ref mut v) = arr.data {
1066 for i in 0..16 {
1067 v[i] = (i * 17) as u8; }
1069 }
1070
1071 let result = proc.process_array(&arr, &pool);
1072 assert_eq!(result.output_arrays.len(), 1);
1073 let out = &result.output_arrays[0];
1074 assert_eq!(out.dims.len(), 3);
1075 assert_eq!(out.dims[0].size, 3); assert_eq!(out.dims[1].size, 4); assert_eq!(out.dims[2].size, 4); if let NDDataBuffer::U8(ref v) = out.data {
1082 assert_eq!([v[0], v[1], v[2]], RAINBOW_COLOR_MAP[0]);
1083 assert_eq!([v[0], v[1], v[2]], [0, 0, 0]);
1084 let last = 15 * 3;
1086 assert_eq!([v[last], v[last + 1], v[last + 2]], RAINBOW_COLOR_MAP[255]);
1087 } else {
1088 panic!("expected UInt8 output");
1089 }
1090 }
1091
1092 #[test]
1093 fn test_false_color_iron_table() {
1094 let config = ColorConvertConfig {
1095 target_mode: NDColorMode::RGB1,
1096 bayer_pattern: NDBayerPattern::RGGB,
1097 false_color: 2,
1098 };
1099 let proc = ColorConvertProcessor::new(config);
1100 let pool = NDArrayPool::new(1_000_000);
1101
1102 let mut arr = NDArray::new(
1104 vec![NDDimension::new(2), NDDimension::new(1)],
1105 NDDataType::UInt8,
1106 );
1107 if let NDDataBuffer::U8(ref mut v) = arr.data {
1108 v[0] = 0;
1109 v[1] = 192;
1110 }
1111
1112 let result = proc.process_array(&arr, &pool);
1113 let out = &result.output_arrays[0];
1114 if let NDDataBuffer::U8(ref v) = out.data {
1115 assert_eq!([v[0], v[1], v[2]], IRON_COLOR_MAP[0]);
1117 assert_eq!([v[3], v[4], v[5]], IRON_COLOR_MAP[192]);
1118 assert_eq!([v[3], v[4], v[5]], [192, 160, 64]);
1120 assert_ne!(IRON_COLOR_MAP[192], RAINBOW_COLOR_MAP[192]);
1121 } else {
1122 panic!("expected UInt8 output");
1123 }
1124 }
1125
1126 #[test]
1127 fn test_r6_66_false_color_int8_uses_low_byte_index() {
1128 let config = ColorConvertConfig {
1134 target_mode: NDColorMode::RGB1,
1135 bayer_pattern: NDBayerPattern::RGGB,
1136 false_color: 2,
1137 };
1138 let proc = ColorConvertProcessor::new(config);
1139 let pool = NDArrayPool::new(1_000_000);
1140
1141 let mut arr = NDArray::new(
1142 vec![NDDimension::new(2), NDDimension::new(1)],
1143 NDDataType::Int8,
1144 );
1145 if let NDDataBuffer::I8(ref mut v) = arr.data {
1146 v[0] = 0;
1147 v[1] = -64; }
1149
1150 let result = proc.process_array(&arr, &pool);
1151 let out = &result.output_arrays[0];
1152 let NDDataBuffer::I8(ref v) = out.data else {
1153 panic!("Int8 input must stay Int8 (C allocates with pArray->dataType)");
1154 };
1155 let e0 = IRON_COLOR_MAP[0];
1156 let e192 = IRON_COLOR_MAP[192]; assert_eq!(
1158 [v[0], v[1], v[2]],
1159 [e0[0] as i8, e0[1] as i8, e0[2] as i8],
1160 "Int8 mono must be colormapped, not replicated to grayscale"
1161 );
1162 assert_eq!(
1163 [v[3], v[4], v[5]],
1164 [e192[0] as i8, e192[1] as i8, e192[2] as i8]
1165 );
1166 assert_eq!(v[3], -64, "LUT byte 192 reinterpreted as epicsInt8");
1167 }
1168
1169 #[test]
1170 fn test_r6_66_false_color_ignored_for_uint16() {
1171 let config = ColorConvertConfig {
1175 target_mode: NDColorMode::RGB1,
1176 bayer_pattern: NDBayerPattern::RGGB,
1177 false_color: 1,
1178 };
1179 let proc = ColorConvertProcessor::new(config);
1180 let pool = NDArrayPool::new(1_000_000);
1181
1182 let mut arr = NDArray::new(
1183 vec![NDDimension::new(2), NDDimension::new(1)],
1184 NDDataType::UInt16,
1185 );
1186 if let NDDataBuffer::U16(ref mut v) = arr.data {
1187 v[0] = 1000;
1188 v[1] = 2000;
1189 }
1190
1191 let result = proc.process_array(&arr, &pool);
1192 let out = &result.output_arrays[0];
1193 let NDDataBuffer::U16(ref v) = out.data else {
1194 panic!("expected UInt16 output");
1195 };
1196 assert_eq!(v[..6], [1000, 1000, 1000, 2000, 2000, 2000]);
1197 }
1198
1199 #[test]
1200 fn test_rgb1_to_rgb2_conversion() {
1201 let config = ColorConvertConfig {
1202 target_mode: NDColorMode::RGB2,
1203 bayer_pattern: NDBayerPattern::RGGB,
1204 false_color: 0,
1205 };
1206 let proc = ColorConvertProcessor::new(config);
1207 let pool = NDArrayPool::new(1_000_000);
1208
1209 let mut arr = NDArray::new(
1211 vec![
1212 NDDimension::new(3),
1213 NDDimension::new(4),
1214 NDDimension::new(4),
1215 ],
1216 NDDataType::UInt8,
1217 );
1218 if let NDDataBuffer::U8(ref mut v) = arr.data {
1219 for i in 0..v.len() {
1220 v[i] = (i % 256) as u8;
1221 }
1222 }
1223 set_color_mode_attr(&mut arr, NDColorMode::RGB1);
1226
1227 let result = proc.process_array(&arr, &pool);
1228 assert_eq!(result.output_arrays.len(), 1);
1229 let out = &result.output_arrays[0];
1230 assert_eq!(out.dims.len(), 3);
1231 assert_eq!(out.dims[1].size, 3);
1233 }
1234
1235 #[test]
1236 fn test_rgb2_to_mono_conversion() {
1237 let config = ColorConvertConfig {
1238 target_mode: NDColorMode::Mono,
1239 bayer_pattern: NDBayerPattern::RGGB,
1240 false_color: 0,
1241 };
1242 let proc = ColorConvertProcessor::new(config);
1243 let pool = NDArrayPool::new(1_000_000);
1244
1245 let mut arr = NDArray::new(
1247 vec![
1248 NDDimension::new(4),
1249 NDDimension::new(3),
1250 NDDimension::new(4),
1251 ],
1252 NDDataType::UInt8,
1253 );
1254 if let NDDataBuffer::U8(ref mut v) = arr.data {
1255 for i in 0..v.len() {
1256 v[i] = 128;
1257 }
1258 }
1259 set_color_mode_attr(&mut arr, NDColorMode::RGB2);
1262
1263 let result = proc.process_array(&arr, &pool);
1264 assert_eq!(result.output_arrays.len(), 1);
1265 let out = &result.output_arrays[0];
1266 assert_eq!(out.dims.len(), 2);
1268 }
1269
1270 #[test]
1275 fn r9_80_dims_never_imply_a_color_mode() {
1276 for dims in [
1277 vec![
1278 NDDimension::new(3),
1279 NDDimension::new(4),
1280 NDDimension::new(4),
1281 ],
1282 vec![
1283 NDDimension::new(4),
1284 NDDimension::new(3),
1285 NDDimension::new(4),
1286 ],
1287 vec![
1288 NDDimension::new(4),
1289 NDDimension::new(4),
1290 NDDimension::new(3),
1291 ],
1292 vec![NDDimension::new(4), NDDimension::new(4)],
1293 ] {
1294 let arr = NDArray::new(dims, NDDataType::UInt8);
1295 assert_eq!(
1296 arr.info().color_mode,
1297 NDColorMode::Mono,
1298 "no ColorMode attribute -> Mono, never an RGB layout guessed from a \
1299 size-3 dimension"
1300 );
1301 }
1302 }
1303
1304 #[test]
1305 fn test_same_mode_passthrough() {
1306 let config = ColorConvertConfig {
1307 target_mode: NDColorMode::Mono,
1308 bayer_pattern: NDBayerPattern::RGGB,
1309 false_color: 0,
1310 };
1311 let proc = ColorConvertProcessor::new(config);
1312 let pool = NDArrayPool::new(1_000_000);
1313
1314 let mut arr = NDArray::new(
1316 vec![NDDimension::new(4), NDDimension::new(4)],
1317 NDDataType::UInt8,
1318 );
1319 arr.unique_id = 42;
1320 if let NDDataBuffer::U8(ref mut v) = arr.data {
1321 for i in 0..16 {
1322 v[i] = i as u8;
1323 }
1324 }
1325
1326 let result = proc.process_array(&arr, &pool);
1327 assert_eq!(result.output_arrays.len(), 1);
1328 assert_eq!(result.output_arrays[0].unique_id, 42);
1329 assert_eq!(result.output_arrays[0].dims.len(), 2);
1330 }
1331
1332 fn set_color_mode_attr(arr: &mut NDArray, mode: NDColorMode) {
1333 use ad_core_rs::attributes::{NDAttrSource, NDAttrValue, NDAttribute};
1334 arr.attributes.add(NDAttribute::new_static(
1335 "ColorMode",
1336 String::new(),
1337 NDAttrSource::Driver,
1338 NDAttrValue::Int32(mode as i32),
1339 ));
1340 }
1341
1342 #[test]
1343 fn test_bayer_to_mono_via_rgb1() {
1344 let config = ColorConvertConfig {
1345 target_mode: NDColorMode::Mono,
1346 bayer_pattern: NDBayerPattern::RGGB,
1347 false_color: 0,
1348 };
1349 let proc = ColorConvertProcessor::new(config);
1350 let pool = NDArrayPool::new(1_000_000);
1351
1352 let mut arr = NDArray::new(
1353 vec![NDDimension::new(4), NDDimension::new(4)],
1354 NDDataType::UInt8,
1355 );
1356 set_color_mode_attr(&mut arr, NDColorMode::Bayer);
1357 if let NDDataBuffer::U8(ref mut v) = arr.data {
1358 for i in 0..16 {
1359 v[i] = 128;
1360 }
1361 }
1362
1363 let result = proc.process_array(&arr, &pool);
1364 assert_eq!(result.output_arrays.len(), 1);
1365 assert_eq!(result.output_arrays[0].dims.len(), 2);
1366 }
1367
1368 #[test]
1369 fn test_rgb1_to_yuv444_conversion() {
1370 let config = ColorConvertConfig {
1371 target_mode: NDColorMode::YUV444,
1372 bayer_pattern: NDBayerPattern::RGGB,
1373 false_color: 0,
1374 };
1375 let proc = ColorConvertProcessor::new(config);
1376 let pool = NDArrayPool::new(1_000_000);
1377
1378 let mut arr = NDArray::new(
1379 vec![
1380 NDDimension::new(3),
1381 NDDimension::new(4),
1382 NDDimension::new(4),
1383 ],
1384 NDDataType::UInt8,
1385 );
1386 if let NDDataBuffer::U8(ref mut v) = arr.data {
1387 for i in 0..v.len() {
1388 v[i] = (i % 256) as u8;
1389 }
1390 }
1391
1392 let result = proc.process_array(&arr, &pool);
1393 assert_eq!(result.output_arrays.len(), 1);
1394 let out = &result.output_arrays[0];
1395 assert_eq!(out.dims.len(), 3);
1396 assert_eq!(out.dims[0].size, 3);
1397 }
1398
1399 #[test]
1400 fn test_yuv422_to_rgb1_conversion() {
1401 let config = ColorConvertConfig {
1402 target_mode: NDColorMode::RGB1,
1403 bayer_pattern: NDBayerPattern::RGGB,
1404 false_color: 0,
1405 };
1406 let proc = ColorConvertProcessor::new(config);
1407 let pool = NDArrayPool::new(1_000_000);
1408
1409 let mut arr = NDArray::new(
1411 vec![NDDimension::new(8), NDDimension::new(2)],
1412 NDDataType::UInt8,
1413 );
1414 set_color_mode_attr(&mut arr, NDColorMode::YUV422);
1415 if let NDDataBuffer::U8(ref mut v) = arr.data {
1416 let uyvy: [u8; 16] = [
1418 128, 100, 128, 150, 128, 200, 128, 50, 128, 128, 128, 128, 128, 64, 128, 192,
1419 ];
1420 v[..16].copy_from_slice(&uyvy);
1421 }
1422
1423 let result = proc.process_array(&arr, &pool);
1424 assert_eq!(result.output_arrays.len(), 1);
1425 let out = &result.output_arrays[0];
1426 assert_eq!(out.dims[0].size, 3);
1427 assert_eq!(out.dims[1].size, 4);
1428 assert_eq!(out.dims[2].size, 2);
1429 }
1430
1431 #[test]
1432 fn test_mono_to_yuv422_conversion() {
1433 let config = ColorConvertConfig {
1434 target_mode: NDColorMode::YUV422,
1435 bayer_pattern: NDBayerPattern::RGGB,
1436 false_color: 0,
1437 };
1438 let proc = ColorConvertProcessor::new(config);
1439 let pool = NDArrayPool::new(1_000_000);
1440
1441 let mut arr = NDArray::new(
1442 vec![NDDimension::new(4), NDDimension::new(2)],
1443 NDDataType::UInt8,
1444 );
1445 if let NDDataBuffer::U8(ref mut v) = arr.data {
1446 for i in 0..8 {
1447 v[i] = (i * 30) as u8;
1448 }
1449 }
1450
1451 let result = proc.process_array(&arr, &pool);
1452 assert_eq!(result.output_arrays.len(), 1);
1453 let out = &result.output_arrays[0];
1454 assert_eq!(out.dims.len(), 2);
1455 assert_eq!(out.dims[0].size, 8); }
1457
1458 #[test]
1459 fn test_yuv444_to_mono_conversion() {
1460 let config = ColorConvertConfig {
1461 target_mode: NDColorMode::Mono,
1462 bayer_pattern: NDBayerPattern::RGGB,
1463 false_color: 0,
1464 };
1465 let proc = ColorConvertProcessor::new(config);
1466 let pool = NDArrayPool::new(1_000_000);
1467
1468 let mut arr = NDArray::new(
1469 vec![
1470 NDDimension::new(3),
1471 NDDimension::new(4),
1472 NDDimension::new(4),
1473 ],
1474 NDDataType::UInt8,
1475 );
1476 set_color_mode_attr(&mut arr, NDColorMode::YUV444);
1477 if let NDDataBuffer::U8(ref mut v) = arr.data {
1478 for i in 0..v.len() {
1479 v[i] = 128;
1480 }
1481 }
1482
1483 let result = proc.process_array(&arr, &pool);
1484 assert_eq!(result.output_arrays.len(), 1);
1485 let out = &result.output_arrays[0];
1486 assert_eq!(out.dims.len(), 2);
1487 assert_eq!(out.dims[0].size, 4);
1488 assert_eq!(out.dims[1].size, 4);
1489 }
1490
1491 #[test]
1492 fn test_color_mode_comes_from_the_attribute() {
1493 let mut arr = NDArray::new(
1494 vec![NDDimension::new(8), NDDimension::new(2)],
1495 NDDataType::UInt8,
1496 );
1497 assert_eq!(arr.info().color_mode, NDColorMode::Mono);
1498
1499 set_color_mode_attr(&mut arr, NDColorMode::YUV422);
1500 assert_eq!(arr.info().color_mode, NDColorMode::YUV422);
1501 }
1502
1503 #[test]
1504 fn test_false_color_table_endpoints() {
1505 assert_eq!(RAINBOW_COLOR_MAP[0], [0, 0, 0]);
1509 assert_eq!(RAINBOW_COLOR_MAP[255], [255, 255, 255]);
1510 assert_eq!(IRON_COLOR_MAP[0], [0, 0, 0]);
1511 assert_eq!(IRON_COLOR_MAP[255], [255, 255, 255]);
1512
1513 assert_eq!(RAINBOW_COLOR_MAP[128], [4, 255, 251]);
1516 assert_eq!(IRON_COLOR_MAP[128], [192, 64, 64]);
1517
1518 assert_eq!(false_color_lut(1), Some(&RAINBOW_COLOR_MAP));
1520 assert_eq!(false_color_lut(2), Some(&IRON_COLOR_MAP));
1521 assert_eq!(false_color_lut(0), None);
1522 assert_eq!(false_color_lut(3), None);
1523 }
1524}