kizzasi_io/
compression.rs

1//! Signal compression and decompression utilities
2//!
3//! This module provides various compression methods optimized for sensor data,
4//! including lossless and lossy compression techniques.
5
6use crate::error::{IoError, IoResult};
7use std::collections::HashMap;
8
9/// Compression method for signal data
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum CompressionMethod {
12    /// No compression
13    None,
14    /// Run-Length Encoding (lossless, good for sparse signals)
15    RLE,
16    /// Delta encoding (store differences between samples)
17    Delta,
18    /// Delta + RLE combination
19    DeltaRLE,
20    /// Quantization (lossy, reduce bit depth)
21    Quantize { bits: u8 },
22    /// Differential Pulse Code Modulation
23    DPCM { predictor_order: usize },
24}
25
26/// Compressed signal data
27#[derive(Debug, Clone)]
28pub struct CompressedSignal {
29    /// Compression method used
30    pub method: CompressionMethod,
31    /// Compressed data bytes
32    pub data: Vec<u8>,
33    /// Original length
34    pub original_length: usize,
35    /// Metadata for decompression
36    pub metadata: CompressionMetadata,
37}
38
39/// Metadata needed for decompression
40#[derive(Debug, Clone)]
41pub struct CompressionMetadata {
42    /// Original sample rate
43    pub sample_rate: Option<f32>,
44    /// Original min value (for quantization)
45    pub min_value: f32,
46    /// Original max value (for quantization)
47    pub max_value: f32,
48    /// First sample (for delta encoding)
49    pub first_sample: f32,
50    /// Custom metadata
51    pub custom: HashMap<String, String>,
52}
53
54impl Default for CompressionMetadata {
55    fn default() -> Self {
56        Self {
57            sample_rate: None,
58            min_value: 0.0,
59            max_value: 0.0,
60            first_sample: 0.0,
61            custom: HashMap::new(),
62        }
63    }
64}
65
66/// Signal compressor
67pub struct SignalCompressor {
68    method: CompressionMethod,
69}
70
71impl SignalCompressor {
72    /// Create new compressor with specified method
73    pub fn new(method: CompressionMethod) -> Self {
74        Self { method }
75    }
76
77    /// Compress signal data
78    pub fn compress(&self, signal: &[f32]) -> IoResult<CompressedSignal> {
79        if signal.is_empty() {
80            return Err(IoError::InvalidConfig(
81                "Cannot compress empty signal".to_string(),
82            ));
83        }
84
85        let min_value = signal.iter().copied().fold(f32::INFINITY, f32::min);
86        let max_value = signal.iter().copied().fold(f32::NEG_INFINITY, f32::max);
87        let metadata = CompressionMetadata {
88            first_sample: signal[0],
89            min_value,
90            max_value,
91            ..Default::default()
92        };
93
94        let data = match self.method {
95            CompressionMethod::None => self.compress_none(signal)?,
96            CompressionMethod::RLE => self.compress_rle(signal)?,
97            CompressionMethod::Delta => self.compress_delta(signal)?,
98            CompressionMethod::DeltaRLE => self.compress_delta_rle(signal)?,
99            CompressionMethod::Quantize { bits } => {
100                self.compress_quantize(signal, bits, metadata.min_value, metadata.max_value)?
101            }
102            CompressionMethod::DPCM { predictor_order } => {
103                self.compress_dpcm(signal, predictor_order)?
104            }
105        };
106
107        Ok(CompressedSignal {
108            method: self.method,
109            data,
110            original_length: signal.len(),
111            metadata,
112        })
113    }
114
115    /// Decompress signal data
116    pub fn decompress(&self, compressed: &CompressedSignal) -> IoResult<Vec<f32>> {
117        match compressed.method {
118            CompressionMethod::None => {
119                self.decompress_none(&compressed.data, compressed.original_length)
120            }
121            CompressionMethod::RLE => self.decompress_rle(&compressed.data),
122            CompressionMethod::Delta => {
123                self.decompress_delta(&compressed.data, compressed.metadata.first_sample)
124            }
125            CompressionMethod::DeltaRLE => {
126                self.decompress_delta_rle(&compressed.data, compressed.metadata.first_sample)
127            }
128            CompressionMethod::Quantize { bits } => self.decompress_quantize(
129                &compressed.data,
130                bits,
131                compressed.metadata.min_value,
132                compressed.metadata.max_value,
133                compressed.original_length,
134            ),
135            CompressionMethod::DPCM { predictor_order } => self.decompress_dpcm(
136                &compressed.data,
137                predictor_order,
138                compressed.metadata.first_sample,
139            ),
140        }
141    }
142
143    /// Get compression ratio
144    pub fn compression_ratio(&self, original: &[f32], compressed: &CompressedSignal) -> f32 {
145        let original_bytes = std::mem::size_of_val(original);
146        let compressed_bytes = compressed.data.len();
147        original_bytes as f32 / compressed_bytes as f32
148    }
149
150    // === Compression Methods ===
151
152    fn compress_none(&self, signal: &[f32]) -> IoResult<Vec<u8>> {
153        let mut data = Vec::with_capacity(std::mem::size_of_val(signal));
154        for &sample in signal {
155            data.extend_from_slice(&sample.to_le_bytes());
156        }
157        Ok(data)
158    }
159
160    fn compress_rle(&self, signal: &[f32]) -> IoResult<Vec<u8>> {
161        let mut data = Vec::new();
162        let mut i = 0;
163
164        while i < signal.len() {
165            let value = signal[i];
166            let mut count = 1u16;
167
168            // Count consecutive equal values
169            while i + (count as usize) < signal.len()
170                && signal[i + (count as usize)] == value
171                && count < u16::MAX
172            {
173                count += 1;
174            }
175
176            // Store count and value
177            data.extend_from_slice(&count.to_le_bytes());
178            data.extend_from_slice(&value.to_le_bytes());
179
180            i += count as usize;
181        }
182
183        Ok(data)
184    }
185
186    fn compress_delta(&self, signal: &[f32]) -> IoResult<Vec<u8>> {
187        let mut data = Vec::with_capacity(std::mem::size_of_val(signal));
188
189        for i in 1..signal.len() {
190            let delta = signal[i] - signal[i - 1];
191            data.extend_from_slice(&delta.to_le_bytes());
192        }
193
194        Ok(data)
195    }
196
197    fn compress_delta_rle(&self, signal: &[f32]) -> IoResult<Vec<u8>> {
198        // First compute deltas
199        let mut deltas = Vec::with_capacity(signal.len() - 1);
200        for i in 1..signal.len() {
201            deltas.push(signal[i] - signal[i - 1]);
202        }
203
204        // Then apply RLE to deltas
205        self.compress_rle(&deltas)
206    }
207
208    fn compress_quantize(
209        &self,
210        signal: &[f32],
211        bits: u8,
212        min_val: f32,
213        max_val: f32,
214    ) -> IoResult<Vec<u8>> {
215        if bits == 0 || bits > 16 {
216            return Err(IoError::InvalidConfig(
217                "Quantization bits must be 1-16".to_string(),
218            ));
219        }
220
221        let levels = (1u32 << bits) - 1;
222        let range = max_val - min_val;
223
224        if range.abs() < 1e-10 {
225            // All values are the same
226            return Ok(vec![0]);
227        }
228
229        let mut data = Vec::new();
230
231        for &sample in signal {
232            let normalized = ((sample - min_val) / range).clamp(0.0, 1.0);
233            let quantized = (normalized * levels as f32).round() as u16;
234
235            if bits <= 8 {
236                data.push(quantized as u8);
237            } else {
238                data.extend_from_slice(&quantized.to_le_bytes());
239            }
240        }
241
242        Ok(data)
243    }
244
245    fn compress_dpcm(&self, signal: &[f32], order: usize) -> IoResult<Vec<u8>> {
246        if order == 0 || order >= signal.len() {
247            return Err(IoError::InvalidConfig(
248                "Invalid predictor order".to_string(),
249            ));
250        }
251
252        let mut data = Vec::new();
253
254        // Store initial samples
255        for &sample in signal.iter().take(order) {
256            data.extend_from_slice(&sample.to_le_bytes());
257        }
258
259        // Predict and store residuals
260        for i in order..signal.len() {
261            let predicted = self.linear_predict(&signal[i - order..i]);
262            let residual = signal[i] - predicted;
263            data.extend_from_slice(&residual.to_le_bytes());
264        }
265
266        Ok(data)
267    }
268
269    fn linear_predict(&self, history: &[f32]) -> f32 {
270        // Simple linear prediction using average of previous samples
271        if history.is_empty() {
272            return 0.0;
273        }
274        history.iter().sum::<f32>() / history.len() as f32
275    }
276
277    // === Decompression Methods ===
278
279    fn decompress_none(&self, data: &[u8], length: usize) -> IoResult<Vec<f32>> {
280        let mut signal = Vec::with_capacity(length);
281
282        for chunk in data.chunks_exact(4) {
283            let bytes: [u8; 4] = chunk
284                .try_into()
285                .map_err(|_| IoError::ParseError("Invalid float data".to_string()))?;
286            signal.push(f32::from_le_bytes(bytes));
287        }
288
289        Ok(signal)
290    }
291
292    fn decompress_rle(&self, data: &[u8]) -> IoResult<Vec<f32>> {
293        let mut signal = Vec::new();
294        let mut i = 0;
295
296        while i + 6 <= data.len() {
297            let count_bytes: [u8; 2] = data[i..i + 2]
298                .try_into()
299                .expect("RLE decode: slice must be exactly 2 bytes");
300            let count = u16::from_le_bytes(count_bytes);
301
302            let value_bytes: [u8; 4] = data[i + 2..i + 6]
303                .try_into()
304                .expect("RLE decode: slice must be exactly 4 bytes");
305            let value = f32::from_le_bytes(value_bytes);
306
307            for _ in 0..count {
308                signal.push(value);
309            }
310
311            i += 6;
312        }
313
314        Ok(signal)
315    }
316
317    fn decompress_delta(&self, data: &[u8], first_sample: f32) -> IoResult<Vec<f32>> {
318        let mut signal = vec![first_sample];
319
320        for chunk in data.chunks_exact(4) {
321            let bytes: [u8; 4] = chunk
322                .try_into()
323                .map_err(|_| IoError::ParseError("Invalid delta data".to_string()))?;
324            let delta = f32::from_le_bytes(bytes);
325            let next = signal
326                .last()
327                .expect("Delta decode: signal must be non-empty")
328                + delta;
329            signal.push(next);
330        }
331
332        Ok(signal)
333    }
334
335    fn decompress_delta_rle(&self, data: &[u8], first_sample: f32) -> IoResult<Vec<f32>> {
336        let deltas = self.decompress_rle(data)?;
337        let mut signal = vec![first_sample];
338
339        for delta in deltas {
340            let next = signal
341                .last()
342                .expect("Delta decode: signal must be non-empty")
343                + delta;
344            signal.push(next);
345        }
346
347        Ok(signal)
348    }
349
350    fn decompress_quantize(
351        &self,
352        data: &[u8],
353        bits: u8,
354        min_val: f32,
355        max_val: f32,
356        length: usize,
357    ) -> IoResult<Vec<f32>> {
358        let levels = (1u32 << bits) - 1;
359        let range = max_val - min_val;
360        let mut signal = Vec::with_capacity(length);
361
362        if bits <= 8 {
363            for &byte in data {
364                let normalized = byte as f32 / levels as f32;
365                let value = min_val + normalized * range;
366                signal.push(value);
367            }
368        } else {
369            for chunk in data.chunks_exact(2) {
370                let bytes: [u8; 2] = chunk
371                    .try_into()
372                    .expect("Quantization decode: chunk must be 2 bytes");
373                let quantized = u16::from_le_bytes(bytes);
374                let normalized = quantized as f32 / levels as f32;
375                let value = min_val + normalized * range;
376                signal.push(value);
377            }
378        }
379
380        Ok(signal)
381    }
382
383    fn decompress_dpcm(&self, data: &[u8], order: usize, first_sample: f32) -> IoResult<Vec<f32>> {
384        let mut signal = Vec::new();
385
386        // Read initial samples
387        let init_bytes = order * 4;
388        for chunk in data[..init_bytes.min(data.len())].chunks_exact(4) {
389            let bytes: [u8; 4] = chunk
390                .try_into()
391                .expect("DPCM decode: chunk must be 4 bytes");
392            signal.push(f32::from_le_bytes(bytes));
393        }
394
395        if signal.is_empty() {
396            signal.push(first_sample);
397        }
398
399        // Reconstruct from residuals
400        for chunk in data[init_bytes..].chunks_exact(4) {
401            let bytes: [u8; 4] = chunk
402                .try_into()
403                .expect("DPCM decode: chunk must be 4 bytes");
404            let residual = f32::from_le_bytes(bytes);
405            let predicted = self.linear_predict(&signal[signal.len().saturating_sub(order)..]);
406            signal.push(predicted + residual);
407        }
408
409        Ok(signal)
410    }
411}
412
413/// Adaptive compressor that selects best method
414pub struct AdaptiveCompressor {
415    methods: Vec<CompressionMethod>,
416}
417
418impl AdaptiveCompressor {
419    /// Create adaptive compressor with default methods
420    pub fn new() -> Self {
421        Self {
422            methods: vec![
423                CompressionMethod::Delta,
424                CompressionMethod::DeltaRLE,
425                CompressionMethod::Quantize { bits: 8 },
426                CompressionMethod::DPCM { predictor_order: 4 },
427            ],
428        }
429    }
430
431    /// Compress using best method
432    pub fn compress(&self, signal: &[f32]) -> IoResult<CompressedSignal> {
433        let mut best_compressed: Option<CompressedSignal> = None;
434        let mut best_size = usize::MAX;
435
436        for &method in &self.methods {
437            let compressor = SignalCompressor::new(method);
438            if let Ok(compressed) = compressor.compress(signal) {
439                if compressed.data.len() < best_size {
440                    best_size = compressed.data.len();
441                    best_compressed = Some(compressed);
442                }
443            }
444        }
445
446        best_compressed
447            .ok_or_else(|| IoError::SignalError("All compression methods failed".to_string()))
448    }
449
450    /// Decompress signal
451    pub fn decompress(&self, compressed: &CompressedSignal) -> IoResult<Vec<f32>> {
452        let compressor = SignalCompressor::new(compressed.method);
453        compressor.decompress(compressed)
454    }
455}
456
457impl Default for AdaptiveCompressor {
458    fn default() -> Self {
459        Self::new()
460    }
461}
462
463#[cfg(test)]
464mod tests {
465    use super::*;
466
467    #[test]
468    fn test_compress_none() {
469        let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0];
470        let compressor = SignalCompressor::new(CompressionMethod::None);
471
472        let compressed = compressor.compress(&signal).unwrap();
473        let decompressed = compressor.decompress(&compressed).unwrap();
474
475        assert_eq!(signal, decompressed);
476    }
477
478    #[test]
479    fn test_compress_delta() {
480        let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0];
481        let compressor = SignalCompressor::new(CompressionMethod::Delta);
482
483        let compressed = compressor.compress(&signal).unwrap();
484        let decompressed = compressor.decompress(&compressed).unwrap();
485
486        for (a, b) in signal.iter().zip(decompressed.iter()) {
487            assert!((a - b).abs() < 1e-6);
488        }
489    }
490
491    #[test]
492    fn test_compress_rle() {
493        let signal = vec![1.0, 1.0, 1.0, 2.0, 2.0, 3.0];
494        let compressor = SignalCompressor::new(CompressionMethod::RLE);
495
496        let compressed = compressor.compress(&signal).unwrap();
497        let decompressed = compressor.decompress(&compressed).unwrap();
498
499        assert_eq!(signal, decompressed);
500
501        // RLE should compress better than raw
502        let raw_size = signal.len() * 4;
503        assert!(compressed.data.len() < raw_size);
504    }
505
506    #[test]
507    fn test_compress_quantize() {
508        let signal = vec![0.0, 0.25, 0.5, 0.75, 1.0];
509        let compressor = SignalCompressor::new(CompressionMethod::Quantize { bits: 8 });
510
511        let compressed = compressor.compress(&signal).unwrap();
512        let decompressed = compressor.decompress(&compressed).unwrap();
513
514        // Allow small error due to quantization
515        for (a, b) in signal.iter().zip(decompressed.iter()) {
516            assert!((a - b).abs() < 0.01);
517        }
518    }
519
520    #[test]
521    fn test_adaptive_compressor() {
522        let signal = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0];
523        let compressor = AdaptiveCompressor::new();
524
525        let compressed = compressor.compress(&signal).unwrap();
526        let decompressed = compressor.decompress(&compressed).unwrap();
527
528        assert_eq!(signal.len(), decompressed.len());
529    }
530
531    #[test]
532    fn test_compression_ratio() {
533        let signal = vec![1.0; 100]; // Highly compressible
534        let compressor = SignalCompressor::new(CompressionMethod::RLE);
535
536        let compressed = compressor.compress(&signal).unwrap();
537        let ratio = compressor.compression_ratio(&signal, &compressed);
538
539        assert!(ratio > 10.0); // Should compress very well
540    }
541}