Skip to content

Commit

Permalink
ENH: Add reading and writing adapters for deflated TS
Browse files Browse the repository at this point in the history
  • Loading branch information
naterichman authored and Enet4 committed Apr 14, 2024
1 parent e11409c commit 2a37efc
Show file tree
Hide file tree
Showing 5 changed files with 134 additions and 51 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

80 changes: 60 additions & 20 deletions object/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ pub use dicom_dictionary_std::StandardDataDictionary;
pub type DefaultDicomObject<D = StandardDataDictionary> = FileDicomObject<mem::InMemDicomObject<D>>;

use dicom_core::header::Header;
use dicom_encoding::Codec;
use dicom_encoding::adapters::{PixelDataObject, RawPixelData};
use dicom_encoding::transfer_syntax::TransferSyntaxIndex;
use dicom_parser::dataset::{DataSetWriter, IntoTokens};
Expand Down Expand Up @@ -424,21 +425,34 @@ where
.with_context(|| WriteUnsupportedTransferSyntaxSnafu {
uid: self.meta.transfer_syntax.clone(),
})?;
let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
if let Codec::Dataset(Some(adapter))= ts.codec() {
let adapter = adapter.adapt_writer(Box::new(to));
let mut dset_writer = DataSetWriter::with_ts(adapter, ts).context(CreatePrinterSnafu)?;

// We use the default options, because only the inner object knows if something needs to change
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;
// write object
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;

Ok(())
Ok(())

} else {
let mut dset_writer = DataSetWriter::with_ts_cs(to, ts, cs).context(CreatePrinterSnafu)?;

Check failure on line 440 in object/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build (Windows)

cannot find value `cs` in this scope

Check failure on line 440 in object/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check (macOS)

cannot find value `cs` in this scope

// write object
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;

Ok(())
}
}

/// Write the entire object as a DICOM file
/// into the given writer.
/// Preamble, magic code, and file meta group will be included
/// before the inner object.
pub fn write_all<W: Write>(&self, to: W) -> Result<(), WriteError> {
pub fn write_all<W: Write + 'static>(&self, to: W) -> Result<(), WriteError> {
let mut to = BufWriter::new(to);

// write preamble
Expand All @@ -456,14 +470,27 @@ where
.with_context(|| WriteUnsupportedTransferSyntaxSnafu {
uid: self.meta.transfer_syntax.clone(),
})?;
let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
if let Codec::Dataset(Some(adapter))= ts.codec() {
let adapter = adapter.adapt_writer(Box::new(to));
let mut dset_writer = DataSetWriter::with_ts(adapter, ts).context(CreatePrinterSnafu)?;

// write object
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;

Ok(())

// We use the default options, because only the inner object knows if something needs to change
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;
} else {
let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;

Ok(())
// write object
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;

Ok(())
}
}

/// Write the file meta group set into the given writer.
Expand All @@ -477,7 +504,7 @@ where
/// without preamble, magic code, nor file meta group.
///
/// The transfer syntax is selected from the file meta table.
pub fn write_dataset<W: Write>(&self, to: W) -> Result<(), WriteError> {
pub fn write_dataset<W: Write + 'static>(&self, to: W) -> Result<(), WriteError> {
let to = BufWriter::new(to);

// prepare encoder
Expand All @@ -486,14 +513,27 @@ where
.with_context(|| WriteUnsupportedTransferSyntaxSnafu {
uid: self.meta.transfer_syntax.clone(),
})?;
let mut dset_writer = DataSetWriter::with_ts(to, ts).context(CreatePrinterSnafu)?;
if let Codec::Dataset(Some(adapter))= ts.codec() {
let adapter = adapter.adapt_writer(Box::new(to));
let mut dset_writer = DataSetWriter::with_ts(adapter, ts).context(CreatePrinterSnafu)?;

// write object
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;

// write object
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;
Ok(())

Ok(())
} else {
let mut dset_writer = DataSetWriter::with_ts_cs(to, ts, cs).context(CreatePrinterSnafu)?;

Check failure on line 528 in object/src/lib.rs

View workflow job for this annotation

GitHub Actions / Build (Windows)

cannot find value `cs` in this scope

Check failure on line 528 in object/src/lib.rs

View workflow job for this annotation

GitHub Actions / Check (macOS)

cannot find value `cs` in this scope

// write object
dset_writer
.write_sequence((&self.obj).into_tokens())
.context(PrintDataSetSnafu)?;

Ok(())
}
}
}

Expand Down
55 changes: 38 additions & 17 deletions object/src/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,7 @@ impl InMemDicomObject<StandardDataDictionary> {
cs: SpecificCharacterSet,
) -> Result<Self, ReadError>
where
S: Read,
S: Read + 'static,
{
Self::read_dataset_with_dict_ts_cs(from, StandardDataDictionary, ts, cs)
}
Expand All @@ -250,7 +250,7 @@ impl InMemDicomObject<StandardDataDictionary> {
#[inline]
pub fn read_dataset_with_ts<S>(from: S, ts: &TransferSyntax) -> Result<Self, ReadError>
where
S: Read,
S: Read + 'static,
{
Self::read_dataset_with_dict_ts_cs(
from,
Expand Down Expand Up @@ -624,7 +624,7 @@ where
ts: &TransferSyntax,
) -> Result<Self, ReadError>
where
S: Read,
S: Read + 'static,
D: DataDictionary,
{
Self::read_dataset_with_dict_ts_cs(from, dict, ts, SpecificCharacterSet::default())
Expand All @@ -644,12 +644,19 @@ where
cs: SpecificCharacterSet,
) -> Result<Self, ReadError>
where
S: Read,
S: Read + 'static,
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)
if let Codec::Dataset(Some(adapter)) = ts.codec() {
let adapter = adapter.adapt_reader(Box::new(from));
let mut dataset =
DataSetReader::new_with_ts_cs(adapter, ts, cs).context(CreateParserSnafu)?;
InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED, None)
} else {
let mut dataset = DataSetReader::new_with_ts_cs(from, ts, cs).context(CreateParserSnafu)?;
InMemDicomObject::build_object(&mut dataset, dict, false, Length::UNDEFINED, None)
}
}

// Standard methods follow. They are not placed as a trait implementation
Expand Down Expand Up @@ -1564,7 +1571,9 @@ where
/// in which then that character set will be used.
///
/// Note: [`write_dataset_with_ts`] and [`write_dataset_with_ts_cs`]
/// may be easier to use.
/// may be easier to use and _will_ apply a dataset adapter (such as
/// DeflatedExplicitVRLittleEndian (1.2.840.10008.1.2.99)) whereas this
/// method will _not_
///
/// [`write_dataset_with_ts`]: #method.write_dataset_with_ts
/// [`write_dataset_with_ts_cs`]: #method.write_dataset_with_ts_cs
Expand Down Expand Up @@ -1598,18 +1607,30 @@ where
cs: SpecificCharacterSet,
) -> Result<(), WriteError>
where
W: Write,
W: Write + 'static,
{
// prepare data set writer
let mut dset_writer = DataSetWriter::with_ts_cs(to, ts, cs).context(CreatePrinterSnafu)?;
let required_options = IntoTokensOptions::new(self.charset_changed);
if let Codec::Dataset(Some(adapter)) = ts.codec() {
let adapter = adapter.adapt_writer(Box::new(to));
// prepare data set writer
let mut dset_writer = DataSetWriter::with_ts(adapter, ts).context(CreatePrinterSnafu)?;

// write object
dset_writer
.write_sequence(self.into_tokens_with_options(required_options))
.context(PrintDataSetSnafu)?;
// write object
dset_writer
.write_sequence(self.into_tokens())
.context(PrintDataSetSnafu)?;

Ok(())
Ok(())
} else {
// prepare data set writer
let mut dset_writer = DataSetWriter::with_ts_cs(to, ts, cs).context(CreatePrinterSnafu)?;

// write object
dset_writer
.write_sequence(self.into_tokens())
.context(PrintDataSetSnafu)?;

Ok(())
}
}

/// Write this object's data set into the given writer,
Expand All @@ -1621,7 +1642,7 @@ where
/// after which the text encoder is overridden accordingly.
pub fn write_dataset_with_ts<W>(&self, to: W, ts: &TransferSyntax) -> Result<(), WriteError>
where
W: Write,
W: Write + 'static,
{
self.write_dataset_with_ts_cs(to, ts, SpecificCharacterSet::default())
}
Expand Down
2 changes: 2 additions & 0 deletions transfer-syntax-registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,5 @@ features = ["native"]

[dev-dependencies]
dicom-test-files = "0.3"
dicom-object = { path = "../object" }
dicom-pixeldata = { path = "../pixeldata" }
46 changes: 32 additions & 14 deletions transfer-syntax-registry/tests/deflate.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,38 @@

use std::{io::BufReader, fs::File};
use std::{io::BufReader, fs::{metadata, File}};

// use dicom_object::OpenFileOptions;
// use dicom_pixeldata::PixelDecoder;

// #[test]
// fn test_read_data_with_preamble() {
// let path = dicom_test_files::path("pydicom/image_dfl.dcm").expect("test DICOM file should exist");
// let source = BufReader::new(File::open(path).unwrap());
use dicom_object::OpenFileOptions;
use dicom_pixeldata::PixelDecoder;

// // should read preamble even though it's from a reader
// let object = OpenFileOptions::new()
// .from_reader(source)
// .expect("Should read from source successfully");
#[test]
fn test_read_data_deflated() {
let path = dicom_test_files::path("pydicom/image_dfl.dcm").expect("test DICOM file should exist");
let source = BufReader::new(File::open(path).unwrap());

// let res = object.decode_pixel_data();
// println!("{:?}", res);
// should read preamble even though it's from a reader
let object = OpenFileOptions::new()
.from_reader(source)
.expect("Should read from source successfully");

// }
let res = object.decode_pixel_data().unwrap();
assert_eq!((
res.rows() as usize *
res.columns() as usize *
res.number_of_frames() as usize), res.data().len() as usize);
}

#[test]
fn write_deflated(){
let path = dicom_test_files::path("pydicom/image_dfl.dcm").expect("test DICOM file should exist");
let source = BufReader::new(File::open(path.clone()).unwrap());

// should read preamble even though it's from a reader
let object = OpenFileOptions::new()
.from_reader(source)
.expect("Should read from source successfully");

let mut buf = Vec::<u8>::new();
object.write_all(&mut buf).unwrap();
assert_eq!(buf.len(), metadata(path).unwrap().len() as usize);
}

0 comments on commit 2a37efc

Please sign in to comment.