1use std::io::{Read, Write};
2use std::sync::Arc;
3
4use ad_core_rs::codec::{Codec, CodecName};
5use ad_core_rs::ndarray::{NDArray, NDDataBuffer, NDDataType, NDDimension};
6use ad_core_rs::ndarray_pool::NDArrayPool;
7use ad_core_rs::plugin::runtime::{NDPluginProcess, ParamUpdate, ProcessResult};
8
9use flate2::Compression;
10use flate2::read::ZlibDecoder;
11use flate2::write::ZlibEncoder;
12use lz4_flex::block::{compress, decompress};
13use rust_hdf5::format::messages::filter::{
14 FILTER_BLOSC, Filter, FilterPipeline, apply_filters, reverse_filters,
15};
16
17pub fn original_data_type(array: &NDArray) -> NDDataType {
29 match &array.codec {
30 Some(c) => c.original_data_type,
31 None => array.data.data_type(),
32 }
33}
34
35fn buffer_from_bytes(bytes: &[u8], data_type: NDDataType) -> Option<NDDataBuffer> {
40 let elem_size = data_type.element_size();
41 if bytes.len() % elem_size != 0 {
42 return None;
43 }
44 let count = bytes.len() / elem_size;
45
46 Some(match data_type {
47 NDDataType::Int8 => {
48 let mut v = vec![0i8; count];
49 unsafe {
51 std::ptr::copy_nonoverlapping(
52 bytes.as_ptr(),
53 v.as_mut_ptr() as *mut u8,
54 bytes.len(),
55 );
56 }
57 NDDataBuffer::I8(v)
58 }
59 NDDataType::UInt8 => NDDataBuffer::U8(bytes.to_vec()),
60 NDDataType::Int16 => {
61 let mut v = vec![0i16; count];
62 unsafe {
63 std::ptr::copy_nonoverlapping(
64 bytes.as_ptr(),
65 v.as_mut_ptr() as *mut u8,
66 bytes.len(),
67 );
68 }
69 NDDataBuffer::I16(v)
70 }
71 NDDataType::UInt16 => {
72 let mut v = vec![0u16; count];
73 unsafe {
74 std::ptr::copy_nonoverlapping(
75 bytes.as_ptr(),
76 v.as_mut_ptr() as *mut u8,
77 bytes.len(),
78 );
79 }
80 NDDataBuffer::U16(v)
81 }
82 NDDataType::Int32 => {
83 let mut v = vec![0i32; count];
84 unsafe {
85 std::ptr::copy_nonoverlapping(
86 bytes.as_ptr(),
87 v.as_mut_ptr() as *mut u8,
88 bytes.len(),
89 );
90 }
91 NDDataBuffer::I32(v)
92 }
93 NDDataType::UInt32 => {
94 let mut v = vec![0u32; count];
95 unsafe {
96 std::ptr::copy_nonoverlapping(
97 bytes.as_ptr(),
98 v.as_mut_ptr() as *mut u8,
99 bytes.len(),
100 );
101 }
102 NDDataBuffer::U32(v)
103 }
104 NDDataType::Int64 => {
105 let mut v = vec![0i64; count];
106 unsafe {
107 std::ptr::copy_nonoverlapping(
108 bytes.as_ptr(),
109 v.as_mut_ptr() as *mut u8,
110 bytes.len(),
111 );
112 }
113 NDDataBuffer::I64(v)
114 }
115 NDDataType::UInt64 => {
116 let mut v = vec![0u64; count];
117 unsafe {
118 std::ptr::copy_nonoverlapping(
119 bytes.as_ptr(),
120 v.as_mut_ptr() as *mut u8,
121 bytes.len(),
122 );
123 }
124 NDDataBuffer::U64(v)
125 }
126 NDDataType::Float32 => {
127 let mut v = vec![0f32; count];
128 unsafe {
129 std::ptr::copy_nonoverlapping(
130 bytes.as_ptr(),
131 v.as_mut_ptr() as *mut u8,
132 bytes.len(),
133 );
134 }
135 NDDataBuffer::F32(v)
136 }
137 NDDataType::Float64 => {
138 let mut v = vec![0f64; count];
139 unsafe {
140 std::ptr::copy_nonoverlapping(
141 bytes.as_ptr(),
142 v.as_mut_ptr() as *mut u8,
143 bytes.len(),
144 );
145 }
146 NDDataBuffer::F64(v)
147 }
148 })
149}
150
151pub fn compress_lz4(src: &NDArray) -> NDArray {
157 let raw = src.data.as_u8_slice();
158 let original_data_type = src.data.data_type();
159 let original_size = raw.len();
160 let compressed = compress(raw);
162 let compressed_size = compressed.len();
163
164 let mut arr = src.clone();
165 arr.data = NDDataBuffer::U8(compressed);
166 arr.codec = Some(Codec {
167 name: CodecName::LZ4,
168 compressed_size,
169 level: 0,
170 shuffle: 0,
171 compressor: 0,
172 original_data_type,
175 });
176
177 tracing::debug!(
178 original_size,
179 compressed_size,
180 ratio = original_size as f64 / compressed_size.max(1) as f64,
181 "LZ4 compress"
182 );
183
184 arr
185}
186
187pub fn decompress_lz4(src: &NDArray) -> Option<NDArray> {
192 if src.codec.as_ref().map(|c| c.name) != Some(CodecName::LZ4) {
193 return None;
194 }
195 let compressed = src.data.as_u8_slice();
196 let original_type = original_data_type(src);
199 let num_elements: usize = src.dims.iter().map(|d| d.size).product();
200 let uncompressed_size = num_elements * original_type.element_size();
201 let decompressed = decompress(compressed, uncompressed_size).ok()?;
202
203 let buffer = buffer_from_bytes(&decompressed, original_type)?;
204
205 let mut arr = src.clone();
206 arr.data = buffer;
207 arr.codec = None;
208
209 Some(arr)
210}
211
212const ZLIB_DEFAULT_LEVEL: u32 = 6;
223
224pub fn compress_zlib(src: &NDArray) -> NDArray {
230 let raw = src.data.as_u8_slice();
231 let original_data_type = src.data.data_type();
232 let original_size = raw.len();
233
234 let mut encoder = ZlibEncoder::new(Vec::<u8>::new(), Compression::new(ZLIB_DEFAULT_LEVEL));
235 if encoder.write_all(raw).is_err() {
237 return src.clone();
238 }
239 let compressed = match encoder.finish() {
240 Ok(buf) => buf,
241 Err(_) => return src.clone(),
242 };
243 let compressed_size = compressed.len();
244
245 let mut arr = src.clone();
246 arr.data = NDDataBuffer::U8(compressed);
247 arr.codec = Some(Codec {
248 name: CodecName::Zlib,
249 compressed_size,
250 level: ZLIB_DEFAULT_LEVEL as i32,
251 shuffle: 0,
252 compressor: 0,
253 original_data_type,
254 });
255
256 tracing::debug!(
257 original_size,
258 compressed_size,
259 ratio = original_size as f64 / compressed_size.max(1) as f64,
260 "Zlib compress"
261 );
262 arr
263}
264
265pub fn decompress_zlib(src: &NDArray) -> Option<NDArray> {
270 if src.codec.as_ref().map(|c| c.name) != Some(CodecName::Zlib) {
271 return None;
272 }
273 let compressed = src.data.as_u8_slice();
274
275 let original_type = original_data_type(src);
276 let num_elements: usize = src.dims.iter().map(|d| d.size).product();
277 let uncompressed_size = num_elements * original_type.element_size();
278
279 let mut decoder = ZlibDecoder::new(compressed);
280 let mut decompressed = Vec::with_capacity(uncompressed_size);
281 decoder.read_to_end(&mut decompressed).ok()?;
282
283 let buffer = buffer_from_bytes(&decompressed, original_type)?;
284
285 let mut arr = src.clone();
286 arr.data = buffer;
287 arr.codec = None;
288 Some(arr)
289}
290
291const LZ4HDF5_DEFAULT_BLOCK_SIZE: usize = 1 << 20;
311
312pub fn compress_lz4hdf5(src: &NDArray) -> NDArray {
319 let raw = src.data.as_u8_slice();
320 let data_type = src.data.data_type();
321 let original_size = raw.len();
322 let block_size = LZ4HDF5_DEFAULT_BLOCK_SIZE;
323
324 let mut out: Vec<u8> = Vec::with_capacity(original_size / 2 + 12);
326 out.extend_from_slice(&(original_size as u64).to_be_bytes());
327 out.extend_from_slice(&(block_size as u32).to_be_bytes());
328
329 let mut pos = 0usize;
330 while pos < raw.len() {
331 let n = block_size.min(raw.len() - pos);
332 let block = &raw[pos..pos + n];
333 let comp = compress(block);
334 if comp.len() < n {
337 out.extend_from_slice(&(comp.len() as u32).to_be_bytes());
338 out.extend_from_slice(&comp);
339 } else {
340 out.extend_from_slice(&(n as u32).to_be_bytes());
341 out.extend_from_slice(block);
342 }
343 pos += n;
344 }
345
346 let compressed_size = out.len();
347 let mut arr = src.clone();
348 arr.data = NDDataBuffer::U8(out);
349 arr.codec = Some(Codec {
350 name: CodecName::LZ4HDF5,
351 compressed_size,
352 level: 0,
353 shuffle: 0,
354 compressor: 0,
355 original_data_type: data_type,
356 });
357
358 tracing::debug!(
359 original_size,
360 compressed_size,
361 ratio = original_size as f64 / compressed_size.max(1) as f64,
362 "LZ4HDF5 compress"
363 );
364 arr
365}
366
367pub fn decompress_lz4hdf5(src: &NDArray) -> Option<NDArray> {
371 if src.codec.as_ref().map(|c| c.name) != Some(CodecName::LZ4HDF5) {
372 return None;
373 }
374 let buf = src.data.as_u8_slice();
375 if buf.len() < 12 {
376 return None;
377 }
378 let total_bytes = u64::from_be_bytes(buf[0..8].try_into().ok()?) as usize;
379 let block_size = u32::from_be_bytes(buf[8..12].try_into().ok()?) as usize;
380 if block_size == 0 {
381 return None;
382 }
383
384 let original_type = original_data_type(src);
385
386 let mut out: Vec<u8> = Vec::with_capacity(total_bytes);
387 let mut pos = 12usize;
388 while out.len() < total_bytes {
389 let n = block_size.min(total_bytes - out.len());
390 if pos + 4 > buf.len() {
391 return None;
392 }
393 let clen = u32::from_be_bytes(buf[pos..pos + 4].try_into().ok()?) as usize;
394 pos += 4;
395 if pos + clen > buf.len() {
396 return None;
397 }
398 let block_payload = &buf[pos..pos + clen];
399 if clen == n {
400 out.extend_from_slice(block_payload);
402 } else {
403 let block = decompress(block_payload, n).ok()?;
404 if block.len() != n {
405 return None;
406 }
407 out.extend_from_slice(&block);
408 }
409 pos += clen;
410 }
411 if out.len() != total_bytes {
412 return None;
413 }
414
415 let buffer = buffer_from_bytes(&out, original_type)?;
416 let mut arr = src.clone();
417 arr.data = buffer;
418 arr.codec = None;
419 Some(arr)
420}
421
422const BSHUF_TARGET_BLOCK_SIZE_B: usize = 8192;
445const BSHUF_BLOCKED_MULT: usize = 8;
447const BSHUF_MIN_RECOMMEND_BLOCK: usize = 128;
449
450pub(crate) fn bshuf_default_block_size(elem_size: usize) -> usize {
457 let bs = BSHUF_TARGET_BLOCK_SIZE_B / elem_size.max(1);
458 let bs = (bs / BSHUF_BLOCKED_MULT) * BSHUF_BLOCKED_MULT;
459 bs.max(BSHUF_MIN_RECOMMEND_BLOCK)
460}
461
462#[inline]
465fn trans_bit_8x8(mut x: u64) -> u64 {
466 let t = (x ^ (x >> 7)) & 0x00AA_00AA_00AA_00AA;
467 x = x ^ t ^ (t << 7);
468 let t = (x ^ (x >> 14)) & 0x0000_CCCC_0000_CCCC;
469 x = x ^ t ^ (t << 14);
470 let t = (x ^ (x >> 28)) & 0x0000_0000_F0F0_F0F0;
471 x = x ^ t ^ (t << 28);
472 x
473}
474
475#[inline]
477fn read_u64_le(b: &[u8], off: usize) -> u64 {
478 u64::from_le_bytes(b[off..off + 8].try_into().unwrap())
479}
480
481fn bshuf_trans_byte_elem(input: &[u8], out: &mut [u8], size: usize, elem_size: usize) {
484 let mut ii = 0;
485 while ii + 7 < size {
486 for jj in 0..elem_size {
487 for kk in 0..8 {
488 out[jj * size + ii + kk] = input[ii * elem_size + kk * elem_size + jj];
489 }
490 }
491 ii += 8;
492 }
493 let mut ii = size - size % 8;
495 while ii < size {
496 for jj in 0..elem_size {
497 out[jj * size + ii] = input[ii * elem_size + jj];
498 }
499 ii += 1;
500 }
501}
502
503fn bshuf_trans_bit_byte(input: &[u8], out: &mut [u8], size: usize, elem_size: usize) {
506 let nbyte = elem_size * size;
507 let nbyte_bitrow = nbyte / 8;
508 for ii in 0..nbyte_bitrow {
509 let mut x = trans_bit_8x8(read_u64_le(input, ii * 8));
510 for kk in 0..8 {
511 out[kk * nbyte_bitrow + ii] = x as u8;
512 x >>= 8;
513 }
514 }
515}
516
517fn bshuf_trans_bitrow_eight(input: &[u8], out: &mut [u8], size: usize, elem_size: usize) {
520 let nbyte_bitrow = size / 8;
521 for ii in 0..8 {
522 for jj in 0..elem_size {
523 let src = (ii * elem_size + jj) * nbyte_bitrow;
524 let dst = (jj * 8 + ii) * nbyte_bitrow;
525 out[dst..dst + nbyte_bitrow].copy_from_slice(&input[src..src + nbyte_bitrow]);
526 }
527 }
528}
529
530fn bshuf_trans_bit_elem(input: &[u8], size: usize, elem_size: usize) -> Vec<u8> {
534 debug_assert_eq!(size % 8, 0);
535 let nbyte = size * elem_size;
536 let mut a = vec![0u8; nbyte];
537 bshuf_trans_byte_elem(input, &mut a, size, elem_size);
538 let mut b = vec![0u8; nbyte];
539 bshuf_trans_bit_byte(&a, &mut b, size, elem_size);
540 let mut out = vec![0u8; nbyte];
541 bshuf_trans_bitrow_eight(&b, &mut out, size, elem_size);
542 out
543}
544
545fn bshuf_trans_byte_bitrow(input: &[u8], out: &mut [u8], size: usize, elem_size: usize) {
548 let nbyte_row = size / 8;
549 for jj in 0..elem_size {
550 for ii in 0..nbyte_row {
551 for kk in 0..8 {
552 out[ii * 8 * elem_size + jj * 8 + kk] = input[(jj * 8 + kk) * nbyte_row + ii];
553 }
554 }
555 }
556}
557
558fn bshuf_shuffle_bit_eightelem(input: &[u8], out: &mut [u8], size: usize, elem_size: usize) {
561 let nbyte = elem_size * size;
562 let mut jj = 0;
563 while jj < 8 * elem_size {
564 let mut ii = 0;
565 while ii + 8 * elem_size - 1 < nbyte {
566 let mut x = trans_bit_8x8(read_u64_le(input, ii + jj));
567 for kk in 0..8 {
568 out[ii + jj / 8 + kk * elem_size] = x as u8;
569 x >>= 8;
570 }
571 ii += 8 * elem_size;
572 }
573 jj += 8;
574 }
575}
576
577fn bshuf_untrans_bit_elem(input: &[u8], size: usize, elem_size: usize) -> Vec<u8> {
580 debug_assert_eq!(size % 8, 0);
581 let nbyte = size * elem_size;
582 let mut tmp = vec![0u8; nbyte];
583 bshuf_trans_byte_bitrow(input, &mut tmp, size, elem_size);
584 let mut out = vec![0u8; nbyte];
585 bshuf_shuffle_bit_eightelem(&tmp, &mut out, size, elem_size);
586 out
587}
588
589fn bshuf_compress_lz4_block(
593 out: &mut Vec<u8>,
594 raw: &[u8],
595 elem_start: usize,
596 size: usize,
597 elem_size: usize,
598) {
599 let off = elem_start * elem_size;
600 let shuffled = bshuf_trans_bit_elem(&raw[off..off + size * elem_size], size, elem_size);
601 let comp = compress(&shuffled);
602 out.extend_from_slice(&(comp.len() as u32).to_be_bytes());
603 out.extend_from_slice(&comp);
604}
605
606fn bshuf_decompress_lz4_block(
610 buf: &[u8],
611 pos: usize,
612 size: usize,
613 elem_size: usize,
614) -> Option<(Vec<u8>, usize)> {
615 if pos + 4 > buf.len() {
616 return None;
617 }
618 let clen = u32::from_be_bytes(buf[pos..pos + 4].try_into().ok()?) as usize;
619 let dstart = pos + 4;
620 if dstart + clen > buf.len() {
621 return None;
622 }
623 let shuffled = decompress(&buf[dstart..dstart + clen], size * elem_size).ok()?;
624 if shuffled.len() != size * elem_size {
625 return None;
626 }
627 Some((
628 bshuf_untrans_bit_elem(&shuffled, size, elem_size),
629 dstart + clen,
630 ))
631}
632
633pub fn compress_bslz4(src: &NDArray) -> NDArray {
646 let raw = src.data.as_u8_slice();
647 let data_type = src.data.data_type();
648 let elem_size = data_type.element_size();
649 let total_elems = if elem_size > 0 {
650 raw.len() / elem_size
651 } else {
652 0
653 };
654 let block_size = bshuf_default_block_size(elem_size);
655
656 let mut out: Vec<u8> = Vec::with_capacity(raw.len() / 2 + 16);
657
658 let n_full = total_elems / block_size;
659 let mut elem = 0usize;
660 for _ in 0..n_full {
661 bshuf_compress_lz4_block(&mut out, raw, elem, block_size, elem_size);
662 elem += block_size;
663 }
664 let mut last_block = total_elems % block_size;
666 last_block -= last_block % BSHUF_BLOCKED_MULT;
667 if last_block > 0 {
668 bshuf_compress_lz4_block(&mut out, raw, elem, last_block, elem_size);
669 elem += last_block;
670 }
671 if elem < total_elems {
673 out.extend_from_slice(&raw[elem * elem_size..total_elems * elem_size]);
674 }
675
676 let compressed_size = out.len();
677 let mut arr = src.clone();
678 arr.data = NDDataBuffer::U8(out);
679 arr.codec = Some(Codec {
680 name: CodecName::BSLZ4,
681 compressed_size,
682 level: 0,
683 shuffle: 0,
684 compressor: 0,
685 original_data_type: data_type,
686 });
687
688 tracing::debug!(
689 original_size = raw.len(),
690 compressed_size,
691 ratio = raw.len() as f64 / compressed_size.max(1) as f64,
692 "BSLZ4 compress"
693 );
694 arr
695}
696
697pub fn decompress_bslz4(src: &NDArray) -> Option<NDArray> {
705 let codec = src.codec.as_ref()?;
706 if codec.name != CodecName::BSLZ4 {
707 return None;
708 }
709 let buf = src.data.as_u8_slice();
710 let original_type = original_data_type(src);
711 let elem_size = original_type.element_size();
712 if elem_size == 0 {
713 return None;
714 }
715 let total_elems: usize = src.dims.iter().map(|d| d.size).product();
716 let total_bytes = total_elems * elem_size;
717 let block_size = bshuf_default_block_size(elem_size);
718
719 let mut out: Vec<u8> = Vec::with_capacity(total_bytes);
720 let mut pos = 0usize;
721
722 let n_full = total_elems / block_size;
723 for _ in 0..n_full {
724 let (block, next) = bshuf_decompress_lz4_block(buf, pos, block_size, elem_size)?;
725 out.extend_from_slice(&block);
726 pos = next;
727 }
728 let mut last_block = total_elems % block_size;
730 last_block -= last_block % BSHUF_BLOCKED_MULT;
731 if last_block > 0 {
732 let (block, next) = bshuf_decompress_lz4_block(buf, pos, last_block, elem_size)?;
733 out.extend_from_slice(&block);
734 pos = next;
735 }
736 let leftover_bytes = (total_elems % BSHUF_BLOCKED_MULT) * elem_size;
738 if leftover_bytes > 0 {
739 if pos + leftover_bytes > buf.len() {
740 return None;
741 }
742 out.extend_from_slice(&buf[pos..pos + leftover_bytes]);
743 }
744 if out.len() != total_bytes {
745 return None;
746 }
747
748 let buffer = buffer_from_bytes(&out, original_type)?;
749 let mut arr = src.clone();
750 arr.data = buffer;
751 arr.codec = None;
752 Some(arr)
753}
754
755pub fn compress_jpeg(src: &NDArray, quality: u8) -> Option<NDArray> {
763 if src.data.data_type() != NDDataType::UInt8 {
764 return None;
765 }
766
767 let raw = src.data.as_u8_slice();
768 let info = src.info();
769
770 if info.x_size > u16::MAX as usize || info.y_size > u16::MAX as usize {
772 return None;
773 }
774
775 let (width, height, color_type) = match src.dims.len() {
776 2 => {
777 (
779 info.x_size as u16,
780 info.y_size as u16,
781 jpeg_encoder::ColorType::Luma,
782 )
783 }
784 3 if src.dims[0].size == 3 => {
785 (
787 info.x_size as u16,
788 info.y_size as u16,
789 jpeg_encoder::ColorType::Rgb,
790 )
791 }
792 _ => return None,
793 };
794
795 let mut jpeg_buf = Vec::new();
796 let encoder = jpeg_encoder::Encoder::new(&mut jpeg_buf, quality);
797 if encoder.encode(raw, width, height, color_type).is_err() {
798 return None;
799 }
800
801 let compressed_size = jpeg_buf.len();
802 let original_size = raw.len();
803
804 let mut arr = src.clone();
805 arr.data = NDDataBuffer::U8(jpeg_buf);
806 arr.codec = Some(Codec {
807 name: CodecName::JPEG,
808 compressed_size,
809 level: 0,
810 shuffle: 0,
811 compressor: 0,
812 original_data_type: src.data.data_type(),
816 });
817
818 tracing::debug!(
819 original_size,
820 compressed_size,
821 ratio = original_size as f64 / compressed_size.max(1) as f64,
822 "JPEG compress (quality={})",
823 quality,
824 );
825
826 Some(arr)
827}
828
829pub fn decompress_jpeg(src: &NDArray) -> Option<NDArray> {
836 if src.codec.as_ref().map(|c| c.name) != Some(CodecName::JPEG) {
837 return None;
838 }
839
840 let compressed = src.data.as_u8_slice();
841 let mut decoder = jpeg_decoder::Decoder::new(compressed);
842 let pixels = decoder.decode().ok()?;
843 let metadata = decoder.info()?;
844
845 let width = metadata.width as usize;
846 let height = metadata.height as usize;
847
848 let dims = match metadata.pixel_format {
849 jpeg_decoder::PixelFormat::L8 => {
850 vec![NDDimension::new(width), NDDimension::new(height)]
852 }
853 jpeg_decoder::PixelFormat::RGB24 => {
854 vec![
856 NDDimension::new(3),
857 NDDimension::new(width),
858 NDDimension::new(height),
859 ]
860 }
861 _ => return None,
862 };
863
864 let mut arr = src.clone();
865 arr.dims = dims;
866 arr.data = NDDataBuffer::U8(pixels);
867 arr.codec = None;
868
869 Some(arr)
870}
871
872#[derive(Debug, Clone, Copy)]
874pub struct BloscConfig {
875 pub compressor: u32,
877 pub clevel: u32,
879 pub shuffle: u32,
881}
882
883impl Default for BloscConfig {
884 fn default() -> Self {
885 Self {
886 compressor: 0,
887 clevel: 5,
892 shuffle: 0,
893 }
894 }
895}
896
897pub fn compress_blosc(src: &NDArray, config: &BloscConfig) -> NDArray {
899 let raw = src.data.as_u8_slice();
900 let element_size = src.data.data_type().element_size();
901
902 let pipeline = FilterPipeline {
909 filters: vec![Filter {
910 id: FILTER_BLOSC,
911 flags: 0,
912 cd_values: vec![
913 2, 2, element_size as u32, raw.len() as u32, config.clevel, config.shuffle, config.compressor, ],
921 }],
922 };
923
924 let compressed = match apply_filters(&pipeline, raw) {
925 Ok(data) => data,
926 Err(_) => return src.clone(),
927 };
928
929 let compressed_size = compressed.len();
930 let original_data_type = src.data.data_type();
931 let mut arr = src.clone();
932 arr.data = NDDataBuffer::U8(compressed);
933 arr.codec = Some(Codec {
934 name: CodecName::Blosc,
935 compressed_size,
936 level: config.clevel as i32,
939 shuffle: config.shuffle as i32,
940 compressor: config.compressor as i32,
941 original_data_type,
942 });
943 arr
944}
945
946pub fn decompress_blosc(src: &NDArray) -> Option<NDArray> {
948 let codec = src.codec.as_ref()?;
949 if codec.name != CodecName::Blosc {
950 return None;
951 }
952
953 let compressed = src.data.as_u8_slice();
954 let original_type = original_data_type(src);
955 let element_size = original_type.element_size();
956
957 let pipeline = FilterPipeline {
962 filters: vec![Filter {
963 id: FILTER_BLOSC,
964 flags: 0,
965 cd_values: vec![
966 2,
967 2,
968 element_size as u32,
969 0,
970 codec.level as u32,
971 codec.shuffle as u32,
972 codec.compressor as u32,
973 ],
974 }],
975 };
976
977 let decompressed = reverse_filters(&pipeline, compressed).ok()?;
978
979 let buffer = buffer_from_bytes(&decompressed, original_type)?;
980
981 let mut arr = src.clone();
982 arr.data = buffer;
983 arr.codec = None;
984 Some(arr)
985}
986
987#[derive(Debug, Clone, Copy, PartialEq, Eq)]
989pub enum CodecMode {
990 Compress { codec: CodecName, quality: u8 },
992 Decompress,
994}
995
996#[derive(Default)]
1000struct CodecParamIndices {
1001 mode: Option<usize>,
1002 compressor: Option<usize>,
1003 comp_factor: Option<usize>,
1004 jpeg_quality: Option<usize>,
1005 blosc_compressor: Option<usize>,
1006 blosc_clevel: Option<usize>,
1007 blosc_shuffle: Option<usize>,
1008 blosc_numthreads: Option<usize>,
1009 codec_status: Option<usize>,
1010 codec_error: Option<usize>,
1011}
1012
1013pub struct CodecProcessor {
1014 mode: CodecMode,
1015 compression_ratio: f64,
1016 jpeg_quality: u8,
1017 blosc_config: BloscConfig,
1018 params: CodecParamIndices,
1019}
1020
1021impl CodecProcessor {
1022 pub fn new(mode: CodecMode) -> Self {
1023 let quality = match mode {
1024 CodecMode::Compress { quality, .. } => quality,
1025 _ => 85,
1026 };
1027 Self {
1028 mode,
1029 compression_ratio: 1.0,
1030 jpeg_quality: quality,
1031 blosc_config: BloscConfig::default(),
1032 params: CodecParamIndices::default(),
1033 }
1034 }
1035
1036 pub fn compression_ratio(&self) -> f64 {
1039 self.compression_ratio
1040 }
1041}
1042
1043impl NDPluginProcess for CodecProcessor {
1044 fn process_array(&mut self, array: &NDArray, _pool: &NDArrayPool) -> ProcessResult {
1045 let original_bytes = array.data.as_u8_slice().len();
1046
1047 let result = match self.mode {
1048 CodecMode::Compress { .. } if array.codec.is_some() => {
1049 Some(array.clone())
1051 }
1052 CodecMode::Compress {
1053 codec: CodecName::LZ4,
1054 ..
1055 } => Some(compress_lz4(array)),
1056 CodecMode::Compress {
1057 codec: CodecName::JPEG,
1058 ..
1059 } => compress_jpeg(array, self.jpeg_quality),
1060 CodecMode::Compress {
1061 codec: CodecName::Zlib,
1062 ..
1063 } => Some(compress_zlib(array)),
1064 CodecMode::Compress {
1065 codec: CodecName::Blosc,
1066 ..
1067 } => Some(compress_blosc(array, &self.blosc_config)),
1068 CodecMode::Compress {
1069 codec: CodecName::LZ4HDF5,
1070 ..
1071 } => Some(compress_lz4hdf5(array)),
1072 CodecMode::Compress {
1073 codec: CodecName::BSLZ4,
1074 ..
1075 } => Some(compress_bslz4(array)),
1076 CodecMode::Compress { .. } => None,
1077 CodecMode::Decompress => match array.codec.as_ref().map(|c| c.name) {
1078 Some(CodecName::LZ4) => decompress_lz4(array),
1079 Some(CodecName::JPEG) => decompress_jpeg(array),
1080 Some(CodecName::Zlib) => decompress_zlib(array),
1081 Some(CodecName::Blosc) => decompress_blosc(array),
1082 Some(CodecName::LZ4HDF5) => decompress_lz4hdf5(array),
1083 Some(CodecName::BSLZ4) => decompress_bslz4(array),
1084 _ => None,
1085 },
1086 };
1087
1088 let mut updates = Vec::new();
1089
1090 match result {
1091 Some(ref out) => {
1092 let output_bytes = out.data.as_u8_slice().len();
1093 match self.mode {
1094 CodecMode::Compress { .. } => {
1095 self.compression_ratio = original_bytes as f64 / output_bytes.max(1) as f64;
1096 }
1097 CodecMode::Decompress => {
1098 self.compression_ratio = output_bytes as f64 / original_bytes.max(1) as f64;
1099 }
1100 }
1101 if let Some(idx) = self.params.comp_factor {
1102 updates.push(ParamUpdate::float64(idx, self.compression_ratio));
1103 }
1104 if let Some(idx) = self.params.codec_status {
1105 updates.push(ParamUpdate::int32(idx, 0)); }
1107 if let Some(idx) = self.params.codec_error {
1108 updates.push(ParamUpdate::Octet {
1109 reason: idx,
1110 addr: 0,
1111 value: String::new(),
1112 });
1113 }
1114 let mut r = ProcessResult::arrays(vec![Arc::new(out.clone())]);
1115 r.param_updates = updates;
1116 r
1117 }
1118 None => {
1119 self.compression_ratio = 1.0;
1121 if let Some(idx) = self.params.comp_factor {
1122 updates.push(ParamUpdate::float64(idx, 1.0));
1123 }
1124 if let Some(idx) = self.params.codec_status {
1125 updates.push(ParamUpdate::int32(idx, 1)); }
1127 if let Some(idx) = self.params.codec_error {
1128 updates.push(ParamUpdate::Octet {
1129 reason: idx,
1130 addr: 0,
1131 value: "codec operation failed or unsupported".to_string(),
1132 });
1133 }
1134 let mut r = ProcessResult::arrays(vec![Arc::new(array.clone())]);
1135 r.param_updates = updates;
1136 r
1137 }
1138 }
1139 }
1140
1141 fn plugin_type(&self) -> &str {
1142 "NDPluginCodec"
1143 }
1144
1145 fn compression_aware(&self) -> bool {
1154 true
1155 }
1156
1157 fn register_params(
1158 &mut self,
1159 base: &mut asyn_rs::port::PortDriverBase,
1160 ) -> asyn_rs::error::AsynResult<()> {
1161 use asyn_rs::param::ParamType;
1162 base.create_param("MODE", ParamType::Int32)?;
1163 base.create_param("COMPRESSOR", ParamType::Int32)?;
1164 base.create_param("COMP_FACTOR", ParamType::Float64)?;
1165 base.create_param("JPEG_QUALITY", ParamType::Int32)?;
1166 base.create_param("BLOSC_COMPRESSOR", ParamType::Int32)?;
1167 base.create_param("BLOSC_CLEVEL", ParamType::Int32)?;
1168 base.create_param("BLOSC_SHUFFLE", ParamType::Int32)?;
1169 base.create_param("BLOSC_NUMTHREADS", ParamType::Int32)?;
1170 base.create_param("CODEC_STATUS", ParamType::Int32)?;
1171 base.create_param("CODEC_ERROR", ParamType::Octet)?;
1172
1173 self.params.mode = base.find_param("MODE");
1174 self.params.compressor = base.find_param("COMPRESSOR");
1175 self.params.comp_factor = base.find_param("COMP_FACTOR");
1176 self.params.jpeg_quality = base.find_param("JPEG_QUALITY");
1177 self.params.blosc_compressor = base.find_param("BLOSC_COMPRESSOR");
1178 self.params.blosc_clevel = base.find_param("BLOSC_CLEVEL");
1179 self.params.blosc_shuffle = base.find_param("BLOSC_SHUFFLE");
1180 self.params.blosc_numthreads = base.find_param("BLOSC_NUMTHREADS");
1181 self.params.codec_status = base.find_param("CODEC_STATUS");
1182 self.params.codec_error = base.find_param("CODEC_ERROR");
1183 Ok(())
1184 }
1185
1186 fn on_param_change(
1187 &mut self,
1188 reason: usize,
1189 params: &ad_core_rs::plugin::runtime::PluginParamSnapshot,
1190 ) -> ad_core_rs::plugin::runtime::ParamChangeResult {
1191 if Some(reason) == self.params.mode {
1192 let v = params.value.as_i32();
1193 if v == 0 {
1194 let codec = match self.mode {
1196 CodecMode::Compress { codec, .. } => codec,
1197 _ => CodecName::LZ4,
1198 };
1199 self.mode = CodecMode::Compress {
1200 codec,
1201 quality: self.jpeg_quality,
1202 };
1203 } else {
1204 self.mode = CodecMode::Decompress;
1205 }
1206 } else if Some(reason) == self.params.compressor {
1207 let codec = match params.value.as_i32() {
1212 0 => CodecName::None,
1213 1 => CodecName::JPEG,
1214 2 => CodecName::Blosc,
1215 3 => CodecName::LZ4,
1216 4 => CodecName::BSLZ4,
1217 5 => CodecName::Zlib,
1218 6 => CodecName::LZ4HDF5,
1219 _ => CodecName::None,
1220 };
1221 if let CodecMode::Compress { .. } = self.mode {
1222 self.mode = CodecMode::Compress {
1223 codec,
1224 quality: self.jpeg_quality,
1225 };
1226 }
1227 } else if Some(reason) == self.params.jpeg_quality {
1228 self.jpeg_quality = params.value.as_i32().clamp(1, 100) as u8;
1229 if let CodecMode::Compress { codec, .. } = self.mode {
1230 self.mode = CodecMode::Compress {
1231 codec,
1232 quality: self.jpeg_quality,
1233 };
1234 }
1235 } else if Some(reason) == self.params.blosc_compressor {
1236 self.blosc_config.compressor = params.value.as_i32().max(0) as u32;
1237 } else if Some(reason) == self.params.blosc_clevel {
1238 self.blosc_config.clevel = params.value.as_i32().clamp(0, 9) as u32;
1239 } else if Some(reason) == self.params.blosc_shuffle {
1240 self.blosc_config.shuffle = params.value.as_i32().max(0) as u32;
1241 }
1242
1243 ad_core_rs::plugin::runtime::ParamChangeResult::updates(vec![])
1244 }
1245}
1246
1247#[cfg(test)]
1248mod tests {
1249 use super::*;
1250
1251 fn make_u8_array(width: usize, height: usize) -> NDArray {
1252 let mut arr = NDArray::new(
1253 vec![NDDimension::new(width), NDDimension::new(height)],
1254 NDDataType::UInt8,
1255 );
1256 if let NDDataBuffer::U8(ref mut v) = arr.data {
1257 for i in 0..v.len() {
1258 v[i] = (i % 256) as u8;
1259 }
1260 }
1261 arr
1262 }
1263
1264 fn make_rgb_array(width: usize, height: usize) -> NDArray {
1265 use ad_core_rs::attributes::{NDAttrSource, NDAttrValue, NDAttribute};
1266 let mut arr = NDArray::new(
1267 vec![
1268 NDDimension::new(3),
1269 NDDimension::new(width),
1270 NDDimension::new(height),
1271 ],
1272 NDDataType::UInt8,
1273 );
1274 arr.attributes.add(NDAttribute::new_static(
1276 "ColorMode",
1277 "Color Mode",
1278 NDAttrSource::Driver,
1279 NDAttrValue::Int32(2), ));
1281 if let NDDataBuffer::U8(ref mut v) = arr.data {
1282 for i in 0..v.len() {
1283 v[i] = (i % 256) as u8;
1284 }
1285 }
1286 arr
1287 }
1288
1289 #[test]
1295 fn compressors_record_type_in_codec_not_an_attribute() {
1296 let mut arr = NDArray::new(vec![NDDimension::new(8)], NDDataType::UInt16);
1297 if let NDDataBuffer::U16(ref mut v) = arr.data {
1298 for (i, x) in v.iter_mut().enumerate() {
1299 *x = (i * 7) as u16;
1300 }
1301 }
1302 for compressed in [
1303 compress_lz4(&arr),
1304 compress_zlib(&arr),
1305 compress_lz4hdf5(&arr),
1306 compress_bslz4(&arr),
1307 compress_blosc(&arr, &BloscConfig::default()),
1308 ] {
1309 assert_eq!(
1310 compressed.codec.as_ref().unwrap().original_data_type,
1311 NDDataType::UInt16,
1312 "the original element type must travel in the codec"
1313 );
1314 assert!(
1315 compressed
1316 .attributes
1317 .get("CODEC_ORIGINAL_DATA_TYPE")
1318 .is_none(),
1319 "no codec carrier attribute may be attached to a compressed frame"
1320 );
1321 }
1322 }
1323
1324 #[test]
1325 fn test_adp29_blosc_default_clevel_and_codec_params() {
1326 assert_eq!(
1329 BloscConfig::default().clevel,
1330 5,
1331 "default Blosc clevel must be 5 (C parity)"
1332 );
1333
1334 let mut arr = NDArray::new(vec![NDDimension::new(8)], NDDataType::UInt16);
1337 if let NDDataBuffer::U16(ref mut v) = arr.data {
1338 for (i, x) in v.iter_mut().enumerate() {
1339 *x = (i * 7) as u16;
1340 }
1341 }
1342 let out = compress_blosc(&arr, &BloscConfig::default());
1343 let codec = out.codec.as_ref().expect("blosc codec metadata");
1344 assert_eq!(codec.level, 5, "codec.level records the default clevel 5");
1347 assert_eq!(codec.shuffle, 0, "codec.shuffle records shuffle");
1348 assert_eq!(codec.compressor, 0, "codec.compressor records compressor");
1349 }
1350
1351 #[test]
1352 fn test_blosc_roundtrip_u16_default_compressor() {
1353 let mut arr = NDArray::new(
1359 vec![NDDimension::new(100), NDDimension::new(20)],
1360 NDDataType::UInt16,
1361 );
1362 if let NDDataBuffer::U16(ref mut v) = arr.data {
1363 for (i, x) in v.iter_mut().enumerate() {
1364 *x = (i * 37 % 65521) as u16;
1365 }
1366 }
1367 let original = arr.data.as_u8_slice().to_vec();
1368
1369 let compressed = compress_blosc(&arr, &BloscConfig::default());
1370 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::Blosc);
1371 assert_ne!(
1372 compressed.data.as_u8_slice(),
1373 original.as_slice(),
1374 "blosc must actually compress (not fall back to the raw clone)"
1375 );
1376
1377 let decompressed = decompress_blosc(&compressed).expect("blosc round-trip");
1378 assert!(decompressed.codec.is_none());
1379 assert_eq!(decompressed.data.data_type(), NDDataType::UInt16);
1380 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1381 }
1382
1383 #[test]
1384 fn test_blosc_roundtrip_u16_lz4_subcompressor() {
1385 let cfg = BloscConfig {
1388 compressor: 1,
1389 clevel: 5,
1390 shuffle: 1,
1391 };
1392 let mut arr = NDArray::new(vec![NDDimension::new(256)], NDDataType::UInt16);
1393 if let NDDataBuffer::U16(ref mut v) = arr.data {
1394 for (i, x) in v.iter_mut().enumerate() {
1395 *x = (i * 13 % 65521) as u16;
1396 }
1397 }
1398 let original = arr.data.as_u8_slice().to_vec();
1399
1400 let compressed = compress_blosc(&arr, &cfg);
1401 let codec = compressed.codec.as_ref().unwrap();
1402 assert_eq!(codec.compressor, 1, "records the LZ4 sub-compressor");
1403 assert_eq!(codec.shuffle, 1, "records byte shuffle");
1404
1405 let decompressed = decompress_blosc(&compressed).expect("blosc lz4 round-trip");
1406 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1407 }
1408
1409 #[test]
1412 fn test_lz4_roundtrip_u8() {
1413 let arr = make_u8_array(4, 4);
1414 let original_data = arr.data.as_u8_slice().to_vec();
1415
1416 let compressed = compress_lz4(&arr);
1417 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::LZ4);
1418 assert_ne!(compressed.data.as_u8_slice(), original_data.as_slice());
1420
1421 let decompressed = decompress_lz4(&compressed).unwrap();
1422 assert!(decompressed.codec.is_none());
1423 assert_eq!(decompressed.data.data_type(), NDDataType::UInt8);
1424 assert_eq!(decompressed.data.as_u8_slice(), original_data.as_slice());
1425 }
1426
1427 #[test]
1428 fn test_decompress_runtime_does_not_drop_compressed_input() {
1429 use ad_core_rs::plugin::channel::{NDArrayOutput, ndarray_channel};
1436 use ad_core_rs::plugin::runtime::create_plugin_runtime_with_output;
1437 use ad_core_rs::plugin::wiring::WiringRegistry;
1438 use std::sync::atomic::Ordering;
1439
1440 let mut raw = make_u8_array(4, 4);
1442 raw.unique_id = 1;
1443 let original_data = raw.data.as_u8_slice().to_vec();
1444 let compressed = compress_lz4(&raw);
1445 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::LZ4);
1446 assert_eq!(compressed.unique_id, 1);
1447
1448 let mut sentinel = make_u8_array(4, 4);
1452 sentinel.unique_id = 2;
1453
1454 let pool = Arc::new(NDArrayPool::new(1_000_000));
1455 let (ds_sender, mut ds_rx) = ndarray_channel("DS", 10);
1456 let mut output = NDArrayOutput::new();
1457 output.add(ds_sender);
1458 let (handle, _jh) = create_plugin_runtime_with_output(
1459 "CODEC_DECOMP",
1460 CodecProcessor::new(CodecMode::Decompress),
1461 pool,
1462 10,
1463 output,
1464 "",
1465 Arc::new(WiringRegistry::new()),
1466 );
1467 let dropped = handle.array_sender().dropped_arrays_counter().clone();
1468 handle
1469 .port_runtime()
1470 .port_handle()
1471 .write_int32_blocking(handle.plugin_params.enable_callbacks, 0, 1)
1472 .unwrap();
1473 assert!(
1475 handle.wait_params_applied(std::time::Duration::from_secs(10)),
1476 "data thread did not apply EnableCallbacks"
1477 );
1478
1479 let rt = tokio::runtime::Builder::new_current_thread()
1480 .enable_all()
1481 .build()
1482 .unwrap();
1483 rt.block_on(handle.array_sender().publish(Arc::new(compressed)));
1484 rt.block_on(handle.array_sender().publish(Arc::new(sentinel)));
1485
1486 let first = ds_rx.blocking_recv().expect("downstream array");
1487 assert_eq!(
1488 first.unique_id, 1,
1489 "compressed input must be decompressed and delivered, not dropped"
1490 );
1491 assert!(
1492 first.codec.is_none(),
1493 "delivered array must be decompressed (codec cleared)"
1494 );
1495 assert_eq!(first.data.as_u8_slice(), original_data.as_slice());
1496 assert_eq!(
1497 dropped.load(Ordering::Acquire),
1498 0,
1499 "compression-aware Codec must not count its compressed input as dropped"
1500 );
1501 }
1502
1503 #[test]
1504 fn test_lz4_roundtrip_u16() {
1505 let mut arr = NDArray::new(
1506 vec![NDDimension::new(8), NDDimension::new(8)],
1507 NDDataType::UInt16,
1508 );
1509 if let NDDataBuffer::U16(ref mut v) = arr.data {
1510 for i in 0..v.len() {
1511 v[i] = (i * 100) as u16;
1512 }
1513 }
1514 let original_bytes = arr.data.as_u8_slice().to_vec();
1515
1516 let compressed = compress_lz4(&arr);
1517 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::LZ4);
1518 assert_eq!(
1520 compressed.codec.as_ref().unwrap().original_data_type,
1521 NDDataType::UInt16
1522 );
1523 assert!(
1525 compressed
1526 .attributes
1527 .get("CODEC_ORIGINAL_DATA_TYPE")
1528 .is_none()
1529 );
1530
1531 let decompressed = decompress_lz4(&compressed).unwrap();
1532 assert!(decompressed.codec.is_none());
1533 assert_eq!(decompressed.data.data_type(), NDDataType::UInt16);
1534 assert_eq!(decompressed.data.as_u8_slice(), original_bytes.as_slice());
1535 }
1536
1537 #[test]
1538 fn test_lz4_roundtrip_f64() {
1539 let mut arr = NDArray::new(vec![NDDimension::new(16)], NDDataType::Float64);
1540 if let NDDataBuffer::F64(ref mut v) = arr.data {
1541 for i in 0..v.len() {
1542 v[i] = i as f64 * 1.5;
1543 }
1544 }
1545 let original_bytes = arr.data.as_u8_slice().to_vec();
1546
1547 let compressed = compress_lz4(&arr);
1548 let decompressed = decompress_lz4(&compressed).unwrap();
1549 assert_eq!(decompressed.data.data_type(), NDDataType::Float64);
1550 assert_eq!(decompressed.data.as_u8_slice(), original_bytes.as_slice());
1551 }
1552
1553 #[test]
1554 fn test_lz4_compresses_repetitive_data() {
1555 let mut arr = NDArray::new(
1557 vec![NDDimension::new(256), NDDimension::new(256)],
1558 NDDataType::UInt8,
1559 );
1560 if let NDDataBuffer::U8(ref mut v) = arr.data {
1562 for x in v.iter_mut() {
1563 *x = 0;
1564 }
1565 }
1566 let original_size = arr.data.as_u8_slice().len();
1567
1568 let compressed = compress_lz4(&arr);
1569 let compressed_size = compressed.codec.as_ref().unwrap().compressed_size;
1570 assert!(
1571 compressed_size < original_size,
1572 "compressed ({}) should be smaller than original ({})",
1573 compressed_size,
1574 original_size,
1575 );
1576 }
1577
1578 #[test]
1579 fn test_lz4_preserves_metadata() {
1580 let mut arr = make_u8_array(4, 4);
1581 arr.unique_id = 42;
1582
1583 let compressed = compress_lz4(&arr);
1584 assert_eq!(compressed.unique_id, 42);
1585 assert_eq!(compressed.dims.len(), 2);
1586 assert_eq!(compressed.dims[0].size, 4);
1587 assert_eq!(compressed.dims[1].size, 4);
1588 }
1589
1590 #[test]
1593 fn test_bitshuffle_block_transpose_roundtrip() {
1594 for &(n, elem_size) in &[(16usize, 4usize), (8, 2), (256, 8), (128, 1)] {
1597 let input: Vec<u8> = (0..n * elem_size).map(|i| (i * 7 + 3) as u8).collect();
1598 let shuffled = bshuf_trans_bit_elem(&input, n, elem_size);
1599 assert_eq!(shuffled.len(), input.len());
1600 let restored = bshuf_untrans_bit_elem(&shuffled, n, elem_size);
1601 assert_eq!(restored, input, "elem_size {elem_size}, n {n}");
1602 }
1603 }
1604
1605 #[test]
1606 fn test_bitshuffle_matches_c_reference_vector() {
1607 let input: Vec<u8> = (0..16u16).flat_map(|v| v.to_le_bytes()).collect();
1615 let shuffled = bshuf_trans_bit_elem(&input, 16, 2);
1616 let mut expected = vec![0u8; 32];
1617 expected[..8].copy_from_slice(&[170, 170, 204, 204, 240, 240, 0, 255]);
1618 assert_eq!(
1619 shuffled, expected,
1620 "canonical bitshuffle transpose must match the C library bytes"
1621 );
1622 }
1623
1624 #[test]
1625 fn test_bslz4_roundtrip_u8() {
1626 let mut arr = NDArray::new(
1627 vec![NDDimension::new(64), NDDimension::new(64)],
1628 NDDataType::UInt8,
1629 );
1630 if let NDDataBuffer::U8(ref mut v) = arr.data {
1631 for (i, x) in v.iter_mut().enumerate() {
1632 *x = (i % 251) as u8;
1633 }
1634 }
1635 let original = arr.data.as_u8_slice().to_vec();
1636
1637 let compressed = compress_bslz4(&arr);
1638 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::BSLZ4);
1639 assert_ne!(compressed.data.as_u8_slice(), original.as_slice());
1640
1641 let decompressed = decompress_bslz4(&compressed).unwrap();
1642 assert!(decompressed.codec.is_none());
1643 assert_eq!(decompressed.data.data_type(), NDDataType::UInt8);
1644 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1645 }
1646
1647 #[test]
1648 fn test_bslz4_roundtrip_u16() {
1649 let mut arr = NDArray::new(
1650 vec![NDDimension::new(100), NDDimension::new(20)],
1651 NDDataType::UInt16,
1652 );
1653 if let NDDataBuffer::U16(ref mut v) = arr.data {
1654 for (i, x) in v.iter_mut().enumerate() {
1655 *x = (i * 37 % 65521) as u16;
1656 }
1657 }
1658 let original = arr.data.as_u8_slice().to_vec();
1659
1660 let compressed = compress_bslz4(&arr);
1661 assert_eq!(
1662 compressed.codec.as_ref().unwrap().original_data_type,
1663 NDDataType::UInt16
1664 );
1665 let decompressed = decompress_bslz4(&compressed).unwrap();
1666 assert_eq!(decompressed.data.data_type(), NDDataType::UInt16);
1667 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1668 }
1669
1670 #[test]
1671 fn test_bslz4_roundtrip_f64_with_negatives() {
1672 let mut arr = NDArray::new(vec![NDDimension::new(73)], NDDataType::Float64);
1673 if let NDDataBuffer::F64(ref mut v) = arr.data {
1674 for (i, x) in v.iter_mut().enumerate() {
1675 *x = (i as f64 - 36.0) * 2.5;
1676 }
1677 }
1678 let original = arr.data.as_u8_slice().to_vec();
1679
1680 let compressed = compress_bslz4(&arr);
1681 let decompressed = decompress_bslz4(&compressed).unwrap();
1682 assert_eq!(decompressed.data.data_type(), NDDataType::Float64);
1683 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1684 }
1685
1686 #[test]
1687 fn test_bslz4_roundtrip_multi_block() {
1688 let elem_size = 4usize;
1691 let block = bshuf_default_block_size(elem_size);
1692 let count = block * 2 + block / 2 + 3;
1694 let mut arr = NDArray::new(vec![NDDimension::new(count)], NDDataType::Int32);
1695 if let NDDataBuffer::I32(ref mut v) = arr.data {
1696 for (i, x) in v.iter_mut().enumerate() {
1697 *x = (i as i32).wrapping_mul(2_654_435_761u32 as i32);
1698 }
1699 }
1700 let original = arr.data.as_u8_slice().to_vec();
1701
1702 let compressed = compress_bslz4(&arr);
1703 let decompressed = decompress_bslz4(&compressed).unwrap();
1704 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1705 }
1706
1707 #[test]
1708 fn test_bslz4_compresses_repetitive_data() {
1709 let arr = NDArray::new(
1711 vec![NDDimension::new(256), NDDimension::new(256)],
1712 NDDataType::UInt16,
1713 );
1714 let original_size = arr.data.as_u8_slice().len();
1715 let compressed = compress_bslz4(&arr);
1716 let compressed_size = compressed.codec.as_ref().unwrap().compressed_size;
1717 assert!(
1718 compressed_size < original_size,
1719 "bslz4 compressed ({compressed_size}) should be < original ({original_size})"
1720 );
1721 }
1722
1723 #[test]
1724 fn test_bslz4_via_processor() {
1725 let mut arr = NDArray::new(
1727 vec![NDDimension::new(32), NDDimension::new(32)],
1728 NDDataType::UInt16,
1729 );
1730 if let NDDataBuffer::U16(ref mut v) = arr.data {
1731 for (i, x) in v.iter_mut().enumerate() {
1732 *x = (i * 11) as u16;
1733 }
1734 }
1735 let original = arr.data.as_u8_slice().to_vec();
1736 let pool = NDArrayPool::new(10_000_000);
1737
1738 let mut comp = CodecProcessor::new(CodecMode::Compress {
1739 codec: CodecName::BSLZ4,
1740 quality: 0,
1741 });
1742 let compressed = comp.process_array(&arr, &pool);
1743 let compressed_arr = &compressed.output_arrays[0];
1744 assert_eq!(
1745 compressed_arr.codec.as_ref().unwrap().name,
1746 CodecName::BSLZ4
1747 );
1748
1749 let mut decomp = CodecProcessor::new(CodecMode::Decompress);
1750 let result = decomp.process_array(compressed_arr, &pool);
1751 assert_eq!(
1752 result.output_arrays[0].data.as_u8_slice(),
1753 original.as_slice()
1754 );
1755 }
1756
1757 #[test]
1760 fn test_jpeg_compress_mono() {
1761 let arr = make_u8_array(16, 16);
1762 let compressed = compress_jpeg(&arr, 90).unwrap();
1763 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::JPEG);
1764 let data = compressed.data.as_u8_slice();
1766 assert_eq!(&data[0..2], &[0xFF, 0xD8]);
1767 }
1768
1769 #[test]
1770 fn test_jpeg_compress_rgb() {
1771 let arr = make_rgb_array(16, 16);
1772 let compressed = compress_jpeg(&arr, 90).unwrap();
1773 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::JPEG);
1774 let data = compressed.data.as_u8_slice();
1775 assert_eq!(&data[0..2], &[0xFF, 0xD8]);
1776 }
1777
1778 #[test]
1779 fn test_jpeg_roundtrip_mono() {
1780 let arr = make_u8_array(16, 16);
1781 let compressed = compress_jpeg(&arr, 100).unwrap();
1782 let decompressed = decompress_jpeg(&compressed).unwrap();
1783 assert!(decompressed.codec.is_none());
1784 assert_eq!(decompressed.dims.len(), 2);
1785 assert_eq!(decompressed.dims[0].size, 16); assert_eq!(decompressed.dims[1].size, 16); assert_eq!(decompressed.data.data_type(), NDDataType::UInt8);
1788 assert_eq!(decompressed.data.len(), 16 * 16);
1790 }
1791
1792 #[test]
1793 fn test_jpeg_roundtrip_rgb() {
1794 let arr = make_rgb_array(16, 16);
1795 let compressed = compress_jpeg(&arr, 100).unwrap();
1796 let decompressed = decompress_jpeg(&compressed).unwrap();
1797 assert!(decompressed.codec.is_none());
1798 assert_eq!(decompressed.dims.len(), 3);
1799 assert_eq!(decompressed.dims[0].size, 3); assert_eq!(decompressed.dims[1].size, 16); assert_eq!(decompressed.dims[2].size, 16); assert_eq!(decompressed.data.len(), 3 * 16 * 16);
1803 }
1804
1805 #[test]
1806 fn test_jpeg_rejects_non_u8() {
1807 let arr = NDArray::new(
1808 vec![NDDimension::new(8), NDDimension::new(8)],
1809 NDDataType::UInt16,
1810 );
1811 assert!(compress_jpeg(&arr, 90).is_none());
1812 }
1813
1814 #[test]
1815 fn test_jpeg_rejects_1d() {
1816 let arr = NDArray::new(vec![NDDimension::new(64)], NDDataType::UInt8);
1817 assert!(compress_jpeg(&arr, 90).is_none());
1818 }
1819
1820 #[test]
1821 fn test_jpeg_quality_affects_size() {
1822 let arr = make_u8_array(64, 64);
1823 let high = compress_jpeg(&arr, 95).unwrap();
1824 let low = compress_jpeg(&arr, 10).unwrap();
1825 let high_size = high.codec.as_ref().unwrap().compressed_size;
1826 let low_size = low.codec.as_ref().unwrap().compressed_size;
1827 assert!(
1828 high_size > low_size,
1829 "high quality ({}) should produce larger output than low quality ({})",
1830 high_size,
1831 low_size,
1832 );
1833 }
1834
1835 #[test]
1838 fn test_zlib_roundtrip_u8() {
1839 let arr = make_u8_array(8, 8);
1840 let original = arr.data.as_u8_slice().to_vec();
1841
1842 let compressed = compress_zlib(&arr);
1843 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::Zlib);
1844 assert_ne!(compressed.data.as_u8_slice(), original.as_slice());
1845
1846 let decompressed = decompress_zlib(&compressed).unwrap();
1847 assert!(decompressed.codec.is_none());
1848 assert_eq!(decompressed.data.data_type(), NDDataType::UInt8);
1849 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1850 }
1851
1852 #[test]
1853 fn test_zlib_roundtrip_u16() {
1854 let mut arr = NDArray::new(
1855 vec![NDDimension::new(16), NDDimension::new(16)],
1856 NDDataType::UInt16,
1857 );
1858 if let NDDataBuffer::U16(ref mut v) = arr.data {
1859 for (i, x) in v.iter_mut().enumerate() {
1860 *x = (i * 257 % 65521) as u16;
1861 }
1862 }
1863 let original = arr.data.as_u8_slice().to_vec();
1864
1865 let compressed = compress_zlib(&arr);
1866 assert_eq!(
1867 compressed.codec.as_ref().unwrap().original_data_type,
1868 NDDataType::UInt16
1869 );
1870
1871 let decompressed = decompress_zlib(&compressed).unwrap();
1872 assert_eq!(decompressed.data.data_type(), NDDataType::UInt16);
1873 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1874 }
1875
1876 #[test]
1877 fn test_zlib_roundtrip_f64_with_negatives() {
1878 let mut arr = NDArray::new(vec![NDDimension::new(64)], NDDataType::Float64);
1879 if let NDDataBuffer::F64(ref mut v) = arr.data {
1880 for (i, x) in v.iter_mut().enumerate() {
1881 *x = (i as f64 - 32.0) * 3.25;
1882 }
1883 }
1884 let original = arr.data.as_u8_slice().to_vec();
1885
1886 let compressed = compress_zlib(&arr);
1887 let decompressed = decompress_zlib(&compressed).unwrap();
1888 assert_eq!(decompressed.data.data_type(), NDDataType::Float64);
1889 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1890 }
1891
1892 #[test]
1893 fn test_zlib_compresses_repetitive_data() {
1894 let arr = NDArray::new(
1895 vec![NDDimension::new(256), NDDimension::new(256)],
1896 NDDataType::UInt8,
1897 );
1898 let original_size = arr.data.as_u8_slice().len();
1899 let compressed = compress_zlib(&arr);
1900 let compressed_size = compressed.codec.as_ref().unwrap().compressed_size;
1901 assert!(
1902 compressed_size < original_size,
1903 "zlib compressed ({compressed_size}) should be < original ({original_size})"
1904 );
1905 }
1906
1907 #[test]
1908 fn test_zlib_via_processor() {
1909 let mut arr = NDArray::new(
1910 vec![NDDimension::new(32), NDDimension::new(32)],
1911 NDDataType::UInt16,
1912 );
1913 if let NDDataBuffer::U16(ref mut v) = arr.data {
1914 for (i, x) in v.iter_mut().enumerate() {
1915 *x = (i * 13) as u16;
1916 }
1917 }
1918 let original = arr.data.as_u8_slice().to_vec();
1919 let pool = NDArrayPool::new(10_000_000);
1920
1921 let mut comp = CodecProcessor::new(CodecMode::Compress {
1922 codec: CodecName::Zlib,
1923 quality: 0,
1924 });
1925 let compressed = comp.process_array(&arr, &pool);
1926 let compressed_arr = &compressed.output_arrays[0];
1927 assert_eq!(compressed_arr.codec.as_ref().unwrap().name, CodecName::Zlib);
1928
1929 let mut decomp = CodecProcessor::new(CodecMode::Decompress);
1930 let result = decomp.process_array(compressed_arr, &pool);
1931 assert_eq!(
1932 result.output_arrays[0].data.as_u8_slice(),
1933 original.as_slice()
1934 );
1935 }
1936
1937 #[test]
1940 fn test_lz4hdf5_roundtrip_u8() {
1941 let mut arr = NDArray::new(
1942 vec![NDDimension::new(64), NDDimension::new(64)],
1943 NDDataType::UInt8,
1944 );
1945 if let NDDataBuffer::U8(ref mut v) = arr.data {
1946 for (i, x) in v.iter_mut().enumerate() {
1947 *x = (i % 251) as u8;
1948 }
1949 }
1950 let original = arr.data.as_u8_slice().to_vec();
1951
1952 let compressed = compress_lz4hdf5(&arr);
1953 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::LZ4HDF5);
1954 assert_ne!(compressed.data.as_u8_slice(), original.as_slice());
1955
1956 let decompressed = decompress_lz4hdf5(&compressed).unwrap();
1957 assert!(decompressed.codec.is_none());
1958 assert_eq!(decompressed.data.data_type(), NDDataType::UInt8);
1959 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1960 }
1961
1962 #[test]
1963 fn test_lz4hdf5_roundtrip_u16() {
1964 let mut arr = NDArray::new(
1965 vec![NDDimension::new(80), NDDimension::new(40)],
1966 NDDataType::UInt16,
1967 );
1968 if let NDDataBuffer::U16(ref mut v) = arr.data {
1969 for (i, x) in v.iter_mut().enumerate() {
1970 *x = (i * 37 % 65521) as u16;
1971 }
1972 }
1973 let original = arr.data.as_u8_slice().to_vec();
1974
1975 let compressed = compress_lz4hdf5(&arr);
1976 assert_eq!(
1977 compressed.codec.as_ref().unwrap().original_data_type,
1978 NDDataType::UInt16
1979 );
1980
1981 let decompressed = decompress_lz4hdf5(&compressed).unwrap();
1982 assert_eq!(decompressed.data.data_type(), NDDataType::UInt16);
1983 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1984 }
1985
1986 #[test]
1987 fn test_lz4hdf5_roundtrip_f64_with_negatives() {
1988 let mut arr = NDArray::new(vec![NDDimension::new(97)], NDDataType::Float64);
1989 if let NDDataBuffer::F64(ref mut v) = arr.data {
1990 for (i, x) in v.iter_mut().enumerate() {
1991 *x = (i as f64 - 48.0) * 1.75;
1992 }
1993 }
1994 let original = arr.data.as_u8_slice().to_vec();
1995
1996 let compressed = compress_lz4hdf5(&arr);
1997 let decompressed = decompress_lz4hdf5(&compressed).unwrap();
1998 assert_eq!(decompressed.data.data_type(), NDDataType::Float64);
1999 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
2000 }
2001
2002 #[test]
2003 fn test_lz4hdf5_multi_block_roundtrip() {
2004 let block = LZ4HDF5_DEFAULT_BLOCK_SIZE;
2007 let count = block * 2 + block / 3 + 7; let mut arr = NDArray::new(vec![NDDimension::new(count)], NDDataType::UInt8);
2009 if let NDDataBuffer::U8(ref mut v) = arr.data {
2010 for (i, x) in v.iter_mut().enumerate() {
2011 *x = (i.wrapping_mul(2_654_435_761) % 251) as u8;
2012 }
2013 }
2014 let original = arr.data.as_u8_slice().to_vec();
2015
2016 let compressed = compress_lz4hdf5(&arr);
2017 let decompressed = decompress_lz4hdf5(&compressed).unwrap();
2018 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
2019 }
2020
2021 #[test]
2022 fn test_lz4hdf5_compresses_repetitive_data() {
2023 let arr = NDArray::new(
2024 vec![NDDimension::new(256), NDDimension::new(256)],
2025 NDDataType::UInt16,
2026 );
2027 let original_size = arr.data.as_u8_slice().len();
2028 let compressed = compress_lz4hdf5(&arr);
2029 let compressed_size = compressed.codec.as_ref().unwrap().compressed_size;
2030 assert!(
2031 compressed_size < original_size,
2032 "lz4hdf5 compressed ({compressed_size}) should be < original ({original_size})"
2033 );
2034 }
2035
2036 #[test]
2037 fn test_lz4hdf5_via_processor() {
2038 let mut arr = NDArray::new(
2039 vec![NDDimension::new(48), NDDimension::new(48)],
2040 NDDataType::UInt16,
2041 );
2042 if let NDDataBuffer::U16(ref mut v) = arr.data {
2043 for (i, x) in v.iter_mut().enumerate() {
2044 *x = (i * 7) as u16;
2045 }
2046 }
2047 let original = arr.data.as_u8_slice().to_vec();
2048 let pool = NDArrayPool::new(10_000_000);
2049
2050 let mut comp = CodecProcessor::new(CodecMode::Compress {
2051 codec: CodecName::LZ4HDF5,
2052 quality: 0,
2053 });
2054 let compressed = comp.process_array(&arr, &pool);
2055 let compressed_arr = &compressed.output_arrays[0];
2056 assert_eq!(
2057 compressed_arr.codec.as_ref().unwrap().name,
2058 CodecName::LZ4HDF5
2059 );
2060
2061 let mut decomp = CodecProcessor::new(CodecMode::Decompress);
2062 let result = decomp.process_array(compressed_arr, &pool);
2063 assert_eq!(
2064 result.output_arrays[0].data.as_u8_slice(),
2065 original.as_slice()
2066 );
2067 }
2068
2069 #[test]
2072 fn test_compressor_ordinal_mapping() {
2073 use ad_core_rs::plugin::runtime::{ParamChangeValue, PluginParamSnapshot};
2077
2078 let cases = [
2079 (0i32, CodecName::None),
2080 (1, CodecName::JPEG),
2081 (2, CodecName::Blosc),
2082 (3, CodecName::LZ4),
2083 (4, CodecName::BSLZ4),
2084 (5, CodecName::Zlib),
2085 (6, CodecName::LZ4HDF5),
2086 ];
2087
2088 for (ordinal, expected) in cases {
2089 let mut proc = CodecProcessor::new(CodecMode::Compress {
2090 codec: CodecName::LZ4,
2091 quality: 85,
2092 });
2093 proc.params.compressor = Some(0);
2096 let snapshot = PluginParamSnapshot {
2097 enable_callbacks: true,
2098 reason: 0,
2099 addr: 0,
2100 value: ParamChangeValue::Int32(ordinal),
2101 };
2102 proc.on_param_change(0, &snapshot);
2103 match proc.mode {
2104 CodecMode::Compress { codec, .. } => assert_eq!(
2105 codec, expected,
2106 "ordinal {ordinal} should select {expected:?}"
2107 ),
2108 other => panic!("expected Compress mode, got {other:?}"),
2109 }
2110 }
2111 }
2112
2113 #[test]
2116 fn test_decompress_wrong_codec() {
2117 let arr = make_u8_array(4, 4);
2118 assert!(decompress_lz4(&arr).is_none());
2119 assert!(decompress_jpeg(&arr).is_none());
2120 assert!(decompress_zlib(&arr).is_none());
2121 assert!(decompress_lz4hdf5(&arr).is_none());
2122 }
2123
2124 #[test]
2127 fn test_processor_lz4_compress() {
2128 let pool = NDArrayPool::new(1_000_000);
2129 let mut proc = CodecProcessor::new(CodecMode::Compress {
2130 codec: CodecName::LZ4,
2131 quality: 0,
2132 });
2133 let arr = make_u8_array(32, 32);
2134 let result = proc.process_array(&arr, &pool);
2135 assert_eq!(result.output_arrays.len(), 1);
2136 assert_eq!(
2137 result.output_arrays[0].codec.as_ref().unwrap().name,
2138 CodecName::LZ4
2139 );
2140 assert!(proc.compression_ratio() >= 1.0);
2141 }
2142
2143 #[test]
2144 fn test_processor_jpeg_compress() {
2145 let pool = NDArrayPool::new(1_000_000);
2146 let mut proc = CodecProcessor::new(CodecMode::Compress {
2147 codec: CodecName::JPEG,
2148 quality: 80,
2149 });
2150 let arr = make_u8_array(16, 16);
2151 let result = proc.process_array(&arr, &pool);
2152 assert_eq!(result.output_arrays.len(), 1);
2153 assert_eq!(
2154 result.output_arrays[0].codec.as_ref().unwrap().name,
2155 CodecName::JPEG
2156 );
2157 }
2158
2159 #[test]
2160 fn test_processor_decompress_auto_lz4() {
2161 let pool = NDArrayPool::new(1_000_000);
2162 let arr = make_u8_array(16, 16);
2163 let compressed = compress_lz4(&arr);
2164
2165 let mut proc = CodecProcessor::new(CodecMode::Decompress);
2166 let result = proc.process_array(&compressed, &pool);
2167 assert_eq!(result.output_arrays.len(), 1);
2168 assert!(result.output_arrays[0].codec.is_none());
2169 assert_eq!(
2170 result.output_arrays[0].data.as_u8_slice(),
2171 arr.data.as_u8_slice()
2172 );
2173 assert!(proc.compression_ratio() > 0.0);
2174 }
2175
2176 #[test]
2177 fn test_processor_decompress_auto_jpeg() {
2178 let pool = NDArrayPool::new(1_000_000);
2179 let arr = make_u8_array(16, 16);
2180 let compressed = compress_jpeg(&arr, 90).unwrap();
2181
2182 let mut proc = CodecProcessor::new(CodecMode::Decompress);
2183 let result = proc.process_array(&compressed, &pool);
2184 assert_eq!(result.output_arrays.len(), 1);
2185 assert!(result.output_arrays[0].codec.is_none());
2186 }
2187
2188 #[test]
2189 fn test_processor_decompress_no_codec() {
2190 let pool = NDArrayPool::new(1_000_000);
2191 let arr = make_u8_array(8, 8);
2192 let mut proc = CodecProcessor::new(CodecMode::Decompress);
2193 let result = proc.process_array(&arr, &pool);
2194 assert_eq!(result.output_arrays.len(), 1);
2196 assert_eq!(proc.compression_ratio(), 1.0);
2197 }
2198
2199 #[test]
2200 fn test_processor_compression_ratio() {
2201 let pool = NDArrayPool::new(1_000_000);
2202 let mut arr = NDArray::new(
2204 vec![NDDimension::new(128), NDDimension::new(128)],
2205 NDDataType::UInt8,
2206 );
2207 if let NDDataBuffer::U8(ref mut v) = arr.data {
2208 for x in v.iter_mut() {
2209 *x = 0;
2210 }
2211 }
2212
2213 let mut proc = CodecProcessor::new(CodecMode::Compress {
2214 codec: CodecName::LZ4,
2215 quality: 0,
2216 });
2217 let _ = proc.process_array(&arr, &pool);
2218 let ratio = proc.compression_ratio();
2219 assert!(
2220 ratio > 2.0,
2221 "all-zeros 128x128 should compress at least 2x, got {}",
2222 ratio,
2223 );
2224 }
2225
2226 #[test]
2227 fn test_processor_plugin_type() {
2228 let proc = CodecProcessor::new(CodecMode::Decompress);
2229 assert_eq!(proc.plugin_type(), "NDPluginCodec");
2230 }
2231
2232 #[test]
2235 fn test_buffer_from_bytes_u8() {
2236 let data = vec![1u8, 2, 3, 4];
2237 let buf = buffer_from_bytes(&data, NDDataType::UInt8).unwrap();
2238 assert_eq!(buf.data_type(), NDDataType::UInt8);
2239 assert_eq!(buf.len(), 4);
2240 assert_eq!(buf.as_u8_slice(), &[1, 2, 3, 4]);
2241 }
2242
2243 #[test]
2244 fn test_buffer_from_bytes_u16() {
2245 let original = vec![1000u16, 2000, 3000];
2246 let bytes: Vec<u8> = original.iter().flat_map(|v| v.to_ne_bytes()).collect();
2247 let buf = buffer_from_bytes(&bytes, NDDataType::UInt16).unwrap();
2248 assert_eq!(buf.data_type(), NDDataType::UInt16);
2249 assert_eq!(buf.len(), 3);
2250 if let NDDataBuffer::U16(v) = buf {
2251 assert_eq!(v, original);
2252 } else {
2253 panic!("wrong buffer type");
2254 }
2255 }
2256
2257 #[test]
2258 fn test_buffer_from_bytes_bad_alignment() {
2259 let data = vec![0u8; 3];
2261 assert!(buffer_from_bytes(&data, NDDataType::UInt16).is_none());
2262 }
2263
2264 #[test]
2265 fn test_buffer_from_bytes_f64_roundtrip() {
2266 let original = vec![1.5f64, -2.7, 3.14159];
2267 let bytes: Vec<u8> = original.iter().flat_map(|v| v.to_ne_bytes()).collect();
2268 let buf = buffer_from_bytes(&bytes, NDDataType::Float64).unwrap();
2269 if let NDDataBuffer::F64(v) = buf {
2270 assert_eq!(v, original);
2271 } else {
2272 panic!("wrong buffer type");
2273 }
2274 }
2275}