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 std::thread::sleep(std::time::Duration::from_millis(10));
1474
1475 let rt = tokio::runtime::Builder::new_current_thread()
1476 .enable_all()
1477 .build()
1478 .unwrap();
1479 rt.block_on(handle.array_sender().publish(Arc::new(compressed)));
1480 rt.block_on(handle.array_sender().publish(Arc::new(sentinel)));
1481
1482 let first = ds_rx.blocking_recv().expect("downstream array");
1483 assert_eq!(
1484 first.unique_id, 1,
1485 "compressed input must be decompressed and delivered, not dropped"
1486 );
1487 assert!(
1488 first.codec.is_none(),
1489 "delivered array must be decompressed (codec cleared)"
1490 );
1491 assert_eq!(first.data.as_u8_slice(), original_data.as_slice());
1492 assert_eq!(
1493 dropped.load(Ordering::Acquire),
1494 0,
1495 "compression-aware Codec must not count its compressed input as dropped"
1496 );
1497 }
1498
1499 #[test]
1500 fn test_lz4_roundtrip_u16() {
1501 let mut arr = NDArray::new(
1502 vec![NDDimension::new(8), NDDimension::new(8)],
1503 NDDataType::UInt16,
1504 );
1505 if let NDDataBuffer::U16(ref mut v) = arr.data {
1506 for i in 0..v.len() {
1507 v[i] = (i * 100) as u16;
1508 }
1509 }
1510 let original_bytes = arr.data.as_u8_slice().to_vec();
1511
1512 let compressed = compress_lz4(&arr);
1513 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::LZ4);
1514 assert_eq!(
1516 compressed.codec.as_ref().unwrap().original_data_type,
1517 NDDataType::UInt16
1518 );
1519 assert!(
1521 compressed
1522 .attributes
1523 .get("CODEC_ORIGINAL_DATA_TYPE")
1524 .is_none()
1525 );
1526
1527 let decompressed = decompress_lz4(&compressed).unwrap();
1528 assert!(decompressed.codec.is_none());
1529 assert_eq!(decompressed.data.data_type(), NDDataType::UInt16);
1530 assert_eq!(decompressed.data.as_u8_slice(), original_bytes.as_slice());
1531 }
1532
1533 #[test]
1534 fn test_lz4_roundtrip_f64() {
1535 let mut arr = NDArray::new(vec![NDDimension::new(16)], NDDataType::Float64);
1536 if let NDDataBuffer::F64(ref mut v) = arr.data {
1537 for i in 0..v.len() {
1538 v[i] = i as f64 * 1.5;
1539 }
1540 }
1541 let original_bytes = arr.data.as_u8_slice().to_vec();
1542
1543 let compressed = compress_lz4(&arr);
1544 let decompressed = decompress_lz4(&compressed).unwrap();
1545 assert_eq!(decompressed.data.data_type(), NDDataType::Float64);
1546 assert_eq!(decompressed.data.as_u8_slice(), original_bytes.as_slice());
1547 }
1548
1549 #[test]
1550 fn test_lz4_compresses_repetitive_data() {
1551 let mut arr = NDArray::new(
1553 vec![NDDimension::new(256), NDDimension::new(256)],
1554 NDDataType::UInt8,
1555 );
1556 if let NDDataBuffer::U8(ref mut v) = arr.data {
1558 for x in v.iter_mut() {
1559 *x = 0;
1560 }
1561 }
1562 let original_size = arr.data.as_u8_slice().len();
1563
1564 let compressed = compress_lz4(&arr);
1565 let compressed_size = compressed.codec.as_ref().unwrap().compressed_size;
1566 assert!(
1567 compressed_size < original_size,
1568 "compressed ({}) should be smaller than original ({})",
1569 compressed_size,
1570 original_size,
1571 );
1572 }
1573
1574 #[test]
1575 fn test_lz4_preserves_metadata() {
1576 let mut arr = make_u8_array(4, 4);
1577 arr.unique_id = 42;
1578
1579 let compressed = compress_lz4(&arr);
1580 assert_eq!(compressed.unique_id, 42);
1581 assert_eq!(compressed.dims.len(), 2);
1582 assert_eq!(compressed.dims[0].size, 4);
1583 assert_eq!(compressed.dims[1].size, 4);
1584 }
1585
1586 #[test]
1589 fn test_bitshuffle_block_transpose_roundtrip() {
1590 for &(n, elem_size) in &[(16usize, 4usize), (8, 2), (256, 8), (128, 1)] {
1593 let input: Vec<u8> = (0..n * elem_size).map(|i| (i * 7 + 3) as u8).collect();
1594 let shuffled = bshuf_trans_bit_elem(&input, n, elem_size);
1595 assert_eq!(shuffled.len(), input.len());
1596 let restored = bshuf_untrans_bit_elem(&shuffled, n, elem_size);
1597 assert_eq!(restored, input, "elem_size {elem_size}, n {n}");
1598 }
1599 }
1600
1601 #[test]
1602 fn test_bitshuffle_matches_c_reference_vector() {
1603 let input: Vec<u8> = (0..16u16).flat_map(|v| v.to_le_bytes()).collect();
1611 let shuffled = bshuf_trans_bit_elem(&input, 16, 2);
1612 let mut expected = vec![0u8; 32];
1613 expected[..8].copy_from_slice(&[170, 170, 204, 204, 240, 240, 0, 255]);
1614 assert_eq!(
1615 shuffled, expected,
1616 "canonical bitshuffle transpose must match the C library bytes"
1617 );
1618 }
1619
1620 #[test]
1621 fn test_bslz4_roundtrip_u8() {
1622 let mut arr = NDArray::new(
1623 vec![NDDimension::new(64), NDDimension::new(64)],
1624 NDDataType::UInt8,
1625 );
1626 if let NDDataBuffer::U8(ref mut v) = arr.data {
1627 for (i, x) in v.iter_mut().enumerate() {
1628 *x = (i % 251) as u8;
1629 }
1630 }
1631 let original = arr.data.as_u8_slice().to_vec();
1632
1633 let compressed = compress_bslz4(&arr);
1634 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::BSLZ4);
1635 assert_ne!(compressed.data.as_u8_slice(), original.as_slice());
1636
1637 let decompressed = decompress_bslz4(&compressed).unwrap();
1638 assert!(decompressed.codec.is_none());
1639 assert_eq!(decompressed.data.data_type(), NDDataType::UInt8);
1640 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1641 }
1642
1643 #[test]
1644 fn test_bslz4_roundtrip_u16() {
1645 let mut arr = NDArray::new(
1646 vec![NDDimension::new(100), NDDimension::new(20)],
1647 NDDataType::UInt16,
1648 );
1649 if let NDDataBuffer::U16(ref mut v) = arr.data {
1650 for (i, x) in v.iter_mut().enumerate() {
1651 *x = (i * 37 % 65521) as u16;
1652 }
1653 }
1654 let original = arr.data.as_u8_slice().to_vec();
1655
1656 let compressed = compress_bslz4(&arr);
1657 assert_eq!(
1658 compressed.codec.as_ref().unwrap().original_data_type,
1659 NDDataType::UInt16
1660 );
1661 let decompressed = decompress_bslz4(&compressed).unwrap();
1662 assert_eq!(decompressed.data.data_type(), NDDataType::UInt16);
1663 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1664 }
1665
1666 #[test]
1667 fn test_bslz4_roundtrip_f64_with_negatives() {
1668 let mut arr = NDArray::new(vec![NDDimension::new(73)], NDDataType::Float64);
1669 if let NDDataBuffer::F64(ref mut v) = arr.data {
1670 for (i, x) in v.iter_mut().enumerate() {
1671 *x = (i as f64 - 36.0) * 2.5;
1672 }
1673 }
1674 let original = arr.data.as_u8_slice().to_vec();
1675
1676 let compressed = compress_bslz4(&arr);
1677 let decompressed = decompress_bslz4(&compressed).unwrap();
1678 assert_eq!(decompressed.data.data_type(), NDDataType::Float64);
1679 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1680 }
1681
1682 #[test]
1683 fn test_bslz4_roundtrip_multi_block() {
1684 let elem_size = 4usize;
1687 let block = bshuf_default_block_size(elem_size);
1688 let count = block * 2 + block / 2 + 3;
1690 let mut arr = NDArray::new(vec![NDDimension::new(count)], NDDataType::Int32);
1691 if let NDDataBuffer::I32(ref mut v) = arr.data {
1692 for (i, x) in v.iter_mut().enumerate() {
1693 *x = (i as i32).wrapping_mul(2_654_435_761u32 as i32);
1694 }
1695 }
1696 let original = arr.data.as_u8_slice().to_vec();
1697
1698 let compressed = compress_bslz4(&arr);
1699 let decompressed = decompress_bslz4(&compressed).unwrap();
1700 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1701 }
1702
1703 #[test]
1704 fn test_bslz4_compresses_repetitive_data() {
1705 let arr = NDArray::new(
1707 vec![NDDimension::new(256), NDDimension::new(256)],
1708 NDDataType::UInt16,
1709 );
1710 let original_size = arr.data.as_u8_slice().len();
1711 let compressed = compress_bslz4(&arr);
1712 let compressed_size = compressed.codec.as_ref().unwrap().compressed_size;
1713 assert!(
1714 compressed_size < original_size,
1715 "bslz4 compressed ({compressed_size}) should be < original ({original_size})"
1716 );
1717 }
1718
1719 #[test]
1720 fn test_bslz4_via_processor() {
1721 let mut arr = NDArray::new(
1723 vec![NDDimension::new(32), NDDimension::new(32)],
1724 NDDataType::UInt16,
1725 );
1726 if let NDDataBuffer::U16(ref mut v) = arr.data {
1727 for (i, x) in v.iter_mut().enumerate() {
1728 *x = (i * 11) as u16;
1729 }
1730 }
1731 let original = arr.data.as_u8_slice().to_vec();
1732 let pool = NDArrayPool::new(10_000_000);
1733
1734 let mut comp = CodecProcessor::new(CodecMode::Compress {
1735 codec: CodecName::BSLZ4,
1736 quality: 0,
1737 });
1738 let compressed = comp.process_array(&arr, &pool);
1739 let compressed_arr = &compressed.output_arrays[0];
1740 assert_eq!(
1741 compressed_arr.codec.as_ref().unwrap().name,
1742 CodecName::BSLZ4
1743 );
1744
1745 let mut decomp = CodecProcessor::new(CodecMode::Decompress);
1746 let result = decomp.process_array(compressed_arr, &pool);
1747 assert_eq!(
1748 result.output_arrays[0].data.as_u8_slice(),
1749 original.as_slice()
1750 );
1751 }
1752
1753 #[test]
1756 fn test_jpeg_compress_mono() {
1757 let arr = make_u8_array(16, 16);
1758 let compressed = compress_jpeg(&arr, 90).unwrap();
1759 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::JPEG);
1760 let data = compressed.data.as_u8_slice();
1762 assert_eq!(&data[0..2], &[0xFF, 0xD8]);
1763 }
1764
1765 #[test]
1766 fn test_jpeg_compress_rgb() {
1767 let arr = make_rgb_array(16, 16);
1768 let compressed = compress_jpeg(&arr, 90).unwrap();
1769 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::JPEG);
1770 let data = compressed.data.as_u8_slice();
1771 assert_eq!(&data[0..2], &[0xFF, 0xD8]);
1772 }
1773
1774 #[test]
1775 fn test_jpeg_roundtrip_mono() {
1776 let arr = make_u8_array(16, 16);
1777 let compressed = compress_jpeg(&arr, 100).unwrap();
1778 let decompressed = decompress_jpeg(&compressed).unwrap();
1779 assert!(decompressed.codec.is_none());
1780 assert_eq!(decompressed.dims.len(), 2);
1781 assert_eq!(decompressed.dims[0].size, 16); assert_eq!(decompressed.dims[1].size, 16); assert_eq!(decompressed.data.data_type(), NDDataType::UInt8);
1784 assert_eq!(decompressed.data.len(), 16 * 16);
1786 }
1787
1788 #[test]
1789 fn test_jpeg_roundtrip_rgb() {
1790 let arr = make_rgb_array(16, 16);
1791 let compressed = compress_jpeg(&arr, 100).unwrap();
1792 let decompressed = decompress_jpeg(&compressed).unwrap();
1793 assert!(decompressed.codec.is_none());
1794 assert_eq!(decompressed.dims.len(), 3);
1795 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);
1799 }
1800
1801 #[test]
1802 fn test_jpeg_rejects_non_u8() {
1803 let arr = NDArray::new(
1804 vec![NDDimension::new(8), NDDimension::new(8)],
1805 NDDataType::UInt16,
1806 );
1807 assert!(compress_jpeg(&arr, 90).is_none());
1808 }
1809
1810 #[test]
1811 fn test_jpeg_rejects_1d() {
1812 let arr = NDArray::new(vec![NDDimension::new(64)], NDDataType::UInt8);
1813 assert!(compress_jpeg(&arr, 90).is_none());
1814 }
1815
1816 #[test]
1817 fn test_jpeg_quality_affects_size() {
1818 let arr = make_u8_array(64, 64);
1819 let high = compress_jpeg(&arr, 95).unwrap();
1820 let low = compress_jpeg(&arr, 10).unwrap();
1821 let high_size = high.codec.as_ref().unwrap().compressed_size;
1822 let low_size = low.codec.as_ref().unwrap().compressed_size;
1823 assert!(
1824 high_size > low_size,
1825 "high quality ({}) should produce larger output than low quality ({})",
1826 high_size,
1827 low_size,
1828 );
1829 }
1830
1831 #[test]
1834 fn test_zlib_roundtrip_u8() {
1835 let arr = make_u8_array(8, 8);
1836 let original = arr.data.as_u8_slice().to_vec();
1837
1838 let compressed = compress_zlib(&arr);
1839 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::Zlib);
1840 assert_ne!(compressed.data.as_u8_slice(), original.as_slice());
1841
1842 let decompressed = decompress_zlib(&compressed).unwrap();
1843 assert!(decompressed.codec.is_none());
1844 assert_eq!(decompressed.data.data_type(), NDDataType::UInt8);
1845 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1846 }
1847
1848 #[test]
1849 fn test_zlib_roundtrip_u16() {
1850 let mut arr = NDArray::new(
1851 vec![NDDimension::new(16), NDDimension::new(16)],
1852 NDDataType::UInt16,
1853 );
1854 if let NDDataBuffer::U16(ref mut v) = arr.data {
1855 for (i, x) in v.iter_mut().enumerate() {
1856 *x = (i * 257 % 65521) as u16;
1857 }
1858 }
1859 let original = arr.data.as_u8_slice().to_vec();
1860
1861 let compressed = compress_zlib(&arr);
1862 assert_eq!(
1863 compressed.codec.as_ref().unwrap().original_data_type,
1864 NDDataType::UInt16
1865 );
1866
1867 let decompressed = decompress_zlib(&compressed).unwrap();
1868 assert_eq!(decompressed.data.data_type(), NDDataType::UInt16);
1869 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1870 }
1871
1872 #[test]
1873 fn test_zlib_roundtrip_f64_with_negatives() {
1874 let mut arr = NDArray::new(vec![NDDimension::new(64)], NDDataType::Float64);
1875 if let NDDataBuffer::F64(ref mut v) = arr.data {
1876 for (i, x) in v.iter_mut().enumerate() {
1877 *x = (i as f64 - 32.0) * 3.25;
1878 }
1879 }
1880 let original = arr.data.as_u8_slice().to_vec();
1881
1882 let compressed = compress_zlib(&arr);
1883 let decompressed = decompress_zlib(&compressed).unwrap();
1884 assert_eq!(decompressed.data.data_type(), NDDataType::Float64);
1885 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1886 }
1887
1888 #[test]
1889 fn test_zlib_compresses_repetitive_data() {
1890 let arr = NDArray::new(
1891 vec![NDDimension::new(256), NDDimension::new(256)],
1892 NDDataType::UInt8,
1893 );
1894 let original_size = arr.data.as_u8_slice().len();
1895 let compressed = compress_zlib(&arr);
1896 let compressed_size = compressed.codec.as_ref().unwrap().compressed_size;
1897 assert!(
1898 compressed_size < original_size,
1899 "zlib compressed ({compressed_size}) should be < original ({original_size})"
1900 );
1901 }
1902
1903 #[test]
1904 fn test_zlib_via_processor() {
1905 let mut arr = NDArray::new(
1906 vec![NDDimension::new(32), NDDimension::new(32)],
1907 NDDataType::UInt16,
1908 );
1909 if let NDDataBuffer::U16(ref mut v) = arr.data {
1910 for (i, x) in v.iter_mut().enumerate() {
1911 *x = (i * 13) as u16;
1912 }
1913 }
1914 let original = arr.data.as_u8_slice().to_vec();
1915 let pool = NDArrayPool::new(10_000_000);
1916
1917 let mut comp = CodecProcessor::new(CodecMode::Compress {
1918 codec: CodecName::Zlib,
1919 quality: 0,
1920 });
1921 let compressed = comp.process_array(&arr, &pool);
1922 let compressed_arr = &compressed.output_arrays[0];
1923 assert_eq!(compressed_arr.codec.as_ref().unwrap().name, CodecName::Zlib);
1924
1925 let mut decomp = CodecProcessor::new(CodecMode::Decompress);
1926 let result = decomp.process_array(compressed_arr, &pool);
1927 assert_eq!(
1928 result.output_arrays[0].data.as_u8_slice(),
1929 original.as_slice()
1930 );
1931 }
1932
1933 #[test]
1936 fn test_lz4hdf5_roundtrip_u8() {
1937 let mut arr = NDArray::new(
1938 vec![NDDimension::new(64), NDDimension::new(64)],
1939 NDDataType::UInt8,
1940 );
1941 if let NDDataBuffer::U8(ref mut v) = arr.data {
1942 for (i, x) in v.iter_mut().enumerate() {
1943 *x = (i % 251) as u8;
1944 }
1945 }
1946 let original = arr.data.as_u8_slice().to_vec();
1947
1948 let compressed = compress_lz4hdf5(&arr);
1949 assert_eq!(compressed.codec.as_ref().unwrap().name, CodecName::LZ4HDF5);
1950 assert_ne!(compressed.data.as_u8_slice(), original.as_slice());
1951
1952 let decompressed = decompress_lz4hdf5(&compressed).unwrap();
1953 assert!(decompressed.codec.is_none());
1954 assert_eq!(decompressed.data.data_type(), NDDataType::UInt8);
1955 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1956 }
1957
1958 #[test]
1959 fn test_lz4hdf5_roundtrip_u16() {
1960 let mut arr = NDArray::new(
1961 vec![NDDimension::new(80), NDDimension::new(40)],
1962 NDDataType::UInt16,
1963 );
1964 if let NDDataBuffer::U16(ref mut v) = arr.data {
1965 for (i, x) in v.iter_mut().enumerate() {
1966 *x = (i * 37 % 65521) as u16;
1967 }
1968 }
1969 let original = arr.data.as_u8_slice().to_vec();
1970
1971 let compressed = compress_lz4hdf5(&arr);
1972 assert_eq!(
1973 compressed.codec.as_ref().unwrap().original_data_type,
1974 NDDataType::UInt16
1975 );
1976
1977 let decompressed = decompress_lz4hdf5(&compressed).unwrap();
1978 assert_eq!(decompressed.data.data_type(), NDDataType::UInt16);
1979 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1980 }
1981
1982 #[test]
1983 fn test_lz4hdf5_roundtrip_f64_with_negatives() {
1984 let mut arr = NDArray::new(vec![NDDimension::new(97)], NDDataType::Float64);
1985 if let NDDataBuffer::F64(ref mut v) = arr.data {
1986 for (i, x) in v.iter_mut().enumerate() {
1987 *x = (i as f64 - 48.0) * 1.75;
1988 }
1989 }
1990 let original = arr.data.as_u8_slice().to_vec();
1991
1992 let compressed = compress_lz4hdf5(&arr);
1993 let decompressed = decompress_lz4hdf5(&compressed).unwrap();
1994 assert_eq!(decompressed.data.data_type(), NDDataType::Float64);
1995 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
1996 }
1997
1998 #[test]
1999 fn test_lz4hdf5_multi_block_roundtrip() {
2000 let block = LZ4HDF5_DEFAULT_BLOCK_SIZE;
2003 let count = block * 2 + block / 3 + 7; let mut arr = NDArray::new(vec![NDDimension::new(count)], NDDataType::UInt8);
2005 if let NDDataBuffer::U8(ref mut v) = arr.data {
2006 for (i, x) in v.iter_mut().enumerate() {
2007 *x = (i.wrapping_mul(2_654_435_761) % 251) as u8;
2008 }
2009 }
2010 let original = arr.data.as_u8_slice().to_vec();
2011
2012 let compressed = compress_lz4hdf5(&arr);
2013 let decompressed = decompress_lz4hdf5(&compressed).unwrap();
2014 assert_eq!(decompressed.data.as_u8_slice(), original.as_slice());
2015 }
2016
2017 #[test]
2018 fn test_lz4hdf5_compresses_repetitive_data() {
2019 let arr = NDArray::new(
2020 vec![NDDimension::new(256), NDDimension::new(256)],
2021 NDDataType::UInt16,
2022 );
2023 let original_size = arr.data.as_u8_slice().len();
2024 let compressed = compress_lz4hdf5(&arr);
2025 let compressed_size = compressed.codec.as_ref().unwrap().compressed_size;
2026 assert!(
2027 compressed_size < original_size,
2028 "lz4hdf5 compressed ({compressed_size}) should be < original ({original_size})"
2029 );
2030 }
2031
2032 #[test]
2033 fn test_lz4hdf5_via_processor() {
2034 let mut arr = NDArray::new(
2035 vec![NDDimension::new(48), NDDimension::new(48)],
2036 NDDataType::UInt16,
2037 );
2038 if let NDDataBuffer::U16(ref mut v) = arr.data {
2039 for (i, x) in v.iter_mut().enumerate() {
2040 *x = (i * 7) as u16;
2041 }
2042 }
2043 let original = arr.data.as_u8_slice().to_vec();
2044 let pool = NDArrayPool::new(10_000_000);
2045
2046 let mut comp = CodecProcessor::new(CodecMode::Compress {
2047 codec: CodecName::LZ4HDF5,
2048 quality: 0,
2049 });
2050 let compressed = comp.process_array(&arr, &pool);
2051 let compressed_arr = &compressed.output_arrays[0];
2052 assert_eq!(
2053 compressed_arr.codec.as_ref().unwrap().name,
2054 CodecName::LZ4HDF5
2055 );
2056
2057 let mut decomp = CodecProcessor::new(CodecMode::Decompress);
2058 let result = decomp.process_array(compressed_arr, &pool);
2059 assert_eq!(
2060 result.output_arrays[0].data.as_u8_slice(),
2061 original.as_slice()
2062 );
2063 }
2064
2065 #[test]
2068 fn test_compressor_ordinal_mapping() {
2069 use ad_core_rs::plugin::runtime::{ParamChangeValue, PluginParamSnapshot};
2073
2074 let cases = [
2075 (0i32, CodecName::None),
2076 (1, CodecName::JPEG),
2077 (2, CodecName::Blosc),
2078 (3, CodecName::LZ4),
2079 (4, CodecName::BSLZ4),
2080 (5, CodecName::Zlib),
2081 (6, CodecName::LZ4HDF5),
2082 ];
2083
2084 for (ordinal, expected) in cases {
2085 let mut proc = CodecProcessor::new(CodecMode::Compress {
2086 codec: CodecName::LZ4,
2087 quality: 85,
2088 });
2089 proc.params.compressor = Some(0);
2092 let snapshot = PluginParamSnapshot {
2093 enable_callbacks: true,
2094 reason: 0,
2095 addr: 0,
2096 value: ParamChangeValue::Int32(ordinal),
2097 };
2098 proc.on_param_change(0, &snapshot);
2099 match proc.mode {
2100 CodecMode::Compress { codec, .. } => assert_eq!(
2101 codec, expected,
2102 "ordinal {ordinal} should select {expected:?}"
2103 ),
2104 other => panic!("expected Compress mode, got {other:?}"),
2105 }
2106 }
2107 }
2108
2109 #[test]
2112 fn test_decompress_wrong_codec() {
2113 let arr = make_u8_array(4, 4);
2114 assert!(decompress_lz4(&arr).is_none());
2115 assert!(decompress_jpeg(&arr).is_none());
2116 assert!(decompress_zlib(&arr).is_none());
2117 assert!(decompress_lz4hdf5(&arr).is_none());
2118 }
2119
2120 #[test]
2123 fn test_processor_lz4_compress() {
2124 let pool = NDArrayPool::new(1_000_000);
2125 let mut proc = CodecProcessor::new(CodecMode::Compress {
2126 codec: CodecName::LZ4,
2127 quality: 0,
2128 });
2129 let arr = make_u8_array(32, 32);
2130 let result = proc.process_array(&arr, &pool);
2131 assert_eq!(result.output_arrays.len(), 1);
2132 assert_eq!(
2133 result.output_arrays[0].codec.as_ref().unwrap().name,
2134 CodecName::LZ4
2135 );
2136 assert!(proc.compression_ratio() >= 1.0);
2137 }
2138
2139 #[test]
2140 fn test_processor_jpeg_compress() {
2141 let pool = NDArrayPool::new(1_000_000);
2142 let mut proc = CodecProcessor::new(CodecMode::Compress {
2143 codec: CodecName::JPEG,
2144 quality: 80,
2145 });
2146 let arr = make_u8_array(16, 16);
2147 let result = proc.process_array(&arr, &pool);
2148 assert_eq!(result.output_arrays.len(), 1);
2149 assert_eq!(
2150 result.output_arrays[0].codec.as_ref().unwrap().name,
2151 CodecName::JPEG
2152 );
2153 }
2154
2155 #[test]
2156 fn test_processor_decompress_auto_lz4() {
2157 let pool = NDArrayPool::new(1_000_000);
2158 let arr = make_u8_array(16, 16);
2159 let compressed = compress_lz4(&arr);
2160
2161 let mut proc = CodecProcessor::new(CodecMode::Decompress);
2162 let result = proc.process_array(&compressed, &pool);
2163 assert_eq!(result.output_arrays.len(), 1);
2164 assert!(result.output_arrays[0].codec.is_none());
2165 assert_eq!(
2166 result.output_arrays[0].data.as_u8_slice(),
2167 arr.data.as_u8_slice()
2168 );
2169 assert!(proc.compression_ratio() > 0.0);
2170 }
2171
2172 #[test]
2173 fn test_processor_decompress_auto_jpeg() {
2174 let pool = NDArrayPool::new(1_000_000);
2175 let arr = make_u8_array(16, 16);
2176 let compressed = compress_jpeg(&arr, 90).unwrap();
2177
2178 let mut proc = CodecProcessor::new(CodecMode::Decompress);
2179 let result = proc.process_array(&compressed, &pool);
2180 assert_eq!(result.output_arrays.len(), 1);
2181 assert!(result.output_arrays[0].codec.is_none());
2182 }
2183
2184 #[test]
2185 fn test_processor_decompress_no_codec() {
2186 let pool = NDArrayPool::new(1_000_000);
2187 let arr = make_u8_array(8, 8);
2188 let mut proc = CodecProcessor::new(CodecMode::Decompress);
2189 let result = proc.process_array(&arr, &pool);
2190 assert_eq!(result.output_arrays.len(), 1);
2192 assert_eq!(proc.compression_ratio(), 1.0);
2193 }
2194
2195 #[test]
2196 fn test_processor_compression_ratio() {
2197 let pool = NDArrayPool::new(1_000_000);
2198 let mut arr = NDArray::new(
2200 vec![NDDimension::new(128), NDDimension::new(128)],
2201 NDDataType::UInt8,
2202 );
2203 if let NDDataBuffer::U8(ref mut v) = arr.data {
2204 for x in v.iter_mut() {
2205 *x = 0;
2206 }
2207 }
2208
2209 let mut proc = CodecProcessor::new(CodecMode::Compress {
2210 codec: CodecName::LZ4,
2211 quality: 0,
2212 });
2213 let _ = proc.process_array(&arr, &pool);
2214 let ratio = proc.compression_ratio();
2215 assert!(
2216 ratio > 2.0,
2217 "all-zeros 128x128 should compress at least 2x, got {}",
2218 ratio,
2219 );
2220 }
2221
2222 #[test]
2223 fn test_processor_plugin_type() {
2224 let proc = CodecProcessor::new(CodecMode::Decompress);
2225 assert_eq!(proc.plugin_type(), "NDPluginCodec");
2226 }
2227
2228 #[test]
2231 fn test_buffer_from_bytes_u8() {
2232 let data = vec![1u8, 2, 3, 4];
2233 let buf = buffer_from_bytes(&data, NDDataType::UInt8).unwrap();
2234 assert_eq!(buf.data_type(), NDDataType::UInt8);
2235 assert_eq!(buf.len(), 4);
2236 assert_eq!(buf.as_u8_slice(), &[1, 2, 3, 4]);
2237 }
2238
2239 #[test]
2240 fn test_buffer_from_bytes_u16() {
2241 let original = vec![1000u16, 2000, 3000];
2242 let bytes: Vec<u8> = original.iter().flat_map(|v| v.to_ne_bytes()).collect();
2243 let buf = buffer_from_bytes(&bytes, NDDataType::UInt16).unwrap();
2244 assert_eq!(buf.data_type(), NDDataType::UInt16);
2245 assert_eq!(buf.len(), 3);
2246 if let NDDataBuffer::U16(v) = buf {
2247 assert_eq!(v, original);
2248 } else {
2249 panic!("wrong buffer type");
2250 }
2251 }
2252
2253 #[test]
2254 fn test_buffer_from_bytes_bad_alignment() {
2255 let data = vec![0u8; 3];
2257 assert!(buffer_from_bytes(&data, NDDataType::UInt16).is_none());
2258 }
2259
2260 #[test]
2261 fn test_buffer_from_bytes_f64_roundtrip() {
2262 let original = vec![1.5f64, -2.7, 3.14159];
2263 let bytes: Vec<u8> = original.iter().flat_map(|v| v.to_ne_bytes()).collect();
2264 let buf = buffer_from_bytes(&bytes, NDDataType::Float64).unwrap();
2265 if let NDDataBuffer::F64(v) = buf {
2266 assert_eq!(v, original);
2267 } else {
2268 panic!("wrong buffer type");
2269 }
2270 }
2271}