use std::{
borrow::Cow,
fmt,
fs::File,
io::{BufRead, BufReader, Read, Seek},
path::Path,
};
use dicom_core::{
header::HasLength,
value::{PixelFragmentSequence, C},
DataDictionary, DataElement, DicomValue, Length, Tag, VR,
};
use dicom_dictionary_std::{tags, StandardDataDictionary};
use dicom_encoding::{decode::DecodeFrom, TransferSyntaxIndex};
use dicom_parser::{
dataset::{
lazy_read::{LazyDataSetReader, LazyDataSetReaderOptions},
DataToken, LazyDataToken,
},
DynStatefulDecoder, StatefulDecode, StatefulDecoder,
};
use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
use snafu::prelude::*;
use snafu::Backtrace;
use crate::{
file::ReadPreamble,
mem::{InMemElement, InMemFragment},
FileMetaTable, InMemDicomObject,
};
pub use dicom_parser::dataset::read::OddLengthStrategy;
pub use dicom_parser::stateful::decode::CharacterSetOverride;
pub type Result<T, E = Error> = std::result::Result<T, E>;
#[derive(Debug, Snafu)]
pub struct Error(InnerError);
#[derive(Debug, Snafu)]
#[non_exhaustive]
pub(crate) enum InnerError {
#[snafu(display("Could not open file '{}'", filename.display()))]
OpenFile {
filename: std::path::PathBuf,
backtrace: Backtrace,
source: std::io::Error,
},
ReadPreambleBytes {
backtrace: Backtrace,
source: std::io::Error,
},
CreateParser {
#[snafu(
backtrace,
source(from(dicom_parser::dataset::lazy_read::Error, Box::from))
)]
source: Box<dicom_parser::dataset::lazy_read::Error>,
},
ReadToken {
#[snafu(
backtrace,
source(from(dicom_parser::dataset::lazy_read::Error, Box::from))
)]
source: Box<dicom_parser::dataset::lazy_read::Error>,
},
IllegalStateStart { backtrace: Backtrace },
IllegalStateMeta { backtrace: Backtrace },
IllegalStateInPixel { backtrace: Backtrace },
MissingElementValue { backtrace: Backtrace },
UnrecognizedTransferSyntax {
ts_uid: String,
backtrace: Backtrace,
},
GuessTransferSyntax { backtrace: Backtrace },
#[snafu(display("Unexpected token {token:?}"))]
UnexpectedToken {
token: dicom_parser::dataset::LazyDataTokenRepr,
backtrace: Backtrace,
},
#[snafu(display("Unexpected data token {token:?}"))]
UnexpectedDataToken {
token: dicom_parser::dataset::DataToken,
backtrace: Backtrace,
},
#[snafu(display("Could not collect data in {tag}"))]
CollectDataValue {
tag: Tag,
#[snafu(backtrace, source(from(dicom_parser::dataset::Error, Box::from)))]
source: Box<dicom_parser::dataset::Error>,
},
PrematureEnd { backtrace: Backtrace },
BuildMetaTable {
#[snafu(backtrace, source(from(crate::meta::Error, Box::new)))]
source: Box<crate::meta::Error>,
},
ReadItem {
#[snafu(
backtrace,
source(from(dicom_parser::stateful::decode::Error, Box::from))
)]
source: Box<dicom_parser::stateful::decode::Error>,
},
}
#[derive(Debug, Default)]
pub struct DicomCollectorOptions<D = StandardDataDictionary, R = TransferSyntaxRegistry> {
dict: D,
ts_index: R,
ts_hint: Option<Cow<'static, str>>,
read_preamble: ReadPreamble,
odd_length: OddLengthStrategy,
charset_override: CharacterSetOverride,
}
impl DicomCollectorOptions {
pub fn new() -> Self {
Self::default()
}
}
impl<D, R> DicomCollectorOptions<D, R> {
pub fn dict<D2>(self, dict: D2) -> DicomCollectorOptions<D2, R> {
DicomCollectorOptions {
dict,
ts_index: self.ts_index,
ts_hint: self.ts_hint,
read_preamble: self.read_preamble,
odd_length: self.odd_length,
charset_override: self.charset_override,
}
}
pub fn ts_index<R2>(self, ts_index: R2) -> DicomCollectorOptions<D, R2> {
DicomCollectorOptions {
dict: self.dict,
ts_index,
ts_hint: self.ts_hint,
read_preamble: self.read_preamble,
odd_length: self.odd_length,
charset_override: self.charset_override,
}
}
pub fn expected_ts(mut self, ts_uid: impl Into<Cow<'static, str>>) -> Self {
self.ts_hint = Some(ts_uid.into());
self
}
pub fn unset_expected_ts(mut self) -> Self {
self.ts_hint = None;
self
}
pub fn read_preamble(mut self, option: ReadPreamble) -> Self {
self.read_preamble = option;
self
}
pub fn odd_length_strategy(mut self, option: OddLengthStrategy) -> Self {
self.odd_length = option;
self
}
pub fn charset_override(mut self, option: CharacterSetOverride) -> Self {
self.charset_override = option;
self
}
pub fn open_file(
self,
filename: impl AsRef<Path>,
) -> Result<DicomCollector<BufReader<File>, D, R>>
where
R: TransferSyntaxIndex,
{
let filename = filename.as_ref();
let reader = BufReader::new(File::open(filename).context(OpenFileSnafu { filename })?);
Ok(DicomCollector {
source: CollectionSource::new(
reader,
self.ts_index,
self.odd_length,
self.charset_override,
),
dictionary: self.dict,
ts_hint: self.ts_hint,
file_meta: None,
read_preamble: self.read_preamble,
state: Default::default(),
})
}
pub fn from_reader<S>(self, reader: BufReader<S>) -> DicomCollector<BufReader<S>, D, R>
where
S: Read + Seek,
R: TransferSyntaxIndex,
{
DicomCollector {
source: CollectionSource::new(
reader,
self.ts_index,
self.odd_length,
self.charset_override,
),
dictionary: self.dict,
ts_hint: self.ts_hint,
file_meta: None,
read_preamble: self.read_preamble,
state: Default::default(),
}
}
}
enum CollectionSource<S, R> {
Raw {
reader: Option<S>,
ts_index: R,
odd_length: OddLengthStrategy,
charset_override: CharacterSetOverride,
},
Parser(LazyDataSetReader<DynStatefulDecoder<S>>),
}
impl<S, R> fmt::Debug for CollectionSource<S, R>
where
R: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CollectionSource::Raw {
reader,
ts_index,
odd_length,
charset_override,
} => f
.debug_struct("Raw")
.field("ts_index", ts_index)
.field("odd_length", odd_length)
.field("charset_override", charset_override)
.field(
"reader",
&match reader {
Some(_) => "Some(_)",
None => "None",
},
)
.finish(),
CollectionSource::Parser(_) => f.write_str("Parser(..)"),
}
}
}
impl<S, R> CollectionSource<S, R>
where
S: Read + Seek,
R: TransferSyntaxIndex,
{
fn new(
raw_source: S,
ts_index: R,
odd_length: OddLengthStrategy,
charset_override: CharacterSetOverride,
) -> Self {
CollectionSource::Raw {
reader: Some(raw_source),
ts_index,
odd_length,
charset_override,
}
}
fn has_parser(&self) -> bool {
matches!(self, CollectionSource::Parser(_))
}
fn raw_reader_mut(&mut self) -> &mut S {
match self {
CollectionSource::Raw { reader, .. } => reader.as_mut().unwrap(),
CollectionSource::Parser(_) => {
panic!("cannot retrieve raw reader after setting parser")
}
}
}
fn set_parser_with_ts(
&mut self,
ts_uid: &str,
) -> Result<&mut LazyDataSetReader<DynStatefulDecoder<S>>> {
match self {
CollectionSource::Raw {
reader: src,
ts_index,
odd_length,
charset_override,
} => {
let src = src.take().unwrap();
let ts = ts_index
.get(ts_uid)
.context(UnrecognizedTransferSyntaxSnafu {
ts_uid: ts_uid.to_string(),
})?;
let mut options = LazyDataSetReaderOptions::default();
options.odd_length = *odd_length;
options.charset_override = *charset_override;
*self = CollectionSource::Parser(
LazyDataSetReader::new_with_ts_options(src, ts, options)
.context(CreateParserSnafu)?,
);
let CollectionSource::Parser(parser) = self else {
unreachable!();
};
Ok(parser)
}
CollectionSource::Parser(decoder) => Ok(decoder),
}
}
fn parser(&mut self) -> &mut LazyDataSetReader<DynStatefulDecoder<S>> {
match self {
CollectionSource::Raw { .. } => panic!("parser transfer syntax not set"),
CollectionSource::Parser(parser) => parser,
}
}
}
pub struct DicomCollector<S, D = StandardDataDictionary, R = TransferSyntaxRegistry> {
source: CollectionSource<S, R>,
dictionary: D,
ts_hint: Option<Cow<'static, str>>,
file_meta: Option<FileMetaTable>,
read_preamble: ReadPreamble,
state: CollectorState,
}
#[derive(Debug, Default, Copy, Clone, PartialEq)]
enum CollectorState {
#[default]
Start,
Preamble,
FileMeta,
InDataset,
InPixelData,
}
impl<S, D, R> fmt::Debug for DicomCollector<S, D, R>
where
D: fmt::Debug,
R: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("DicomCollector")
.field("source", &self.source)
.field("dictionary", &self.dictionary)
.field("ts_hint", &self.ts_hint)
.field(
"file_meta",
if self.file_meta.is_some() {
&"Some(...)"
} else {
&"None"
},
)
.field("read_preamble", &self.read_preamble)
.field("state", &self.state)
.finish()
}
}
impl<S> DicomCollector<BufReader<S>>
where
S: Read + Seek,
{
pub fn new(reader: BufReader<S>) -> Self {
DicomCollector {
source: CollectionSource::new(
reader,
TransferSyntaxRegistry,
Default::default(),
Default::default(),
),
dictionary: StandardDataDictionary,
ts_hint: None,
file_meta: None,
read_preamble: Default::default(),
state: Default::default(),
}
}
pub fn new_with_ts(
reader: BufReader<S>,
transfer_syntax: impl Into<Cow<'static, str>>,
) -> Self {
DicomCollector {
source: CollectionSource::new(
reader,
TransferSyntaxRegistry,
Default::default(),
Default::default(),
),
dictionary: StandardDataDictionary,
ts_hint: Some(transfer_syntax.into()),
file_meta: None,
read_preamble: Default::default(),
state: Default::default(),
}
}
}
impl DicomCollector<BufReader<File>> {
pub fn open_file(filename: impl AsRef<Path>) -> Result<Self> {
Self::open_file_with_dict(filename, StandardDataDictionary)
}
}
impl<D> DicomCollector<BufReader<File>, D>
where
D: DataDictionary + Clone,
{
pub fn open_file_with_dict(filename: impl AsRef<Path>, dict: D) -> Result<Self> {
let filename = filename.as_ref();
let reader = BufReader::new(File::open(filename).context(OpenFileSnafu { filename })?);
Ok(Self::new_with_dict(reader, dict))
}
}
impl<S, D> DicomCollector<BufReader<S>, D>
where
D: DataDictionary + Clone,
S: Read + Seek,
{
fn new_with_dict(reader: BufReader<S>, dictionary: D) -> Self {
DicomCollector {
source: CollectionSource::new(
reader,
TransferSyntaxRegistry,
Default::default(),
Default::default(),
),
dictionary,
ts_hint: None,
file_meta: None,
read_preamble: Default::default(),
state: Default::default(),
}
}
}
impl<S, D, R> DicomCollector<BufReader<S>, D, R>
where
D: DataDictionary + Clone,
S: Read + Seek,
R: TransferSyntaxIndex,
{
pub fn read_preamble(&mut self) -> Result<Option<[u8; 128]>> {
ensure!(self.state == CollectorState::Start, IllegalStateStartSnafu);
if self.read_preamble == ReadPreamble::Never {
self.state = CollectorState::Preamble;
return Ok(None);
}
let reader = self.source.raw_reader_mut();
let preamble = {
if self.read_preamble == ReadPreamble::Always {
let mut buf = [0; 128];
reader
.read_exact(&mut buf)
.context(ReadPreambleBytesSnafu)?;
Some(buf)
} else {
let buf = reader.fill_buf().context(ReadPreambleBytesSnafu)?;
if buf.len() < 4 {
return PrematureEndSnafu.fail().map_err(From::from);
}
if buf.len() >= 128 + 4 && &buf[128..132] == b"DICM" {
let out: [u8; 128] = std::convert::TryInto::try_into(&buf[0..128])
.expect("128 byte slice into array");
reader.consume(128);
Some(out)
} else if &buf[0..4] == b"DICM" {
None
} else {
let mut out = [0; 128];
reader
.read_exact(&mut out)
.context(ReadPreambleBytesSnafu)?;
Some(out)
}
}
};
self.state = CollectorState::Preamble;
Ok(preamble)
}
pub fn read_file_meta(&mut self) -> Result<&FileMetaTable> {
if self.state == CollectorState::Start {
self.read_preamble()?;
}
if self.state == CollectorState::Preamble {
let reader = self.source.raw_reader_mut();
self.file_meta = Some(FileMetaTable::from_reader(reader).context(BuildMetaTableSnafu)?);
self.state = CollectorState::FileMeta;
}
self.file_meta
.as_ref()
.context(IllegalStateMetaSnafu)
.map_err(From::from)
}
#[inline]
pub fn take_file_meta(&mut self) -> Option<FileMetaTable> {
self.file_meta.take()
}
pub fn read_dataset_to_end(&mut self, to: &mut InMemDicomObject<D>) -> Result<()> {
let parser = if !self.source.has_parser() {
let ts = {
if self.ts_hint.is_none() {
self.populate_ts_hint();
}
self.ts_hint.as_deref()
}
.context(GuessTransferSyntaxSnafu)?;
self.source.set_parser_with_ts(ts)?
} else {
self.source.parser()
};
Self::collect_to_object(&mut self.state, parser, false, None, to, &self.dictionary)
}
pub fn read_dataset_up_to(
&mut self,
stop_tag: Tag,
to: &mut InMemDicomObject<D>,
) -> Result<()> {
let parser = if !self.source.has_parser() {
let ts = {
if self.ts_hint.is_none() {
self.populate_ts_hint();
}
self.ts_hint.as_deref()
}
.context(GuessTransferSyntaxSnafu)?;
self.source.set_parser_with_ts(ts)?
} else {
self.source.parser()
};
Self::collect_to_object(
&mut self.state,
parser,
false,
Some(stop_tag),
to,
&self.dictionary,
)
}
#[inline]
pub fn read_dataset_up_to_pixeldata(&mut self, to: &mut InMemDicomObject<D>) -> Result<()> {
self.read_dataset_up_to(dicom_dictionary_std::tags::PIXEL_DATA, to)
}
pub fn read_next_fragment(&mut self, to: &mut Vec<u8>) -> Result<Option<u32>> {
if self.state == CollectorState::Start || self.state == CollectorState::Preamble {
self.read_file_meta()?;
}
if !self.source.has_parser() {
let ts = {
if self.ts_hint.is_none() {
self.populate_ts_hint();
}
self.ts_hint.as_deref()
}
.context(GuessTransferSyntaxSnafu)?;
self.source.set_parser_with_ts(ts)?;
} else {
self.source.parser();
}
if self.state != CollectorState::InPixelData {
self.skip_until(|token| {
match token {
LazyDataToken::ElementHeader(header)
if header.tag == tags::PIXEL_DATA && header.length().is_defined() =>
{
true
}
LazyDataToken::PixelSequenceStart => true,
_ => false,
}
})?;
self.state = CollectorState::InPixelData;
}
let parser = if !self.source.has_parser() {
let ts = {
if self.ts_hint.is_none() {
self.populate_ts_hint();
}
self.ts_hint.as_deref()
}
.context(GuessTransferSyntaxSnafu)?;
self.source.set_parser_with_ts(ts)?
} else {
self.source.parser()
};
while let Some(token) = parser.advance() {
match token.context(ReadTokenSnafu)? {
LazyDataToken::LazyValue { header, decoder } => {
debug_assert!(header.length().is_defined());
let len = header.length().0;
decoder.read_to_vec(len, to).context(ReadItemSnafu)?;
return Ok(Some(len));
}
LazyDataToken::LazyItemValue { len, decoder } => {
decoder.read_to_vec(len, to).context(ReadItemSnafu)?;
return Ok(Some(len));
}
LazyDataToken::ItemStart { len: Length(0) } => return Ok(Some(0)),
_ => {
}
}
}
Ok(None)
}
pub fn read_basic_offset_table(&mut self, to: &mut Vec<u32>) -> Result<Option<u32>> {
if self.state == CollectorState::InPixelData {
return IllegalStateInPixelSnafu.fail().map_err(From::from);
}
if self.state == CollectorState::Start || self.state == CollectorState::Preamble {
self.read_file_meta()?;
}
if !self.source.has_parser() {
let ts = {
if self.ts_hint.is_none() {
self.populate_ts_hint();
}
self.ts_hint.as_deref()
}
.context(GuessTransferSyntaxSnafu)?;
self.source.set_parser_with_ts(ts)?;
} else {
self.source.parser();
}
if self.state != CollectorState::InPixelData {
self.skip_until(|token| {
match token {
LazyDataToken::ElementHeader(header)
if header.tag == tags::PIXEL_DATA && header.length().is_defined() =>
{
true
}
LazyDataToken::PixelSequenceStart => true,
_ => false,
}
})?;
self.state = CollectorState::InPixelData;
}
let parser = if !self.source.has_parser() {
let ts = {
if self.ts_hint.is_none() {
self.populate_ts_hint();
}
self.ts_hint.as_deref()
}
.context(GuessTransferSyntaxSnafu)?;
self.source.set_parser_with_ts(ts)?
} else {
self.source.parser()
};
while let Some(token) = parser.advance() {
match token.context(ReadTokenSnafu)? {
LazyDataToken::LazyValue { .. } => {
return Ok(None);
}
LazyDataToken::LazyItemValue { len, decoder } => {
decoder.read_u32_to_vec(len, to).context(ReadItemSnafu)?;
return Ok(Some(len));
}
LazyDataToken::ItemStart { len: Length(0) } => return Ok(Some(0)),
_ => {
}
}
}
Ok(None)
}
#[inline]
fn populate_ts_hint(&mut self) {
if let Some(meta) = self.file_meta.as_ref() {
self.ts_hint = Some(Cow::Owned(meta.transfer_syntax().to_string()));
}
}
fn skip_until(
&mut self,
mut pred: impl FnMut(
&LazyDataToken<
&mut StatefulDecoder<Box<dyn DecodeFrom<BufReader<S>> + 'static>, BufReader<S>>,
>,
) -> bool,
) -> Result<bool> {
let parser = self.source.parser();
while let Some(token) = parser.advance() {
let token = token.context(ReadTokenSnafu)?;
if pred(&token) {
return Ok(true);
}
token.skip().context(ReadItemSnafu)?;
self.state = CollectorState::InDataset;
}
Ok(false)
}
fn collect_to_object(
state: &mut CollectorState,
token_src: &mut LazyDataSetReader<DynStatefulDecoder<BufReader<S>>>,
in_item: bool,
read_until: Option<Tag>,
to: &mut InMemDicomObject<D>,
dict: &D,
) -> Result<()> {
let mut elements = Vec::new();
Self::collect_elements(state, token_src, in_item, read_until, &mut elements, dict)?;
to.extend(elements);
Ok(())
}
fn collect_elements(
state: &mut CollectorState,
token_src: &mut LazyDataSetReader<DynStatefulDecoder<BufReader<S>>>,
in_item: bool,
read_until: Option<Tag>,
to: &mut Vec<DataElement<InMemDicomObject<D>>>,
dict: &D,
) -> Result<()> {
while let Some(token) = token_src.peek().context(ReadTokenSnafu)? {
let token = token.clone();
let elem = match token {
DataToken::PixelSequenceStart => {
if read_until
.map(|t| t <= Tag(0x7fe0, 0x0010))
.unwrap_or(false)
{
break;
}
*state = CollectorState::InPixelData;
token_src.advance();
let value = Self::build_encapsulated_data(&mut *token_src)?;
DataElement::new(Tag(0x7fe0, 0x0010), VR::OB, value)
}
DataToken::ElementHeader(header) => {
if read_until.map(|t| t <= header.tag).unwrap_or(false) {
break;
}
drop(token);
*state = CollectorState::InDataset;
token_src.advance();
let next_token = token_src.advance().context(MissingElementValueSnafu)?;
match next_token.context(ReadTokenSnafu)? {
token @ LazyDataToken::LazyValue { .. }
| token @ LazyDataToken::LazyItemValue { .. } => {
InMemElement::new_with_len(
header.tag,
header.vr,
header.len,
token
.into_value()
.context(CollectDataValueSnafu { tag: header.tag })?,
)
}
token => {
return UnexpectedTokenSnafu { token }.fail().map_err(From::from);
}
}
}
DataToken::SequenceStart { tag, len } => {
if read_until.map(|t| t <= tag).unwrap_or(false) {
break;
}
*state = CollectorState::InDataset;
token_src.advance();
let mut items = C::new();
Self::collect_sequence(
&mut *state,
tag,
len,
&mut *token_src,
dict,
&mut items,
)?;
DataElement::new_with_len(
tag,
VR::SQ,
len,
DicomValue::new_sequence(items, len),
)
}
DataToken::ItemEnd if in_item => {
token_src.advance();
return Ok(());
}
token => {
return UnexpectedDataTokenSnafu {
token: token.clone(),
}
.fail()
.map_err(From::from)
}
};
to.push(elem);
}
Ok(())
}
fn build_encapsulated_data(
dataset: &mut LazyDataSetReader<DynStatefulDecoder<BufReader<S>>>,
) -> Result<DicomValue<InMemDicomObject<D>, InMemFragment>> {
let mut offset_table = None;
let mut fragments = C::new();
let mut first = true;
while let Some(token) = dataset.advance() {
let token = token.context(ReadTokenSnafu)?;
match token {
LazyDataToken::LazyItemValue { decoder, len } => {
if first {
let mut table = Vec::new();
decoder
.read_u32_to_vec(len, &mut table)
.context(ReadItemSnafu)?;
first = false;
} else {
let mut data = Vec::new();
decoder.read_to_vec(len, &mut data).context(ReadItemSnafu)?;
fragments.push(data);
}
}
LazyDataToken::ItemEnd => {
if offset_table.is_none() {
offset_table = Some(Vec::new())
}
}
LazyDataToken::ItemStart { len: _ } => { }
LazyDataToken::SequenceEnd => {
break;
}
token @ LazyDataToken::ElementHeader(_)
| token @ LazyDataToken::PixelSequenceStart
| token @ LazyDataToken::SequenceStart { .. }
| token @ LazyDataToken::LazyValue { .. }
| token => {
return UnexpectedTokenSnafu { token }.fail().map_err(From::from);
}
}
}
Ok(DicomValue::from(PixelFragmentSequence::new(
offset_table.unwrap_or_default(),
fragments,
)))
}
fn collect_sequence(
state: &mut CollectorState,
_tag: Tag,
_len: Length,
token_src: &mut LazyDataSetReader<DynStatefulDecoder<BufReader<S>>>,
dict: &D,
items: &mut C<InMemDicomObject<D>>,
) -> Result<()> {
while let Some(token) = token_src.advance() {
match token.context(ReadTokenSnafu)? {
LazyDataToken::ItemStart { len: _ } => {
let mut obj = InMemDicomObject::new_empty_with_dict(dict.clone());
Self::collect_to_object(state, token_src, true, None, &mut obj, dict)?;
items.push(obj);
}
LazyDataToken::SequenceEnd => {
return Ok(());
}
token => return UnexpectedTokenSnafu { token }.fail().map_err(From::from),
};
}
PrematureEndSnafu.fail().map_err(From::from)
}
}
#[cfg(test)]
mod tests {
use std::io::{BufReader, Write};
use dicom_core::{prelude::*, value::DataSetSequence, PrimitiveValue};
use dicom_dictionary_std::{tags, uids, StandardDataDictionary};
use dicom_encoding::TransferSyntaxIndex;
use dicom_parser::dataset::read::OddLengthStrategy;
use dicom_transfer_syntax_registry::TransferSyntaxRegistry;
use crate::{
file::ReadPreamble, DicomCollectorOptions, FileMetaTable, FileMetaTableBuilder,
InMemDicomObject,
};
use super::DicomCollector;
#[test]
fn test_read_dataset_to_end_set_ts() {
let dataset1 = InMemDicomObject::<StandardDataDictionary>::from_element_iter([
DataElement::new(
tags::SOP_INSTANCE_UID,
VR::UI,
"2.25.51008724832548260562721775118239811861\0",
),
DataElement::new(
tags::SOP_CLASS_UID,
VR::UI,
uids::NUCLEAR_MEDICINE_IMAGE_STORAGE,
),
DataElement::new(tags::PATIENT_NAME, VR::PN, "Doe^John"),
DataElement::new(tags::STUDY_DESCRIPTION, VR::LO, "Test study"),
DataElement::new(tags::ROWS, VR::US, PrimitiveValue::from(64_u16)),
DataElement::new(tags::COLUMNS, VR::US, PrimitiveValue::from(64_u16)),
DataElement::new(tags::BITS_ALLOCATED, VR::US, PrimitiveValue::from(8_u16)),
DataElement::new(tags::BITS_STORED, VR::US, PrimitiveValue::from(8_u16)),
DataElement::new(tags::HIGH_BIT, VR::US, PrimitiveValue::from(7_u16)),
DataElement::new(
tags::PIXEL_DATA,
VR::OB,
PrimitiveValue::from(vec![0x55u8; 64 * 64]),
),
]);
let ts_expl_vr_le = TransferSyntaxRegistry
.get(uids::EXPLICIT_VR_LITTLE_ENDIAN)
.unwrap();
let mut encoded = Vec::new();
dataset1
.write_dataset_with_ts(&mut encoded, ts_expl_vr_le)
.unwrap();
let reader = BufReader::new(std::io::Cursor::new(&encoded));
let mut collector = DicomCollector::new_with_ts(reader, uids::EXPLICIT_VR_LITTLE_ENDIAN);
let mut dset = InMemDicomObject::new_empty();
collector.read_dataset_to_end(&mut dset).unwrap();
assert_eq!(dset, dataset1);
}
#[test]
fn test_read_dataset_to_end_infer_from_meta() {
let dataset1 = InMemDicomObject::<StandardDataDictionary>::from_element_iter([
DataElement::new(
tags::SOP_INSTANCE_UID,
VR::UI,
"2.25.245029432991021387484564600987886994494",
),
DataElement::new(
tags::SOP_CLASS_UID,
VR::UI,
uids::NUCLEAR_MEDICINE_IMAGE_STORAGE,
),
DataElement::new(tags::PATIENT_NAME, VR::PN, "Doe^John"),
DataElement::new(tags::STUDY_DESCRIPTION, VR::LO, "Test study"),
DataElement::new(tags::ROWS, VR::US, PrimitiveValue::from(128_u16)),
DataElement::new(tags::COLUMNS, VR::US, PrimitiveValue::from(128_u16)),
DataElement::new(tags::BITS_ALLOCATED, VR::US, PrimitiveValue::from(16_u16)),
DataElement::new(tags::BITS_STORED, VR::US, PrimitiveValue::from(16_u16)),
DataElement::new(tags::HIGH_BIT, VR::US, PrimitiveValue::from(15_u16)),
DataElement::new(
tags::PIXEL_DATA,
VR::OB,
PrimitiveValue::from(vec![0x55u8; 128 * 128 * 2]),
),
]);
let file_dataset1 = dataset1
.clone()
.with_meta(FileMetaTableBuilder::new().transfer_syntax(uids::EXPLICIT_VR_LITTLE_ENDIAN))
.unwrap();
let mut encoded = Vec::new();
encoded.write_all(b"DICM").unwrap();
file_dataset1.meta().write(&mut encoded).unwrap();
file_dataset1
.write_dataset_with_ts(
&mut encoded,
TransferSyntaxRegistry
.get(uids::EXPLICIT_VR_LITTLE_ENDIAN)
.unwrap(),
)
.unwrap();
let reader = BufReader::new(std::io::Cursor::new(&encoded));
let mut collector = DicomCollector::new(reader);
let mut dset = InMemDicomObject::new_empty();
let file_meta = collector.read_file_meta().unwrap();
assert_eq!(file_meta.transfer_syntax(), uids::EXPLICIT_VR_LITTLE_ENDIAN,);
collector.read_dataset_to_end(&mut dset).unwrap();
assert_eq!(dset, dataset1);
}
#[test]
fn test_take_file_meta() {
let dataset1 = InMemDicomObject::<StandardDataDictionary>::from_element_iter([
DataElement::new(
tags::SOP_INSTANCE_UID,
VR::UI,
"2.25.248821220596756482508841578490676982546",
),
DataElement::new(
tags::SOP_CLASS_UID,
VR::UI,
uids::NUCLEAR_MEDICINE_IMAGE_STORAGE,
),
DataElement::new(tags::PATIENT_NAME, VR::PN, "Doe^John"),
DataElement::new(tags::STUDY_DESCRIPTION, VR::LO, "Test study"),
DataElement::new(tags::ROWS, VR::US, PrimitiveValue::from(64_u16)),
DataElement::new(tags::COLUMNS, VR::US, PrimitiveValue::from(64_u16)),
DataElement::new(tags::SAMPLES_PER_PIXEL, VR::US, PrimitiveValue::from(1_u16)),
DataElement::new(tags::BITS_ALLOCATED, VR::US, PrimitiveValue::from(8_u16)),
DataElement::new(tags::BITS_STORED, VR::US, PrimitiveValue::from(8_u16)),
DataElement::new(tags::HIGH_BIT, VR::US, PrimitiveValue::from(7_u16)),
DataElement::new(
tags::PIXEL_DATA,
VR::OB,
PrimitiveValue::from(vec![0x55u8; 64 * 64]),
),
]);
let file_dataset1 = dataset1
.clone()
.with_meta(FileMetaTableBuilder::new().transfer_syntax(uids::EXPLICIT_VR_LITTLE_ENDIAN))
.unwrap();
let mut encoded = Vec::new();
encoded.write_all(b"DICM").unwrap();
file_dataset1.meta().write(&mut encoded).unwrap();
file_dataset1
.write_dataset_with_ts(
&mut encoded,
TransferSyntaxRegistry
.get(uids::EXPLICIT_VR_LITTLE_ENDIAN)
.unwrap(),
)
.unwrap();
let reader = BufReader::new(std::io::Cursor::new(&encoded));
let mut collector = DicomCollector::new(reader);
let _: &FileMetaTable = collector.read_file_meta().unwrap();
let mut main_dataset = InMemDicomObject::new_empty();
collector
.read_dataset_up_to_pixeldata(&mut main_dataset)
.unwrap();
let file_meta: FileMetaTable = collector
.take_file_meta()
.expect("should have file meta info");
assert_eq!(
file_meta.media_storage_sop_instance_uid(),
"2.25.248821220596756482508841578490676982546"
);
let mut fragment_data = Vec::new();
let bytes_read = collector.read_next_fragment(&mut fragment_data).unwrap();
assert_eq!(bytes_read, Some(64 * 64));
assert_eq!(fragment_data.len(), bytes_read.unwrap() as usize);
}
#[test]
fn test_read_dataset_nested() {
let dataset1 = InMemDicomObject::<StandardDataDictionary>::from_element_iter([
DataElement::new(
tags::SOP_INSTANCE_UID,
VR::UI,
"2.25.245029432991021387484564600987886994494",
),
DataElement::new(
tags::SOP_CLASS_UID,
VR::UI,
uids::NUCLEAR_MEDICINE_IMAGE_STORAGE,
),
DataElement::new(tags::PATIENT_NAME, VR::PN, "Doe^John"),
DataElement::new(tags::STUDY_DESCRIPTION, VR::LO, "Test study"),
DataElement::new(
tags::ANATOMIC_REGION_SEQUENCE,
VR::SQ,
DataSetSequence::from(vec![InMemDicomObject::from_element_iter([
DataElement::new(tags::CODE_VALUE, VR::SH, "51185008"),
DataElement::new(tags::CODING_SCHEME_DESIGNATOR, VR::SH, "SCT"),
DataElement::new(tags::CODE_MEANING, VR::LO, "chest"),
DataElement::new(
tags::ANATOMIC_REGION_MODIFIER_SEQUENCE,
VR::SQ,
DataSetSequence::from(vec![InMemDicomObject::from_element_iter([
DataElement::new(tags::CODE_VALUE, VR::SH, "302551006"),
DataElement::new(tags::CODING_SCHEME_DESIGNATOR, VR::SH, "SCT"),
DataElement::new(tags::CODE_MEANING, VR::LO, "entire thorax "),
])]),
),
])]),
),
DataElement::new(tags::ROWS, VR::US, PrimitiveValue::from(128_u16)),
DataElement::new(tags::COLUMNS, VR::US, PrimitiveValue::from(128_u16)),
DataElement::new(tags::BITS_ALLOCATED, VR::US, PrimitiveValue::from(16_u16)),
DataElement::new(tags::BITS_STORED, VR::US, PrimitiveValue::from(16_u16)),
DataElement::new(tags::HIGH_BIT, VR::US, PrimitiveValue::from(7_u16)),
DataElement::new(
tags::PIXEL_DATA,
VR::OB,
PrimitiveValue::from(vec![0x55_u8; 128 * 128]),
),
]);
let ts_expl_vr_le = TransferSyntaxRegistry
.get(uids::EXPLICIT_VR_LITTLE_ENDIAN)
.unwrap();
let mut encoded = Vec::new();
dataset1
.write_dataset_with_ts(&mut encoded, ts_expl_vr_le)
.unwrap();
let reader = BufReader::new(std::io::Cursor::new(&encoded));
let mut collector = DicomCollector::new_with_ts(reader, uids::EXPLICIT_VR_LITTLE_ENDIAN);
let mut dset = InMemDicomObject::new_empty();
collector.read_dataset_to_end(&mut dset).unwrap();
let v = dset
.value_at((tags::ANATOMIC_REGION_SEQUENCE, tags::CODE_VALUE))
.unwrap()
.to_str()
.unwrap();
assert_eq!(v, "51185008");
let v = dset
.value_at((
tags::ANATOMIC_REGION_SEQUENCE,
tags::ANATOMIC_REGION_MODIFIER_SEQUENCE,
tags::CODE_MEANING,
))
.unwrap()
.to_str()
.unwrap();
assert_eq!(v, "entire thorax");
}
#[test]
fn test_read_dataset_two_parts() {
let dataset1 = InMemDicomObject::<StandardDataDictionary>::from_element_iter([
DataElement::new(
tags::SOP_INSTANCE_UID,
VR::UI,
"2.25.245029432991021387484564600987886994494",
),
DataElement::new(
tags::SOP_CLASS_UID,
VR::UI,
uids::NUCLEAR_MEDICINE_IMAGE_STORAGE,
),
DataElement::new(tags::PATIENT_NAME, VR::PN, "Doe^John"),
DataElement::new(tags::STUDY_DESCRIPTION, VR::LO, "Test study"),
DataElement::new(tags::ROWS, VR::US, PrimitiveValue::from(128_u16)),
DataElement::new(tags::COLUMNS, VR::US, PrimitiveValue::from(128_u16)),
DataElement::new(tags::BITS_ALLOCATED, VR::US, PrimitiveValue::from(16_u16)),
DataElement::new(tags::BITS_STORED, VR::US, PrimitiveValue::from(16_u16)),
DataElement::new(tags::HIGH_BIT, VR::US, PrimitiveValue::from(7_u16)),
DataElement::new(
tags::PIXEL_DATA,
VR::OB,
PrimitiveValue::from(vec![0x55_u8; 128 * 128]),
),
]);
let ts_expl_vr_le = TransferSyntaxRegistry
.get(uids::EXPLICIT_VR_LITTLE_ENDIAN)
.unwrap();
let mut encoded = Vec::new();
dataset1
.write_dataset_with_ts(&mut encoded, ts_expl_vr_le)
.unwrap();
let reader = BufReader::new(std::io::Cursor::new(&encoded));
let mut collector = DicomCollectorOptions::new()
.expected_ts(uids::EXPLICIT_VR_LITTLE_ENDIAN)
.read_preamble(ReadPreamble::Never)
.odd_length_strategy(OddLengthStrategy::Fail)
.from_reader(reader);
let mut dset1 = InMemDicomObject::new_empty();
collector
.read_dataset_up_to(tags::ROWS, &mut dset1)
.unwrap();
assert_eq!(
dset1.get(tags::PATIENT_NAME).unwrap().to_str().unwrap(),
"Doe^John"
);
assert_eq!(
dset1
.get(tags::STUDY_DESCRIPTION)
.unwrap()
.to_str()
.unwrap(),
"Test study"
);
assert!(dset1.get(tags::ROWS).is_none());
assert!(dset1.get(tags::PIXEL_DATA).is_none());
let mut dset2 = InMemDicomObject::new_empty();
collector.read_dataset_to_end(&mut dset2).unwrap();
assert_eq!(dset2.get(tags::ROWS).unwrap().to_int::<u16>().unwrap(), 128);
assert_eq!(
dset2.get(tags::COLUMNS).unwrap().to_int::<u16>().unwrap(),
128
);
assert_eq!(
&*dset2.get(tags::PIXEL_DATA).unwrap().to_bytes().unwrap(),
&[0x55_u8; 128 * 128]
);
assert!(dset2.get(tags::SOP_INSTANCE_UID).is_none());
assert!(dset2.get(tags::PATIENT_NAME).is_none());
assert!(dset2.get(tags::STUDY_DESCRIPTION).is_none());
}
#[test]
fn test_read_fragments() {
let filename = dicom_test_files::path("WG04/JPLY/SC1_JPLY").unwrap();
let mut collector = DicomCollector::open_file(filename).unwrap();
let fmi = collector.read_file_meta().unwrap();
assert_eq!(fmi.transfer_syntax(), uids::JPEG_EXTENDED12_BIT);
let mut bot = Vec::new();
let len = collector
.read_next_fragment(&mut bot)
.expect("should read basic offset table successfully")
.expect("should have basic offset table fragment");
assert_eq!(len, 0);
assert!(bot.is_empty());
let mut fragment = Vec::with_capacity(131_072);
let len = collector
.read_next_fragment(&mut fragment)
.expect("should read fragment successfully")
.expect("should have fragment #0");
assert_eq!(len, 65_536);
assert_eq!(&fragment[0..4], &[0xFF, 0xD8, 0xFF, 0xC1]);
let len = collector
.read_next_fragment(&mut fragment)
.expect("should read fragment successfully")
.expect("should have fragment #1");
assert_eq!(len, 65_536);
assert_eq!(fragment.len(), 131_072);
assert_eq!(&fragment[0..4], &[0xFF, 0xD8, 0xFF, 0xC1]);
assert_eq!(&fragment[65_536..65_540], &[0x04, 0x6C, 0x3B, 0x60]);
let mut remaining: i32 = 10;
fragment.clear();
while let Some(_len) = collector
.read_next_fragment(&mut fragment)
.expect("should have read fragment successfully")
{
remaining -= 1;
assert!(!fragment.is_empty());
fragment.clear();
}
assert_eq!(remaining, 0);
}
#[test]
fn test_read_bot_and_fragments() {
let filename = dicom_test_files::path("pydicom/SC_rgb_rle_2frame.dcm").unwrap();
let mut collector = DicomCollector::open_file(filename).unwrap();
let fmi = collector.read_file_meta().unwrap();
assert_eq!(fmi.transfer_syntax(), uids::RLE_LOSSLESS);
let mut bot = Vec::new();
let len = collector
.read_basic_offset_table(&mut bot)
.expect("should read basic offset table successfully")
.expect("should have basic offset table fragment");
assert_eq!(len, 8);
assert_eq!(&bot, &[0x0000, 0x02A0]);
assert!(matches!(
collector.read_basic_offset_table(&mut bot),
Err(super::Error(super::InnerError::IllegalStateInPixel { .. })),
));
let mut fragment = Vec::with_capacity(2048);
let len = collector
.read_next_fragment(&mut fragment)
.expect("should read fragment successfully")
.expect("should have fragment #0");
assert_eq!(len, 664);
assert_eq!(&fragment[0..5], &[0x03, 0x00, 0x00, 0x00, 0x40]);
let len = collector
.read_next_fragment(&mut fragment)
.expect("should read fragment successfully")
.expect("should have fragment #1");
assert_eq!(len, 664);
assert_eq!(fragment.len(), 664 + 664);
assert_eq!(&fragment[0..5], &[0x03, 0x00, 0x00, 0x00, 0x40]);
assert_eq!(&fragment[664 + 659..], &[0x00, 0x9D, 0x00, 0x9D, 0x00]);
assert!(collector
.read_next_fragment(&mut fragment)
.expect("attempt to read the next fragment should not have failed")
.is_none());
}
#[test]
fn test_lazy_dicom_read() {
let file_path_buf = dicom_test_files::path("WG04/J2KR/MG1_J2KR")
.expect("should be able to retrieve test file");
let filename = file_path_buf
.to_str()
.expect("should be able to retrieve test file");
let mut collector = DicomCollector::open_file(filename)
.expect("should be able to open the test file with the collector");
let _fmi = collector.read_file_meta();
let mut obj = InMemDicomObject::new_empty();
collector
.read_dataset_up_to_pixeldata(&mut obj)
.expect("should be able to read up to the PixelData element");
collector
.read_dataset_to_end(&mut obj)
.expect("should be able to read the rest of the DICOM");
}
}