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
307impl NDArrayInfo {
308 pub fn user_dims(&self) -> [usize; 3] {
319 [self.x_dim, self.y_dim, self.color_dim]
320 }
321}
322
323#[derive(Debug, Clone)]
325pub struct NDArray {
326 pub unique_id: i32,
327 pub timestamp: EpicsTimestamp,
328 pub time_stamp: f64,
330 pub dims: Vec<NDDimension>,
331 pub data: NDDataBuffer,
332 pub attributes: NDAttributeList,
333 pub codec: Option<Codec>,
334 pub pool_id: u64,
338 pub data_size: usize,
342}
343
344impl NDArray {
345 pub fn new(dims: Vec<NDDimension>, data_type: NDDataType) -> Self {
347 let num_elements: usize = if dims.is_empty() {
348 0
349 } else {
350 dims.iter().map(|d| d.size).product()
351 };
352 Self {
353 unique_id: 0,
354 timestamp: EpicsTimestamp::default(),
355 time_stamp: 0.0,
356 dims,
357 data: NDDataBuffer::zeros(data_type, num_elements),
358 attributes: NDAttributeList::new(),
359 codec: None,
360 pool_id: 0,
361 data_size: num_elements * data_type.element_size(),
362 }
363 }
364
365 pub fn with_data(dims: Vec<NDDimension>, data: NDDataBuffer) -> Self {
371 let data_size = data.len() * data.data_type().element_size();
372 Self {
373 unique_id: 0,
374 timestamp: EpicsTimestamp::default(),
375 time_stamp: 0.0,
376 dims,
377 data,
378 attributes: NDAttributeList::new(),
379 codec: None,
380 pool_id: 0,
381 data_size,
382 }
383 }
384
385 pub fn update_time_stamps(&mut self, epics_ts: EpicsTimestamp) {
399 self.timestamp = epics_ts;
400 self.time_stamp = epics_ts.as_f64();
401 }
402
403 pub fn update_time_stamps_now(&mut self) {
409 self.update_time_stamps(EpicsTimestamp::now());
410 }
411
412 pub fn info(&self) -> NDArrayInfo {
417 use crate::color::NDColorMode;
418
419 let bytes_per_element = self.data.data_type().element_size();
420 let num_elements = self.data.len();
421 let total_bytes = num_elements * bytes_per_element;
422
423 let ndims = self.dims.len();
424
425 let color_mode = self
427 .attributes
428 .get("ColorMode")
429 .and_then(|a| a.value.as_i64())
430 .map(|v| NDColorMode::from_i32(v as i32))
431 .unwrap_or(NDColorMode::Mono);
432
433 let (x_size, y_size, color_size, x_dim, y_dim, color_dim, x_stride, y_stride, color_stride) =
434 match ndims {
435 0 => (0, 0, 0, 0, 0, 0, 0, 0, 0),
439 1 => (self.dims[0].size, 0, 0, 0, 0, 0, 1, 0, 0),
440 2 => {
441 let xs = self.dims[0].size;
442 let ys = self.dims[1].size;
443 (xs, ys, 0, 0, 1, 0, 1, xs, 0)
445 }
446 3 => {
447 match color_mode {
449 NDColorMode::RGB1 => {
450 let cs = self.dims[0].size;
452 let xs = self.dims[1].size;
453 let ys = self.dims[2].size;
454 (xs, ys, cs, 1, 2, 0, cs, xs * cs, 1)
455 }
456 NDColorMode::RGB2 => {
457 let xs = self.dims[0].size;
459 let cs = self.dims[1].size;
460 let ys = self.dims[2].size;
461 (xs, ys, cs, 0, 2, 1, 1, xs * cs, xs)
462 }
463 NDColorMode::RGB3 => {
464 let xs = self.dims[0].size;
466 let ys = self.dims[1].size;
467 let cs = self.dims[2].size;
468 (xs, ys, cs, 0, 1, 2, 1, xs, xs * ys)
469 }
470 _ => {
471 let xs = self.dims[0].size;
480 let ys = self.dims[1].size;
481 let cs = self.dims[2].size;
482 (xs, ys, cs, 0, 1, 2, 1, xs, xs * ys)
483 }
484 }
485 }
486 _ => {
491 let xs = self.dims[0].size;
492 let ys = self.dims[1].size;
493 (xs, ys, 0, 0, 1, 0, 1, xs, 0)
494 }
495 };
496
497 NDArrayInfo {
498 total_bytes,
499 bytes_per_element,
500 num_elements,
501 x_size,
502 y_size,
503 color_size,
504 x_dim,
505 y_dim,
506 color_dim,
507 x_stride,
508 y_stride,
509 color_stride,
510 color_mode,
511 }
512 }
513
514 pub fn report(&self, details: i32) -> String {
518 let mut out = String::new();
519 out.push('\n');
520 out.push_str("NDArray:\n");
521 let dim_sizes: Vec<String> = self.dims.iter().map(|d| d.size.to_string()).collect();
522 out.push_str(&format!(
523 " ndims={} dims=[{}]\n",
524 self.dims.len(),
525 dim_sizes.join(" ")
526 ));
527 out.push_str(&format!(
528 " dataType={:?}, dataSize={}, numElements={}\n",
529 self.data.data_type(),
530 self.data_size,
531 self.data.len()
532 ));
533 out.push_str(&format!(
534 " uniqueId={}, timeStamp={}, epicsTS.secPastEpoch={}, epicsTS.nsec={}\n",
535 self.unique_id, self.time_stamp, self.timestamp.sec, self.timestamp.nsec
536 ));
537 out.push_str(&format!(" poolId={}\n", self.pool_id));
538 match &self.codec {
539 Some(c) => out.push_str(&format!(
540 " codec={:?}, compressedSize={}\n",
541 c.name, c.compressed_size
542 )),
543 None => out.push_str(" codec=none\n"),
544 }
545 out.push_str(&format!(
546 " number of attributes={}\n",
547 self.attributes.len()
548 ));
549 if details > 5 {
550 for attr in self.attributes.iter() {
551 out.push_str(&format!(
552 " attribute name={}, value={}, source={:?}\n",
553 attr.name,
554 attr.value.as_string(),
555 attr.source
556 ));
557 }
558 }
559 out
560 }
561
562 pub fn validate(&self) -> ADResult<()> {
564 let expected: usize = if self.dims.is_empty() {
565 0
566 } else {
567 self.dims.iter().map(|d| d.size).product()
568 };
569 if self.data.len() != expected {
570 return Err(ADError::BufferSizeMismatch {
571 expected,
572 actual: self.data.len(),
573 });
574 }
575 Ok(())
576 }
577}
578
579#[cfg(test)]
580mod tests {
581 use super::*;
582
583 #[test]
584 fn test_element_size_all_types() {
585 assert_eq!(NDDataType::Int8.element_size(), 1);
586 assert_eq!(NDDataType::UInt8.element_size(), 1);
587 assert_eq!(NDDataType::Int16.element_size(), 2);
588 assert_eq!(NDDataType::UInt16.element_size(), 2);
589 assert_eq!(NDDataType::Int32.element_size(), 4);
590 assert_eq!(NDDataType::UInt32.element_size(), 4);
591 assert_eq!(NDDataType::Int64.element_size(), 8);
592 assert_eq!(NDDataType::UInt64.element_size(), 8);
593 assert_eq!(NDDataType::Float32.element_size(), 4);
594 assert_eq!(NDDataType::Float64.element_size(), 8);
595 }
596
597 #[test]
598 fn test_from_ordinal_roundtrip() {
599 for i in 0..10u8 {
600 let dt = NDDataType::from_ordinal(i).unwrap();
601 assert_eq!(dt as u8, i);
602 }
603 assert!(NDDataType::from_ordinal(10).is_none());
604 }
605
606 #[test]
607 fn test_buffer_zeros_type_and_len() {
608 let buf = NDDataBuffer::zeros(NDDataType::UInt16, 100);
609 assert_eq!(buf.data_type(), NDDataType::UInt16);
610 assert_eq!(buf.len(), 100);
611 assert_eq!(buf.total_bytes(), 200);
612 }
613
614 #[test]
615 fn test_buffer_zeros_all_types() {
616 for i in 0..10u8 {
617 let dt = NDDataType::from_ordinal(i).unwrap();
618 let buf = NDDataBuffer::zeros(dt, 10);
619 assert_eq!(buf.data_type(), dt);
620 assert_eq!(buf.len(), 10);
621 assert_eq!(buf.total_bytes(), 10 * dt.element_size());
622 }
623 }
624
625 #[test]
626 fn test_buffer_as_u8_slice() {
627 let buf = NDDataBuffer::U8(vec![1, 2, 3]);
628 assert_eq!(buf.as_u8_slice(), &[1, 2, 3]);
629 }
630
631 #[test]
632 fn test_ndarray_new_allocates() {
633 let dims = vec![NDDimension::new(256), NDDimension::new(256)];
634 let arr = NDArray::new(dims, NDDataType::UInt8);
635 assert_eq!(arr.data.len(), 256 * 256);
636 assert_eq!(arr.data.data_type(), NDDataType::UInt8);
637 }
638
639 #[test]
640 fn test_ndarray_validate_ok() {
641 let dims = vec![NDDimension::new(10), NDDimension::new(20)];
642 let arr = NDArray::new(dims, NDDataType::Float64);
643 arr.validate().unwrap();
644 }
645
646 #[test]
647 fn test_ndarray_validate_mismatch() {
648 let mut arr = NDArray::new(
649 vec![NDDimension::new(10), NDDimension::new(20)],
650 NDDataType::UInt8,
651 );
652 arr.data = NDDataBuffer::U8(vec![0; 100]);
653 assert!(arr.validate().is_err());
654 }
655
656 #[test]
657 fn test_ndarray_info_2d_mono() {
658 let dims = vec![NDDimension::new(640), NDDimension::new(480)];
659 let arr = NDArray::new(dims, NDDataType::UInt16);
660 let info = arr.info();
661 assert_eq!(info.x_size, 640);
662 assert_eq!(info.y_size, 480);
663 assert_eq!(info.color_size, 0);
665 assert_eq!(info.num_elements, 640 * 480);
666 assert_eq!(info.bytes_per_element, 2);
667 assert_eq!(info.total_bytes, 640 * 480 * 2);
668 }
669
670 #[test]
671 fn test_ndarray_info_3d_rgb() {
672 use crate::attributes::{NDAttrSource, NDAttrValue, NDAttribute};
673 use crate::color::NDColorMode;
674
675 let dims = vec![
677 NDDimension::new(3),
678 NDDimension::new(640),
679 NDDimension::new(480),
680 ];
681 let arr = NDArray::new(dims, NDDataType::UInt8);
682 let info = arr.info();
683 assert_eq!(info.x_size, 3);
684 assert_eq!(info.y_size, 640);
685 assert_eq!(info.color_size, 480);
686
687 let dims = vec![
689 NDDimension::new(3),
690 NDDimension::new(640),
691 NDDimension::new(480),
692 ];
693 let mut arr = NDArray::new(dims, NDDataType::UInt8);
694 arr.attributes.add(NDAttribute {
695 name: "ColorMode".into(),
696 description: "Color Mode".into(),
697 source: NDAttrSource::Driver,
698 value: NDAttrValue::Int32(NDColorMode::RGB1 as i32),
699 source_impl: None,
700 });
701 let info = arr.info();
702 assert_eq!(info.color_size, 3);
703 assert_eq!(info.x_size, 640);
704 assert_eq!(info.y_size, 480);
705 assert_eq!(info.x_dim, 1);
706 assert_eq!(info.y_dim, 2);
707 assert_eq!(info.color_dim, 0);
708 assert_eq!(info.num_elements, 3 * 640 * 480);
709 }
710
711 #[test]
712 fn test_ndarray_info_1d() {
713 let dims = vec![NDDimension::new(1024)];
714 let arr = NDArray::new(dims, NDDataType::Float64);
715 let info = arr.info();
716 assert_eq!(info.x_size, 1024);
717 assert_eq!(info.y_size, 0);
719 assert_eq!(info.color_size, 0);
720 }
721
722 #[test]
723 fn test_ndarray_info_4d_not_color() {
724 let dims = vec![
728 NDDimension::new(8),
729 NDDimension::new(640),
730 NDDimension::new(480),
731 NDDimension::new(5),
732 ];
733 let arr = NDArray::new(dims, NDDataType::UInt8);
734 let info = arr.info();
735 assert_eq!(info.x_size, 8);
736 assert_eq!(info.y_size, 640);
737 assert_eq!(info.color_size, 0, "4-D array must not get a color size");
738 assert_eq!(info.x_dim, 0);
739 assert_eq!(info.y_dim, 1);
740 assert_eq!(info.color_dim, 0);
741 assert_eq!(info.x_stride, 1);
742 assert_eq!(info.y_stride, 8);
743 assert_eq!(info.color_stride, 0);
744 assert_eq!(info.num_elements, 8 * 640 * 480 * 5);
745 }
746
747 #[test]
748 fn test_buffer_is_empty() {
749 let buf = NDDataBuffer::zeros(NDDataType::UInt8, 0);
750 assert!(buf.is_empty());
751 let buf2 = NDDataBuffer::zeros(NDDataType::UInt8, 1);
752 assert!(!buf2.is_empty());
753 }
754
755 #[test]
756 fn test_codec_field_preserved() {
757 let mut arr = NDArray::new(vec![NDDimension::new(10)], NDDataType::UInt8);
758 arr.codec = Some(Codec {
759 name: crate::codec::CodecName::JPEG,
760 compressed_size: 42,
761 level: 0,
762 shuffle: 0,
763 compressor: 0,
764 original_data_type: NDDataType::UInt8,
765 });
766 let cloned = arr.clone();
767 assert_eq!(cloned.codec.as_ref().unwrap().compressed_size, 42);
768 }
769}