use dicom_core::{DataDictionary, Tag};
use dicom_dictionary_std::tags;
use dicom_object::{mem::InMemElement, FileDicomObject, InMemDicomObject};
use snafu::{ensure, Backtrace, OptionExt, ResultExt, Snafu};
use std::fmt;
#[derive(Debug, Copy, Clone)]
#[non_exhaustive]
pub enum AttributeName {
Columns,
Rows,
BitsAllocated,
BitsStored,
HighBit,
NumberOfFrames,
PhotometricInterpretation,
PixelData,
PixelRepresentation,
PlanarConfiguration,
SamplesPerPixel,
VoiLutFunction,
WindowCenter,
WindowWidth,
}
impl std::fmt::Display for AttributeName {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
AttributeName::VoiLutFunction => f.write_str("VOILUTFunction"),
_ => std::fmt::Debug::fmt(self, f),
}
}
}
#[derive(Debug, Snafu)]
pub enum GetAttributeError {
#[snafu(display("Missing required attribute `{}`", name))]
MissingRequired {
name: AttributeName,
backtrace: Backtrace,
},
#[snafu(display("Could not retrieve attribute `{}`", name))]
Retrieve {
name: AttributeName,
#[snafu(backtrace)]
#[snafu(source(from(dicom_object::Error, Box::from)))]
source: Box<dicom_object::Error>,
},
#[snafu(display("Could not get attribute `{}`", name))]
CastValue {
name: AttributeName,
source: dicom_core::value::CastValueError,
backtrace: Backtrace,
},
#[snafu(display("Could not convert attribute `{}`", name))]
ConvertValue {
name: AttributeName,
#[snafu(source(from(dicom_core::value::ConvertValueError, Box::from)))]
source: Box<dicom_core::value::ConvertValueError>,
backtrace: Backtrace,
},
#[snafu(display("Semantically invalid value `{}` for attribute `{}`", value, name))]
InvalidValue {
name: AttributeName,
value: String,
backtrace: Backtrace,
},
}
pub type Result<T, E = GetAttributeError> = std::result::Result<T, E>;
pub fn cols<D: DataDictionary + Clone>(obj: &FileDicomObject<InMemDicomObject<D>>) -> Result<u16> {
retrieve_required_u16(obj, tags::COLUMNS, AttributeName::Columns)
}
pub fn rows<D: DataDictionary + Clone>(obj: &FileDicomObject<InMemDicomObject<D>>) -> Result<u16> {
retrieve_required_u16(obj, tags::ROWS, AttributeName::Rows)
}
pub fn voi_lut_function<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<Option<String>> {
let elem = if let Some(elem) =
obj.element_opt(tags::VOILUT_FUNCTION)
.context(RetrieveSnafu {
name: AttributeName::VoiLutFunction,
})? {
elem
} else {
return Ok(None);
};
let value = elem
.string()
.context(CastValueSnafu {
name: AttributeName::VoiLutFunction,
})?
.trim()
.to_string();
Ok(Some(value))
}
pub fn samples_per_pixel<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<u16> {
retrieve_required_u16(obj, tags::SAMPLES_PER_PIXEL, AttributeName::SamplesPerPixel)
}
pub fn bits_allocated<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<u16> {
retrieve_required_u16(obj, tags::BITS_ALLOCATED, AttributeName::BitsAllocated)
}
pub fn bits_stored<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<u16> {
retrieve_required_u16(obj, tags::BITS_STORED, AttributeName::BitsStored)
}
pub fn high_bit<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<u16> {
retrieve_required_u16(obj, tags::HIGH_BIT, AttributeName::HighBit)
}
pub fn pixel_data<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<&InMemElement<D>> {
let name = AttributeName::PixelData;
obj.element_opt(tags::PIXEL_DATA)
.context(RetrieveSnafu { name })?
.context(MissingRequiredSnafu { name })
}
pub fn rescale_intercept<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> f64 {
obj.element(tags::RESCALE_INTERCEPT)
.map_or(Ok(0.), |e| e.to_float64())
.unwrap_or(0.)
}
pub fn rescale_slope<D: DataDictionary + Clone>(obj: &FileDicomObject<InMemDicomObject<D>>) -> f64 {
obj.element(tags::RESCALE_SLOPE)
.map_or(Ok(1.0), |e| e.to_float64())
.unwrap_or(1.0)
}
pub fn number_of_frames<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<u32> {
let name = AttributeName::NumberOfFrames;
let elem = if let Some(elem) = obj
.element_opt(tags::NUMBER_OF_FRAMES)
.context(RetrieveSnafu { name })?
{
elem
} else {
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)
}
pub fn window_center<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<Option<f64>> {
retrieve_optional_to_f64(obj, tags::WINDOW_CENTER, AttributeName::WindowCenter)
}
pub fn window_width<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<Option<f64>> {
retrieve_optional_to_f64(obj, tags::WINDOW_WIDTH, AttributeName::WindowWidth)
}
#[inline]
fn retrieve_required_u16<D>(
obj: &FileDicomObject<InMemDicomObject<D>>,
tag: Tag,
name: AttributeName,
) -> Result<u16>
where
D: DataDictionary + Clone,
{
obj.element_opt(tag)
.context(RetrieveSnafu { name })?
.context(MissingRequiredSnafu { name })?
.uint16()
.context(CastValueSnafu { name })
}
#[inline]
fn retrieve_optional_to_f64<D>(
obj: &FileDicomObject<InMemDicomObject<D>>,
tag: Tag,
name: AttributeName,
) -> Result<Option<f64>>
where
D: DataDictionary + Clone,
{
match obj.element_opt(tag).context(RetrieveSnafu { name })? {
Some(e) => e.to_float64().context(ConvertValueSnafu { name }).map(Some),
None => Ok(None),
}
}
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
#[repr(u16)]
pub enum PixelRepresentation {
Unsigned = 0,
Signed = 1,
}
pub fn pixel_representation<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<PixelRepresentation> {
let p = retrieve_required_u16(
obj,
tags::PIXEL_REPRESENTATION,
AttributeName::PixelRepresentation,
)?;
match p {
0 => Ok(PixelRepresentation::Unsigned),
1 => Ok(PixelRepresentation::Signed),
_ => InvalidValueSnafu {
name: AttributeName::PixelRepresentation,
value: p.to_string(),
}
.fail(),
}
}
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
#[repr(u16)]
pub enum PlanarConfiguration {
Standard = 0,
PixelFirst = 1,
}
impl fmt::Display for PlanarConfiguration {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
(*self as u16).fmt(f)
}
}
#[cfg(not(feature = "gdcm"))]
pub fn planar_configuration<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<PlanarConfiguration> {
let elem = if let Some(elem) =
obj.element_opt(tags::PLANAR_CONFIGURATION)
.context(RetrieveSnafu {
name: AttributeName::PlanarConfiguration,
})? {
elem
} else {
return Ok(PlanarConfiguration::Standard);
};
let p = elem.to_int::<i16>().context(ConvertValueSnafu {
name: AttributeName::PlanarConfiguration,
})?;
match p {
0 => Ok(PlanarConfiguration::Standard),
1 => Ok(PlanarConfiguration::PixelFirst),
_ => InvalidValueSnafu {
name: AttributeName::PlanarConfiguration,
value: p.to_string(),
}
.fail(),
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum PhotometricInterpretation {
Monochrome1,
Monochrome2,
PaletteColor,
Rgb,
YbrFull,
YbrFull422,
YbrPartial420,
YbrIct,
YbrRct,
Other(String),
}
impl PhotometricInterpretation {
pub fn as_str(&self) -> &str {
self.as_ref()
}
pub fn is_monochrome(&self) -> bool {
matches!(
self,
PhotometricInterpretation::Monochrome1 | PhotometricInterpretation::Monochrome2
)
}
}
impl AsRef<str> for PhotometricInterpretation {
fn as_ref(&self) -> &str {
match self {
PhotometricInterpretation::Monochrome1 => "MONOCHROME1",
PhotometricInterpretation::Monochrome2 => "MONOCHROME2",
PhotometricInterpretation::PaletteColor => "PALETTE COLOR",
PhotometricInterpretation::Rgb => "RGB",
PhotometricInterpretation::YbrFull => "YBR_FULL",
PhotometricInterpretation::YbrFull422 => "YBR_FULL_422",
PhotometricInterpretation::YbrPartial420 => "YBR_PARTIAL_420",
PhotometricInterpretation::YbrIct => "YBR_ICT",
PhotometricInterpretation::YbrRct => "YBR_RCT",
PhotometricInterpretation::Other(s) => s,
}
}
}
impl From<String> for PhotometricInterpretation {
fn from(s: String) -> Self {
match s.as_str() {
"MONOCHROME1" => PhotometricInterpretation::Monochrome1,
"MONOCHROME2" => PhotometricInterpretation::Monochrome2,
"PALETTE COLOR" => PhotometricInterpretation::PaletteColor,
"RGB" => PhotometricInterpretation::Rgb,
"YBR_FULL" => PhotometricInterpretation::YbrFull,
"YBR_FULL_422" => PhotometricInterpretation::YbrFull422,
"YBR_PARTIAL_420" => PhotometricInterpretation::YbrPartial420,
"YBR_ICT" => PhotometricInterpretation::YbrIct,
"YBR_RCT" => PhotometricInterpretation::YbrRct,
_ => PhotometricInterpretation::Other(s),
}
}
}
impl From<&str> for PhotometricInterpretation {
fn from(s: &str) -> Self {
match s {
"MONOCHROME1" => PhotometricInterpretation::Monochrome1,
"MONOCHROME2" => PhotometricInterpretation::Monochrome2,
"PALETTE COLOR" => PhotometricInterpretation::PaletteColor,
"RGB" => PhotometricInterpretation::Rgb,
"YBR_FULL" => PhotometricInterpretation::YbrFull,
"YBR_FULL_422" => PhotometricInterpretation::YbrFull422,
"YBR_PARTIAL_420" => PhotometricInterpretation::YbrPartial420,
"YBR_ICT" => PhotometricInterpretation::YbrIct,
"YBR_RCT" => PhotometricInterpretation::YbrRct,
_ => PhotometricInterpretation::Other(s.to_string()),
}
}
}
impl fmt::Display for PhotometricInterpretation {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PhotometricInterpretation::Monochrome1 => f.write_str("MONOCHROME1"),
PhotometricInterpretation::Monochrome2 => f.write_str("MONOCHROME2"),
PhotometricInterpretation::PaletteColor => f.write_str("PALETTE COLOR"),
PhotometricInterpretation::Rgb => f.write_str("RGB"),
PhotometricInterpretation::YbrFull => f.write_str("YBR_FULL"),
PhotometricInterpretation::YbrFull422 => f.write_str("YBR_FULL_422"),
PhotometricInterpretation::YbrPartial420 => f.write_str("YBR_PARTIAL_420"),
PhotometricInterpretation::YbrIct => f.write_str("YBR_ICT"),
PhotometricInterpretation::YbrRct => f.write_str("YBR_RCT"),
PhotometricInterpretation::Other(s) => f.write_str(s),
}
}
}
pub fn photometric_interpretation<D: DataDictionary + Clone>(
obj: &FileDicomObject<InMemDicomObject<D>>,
) -> Result<PhotometricInterpretation> {
let name = AttributeName::PhotometricInterpretation;
Ok(obj
.element_opt(tags::PHOTOMETRIC_INTERPRETATION)
.context(RetrieveSnafu { name })?
.context(MissingRequiredSnafu { name })?
.string()
.context(CastValueSnafu { name })?
.trim()
.into())
}
#[cfg(test)]
mod tests {
#[test]
fn errors_are_not_too_large() {
let size = std::mem::size_of::<super::GetAttributeError>();
assert!(
size <= 64,
"GetAttributeError size is too large ({} > 64)",
size
);
}
}