Skip to content

Commit

Permalink
ENH: Add support for deflate writing
Browse files Browse the repository at this point in the history
* Note, not all paths lead to writing, for example using
  `InMemDicomObject.read_dataset_with_dict` would bypass deflate.
* No support for writing right now
  • Loading branch information
naterichman committed Nov 19, 2023
1 parent a633f1a commit a0c9f47
Show file tree
Hide file tree
Showing 10 changed files with 159 additions and 36 deletions.
4 changes: 2 additions & 2 deletions object/src/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub type Result<T, E = ReadError> = std::result::Result<T, E>;
/// preamble: file meta group, followed by the rest of the data set.
pub fn from_reader<F>(file: F) -> Result<DefaultDicomObject>
where
F: Read,
F: Read + 'static,
{
OpenFileOptions::new().from_reader(file)
}
Expand Down Expand Up @@ -141,7 +141,7 @@ impl<D, T> OpenFileOptions<D, T> {
/// This method assumes
/// the standard file encoding structure without the preamble:
/// file meta group, followed by the rest of the data set.
pub fn from_reader<R>(self, from: R) -> Result<DefaultDicomObject<D>>
pub fn from_reader<'s: 'static, R: 's>(self, from: R) -> Result<DefaultDicomObject<D>>
where
R: Read,
D: DataDictionary,
Expand Down
51 changes: 34 additions & 17 deletions object/src/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ impl FileDicomObject<InMemDicomObject<StandardDataDictionary>> {
/// followed by the rest of the data set.
pub fn from_reader<S>(src: S) -> Result<Self, ReadError>
where
S: Read,
S: Read + 'static,
{
Self::from_reader_with_dict(src, StandardDataDictionary)
}
Expand Down Expand Up @@ -374,19 +374,36 @@ where
// read rest of data according to metadata, feed it to object
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)?;
if let Codec::Dataset(Some(adapter)) = ts.codec() {
let adapter = adapter.adapt_reader(Box::new(file));
let mut dataset =
DataSetReader::new_with_ts_cs(adapter, ts, cs).context(CreateParserSnafu)?;

Ok(FileDicomObject {
meta,
obj: InMemDicomObject::build_object(
&mut dataset,
dict,
false,
Length::UNDEFINED,
read_until,
)?,
})
Ok(FileDicomObject {
meta,
obj: InMemDicomObject::build_object(
&mut dataset,
dict,
false,
Length::UNDEFINED,
read_until,
)?,
})
} else {
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 {
ReadUnsupportedTransferSyntaxSnafu {
uid: meta.transfer_syntax,
Expand All @@ -402,7 +419,7 @@ where
/// skipping it when found.
/// Then it reads the file meta group,
/// followed by the rest of the data set.
pub fn from_reader_with_dict<S>(src: S, dict: D) -> Result<Self, ReadError>
pub fn from_reader_with_dict<'s: 'static, S: 's>(src: S, dict: D) -> Result<Self, ReadError>
where
S: Read,
{
Expand All @@ -422,23 +439,23 @@ where
/// is insufficient. Otherwise, please use [`from_reader_with_dict`] instead.
///
/// [`from_reader_with_dict`]: #method.from_reader_with_dict
pub fn from_reader_with<'s, S: 's, R>(src: S, dict: D, ts_index: R) -> Result<Self, ReadError>
pub fn from_reader_with<'s: 'static, S: 's, R>(src: S, dict: D, ts_index: R) -> Result<Self, ReadError>
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>(
pub(crate) fn from_reader_with_all_options<'s: 'static, S, R>(
src: S,
dict: D,
ts_index: R,
read_until: Option<Tag>,
mut read_preamble: ReadPreamble,
) -> Result<Self, ReadError>
where
S: Read,
S: Read + 's,
R: TransferSyntaxIndex,
{
let mut file = BufReader::new(src);
Expand Down
16 changes: 16 additions & 0 deletions pixeldata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2224,6 +2224,22 @@ mod tests {
}
}

#[test]
fn test_can_read_deflated(){

let path = dicom_test_files::path("pydicom/image_dfl.dcm").expect("test DICOM file should exist");

// should read preamble even though it's from a reader
let obj = open_file(path.clone()).expect("Should read file");

let res = obj.decode_pixel_data().expect("Should decode pixel data.");
assert_eq!(res.to_vec::<u8>().unwrap().len(), (res.rows() as usize * res.columns() as usize));
let mut buf = Vec::<u8>::new();
obj.write_all(&mut buf).expect("Should write deflated");

assert_eq!(std::fs::metadata(path).unwrap().len() as usize, buf.len())
}

#[cfg(not(feature = "gdcm"))]
mod not_gdcm {
#[cfg(any(feature = "transfer-syntax-registry/rle", feature = "image"))]
Expand Down
1 change: 1 addition & 0 deletions pixeldata/tests/integration_tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
use std::{io::BufReader, fs::File};
8 changes: 7 additions & 1 deletion transfer-syntax-registry/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@ keywords = ["dicom"]
readme = "README.md"

[features]
default = ["rayon", "simd"]
default = ["rayon", "simd", "flate2"]

# inventory for compile time plugin-based transfer syntax registration
inventory-registry = ['dicom-encoding/inventory-registry']

# Flate2 for Deflate read/write
flate2 = ["dep:flate2"]
# natively implemented image encodings
native = ["jpeg", "openjp2", "rle"]
# native implementations that work on Windows
Expand Down Expand Up @@ -45,6 +47,10 @@ lazy_static = "1.2.0"
byteordered = "0.6"
tracing = "0.1.34"

[dependencies.flate2]
version = "1.0.28"
optional = true

[dependencies.jpeg2k]
version = "0.6.6"
optional = true
Expand Down
56 changes: 56 additions & 0 deletions transfer-syntax-registry/src/adapters/encapsulated.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#[derive(debug)]
pub struct EncapsulatedAdapter;

pub const ENCAPSULATED_UNCOMPRESSED_EXPLICIT_VR_LITTLE_ENDIAN: TransferSyntax<EncapsulatedAdapter> = TransferSyntax::new(
"1.2.840.10008.1.2.1.98",
"Encapsulated Uncompressed Explicit VR Little Endian",
Endianness::Little,
true,
Codec::Encapsulated(Some(EncapsulatedAdapter), Some(EncapsulatedAdapter),
);


impl PixelDataReader for EncapsulatedAdapter {
fn decode_frame(
&self,
src: &dyn PixelDataObject,
frame: u32,
dst: &mut Vec<u8>,
) -> DecodeResult<()>{
let cols = src
.cols()
.context(decode_error::MissingAttributeSnafu { name: "Columns" })?;
let rows = src
.rows()
.context(decode_error::MissingAttributeSnafu { name: "Rows" })?;
let samples_per_pixel =
src.samples_per_pixel()
.context(decode_error::MissingAttributeSnafu {
name: "SamplesPerPixel",
})?;
let bits_allocated = src
.bits_allocated()
.context(decode_error::MissingAttributeSnafu {
name: "BitsAllocated",
})?;

if bits_allocated != 8 && bits_allocated != 16 {
whatever!("BitsAllocated other than 8 or 16 is not supported");
}
// Encapsulated Uncompressed has each frame encoded in one fragment
// So we can assume 1 frag = 1 frame
// ref. PS3.5 A.4.11
let nr_frames =
src.number_of_fragments()
.whatever_context("Invalid pixel data, no fragments found")? as usize;
ensure!(
nr_frames > frame as usize,
decode_error::FrameRangeOutOfBoundsSnafu
);

let bytes_per_sample = (bits_allocated / 8) as usize;
let samples_per_pixel = samples_per_pixel as usize;
let decoded_pixel_data = match &src.fragment(frame as usize).value();
dst
}
}
22 changes: 7 additions & 15 deletions transfer-syntax-registry/src/deflate.rs
Original file line number Diff line number Diff line change
@@ -1,43 +1,35 @@
//! Implementation of Deflated Explicit VR Little Endian.
use std::io::{Read, Write};

use byteordered::Endianness;
use dicom_encoding::{Codec, TransferSyntax, transfer_syntax::DataRWAdapter};
use dicom_encoding::transfer_syntax::DataRWAdapter;
use flate2;
use flate2::Compression;

/// Immaterial type representing an adapter for deflated data.
#[derive(Debug)]
pub struct FlateAdapter;

/// **Fully implemented**: Deflated Explicit VR Little Endian
pub const DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN: TransferSyntax<FlateAdapter> = TransferSyntax::new(
"1.2.840.10008.1.2.1.99",
"Deflated Explicit VR Little Endian",
Endianness::Little,
true,
Codec::Dataset(FlateAdapter),
);

impl<R: 'static, W: 'static> DataRWAdapter<R, W> for FlateAdapter
where
R: Read,
W: Write,
{
type Reader = flate2::read::DeflateDecoder<R>;
type Writer = flate2::write::DeflateEncoder<W>;
// type Reader = Box<flate2::read::DeflateDecoder<R>>;
// type Writer = Box<flate2::write::DeflateEncoder<W>>;
type Reader = Box<dyn Read>;
type Writer = Box<dyn Write>;

fn adapt_reader(&self, reader: R) -> Self::Reader
where
R: Read,
{
flate2::read::DeflateDecoder::new(reader)
Box::new(flate2::read::DeflateDecoder::new(reader))
}

fn adapt_writer(&self, writer: W) -> Self::Writer
where
W: Write,
{
flate2::write::DeflateEncoder::new(writer, Compression::fast())
Box::new(flate2::write::DeflateEncoder::new(writer, Compression::fast()))
}
}
16 changes: 15 additions & 1 deletion transfer-syntax-registry/src/entries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ use crate::adapters::jpeg::JpegAdapter;
use crate::adapters::jpeg2k::Jpeg2000Adapter;
#[cfg(feature = "rle")]
use crate::adapters::rle_lossless::RleLosslessAdapter;
#[cfg(feature = "flate2")]
use crate::deflate::FlateAdapter;

// -- the three base transfer syntaxes, fully supported --

Expand Down Expand Up @@ -177,15 +179,27 @@ pub const JPEG_LOSSLESS_NON_HIERARCHICAL_FIRST_ORDER_PREDICTION: Ts = create_ts_
"JPEG Lossless, Non-Hierarchical, First-Order Prediction",
);

// --- stub transfer syntaxes, known but not supported ---
#[cfg(feature = "flate2")]
/// **Fully implemented**: Deflated Explicit VR Little Endian
pub const DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN: TransferSyntax<FlateAdapter, NeverAdapter, NeverAdapter> = TransferSyntax::new(
"1.2.840.10008.1.2.1.99",
"Deflated Explicit VR Little Endian",
Endianness::Little,
true,
Codec::Dataset(Some(FlateAdapter)),
);

#[cfg(not(feature = "flate2"))]
/// **Stub descriptor:** Deflated Explicit VR Little Endian
pub const DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN: Ts = Ts::new_ele(
"1.2.840.10008.1.2.1.99",
"Deflated Explicit VR Little Endian",
Codec::Dataset(None),
);

// --- stub transfer syntaxes, known but not supported ---


/// **Stub descriptor:** JPIP Referenced Deflate
pub const JPIP_REFERENCED_DEFLATE: Ts = Ts::new_ele(
"1.2.840.10008.1.2.4.95",
Expand Down
1 change: 1 addition & 0 deletions transfer-syntax-registry/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ pub use dicom_encoding::TransferSyntax;
pub mod entries;

mod adapters;
mod deflate;

#[cfg(feature = "inventory-registry")]
pub use dicom_encoding::inventory;
Expand Down
20 changes: 20 additions & 0 deletions transfer-syntax-registry/tests/deflate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

use std::{io::BufReader, fs::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());

// // 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();
// println!("{:?}", res);

// }

0 comments on commit a0c9f47

Please sign in to comment.