1use std::sync::Arc;
2
3#[cfg(feature = "parallel")]
4use rayon::prelude::*;
5#[cfg(feature = "parallel")]
6use crate::par_util;
7
8use ad_core::color::{self, NDColorMode, NDBayerPattern};
9use ad_core::ndarray::{NDArray, NDDataBuffer, NDDataType, NDDimension};
10use ad_core::ndarray_pool::NDArrayPool;
11use ad_core::plugin::runtime::{NDPluginProcess, ProcessResult};
12
13pub fn bayer_to_rgb1(src: &NDArray, pattern: NDBayerPattern) -> Option<NDArray> {
15 if src.dims.len() != 2 {
16 return None;
17 }
18 let w = src.dims[0].size;
19 let h = src.dims[1].size;
20
21 let n = w * h;
23 let src_vals: Vec<f64> = (0..n).map(|i| src.data.get_as_f64(i).unwrap_or(0.0)).collect();
24 let get_val = |x: usize, y: usize| -> f64 { src_vals[y * w + x] };
25
26 let mut r = vec![0.0f64; n];
27 let mut g = vec![0.0f64; n];
28 let mut b = vec![0.0f64; n];
29
30 let (r_row_even, r_col_even) = match pattern {
32 NDBayerPattern::RGGB => (true, true),
33 NDBayerPattern::GBRG => (true, false),
34 NDBayerPattern::GRBG => (false, true),
35 NDBayerPattern::BGGR => (false, false),
36 };
37
38 let demosaic_row = |y: usize, r_row: &mut [f64], g_row: &mut [f64], b_row: &mut [f64]| {
40 let even_row = (y % 2 == 0) == r_row_even;
41 for x in 0..w {
42 let val = get_val(x, y);
43 let even_col = (x % 2 == 0) == r_col_even;
44
45 match (even_row, even_col) {
46 (true, true) => {
47 r_row[x] = val;
48 let mut gsum = 0.0; let mut gc = 0;
49 if x > 0 { gsum += get_val(x - 1, y); gc += 1; }
50 if x < w - 1 { gsum += get_val(x + 1, y); gc += 1; }
51 if y > 0 { gsum += get_val(x, y - 1); gc += 1; }
52 if y < h - 1 { gsum += get_val(x, y + 1); gc += 1; }
53 g_row[x] = if gc > 0 { gsum / gc as f64 } else { 0.0 };
54 let mut bsum = 0.0; let mut bc = 0;
55 if x > 0 && y > 0 { bsum += get_val(x - 1, y - 1); bc += 1; }
56 if x < w - 1 && y > 0 { bsum += get_val(x + 1, y - 1); bc += 1; }
57 if x > 0 && y < h - 1 { bsum += get_val(x - 1, y + 1); bc += 1; }
58 if x < w - 1 && y < h - 1 { bsum += get_val(x + 1, y + 1); bc += 1; }
59 b_row[x] = if bc > 0 { bsum / bc as f64 } else { 0.0 };
60 }
61 (true, false) | (false, true) => {
62 g_row[x] = val;
63 if even_row {
64 let mut rsum = 0.0; let mut rc = 0;
65 if x > 0 { rsum += get_val(x - 1, y); rc += 1; }
66 if x < w - 1 { rsum += get_val(x + 1, y); rc += 1; }
67 r_row[x] = if rc > 0 { rsum / rc as f64 } else { 0.0 };
68 let mut bsum = 0.0; let mut bc = 0;
69 if y > 0 { bsum += get_val(x, y - 1); bc += 1; }
70 if y < h - 1 { bsum += get_val(x, y + 1); bc += 1; }
71 b_row[x] = if bc > 0 { bsum / bc as f64 } else { 0.0 };
72 } else {
73 let mut bsum = 0.0; let mut bc = 0;
74 if x > 0 { bsum += get_val(x - 1, y); bc += 1; }
75 if x < w - 1 { bsum += get_val(x + 1, y); bc += 1; }
76 b_row[x] = if bc > 0 { bsum / bc as f64 } else { 0.0 };
77 let mut rsum = 0.0; let mut rc = 0;
78 if y > 0 { rsum += get_val(x, y - 1); rc += 1; }
79 if y < h - 1 { rsum += get_val(x, y + 1); rc += 1; }
80 r_row[x] = if rc > 0 { rsum / rc as f64 } else { 0.0 };
81 }
82 }
83 (false, false) => {
84 b_row[x] = val;
85 let mut gsum = 0.0; let mut gc = 0;
86 if x > 0 { gsum += get_val(x - 1, y); gc += 1; }
87 if x < w - 1 { gsum += get_val(x + 1, y); gc += 1; }
88 if y > 0 { gsum += get_val(x, y - 1); gc += 1; }
89 if y < h - 1 { gsum += get_val(x, y + 1); gc += 1; }
90 g_row[x] = if gc > 0 { gsum / gc as f64 } else { 0.0 };
91 let mut rsum = 0.0; let mut rc = 0;
92 if x > 0 && y > 0 { rsum += get_val(x - 1, y - 1); rc += 1; }
93 if x < w - 1 && y > 0 { rsum += get_val(x + 1, y - 1); rc += 1; }
94 if x > 0 && y < h - 1 { rsum += get_val(x - 1, y + 1); rc += 1; }
95 if x < w - 1 && y < h - 1 { rsum += get_val(x + 1, y + 1); rc += 1; }
96 r_row[x] = if rc > 0 { rsum / rc as f64 } else { 0.0 };
97 }
98 }
99 }
100 };
101
102 #[cfg(feature = "parallel")]
103 let use_parallel = par_util::should_parallelize(n);
104 #[cfg(not(feature = "parallel"))]
105 let use_parallel = false;
106
107 if use_parallel {
108 #[cfg(feature = "parallel")]
109 {
110 let r_rows: Vec<&mut [f64]> = r.chunks_mut(w).collect();
112 let g_rows: Vec<&mut [f64]> = g.chunks_mut(w).collect();
113 let b_rows: Vec<&mut [f64]> = b.chunks_mut(w).collect();
114
115 par_util::thread_pool().install(|| {
116 r_rows.into_par_iter()
117 .zip(g_rows.into_par_iter())
118 .zip(b_rows.into_par_iter())
119 .enumerate()
120 .for_each(|(y, ((r_row, g_row), b_row))| {
121 demosaic_row(y, r_row, g_row, b_row);
122 });
123 });
124 }
125 } else {
126 for y in 0..h {
127 let row_start = y * w;
128 let row_end = row_start + w;
129 demosaic_row(
130 y,
131 &mut r[row_start..row_end],
132 &mut g[row_start..row_end],
133 &mut b[row_start..row_end],
134 );
135 }
136 }
137
138 let out_data = match src.data.data_type() {
140 NDDataType::UInt8 => {
141 let mut out = vec![0u8; n * 3];
142 for i in 0..n {
143 out[i * 3] = r[i].clamp(0.0, 255.0) as u8;
144 out[i * 3 + 1] = g[i].clamp(0.0, 255.0) as u8;
145 out[i * 3 + 2] = b[i].clamp(0.0, 255.0) as u8;
146 }
147 NDDataBuffer::U8(out)
148 }
149 NDDataType::UInt16 => {
150 let mut out = vec![0u16; n * 3];
151 for i in 0..n {
152 out[i * 3] = r[i].clamp(0.0, 65535.0) as u16;
153 out[i * 3 + 1] = g[i].clamp(0.0, 65535.0) as u16;
154 out[i * 3 + 2] = b[i].clamp(0.0, 65535.0) as u16;
155 }
156 NDDataBuffer::U16(out)
157 }
158 _ => return None,
159 };
160
161 let dims = vec![NDDimension::new(3), NDDimension::new(w), NDDimension::new(h)];
162 let mut arr = NDArray::new(dims, src.data.data_type());
163 arr.data = out_data;
164 arr.unique_id = src.unique_id;
165 arr.timestamp = src.timestamp;
166 arr.attributes = src.attributes.clone();
167 Some(arr)
168}
169
170fn jet_colormap() -> [[u8; 3]; 256] {
174 let mut lut = [[0u8; 3]; 256];
175 for i in 0..256 {
176 let v = i as f64 / 255.0;
177 let r = (1.5 - (4.0 * v - 3.0).abs()).clamp(0.0, 1.0);
179 let g = (1.5 - (4.0 * v - 2.0).abs()).clamp(0.0, 1.0);
180 let b = (1.5 - (4.0 * v - 1.0).abs()).clamp(0.0, 1.0);
181 lut[i] = [(r * 255.0) as u8, (g * 255.0) as u8, (b * 255.0) as u8];
182 }
183 lut
184}
185
186fn false_color_mono_to_rgb1(src: &NDArray) -> Option<NDArray> {
191 if src.dims.len() != 2 || src.data.data_type() != NDDataType::UInt8 {
192 return None;
193 }
194
195 let w = src.dims[0].size;
196 let h = src.dims[1].size;
197 let n = w * h;
198 let lut = jet_colormap();
199
200 let src_slice = src.data.as_u8_slice();
201 let mut out = vec![0u8; n * 3];
202 for i in 0..n {
203 let val = src_slice[i] as usize;
204 let [r, g, b] = lut[val];
205 out[i * 3] = r;
206 out[i * 3 + 1] = g;
207 out[i * 3 + 2] = b;
208 }
209
210 let dims = vec![NDDimension::new(3), NDDimension::new(w), NDDimension::new(h)];
211 let mut arr = NDArray::new(dims, NDDataType::UInt8);
212 arr.data = NDDataBuffer::U8(out);
213 arr.unique_id = src.unique_id;
214 arr.timestamp = src.timestamp;
215 arr.attributes = src.attributes.clone();
216 Some(arr)
217}
218
219fn detect_color_mode(array: &NDArray) -> NDColorMode {
225 match array.dims.len() {
226 0 | 1 => NDColorMode::Mono,
227 2 => NDColorMode::Mono, 3 => {
229 if array.dims[0].size == 3 {
231 NDColorMode::RGB1
232 } else if array.dims[1].size == 3 {
233 NDColorMode::RGB2
234 } else if array.dims[2].size == 3 {
235 NDColorMode::RGB3
236 } else {
237 NDColorMode::Mono
238 }
239 }
240 _ => NDColorMode::Mono,
241 }
242}
243
244#[derive(Debug, Clone)]
246pub struct ColorConvertConfig {
247 pub target_mode: NDColorMode,
248 pub bayer_pattern: NDBayerPattern,
249 pub false_color: bool,
250}
251
252pub struct ColorConvertProcessor {
254 config: ColorConvertConfig,
255}
256
257impl ColorConvertProcessor {
258 pub fn new(config: ColorConvertConfig) -> Self {
259 Self { config }
260 }
261}
262
263impl NDPluginProcess for ColorConvertProcessor {
264 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
265 let src_mode = detect_color_mode(array);
266 let result = match (src_mode, self.config.target_mode) {
267 (s, t) if s == t => Some(array.clone()),
269
270 (NDColorMode::RGB1 | NDColorMode::RGB2 | NDColorMode::RGB3, NDColorMode::Mono) => {
272 let rgb1 = if src_mode != NDColorMode::RGB1 {
274 color::convert_rgb_layout(array, src_mode, NDColorMode::RGB1).ok()
275 } else {
276 Some(array.clone())
277 };
278 rgb1.and_then(|a| color::rgb1_to_mono(&a).ok())
279 }
280
281 (NDColorMode::Mono, NDColorMode::RGB1) if self.config.false_color => {
283 false_color_mono_to_rgb1(array)
284 }
285
286 (NDColorMode::Mono, NDColorMode::RGB1) => color::mono_to_rgb1(array).ok(),
288 (NDColorMode::Mono, NDColorMode::RGB2 | NDColorMode::RGB3) => {
289 color::mono_to_rgb1(array).ok().and_then(|a| {
290 color::convert_rgb_layout(&a, NDColorMode::RGB1, self.config.target_mode).ok()
291 })
292 }
293
294 (NDColorMode::Bayer, NDColorMode::RGB1) => {
296 bayer_to_rgb1(array, self.config.bayer_pattern)
297 }
298 (NDColorMode::Bayer, NDColorMode::RGB2 | NDColorMode::RGB3) => {
299 bayer_to_rgb1(array, self.config.bayer_pattern).and_then(|a| {
300 color::convert_rgb_layout(&a, NDColorMode::RGB1, self.config.target_mode).ok()
301 })
302 }
303
304 (
306 NDColorMode::RGB1 | NDColorMode::RGB2 | NDColorMode::RGB3,
307 NDColorMode::RGB1 | NDColorMode::RGB2 | NDColorMode::RGB3,
308 ) => color::convert_rgb_layout(array, src_mode, self.config.target_mode).ok(),
309
310 _ => None,
311 };
312 match result {
313 Some(out) => ProcessResult::arrays(vec![Arc::new(out)]),
314 None => ProcessResult::empty(),
315 }
316 }
317
318 fn plugin_type(&self) -> &str {
319 "NDPluginColorConvert"
320 }
321}
322
323#[cfg(test)]
324mod tests {
325 use super::*;
326
327 #[test]
328 fn test_bayer_to_rgb1_basic() {
329 let mut arr = NDArray::new(
331 vec![NDDimension::new(4), NDDimension::new(4)],
332 NDDataType::UInt8,
333 );
334 if let NDDataBuffer::U8(ref mut v) = arr.data {
335 for i in 0..16 {
337 v[i] = 128;
338 }
339 }
340
341 let rgb = bayer_to_rgb1(&arr, NDBayerPattern::RGGB).unwrap();
342 assert_eq!(rgb.dims.len(), 3);
343 assert_eq!(rgb.dims[0].size, 3); assert_eq!(rgb.dims[1].size, 4); assert_eq!(rgb.dims[2].size, 4); }
347
348 #[test]
349 fn test_color_convert_processor_bayer() {
350 let config = ColorConvertConfig {
351 target_mode: NDColorMode::RGB1,
352 bayer_pattern: NDBayerPattern::RGGB,
353 false_color: false,
354 };
355 let mut proc = ColorConvertProcessor::new(config);
356 let pool = NDArrayPool::new(1_000_000);
357
358 let mut arr = NDArray::new(
359 vec![NDDimension::new(4), NDDimension::new(4)],
360 NDDataType::UInt8,
361 );
362 if let NDDataBuffer::U8(ref mut v) = arr.data {
363 for i in 0..16 {
364 v[i] = 128;
365 }
366 }
367
368 let result = proc.process_array(&arr, &pool);
369 assert_eq!(result.output_arrays.len(), 1);
370 assert_eq!(result.output_arrays[0].dims[0].size, 3); }
372
373 #[test]
374 fn test_false_color_conversion() {
375 let config = ColorConvertConfig {
376 target_mode: NDColorMode::RGB1,
377 bayer_pattern: NDBayerPattern::RGGB,
378 false_color: true,
379 };
380 let mut proc = ColorConvertProcessor::new(config);
381 let pool = NDArrayPool::new(1_000_000);
382
383 let mut arr = NDArray::new(
385 vec![NDDimension::new(4), NDDimension::new(4)],
386 NDDataType::UInt8,
387 );
388 if let NDDataBuffer::U8(ref mut v) = arr.data {
389 for i in 0..16 {
390 v[i] = (i * 17) as u8; }
392 }
393
394 let result = proc.process_array(&arr, &pool);
395 assert_eq!(result.output_arrays.len(), 1);
396 let out = &result.output_arrays[0];
397 assert_eq!(out.dims.len(), 3);
398 assert_eq!(out.dims[0].size, 3); assert_eq!(out.dims[1].size, 4); assert_eq!(out.dims[2].size, 4); let lut = jet_colormap();
404 if let NDDataBuffer::U8(ref v) = out.data {
405 assert_eq!(v[0], lut[0][0]); assert_eq!(v[1], lut[0][1]); assert_eq!(v[2], lut[0][2]); let last = 15 * 3;
411 assert_eq!(v[last], lut[255][0]); assert_eq!(v[last + 1], lut[255][1]); assert_eq!(v[last + 2], lut[255][2]); } else {
415 panic!("expected UInt8 output");
416 }
417 }
418
419 #[test]
420 fn test_rgb1_to_rgb2_conversion() {
421 let config = ColorConvertConfig {
422 target_mode: NDColorMode::RGB2,
423 bayer_pattern: NDBayerPattern::RGGB,
424 false_color: false,
425 };
426 let mut proc = ColorConvertProcessor::new(config);
427 let pool = NDArrayPool::new(1_000_000);
428
429 let mut arr = NDArray::new(
431 vec![NDDimension::new(3), NDDimension::new(4), NDDimension::new(4)],
432 NDDataType::UInt8,
433 );
434 if let NDDataBuffer::U8(ref mut v) = arr.data {
435 for i in 0..v.len() {
436 v[i] = (i % 256) as u8;
437 }
438 }
439
440 let result = proc.process_array(&arr, &pool);
441 assert_eq!(result.output_arrays.len(), 1);
442 let out = &result.output_arrays[0];
443 assert_eq!(out.dims.len(), 3);
444 assert_eq!(out.dims[1].size, 3);
446 }
447
448 #[test]
449 fn test_rgb2_to_mono_conversion() {
450 let config = ColorConvertConfig {
451 target_mode: NDColorMode::Mono,
452 bayer_pattern: NDBayerPattern::RGGB,
453 false_color: false,
454 };
455 let mut proc = ColorConvertProcessor::new(config);
456 let pool = NDArrayPool::new(1_000_000);
457
458 let mut arr = NDArray::new(
460 vec![NDDimension::new(4), NDDimension::new(3), NDDimension::new(4)],
461 NDDataType::UInt8,
462 );
463 if let NDDataBuffer::U8(ref mut v) = arr.data {
464 for i in 0..v.len() {
465 v[i] = 128;
466 }
467 }
468
469 let result = proc.process_array(&arr, &pool);
470 assert_eq!(result.output_arrays.len(), 1);
471 let out = &result.output_arrays[0];
472 assert_eq!(out.dims.len(), 2);
474 }
475
476 #[test]
477 fn test_detect_color_mode() {
478 let arr2d = NDArray::new(
480 vec![NDDimension::new(4), NDDimension::new(4)],
481 NDDataType::UInt8,
482 );
483 assert_eq!(detect_color_mode(&arr2d), NDColorMode::Mono);
484
485 let arr_rgb1 = NDArray::new(
487 vec![NDDimension::new(3), NDDimension::new(4), NDDimension::new(4)],
488 NDDataType::UInt8,
489 );
490 assert_eq!(detect_color_mode(&arr_rgb1), NDColorMode::RGB1);
491
492 let arr_rgb2 = NDArray::new(
494 vec![NDDimension::new(4), NDDimension::new(3), NDDimension::new(4)],
495 NDDataType::UInt8,
496 );
497 assert_eq!(detect_color_mode(&arr_rgb2), NDColorMode::RGB2);
498
499 let arr_rgb3 = NDArray::new(
501 vec![NDDimension::new(4), NDDimension::new(4), NDDimension::new(3)],
502 NDDataType::UInt8,
503 );
504 assert_eq!(detect_color_mode(&arr_rgb3), NDColorMode::RGB3);
505 }
506
507 #[test]
508 fn test_same_mode_passthrough() {
509 let config = ColorConvertConfig {
510 target_mode: NDColorMode::Mono,
511 bayer_pattern: NDBayerPattern::RGGB,
512 false_color: false,
513 };
514 let mut proc = ColorConvertProcessor::new(config);
515 let pool = NDArrayPool::new(1_000_000);
516
517 let mut arr = NDArray::new(
519 vec![NDDimension::new(4), NDDimension::new(4)],
520 NDDataType::UInt8,
521 );
522 arr.unique_id = 42;
523 if let NDDataBuffer::U8(ref mut v) = arr.data {
524 for i in 0..16 {
525 v[i] = i as u8;
526 }
527 }
528
529 let result = proc.process_array(&arr, &pool);
530 assert_eq!(result.output_arrays.len(), 1);
531 assert_eq!(result.output_arrays[0].unique_id, 42);
532 assert_eq!(result.output_arrays[0].dims.len(), 2);
533 }
534
535 #[test]
536 fn test_jet_colormap_endpoints() {
537 let lut = jet_colormap();
538 assert_eq!(lut[0][0], 0); assert_eq!(lut[0][1], 0); assert_eq!(lut[0][2], 127); assert_eq!(lut[255][0], 127); assert_eq!(lut[255][1], 0); assert_eq!(lut[255][2], 0); }
548}