#![allow(clippy::derive_partial_eq_without_eq)]
pub mod collector;
pub mod file;
pub mod mem;
pub mod meta;
pub mod ops;
pub mod tokens;
pub use crate::collector::{DicomCollector, DicomCollectorOptions};
pub use crate::file::{from_reader, open_file, OpenFileOptions};
pub use crate::mem::InMemDicomObject;
pub use crate::meta::{FileMetaTable, FileMetaTableBuilder};
use dicom_core::ops::{AttributeSelector, AttributeSelectorStep};
use dicom_core::value::{DicomValueType, ValueType};
pub use dicom_core::Tag;
use dicom_core::{DataDictionary, DicomValue, PrimitiveValue};
use dicom_dictionary_std::uids;
pub use dicom_dictionary_std::StandardDataDictionary;
pub type DefaultDicomObject<D = StandardDataDictionary> = FileDicomObject<mem::InMemDicomObject<D>>;
use dicom_core::header::{GroupNumber, HasLength};
use dicom_encoding::adapters::{PixelDataObject, RawPixelData};
use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
use dicom_encoding::Codec;
use dicom_parser::dataset::{DataSetWriter, IntoTokens};
use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
use itertools::Either;
use meta::FileMetaAttribute;
use smallvec::SmallVec;
use snafu::{prelude::*, Backtrace};
use std::borrow::Cow;
use std::fs::File;
use std::io::{BufWriter, Write};
use std::path::Path;
pub const IMPLEMENTATION_CLASS_UID: &str = "2.25.269557681719719925684832053554655571250";
pub const IMPLEMENTATION_VERSION_NAME: &str = "DICOM-rs 0.9.1";
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum AttributeError {
#[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>>,
},
ConvertValue {
source: dicom_core::value::ConvertValueError,
},
NotDataSet,
DataSetItemOutOfBounds,
NotPixelData,
FragmentOutOfBounds,
}
pub trait DicomAttribute: DicomValueType {
type Item<'a>: HasLength
where
Self: 'a;
type PixelData<'a>
where
Self: 'a;
fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError>;
fn item(&self, index: u32) -> Result<Self::Item<'_>, AttributeError>;
fn num_items(&self) -> Option<u32>;
fn fragment(&self, index: u32) -> Result<Self::PixelData<'_>, AttributeError>;
fn num_fragments(&self) -> Option<u32> {
if DicomValueType::value_type(self) == ValueType::PixelSequence {
self.num_items()
} else {
None
}
}
fn to_str(&self) -> Result<Cow<'_, str>, AttributeError> {
Ok(Cow::Owned(self.to_primitive_value()?.to_str().to_string()))
}
fn to_u16(&self) -> Result<u16, AttributeError> {
self.to_primitive_value()?
.to_int()
.context(ConvertValueSnafu)
}
fn to_i32(&self) -> Result<i32, AttributeError> {
self.to_primitive_value()?
.to_int()
.context(ConvertValueSnafu)
}
fn to_u32(&self) -> Result<u32, AttributeError> {
self.to_primitive_value()?
.to_int()
.context(ConvertValueSnafu)
}
fn to_f32(&self) -> Result<f32, AttributeError> {
self.to_primitive_value()?
.to_float32()
.context(ConvertValueSnafu)
}
fn to_f64(&self) -> Result<f64, AttributeError> {
self.to_primitive_value()?
.to_float64()
.context(ConvertValueSnafu)
}
fn to_bytes(&self) -> Result<Cow<'_, [u8]>, AttributeError> {
Ok(Cow::Owned(self.to_primitive_value()?.to_bytes().to_vec()))
}
}
pub trait DicomObject {
type Attribute<'a>: DicomAttribute
where
Self: 'a;
type LeafAttribute<'a>: DicomAttribute
where
Self: 'a;
fn attr_opt(&self, tag: Tag) -> Result<Option<Self::Attribute<'_>>, AccessError>;
fn attr_by_name_opt(
&self,
name: &str,
) -> Result<Option<Self::Attribute<'_>>, AccessByNameError>;
fn attr(&self, tag: Tag) -> Result<Self::Attribute<'_>, AccessError> {
self.attr_opt(tag)?
.context(NoSuchDataElementTagSnafu { tag })
}
fn at(
&self,
selector: impl Into<AttributeSelector>,
) -> Result<Self::LeafAttribute<'_>, AtAccessError>;
fn attr_by_name(&self, name: &str) -> Result<Self::Attribute<'_>, AccessByNameError> {
self.attr_by_name_opt(name)?
.context(NoSuchAttributeNameSnafu { name })
}
}
impl<O, P> DicomAttribute for DicomValue<O, P>
where
O: HasLength,
for<'a> &'a O: HasLength,
{
type Item<'a>
= &'a O
where
Self: 'a,
O: 'a;
type PixelData<'a>
= &'a P
where
Self: 'a,
P: 'a;
#[inline]
fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError> {
match self {
DicomValue::Primitive(value) => Ok(value.clone()),
_ => Err(AttributeError::ConvertValue {
source: dicom_core::value::ConvertValueError {
requested: "primitive",
original: self.value_type(),
cause: None,
},
}),
}
}
#[inline]
fn item(&self, index: u32) -> Result<&O, AttributeError> {
let items = self.items().context(NotDataSetSnafu)?;
items
.get(index as usize)
.context(DataSetItemOutOfBoundsSnafu)
}
#[inline]
fn num_items(&self) -> Option<u32> {
match self {
DicomValue::PixelSequence(seq) => Some(seq.fragments().len() as u32),
DicomValue::Sequence(seq) => Some(seq.multiplicity()),
_ => None,
}
}
#[inline]
fn fragment(&self, index: u32) -> Result<Self::PixelData<'_>, AttributeError> {
match self {
DicomValue::PixelSequence(seq) => Ok(seq
.fragments()
.get(index as usize)
.context(FragmentOutOfBoundsSnafu)?),
_ => Err(AttributeError::NotPixelData),
}
}
#[inline]
fn to_str(&self) -> Result<Cow<'_, str>, AttributeError> {
DicomValue::to_str(self).context(ConvertValueSnafu)
}
#[inline]
fn to_bytes(&self) -> Result<Cow<'_, [u8]>, AttributeError> {
DicomValue::to_bytes(self).context(ConvertValueSnafu)
}
}
impl<'b, O, P> DicomAttribute for &'b DicomValue<O, P>
where
O: HasLength,
&'b O: HasLength,
O: Clone,
P: Clone,
{
type Item<'a>
= &'b O
where
Self: 'a,
O: 'a;
type PixelData<'a>
= &'b P
where
Self: 'a,
P: 'a;
#[inline]
fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError> {
match self {
DicomValue::Primitive(value) => Ok(value.clone()),
_ => Err(AttributeError::ConvertValue {
source: dicom_core::value::ConvertValueError {
requested: "primitive",
original: self.value_type(),
cause: None,
},
}),
}
}
#[inline]
fn item(&self, index: u32) -> Result<Self::Item<'_>, AttributeError> {
let items = self.items().context(NotDataSetSnafu)?;
items
.get(index as usize)
.context(DataSetItemOutOfBoundsSnafu)
}
#[inline]
fn num_items(&self) -> Option<u32> {
match self {
DicomValue::PixelSequence(seq) => Some(seq.fragments().len() as u32),
DicomValue::Sequence(seq) => Some(seq.multiplicity()),
_ => None,
}
}
#[inline]
fn fragment(&self, index: u32) -> Result<Self::PixelData<'_>, AttributeError> {
match self {
DicomValue::PixelSequence(seq) => seq
.fragments()
.get(index as usize)
.context(FragmentOutOfBoundsSnafu),
_ => Err(AttributeError::NotPixelData),
}
}
#[inline]
fn num_fragments(&self) -> Option<u32> {
match self {
DicomValue::PixelSequence(seq) => Some(seq.fragments().len() as u32),
_ => None,
}
}
#[inline]
fn to_str(&self) -> Result<Cow<'_, str>, AttributeError> {
DicomValue::to_str(self).context(ConvertValueSnafu)
}
#[inline]
fn to_bytes(&self) -> Result<Cow<'_, [u8]>, AttributeError> {
DicomValue::to_bytes(self).context(ConvertValueSnafu)
}
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum ReadError {
#[snafu(display("Could not open file '{}'", filename.display()))]
OpenFile {
filename: std::path::PathBuf,
backtrace: Backtrace,
source: std::io::Error,
},
#[snafu(display("Could not read from file '{}'", filename.display()))]
ReadFile {
filename: std::path::PathBuf,
backtrace: Backtrace,
source: std::io::Error,
},
ReadPreambleBytes {
backtrace: Backtrace,
source: std::io::Error,
},
#[snafu(display("Could not parse meta group data set"))]
ParseMetaDataSet {
#[snafu(backtrace)]
source: crate::meta::Error,
},
#[snafu(display("Could not parse sop attribute"))]
ParseSopAttribute {
#[snafu(source(from(dicom_core::value::ConvertValueError, Box::from)))]
source: Box<dicom_core::value::ConvertValueError>,
backtrace: Backtrace,
},
#[snafu(display("Could not create data set parser"))]
CreateParser {
#[snafu(backtrace)]
source: dicom_parser::dataset::read::Error,
},
#[snafu(display("Could not read data set token"))]
ReadToken {
#[snafu(backtrace)]
source: dicom_parser::dataset::read::Error,
},
#[snafu(display("Missing element value after header token"))]
MissingElementValue { backtrace: Backtrace },
#[snafu(display("Unrecognized transfer syntax `{}`", uid))]
ReadUnrecognizedTransferSyntax { uid: String, backtrace: Backtrace },
#[snafu(display("Unsupported reading for transfer syntax `{}` ({})", uid, name))]
ReadUnsupportedTransferSyntax {
uid: &'static str,
name: &'static str,
backtrace: Backtrace,
},
#[snafu(display(
"Unsupported reading for transfer syntax `{uid}` ({name}, try enabling feature `{feature_name}`)"
))]
ReadUnsupportedTransferSyntaxWithSuggestion {
uid: &'static str,
name: &'static str,
feature_name: &'static str,
backtrace: Backtrace,
},
#[snafu(display("Unexpected token {:?}", token))]
UnexpectedToken {
token: Box<dicom_parser::dataset::DataToken>,
backtrace: Backtrace,
},
#[snafu(display("Premature data set end"))]
PrematureEnd { backtrace: Backtrace },
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum WriteError {
#[snafu(display("Could not write to file '{}'", filename.display()))]
WriteFile {
filename: std::path::PathBuf,
backtrace: Backtrace,
source: std::io::Error,
},
#[snafu(display("Could not write object preamble"))]
WritePreamble {
backtrace: Backtrace,
source: std::io::Error,
},
#[snafu(display("Could not write magic code"))]
WriteMagicCode {
backtrace: Backtrace,
source: std::io::Error,
},
#[snafu(display("Could not create data set printer"))]
CreatePrinter {
#[snafu(backtrace)]
source: dicom_parser::dataset::write::Error,
},
#[snafu(display("Could not print meta group data set"))]
PrintMetaDataSet {
#[snafu(backtrace)]
source: crate::meta::Error,
},
#[snafu(display("Could not print data set"))]
PrintDataSet {
#[snafu(backtrace)]
source: dicom_parser::dataset::write::Error,
},
#[snafu(display("Unrecognized transfer syntax `{uid}`"))]
WriteUnrecognizedTransferSyntax { uid: String, backtrace: Backtrace },
#[snafu(display("Unsupported transfer syntax `{uid}` ({name})"))]
WriteUnsupportedTransferSyntax {
uid: &'static str,
name: &'static str,
backtrace: Backtrace,
},
#[snafu(display(
"Unsupported transfer syntax `{uid}` ({name}, try enabling feature `{feature_name}`)"
))]
WriteUnsupportedTransferSyntaxWithSuggestion {
uid: &'static str,
name: &'static str,
feature_name: &'static str,
backtrace: Backtrace,
},
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum PrivateElementError {
#[snafu(display("Group number must be odd, found {:#06x}", group))]
InvalidGroup { group: GroupNumber },
#[snafu(display("Private creator {} not found in group {:#06x}", creator, group))]
PrivateCreatorNotFound { creator: String, group: GroupNumber },
#[snafu(display(
"Private Creator {} found in group {:#06x}, but elem {:#06x} not found",
creator,
group,
elem
))]
ElementNotFound {
creator: String,
group: GroupNumber,
elem: u8,
},
#[snafu(display("No space available in group {:#06x}", group))]
NoSpace { group: GroupNumber },
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum AccessError {
#[snafu(display("No such data element with tag {}", tag))]
NoSuchDataElementTag { tag: Tag, backtrace: Backtrace },
}
impl AccessError {
pub fn into_access_by_name(self, alias: impl Into<String>) -> AccessByNameError {
match self {
AccessError::NoSuchDataElementTag { tag, backtrace } => {
AccessByNameError::NoSuchDataElementAlias {
tag,
alias: alias.into(),
backtrace,
}
}
}
}
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
#[snafu(visibility(pub(crate)))]
pub enum AtAccessError {
MissingSequence {
selector: AttributeSelector,
step_index: u32,
},
NotASequence {
selector: AttributeSelector,
step_index: u32,
},
MissingLeafElement { selector: AttributeSelector },
}
#[derive(Debug, Snafu)]
pub enum AccessByNameError {
#[snafu(display("No such data element {} (with tag {})", alias, tag))]
NoSuchDataElementAlias {
tag: Tag,
alias: String,
backtrace: Backtrace,
},
#[snafu(display("Unknown data attribute named `{}`", name))]
NoSuchAttributeName { name: String, backtrace: Backtrace },
}
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub enum WithMetaError {
BuildMetaTable {
#[snafu(backtrace)]
source: crate::meta::Error,
},
PrepareMetaTable {
#[snafu(source(from(dicom_core::value::ConvertValueError, Box::from)))]
source: Box<dicom_core::value::ConvertValueError>,
backtrace: Backtrace,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct FileDicomObject<O> {
meta: FileMetaTable,
obj: O,
}
impl<O> FileDicomObject<O> {
pub fn meta(&self) -> &FileMetaTable {
&self.meta
}
pub fn meta_mut(&mut self) -> &mut FileMetaTable {
&mut self.meta
}
pub fn update_meta(&mut self, f: impl FnOnce(&mut FileMetaTable)) {
f(&mut self.meta);
self.meta.update_information_group_length();
}
pub fn into_inner(self) -> O {
self.obj
}
}
impl<O> FileDicomObject<O>
where
for<'a> &'a O: IntoTokens,
{
pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<(), WriteError> {
let path = path.as_ref();
let file = File::create(path).context(WriteFileSnafu { filename: path })?;
let mut to = BufWriter::new(file);
to.write_all(&[0_u8; 128][..])
.context(WriteFileSnafu { filename: path })?;
to.write_all(b"DICM")
.context(WriteFileSnafu { filename: path })?;
self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
self.write_dataset_impl(to)
}
pub fn write_all(&self, to: impl Write) -> Result<(), WriteError> {
let mut to = BufWriter::new(to);
to.write_all(&[0_u8; 128][..]).context(WritePreambleSnafu)?;
to.write_all(b"DICM").context(WriteMagicCodeSnafu)?;
self.meta.write(&mut to).context(PrintMetaDataSetSnafu)?;
self.write_dataset_impl(to)
}
pub fn write_meta<W: Write>(&self, to: W) -> Result<(), WriteError> {
self.meta.write(to).context(PrintMetaDataSetSnafu)
}
pub fn write_dataset<W: Write>(&self, to: W) -> Result<(), WriteError> {
let to = BufWriter::new(to);
self.write_dataset_impl(to)
}
fn write_dataset_impl(&self, to: impl Write) -> Result<(), WriteError> {
let ts_uid = self.meta.transfer_syntax();
let ts = if let Some(ts) = TransferSyntaxRegistry.get(ts_uid) {
ts
} else {
return WriteUnrecognizedTransferSyntaxSnafu {
uid: ts_uid.to_string(),
}
.fail();
};
match ts.codec() {
Codec::Dataset(Some(adapter)) => {
let adapter = adapter.adapt_writer(Box::new(to));
let mut dset_writer =
DataSetWriter::with_ts(adapter, ts).context(CreatePrinterSnafu)?;
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;
dset_writer.flush().context(PrintDataSetSnafu)?;
Ok(())
}
Codec::Dataset(None) => {
if ts_uid == uids::DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN
|| ts_uid == uids::JPIP_REFERENCED_DEFLATE
|| ts_uid == uids::JPIPHTJ2K_REFERENCED_DEFLATE
{
return WriteUnsupportedTransferSyntaxWithSuggestionSnafu {
uid: ts.uid(),
name: ts.name(),
feature_name: "dicom-transfer-syntax-registry/deflate",
}
.fail();
}
WriteUnsupportedTransferSyntaxSnafu {
uid: ts.uid(),
name: ts.name(),
}
.fail()
}
Codec::None | Codec::EncapsulatedPixelData(..) => {
let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;
dset_writer.flush().context(PrintDataSetSnafu)?;
Ok(())
}
}
}
}
impl<O> ::std::ops::Deref for FileDicomObject<O> {
type Target = O;
fn deref(&self) -> &Self::Target {
&self.obj
}
}
impl<O> ::std::ops::DerefMut for FileDicomObject<O> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.obj
}
}
impl<L, R> DicomAttribute for Either<L, R>
where
L: DicomAttribute,
R: DicomAttribute,
{
type Item<'a>
= Either<L::Item<'a>, R::Item<'a>>
where
Self: 'a;
type PixelData<'a>
= Either<L::PixelData<'a>, R::PixelData<'a>>
where
Self: 'a;
fn to_primitive_value(&self) -> Result<PrimitiveValue, AttributeError> {
match self {
Either::Left(l) => l.to_primitive_value(),
Either::Right(r) => r.to_primitive_value(),
}
}
fn item(&self, index: u32) -> Result<Self::Item<'_>, AttributeError> {
match self {
Either::Left(l) => l.item(index).map(Either::Left),
Either::Right(r) => r.item(index).map(Either::Right),
}
}
fn num_items(&self) -> Option<u32> {
match self {
Either::Left(l) => l.num_items(),
Either::Right(r) => r.num_items(),
}
}
fn fragment(&self, index: u32) -> Result<Self::PixelData<'_>, AttributeError> {
match self {
Either::Left(l) => l.fragment(index).map(Either::Left),
Either::Right(r) => r.fragment(index).map(Either::Right),
}
}
}
impl<O> DicomObject for FileDicomObject<O>
where
O: DicomObject,
{
type Attribute<'a>
= Either<FileMetaAttribute<'a>, O::Attribute<'a>>
where
Self: 'a,
O: 'a;
type LeafAttribute<'a>
= Either<FileMetaAttribute<'a>, O::LeafAttribute<'a>>
where
Self: 'a,
O: 'a;
#[inline]
fn attr_opt(&self, tag: Tag) -> Result<Option<Self::Attribute<'_>>, AccessError> {
match tag {
Tag(0x0002, _) => {
let attr = self.meta.attr_opt(tag)?;
Ok(attr.map(Either::Left))
}
_ => {
let attr = self.obj.attr_opt(tag)?;
Ok(attr.map(Either::Right))
}
}
}
#[inline]
fn attr_by_name_opt(
&self,
name: &str,
) -> Result<Option<Self::Attribute<'_>>, AccessByNameError> {
match name {
"FileMetaInformationGroupLength"
| "FileMetaInformationVersion"
| "MediaStorageSOPClassUID"
| "MediaStorageSOPInstanceUID"
| "TransferSyntaxUID"
| "ImplementationClassUID"
| "ImplementationVersionName"
| "SourceApplicationEntityTitle"
| "SendingApplicationEntityTitle"
| "ReceivingApplicationEntityTitle"
| "PrivateInformationCreatorUID"
| "PrivateInformation" => {
let attr = self.meta.attr_by_name_opt(name)?;
Ok(attr.map(Either::Left))
}
_ => {
let attr = self.obj.attr_by_name_opt(name)?;
Ok(attr.map(Either::Right))
}
}
}
#[inline]
fn attr(&self, tag: Tag) -> Result<Self::Attribute<'_>, AccessError> {
match tag {
Tag(0x0002, _) => {
let attr = self.meta.attr(tag)?;
Ok(Either::Left(attr))
}
_ => {
let attr = self.obj.attr(tag)?;
Ok(Either::Right(attr))
}
}
}
fn at(
&self,
selector: impl Into<AttributeSelector>,
) -> Result<Self::LeafAttribute<'_>, AtAccessError> {
let selector: AttributeSelector = selector.into();
match selector.first_step() {
AttributeSelectorStep::Tag(tag @ Tag(0x0002, _)) => {
let attr = self
.meta
.attr(*tag)
.map_err(|_| AtAccessError::MissingLeafElement { selector })?;
Ok(Either::Left(attr))
}
_ => {
let attr = self.obj.at(selector)?;
Ok(Either::Right(attr))
}
}
}
}
impl<'s, O: 's> DicomObject for &'s FileDicomObject<O>
where
O: DicomObject,
{
type Attribute<'a>
= Either<FileMetaAttribute<'a>, O::Attribute<'a>>
where
Self: 'a,
O: 'a;
type LeafAttribute<'a>
= Either<FileMetaAttribute<'a>, O::LeafAttribute<'a>>
where
Self: 'a,
O: 'a;
#[inline]
fn attr_opt(&self, tag: Tag) -> Result<Option<Self::Attribute<'_>>, AccessError> {
(**self).attr_opt(tag)
}
#[inline]
fn attr_by_name_opt(
&self,
name: &str,
) -> Result<Option<Self::Attribute<'_>>, AccessByNameError> {
(**self).attr_by_name_opt(name)
}
#[inline]
fn attr(&self, tag: Tag) -> Result<Self::Attribute<'_>, AccessError> {
(**self).attr(tag)
}
#[inline]
fn attr_by_name(&self, name: &str) -> Result<Self::Attribute<'_>, AccessByNameError> {
(**self).attr_by_name(name)
}
#[inline]
fn at(
&self,
selector: impl Into<AttributeSelector>,
) -> Result<Self::LeafAttribute<'_>, AtAccessError> {
(**self).at(selector)
}
}
impl<O> IntoIterator for FileDicomObject<O>
where
O: IntoIterator,
{
type Item = <O as IntoIterator>::Item;
type IntoIter = <O as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
self.obj.into_iter()
}
}
impl<'a, O> IntoIterator for &'a FileDicomObject<O>
where
&'a O: IntoIterator,
{
type Item = <&'a O as IntoIterator>::Item;
type IntoIter = <&'a O as IntoIterator>::IntoIter;
fn into_iter(self) -> Self::IntoIter {
(&self.obj).into_iter()
}
}
impl<D> PixelDataObject for FileDicomObject<InMemDicomObject<D>>
where
D: DataDictionary + Clone,
{
fn transfer_syntax_uid(&self) -> &str {
self.meta.transfer_syntax()
}
fn rows(&self) -> Option<u16> {
(**self)
.get(dicom_dictionary_std::tags::ROWS)?
.uint16()
.ok()
}
fn cols(&self) -> Option<u16> {
(**self)
.get(dicom_dictionary_std::tags::COLUMNS)?
.uint16()
.ok()
}
fn samples_per_pixel(&self) -> Option<u16> {
(**self)
.get(dicom_dictionary_std::tags::SAMPLES_PER_PIXEL)?
.uint16()
.ok()
}
fn bits_allocated(&self) -> Option<u16> {
(**self)
.get(dicom_dictionary_std::tags::BITS_ALLOCATED)?
.uint16()
.ok()
}
fn bits_stored(&self) -> Option<u16> {
(**self)
.get(dicom_dictionary_std::tags::BITS_STORED)?
.uint16()
.ok()
}
fn photometric_interpretation(&self) -> Option<&str> {
(**self)
.get(dicom_dictionary_std::tags::PHOTOMETRIC_INTERPRETATION)?
.string()
.ok()
.map(|s| s.trim_end())
}
fn number_of_frames(&self) -> Option<u32> {
(**self)
.get(dicom_dictionary_std::tags::NUMBER_OF_FRAMES)?
.to_int()
.ok()
}
fn number_of_fragments(&self) -> Option<u32> {
let pixel_data = (**self).get(dicom_dictionary_std::tags::PIXEL_DATA)?;
match pixel_data.value() {
DicomValue::Primitive(_p) => Some(1),
DicomValue::PixelSequence(v) => Some(v.fragments().len() as u32),
DicomValue::Sequence(..) => None,
}
}
fn fragment(&self, fragment: usize) -> Option<Cow<'_, [u8]>> {
let pixel_data = (**self).get(dicom_dictionary_std::tags::PIXEL_DATA)?;
match pixel_data.value() {
DicomValue::PixelSequence(v) => Some(Cow::Borrowed(v.fragments()[fragment].as_ref())),
DicomValue::Primitive(p) if fragment == 0 => Some(p.to_bytes()),
_ => None,
}
}
fn offset_table(&self) -> Option<Cow<'_, [u32]>> {
let pixel_data = (**self).get(dicom_dictionary_std::tags::PIXEL_DATA)?;
match pixel_data.value() {
DicomValue::Primitive(_) => None,
DicomValue::Sequence(_) => None,
DicomValue::PixelSequence(seq) => Some(Cow::from(seq.offset_table())),
}
}
fn raw_pixel_data(&self) -> Option<RawPixelData> {
let pixel_data = (**self).get(dicom_dictionary_std::tags::PIXEL_DATA)?;
match pixel_data.value() {
DicomValue::Primitive(p) => {
let fragment = p.to_bytes().to_vec();
let mut fragments = SmallVec::new();
fragments.push(fragment);
Some(RawPixelData {
fragments,
offset_table: SmallVec::new(),
})
}
DicomValue::PixelSequence(v) => {
let (offset_table, fragments) = v.clone().into_parts();
Some(RawPixelData {
fragments,
offset_table,
})
}
DicomValue::Sequence(..) => None,
}
}
}
#[cfg(test)]
mod tests {
use dicom_core::{DataElement, PrimitiveValue, VR};
use dicom_dictionary_std::{tags, uids};
use crate::meta::FileMetaTableBuilder;
use crate::{AccessError, DicomAttribute as _, DicomObject, FileDicomObject, InMemDicomObject};
fn assert_type_not_too_large<T>(max_size: usize) {
let size = std::mem::size_of::<T>();
if size > max_size {
panic!(
"Type {} of byte size {} exceeds acceptable size {}",
std::any::type_name::<T>(),
size,
max_size
);
}
}
#[test]
fn errors_not_too_large() {
assert_type_not_too_large::<AccessError>(64);
}
#[test]
fn smoke_test() {
const FILE_NAME: &str = ".smoke-test.dcm";
let meta = FileMetaTableBuilder::new()
.transfer_syntax(
dicom_transfer_syntax_registry::entries::EXPLICIT_VR_LITTLE_ENDIAN.uid(),
)
.media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
.media_storage_sop_instance_uid("1.2.3.456")
.implementation_class_uid("1.2.345.6.7890.1.234")
.build()
.unwrap();
let obj = FileDicomObject::new_empty_with_meta(meta);
obj.write_to_file(FILE_NAME).unwrap();
let obj2 = FileDicomObject::open_file(FILE_NAME).unwrap();
assert_eq!(obj, obj2);
let _ = std::fs::remove_file(FILE_NAME);
}
#[test]
fn file_dicom_object_can_use_inner() {
let mut obj = InMemDicomObject::new_empty();
obj.put(DataElement::new(
dicom_dictionary_std::tags::PATIENT_NAME,
VR::PN,
PrimitiveValue::from("John Doe"),
));
let mut obj = obj
.with_meta(
FileMetaTableBuilder::new()
.media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.7")
.media_storage_sop_instance_uid("1.2.23456789")
.transfer_syntax("1.2.840.10008.1.2.1"),
)
.unwrap();
assert_eq!(
obj.element(tags::PATIENT_NAME)
.unwrap()
.value()
.to_str()
.unwrap(),
"John Doe",
);
assert_eq!(
DicomObject::attr(&obj, tags::PATIENT_NAME)
.unwrap()
.to_str()
.unwrap(),
"John Doe",
);
obj.take_element(tags::PATIENT_NAME).unwrap();
assert!(matches!(
obj.element(tags::PATIENT_NAME),
Err(AccessError::NoSuchDataElementTag { .. }),
));
}
#[test]
fn file_dicom_object_can_iterate_over_elements() {
let mut obj = InMemDicomObject::new_empty();
obj.put(DataElement::new(
dicom_dictionary_std::tags::PATIENT_NAME,
VR::PN,
PrimitiveValue::from("John Doe"),
));
obj.put(DataElement::new(
dicom_dictionary_std::tags::SOP_INSTANCE_UID,
VR::PN,
PrimitiveValue::from("1.2.987654321"),
));
let obj = obj
.with_meta(
FileMetaTableBuilder::new()
.media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.7")
.media_storage_sop_instance_uid("1.2.987654321")
.transfer_syntax("1.2.840.10008.1.2.1"),
)
.unwrap();
let mut iter = (&obj).into_iter();
assert_eq!(
iter.next().unwrap().header().tag,
dicom_dictionary_std::tags::SOP_INSTANCE_UID
);
assert_eq!(
iter.next().unwrap().header().tag,
dicom_dictionary_std::tags::PATIENT_NAME
);
assert_eq!(iter.next(), None);
let mut iter = obj.into_iter();
assert_eq!(
iter.next().unwrap().header().tag,
dicom_dictionary_std::tags::SOP_INSTANCE_UID
);
assert_eq!(
iter.next().unwrap().header().tag,
dicom_dictionary_std::tags::PATIENT_NAME
);
assert_eq!(iter.next(), None);
}
#[test]
pub fn file_dicom_can_update_meta() {
let meta = FileMetaTableBuilder::new()
.transfer_syntax(
dicom_transfer_syntax_registry::entries::EXPLICIT_VR_LITTLE_ENDIAN.uid(),
)
.media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
.media_storage_sop_instance_uid("2.25.280986007517028771599125034987786349815")
.implementation_class_uid("1.2.345.6.7890.1.234")
.build()
.unwrap();
let mut obj = FileDicomObject::new_empty_with_meta(meta);
obj.update_meta(|meta| {
meta.receiving_application_entity_title = Some("SOMETHING".to_string());
});
assert_eq!(
obj.meta().receiving_application_entity_title.as_deref(),
Some("SOMETHING"),
);
}
#[test]
fn dicom_object_api_on_file_dicom_object() {
use crate::{DicomAttribute as _, DicomObject as _};
let meta = FileMetaTableBuilder::new()
.transfer_syntax(uids::RLE_LOSSLESS)
.media_storage_sop_class_uid(uids::ENHANCED_MR_IMAGE_STORAGE)
.media_storage_sop_instance_uid("2.25.94766187067244888884745908966163363746")
.build()
.unwrap();
let obj = FileDicomObject::new_empty_with_meta(meta);
assert_eq!(
obj.attr(tags::TRANSFER_SYNTAX_UID)
.unwrap()
.to_str()
.unwrap(),
uids::RLE_LOSSLESS
);
let sop_class_uid = obj.attr_opt(tags::MEDIA_STORAGE_SOP_CLASS_UID).unwrap();
let sop_class_uid = sop_class_uid.as_ref().map(|v| v.to_str().unwrap());
assert_eq!(
sop_class_uid.as_deref(),
Some(uids::ENHANCED_MR_IMAGE_STORAGE)
);
assert_eq!(
obj.attr_by_name("MediaStorageSOPInstanceUID")
.unwrap()
.to_str()
.unwrap(),
"2.25.94766187067244888884745908966163363746"
);
assert_eq!(
obj.at(tags::MEDIA_STORAGE_SOP_INSTANCE_UID)
.unwrap()
.to_str()
.unwrap(),
"2.25.94766187067244888884745908966163363746"
);
}
#[test]
fn operations_api_on_primitive_values() {
use crate::DicomAttribute;
use dicom_dictionary_std::tags;
let obj = InMemDicomObject::from_element_iter([
DataElement::new(tags::PATIENT_NAME, VR::PN, PrimitiveValue::from("Doe^John")),
DataElement::new(tags::INSTANCE_NUMBER, VR::IS, PrimitiveValue::from("5")),
]);
let patient_name = obj.get(tags::PATIENT_NAME).unwrap().value();
assert_eq!(&DicomAttribute::to_str(patient_name).unwrap(), "Doe^John");
let instance_number = obj.get(tags::INSTANCE_NUMBER).unwrap().value();
assert_eq!(DicomAttribute::to_u32(instance_number).unwrap(), 5);
assert_eq!(patient_name.num_items(), None);
assert_eq!(patient_name.num_fragments(), None);
assert!(matches!(
patient_name.item(0),
Err(crate::AttributeError::NotDataSet)
));
assert_eq!(instance_number.num_items(), None);
assert_eq!(instance_number.num_fragments(), None);
assert!(matches!(
patient_name.item(0),
Err(crate::AttributeError::NotDataSet)
));
assert!(matches!(
instance_number.item(0),
Err(crate::AttributeError::NotDataSet)
));
}
}