diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index b14e5bb2..c8fa2315 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))] @@ -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,35 @@ 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_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 + } else { + frame_samples * ((bits_allocated as usize + 7) / 8) + }; + let frame_offset = frame_size * (frame as usize); + 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_size).context( + FrameOutOfRangeSnafu { frame_number: frame, - })? - .to_vec() + }, + )?; + + 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).map(move |bit| ((byte >> bit) & 1) * 255)) + .take(frame_pixels) + .collect() + } else { + frame_data.to_vec() + }; + + pixel_data } DicomValue::Sequence(..) => InvalidPixelDataSnafu.fail()?, }; @@ -2910,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(); + } }