Skip to content

Commit 62b5e72

Browse files
committed
Probably before rethink
1 parent 5f31708 commit 62b5e72

File tree

5 files changed

+101
-108
lines changed

5 files changed

+101
-108
lines changed

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pylibjpeg-rle"
3-
version = "1.1.0"
3+
version = "1.2.0"
44
authors = ["scaramallion <[email protected]>"]
55
edition = "2024"
66
exclude = [".github", "docs", ".codecov.yml", "asv.*", ".gitignore", ".coveragerc"]
@@ -11,5 +11,5 @@ crate-type = ["cdylib"]
1111

1212

1313
[dependencies]
14-
pyo3 = { version = "0.24.2", features = ["extension-module"] }
14+
pyo3 = { version = "0.25.0", features = ["extension-module"] }
1515
bitvec = { version = "1.0.1" }

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2020-2021 scaramallion
3+
Copyright (c) 2020-2025 scaramallion
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ classifiers = [
1818
"Programming Language :: Python :: 3.11",
1919
"Programming Language :: Python :: 3.12",
2020
"Programming Language :: Python :: 3.13",
21+
"Programming Language :: Python :: 3.14",
2122
"Operating System :: MacOS :: MacOS X",
2223
"Operating System :: POSIX :: Linux",
2324
"Operating System :: Microsoft :: Windows",
@@ -59,7 +60,7 @@ omit = [
5960
]
6061

6162
[tool.mypy]
62-
python_version = "3.8"
63+
python_version = "3.10"
6364
files = "rle"
6465
exclude = ["rle/tests", "rle/benchmarks"]
6566
show_error_codes = true

rle/utils.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,6 @@ def decode_pixel_data(
2626
) -> Union[np.ndarray, bytearray]:
2727
"""Return the decoded RLE Lossless data as a :class:`numpy.ndarray`.
2828
29-
Intended for use with *pydicom* ``Dataset`` objects.
30-
3129
Parameters
3230
----------
3331
src : bytes
@@ -192,11 +190,11 @@ def encode_pixel_data(
192190
**kwargs
193191
If `ds` is not used then the following are required:
194192
195-
* ``'rows': int`` the number of rows contained in `src`
196-
* ``'columns': int`` the number of columns contained in `src`
197-
* ``samples_per_pixel': int`` the number of samples per pixel, either
193+
* ``'rows'``: :class:`int` the number of rows contained in `src`
194+
* ``'columns'``: :class:`int` the number of columns contained in `src`
195+
* ``'samples_per_pixel'``: :class:`int` the number of samples per pixel, either
198196
1 for monochrome or 3 for RGB or similar data.
199-
* ``'bits_allocated': int`` the number of bits needed to contain each
197+
* ``'bits_allocated'``: :class:`int` the number of bits needed to contain each
200198
pixel, either 1, 8, 16, 32 or 64.
201199
202200
Returns

src/lib.rs

Lines changed: 92 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use std::convert::TryFrom;
33
use std::error::Error;
44

5-
use bitvec::prelude::*;
5+
// use bitvec::prelude::*;
66

77
use pyo3::prelude::*;
88
use pyo3::wrap_pyfunction;
@@ -60,7 +60,7 @@ fn _parse_header(src: &[u8; 64]) -> [u32; 15] {
6060
Parameters
6161
----------
6262
src
63-
The 64 byte RLE header.
63+
The 64 byte RLE header containing 15 little-endian ordered offset values.
6464
*/
6565
return [
6666
u32::from_le_bytes([ src[4], src[5], src[6], src[7]]),
@@ -158,7 +158,7 @@ fn _decode_frame(
158158

159159
// Check 'Bits Allocated' is 1 or a multiple of 8
160160
let mut is_bit_packed = false;
161-
let mut bytes_per_pixel: u8;
161+
let mut bytes_per_pixel: u8; // Valid values are 1 | 2 | 4 | 8
162162
match bpp {
163163
0 => return err_invalid_bits_allocated,
164164
1 => {
@@ -193,6 +193,7 @@ fn _decode_frame(
193193
// Don't need to check the unwrap as we just checked
194194
// there's enough data in `src`
195195
let header = <&[u8; 64]>::try_from(&src[0..64]).unwrap();
196+
// All 15 header offsets, however no guarantee they will be non-zero
196197
let all_offsets: [u32; 15] = _parse_header(header);
197198

198199
// First offset must always be 64
@@ -221,13 +222,19 @@ fn _decode_frame(
221222
// Check the samples per pixel is conformant
222223
let spp: u8 = nr_segments / bytes_per_pixel;
223224
match spp {
224-
1 | 3 => {},
225+
1 => {},
226+
3 => {
227+
// Bit packed data must be 1 sample per pixel
228+
match bpp {
229+
1 => { return err_invalid_nr_samples },
230+
_ => {}
231+
}
232+
},
225233
_ => return err_invalid_nr_samples
226234
}
227235

228-
/*
229-
Example
230-
-------
236+
/* Example
237+
----------
231238
RLE encoded data is ordered like this (for 16-bit, 3 sample):
232239
Segment: 1 | 2 | 3 | 4 | 5 | 6
233240
R MSB | R LSB | G MSB | G LSB | B MSB | B LSB
@@ -242,23 +249,6 @@ fn _decode_frame(
242249
MSB LSB MSB LSB ... MSB LSB | MSB LSB MSB LSB ... MSB LSB | ...
243250
*/
244251

245-
/*
246-
Bit-packed data
247-
---------------
248-
249-
For bit-packed data (i.e. a *Bits Allocated* of 1), we decode the RLE encoding but
250-
leave the bit-packing in place. This means the length of each decoded segment is not
251-
equal to the number of pixels but instead the number of pixels / 8, rounded up to the
252-
nearest whole integer with the difference being accounted for by padding 0b0 bits.
253-
254-
Each decoded segment must have these padding bits removed and the final frame should be
255-
the concatenated unpadded segments. Fortunately for bit-packed data there can be at
256-
most 3 segments (with a *Samples per Pixel* of 3).
257-
258-
Might be a good idea to use bool arrays instead of u8 and implicitly unpack to avoid
259-
annoyances with multi-segment padding, then convert to u8 at the end.
260-
*/
261-
262252
// Decode each segment and place it into the vector
263253
// ------------------------------------------------
264254
// TODO: handle unwrap
@@ -303,57 +293,40 @@ fn _decode_frame(
303293
usize::from(bytes_per_pixel),
304294
usize::from(byte_offset) + so
305295
)?;
296+
306297
if len != pps { return err_segment_length }
307298
}
308299
}
309300

310301
return Ok(frame)
311302
}
312303

313-
// Bit-packed data
314-
// The number of whole bytes per segment after decoding
315-
// TODO: handle unwraps
316-
let mut bytes_per_segment = usize::try_from(nr_pixels / 8).unwrap();
317-
match nr_pixels % 8 {
318-
0 => {},
319-
_ => { bytes_per_segment += 1); },
320-
}
321-
322-
let mut frame: Vec<u8> = Vec::new();
323-
for idx in 0..(offsets.len() - 1) { // 0..1 or 0..3
324-
// Start and end indices of the segment
325-
let start = usize::try_from(offsets[idx]).unwrap();
326-
let end = usize::try_from(offsets[idx + 1]).unwrap();
327-
// TODO: match here on the unwrap
328-
let segment = _decode_bit_packed_segment(
329-
<&[u8]>::try_from(&src[start..end]).unwrap(),
330-
usize::from(bytes_per_segment),
331-
)?;
332-
333-
if segment.len() != bytes_per_segment { return err_segment_length }
334-
335-
if spp == 1 { return Ok(segment) }
304+
/* Bit-packed data
305+
------------------
306+
For bit-packed data (i.e. a *Bits Allocated* of 1), we decode the RLE encoded data but
307+
leave the bit-packing in place. This means the length of each decoded segment is not
308+
equal to the number of pixels but instead the number of pixels / 8, rounded up to the
309+
nearest whole integer, with the difference being accounted for by 0b0 padding bits.
310+
*/
336311

337-
frame.extend(segment);
312+
// The expected number of whole bytes per segment after decoding
313+
let mut bytes_per_segment: usize;
314+
match nr_pixels % 8 {
315+
0 => { bytes_per_segment = pps / 8; },
316+
_ => { bytes_per_segment = (pps + (8 - pps % 8)) / 8; },
338317
}
339318

340-
// Multiple samples but byte aligned
341-
if nr_pixels % 8 == 0 { return Ok(frame) }
342-
343-
// Multiple samples but not byte aligned
344-
// -> need to remove the bit padding between segments
345-
// TODO: handle unwraps
346-
let mut bv = BitVec::<_, Msb0>::from_vec(frame);
347-
let step = usize::try_from(nr_pixels).unwrap();
348-
for start in (step..bv.len() - 1).step_by(step) {
349-
let end = start + (8 - usize::try_from(nr_pixels % 8).unwrap());
350-
let bv2 = bv.drain(start..end);
351-
}
319+
// For bit-packed data we can only have 1 segment.
320+
let start = usize::try_from(offsets[0]).unwrap();
321+
let end = usize::try_from(offsets[1]).unwrap();
322+
let segment = _decode_bit_packed_segment(
323+
<&[u8]>::try_from(&src[start..end]).unwrap(),
324+
bytes_per_segment,
325+
)?;
352326

353-
// Set any trailing bits to 0b0
354-
bv.set_uninitialized(false);
327+
if segment.len() != bytes_per_segment) { return err_segment_length }
355328

356-
Ok(bv.into_vec())
329+
return Ok(segment)
357330
}
358331

359332

@@ -373,8 +346,7 @@ fn _decode_bit_packed_segment(
373346

374347
let err_eod = Err(
375348
String::from(
376-
"The end of the data was reached before the segment was \
377-
completely decoded"
349+
"The end of the data was reached before the segment was completely decoded"
378350
).into()
379351
);
380352

@@ -625,7 +597,7 @@ fn encode_frame<'py>(
625597
spp : int
626598
The number of samples per pixel, supported values are 1 or 3.
627599
bpp : int
628-
The number of bits per pixel, supported values are 8, 16, 32 and 64.
600+
The number of bits per pixel, supported values are 1, 8, 16, 32 and 64.
629601
byteorder : str
630602
Required if `bpp` is greater than 1, '>' if `src` is in big endian byte
631603
order, '<' if little endian.
@@ -651,18 +623,19 @@ fn _encode_frame(
651623
Parameters
652624
----------
653625
src
654-
The data to be RLE encoded, ordered as R1, G1, B1, R2, G2, B2, ...,
655-
Rn, Gn, Bn (i.e. Planar Configuration 0).
626+
The data to be RLE encoded, with multi-sample data ordered as R1, G1, B1,
627+
R2, G2, B2, ..., Rn, Gn, Bn (i.e. Planar Configuration 0).
656628
dst
657629
The vector storing the encoded data.
658630
rows
659631
The number of rows in the data.
660632
cols
661633
The number of columns in the data.
662634
spp
663-
The number of samples per pixel, supported values are 1 or 3.
635+
The number of samples per pixel, supported values are 1 or 3. May only be 1 if `bpp`
636+
is 1.
664637
bpp
665-
The number of bits per pixel, supported values are 8, 16, 32 and 64.
638+
The number of bits per pixel, supported values are 1, 8, 16, 32 and 64.
666639
byteorder
667640
Required if bpp is greater than 1, '>' if `src` is in big endian byte
668641
order, '<' if little endian.
@@ -696,26 +669,31 @@ fn _encode_frame(
696669
);
697670

698671
// Check 'Samples per Pixel' is either 1 or 3
699-
match spp {
700-
1 | 3 => {},
701-
_ => return err_invalid_nr_samples
702-
}
703-
704672
// Check 'Bits Allocated' is 1 or a multiple of 8
673+
// Check 'Samples per Pixel' is 1 if 'Bits Allocated' is 1
705674
let mut is_bit_packed = false;
706675
let mut bytes_per_pixel: u8;
707-
match bpp {
708-
0 => return err_invalid_bits_allocated,
676+
match spp {
709677
1 => {
710-
is_bit_packed = true;
711-
bytes_per_pixel = 1;
712-
},
713-
_ => match bpp % 8 {
714-
0 => {
715-
bytes_per_pixel = bpp / 8;
716-
},
717-
_ => return err_invalid_bits_allocated
678+
match bpp {
679+
1 => {
680+
is_bit_packed = true;
681+
bytes_per_pixel = 1;
682+
},
683+
8 | 16 | 32 | 64 => {
684+
bytes_per_pixel = bpp / 8;
685+
},
686+
_ => { return err_invalid_bits_allocated }
687+
}
718688
}
689+
3 => {
690+
match bpp {
691+
1 => { return err_invalid_nr_samples },
692+
8 | 16 | 32 | 64 => { bytes_per_pixel = bpp / 8; },
693+
_ => { return err_invalid_bits_allocated }
694+
}
695+
},
696+
_ => return err_invalid_nr_samples
719697
}
720698

721699
// Check `byteorder` is a valid character
@@ -734,11 +712,17 @@ fn _encode_frame(
734712
let r = usize::try_from(rows).unwrap();
735713
let c = usize::try_from(cols).unwrap();
736714

737-
// FIXME: add support for bit-packed data
715+
let total_pixels = r * c * usize::from(spp);
738716
if !is_bit_packed {
739-
if src.len() != r * c * usize::from(spp * bytes_per_pixel) {
740-
return err_invalid_parameters
717+
let total_length = total_pixels * usize::from(bytes_per_pixel);
718+
if src.len() != total_length { return err_invalid_parameters }
719+
} else {
720+
let mut total_length: usize;
721+
match total_pixels % 8 {
722+
0 => { total_length = total_pixels / 8; },
723+
_ => { total_length = (total_pixels + (8 - total_pixels % 8)) / 8; }
741724
}
725+
if src.len() != total_length { return err_invalid_parameters }
742726
}
743727

744728
let nr_segments: u8 = spp * bytes_per_pixel;
@@ -749,10 +733,11 @@ fn _encode_frame(
749733
dst.extend(u32::from(nr_segments).to_le_bytes().to_vec());
750734
dst.extend([0u8; 60].to_vec());
751735

752-
// A vector of the start indexes used when segmenting - default big endian
736+
// A vector of the start indexes used when segmenting
737+
// Start with big-endian ordered pixel sample values
753738
let mut start_indices: Vec<usize> = (0..usize::from(nr_segments)).collect();
754739
if byteorder != '>' {
755-
// `src` has little endian byte ordering
740+
// Typically `src` uses little endian byte ordering
756741
for idx in 0..spp {
757742
let s = usize::from(idx * bytes_per_pixel);
758743
let e = usize::from((idx + 1) * bytes_per_pixel);
@@ -761,6 +746,8 @@ fn _encode_frame(
761746
}
762747

763748
// Encode the data and update the RLE header segment offsets
749+
// Segments are ordered from most significant byte to least significant for
750+
// multi-byte values
764751
for idx in 0..usize::from(nr_segments) {
765752
// Update RLE header: convert current offset to 4x le ordered u8s
766753
let current_offset = (u32::try_from(dst.len()).unwrap()).to_le_bytes();
@@ -769,14 +756,21 @@ fn _encode_frame(
769756
}
770757

771758
// Encode! Note the offset start of the `src` iter
772-
let segment: Vec<u8> = src[start_indices[idx]..]
773-
.into_iter()
774-
.step_by(usize::from(spp * bytes_per_pixel))
775-
.cloned()
776-
.collect();
777-
778-
// FIXME: `cols` probably not correct for bit-packed data
779-
_encode_segment_from_vector(segment, dst, cols)?;
759+
if !is_bit_packed {
760+
let segment: Vec<u8> = src[start_indices[idx]..]
761+
.into_iter()
762+
.step_by(usize::from(spp * bytes_per_pixel))
763+
.cloned()
764+
.collect();
765+
766+
_encode_segment_from_vector(segment, dst, cols)?;
767+
} else {
768+
// Should only ever be 1 sample per pixel -> 1 segment
769+
// cols is wrong here, should be / 8
770+
// Also the DICOM Standard says each row should be encoded separately,
771+
// what do we do if the number of columns isn't divisble by 8?
772+
_encode_segment_from_vector(src, dst, cols)?;
773+
}
780774
}
781775

782776
Ok(())

0 commit comments

Comments
 (0)