Skip to main content

dicom_toolkit_codec/jpeg_ls/
params.rs

1//! JPEG-LS parameters and default threshold computation (ISO/IEC 14495-1).
2
3/// Interleave mode for multi-component images.
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum InterleaveMode {
6    /// Each component encoded in a separate scan.
7    None = 0,
8    /// Components interleaved line by line.
9    Line = 1,
10    /// Components interleaved sample by sample (pixel interleaved).
11    Sample = 2,
12}
13
14impl InterleaveMode {
15    pub fn from_u8(v: u8) -> Option<Self> {
16        match v {
17            0 => Some(Self::None),
18            1 => Some(Self::Line),
19            2 => Some(Self::Sample),
20            _ => None,
21        }
22    }
23}
24
25/// HP color transform identifiers (extension, not part of base standard).
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ColorTransform {
28    None = 0,
29    /// R' = R - G, B' = B - G
30    Hp1 = 1,
31    /// Iterative green centering
32    Hp2 = 2,
33    /// Complex bit-shifted
34    Hp3 = 3,
35}
36
37impl ColorTransform {
38    pub fn from_u8(v: u8) -> Option<Self> {
39        match v {
40            0 => Some(Self::None),
41            1 => Some(Self::Hp1),
42            2 => Some(Self::Hp2),
43            3 => Some(Self::Hp3),
44            _ => None,
45        }
46    }
47}
48
49/// Custom JPEG-LS coding parameters (T1, T2, T3, RESET, MAXVAL).
50#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51pub struct JlsCustomParameters {
52    pub max_val: i32,
53    pub t1: i32,
54    pub t2: i32,
55    pub t3: i32,
56    pub reset: i32,
57}
58
59/// Frame-level JPEG-LS parameters.
60#[derive(Debug, Clone)]
61pub struct JlsParameters {
62    pub width: u32,
63    pub height: u32,
64    pub bits_per_sample: u8,
65    pub components: u8,
66    /// NEAR parameter: 0 = lossless, >0 = near-lossless with bounded error.
67    pub near: i32,
68    pub interleave: InterleaveMode,
69    pub color_transform: ColorTransform,
70    pub custom: JlsCustomParameters,
71}
72
73impl Default for JlsParameters {
74    fn default() -> Self {
75        Self {
76            width: 0,
77            height: 0,
78            bits_per_sample: 8,
79            components: 1,
80            near: 0,
81            interleave: InterleaveMode::None,
82            color_transform: ColorTransform::None,
83            custom: JlsCustomParameters::default(),
84        }
85    }
86}
87
88// Default threshold constants from the standard.
89const BASIC_T1: i32 = 3;
90const BASIC_T2: i32 = 7;
91const BASIC_T3: i32 = 21;
92
93/// Default reset interval.
94pub const BASIC_RESET: i32 = 64;
95
96fn clamp(val: i32, lo: i32, hi: i32) -> i32 {
97    if val > hi || val < lo {
98        lo
99    } else {
100        val
101    }
102}
103
104/// Compute the default T1, T2, T3, RESET values for a given MAXVAL and NEAR.
105///
106/// Per ISO/IEC 14495-1 §C.2.4.1.1 and CharLS `ComputeDefault`.
107pub fn compute_default(max_val: i32, near: i32) -> JlsCustomParameters {
108    let factor = (max_val.min(4095) + 128) / 256;
109
110    let t1 = clamp(factor * (BASIC_T1 - 2) + 2 + 3 * near, near + 1, max_val);
111    let t2 = clamp(factor * (BASIC_T2 - 3) + 3 + 5 * near, t1, max_val);
112    let t3 = clamp(factor * (BASIC_T3 - 4) + 4 + 7 * near, t2, max_val);
113
114    JlsCustomParameters {
115        max_val,
116        t1,
117        t2,
118        t3,
119        reset: BASIC_RESET,
120    }
121}
122
123/// Compute derived traits for the codec from MAXVAL and NEAR.
124#[derive(Debug, Clone, Copy)]
125pub struct DerivedTraits {
126    pub max_val: i32,
127    pub near: i32,
128    pub range: i32,
129    pub bpp: i32,
130    pub qbpp: i32,
131    pub limit: i32,
132    pub reset: i32,
133}
134
135impl DerivedTraits {
136    pub fn new(max_val: i32, near: i32, reset: i32) -> Self {
137        let range = (max_val + 2 * near) / (2 * near + 1) + 1;
138        let bpp = log2_ceil(max_val);
139        let qbpp = log2_ceil(range);
140        let limit = 2 * (bpp + bpp.max(8));
141        Self {
142            max_val,
143            near,
144            range,
145            bpp,
146            qbpp,
147            limit,
148            reset,
149        }
150    }
151
152    /// Quantize an error value for near-lossless mode.
153    #[inline]
154    pub fn quantize_error(&self, e: i32) -> i32 {
155        if self.near == 0 {
156            return e;
157        }
158        if e > 0 {
159            (e + self.near) / (2 * self.near + 1)
160        } else {
161            -(self.near - e) / (2 * self.near + 1)
162        }
163    }
164
165    /// Map an error value into the modular range.
166    #[inline]
167    pub fn mod_range(&self, mut err: i32) -> i32 {
168        if err < 0 {
169            err += self.range;
170        }
171        if err >= (self.range + 1) / 2 {
172            err -= self.range;
173        }
174        err
175    }
176
177    /// Compute the error value: quantize then mod-range.
178    #[inline]
179    pub fn compute_error_val(&self, e: i32) -> i32 {
180        self.mod_range(self.quantize_error(e))
181    }
182
183    /// De-quantize an error value.
184    #[inline]
185    pub fn dequantize(&self, err: i32) -> i32 {
186        err * (2 * self.near + 1)
187    }
188
189    /// Reconstruct the sample from prediction and error.
190    #[inline]
191    pub fn compute_reconstructed(&self, px: i32, err: i32) -> i32 {
192        let val = px + self.dequantize(err);
193        self.fix_reconstructed(val)
194    }
195
196    fn fix_reconstructed(&self, mut val: i32) -> i32 {
197        if val < -self.near {
198            val += self.range * (2 * self.near + 1);
199        } else if val > self.max_val + self.near {
200            val -= self.range * (2 * self.near + 1);
201        }
202        self.correct_prediction(val)
203    }
204
205    /// Clamp a predicted value to [0, MAXVAL].
206    #[inline]
207    pub fn correct_prediction(&self, pxc: i32) -> i32 {
208        if (pxc & self.max_val) == pxc {
209            pxc
210        } else {
211            (!(pxc >> 31)) & self.max_val
212        }
213    }
214}
215
216/// Compute ⌈log₂(n)⌉ (minimum bits needed to represent values up to n).
217fn log2_ceil(n: i32) -> i32 {
218    let mut x = 0;
219    while n > (1i32 << x) {
220        x += 1;
221    }
222    x
223}
224
225// ── Tests ─────────────────────────────────────────────────────────────────────
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn default_thresholds_8bit() {
233        let p = compute_default(255, 0);
234        assert_eq!(p.t1, 3);
235        assert_eq!(p.t2, 7);
236        assert_eq!(p.t3, 21);
237        assert_eq!(p.reset, 64);
238        assert_eq!(p.max_val, 255);
239    }
240
241    #[test]
242    fn default_thresholds_12bit() {
243        let p = compute_default(4095, 0);
244        assert_eq!(p.t1, 18);
245        assert_eq!(p.t2, 67);
246        assert_eq!(p.t3, 276);
247        assert_eq!(p.reset, 64);
248    }
249
250    #[test]
251    fn default_thresholds_16bit() {
252        let p = compute_default(65535, 0);
253        // FACTOR = min(65535,4095)+128)/256 = (4095+128)/256 = 16
254        assert_eq!(p.t1, 18);
255        assert_eq!(p.t2, 67);
256        assert_eq!(p.t3, 276);
257    }
258
259    #[test]
260    fn default_thresholds_near_lossless() {
261        let p = compute_default(255, 2);
262        // FACTOR=1, T1 = clamp(1*(3-2)+2+6, 3, 255) = clamp(9,3,255) = 9
263        assert_eq!(p.t1, 9);
264        // T2 = clamp(1*(7-3)+3+10, 9, 255) = clamp(17,9,255) = 17
265        assert_eq!(p.t2, 17);
266        // T3 = clamp(1*(21-4)+4+14, 17, 255) = clamp(35,17,255) = 35
267        assert_eq!(p.t3, 35);
268    }
269
270    #[test]
271    fn derived_traits_lossless_8bit() {
272        let t = DerivedTraits::new(255, 0, BASIC_RESET);
273        assert_eq!(t.range, 256);
274        assert_eq!(t.bpp, 8);
275        assert_eq!(t.qbpp, 8); // log2_ceil(256) = 8 since 2^8 = 256
276        assert_eq!(t.limit, 32);
277    }
278
279    #[test]
280    fn derived_traits_near_lossless() {
281        let t = DerivedTraits::new(255, 2, BASIC_RESET);
282        // RANGE = (255 + 4) / 5 + 1 = 51 + 1 = 52
283        assert_eq!(t.range, 52);
284        assert_eq!(t.bpp, 8);
285        assert_eq!(t.qbpp, 6); // log2_ceil(52) = 6
286    }
287
288    #[test]
289    fn log2_ceil_values() {
290        assert_eq!(log2_ceil(1), 0);
291        assert_eq!(log2_ceil(2), 1);
292        assert_eq!(log2_ceil(255), 8);
293        assert_eq!(log2_ceil(256), 8);
294        assert_eq!(log2_ceil(257), 9);
295        assert_eq!(log2_ceil(4095), 12);
296        assert_eq!(log2_ceil(65535), 16);
297    }
298
299    #[test]
300    fn mod_range_lossless_8bit() {
301        let t = DerivedTraits::new(255, 0, BASIC_RESET);
302        assert_eq!(t.mod_range(0), 0);
303        assert_eq!(t.mod_range(1), 1);
304        assert_eq!(t.mod_range(127), 127);
305        assert_eq!(t.mod_range(128), -128);
306        assert_eq!(t.mod_range(-1), -1);
307        assert_eq!(t.mod_range(-128), -128);
308    }
309
310    #[test]
311    fn correct_prediction_clamps() {
312        let t = DerivedTraits::new(255, 0, BASIC_RESET);
313        assert_eq!(t.correct_prediction(100), 100);
314        assert_eq!(t.correct_prediction(0), 0);
315        assert_eq!(t.correct_prediction(255), 255);
316        assert_eq!(t.correct_prediction(300), 255); // clamped high
317        assert_eq!(t.correct_prediction(-1), 0); // clamped low (sign bit flip)
318    }
319}