From 1891cc7cefe52d5ad9e39e66dc953fe6d16c4908 Mon Sep 17 00:00:00 2001 From: Cryt1c Date: Sat, 31 May 2025 19:42:15 +0200 Subject: [PATCH 1/8] Implement DICOM-SEG --- pixeldata/src/lib.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index b14e5bb2..f8cfd171 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -468,7 +468,7 @@ impl DecodedPixelData<'_> { /// Retrieve a slice of a frame's raw pixel data samples as bytes, /// irrespective of the expected size of each sample. pub fn frame_data(&self, frame: u32) -> Result<&[u8]> { - let bytes_per_sample = self.bits_allocated as usize / 8; + let bytes_per_sample = self.bits_allocated as usize + 7 / 8; let frame_length = self.rows as usize * self.cols as usize * self.samples_per_pixel as usize @@ -1157,6 +1157,10 @@ impl DecodedPixelData<'_> { } } } + 1 => { + let data = self.frame_data(frame)?; + self.mono_image_with_extend(data.iter().copied(), *bit_depth)? + } _ => InvalidBitsAllocatedSnafu.fail()?, }; // Convert MONOCHROME1 => MONOCHROME2 @@ -2268,17 +2272,26 @@ where } DicomValue::Primitive(p) => { // Non-encoded, just return the pixel data for a single frame - let frame_size = ((bits_allocated + 7) / 8) as usize - * samples_per_pixel as usize - * rows as usize - * cols as usize; - let frame_offset = frame_size * frame as usize; + let frame_bits = rows as usize * cols as usize; + let frame_bytes = (frame_bits + 7) / 8; // Number of bytes required to store the bits + let frame_offset = frame_bytes * frame as usize; // Byte offset for the current frame + let data = p.to_bytes(); - data.get(frame_offset..frame_offset + frame_size) - .with_context(|| FrameOutOfRangeSnafu { + + let frame_data = data + .get(frame_offset..frame_offset + frame_bytes) + .ok_or_else(|| FrameOutOfRangeSnafu { frame_number: frame, - })? - .to_vec() + }) + .unwrap(); + + let unpacked_pixel_data = frame_data + .iter() + .flat_map(|&byte| (0..8).rev().map(move |bit| ((byte >> bit) & 1) * 255)) + .take(frame_bits) + .collect(); + + unpacked_pixel_data } DicomValue::Sequence(..) => InvalidPixelDataSnafu.fail()?, }; From 9ccabb9058633b8ada418e7df1acb42ca28b5768 Mon Sep 17 00:00:00 2001 From: Cryt1c Date: Sun, 1 Jun 2025 15:31:01 +0200 Subject: [PATCH 2/8] Fix bytes_per_sample calculation --- pixeldata/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index f8cfd171..0ecd1145 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -468,7 +468,7 @@ impl DecodedPixelData<'_> { /// Retrieve a slice of a frame's raw pixel data samples as bytes, /// irrespective of the expected size of each sample. pub fn frame_data(&self, frame: u32) -> Result<&[u8]> { - let bytes_per_sample = self.bits_allocated as usize + 7 / 8; + let bytes_per_sample = (self.bits_allocated as usize + 7) / 8; let frame_length = self.rows as usize * self.cols as usize * self.samples_per_pixel as usize From ad81c6cb2164d50b4d4fbec4f9ec8571537020cf Mon Sep 17 00:00:00 2001 From: Cryt1c Date: Sun, 1 Jun 2025 16:01:51 +0200 Subject: [PATCH 3/8] Implement bits_allocated branch --- pixeldata/src/lib.rs | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index 0ecd1145..b21d345d 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -2272,26 +2272,31 @@ where } DicomValue::Primitive(p) => { // Non-encoded, just return the pixel data for a single frame - let frame_bits = rows as usize * cols as usize; - let frame_bytes = (frame_bits + 7) / 8; // Number of bytes required to store the bits - let frame_offset = frame_bytes * frame as usize; // Byte offset for the current frame + let frame_pixels = (rows as usize) * (cols as usize); + let frame_samples = frame_pixels * (samples_per_pixel as usize); + let bytes_per_sample = ((bits_allocated + 7) / 8) as usize; + let frame_size = frame_samples * bytes_per_sample; + let frame_offset = frame_size * (frame as usize); let data = p.to_bytes(); - let frame_data = data - .get(frame_offset..frame_offset + frame_bytes) - .ok_or_else(|| FrameOutOfRangeSnafu { + let frame_data = data.get(frame_offset..frame_offset + frame_size).context( + FrameOutOfRangeSnafu { frame_number: frame, - }) - .unwrap(); - - let unpacked_pixel_data = frame_data - .iter() - .flat_map(|&byte| (0..8).rev().map(move |bit| ((byte >> bit) & 1) * 255)) - .take(frame_bits) - .collect(); - - unpacked_pixel_data + }, + )?; + + let pixel_data = if bits_allocated == 1 { + // Map every bit in each byte to a separate byte of either 0 or 255 + frame_data + .iter() + .flat_map(|&byte| (0..8).rev().map(move |bit| ((byte >> bit) & 1) * 255)) + .take(frame_pixels) + .collect() + } else { + frame_data.to_vec() + }; + pixel_data } DicomValue::Sequence(..) => InvalidPixelDataSnafu.fail()?, }; From 5a926097da96d42f617a3d8c22a6a6cffb3b9a04 Mon Sep 17 00:00:00 2001 From: Cryt1c Date: Sun, 1 Jun 2025 16:37:15 +0200 Subject: [PATCH 4/8] Implement bits_allocated branch --- pixeldata/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index b21d345d..bec04e3b 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -2274,8 +2274,11 @@ where // Non-encoded, just return the pixel data for a single frame let frame_pixels = (rows as usize) * (cols as usize); let frame_samples = frame_pixels * (samples_per_pixel as usize); - let bytes_per_sample = ((bits_allocated + 7) / 8) as usize; - let frame_size = frame_samples * bytes_per_sample; + let frame_size = if bits_allocated == 1 { + frame_samples / 8 as usize + } else { + frame_samples * ((bits_allocated + 7) / 8) as usize + }; let frame_offset = frame_size * (frame as usize); let data = p.to_bytes(); @@ -2296,6 +2299,7 @@ where } else { frame_data.to_vec() }; + pixel_data } DicomValue::Sequence(..) => InvalidPixelDataSnafu.fail()?, From 56455cec80e952798c4fe7290d392896b08bbd16 Mon Sep 17 00:00:00 2001 From: Cryt1c Date: Sun, 1 Jun 2025 16:44:47 +0200 Subject: [PATCH 5/8] Add test for 1bit image decoding --- pixeldata/src/lib.rs | 32 +++++++++++++++++++++++++++++++- 1 file changed, 31 insertions(+), 1 deletion(-) diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index bec04e3b..a531f1ab 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -169,7 +169,7 @@ pub enum InnerError { #[snafu(display("PixelData attribute is not a primitive value or pixel sequence"))] InvalidPixelData { backtrace: Backtrace }, - #[snafu(display("Invalid BitsAllocated, must be 8 or 16"))] + #[snafu(display("Invalid BitsAllocated, must be 1, 8 or 16"))] InvalidBitsAllocated { backtrace: Backtrace }, #[snafu(display("Unsupported PhotometricInterpretation `{}`", pi))] @@ -2932,4 +2932,34 @@ mod tests { let interleaved: Vec = vec![1, 5, 9, 2, 6, 10, 3, 7, 11, 4, 8, 12]; assert_eq!(interleave(&planar), interleaved); } + + #[cfg(feature = "image")] + #[test] + fn test_1bit_image_decoding() { + use crate::PixelDecoder as _; + use std::path::Path; + + let test_file = + dicom_test_files::path("pydicom/liver.dcm").expect("test DICOM file should exist"); + println!("Parsing pixel data for {}", test_file.display()); + let obj = dicom_object::open_file(test_file).unwrap(); + let pixel_data = obj.decode_pixel_data_frame(0).unwrap(); + let output_dir = + Path::new("../target/dicom_test_files/_out/test_1bit_image_decoding"); + std::fs::create_dir_all(output_dir).unwrap(); + + assert_eq!(pixel_data.number_of_frames(), 1, "expected 1 frame only"); + + let image = pixel_data.to_dynamic_image(0).unwrap(); + let image_path = output_dir.join(format!( + "{}-{}.png", + Path::new("pydicom/liver.dcm") + .file_stem() + .unwrap() + .to_str() + .unwrap(), + 0, + )); + image.save(image_path).unwrap(); + } } From 99aa235e34739b2c17537f6730638c4449644934 Mon Sep 17 00:00:00 2001 From: Cryt1c Date: Sun, 1 Jun 2025 16:53:07 +0200 Subject: [PATCH 6/8] Remove reversion of the range --- pixeldata/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index a531f1ab..028b5b2f 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -2293,7 +2293,7 @@ where // Map every bit in each byte to a separate byte of either 0 or 255 frame_data .iter() - .flat_map(|&byte| (0..8).rev().map(move |bit| ((byte >> bit) & 1) * 255)) + .flat_map(|&byte| (0..8).map(move |bit| ((byte >> bit) & 1) * 255)) .take(frame_pixels) .collect() } else { From 8a7ceb8441764956071f396e9f4bb4b64e901473 Mon Sep 17 00:00:00 2001 From: David Peherstorfer Date: Mon, 2 Jun 2025 20:12:49 +0200 Subject: [PATCH 7/8] Update pixeldata/src/lib.rs Co-authored-by: Eduardo Pinho --- pixeldata/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index 028b5b2f..b472bb18 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -2275,7 +2275,7 @@ where let frame_pixels = (rows as usize) * (cols as usize); let frame_samples = frame_pixels * (samples_per_pixel as usize); let frame_size = if bits_allocated == 1 { - frame_samples / 8 as usize + frame_samples / 8 } else { frame_samples * ((bits_allocated + 7) / 8) as usize }; From e28fb497a83488092593ee5d51ac2618a552a559 Mon Sep 17 00:00:00 2001 From: Cryt1c Date: Mon, 2 Jun 2025 20:16:08 +0200 Subject: [PATCH 8/8] Change usize cast --- pixeldata/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index b472bb18..c8fa2315 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -2277,7 +2277,7 @@ where let frame_size = if bits_allocated == 1 { frame_samples / 8 } else { - frame_samples * ((bits_allocated + 7) / 8) as usize + frame_samples * ((bits_allocated as usize + 7) / 8) }; let frame_offset = frame_size * (frame as usize);