Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
199 changes: 199 additions & 0 deletions pixeldata/src/attribute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ pub enum AttributeName {
LutDescriptor,
LutData,
LutExplanation,
OverlayRows,
OverlayColumns,
OverlayType,
OverlayOrigin,
OverlayBitsAllocated,
OverlayBitPosition,
OverlayData,
NumberOfFramesInOverlay,
ImageFrameOrigin,
}

impl std::fmt::Display for AttributeName {
Expand Down Expand Up @@ -769,6 +778,196 @@ pub fn voi_lut_sequence<D: DataDictionary + Clone>(
})
}

/// Get the Overlay Rows (60xx,0010) of the overlay plane in the given group
pub fn overlay_rows<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Result<u16> {
retrieve_required_u16(obj, Tag(group, 0x0010), AttributeName::OverlayRows)
}

/// Get the Overlay Columns (60xx,0011) of the overlay plane in the given group
pub fn overlay_columns<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Result<u16> {
retrieve_required_u16(obj, Tag(group, 0x0011), AttributeName::OverlayColumns)
}

/// Get the Overlay Type (60xx,0040) of the overlay plane in the given group
pub fn overlay_type<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Result<String> {
let name = AttributeName::OverlayType;
Ok(obj
.element_opt(Tag(group, 0x0040))
.context(RetrieveSnafu { name })?
.context(MissingRequiredSnafu { name })?
.string()
.context(CastValueSnafu { name })?
.trim_matches(|c: char| c.is_whitespace() || c == '\0')
.to_string())
}

/// Get the Overlay Origin (60xx,0050) of the overlay plane in the given group,
/// as the 1-based `[row, column]` of the image pixel
/// under the top left overlay pixel
/// (values may be zero or negative)
pub fn overlay_origin<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Result<[i32; 2]> {
let name = AttributeName::OverlayOrigin;
let origin = obj
.element_opt(Tag(group, 0x0050))
.context(RetrieveSnafu { name })?
.context(MissingRequiredSnafu { name })?
.to_multi_int::<i32>()
.context(ConvertValueSnafu { name })?;
ensure!(
origin.len() >= 2,
InvalidValueSnafu {
name,
value: format!("value with multiplicity {}", origin.len()),
}
);
Ok([origin[0], origin[1]])
}

/// Get the Overlay Bits Allocated (60xx,0100) of the overlay plane
/// in the given group
pub fn overlay_bits_allocated<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Result<u16> {
retrieve_required_u16(obj, Tag(group, 0x0100), AttributeName::OverlayBitsAllocated)
}

/// Get the Overlay Bit Position (60xx,0102) of the overlay plane
/// in the given group
pub fn overlay_bit_position<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Result<u16> {
retrieve_required_u16(obj, Tag(group, 0x0102), AttributeName::OverlayBitPosition)
}

/// Get the Number of Frames in Overlay (60xx,0015) of the overlay plane
/// in the given group,
/// returning 1 if it is not present
pub fn number_of_frames_in_overlay<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
Comment thread
feliwir marked this conversation as resolved.
) -> Result<u32> {
let name = AttributeName::NumberOfFramesInOverlay;
let elem = if let Some(elem) = obj
.element_opt(Tag(group, 0x0015))
.context(RetrieveSnafu { name })?
{
elem
} else {
return Ok(1);
};

if elem.is_empty() {
return Ok(1);
}

let integer = elem.to_int::<i32>().context(ConvertValueSnafu { name })?;

ensure!(
integer > 0,
InvalidValueSnafu {
name,
value: integer.to_string(),
}
);

Ok(integer as u32)
}

/// Get the Image Frame Origin (60xx,0051) of the overlay plane
/// in the given group,
/// as the 1-based number of the first image frame the overlay applies to,
/// returning 1 if it is not present
pub fn image_frame_origin<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Result<u32> {
let name = AttributeName::ImageFrameOrigin;
let elem = if let Some(elem) = obj
.element_opt(Tag(group, 0x0051))
.context(RetrieveSnafu { name })?
{
elem
} else {
return Ok(1);
};

if elem.is_empty() {
return Ok(1);
}

let integer = elem.to_int::<i32>().context(ConvertValueSnafu { name })?;

ensure!(
integer > 0,
InvalidValueSnafu {
name,
value: integer.to_string(),
}
);

Ok(integer as u32)
}

/// Get the Overlay Label (60xx,1500) of the overlay plane in the given group,
/// if it is present
pub fn overlay_label<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Option<String> {
obj.get(Tag(group, 0x1500))
.and_then(|e| e.string().ok())
.map(|s| {
s.trim_matches(|c: char| c.is_whitespace() || c == '\0')
.to_string()
})
}

/// Get the Overlay Description (60xx,0022) of the overlay plane
/// in the given group,
/// if it is present
pub fn overlay_description<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Option<String> {
obj.get(Tag(group, 0x0022))
.and_then(|e| e.string().ok())
.map(|s| {
s.trim_matches(|c: char| c.is_whitespace() || c == '\0')
.to_string()
})
}

/// Get the Overlay Data (60xx,3000) of the overlay plane in the given group
/// as a byte stream in little endian order,
/// returning `None` if the element is not present
pub fn overlay_data<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
group: u16,
) -> Result<Option<std::borrow::Cow<'_, [u8]>>> {
let name = AttributeName::OverlayData;
match obj
.element_opt(Tag(group, 0x3000))
.context(RetrieveSnafu { name })?
{
Some(elem) => Ok(Some(elem.to_bytes().context(ConvertValueSnafu { name })?)),
None => Ok(None),
}
}

#[cfg(test)]
mod tests {
use super::rescale_intercept;
Expand Down
8 changes: 8 additions & 0 deletions pixeldata/src/gdcm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,14 @@ where
enforce_frame_fg_vm_match: false,
})
}

fn decode_overlays(&self) -> Result<Vec<crate::OverlayPlane>> {
crate::overlay::decode_overlays(self)
}

fn decode_overlay(&self, index: u8) -> Result<Option<crate::OverlayPlane>> {
crate::overlay::decode_overlay_group(self, 0x6000 + 2 * index as u16)
}
}

fn interleave_planes(cols: usize, rows: usize, bits_allocated: usize, data: Vec<u8>) -> Vec<u8> {
Expand Down
50 changes: 50 additions & 0 deletions pixeldata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ pub use ndarray;

mod attribute;
mod lut;
mod overlay;
mod transcode;

pub mod encapsulation;
Expand All @@ -169,6 +170,7 @@ pub use attribute::{
AttributeName, PhotometricInterpretation, PixelRepresentation, PlanarConfiguration,
};
pub use lut::{CreateLutError, Lut};
pub use overlay::{OverlayError, OverlayPlane, OverlayType};
pub use transcode::{Error as TranscodeError, Result as TranscodeResult, Transcode};
pub use transform::{Rescale, VoiLutFunction, WindowLevel, WindowLevelTransform};

Expand Down Expand Up @@ -283,6 +285,11 @@ enum InnerError {
nr_frames: u32,
backtrace: Backtrace,
},
#[snafu(transparent)]
Overlay {
#[snafu(backtrace)]
source: overlay::OverlayError,
},
}

pub type Result<T, E = Error> = std::result::Result<T, E>;
Expand All @@ -293,6 +300,12 @@ impl From<attribute::GetAttributeError> for crate::Error {
}
}

impl From<overlay::OverlayError> for crate::Error {
fn from(source: overlay::OverlayError) -> Self {
Error(crate::InnerError::Overlay { source })
}
}

/// Option set for converting decoded pixel data
/// into other common data structures,
/// such as a vector, an image, or a multidimensional array.
Expand Down Expand Up @@ -2120,6 +2133,35 @@ pub trait PixelDecoder {

Ok(px)
}

/// Decode all overlay planes in this object,
/// scanning the repeating groups `6000` to `601E`
/// (see [PS3.3 C.9.2][1] of the DICOM standard).
///
/// Overlay data is recorded outside the pixel data
/// and is always in native form,
/// so no pixel data codec is involved.
/// Legacy overlay planes (retired in DICOM 2004)
/// which are embedded in unused bits of the pixel data samples
/// are also decoded,
/// as long as the pixel data is not in an encapsulated form.
///
/// The default implementation yields no overlay planes.
///
/// [1]: https://dicom.nema.org/medical/dicom/current/output/chtml/part03/sect_C.9.2.html
fn decode_overlays(&self) -> Result<Vec<OverlayPlane>> {
Ok(Vec::new())
}

/// Decode the overlay plane with the given index
/// (0 to 15, for the repeating groups `6000` to `601E`),
/// returning `Ok(None)` if the plane is not present.
///
/// The default implementation yields no overlay plane.
fn decode_overlay(&self, index: u8) -> Result<Option<OverlayPlane>> {
let _ = index;
Ok(None)
}
}

/// Aggregator of key properties for imaging data,
Expand Down Expand Up @@ -2524,6 +2566,14 @@ where
enforce_frame_fg_vm_match: false,
})
}

fn decode_overlays(&self) -> Result<Vec<OverlayPlane>> {
overlay::decode_overlays(self)
}

fn decode_overlay(&self, index: u8) -> Result<Option<OverlayPlane>> {
overlay::decode_overlay_group(self, 0x6000 + 2 * index as u16)
}
}

#[cfg(test)]
Expand Down
Loading
Loading