1use crate::attributes::NDAttributeList;
2use crate::codec::Codec;
3use crate::error::{ADError, ADResult};
4use crate::timestamp::EpicsTimestamp;
5
6pub const ND_ARRAY_MAX_DIMS: usize = 10;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(u8)]
15pub enum NDDataType {
16 Int8 = 0,
17 UInt8 = 1,
18 Int16 = 2,
19 UInt16 = 3,
20 Int32 = 4,
21 UInt32 = 5,
22 Int64 = 6,
23 UInt64 = 7,
24 Float32 = 8,
25 Float64 = 9,
26}
27
28impl NDDataType {
29 pub fn element_size(&self) -> usize {
30 match self {
31 Self::Int8 | Self::UInt8 => 1,
32 Self::Int16 | Self::UInt16 => 2,
33 Self::Int32 | Self::UInt32 | Self::Float32 => 4,
34 Self::Int64 | Self::UInt64 | Self::Float64 => 8,
35 }
36 }
37
38 pub fn from_ordinal(v: u8) -> Option<Self> {
39 match v {
40 0 => Some(Self::Int8),
41 1 => Some(Self::UInt8),
42 2 => Some(Self::Int16),
43 3 => Some(Self::UInt16),
44 4 => Some(Self::Int32),
45 5 => Some(Self::UInt32),
46 6 => Some(Self::Int64),
47 7 => Some(Self::UInt64),
48 8 => Some(Self::Float32),
49 9 => Some(Self::Float64),
50 _ => None,
51 }
52 }
53}
54
55#[derive(Debug, Clone)]
57pub enum NDDataBuffer {
58 I8(Vec<i8>),
59 U8(Vec<u8>),
60 I16(Vec<i16>),
61 U16(Vec<u16>),
62 I32(Vec<i32>),
63 U32(Vec<u32>),
64 I64(Vec<i64>),
65 U64(Vec<u64>),
66 F32(Vec<f32>),
67 F64(Vec<f64>),
68}
69
70impl NDDataBuffer {
71 pub fn zeros(data_type: NDDataType, count: usize) -> Self {
72 match data_type {
73 NDDataType::Int8 => Self::I8(vec![0; count]),
74 NDDataType::UInt8 => Self::U8(vec![0; count]),
75 NDDataType::Int16 => Self::I16(vec![0; count]),
76 NDDataType::UInt16 => Self::U16(vec![0; count]),
77 NDDataType::Int32 => Self::I32(vec![0; count]),
78 NDDataType::UInt32 => Self::U32(vec![0; count]),
79 NDDataType::Int64 => Self::I64(vec![0; count]),
80 NDDataType::UInt64 => Self::U64(vec![0; count]),
81 NDDataType::Float32 => Self::F32(vec![0.0; count]),
82 NDDataType::Float64 => Self::F64(vec![0.0; count]),
83 }
84 }
85
86 pub fn data_type(&self) -> NDDataType {
87 match self {
88 Self::I8(_) => NDDataType::Int8,
89 Self::U8(_) => NDDataType::UInt8,
90 Self::I16(_) => NDDataType::Int16,
91 Self::U16(_) => NDDataType::UInt16,
92 Self::I32(_) => NDDataType::Int32,
93 Self::U32(_) => NDDataType::UInt32,
94 Self::I64(_) => NDDataType::Int64,
95 Self::U64(_) => NDDataType::UInt64,
96 Self::F32(_) => NDDataType::Float32,
97 Self::F64(_) => NDDataType::Float64,
98 }
99 }
100
101 pub fn len(&self) -> usize {
102 match self {
103 Self::I8(v) => v.len(),
104 Self::U8(v) => v.len(),
105 Self::I16(v) => v.len(),
106 Self::U16(v) => v.len(),
107 Self::I32(v) => v.len(),
108 Self::U32(v) => v.len(),
109 Self::I64(v) => v.len(),
110 Self::U64(v) => v.len(),
111 Self::F32(v) => v.len(),
112 Self::F64(v) => v.len(),
113 }
114 }
115
116 pub fn is_empty(&self) -> bool {
117 self.len() == 0
118 }
119
120 pub fn total_bytes(&self) -> usize {
121 self.len() * self.data_type().element_size()
122 }
123
124 pub fn capacity_bytes(&self) -> usize {
126 let cap = match self {
127 Self::I8(v) => v.capacity(),
128 Self::U8(v) => v.capacity(),
129 Self::I16(v) => v.capacity(),
130 Self::U16(v) => v.capacity(),
131 Self::I32(v) => v.capacity(),
132 Self::U32(v) => v.capacity(),
133 Self::I64(v) => v.capacity(),
134 Self::U64(v) => v.capacity(),
135 Self::F32(v) => v.capacity(),
136 Self::F64(v) => v.capacity(),
137 };
138 cap * self.data_type().element_size()
139 }
140
141 pub fn resize(&mut self, new_len: usize) {
143 match self {
144 Self::I8(v) => v.resize(new_len, 0),
145 Self::U8(v) => v.resize(new_len, 0),
146 Self::I16(v) => v.resize(new_len, 0),
147 Self::U16(v) => v.resize(new_len, 0),
148 Self::I32(v) => v.resize(new_len, 0),
149 Self::U32(v) => v.resize(new_len, 0),
150 Self::I64(v) => v.resize(new_len, 0),
151 Self::U64(v) => v.resize(new_len, 0),
152 Self::F32(v) => v.resize(new_len, 0.0),
153 Self::F64(v) => v.resize(new_len, 0.0),
154 }
155 }
156
157 pub fn as_u8_slice(&self) -> &[u8] {
159 match self {
160 Self::I8(v) => unsafe { std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len()) },
161 Self::U8(v) => v.as_slice(),
162 Self::I16(v) => unsafe {
163 std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 2)
164 },
165 Self::U16(v) => unsafe {
166 std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 2)
167 },
168 Self::I32(v) => unsafe {
169 std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 4)
170 },
171 Self::U32(v) => unsafe {
172 std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 4)
173 },
174 Self::I64(v) => unsafe {
175 std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 8)
176 },
177 Self::U64(v) => unsafe {
178 std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 8)
179 },
180 Self::F32(v) => unsafe {
181 std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 4)
182 },
183 Self::F64(v) => unsafe {
184 std::slice::from_raw_parts(v.as_ptr() as *const u8, v.len() * 8)
185 },
186 }
187 }
188
189 pub fn get_as_f64(&self, index: usize) -> Option<f64> {
191 match self {
192 Self::I8(v) => v.get(index).map(|&x| x as f64),
193 Self::U8(v) => v.get(index).map(|&x| x as f64),
194 Self::I16(v) => v.get(index).map(|&x| x as f64),
195 Self::U16(v) => v.get(index).map(|&x| x as f64),
196 Self::I32(v) => v.get(index).map(|&x| x as f64),
197 Self::U32(v) => v.get(index).map(|&x| x as f64),
198 Self::I64(v) => v.get(index).map(|&x| x as f64),
199 Self::U64(v) => v.get(index).map(|&x| x as f64),
200 Self::F32(v) => v.get(index).map(|&x| x as f64),
201 Self::F64(v) => v.get(index).copied(),
202 }
203 }
204
205 pub fn set_from_f64(&mut self, index: usize, value: f64) {
207 match self {
208 Self::I8(v) => {
209 if let Some(e) = v.get_mut(index) {
210 *e = value as i8;
211 }
212 }
213 Self::U8(v) => {
214 if let Some(e) = v.get_mut(index) {
215 *e = value as u8;
216 }
217 }
218 Self::I16(v) => {
219 if let Some(e) = v.get_mut(index) {
220 *e = value as i16;
221 }
222 }
223 Self::U16(v) => {
224 if let Some(e) = v.get_mut(index) {
225 *e = value as u16;
226 }
227 }
228 Self::I32(v) => {
229 if let Some(e) = v.get_mut(index) {
230 *e = value as i32;
231 }
232 }
233 Self::U32(v) => {
234 if let Some(e) = v.get_mut(index) {
235 *e = value as u32;
236 }
237 }
238 Self::I64(v) => {
239 if let Some(e) = v.get_mut(index) {
240 *e = value as i64;
241 }
242 }
243 Self::U64(v) => {
244 if let Some(e) = v.get_mut(index) {
245 *e = value as u64;
246 }
247 }
248 Self::F32(v) => {
249 if let Some(e) = v.get_mut(index) {
250 *e = value as f32;
251 }
252 }
253 Self::F64(v) => {
254 if let Some(e) = v.get_mut(index) {
255 *e = value;
256 }
257 }
258 }
259 }
260}
261
262#[derive(Debug, Clone)]
264pub struct NDDimension {
265 pub size: usize,
266 pub offset: usize,
267 pub binning: usize,
268 pub reverse: bool,
269}
270
271impl NDDimension {
272 pub fn new(size: usize) -> Self {
273 Self {
274 size,
275 offset: 0,
276 binning: 1,
277 reverse: false,
278 }
279 }
280}
281
282#[derive(Debug, Clone)]
284pub struct NDArrayInfo {
285 pub total_bytes: usize,
286 pub bytes_per_element: usize,
287 pub num_elements: usize,
288 pub x_size: usize,
289 pub y_size: usize,
290 pub color_size: usize,
291 pub x_dim: usize,
293 pub y_dim: usize,
295 pub color_dim: usize,
297 pub x_stride: usize,
299 pub y_stride: usize,
301 pub color_stride: usize,
303 pub color_mode: crate::color::NDColorMode,
305}
306
307#[derive(Debug, Clone)]
309pub struct NDArray {
310 pub unique_id: i32,
311 pub timestamp: EpicsTimestamp,
312 pub time_stamp: f64,
314 pub dims: Vec<NDDimension>,
315 pub data: NDDataBuffer,
316 pub attributes: NDAttributeList,
317 pub codec: Option<Codec>,
318 pub pool_id: u64,
322 pub data_size: usize,
326}
327
328impl NDArray {
329 pub fn new(dims: Vec<NDDimension>, data_type: NDDataType) -> Self {
331 let num_elements: usize = if dims.is_empty() {
332 0
333 } else {
334 dims.iter().map(|d| d.size).product()
335 };
336 Self {
337 unique_id: 0,
338 timestamp: EpicsTimestamp::default(),
339 time_stamp: 0.0,
340 dims,
341 data: NDDataBuffer::zeros(data_type, num_elements),
342 attributes: NDAttributeList::new(),
343 codec: None,
344 pool_id: 0,
345 data_size: num_elements * data_type.element_size(),
346 }
347 }
348
349 pub fn with_data(dims: Vec<NDDimension>, data: NDDataBuffer) -> Self {
355 let data_size = data.len() * data.data_type().element_size();
356 Self {
357 unique_id: 0,
358 timestamp: EpicsTimestamp::default(),
359 time_stamp: 0.0,
360 dims,
361 data,
362 attributes: NDAttributeList::new(),
363 codec: None,
364 pool_id: 0,
365 data_size,
366 }
367 }
368
369 pub fn info(&self) -> NDArrayInfo {
374 use crate::color::NDColorMode;
375
376 let bytes_per_element = self.data.data_type().element_size();
377 let num_elements = self.data.len();
378 let total_bytes = num_elements * bytes_per_element;
379
380 let ndims = self.dims.len();
381
382 let color_mode = self
384 .attributes
385 .get("ColorMode")
386 .and_then(|a| a.value.as_i64())
387 .map(|v| NDColorMode::from_i32(v as i32))
388 .unwrap_or(NDColorMode::Mono);
389
390 let (x_size, y_size, color_size, x_dim, y_dim, color_dim, x_stride, y_stride, color_stride) =
391 match ndims {
392 0 => (0, 0, 0, 0, 0, 0, 0, 0, 0),
396 1 => (self.dims[0].size, 0, 0, 0, 0, 0, 1, 0, 0),
397 2 => {
398 let xs = self.dims[0].size;
399 let ys = self.dims[1].size;
400 (xs, ys, 0, 0, 1, 0, 1, xs, 0)
402 }
403 3 => {
404 match color_mode {
406 NDColorMode::RGB1 => {
407 let cs = self.dims[0].size;
409 let xs = self.dims[1].size;
410 let ys = self.dims[2].size;
411 (xs, ys, cs, 1, 2, 0, cs, xs * cs, 1)
412 }
413 NDColorMode::RGB2 => {
414 let xs = self.dims[0].size;
416 let cs = self.dims[1].size;
417 let ys = self.dims[2].size;
418 (xs, ys, cs, 0, 2, 1, 1, xs * cs, xs)
419 }
420 NDColorMode::RGB3 => {
421 let xs = self.dims[0].size;
423 let ys = self.dims[1].size;
424 let cs = self.dims[2].size;
425 (xs, ys, cs, 0, 1, 2, 1, xs, xs * ys)
426 }
427 _ => {
428 let xs = self.dims[0].size;
437 let ys = self.dims[1].size;
438 let cs = self.dims[2].size;
439 (xs, ys, cs, 0, 1, 2, 1, xs, xs * ys)
440 }
441 }
442 }
443 _ => {
448 let xs = self.dims[0].size;
449 let ys = self.dims[1].size;
450 (xs, ys, 0, 0, 1, 0, 1, xs, 0)
451 }
452 };
453
454 NDArrayInfo {
455 total_bytes,
456 bytes_per_element,
457 num_elements,
458 x_size,
459 y_size,
460 color_size,
461 x_dim,
462 y_dim,
463 color_dim,
464 x_stride,
465 y_stride,
466 color_stride,
467 color_mode,
468 }
469 }
470
471 pub fn report(&self, details: i32) -> String {
475 let mut out = String::new();
476 out.push('\n');
477 out.push_str("NDArray:\n");
478 let dim_sizes: Vec<String> = self.dims.iter().map(|d| d.size.to_string()).collect();
479 out.push_str(&format!(
480 " ndims={} dims=[{}]\n",
481 self.dims.len(),
482 dim_sizes.join(" ")
483 ));
484 out.push_str(&format!(
485 " dataType={:?}, dataSize={}, numElements={}\n",
486 self.data.data_type(),
487 self.data_size,
488 self.data.len()
489 ));
490 out.push_str(&format!(
491 " uniqueId={}, timeStamp={}, epicsTS.secPastEpoch={}, epicsTS.nsec={}\n",
492 self.unique_id, self.time_stamp, self.timestamp.sec, self.timestamp.nsec
493 ));
494 out.push_str(&format!(" poolId={}\n", self.pool_id));
495 match &self.codec {
496 Some(c) => out.push_str(&format!(
497 " codec={:?}, compressedSize={}\n",
498 c.name, c.compressed_size
499 )),
500 None => out.push_str(" codec=none\n"),
501 }
502 out.push_str(&format!(
503 " number of attributes={}\n",
504 self.attributes.len()
505 ));
506 if details > 5 {
507 for attr in self.attributes.iter() {
508 out.push_str(&format!(
509 " attribute name={}, value={}, source={:?}\n",
510 attr.name,
511 attr.value.as_string(),
512 attr.source
513 ));
514 }
515 }
516 out
517 }
518
519 pub fn validate(&self) -> ADResult<()> {
521 let expected: usize = if self.dims.is_empty() {
522 0
523 } else {
524 self.dims.iter().map(|d| d.size).product()
525 };
526 if self.data.len() != expected {
527 return Err(ADError::BufferSizeMismatch {
528 expected,
529 actual: self.data.len(),
530 });
531 }
532 Ok(())
533 }
534}
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539
540 #[test]
541 fn test_element_size_all_types() {
542 assert_eq!(NDDataType::Int8.element_size(), 1);
543 assert_eq!(NDDataType::UInt8.element_size(), 1);
544 assert_eq!(NDDataType::Int16.element_size(), 2);
545 assert_eq!(NDDataType::UInt16.element_size(), 2);
546 assert_eq!(NDDataType::Int32.element_size(), 4);
547 assert_eq!(NDDataType::UInt32.element_size(), 4);
548 assert_eq!(NDDataType::Int64.element_size(), 8);
549 assert_eq!(NDDataType::UInt64.element_size(), 8);
550 assert_eq!(NDDataType::Float32.element_size(), 4);
551 assert_eq!(NDDataType::Float64.element_size(), 8);
552 }
553
554 #[test]
555 fn test_from_ordinal_roundtrip() {
556 for i in 0..10u8 {
557 let dt = NDDataType::from_ordinal(i).unwrap();
558 assert_eq!(dt as u8, i);
559 }
560 assert!(NDDataType::from_ordinal(10).is_none());
561 }
562
563 #[test]
564 fn test_buffer_zeros_type_and_len() {
565 let buf = NDDataBuffer::zeros(NDDataType::UInt16, 100);
566 assert_eq!(buf.data_type(), NDDataType::UInt16);
567 assert_eq!(buf.len(), 100);
568 assert_eq!(buf.total_bytes(), 200);
569 }
570
571 #[test]
572 fn test_buffer_zeros_all_types() {
573 for i in 0..10u8 {
574 let dt = NDDataType::from_ordinal(i).unwrap();
575 let buf = NDDataBuffer::zeros(dt, 10);
576 assert_eq!(buf.data_type(), dt);
577 assert_eq!(buf.len(), 10);
578 assert_eq!(buf.total_bytes(), 10 * dt.element_size());
579 }
580 }
581
582 #[test]
583 fn test_buffer_as_u8_slice() {
584 let buf = NDDataBuffer::U8(vec![1, 2, 3]);
585 assert_eq!(buf.as_u8_slice(), &[1, 2, 3]);
586 }
587
588 #[test]
589 fn test_ndarray_new_allocates() {
590 let dims = vec![NDDimension::new(256), NDDimension::new(256)];
591 let arr = NDArray::new(dims, NDDataType::UInt8);
592 assert_eq!(arr.data.len(), 256 * 256);
593 assert_eq!(arr.data.data_type(), NDDataType::UInt8);
594 }
595
596 #[test]
597 fn test_ndarray_validate_ok() {
598 let dims = vec![NDDimension::new(10), NDDimension::new(20)];
599 let arr = NDArray::new(dims, NDDataType::Float64);
600 arr.validate().unwrap();
601 }
602
603 #[test]
604 fn test_ndarray_validate_mismatch() {
605 let mut arr = NDArray::new(
606 vec![NDDimension::new(10), NDDimension::new(20)],
607 NDDataType::UInt8,
608 );
609 arr.data = NDDataBuffer::U8(vec![0; 100]);
610 assert!(arr.validate().is_err());
611 }
612
613 #[test]
614 fn test_ndarray_info_2d_mono() {
615 let dims = vec![NDDimension::new(640), NDDimension::new(480)];
616 let arr = NDArray::new(dims, NDDataType::UInt16);
617 let info = arr.info();
618 assert_eq!(info.x_size, 640);
619 assert_eq!(info.y_size, 480);
620 assert_eq!(info.color_size, 0);
622 assert_eq!(info.num_elements, 640 * 480);
623 assert_eq!(info.bytes_per_element, 2);
624 assert_eq!(info.total_bytes, 640 * 480 * 2);
625 }
626
627 #[test]
628 fn test_ndarray_info_3d_rgb() {
629 use crate::attributes::{NDAttrSource, NDAttrValue, NDAttribute};
630 use crate::color::NDColorMode;
631
632 let dims = vec![
634 NDDimension::new(3),
635 NDDimension::new(640),
636 NDDimension::new(480),
637 ];
638 let arr = NDArray::new(dims, NDDataType::UInt8);
639 let info = arr.info();
640 assert_eq!(info.x_size, 3);
641 assert_eq!(info.y_size, 640);
642 assert_eq!(info.color_size, 480);
643
644 let dims = vec![
646 NDDimension::new(3),
647 NDDimension::new(640),
648 NDDimension::new(480),
649 ];
650 let mut arr = NDArray::new(dims, NDDataType::UInt8);
651 arr.attributes.add(NDAttribute {
652 name: "ColorMode".into(),
653 description: "Color Mode".into(),
654 source: NDAttrSource::Driver,
655 value: NDAttrValue::Int32(NDColorMode::RGB1 as i32),
656 source_impl: None,
657 });
658 let info = arr.info();
659 assert_eq!(info.color_size, 3);
660 assert_eq!(info.x_size, 640);
661 assert_eq!(info.y_size, 480);
662 assert_eq!(info.x_dim, 1);
663 assert_eq!(info.y_dim, 2);
664 assert_eq!(info.color_dim, 0);
665 assert_eq!(info.num_elements, 3 * 640 * 480);
666 }
667
668 #[test]
669 fn test_ndarray_info_1d() {
670 let dims = vec![NDDimension::new(1024)];
671 let arr = NDArray::new(dims, NDDataType::Float64);
672 let info = arr.info();
673 assert_eq!(info.x_size, 1024);
674 assert_eq!(info.y_size, 0);
676 assert_eq!(info.color_size, 0);
677 }
678
679 #[test]
680 fn test_ndarray_info_4d_not_color() {
681 let dims = vec![
685 NDDimension::new(8),
686 NDDimension::new(640),
687 NDDimension::new(480),
688 NDDimension::new(5),
689 ];
690 let arr = NDArray::new(dims, NDDataType::UInt8);
691 let info = arr.info();
692 assert_eq!(info.x_size, 8);
693 assert_eq!(info.y_size, 640);
694 assert_eq!(info.color_size, 0, "4-D array must not get a color size");
695 assert_eq!(info.x_dim, 0);
696 assert_eq!(info.y_dim, 1);
697 assert_eq!(info.color_dim, 0);
698 assert_eq!(info.x_stride, 1);
699 assert_eq!(info.y_stride, 8);
700 assert_eq!(info.color_stride, 0);
701 assert_eq!(info.num_elements, 8 * 640 * 480 * 5);
702 }
703
704 #[test]
705 fn test_buffer_is_empty() {
706 let buf = NDDataBuffer::zeros(NDDataType::UInt8, 0);
707 assert!(buf.is_empty());
708 let buf2 = NDDataBuffer::zeros(NDDataType::UInt8, 1);
709 assert!(!buf2.is_empty());
710 }
711
712 #[test]
713 fn test_codec_field_preserved() {
714 let mut arr = NDArray::new(vec![NDDimension::new(10)], NDDataType::UInt8);
715 arr.codec = Some(Codec {
716 name: crate::codec::CodecName::JPEG,
717 compressed_size: 42,
718 level: 0,
719 shuffle: 0,
720 compressor: 0,
721 original_data_type: NDDataType::UInt8,
722 });
723 let cloned = arr.clone();
724 assert_eq!(cloned.codec.as_ref().unwrap().compressed_size, 42);
725 }
726}