use dicom_core::{ops::AttributeOp, value::C};
use snafu::Snafu;
use std::borrow::Cow;
#[derive(Debug, Snafu)]
#[non_exhaustive]
#[snafu(visibility(pub), module)]
pub enum DecodeError {
#[snafu(whatever, display("{}", message))]
Custom {
message: String,
#[snafu(source(from(Box<dyn std::error::Error + Send + Sync + 'static>, Some)))]
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
},
#[snafu(display("Pixel Data is not encapsulated"))]
NotEncapsulated,
#[snafu(display("Frame of pixel data is missing or out of bounds"))]
FrameRangeOutOfBounds,
#[snafu(display("Missing required attribute `{}`", name))]
MissingAttribute { name: &'static str },
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
#[snafu(visibility(pub), module)]
pub enum EncodeError {
#[snafu(whatever, display("{}", message))]
Custom {
message: String,
#[snafu(source(from(Box<dyn std::error::Error + Send + Sync + 'static>, Some)))]
source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
},
#[snafu(display("Pixel Data is not native"))]
NotNative,
#[snafu(display("Frame of pixel data is missing or out of bounds"))]
FrameRangeOutOfBounds,
#[snafu(display("Missing required attribute `{}`", name))]
MissingAttribute { name: &'static str },
}
pub type DecodeResult<T, E = DecodeError> = Result<T, E>;
pub type EncodeResult<T, E = EncodeError> = Result<T, E>;
#[derive(Debug)]
pub struct RawPixelData {
pub fragments: C<Vec<u8>>,
pub offset_table: C<u32>,
}
pub trait PixelDataObject {
fn transfer_syntax_uid(&self) -> &str;
fn rows(&self) -> Option<u16>;
fn cols(&self) -> Option<u16>;
fn samples_per_pixel(&self) -> Option<u16>;
fn bits_allocated(&self) -> Option<u16>;
fn bits_stored(&self) -> Option<u16>;
fn photometric_interpretation(&self) -> Option<&str>;
fn number_of_frames(&self) -> Option<u32>;
fn number_of_fragments(&self) -> Option<u32>;
fn fragment(&self, fragment: usize) -> Option<Cow<'_, [u8]>>;
fn offset_table(&self) -> Option<Cow<'_, [u32]>>;
fn raw_pixel_data(&self) -> Option<RawPixelData>;
fn frame_pixel_data(&self, frame: u32) -> Option<Cow<'_, [u8]>> {
match self.number_of_fragments() {
Some(number_of_fragments)
if number_of_fragments == self.number_of_frames().unwrap_or(1) =>
{
self.fragment(frame as usize)
}
Some(number_of_fragments) => {
let offset_table = self.offset_table()?;
let base_offset = offset_table.get(frame as usize).copied();
let base_offset = if frame == 0 {
base_offset.unwrap_or(0) as usize
} else {
base_offset? as usize
};
let next_offset = offset_table.get(frame as usize + 1);
let mut offset = 0;
let mut frame_data = Vec::new();
for idx in 0..number_of_fragments as usize {
let fragment = self.fragment(idx)?;
if offset >= base_offset {
frame_data.extend_from_slice(&fragment);
}
offset += fragment.len() + 8;
if let Some(&next_offset) = next_offset {
if offset >= next_offset as usize {
break;
}
}
}
Some(Cow::Owned(frame_data))
}
None => {
let bits_allocated = self.bits_allocated()?;
let rows = self.rows()?;
let columns = self.cols()?;
let frame_size = determine_bytes_per_native_frame(
rows,
columns,
self.samples_per_pixel()?,
bits_allocated,
self.photometric_interpretation()?,
);
let pixel_data = self.fragment(0)?;
let (start, end) = if bits_allocated == 1 {
let samples_per_frame = rows as usize * columns as usize;
let start = frame as usize * samples_per_frame / 8;
let end = ((frame as usize + 1) * samples_per_frame).div_ceil(8);
(start, end)
} else {
let start = frame as usize * frame_size;
let end = start + frame_size;
(start, end)
};
if end <= pixel_data.len() {
match pixel_data {
Cow::Borrowed(slice) => slice.get(start..end).map(Cow::Borrowed),
Cow::Owned(vec) => vec.get(start..end).map(|s| Cow::Owned(s.to_vec())),
}
} else {
None
}
}
}
}
}
#[derive(Debug, Default, Clone)]
#[non_exhaustive]
pub struct EncodeOptions {
pub quality: Option<u8>,
pub effort: Option<u8>,
}
impl EncodeOptions {
pub fn new() -> Self {
Self::default()
}
}
pub trait PixelDataReader {
fn decode(&self, src: &dyn PixelDataObject, dst: &mut Vec<u8>) -> DecodeResult<()> {
let frames = src.number_of_frames().unwrap_or(1);
for frame in 0..frames {
self.decode_frame(src, frame, dst)?;
}
Ok(())
}
fn decode_frame(
&self,
src: &dyn PixelDataObject,
frame: u32,
dst: &mut Vec<u8>,
) -> DecodeResult<()>;
}
pub trait PixelDataWriter {
fn encode(
&self,
src: &dyn PixelDataObject,
options: EncodeOptions,
dst: &mut Vec<Vec<u8>>,
offset_table: &mut Vec<u32>,
) -> EncodeResult<Vec<AttributeOp>> {
let frames = src.number_of_frames().unwrap_or(1);
let mut out = Vec::new();
for frame in 0..frames {
let mut frame_data = Vec::new();
out = self.encode_frame(src, frame, options.clone(), &mut frame_data)?;
offset_table.push(frame_data.len() as u32 + 8 * (frame + 1));
dst.push(frame_data);
}
Ok(out)
}
fn encode_frame(
&self,
src: &dyn PixelDataObject,
frame: u32,
options: EncodeOptions,
dst: &mut Vec<u8>,
) -> EncodeResult<Vec<AttributeOp>>;
}
pub type DynPixelDataReader = Box<dyn PixelDataReader + Send + Sync + 'static>;
pub type DynPixelDataWriter = Box<dyn PixelDataWriter + Send + Sync + 'static>;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum NeverPixelAdapter {}
impl PixelDataReader for NeverPixelAdapter {
fn decode(&self, _src: &dyn PixelDataObject, _dst: &mut Vec<u8>) -> DecodeResult<()> {
unreachable!()
}
fn decode_frame(
&self,
_src: &dyn PixelDataObject,
_frame: u32,
_dst: &mut Vec<u8>,
) -> DecodeResult<()> {
unreachable!()
}
}
impl PixelDataWriter for NeverPixelAdapter {
fn encode(
&self,
_src: &dyn PixelDataObject,
_options: EncodeOptions,
_dst: &mut Vec<Vec<u8>>,
_offset_table: &mut Vec<u32>,
) -> EncodeResult<Vec<AttributeOp>> {
unreachable!()
}
fn encode_frame(
&self,
_src: &dyn PixelDataObject,
_frame: u32,
_options: EncodeOptions,
_dst: &mut Vec<u8>,
) -> EncodeResult<Vec<AttributeOp>> {
unreachable!()
}
}
impl PixelDataReader for crate::transfer_syntax::NeverAdapter {
fn decode(&self, _src: &dyn PixelDataObject, _dst: &mut Vec<u8>) -> DecodeResult<()> {
unreachable!()
}
fn decode_frame(
&self,
_src: &dyn PixelDataObject,
_frame: u32,
_dst: &mut Vec<u8>,
) -> DecodeResult<()> {
unreachable!()
}
}
impl PixelDataWriter for crate::transfer_syntax::NeverAdapter {
fn encode(
&self,
_src: &dyn PixelDataObject,
_options: EncodeOptions,
_dst: &mut Vec<Vec<u8>>,
_offset_table: &mut Vec<u32>,
) -> EncodeResult<Vec<AttributeOp>> {
unreachable!()
}
fn encode_frame(
&self,
_src: &dyn PixelDataObject,
_frame: u32,
_options: EncodeOptions,
_dst: &mut Vec<u8>,
) -> EncodeResult<Vec<AttributeOp>> {
unreachable!()
}
}
#[inline]
fn determine_bytes_per_native_frame(
rows: u16,
columns: u16,
samples_per_pixel: u16,
bits_allocated: u16,
photometric_interpretation: &str,
) -> usize {
if bits_allocated == 1 {
return (rows as usize * columns as usize).div_ceil(8);
}
let real_samples_per_pixel =
if samples_per_pixel == 3 && photometric_interpretation == "YBR_FULL_422" {
2
} else {
samples_per_pixel
};
rows as usize
* columns as usize
* real_samples_per_pixel as usize
* (bits_allocated as usize).div_ceil(8)
}
#[cfg(test)]
mod tests {
use std::borrow::Cow;
use dicom_core::value::{InMemFragment, PixelFragmentSequence};
use crate::adapters::RawPixelData;
use super::PixelDataObject;
fn generated_rgb_frames(columns: u16, rows: u16) -> impl Iterator<Item = Vec<u8>> {
(0..=255_u8).map(move |n| vec![n; columns as usize * rows as usize * 3])
}
pub(crate) struct TestDataObject {
pub ts_uid: &'static str,
pub rows: u16,
pub columns: u16,
pub bits_allocated: u16,
pub bits_stored: u16,
pub samples_per_pixel: u16,
pub photometric_interpretation: &'static str,
pub number_of_frames: u32,
pub flat_pixel_data: Option<Vec<u8>>,
pub pixel_data_sequence: Option<PixelFragmentSequence<InMemFragment>>,
}
impl PixelDataObject for TestDataObject {
fn transfer_syntax_uid(&self) -> &str {
&self.ts_uid
}
fn rows(&self) -> Option<u16> {
Some(self.rows)
}
fn cols(&self) -> Option<u16> {
Some(self.columns)
}
fn samples_per_pixel(&self) -> Option<u16> {
Some(self.samples_per_pixel)
}
fn bits_allocated(&self) -> Option<u16> {
Some(self.bits_allocated)
}
fn bits_stored(&self) -> Option<u16> {
Some(self.bits_stored)
}
fn photometric_interpretation(&self) -> Option<&str> {
Some(self.photometric_interpretation)
}
fn number_of_frames(&self) -> Option<u32> {
Some(self.number_of_frames)
}
fn number_of_fragments(&self) -> Option<u32> {
self.pixel_data_sequence
.as_ref()
.map(|v| v.fragments().len() as u32)
}
fn fragment(&self, fragment: usize) -> Option<Cow<'_, [u8]>> {
match (&self.flat_pixel_data, &self.pixel_data_sequence) {
(Some(_), Some(_)) => {
panic!("Invalid pixel data object (both flat and fragment sequence)")
}
(_, Some(v)) => v
.fragments()
.get(fragment)
.map(|f| Cow::Borrowed(f.as_slice())),
(Some(v), _) => {
if fragment == 0 {
Some(Cow::Borrowed(v))
} else {
None
}
}
(None, None) => None,
}
}
fn offset_table(&self) -> Option<Cow<'_, [u32]>> {
match &self.pixel_data_sequence {
Some(v) => Some(Cow::Borrowed(v.offset_table())),
_ => None,
}
}
fn raw_pixel_data(&self) -> Option<RawPixelData> {
match (&self.flat_pixel_data, &self.pixel_data_sequence) {
(Some(_), Some(_)) => {
panic!("Invalid pixel data object (both flat and fragment sequence)")
}
(Some(v), _) => Some(RawPixelData {
fragments: vec![v.clone()].into(),
offset_table: Default::default(),
}),
(_, Some(v)) => Some(RawPixelData {
fragments: v.fragments().into(),
offset_table: v.offset_table().into(),
}),
_ => None,
}
}
}
#[test]
fn frame_pixel_data_in_object_flat() {
let rows = 10;
let columns = 10;
let number_of_frames = 4;
let obj = TestDataObject {
ts_uid: "1.2.840.10008.1.2.1",
rows,
columns,
bits_allocated: 8,
bits_stored: 8,
samples_per_pixel: 3,
photometric_interpretation: "RGB",
number_of_frames,
flat_pixel_data: Some(
generated_rgb_frames(columns, rows)
.take(number_of_frames as usize)
.flatten()
.collect(),
),
pixel_data_sequence: None,
};
assert_eq!(obj.frame_pixel_data(0), Some(vec![0; 10 * 10 * 3].into()));
assert_eq!(obj.frame_pixel_data(1), Some(vec![1; 10 * 10 * 3].into()));
assert_eq!(obj.frame_pixel_data(2), Some(vec![2; 10 * 10 * 3].into()));
assert_eq!(obj.frame_pixel_data(3), Some(vec![3; 10 * 10 * 3].into()));
assert_eq!(obj.frame_pixel_data(4), None);
}
#[test]
fn frame_pixel_data_in_object_encapsulated() {
let obj = TestDataObject {
ts_uid: "9.9.999.9999.9.9.99",
rows: 512,
columns: 512,
bits_allocated: 16,
bits_stored: 12,
samples_per_pixel: 1,
photometric_interpretation: "MONOCHROME2",
number_of_frames: 3,
flat_pixel_data: None,
pixel_data_sequence: Some(PixelFragmentSequence::new(
vec![0, 16 + 20, 16 + 20 + 24],
vec![
vec![0x33; 16],
vec![0x55; 20],
vec![0x77; 24],
vec![0x99; 36],
],
)),
};
let frame_1: Vec<u8> = IntoIterator::into_iter([0x33; 16])
.chain(IntoIterator::into_iter([0x55; 20]))
.collect();
assert_eq!(obj.frame_pixel_data(0), Some(frame_1.into()));
assert_eq!(obj.frame_pixel_data(1), Some(vec![0x77; 24].into()));
assert_eq!(obj.frame_pixel_data(2), Some(vec![0x99; 36].into()));
}
}