Skip to content

Commit

Permalink
Merge pull request #422 from dougyau/single-frame-gdcm
Browse files Browse the repository at this point in the history
Decode single frame using GDCM
  • Loading branch information
Enet4 authored Nov 20, 2023
2 parents 9352c04 + d2e391f commit a35752d
Showing 1 changed file with 194 additions and 12 deletions.
206 changes: 194 additions & 12 deletions pixeldata/src/gdcm.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Decode pixel data using GDCM when the default features are enabled.
use crate::*;
use dicom_dictionary_std::tags;
use dicom_encoding::adapters::DecodeError;
use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
Expand All @@ -18,28 +19,31 @@ where
use super::attribute::*;

let pixel_data = pixel_data(self).context(GetAttributeSnafu)?;

let cols = cols(self).context(GetAttributeSnafu)?;
let rows = rows(self).context(GetAttributeSnafu)?;

let photometric_interpretation =
photometric_interpretation(self).context(GetAttributeSnafu)?;
let pi_type = GDCMPhotometricInterpretation::from_str(photometric_interpretation.as_str())
.map_err(|_| {
UnsupportedPhotometricInterpretationSnafu {
pi: photometric_interpretation.clone(),
}
.build()
})?;
let pi_type = match photometric_interpretation {
PhotometricInterpretation::PaletteColor => GDCMPhotometricInterpretation::PALETTE_COLOR,
_ => GDCMPhotometricInterpretation::from_str(photometric_interpretation.as_str())
.map_err(|_| {
UnsupportedPhotometricInterpretationSnafu {
pi: photometric_interpretation.clone(),
}
.build()
})?,
};

let transfer_syntax = &self.meta().transfer_syntax;
let registry =
TransferSyntaxRegistry
.get(&&transfer_syntax)
.get(transfer_syntax)
.context(UnknownTransferSyntaxSnafu {
ts_uid: transfer_syntax,
})?;
let ts_type = GDCMTransferSyntax::from_str(&registry.uid()).map_err(|_| {
let ts_type = GDCMTransferSyntax::from_str(registry.uid()).map_err(|_| {
UnsupportedTransferSyntaxSnafu {
ts: transfer_syntax.clone(),
}
Expand All @@ -66,7 +70,7 @@ where
};
if fragments.len() > 1 {
// Bundle fragments and decode multi-frame dicoms
let dims = [cols.into(), rows.into(), number_of_frames.into()];
let dims = [cols.into(), rows.into(), number_of_frames];
let fragments: Vec<_> = fragments.iter().map(|frag| frag.as_slice()).collect();
decode_multi_frame_compressed(
fragments.as_slice(),
Expand Down Expand Up @@ -144,6 +148,184 @@ where
window,
})
}

fn decode_pixel_data_frame(&self, frame: u32) -> Result<DecodedPixelData<'_>> {
use super::attribute::*;

let pixel_data = pixel_data(self).context(GetAttributeSnafu)?;

let cols = cols(self).context(GetAttributeSnafu)?;
let rows = rows(self).context(GetAttributeSnafu)?;

let photometric_interpretation =
photometric_interpretation(self).context(GetAttributeSnafu)?;
let pi_type = match photometric_interpretation {
PhotometricInterpretation::PaletteColor => GDCMPhotometricInterpretation::PALETTE_COLOR,
_ => GDCMPhotometricInterpretation::from_str(photometric_interpretation.as_str())
.map_err(|_| {
UnsupportedPhotometricInterpretationSnafu {
pi: photometric_interpretation.clone(),
}
.build()
})?,
};

let transfer_syntax = &self.meta().transfer_syntax;
let registry =
TransferSyntaxRegistry
.get(transfer_syntax)
.context(UnknownTransferSyntaxSnafu {
ts_uid: transfer_syntax,
})?;
let ts_type = GDCMTransferSyntax::from_str(registry.uid()).map_err(|_| {
UnsupportedTransferSyntaxSnafu {
ts: transfer_syntax.clone(),
}
.build()
})?;

let samples_per_pixel = samples_per_pixel(self).context(GetAttributeSnafu)?;
let bits_allocated = bits_allocated(self).context(GetAttributeSnafu)?;
let bits_stored = bits_stored(self).context(GetAttributeSnafu)?;
let high_bit = high_bit(self).context(GetAttributeSnafu)?;
let pixel_representation = pixel_representation(self).context(GetAttributeSnafu)?;
let planar_configuration = if let Ok(el) = self.element(tags::PLANAR_CONFIGURATION) {
el.uint16().unwrap_or(0)
} else {
0
};
let rescale_intercept = rescale_intercept(self);
let rescale_slope = rescale_slope(self);
let number_of_frames = number_of_frames(self).context(GetAttributeSnafu)?;
let voi_lut_function = voi_lut_function(self).context(GetAttributeSnafu)?;
let voi_lut_function = voi_lut_function.and_then(|v| VoiLutFunction::try_from(&*v).ok());

let decoded_pixel_data = match pixel_data.value() {
Value::PixelSequence(v) => {
let fragments = v.fragments();
let gdcm_error_mapper = |source: GDCMError| DecodeError::Custom {
message: source.to_string(),
source: Some(Box::new(source)),
};

let frame = frame as usize;
let data = if number_of_frames == 1 && fragments.len() > 1 {
fragments.iter().flat_map(|frame| frame.to_vec()).collect()
} else {
fragments[frame].to_vec()
};

match ts_type {
GDCMTransferSyntax::ImplicitVRLittleEndian
| GDCMTransferSyntax::ExplicitVRLittleEndian => {
// This is just in case of encapsulated uncompressed data
let frame_size = cols * rows * samples_per_pixel * (bits_allocated / 8);
data.chunks_exact(frame_size as usize)
.nth(frame)
.map(|frame| frame.to_vec())
.unwrap_or_default()
}
_ => {
let buffer = [data.as_slice()];
let dims = [cols.into(), rows.into(), 1];

decode_multi_frame_compressed(
&buffer,
&dims,
pi_type,
ts_type,
samples_per_pixel,
bits_allocated,
bits_stored,
high_bit,
pixel_representation as u16,
)
.map_err(gdcm_error_mapper)
.context(DecodePixelDataSnafu)?
.to_vec()
}
}
}
Value::Primitive(p) => {
// Uncompressed data
let frame_size = cols as usize
* rows as usize
* samples_per_pixel as usize
* (bits_allocated as usize / 8);
p.to_bytes()
.chunks_exact(frame_size)
.nth(frame as usize)
.map(|frame| frame.to_vec())
.unwrap_or_default()
}
Value::Sequence(_) => InvalidPixelDataSnafu.fail()?,
};

// Convert to PlanarConfiguration::Standard
let decoded_pixel_data = if planar_configuration == 1 && samples_per_pixel == 3 {
interleave_planes(
cols as usize,
rows as usize,
bits_allocated as usize,
decoded_pixel_data,
)
} else {
decoded_pixel_data
};

let window = match (
window_center(self).context(GetAttributeSnafu)?,
window_width(self).context(GetAttributeSnafu)?,
) {
(Some(center), Some(width)) => Some(WindowLevel { center, width }),
_ => None,
};

Ok(DecodedPixelData {
data: Cow::from(decoded_pixel_data),
cols: cols.into(),
rows: rows.into(),
number_of_frames: 1,
photometric_interpretation,
samples_per_pixel,
planar_configuration: PlanarConfiguration::Standard,
bits_allocated,
bits_stored,
high_bit,
pixel_representation,
rescale_intercept,
rescale_slope,
voi_lut_function,
window,
})
}
}

fn interleave_planes(cols: usize, rows: usize, bits_allocated: usize, data: Vec<u8>) -> Vec<u8> {
let frame_size = cols * rows * (bits_allocated / 8);
let mut interleaved = Vec::with_capacity(data.len());

let mut i = 0;
while i < frame_size {
interleaved.push(data[i]);
if bits_allocated > 8 {
interleaved.push(data[i + 1])
}

interleaved.push(data[i + frame_size]);
if bits_allocated > 8 {
interleaved.push(data[i + frame_size + 1])
}

interleaved.push(data[i + frame_size * 2]);
if bits_allocated > 8 {
interleaved.push(data[i + frame_size * 2 + 1])
}

i = if bits_allocated > 8 { i + 2 } else { i + 1 };
}

interleaved
}

#[cfg(test)]
Expand Down Expand Up @@ -225,7 +407,7 @@ mod tests {
#[case("pydicom/SC_rgb_rle_2frame.dcm", 1)]
#[case("pydicom/JPEG2000.dcm", 0)]
#[case("pydicom/JPEG2000_UNC.dcm", 0)]
fn test_parse_dicom_pixel_data_individual_frames(#[case] value: &str, #[case] frame: u32) {
fn test_parse_dicom_pixel_data_individual_frames(#[case] value: &str, #[case] frame: u32) {
let test_file = dicom_test_files::path(value).unwrap();
println!("Parsing pixel data for {}", test_file.display());
let obj = open_file(test_file).unwrap();
Expand Down

0 comments on commit a35752d

Please sign in to comment.