use itertools::Itertools;
use smallvec::SmallVec;
use snafu::{OptionExt, ResultExt};
use std::fs::File;
use std::io::{BufReader, Read};
use std::path::Path;
use std::{collections::BTreeMap, io::Write};
use crate::file::ReadPreamble;
use crate::{meta::FileMetaTable, FileMetaTableBuilder};
use crate::{
BuildMetaTableSnafu, CreateParserSnafu, CreatePrinterSnafu, DicomObject, FileDicomObject,
MissingElementValueSnafu, NoSuchAttributeNameSnafu, NoSuchDataElementAliasSnafu,
NoSuchDataElementTagSnafu, OpenFileSnafu, ParseMetaDataSetSnafu, PrematureEndSnafu,
PrepareMetaTableSnafu, PrintDataSetSnafu, ReadFileSnafu, ReadPreambleBytesSnafu,
ReadTokenSnafu, Result, UnexpectedTokenSnafu, UnsupportedTransferSyntaxSnafu,
};
use dicom_core::dictionary::{DataDictionary, DictionaryEntry};
use dicom_core::header::{HasLength, Header};
use dicom_core::value::{Value, C};
use dicom_core::{DataElement, Length, Tag, VR};
use dicom_dictionary_std::{tags, StandardDataDictionary};
use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
use dicom_encoding::{encode::EncodeTo, text::SpecificCharacterSet, TransferSyntax};
use dicom_parser::dataset::{DataSetReader, DataToken};
use dicom_parser::{
dataset::{read::Error as ParserError, DataSetWriter, IntoTokens},
StatefulDecode,
};
use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
pub type InMemElement<D = StandardDataDictionary> = DataElement<InMemDicomObject<D>, InMemFragment>;
pub type InMemFragment = Vec<u8>;
type ParserResult<T> = std::result::Result<T, ParserError>;
#[derive(Debug, Clone)]
pub struct InMemDicomObject<D = StandardDataDictionary> {
entries: BTreeMap<Tag, InMemElement<D>>,
dict: D,
len: Length,
}
impl<D> PartialEq for InMemDicomObject<D> {
fn eq(&self, other: &Self) -> bool {
self.entries == other.entries
}
}
impl<D> HasLength for InMemDicomObject<D> {
fn length(&self) -> Length {
self.len
}
}
impl<'s, D: 's> DicomObject for &'s InMemDicomObject<D>
where
D: DataDictionary,
D: Clone,
{
type Element = &'s InMemElement<D>;
fn element(&self, tag: Tag) -> Result<Self::Element> {
self.entries
.get(&tag)
.context(NoSuchDataElementTagSnafu { tag })
}
fn element_by_name(&self, name: &str) -> Result<Self::Element> {
let tag = self.lookup_name(name)?;
self.element(tag)
}
}
impl FileDicomObject<InMemDicomObject<StandardDataDictionary>> {
pub fn open_file<P: AsRef<Path>>(path: P) -> Result<Self> {
Self::open_file_with_dict(path, StandardDataDictionary)
}
pub fn from_reader<S>(src: S) -> Result<Self>
where
S: Read,
{
Self::from_reader_with_dict(src, StandardDataDictionary)
}
}
impl InMemDicomObject<StandardDataDictionary> {
#[deprecated(since = "0.5.0", note = "Use `new_empty` instead")]
pub fn create_empty() -> Self {
Self::new_empty()
}
pub fn new_empty() -> Self {
InMemDicomObject {
entries: BTreeMap::new(),
dict: StandardDataDictionary,
len: Length::UNDEFINED,
}
}
#[inline]
pub fn from_element_source<I>(iter: I) -> Result<Self>
where
I: IntoIterator<Item = Result<InMemElement<StandardDataDictionary>>>,
{
Self::from_element_source_with_dict(iter, StandardDataDictionary)
}
#[inline]
pub fn from_element_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = InMemElement<StandardDataDictionary>>,
{
Self::from_iter_with_dict(iter, StandardDataDictionary)
}
#[inline]
pub fn read_dataset<S>(decoder: S) -> Result<Self>
where
S: StatefulDecode,
{
Self::read_dataset_with_dict(decoder, StandardDataDictionary)
}
#[inline]
pub fn read_dataset_with_ts_cs<S>(
from: S,
ts: &TransferSyntax,
cs: SpecificCharacterSet,
) -> Result<Self>
where
S: Read,
{
Self::read_dataset_with_dict_ts_cs(from, StandardDataDictionary, ts, cs)
}
#[inline]
pub fn read_dataset_with_ts<S>(from: S, ts: &TransferSyntax) -> Result<Self>
where
S: Read,
{
Self::read_dataset_with_dict_ts_cs(
from,
StandardDataDictionary,
ts,
SpecificCharacterSet::Default,
)
}
}
impl<D> FileDicomObject<InMemDicomObject<D>>
where
D: DataDictionary,
D: Clone,
{
pub fn new_empty_with_dict_and_meta(dict: D, meta: FileMetaTable) -> Self {
FileDicomObject {
meta,
obj: InMemDicomObject {
entries: BTreeMap::new(),
dict,
len: Length::UNDEFINED,
},
}
}
pub fn open_file_with_dict<P: AsRef<Path>>(path: P, dict: D) -> Result<Self> {
Self::open_file_with(path, dict, TransferSyntaxRegistry)
}
pub fn open_file_with<P: AsRef<Path>, R>(path: P, dict: D, ts_index: R) -> Result<Self>
where
P: AsRef<Path>,
R: TransferSyntaxIndex,
{
Self::open_file_with_all_options(path, dict, ts_index, None, ReadPreamble::Auto)
}
pub(crate) fn open_file_with_all_options<P: AsRef<Path>, R>(
path: P,
dict: D,
ts_index: R,
read_until: Option<Tag>,
read_preamble: ReadPreamble,
) -> Result<Self>
where
P: AsRef<Path>,
R: TransferSyntaxIndex,
{
let path = path.as_ref();
let mut file =
BufReader::new(File::open(path).with_context(|_| OpenFileSnafu { filename: path })?);
if read_preamble == ReadPreamble::Auto || read_preamble == ReadPreamble::Always {
let mut buf = [0u8; 128];
file.read_exact(&mut buf)
.with_context(|_| ReadFileSnafu { filename: path })?;
}
let meta = FileMetaTable::from_reader(&mut file).context(ParseMetaDataSetSnafu)?;
if let Some(ts) = ts_index.get(&meta.transfer_syntax) {
let cs = SpecificCharacterSet::Default;
let mut dataset =
DataSetReader::new_with_ts_cs(file, ts, cs).context(CreateParserSnafu)?;
Ok(FileDicomObject {
meta,
obj: InMemDicomObject::build_object(
&mut dataset,
dict,
false,
Length::UNDEFINED,
read_until,
)?,
})
} else {
UnsupportedTransferSyntaxSnafu {
uid: meta.transfer_syntax,
}
.fail()
}
}
pub fn from_reader_with_dict<S>(src: S, dict: D) -> Result<Self>
where
S: Read,
{
Self::from_reader_with(src, dict, TransferSyntaxRegistry)
}
pub fn from_reader_with<'s, S: 's, R>(src: S, dict: D, ts_index: R) -> Result<Self>
where
S: Read,
R: TransferSyntaxIndex,
{
Self::from_reader_with_all_options(src, dict, ts_index, None, ReadPreamble::Auto)
}
pub(crate) fn from_reader_with_all_options<'s, S: 's, R>(
src: S,
dict: D,
ts_index: R,
read_until: Option<Tag>,
read_preamble: ReadPreamble,
) -> Result<Self>
where
S: Read,
R: TransferSyntaxIndex,
{
let mut file = BufReader::new(src);
if read_preamble == ReadPreamble::Always {
let mut buf = [0u8; 128];
file.read_exact(&mut buf).context(ReadPreambleBytesSnafu)?;
}
let meta = FileMetaTable::from_reader(&mut file).context(ParseMetaDataSetSnafu)?;
if let Some(ts) = ts_index.get(&meta.transfer_syntax) {
let cs = SpecificCharacterSet::Default;
let mut dataset =
DataSetReader::new_with_ts_cs(file, ts, cs).context(CreateParserSnafu)?;
let obj = InMemDicomObject::build_object(
&mut dataset,
dict,
false,
Length::UNDEFINED,
read_until,
)?;
Ok(FileDicomObject { meta, obj })
} else {
UnsupportedTransferSyntaxSnafu {
uid: meta.transfer_syntax,
}
.fail()
}
}
}
impl FileDicomObject<InMemDicomObject<StandardDataDictionary>> {
pub fn new_empty_with_meta(meta: FileMetaTable) -> Self {
FileDicomObject {
meta,
obj: InMemDicomObject {
entries: BTreeMap::new(),
dict: StandardDataDictionary,
len: Length::UNDEFINED,
},
}
}
}
impl<D> InMemDicomObject<D>
where
D: DataDictionary,
D: Clone,
{
pub fn new_empty_with_dict(dict: D) -> Self {
InMemDicomObject {
entries: BTreeMap::new(),
dict,
len: Length::UNDEFINED,
}
}
pub fn from_element_source_with_dict<I>(iter: I, dict: D) -> Result<Self>
where
I: IntoIterator<Item = Result<InMemElement<D>>>,
{
let entries: Result<_> = iter.into_iter().map_ok(|e| (e.tag(), e)).collect();
Ok(InMemDicomObject {
entries: entries?,
dict,
len: Length::UNDEFINED,
})
}
pub fn from_iter_with_dict<I>(iter: I, dict: D) -> Self
where
I: IntoIterator<Item = InMemElement<D>>,
{
let entries = iter.into_iter().map(|e| (e.tag(), e)).collect();
InMemDicomObject {
entries,
dict,
len: Length::UNDEFINED,
}
}
pub fn read_dataset_with_dict<S>(decoder: S, dict: D) -> Result<Self>
where
S: StatefulDecode,
D: DataDictionary,
{
let mut dataset = DataSetReader::new(decoder, Default::default());
InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED, None)
}
#[inline]
pub fn read_dataset_with_dict_ts<S>(from: S, dict: D, ts: &TransferSyntax) -> Result<Self>
where
S: Read,
D: DataDictionary,
{
Self::read_dataset_with_dict_ts_cs(from, dict, ts, SpecificCharacterSet::Default)
}
pub fn read_dataset_with_dict_ts_cs<S>(
from: S,
dict: D,
ts: &TransferSyntax,
cs: SpecificCharacterSet,
) -> Result<Self>
where
S: Read,
D: DataDictionary,
{
let from = BufReader::new(from);
let mut dataset = DataSetReader::new_with_ts_cs(from, ts, cs).context(CreateParserSnafu)?;
InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED, None)
}
#[deprecated(
since = "0.5.3",
note = "Always returns None, see `FileDicomObject::meta` instead"
)]
pub fn meta(&self) -> Option<&FileMetaTable> {
None
}
pub fn element(&self, tag: Tag) -> Result<&InMemElement<D>> {
self.entries
.get(&tag)
.context(NoSuchDataElementTagSnafu { tag })
}
pub fn element_by_name(&self, name: &str) -> Result<&InMemElement<D>> {
let tag = self.lookup_name(name)?;
self.entries
.get(&tag)
.with_context(|| NoSuchDataElementAliasSnafu {
tag,
alias: name.to_string(),
})
}
pub fn element_opt(&self, tag: Tag) -> Result<Option<&InMemElement<D>>> {
match self.element(tag) {
Ok(e) => Ok(Some(e)),
Err(super::Error::NoSuchDataElementTag { .. }) => Ok(None),
Err(e) => Err(e),
}
}
pub fn element_by_name_opt(&self, name: &str) -> Result<Option<&InMemElement<D>>> {
match self.element_by_name(name) {
Ok(e) => Ok(Some(e)),
Err(super::Error::NoSuchDataElementAlias { .. }) => Ok(None),
Err(e) => Err(e),
}
}
pub fn put(&mut self, elt: InMemElement<D>) -> Option<InMemElement<D>> {
self.put_element(elt)
}
pub fn put_element(&mut self, elt: InMemElement<D>) -> Option<InMemElement<D>> {
self.entries.insert(elt.tag(), elt)
}
pub fn remove_element(&mut self, tag: Tag) -> bool {
self.entries.remove(&tag).is_some()
}
pub fn remove_element_by_name(&mut self, name: &str) -> Result<bool> {
let tag = self.lookup_name(name)?;
Ok(self.entries.remove(&tag).is_some())
}
pub fn take_element(&mut self, tag: Tag) -> Result<InMemElement<D>> {
self.entries
.remove(&tag)
.context(NoSuchDataElementTagSnafu { tag })
}
pub fn take_element_by_name(&mut self, name: &str) -> Result<InMemElement<D>> {
let tag = self.lookup_name(name)?;
self.entries
.remove(&tag)
.with_context(|| NoSuchDataElementAliasSnafu {
tag,
alias: name.to_string(),
})
}
pub fn retain(&mut self, mut f: impl FnMut(&InMemElement<D>) -> bool) {
self.entries.retain(|_, elem| f(elem));
}
pub fn write_dataset<W, E>(&self, to: W, encoder: E) -> Result<()>
where
W: Write,
E: EncodeTo<W>,
{
let mut dset_writer = DataSetWriter::new(to, encoder);
dset_writer
.write_sequence(self.into_tokens())
.context(PrintDataSetSnafu)?;
Ok(())
}
pub fn write_dataset_with_ts_cs<W>(
&self,
to: W,
ts: &TransferSyntax,
cs: SpecificCharacterSet,
) -> Result<()>
where
W: Write,
{
let mut dset_writer = DataSetWriter::with_ts_cs(to, ts, cs).context(CreatePrinterSnafu)?;
dset_writer
.write_sequence(self.into_tokens())
.context(PrintDataSetSnafu)?;
Ok(())
}
pub fn write_dataset_with_ts<W>(&self, to: W, ts: &TransferSyntax) -> Result<()>
where
W: Write,
{
self.write_dataset_with_ts_cs(to, ts, SpecificCharacterSet::Default)
}
pub fn with_exact_meta(self, meta: FileMetaTable) -> FileDicomObject<Self> {
FileDicomObject { meta, obj: self }
}
pub fn with_meta(self, mut meta: FileMetaTableBuilder) -> Result<FileDicomObject<Self>> {
if let Some(elem) = self.element_opt(tags::SOP_INSTANCE_UID)? {
meta = meta.media_storage_sop_instance_uid(
elem.value().to_str().context(PrepareMetaTableSnafu)?,
);
}
Ok(FileDicomObject {
meta: meta.build().context(BuildMetaTableSnafu)?,
obj: self,
})
}
pub fn iter(&self) -> impl Iterator<Item = &InMemElement<D>> + '_ {
self.into_iter()
}
pub fn tags(&self) -> impl Iterator<Item = Tag> + '_ {
self.entries.keys().copied()
}
fn build_object<I: ?Sized>(
dataset: &mut I,
dict: D,
in_item: bool,
len: Length,
read_until: Option<Tag>,
) -> Result<Self>
where
I: Iterator<Item = ParserResult<DataToken>>,
{
let mut entries: BTreeMap<Tag, InMemElement<D>> = BTreeMap::new();
while let Some(token) = dataset.next() {
let elem = match token.context(ReadTokenSnafu)? {
DataToken::PixelSequenceStart => {
if read_until
.map(|t| t <= Tag(0x7fe0, 0x0010))
.unwrap_or(false)
{
break;
}
let value = InMemDicomObject::build_encapsulated_data(&mut *dataset)?;
DataElement::new(Tag(0x7fe0, 0x0010), VR::OB, value)
}
DataToken::ElementHeader(header) => {
if read_until.map(|t| t <= header.tag).unwrap_or(false) {
break;
}
let next_token = dataset.next().context(MissingElementValueSnafu)?;
match next_token.context(ReadTokenSnafu)? {
DataToken::PrimitiveValue(v) => InMemElement::new_with_len(
header.tag,
header.vr,
header.len,
Value::Primitive(v),
),
token => {
return UnexpectedTokenSnafu { token }.fail();
}
}
}
DataToken::SequenceStart { tag, len } => {
if read_until.map(|t| t <= tag).unwrap_or(false) {
break;
}
let items = Self::build_sequence(tag, len, &mut *dataset, &dict)?;
DataElement::new_with_len(
tag,
VR::SQ,
len,
Value::Sequence { items, size: len },
)
}
DataToken::ItemEnd if in_item => {
return Ok(InMemDicomObject { entries, dict, len });
}
token => return UnexpectedTokenSnafu { token }.fail(),
};
entries.insert(elem.tag(), elem);
}
Ok(InMemDicomObject { entries, dict, len })
}
fn build_encapsulated_data<I>(dataset: I) -> Result<Value<InMemDicomObject<D>, InMemFragment>>
where
I: Iterator<Item = ParserResult<DataToken>>,
{
let mut offset_table = None;
let mut fragments = C::new();
for token in dataset {
match token.context(ReadTokenSnafu)? {
DataToken::OffsetTable(table) => {
offset_table = Some(table);
}
DataToken::ItemValue(data) => {
fragments.push(data);
}
DataToken::ItemEnd => {
if offset_table.is_none() {
offset_table = Some(Vec::new())
}
}
DataToken::ItemStart { len: _ } => { }
DataToken::SequenceEnd => {
break;
}
token @ DataToken::ElementHeader(_)
| token @ DataToken::PixelSequenceStart
| token @ DataToken::SequenceStart { .. }
| token @ DataToken::PrimitiveValue(_) => {
return UnexpectedTokenSnafu { token }.fail();
}
}
}
Ok(Value::PixelSequence {
fragments,
offset_table: offset_table.unwrap_or_default().into(),
})
}
fn build_sequence<I: ?Sized>(
_tag: Tag,
_len: Length,
dataset: &mut I,
dict: &D,
) -> Result<C<InMemDicomObject<D>>>
where
I: Iterator<Item = ParserResult<DataToken>>,
{
let mut items: C<_> = SmallVec::new();
while let Some(token) = dataset.next() {
match token.context(ReadTokenSnafu)? {
DataToken::ItemStart { len } => {
items.push(Self::build_object(
&mut *dataset,
dict.clone(),
true,
len,
None,
)?);
}
DataToken::SequenceEnd => {
return Ok(items);
}
token => return UnexpectedTokenSnafu { token }.fail(),
};
}
PrematureEndSnafu.fail()
}
fn lookup_name(&self, name: &str) -> Result<Tag> {
self.dict
.by_name(name)
.context(NoSuchAttributeNameSnafu { name })
.map(|e| e.tag())
}
}
impl<'a, D> IntoIterator for &'a InMemDicomObject<D> {
type Item = &'a InMemElement<D>;
type IntoIter = ::std::collections::btree_map::Values<'a, Tag, InMemElement<D>>;
fn into_iter(self) -> Self::IntoIter {
self.entries.values()
}
}
impl<D> IntoIterator for InMemDicomObject<D> {
type Item = InMemElement<D>;
type IntoIter = Iter<D>;
fn into_iter(self) -> Self::IntoIter {
Iter {
inner: self.entries.into_iter(),
}
}
}
#[derive(Debug)]
pub struct Iter<D> {
inner: ::std::collections::btree_map::IntoIter<Tag, InMemElement<D>>,
}
impl<D> Iterator for Iter<D> {
type Item = InMemElement<D>;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next().map(|x| x.1)
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.inner.size_hint()
}
fn count(self) -> usize {
self.inner.count()
}
}
impl<D> Extend<InMemElement<D>> for InMemDicomObject<D> {
fn extend<I>(&mut self, iter: I)
where
I: IntoIterator<Item = InMemElement<D>>,
{
self.entries.extend(iter.into_iter().map(|e| (e.tag(), e)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{meta::FileMetaTableBuilder, open_file, Error};
use byteordered::Endianness;
use dicom_core::chrono::FixedOffset;
use dicom_core::value::{DicomDate, DicomDateTime, DicomTime, PrimitiveValue};
use dicom_core::{
dicom_value,
header::{DataElementHeader, Length, VR},
};
use dicom_encoding::{
decode::{basic::BasicDecoder, implicit_le::ImplicitVRLittleEndianDecoder},
encode::{implicit_le::ImplicitVRLittleEndianEncoder, EncoderFor},
};
use dicom_parser::{dataset::IntoTokens, StatefulDecoder};
use tempfile;
fn assert_obj_eq<D>(obj1: &InMemDicomObject<D>, obj2: &InMemDicomObject<D>)
where
D: std::fmt::Debug,
{
assert_eq!(format!("{:?}", obj1), format!("{:?}", obj2))
}
#[test]
fn inmem_object_compare() {
let mut obj1 = InMemDicomObject::new_empty();
let mut obj2 = InMemDicomObject::new_empty();
assert_eq!(obj1, obj2);
let empty_patient_name = DataElement::empty(Tag(0x0010, 0x0010), VR::PN);
obj1.put(empty_patient_name.clone());
assert_ne!(obj1, obj2);
obj2.put(empty_patient_name.clone());
assert_obj_eq(&obj1, &obj2);
}
#[test]
fn inmem_object_read_dataset() {
let data_in = [
0x10, 0x00, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
];
let decoder = ImplicitVRLittleEndianDecoder::default();
let text = SpecificCharacterSet::Default;
let mut cursor = &data_in[..];
let parser = StatefulDecoder::new(
&mut cursor,
decoder,
BasicDecoder::new(Endianness::Little),
text,
);
let obj = InMemDicomObject::read_dataset(parser).unwrap();
let mut gt = InMemDicomObject::new_empty();
let patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
dicom_value!(Strs, ["Doe^John"]),
);
gt.put(patient_name);
assert_eq!(obj, gt);
}
#[test]
fn inmem_object_read_dataset_with_ts_cs() {
let data_in = [
0x10, 0x00, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
];
let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2").unwrap();
let cs = SpecificCharacterSet::Default;
let mut cursor = &data_in[..];
let obj = InMemDicomObject::read_dataset_with_dict_ts_cs(
&mut cursor,
StandardDataDictionary,
&ts,
cs,
)
.unwrap();
let mut gt = InMemDicomObject::new_empty();
let patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
dicom_value!(Strs, ["Doe^John"]),
);
gt.put(patient_name);
assert_eq!(obj, gt);
}
#[test]
fn inmem_object_read_dataset_saves_len() {
let data_in = [
0x08, 0x00, 0x05, 0x00, 0x0a, 0x00, 0x00, 0x00, b'I', b'S', b'O', b'_', b'I', b'R', b' ', b'1', b'0', b'0',
0x08, 0x00, 0x90, 0x00, 0x0c, 0x00, 0x00, 0x00, b'S', b'i', b'm', 0xF5, b'e', b's', b'^', b'J', b'o', 0xE3,
b'o', b' ',
];
let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2").unwrap();
let mut cursor = &data_in[..];
let obj =
InMemDicomObject::read_dataset_with_dict_ts(&mut cursor, StandardDataDictionary, &ts)
.unwrap();
let physician_name = obj.element(Tag(0x0008, 0x0090)).unwrap();
assert_eq!(physician_name.header().len, Length(12));
assert_eq!(physician_name.value().to_str().unwrap(), "Simões^João");
}
#[test]
fn inmem_object_write_dataset() {
let mut obj = InMemDicomObject::new_empty();
let patient_name =
DataElement::new(Tag(0x0010, 0x0010), VR::PN, dicom_value!(Str, "Doe^John"));
obj.put(patient_name);
let mut out = Vec::new();
let printer = EncoderFor::new(ImplicitVRLittleEndianEncoder::default());
obj.write_dataset(&mut out, printer).unwrap();
assert_eq!(
out,
&[
0x10, 0x00, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
][..],
);
}
#[test]
fn inmem_object_write_dataset_with_ts() {
let mut obj = InMemDicomObject::new_empty();
let patient_name =
DataElement::new(Tag(0x0010, 0x0010), VR::PN, dicom_value!(Str, "Doe^John"));
obj.put(patient_name);
let mut out = Vec::new();
let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2.1").unwrap();
obj.write_dataset_with_ts(&mut out, &ts).unwrap();
assert_eq!(
out,
&[
0x10, 0x00, 0x10, 0x00, b'P', b'N', 0x08, 0x00, b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
][..],
);
}
#[test]
fn inmem_object_write_dataset_with_ts_cs() {
let mut obj = InMemDicomObject::new_empty();
let patient_name =
DataElement::new(Tag(0x0010, 0x0010), VR::PN, dicom_value!(Str, "Doe^John"));
obj.put(patient_name);
let mut out = Vec::new();
let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2").unwrap();
let cs = SpecificCharacterSet::Default;
obj.write_dataset_with_ts_cs(&mut out, &ts, cs).unwrap();
assert_eq!(
out,
&[
0x10, 0x00, 0x10, 0x00, 0x08, 0x00, 0x00, 0x00, b'D', b'o', b'e', b'^', b'J', b'o', b'h', b'n',
][..],
);
}
#[test]
fn inmem_object_write_datetime_odd() {
let mut obj = InMemDicomObject::new_empty();
let instance_number =
DataElement::new(Tag(0x0020, 0x0013), VR::IS, PrimitiveValue::from(1_i32));
obj.put(instance_number);
let dt = DicomDateTime::from_date_and_time(
DicomDate::from_ymd(2022, 11, 22).unwrap(),
DicomTime::from_hms(18, 09, 35).unwrap(),
FixedOffset::east_opt(3600).unwrap(),
)
.unwrap();
let instance_coercion_date_time =
DataElement::new(Tag(0x0008, 0x0015), VR::DT, dicom_value!(DateTime, dt));
obj.put(instance_coercion_date_time);
let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2.1").unwrap();
let mut out = Vec::new();
obj.write_dataset_with_ts(&mut out, &ts)
.expect("should write DICOM data without errors");
assert_eq!(
out,
&[
0x08, 0x00, 0x15, 0x00, b'D', b'T', 0x14, 0x00, b'2', b'0', b'2', b'2', b'1', b'1', b'2', b'2', b'1', b'8', b'0', b'9', b'3', b'5', b'+', b'0', b'1', b'0', b'0', b' ', 0x20, 0x00, 0x13, 0x00, b'I', b'S', 0x02, 0x00, b'1', b' ' ][..],
);
}
#[test]
fn inmem_write_to_file_with_meta() {
let sop_uid = "1.4.645.212121";
let mut obj = InMemDicomObject::new_empty();
obj.put(DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
dicom_value!(Strs, ["Doe^John"]),
));
obj.put(DataElement::new(
Tag(0x0008, 0x0060),
VR::CS,
dicom_value!(Strs, ["CR"]),
));
obj.put(DataElement::new(
Tag(0x0008, 0x0018),
VR::UI,
dicom_value!(Strs, [sop_uid]),
));
let file_object = obj
.with_meta(
FileMetaTableBuilder::default()
.transfer_syntax("1.2.840.10008.1.2.1")
.media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
.media_storage_sop_instance_uid(sop_uid),
)
.unwrap();
let dir = tempfile::tempdir().unwrap();
let mut file_path = dir.into_path();
file_path.push(format!("{}.dcm", sop_uid));
file_object.write_to_file(&file_path).unwrap();
let saved_object = open_file(file_path).unwrap();
assert_eq!(file_object, saved_object);
}
#[test]
fn inmem_with_meta_infers_sop_instance_uid() {
let sop_uid = "1.4.645.252521";
let mut obj = InMemDicomObject::new_empty();
obj.put(DataElement::new(
tags::SOP_INSTANCE_UID,
VR::UI,
PrimitiveValue::from(sop_uid),
));
let file_object = obj
.with_meta(
FileMetaTableBuilder::default()
.transfer_syntax("1.2.840.10008.1.2.1")
.media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1"),
)
.unwrap();
let meta = file_object.meta();
assert_eq!(
meta.media_storage_sop_instance_uid
.trim_end_matches(|c| c == '\0'),
sop_uid.trim_end_matches(|c| c == '\0'),
);
}
#[test]
fn inmem_write_to_file_with_exact_meta() {
let sop_uid = "1.4.645.212121";
let mut obj = InMemDicomObject::new_empty();
obj.put(DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
dicom_value!(Strs, ["Doe^John"]),
));
obj.put(DataElement::new(
Tag(0x0008, 0x0060),
VR::CS,
dicom_value!(Strs, ["CR"]),
));
obj.put(DataElement::new(
Tag(0x0008, 0x0018),
VR::UI,
dicom_value!(Strs, [sop_uid]),
));
let file_object = obj.with_exact_meta(
FileMetaTableBuilder::default()
.transfer_syntax("1.2.840.10008.1.2.1")
.media_storage_sop_class_uid("1.2.840.10008.5.1.4.1.1.1")
.media_storage_sop_instance_uid(sop_uid)
.build()
.unwrap(),
);
let dir = tempfile::tempdir().unwrap();
let mut file_path = dir.into_path();
file_path.push(format!("{}.dcm", sop_uid));
file_object.write_to_file(&file_path).unwrap();
let saved_object = open_file(file_path).unwrap();
assert_eq!(file_object, saved_object);
}
#[test]
fn inmem_object_get() {
let another_patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
);
let mut obj = InMemDicomObject::new_empty();
obj.put(another_patient_name.clone());
let elem1 = (&obj).element(Tag(0x0010, 0x0010)).unwrap();
assert_eq!(elem1, &another_patient_name);
}
#[test]
fn inmem_object_get_opt() {
let another_patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
);
let mut obj = InMemDicomObject::new_empty();
obj.put(another_patient_name.clone());
let elem1 = obj.element_opt(Tag(0x0010, 0x0010)).unwrap();
assert_eq!(elem1, Some(&another_patient_name));
assert_eq!(obj.element_opt(Tag(0x0010, 0x0020)).unwrap(), None);
}
#[test]
fn inmem_object_get_by_name() {
let another_patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
);
let mut obj = InMemDicomObject::new_empty();
obj.put(another_patient_name.clone());
let elem1 = (&obj).element_by_name("PatientName").unwrap();
assert_eq!(elem1, &another_patient_name);
}
#[test]
fn inmem_object_get_by_name_opt() {
let another_patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
);
let mut obj = InMemDicomObject::new_empty();
obj.put(another_patient_name.clone());
let elem1 = obj.element_by_name_opt("PatientName").unwrap();
assert_eq!(elem1, Some(&another_patient_name));
assert_eq!(obj.element_by_name_opt("PatientID").unwrap(), None);
}
#[test]
fn inmem_object_take_element() {
let another_patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
);
let mut obj = InMemDicomObject::new_empty();
obj.put(another_patient_name.clone());
let elem1 = obj.take_element(Tag(0x0010, 0x0010)).unwrap();
assert_eq!(elem1, another_patient_name);
assert!(matches!(
obj.take_element(Tag(0x0010, 0x0010)),
Err(Error::NoSuchDataElementTag {
tag: Tag(0x0010, 0x0010),
..
})
));
}
#[test]
fn inmem_object_take_element_by_name() {
let another_patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
);
let mut obj = InMemDicomObject::new_empty();
obj.put(another_patient_name.clone());
let elem1 = obj.take_element_by_name("PatientName").unwrap();
assert_eq!(elem1, another_patient_name);
assert!(matches!(
obj.take_element_by_name("PatientName"),
Err(Error::NoSuchDataElementAlias {
tag: Tag(0x0010, 0x0010),
alias,
..
}) if alias == "PatientName"));
}
#[test]
fn inmem_object_remove_element() {
let another_patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
);
let mut obj = InMemDicomObject::new_empty();
obj.put(another_patient_name.clone());
assert!(obj.remove_element(Tag(0x0010, 0x0010)));
assert_eq!(obj.remove_element(Tag(0x0010, 0x0010)), false);
}
#[test]
fn inmem_object_remove_element_by_name() {
let another_patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
);
let mut obj = InMemDicomObject::new_empty();
obj.put(another_patient_name.clone());
assert!(obj.remove_element_by_name("PatientName").unwrap());
assert_eq!(obj.remove_element_by_name("PatientName").unwrap(), false);
}
#[test]
fn inmem_traverse_elements() {
let sop_uid = "1.4.645.212121";
let mut obj = InMemDicomObject::new_empty();
obj.put(DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
dicom_value!(Strs, ["Doe^John"]),
));
obj.put(DataElement::new(
Tag(0x0008, 0x0060),
VR::CS,
dicom_value!(Strs, ["CR"]),
));
obj.put(DataElement::new(
Tag(0x0008, 0x0018),
VR::UI,
dicom_value!(Strs, [sop_uid]),
));
{
let mut iter = obj.iter();
assert_eq!(
*iter.next().unwrap().header(),
DataElementHeader::new(Tag(0x0008, 0x0018), VR::UI, Length(sop_uid.len() as u32)),
);
assert_eq!(
*iter.next().unwrap().header(),
DataElementHeader::new(Tag(0x0008, 0x0060), VR::CS, Length(2)),
);
assert_eq!(
*iter.next().unwrap().header(),
DataElementHeader::new(Tag(0x0010, 0x0010), VR::PN, Length(8)),
);
}
let tags: Vec<_> = obj.tags().collect();
assert_eq!(
tags,
vec![
Tag(0x0008, 0x0018),
Tag(0x0008, 0x0060),
Tag(0x0010, 0x0010),
]
);
let mut iter = obj.into_iter();
assert_eq!(
iter.next(),
Some(DataElement::new(
Tag(0x0008, 0x0018),
VR::UI,
dicom_value!(Strs, [sop_uid]),
)),
);
assert_eq!(
iter.next(),
Some(DataElement::new(
Tag(0x0008, 0x0060),
VR::CS,
dicom_value!(Strs, ["CR"]),
)),
);
assert_eq!(
iter.next(),
Some(DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::from("Doe^John"),
)),
);
}
#[test]
fn inmem_empty_object_into_tokens() {
let obj = InMemDicomObject::new_empty();
let tokens = obj.into_tokens();
assert_eq!(tokens.count(), 0);
}
#[test]
fn inmem_shallow_object_from_tokens() {
let tokens = vec![
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0008, 0x0060),
vr: VR::CS,
len: Length(2),
}),
DataToken::PrimitiveValue(PrimitiveValue::Str("MG".to_owned())),
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0010, 0x0010),
vr: VR::PN,
len: Length(8),
}),
DataToken::PrimitiveValue(PrimitiveValue::Str("Doe^John".to_owned())),
];
let gt_obj = InMemDicomObject::from_element_iter(vec![
DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
),
DataElement::new(
Tag(0x0008, 0x0060),
VR::CS,
PrimitiveValue::Str("MG".to_string()),
),
]);
let obj = InMemDicomObject::build_object(
&mut tokens.into_iter().map(Result::Ok),
StandardDataDictionary,
false,
Length::UNDEFINED,
None,
)
.unwrap();
assert_obj_eq(&obj, >_obj);
}
#[test]
fn inmem_shallow_object_into_tokens() {
let patient_name = DataElement::new(
Tag(0x0010, 0x0010),
VR::PN,
PrimitiveValue::Str("Doe^John".to_string()),
);
let modality = DataElement::new(
Tag(0x0008, 0x0060),
VR::CS,
PrimitiveValue::Str("MG".to_string()),
);
let mut obj = InMemDicomObject::new_empty();
obj.put(patient_name);
obj.put(modality);
let tokens: Vec<_> = obj.into_tokens().collect();
assert_eq!(
tokens,
vec![
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0008, 0x0060),
vr: VR::CS,
len: Length(2),
}),
DataToken::PrimitiveValue(PrimitiveValue::Str("MG".to_owned())),
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0010, 0x0010),
vr: VR::PN,
len: Length(8),
}),
DataToken::PrimitiveValue(PrimitiveValue::Str("Doe^John".to_owned())),
]
);
}
#[test]
fn inmem_deep_object_from_tokens() {
use smallvec::smallvec;
let obj_1 = InMemDicomObject::from_element_iter(vec![
DataElement::new(Tag(0x0018, 0x6012), VR::US, Value::Primitive(1_u16.into())),
DataElement::new(Tag(0x0018, 0x6014), VR::US, Value::Primitive(2_u16.into())),
]);
let obj_2 = InMemDicomObject::from_element_iter(vec![DataElement::new(
Tag(0x0018, 0x6012),
VR::US,
Value::Primitive(4_u16.into()),
)]);
let gt_obj = InMemDicomObject::from_element_iter(vec![
DataElement::new(
Tag(0x0018, 0x6011),
VR::SQ,
Value::Sequence {
items: smallvec![obj_1, obj_2],
size: Length::UNDEFINED,
},
),
DataElement::new(Tag(0x0020, 0x4000), VR::LT, Value::Primitive("TEST".into())),
]);
let tokens: Vec<_> = vec![
DataToken::SequenceStart {
tag: Tag(0x0018, 0x6011),
len: Length::UNDEFINED,
},
DataToken::ItemStart {
len: Length::UNDEFINED,
},
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0018, 0x6012),
vr: VR::US,
len: Length(2),
}),
DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0018, 0x6014),
vr: VR::US,
len: Length(2),
}),
DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
DataToken::ItemEnd,
DataToken::ItemStart {
len: Length::UNDEFINED,
},
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0018, 0x6012),
vr: VR::US,
len: Length(2),
}),
DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
DataToken::ItemEnd,
DataToken::SequenceEnd,
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0020, 0x4000),
vr: VR::LT,
len: Length(4),
}),
DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
];
let obj = InMemDicomObject::build_object(
&mut tokens.into_iter().map(Result::Ok),
StandardDataDictionary,
false,
Length::UNDEFINED,
None,
)
.unwrap();
assert_obj_eq(&obj, >_obj);
}
#[test]
fn inmem_deep_object_into_tokens() {
use smallvec::smallvec;
let obj_1 = InMemDicomObject::from_element_iter(vec![
DataElement::new(Tag(0x0018, 0x6012), VR::US, Value::Primitive(1_u16.into())),
DataElement::new(Tag(0x0018, 0x6014), VR::US, Value::Primitive(2_u16.into())),
]);
let obj_2 = InMemDicomObject::from_element_iter(vec![DataElement::new(
Tag(0x0018, 0x6012),
VR::US,
Value::Primitive(4_u16.into()),
)]);
let main_obj = InMemDicomObject::from_element_iter(vec![
DataElement::new(
Tag(0x0018, 0x6011),
VR::SQ,
Value::Sequence {
items: smallvec![obj_1, obj_2],
size: Length::UNDEFINED,
},
),
DataElement::new(Tag(0x0020, 0x4000), VR::LT, Value::Primitive("TEST".into())),
]);
let tokens: Vec<_> = main_obj.into_tokens().collect();
assert_eq!(
tokens,
vec![
DataToken::SequenceStart {
tag: Tag(0x0018, 0x6011),
len: Length::UNDEFINED,
},
DataToken::ItemStart {
len: Length::UNDEFINED,
},
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0018, 0x6012),
vr: VR::US,
len: Length(2),
}),
DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0018, 0x6014),
vr: VR::US,
len: Length(2),
}),
DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
DataToken::ItemEnd,
DataToken::ItemStart {
len: Length::UNDEFINED,
},
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0018, 0x6012),
vr: VR::US,
len: Length(2),
}),
DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
DataToken::ItemEnd,
DataToken::SequenceEnd,
DataToken::ElementHeader(DataElementHeader {
tag: Tag(0x0020, 0x4000),
vr: VR::LT,
len: Length(4),
}),
DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
]
);
}
#[test]
fn inmem_encapsulated_pixel_data_from_tokens() {
use smallvec::smallvec;
let gt_obj = InMemDicomObject::from_element_iter(vec![DataElement::new(
Tag(0x7fe0, 0x0010),
VR::OB,
Value::PixelSequence {
fragments: smallvec![vec![0x33; 32]],
offset_table: Default::default(),
},
)]);
let tokens: Vec<_> = vec![
DataToken::PixelSequenceStart,
DataToken::ItemStart { len: Length(0) },
DataToken::ItemEnd,
DataToken::ItemStart { len: Length(32) },
DataToken::ItemValue(vec![0x33; 32]),
DataToken::ItemEnd,
DataToken::SequenceEnd,
];
let obj = InMemDicomObject::build_object(
&mut tokens.into_iter().map(Result::Ok),
StandardDataDictionary,
false,
Length::UNDEFINED,
None,
)
.unwrap();
assert_obj_eq(&obj, >_obj);
}
#[test]
fn inmem_encapsulated_pixel_data_into_tokens() {
use smallvec::smallvec;
let main_obj = InMemDicomObject::from_element_iter(vec![DataElement::new(
Tag(0x7fe0, 0x0010),
VR::OB,
Value::PixelSequence {
fragments: smallvec![vec![0x33; 32]],
offset_table: Default::default(),
},
)]);
let tokens: Vec<_> = main_obj.into_tokens().collect();
assert_eq!(
tokens,
vec![
DataToken::PixelSequenceStart,
DataToken::ItemStart { len: Length(0) },
DataToken::ItemEnd,
DataToken::ItemStart { len: Length(32) },
DataToken::ItemValue(vec![0x33; 32]),
DataToken::ItemEnd,
DataToken::SequenceEnd,
]
);
}
}