From f20d1c58fa2c766ee73d43554549a23c09d6118c Mon Sep 17 00:00:00 2001 From: Nathan Richman Date: Sat, 18 Nov 2023 14:20:40 -0600 Subject: [PATCH 01/16] ENH: First crack at adding dataset read adapter in DS read --- object/src/mem.rs | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/object/src/mem.rs b/object/src/mem.rs index 9cdd8ff6..7d27c71f 100644 --- a/object/src/mem.rs +++ b/object/src/mem.rs @@ -39,6 +39,7 @@ use dicom_core::ops::{ ApplyOp, AttributeAction, AttributeOp, AttributeSelector, AttributeSelectorStep, }; +use dicom_encoding::Codec; use dicom_parser::dataset::read::{DataSetReaderOptions, OddLengthStrategy}; use itertools::Itertools; use smallvec::SmallVec; @@ -500,20 +501,31 @@ where if let Some(ts) = ts_index.get(&meta.transfer_syntax) { let mut options = DataSetReaderOptions::default(); options.odd_length = odd_length; - let mut dataset = DataSetReader::new_with_ts_options( - file, - ts, - options, - ) - .context(CreateParserSnafu)?; - let obj = InMemDicomObject::build_object( - &mut dataset, - dict, - false, - Length::UNDEFINED, - read_until, - )?; - Ok(FileDicomObject { meta, obj }) + + if let Codec::Dataset(Some(adapter)) = ts.codec() { + let adapter = adapter.adapt_reader(Box::new(file)); + let mut dataset = + DataSetReader::new_with_ts_options(adapter, ts, options).context(CreateParserSnafu)?; + let obj = InMemDicomObject::build_object( + &mut dataset, + dict, + false, + Length::UNDEFINED, + read_until, + )?; + Ok(FileDicomObject { meta, obj }) + } else { + let mut dataset = + DataSetReader::new_with_ts_options(file, ts, options).context(CreateParserSnafu)?; + let obj = InMemDicomObject::build_object( + &mut dataset, + dict, + false, + Length::UNDEFINED, + read_until, + )?; + Ok(FileDicomObject { meta, obj }) + } } else { ReadUnsupportedTransferSyntaxSnafu { uid: meta.transfer_syntax, From 57d8907a1b587c16a1250d9f283ff6c7480c6092 Mon Sep 17 00:00:00 2001 From: Nathan Richman Date: Sun, 19 Nov 2023 09:38:53 -0600 Subject: [PATCH 02/16] ENH: Add support for deflate writing * Note, not all paths lead to writing, for example using `InMemDicomObject.read_dataset_with_dict` would bypass deflate. * No support for writing right now --- Cargo.lock | 1 + object/src/file.rs | 4 +- object/src/mem.rs | 47 ++++++++++------ pixeldata/src/lib.rs | 16 ++++++ pixeldata/tests/integration_tests.rs | 1 + transfer-syntax-registry/Cargo.toml | 8 ++- .../src/adapters/encapsulated.rs | 56 +++++++++++++++++++ transfer-syntax-registry/src/deflate.rs | 22 +++----- transfer-syntax-registry/src/entries.rs | 16 +++++- transfer-syntax-registry/src/lib.rs | 1 + transfer-syntax-registry/tests/deflate.rs | 20 +++++++ 11 files changed, 155 insertions(+), 37 deletions(-) create mode 100644 pixeldata/tests/integration_tests.rs create mode 100644 transfer-syntax-registry/src/adapters/encapsulated.rs create mode 100644 transfer-syntax-registry/tests/deflate.rs diff --git a/Cargo.lock b/Cargo.lock index aa30e13f..59e41cd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,6 +656,7 @@ dependencies = [ "dicom-core", "dicom-encoding", "dicom-test-files", + "flate2", "jpeg-decoder", "jpeg-encoder", "jpeg2k", diff --git a/object/src/file.rs b/object/src/file.rs index 9748d653..7e1d7e3b 100644 --- a/object/src/file.rs +++ b/object/src/file.rs @@ -18,7 +18,7 @@ pub type Result = std::result::Result; /// preamble: file meta group, followed by the rest of the data set. pub fn from_reader(file: F) -> Result where - F: Read, + F: Read + 'static, { OpenFileOptions::new().from_reader(file) } @@ -154,7 +154,7 @@ impl OpenFileOptions { /// 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(self, from: R) -> Result> + pub fn from_reader<'s: 'static, R: 's>(self, from: R) -> Result> where R: Read, D: DataDictionary, diff --git a/object/src/mem.rs b/object/src/mem.rs index 7d27c71f..66434264 100644 --- a/object/src/mem.rs +++ b/object/src/mem.rs @@ -162,7 +162,7 @@ impl FileDicomObject> { /// followed by the rest of the data set. pub fn from_reader(src: S) -> Result where - S: Read, + S: Read + 'static, { Self::from_reader_with_dict(src, StandardDataDictionary) } @@ -381,20 +381,31 @@ where if let Some(ts) = ts_index.get(&meta.transfer_syntax) { let mut options = DataSetReaderOptions::default(); options.odd_length = odd_length; - let mut dataset = DataSetReader::new_with_ts_cs_options( - file, - ts, - SpecificCharacterSet::default(), - options, - ) - .context(CreateParserSnafu)?; - let obj = InMemDicomObject::build_object( - &mut dataset, - dict, - false, - Length::UNDEFINED, - read_until, - )?; + + let obj = if let Codec::Dataset(Some(adapter)) = ts.codec() { + let adapter = adapter.adapt_reader(Box::new(file)); + let mut dataset = + DataSetReader::new_with_ts(adapter, ts).context(CreateParserSnafu)?; + + InMemDicomObject::build_object( + &mut dataset, + dict, + false, + Length::UNDEFINED, + read_until, + )? + } else { + let mut dataset = + DataSetReader::new_with_ts(file, ts).context(CreateParserSnafu)?; + + InMemDicomObject::build_object( + &mut dataset, + dict, + false, + Length::UNDEFINED, + read_until, + )? + }; // if Media Storage SOP Class UID is empty attempt to infer from SOP Class UID if meta.media_storage_sop_class_uid().is_empty() { @@ -434,7 +445,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(src: S, dict: D) -> Result + pub fn from_reader_with_dict<'s: 'static, S: 's>(src: S, dict: D) -> Result where S: Read, { @@ -454,7 +465,7 @@ 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, R>(src: S, dict: D, ts_index: R) -> Result + pub fn from_reader_with<'s: 'static, S: 's, R>(src: S, dict: D, ts_index: R) -> Result where S: Read + 's, R: TransferSyntaxIndex, @@ -469,7 +480,7 @@ where ) } - pub(crate) fn from_reader_with_all_options<'s, S, R>( + pub(crate) fn from_reader_with_all_options<'s: 'static, S, R>( src: S, dict: D, ts_index: R, diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index 4c29b2ce..52e3187f 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -2545,6 +2545,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::().unwrap().len(), (res.rows() as usize * res.columns() as usize)); + let mut buf = Vec::::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(feature = "ndarray")] diff --git a/pixeldata/tests/integration_tests.rs b/pixeldata/tests/integration_tests.rs new file mode 100644 index 00000000..e50dc2a3 --- /dev/null +++ b/pixeldata/tests/integration_tests.rs @@ -0,0 +1 @@ +use std::{io::BufReader, fs::File}; diff --git a/transfer-syntax-registry/Cargo.toml b/transfer-syntax-registry/Cargo.toml index cf91f741..b14d5a4a 100644 --- a/transfer-syntax-registry/Cargo.toml +++ b/transfer-syntax-registry/Cargo.toml @@ -11,11 +11,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", "rle"] # native implementations that work on Windows @@ -56,6 +58,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.9.1" optional = true diff --git a/transfer-syntax-registry/src/adapters/encapsulated.rs b/transfer-syntax-registry/src/adapters/encapsulated.rs new file mode 100644 index 00000000..9c43f19a --- /dev/null +++ b/transfer-syntax-registry/src/adapters/encapsulated.rs @@ -0,0 +1,56 @@ +#[derive(debug)] +pub struct EncapsulatedAdapter; + +pub const ENCAPSULATED_UNCOMPRESSED_EXPLICIT_VR_LITTLE_ENDIAN: TransferSyntax = 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, + ) -> 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 + } +} diff --git a/transfer-syntax-registry/src/deflate.rs b/transfer-syntax-registry/src/deflate.rs index 617fc475..29b7bd42 100644 --- a/transfer-syntax-registry/src/deflate.rs +++ b/transfer-syntax-registry/src/deflate.rs @@ -1,8 +1,7 @@ //! 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; @@ -10,34 +9,27 @@ use flate2::Compression; #[derive(Debug)] pub struct FlateAdapter; -/// **Fully implemented**: Deflated Explicit VR Little Endian -pub const DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN: TransferSyntax = TransferSyntax::new( - "1.2.840.10008.1.2.1.99", - "Deflated Explicit VR Little Endian", - Endianness::Little, - true, - Codec::Dataset(FlateAdapter), -); - impl DataRWAdapter for FlateAdapter where R: Read, W: Write, { - type Reader = flate2::read::DeflateDecoder; - type Writer = flate2::write::DeflateEncoder; + // type Reader = Box>; + // type Writer = Box>; + type Reader = Box; + type Writer = Box; 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())) } } diff --git a/transfer-syntax-registry/src/entries.rs b/transfer-syntax-registry/src/entries.rs index 07736d0c..645522ae 100644 --- a/transfer-syntax-registry/src/entries.rs +++ b/transfer-syntax-registry/src/entries.rs @@ -40,6 +40,8 @@ use crate::adapters::jpegls::{JpegLsAdapter, JpegLsLosslessWriter}; use crate::adapters::jpegxl::{JpegXlAdapter, JpegXlLosslessEncoder}; #[cfg(feature = "rle")] use crate::adapters::rle_lossless::RleLosslessAdapter; +#[cfg(feature = "flate2")] +use crate::deflate::FlateAdapter; // -- the three base transfer syntaxes, fully supported -- @@ -181,8 +183,17 @@ 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 = 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", @@ -190,6 +201,9 @@ pub const DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN: Ts = Ts::new_ele( 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", diff --git a/transfer-syntax-registry/src/lib.rs b/transfer-syntax-registry/src/lib.rs index 8314af69..7968992f 100644 --- a/transfer-syntax-registry/src/lib.rs +++ b/transfer-syntax-registry/src/lib.rs @@ -114,6 +114,7 @@ pub use dicom_encoding::TransferSyntax; pub mod entries; mod adapters; +mod deflate; #[cfg(feature = "inventory-registry")] pub use dicom_encoding::inventory; diff --git a/transfer-syntax-registry/tests/deflate.rs b/transfer-syntax-registry/tests/deflate.rs new file mode 100644 index 00000000..b7efd400 --- /dev/null +++ b/transfer-syntax-registry/tests/deflate.rs @@ -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); + +// } \ No newline at end of file From 4c1cd8569fe3a667f9665ac0acb9add05967a9f5 Mon Sep 17 00:00:00 2001 From: Nathan Richman Date: Sun, 3 Dec 2023 13:07:51 -0600 Subject: [PATCH 03/16] ENH: Add reading and writing adapters for deflated TS --- Cargo.lock | 2 + object/src/lib.rs | 80 +++++++++++++++++------ object/src/mem.rs | 55 +++++++++++----- transfer-syntax-registry/Cargo.toml | 2 + transfer-syntax-registry/tests/deflate.rs | 46 +++++++++---- 5 files changed, 134 insertions(+), 51 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 59e41cd7..0670ee64 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -655,6 +655,8 @@ dependencies = [ "charls", "dicom-core", "dicom-encoding", + "dicom-object", + "dicom-pixeldata", "dicom-test-files", "flate2", "jpeg-decoder", diff --git a/object/src/lib.rs b/object/src/lib.rs index abbd8422..74111d2d 100644 --- a/object/src/lib.rs +++ b/object/src/lib.rs @@ -151,6 +151,7 @@ pub use dicom_dictionary_std::StandardDataDictionary; pub type DefaultDicomObject = FileDicomObject>; use dicom_core::header::{GroupNumber, Header}; +use dicom_encoding::Codec; use dicom_encoding::adapters::{PixelDataObject, RawPixelData}; use dicom_encoding::transfer_syntax::TransferSyntaxIndex; use dicom_parser::dataset::{DataSetWriter, IntoTokens}; @@ -475,21 +476,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(to, ts).context(CreatePrinterSnafu)?; + + // 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(&self, to: W) -> Result<(), WriteError> { + pub fn write_all(&self, to: W) -> Result<(), WriteError> { let mut to = BufWriter::new(to); // write preamble @@ -507,14 +521,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. @@ -528,7 +555,7 @@ where /// without preamble, magic code, nor file meta group. /// /// The transfer syntax is selected from the file meta table. - pub fn write_dataset(&self, to: W) -> Result<(), WriteError> { + pub fn write_dataset(&self, to: W) -> Result<(), WriteError> { let to = BufWriter::new(to); // prepare encoder @@ -537,14 +564,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(to, ts).context(CreatePrinterSnafu)?; + + // write object + dset_writer + .write_sequence((&self.obj).into_tokens()) + .context(PrintDataSetSnafu)?; + + Ok(()) + } } } diff --git a/object/src/mem.rs b/object/src/mem.rs index 66434264..f28fa9ad 100644 --- a/object/src/mem.rs +++ b/object/src/mem.rs @@ -238,7 +238,7 @@ impl InMemDicomObject { cs: SpecificCharacterSet, ) -> Result where - S: Read, + S: Read + 'static, { Self::read_dataset_with_dict_ts_cs(from, StandardDataDictionary, ts, cs) } @@ -252,7 +252,7 @@ impl InMemDicomObject { #[inline] pub fn read_dataset_with_ts(from: S, ts: &TransferSyntax) -> Result where - S: Read, + S: Read + 'static, { Self::read_dataset_with_dict_ts_cs( from, @@ -662,7 +662,7 @@ where ts: &TransferSyntax, ) -> Result where - S: Read, + S: Read + 'static, D: DataDictionary, { Self::read_dataset_with_dict_ts_cs(from, dict, ts, SpecificCharacterSet::default()) @@ -682,12 +682,19 @@ where cs: SpecificCharacterSet, ) -> Result 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 @@ -1768,7 +1775,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 @@ -1802,18 +1811,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, @@ -1825,7 +1846,7 @@ where /// after which the text encoder is overridden accordingly. pub fn write_dataset_with_ts(&self, to: W, ts: &TransferSyntax) -> Result<(), WriteError> where - W: Write, + W: Write + 'static, { self.write_dataset_with_ts_cs(to, ts, SpecificCharacterSet::default()) } diff --git a/transfer-syntax-registry/Cargo.toml b/transfer-syntax-registry/Cargo.toml index b14d5a4a..d3341aaa 100644 --- a/transfer-syntax-registry/Cargo.toml +++ b/transfer-syntax-registry/Cargo.toml @@ -100,3 +100,5 @@ features = ["native"] [dev-dependencies] dicom-test-files = "0.3" +dicom-object = { path = "../object" } +dicom-pixeldata = { path = "../pixeldata" } diff --git a/transfer-syntax-registry/tests/deflate.rs b/transfer-syntax-registry/tests/deflate.rs index b7efd400..f184d769 100644 --- a/transfer-syntax-registry/tests/deflate.rs +++ b/transfer-syntax-registry/tests/deflate.rs @@ -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"); -// } \ No newline at end of file + 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::::new(); + object.write_all(&mut buf).unwrap(); + assert_eq!(buf.len(), metadata(path).unwrap().len() as usize); +} \ No newline at end of file From a31eac5c56c4f2704f61a9e1eeb155ea360a31d2 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 10:03:07 +0000 Subject: [PATCH 04/16] [encoding] Change signature of trait DataRWAdapter - make it fully dynamic and pass a lifetime from input to output - change the rest of the code to fit DataRWAdapter - revert 'static lifetime constraints - remove unused file --- encoding/src/transfer_syntax/mod.rs | 69 ++++--------------- object/src/lib.rs | 2 +- object/src/mem.rs | 10 +-- pixeldata/src/lib.rs | 1 + pixeldata/tests/integration_tests.rs | 1 - transfer-syntax-registry/src/deflate.rs | 21 +----- .../tests/submit_dataset.rs | 18 ++--- .../tests/submit_replace.rs | 15 +--- 8 files changed, 32 insertions(+), 105 deletions(-) delete mode 100644 pixeldata/tests/integration_tests.rs diff --git a/encoding/src/transfer_syntax/mod.rs b/encoding/src/transfer_syntax/mod.rs index 570c6cfd..a4bfab38 100644 --- a/encoding/src/transfer_syntax/mod.rs +++ b/encoding/src/transfer_syntax/mod.rs @@ -319,57 +319,29 @@ pub enum Codec { pub type AdapterFreeTransferSyntax = TransferSyntax; -/// An adapter of byte read and write streams. -pub trait DataRWAdapter { - /// The type of the adapted reader. - type Reader: Read; - /// The type of the adapted writer. - type Writer: Write; - +/// A fully dynamic adapter of byte read and write streams. +pub trait DataRWAdapter { /// Adapt a byte reader. - fn adapt_reader(&self, reader: R) -> Self::Reader - where - R: Read; + fn adapt_reader<'r>(&self, reader: Box) -> Box; /// Adapt a byte writer. - fn adapt_writer(&self, writer: W) -> Self::Writer - where - W: Write; + fn adapt_writer<'w>(&self, writer: Box) -> Box; } /// Alias type for a dynamically dispatched data adapter. -pub type DynDataRWAdapter = Box< - dyn DataRWAdapter< - Box, - Box, - Reader = Box, - Writer = Box, - > + Send - + Sync, ->; - -impl DataRWAdapter for &'_ T +pub type DynDataRWAdapter = Box; + +impl DataRWAdapter for &'_ T where - T: DataRWAdapter, - R: Read, - W: Write, + T: DataRWAdapter, { - type Reader = >::Reader; - type Writer = >::Writer; - /// Adapt a byte reader. - fn adapt_reader(&self, reader: R) -> Self::Reader - where - R: Read, - { + fn adapt_reader<'r>(&self, reader: Box) -> Box { (**self).adapt_reader(reader) } /// Adapt a byte writer. - fn adapt_writer(&self, writer: W) -> Self::Writer - where - W: Write, - { + fn adapt_writer<'w>(&self, writer: Box) -> Box { (**self).adapt_writer(writer) } } @@ -384,21 +356,13 @@ where #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] pub enum NeverAdapter {} -impl DataRWAdapter for NeverAdapter { - type Reader = Box; - type Writer = Box; +impl DataRWAdapter for NeverAdapter { - fn adapt_reader(&self, _reader: R) -> Self::Reader - where - R: Read, - { + fn adapt_reader<'r>(&self, _reader: Box) -> Box { unreachable!() } - fn adapt_writer(&self, _writer: W) -> Self::Writer - where - W: Write, - { + fn adapt_writer<'w>(&self, _writer: Box) -> Box { unreachable!() } } @@ -628,12 +592,7 @@ impl TransferSyntax { pub fn erased(self) -> TransferSyntax where D: Send + Sync + 'static, - D: DataRWAdapter< - Box, - Box, - Reader = Box, - Writer = Box, - >, + D: DataRWAdapter, R: Send + Sync + 'static, R: PixelDataReader, W: Send + Sync + 'static, diff --git a/object/src/lib.rs b/object/src/lib.rs index 74111d2d..eb3b6ec2 100644 --- a/object/src/lib.rs +++ b/object/src/lib.rs @@ -503,7 +503,7 @@ where /// into the given writer. /// Preamble, magic code, and file meta group will be included /// before the inner object. - pub fn write_all(&self, to: W) -> Result<(), WriteError> { + pub fn write_all(&self, to: impl Write) -> Result<(), WriteError> { let mut to = BufWriter::new(to); // write preamble diff --git a/object/src/mem.rs b/object/src/mem.rs index f28fa9ad..28f9d1b3 100644 --- a/object/src/mem.rs +++ b/object/src/mem.rs @@ -252,7 +252,7 @@ impl InMemDicomObject { #[inline] pub fn read_dataset_with_ts(from: S, ts: &TransferSyntax) -> Result where - S: Read + 'static, + S: Read, { Self::read_dataset_with_dict_ts_cs( from, @@ -662,7 +662,7 @@ where ts: &TransferSyntax, ) -> Result where - S: Read + 'static, + S: Read, D: DataDictionary, { Self::read_dataset_with_dict_ts_cs(from, dict, ts, SpecificCharacterSet::default()) @@ -682,7 +682,7 @@ where cs: SpecificCharacterSet, ) -> Result where - S: Read + 'static, + S: Read, D: DataDictionary, { let from = BufReader::new(from); @@ -1811,7 +1811,7 @@ where cs: SpecificCharacterSet, ) -> Result<(), WriteError> where - W: Write + 'static, + W: Write, { if let Codec::Dataset(Some(adapter)) = ts.codec() { let adapter = adapter.adapt_writer(Box::new(to)); @@ -1846,7 +1846,7 @@ where /// after which the text encoder is overridden accordingly. pub fn write_dataset_with_ts(&self, to: W, ts: &TransferSyntax) -> Result<(), WriteError> where - W: Write + 'static, + W: Write, { self.write_dataset_with_ts_cs(to, ts, SpecificCharacterSet::default()) } diff --git a/pixeldata/src/lib.rs b/pixeldata/src/lib.rs index 52e3187f..188a0469 100644 --- a/pixeldata/src/lib.rs +++ b/pixeldata/src/lib.rs @@ -2546,6 +2546,7 @@ mod tests { } #[test] + #[ignore = "test is unsound"] fn test_can_read_deflated(){ let path = dicom_test_files::path("pydicom/image_dfl.dcm").expect("test DICOM file should exist"); diff --git a/pixeldata/tests/integration_tests.rs b/pixeldata/tests/integration_tests.rs deleted file mode 100644 index e50dc2a3..00000000 --- a/pixeldata/tests/integration_tests.rs +++ /dev/null @@ -1 +0,0 @@ -use std::{io::BufReader, fs::File}; diff --git a/transfer-syntax-registry/src/deflate.rs b/transfer-syntax-registry/src/deflate.rs index 29b7bd42..b4ae340d 100644 --- a/transfer-syntax-registry/src/deflate.rs +++ b/transfer-syntax-registry/src/deflate.rs @@ -9,27 +9,12 @@ use flate2::Compression; #[derive(Debug)] pub struct FlateAdapter; -impl DataRWAdapter for FlateAdapter -where - R: Read, - W: Write, -{ - // type Reader = Box>; - // type Writer = Box>; - type Reader = Box; - type Writer = Box; - - fn adapt_reader(&self, reader: R) -> Self::Reader - where - R: Read, - { +impl DataRWAdapter for FlateAdapter { + fn adapt_reader<'r>(&self, reader: Box) -> Box { Box::new(flate2::read::DeflateDecoder::new(reader)) } - fn adapt_writer(&self, writer: W) -> Self::Writer - where - W: Write, - { + fn adapt_writer<'w>(&self, writer: Box) -> Box { Box::new(flate2::write::DeflateEncoder::new(writer, Compression::fast())) } } diff --git a/transfer-syntax-registry/tests/submit_dataset.rs b/transfer-syntax-registry/tests/submit_dataset.rs index dfd52bd9..22a238d2 100644 --- a/transfer-syntax-registry/tests/submit_dataset.rs +++ b/transfer-syntax-registry/tests/submit_dataset.rs @@ -15,22 +15,14 @@ use std::io::{Read, Write}; #[derive(Debug)] struct DummyCodecAdapter; -impl DataRWAdapter for DummyCodecAdapter { - type Reader = Box; - type Writer = Box; +impl DataRWAdapter for DummyCodecAdapter { - fn adapt_reader(&self, reader: R) -> Self::Reader - where - R: Read, - { - Box::new(reader) as Box<_> + fn adapt_reader<'r>(&self, reader: Box) -> Box { + reader } - fn adapt_writer(&self, writer: W) -> Self::Writer - where - W: Write, - { - Box::new(writer) as Box<_> + fn adapt_writer<'w>(&self, writer: Box) -> Box { + writer } } diff --git a/transfer-syntax-registry/tests/submit_replace.rs b/transfer-syntax-registry/tests/submit_replace.rs index 824c463b..1360f705 100644 --- a/transfer-syntax-registry/tests/submit_replace.rs +++ b/transfer-syntax-registry/tests/submit_replace.rs @@ -15,21 +15,12 @@ use std::io::{Read, Write}; #[derive(Debug)] struct DummyCodecAdapter; -impl DataRWAdapter for DummyCodecAdapter { - type Reader = Box; - type Writer = Box; - - fn adapt_reader(&self, _reader: R) -> Self::Reader - where - R: Read, - { +impl DataRWAdapter for DummyCodecAdapter { + fn adapt_reader<'r>(&self, _reader: Box) -> Box { unimplemented!() } - fn adapt_writer(&self, _writer: W) -> Self::Writer - where - W: Write, - { + fn adapt_writer<'w>(&self, _writer: Box) -> Box { unimplemented!() } } From cac06f88ce05a73366537826129ad6a34b84c2c3 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 10:06:13 +0000 Subject: [PATCH 05/16] [ts-registry] Skip unsound test --- transfer-syntax-registry/tests/deflate.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/transfer-syntax-registry/tests/deflate.rs b/transfer-syntax-registry/tests/deflate.rs index f184d769..0d51906f 100644 --- a/transfer-syntax-registry/tests/deflate.rs +++ b/transfer-syntax-registry/tests/deflate.rs @@ -23,6 +23,7 @@ fn test_read_data_deflated() { } #[test] +#[ignore = "test is unsound"] 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()); From adb169ccab49b661dbbe35961c8752c7c2e97544 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 10:26:31 +0000 Subject: [PATCH 06/16] Tweak test deflate::test_read_data_deflated - remove use of dicom_pixeldata - inspect dicom object contents --- Cargo.lock | 1 - transfer-syntax-registry/Cargo.toml | 1 - transfer-syntax-registry/tests/deflate.rs | 56 +++++++++++++++++------ 3 files changed, 43 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0670ee64..18c5d7d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -656,7 +656,6 @@ dependencies = [ "dicom-core", "dicom-encoding", "dicom-object", - "dicom-pixeldata", "dicom-test-files", "flate2", "jpeg-decoder", diff --git a/transfer-syntax-registry/Cargo.toml b/transfer-syntax-registry/Cargo.toml index d3341aaa..ed2b5145 100644 --- a/transfer-syntax-registry/Cargo.toml +++ b/transfer-syntax-registry/Cargo.toml @@ -101,4 +101,3 @@ features = ["native"] [dev-dependencies] dicom-test-files = "0.3" dicom-object = { path = "../object" } -dicom-pixeldata = { path = "../pixeldata" } diff --git a/transfer-syntax-registry/tests/deflate.rs b/transfer-syntax-registry/tests/deflate.rs index 0d51906f..a4fd783c 100644 --- a/transfer-syntax-registry/tests/deflate.rs +++ b/transfer-syntax-registry/tests/deflate.rs @@ -1,13 +1,15 @@ +use std::{ + fs::{metadata, File}, + io::BufReader, +}; -use std::{io::BufReader, fs::{metadata, File}}; - - +use dicom_core::Tag; use dicom_object::OpenFileOptions; -use dicom_pixeldata::PixelDecoder; #[test] fn test_read_data_deflated() { - let path = dicom_test_files::path("pydicom/image_dfl.dcm").expect("test DICOM file should exist"); + 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 @@ -15,17 +17,45 @@ fn test_read_data_deflated() { .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); + // inspect some attributes + + // SOP Instance UID + assert_eq!( + object.get(Tag(0x0008, 0x0018)).unwrap().to_str().unwrap(), + "1.3.6.1.4.1.5962.1.1.0.0.0.977067309.6001.0", + ); + + // photometric interpretation + assert_eq!( + object.get(Tag(0x0028, 0x0004)).unwrap().to_str().unwrap(), + "MONOCHROME2", + ); + + let rows: u16 = object.get(Tag(0x0028, 0x0010)).unwrap().to_int().unwrap(); + let cols: u16 = object.get(Tag(0x0028, 0x0011)).unwrap().to_int().unwrap(); + let spp: u16 = object.get(Tag(0x0028, 0x0002)).unwrap().to_int().unwrap(); + assert_eq!((rows, cols, spp), (512, 512, 1)); + + // pixel data + + let pixel_data = object.get(Tag(0x7FE0, 0x0010)).unwrap().to_bytes().unwrap(); + + assert_eq!( + pixel_data.len(), + rows as usize * cols as usize * spp as usize, + ); + + // poke some of the pixel samples + assert_eq!(pixel_data[0], 0xd5); + assert_eq!(pixel_data[0x0080], 0x29); + assert_eq!(pixel_data[0x0804], 0xff); } #[test] #[ignore = "test is unsound"] -fn write_deflated(){ - let path = dicom_test_files::path("pydicom/image_dfl.dcm").expect("test DICOM file should exist"); +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 @@ -36,4 +66,4 @@ fn write_deflated(){ let mut buf = Vec::::new(); object.write_all(&mut buf).unwrap(); assert_eq!(buf.len(), metadata(path).unwrap().len() as usize); -} \ No newline at end of file +} From 76eb6bb41f23a4fcd443e633c60d37fe7a2bdba6 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 11:52:19 +0000 Subject: [PATCH 07/16] [encoding] add TransferSyntax::is_encapsulated_pixel_data --- encoding/src/transfer_syntax/mod.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/encoding/src/transfer_syntax/mod.rs b/encoding/src/transfer_syntax/mod.rs index a4bfab38..e7ed5343 100644 --- a/encoding/src/transfer_syntax/mod.rs +++ b/encoding/src/transfer_syntax/mod.rs @@ -495,6 +495,13 @@ impl TransferSyntax { matches!(self.codec, Codec::Dataset(None)) } + /// Check whether this transfer syntax expects pixel data to be encapsulated. + /// + /// This does not imply that the pixel data can be decoded. + pub fn is_encapsulated_pixel_data(&self) -> bool { + matches!(self.codec, Codec::EncapsulatedPixelData(..)) + } + /// Check whether reading and writing the pixel data is unsupported. /// If this is `true`, encoding and decoding of the data set may still /// be possible, but the pixel data will only be available in its From 3a5ad33d56c0838f5249c697ca202bc873e527c3 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 11:55:17 +0000 Subject: [PATCH 08/16] [ts-registry] rename flate2 feature to deflate --- transfer-syntax-registry/Cargo.toml | 4 ++-- transfer-syntax-registry/src/entries.rs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/transfer-syntax-registry/Cargo.toml b/transfer-syntax-registry/Cargo.toml index ed2b5145..37dc30a0 100644 --- a/transfer-syntax-registry/Cargo.toml +++ b/transfer-syntax-registry/Cargo.toml @@ -11,13 +11,13 @@ keywords = ["dicom"] readme = "README.md" [features] -default = ["rayon", "simd", "flate2"] +default = ["rayon", "simd", "deflate"] # inventory for compile time plugin-based transfer syntax registration inventory-registry = ['dicom-encoding/inventory-registry'] # Flate2 for Deflate read/write -flate2 = ["dep:flate2"] +deflate = ["dep:flate2"] # natively implemented image encodings native = ["jpeg", "rle"] # native implementations that work on Windows diff --git a/transfer-syntax-registry/src/entries.rs b/transfer-syntax-registry/src/entries.rs index 645522ae..52d014a2 100644 --- a/transfer-syntax-registry/src/entries.rs +++ b/transfer-syntax-registry/src/entries.rs @@ -40,7 +40,7 @@ use crate::adapters::jpegls::{JpegLsAdapter, JpegLsLosslessWriter}; use crate::adapters::jpegxl::{JpegXlAdapter, JpegXlLosslessEncoder}; #[cfg(feature = "rle")] use crate::adapters::rle_lossless::RleLosslessAdapter; -#[cfg(feature = "flate2")] +#[cfg(feature = "deflate")] use crate::deflate::FlateAdapter; // -- the three base transfer syntaxes, fully supported -- @@ -183,7 +183,7 @@ pub const JPEG_LOSSLESS_NON_HIERARCHICAL_FIRST_ORDER_PREDICTION: Ts = create_ts_ "JPEG Lossless, Non-Hierarchical, First-Order Prediction", ); -#[cfg(feature = "flate2")] +#[cfg(feature = "deflate")] /// **Fully implemented**: Deflated Explicit VR Little Endian pub const DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN: TransferSyntax = TransferSyntax::new( "1.2.840.10008.1.2.1.99", @@ -193,7 +193,7 @@ pub const DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN: TransferSyntax Date: Sun, 8 Dec 2024 12:11:43 +0000 Subject: [PATCH 09/16] [ts-registry] Extend deflate support to other transfer syntaxes - JPIP Referenced Deflate - JPIP HTJ2K Referenced Deflate --- transfer-syntax-registry/src/entries.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/transfer-syntax-registry/src/entries.rs b/transfer-syntax-registry/src/entries.rs index 52d014a2..0b311676 100644 --- a/transfer-syntax-registry/src/entries.rs +++ b/transfer-syntax-registry/src/entries.rs @@ -203,15 +203,36 @@ pub const DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN: Ts = Ts::new_ele( // --- stub transfer syntaxes, known but not supported --- +/// **Implemented**: JPIP Referenced Deflate +#[cfg(feature = "deflate")] +pub const JPIP_REFERENCED_DEFLATE: TransferSyntax = TransferSyntax::new( + "1.2.840.10008.1.2.4.95", + "JPIP Referenced Deflate", + Endianness::Little, + true, + Codec::Dataset(Some(FlateAdapter)), +); /// **Stub descriptor:** JPIP Referenced Deflate +#[cfg(not(feature = "deflate"))] pub const JPIP_REFERENCED_DEFLATE: Ts = Ts::new_ele( "1.2.840.10008.1.2.4.95", "JPIP Referenced Deflate", Codec::Dataset(None), ); -/// **Stub descriptor:** JPIP Referenced Deflate +/// **Implemented**: JPIP HTJ2K Referenced Deflate +#[cfg(feature = "deflate")] +pub const JPIP_HTJ2K_REFERENCED_DEFLATE: TransferSyntax = TransferSyntax::new( + "1.2.840.10008.1.2.4.205", + "JPIP HT2JK Referenced Deflate", + Endianness::Little, + true, + Codec::Dataset(Some(FlateAdapter)), +); + +/// **Stub descriptor:** JPIP HTJ2K Referenced Deflate +#[cfg(not(feature = "deflate"))] pub const JPIP_HTJ2K_REFERENCED_DEFLATE: Ts = Ts::new_ele( "1.2.840.10008.1.2.4.205", "JPIP HTJ2K Referenced Deflate", From 8f83896ff2adea84824356a00f662a0c8d116202 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 12:12:30 +0000 Subject: [PATCH 10/16] [pixeldata] Add deflated support to dicom-transcode - check for differences in encapsulated data from source TS to target TS instead of whether they are codec-free --- pixeldata/src/transcode.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pixeldata/src/transcode.rs b/pixeldata/src/transcode.rs index ca26239f..f82008f9 100644 --- a/pixeldata/src/transcode.rs +++ b/pixeldata/src/transcode.rs @@ -125,14 +125,14 @@ where ts: current_ts_uid.to_string(), })?; - match (current_ts.is_codec_free(), ts.is_codec_free()) { - (true, true) => { + match (current_ts.is_encapsulated_pixel_data(), ts.is_encapsulated_pixel_data()) { + (false, false) => { // no pixel data conversion is necessary: // change transfer syntax and return self.meta_mut().set_transfer_syntax(ts); Ok(()) } - (false, true) => { + (true, false) => { // decode pixel data let decoded_pixeldata = self.decode_pixel_data().context(DecodePixelDataSnafu)?; @@ -171,7 +171,7 @@ where Ok(()) } - (_, false) => { + (_, true) => { // must decode then encode let writer = match ts.codec() { Codec::EncapsulatedPixelData(_, Some(writer)) => writer, @@ -181,7 +181,7 @@ where Codec::Dataset(None) => return UnsupportedTransferSyntaxSnafu.fail()?, Codec::Dataset(Some(_)) => return UnsupportedTranscodingSnafu.fail()?, Codec::None => { - // already tested in `is_codec_free` + // already tested in `is_encapsulated_pixel_data` unreachable!("Unexpected codec from transfer syntax") } }; From a2f996558a5d0e05a5484d0165979c259b3a0a79 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 12:23:33 +0000 Subject: [PATCH 11/16] [storescu] feature-gate Error::Transcode on "transcode" --- storescu/src/main.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/storescu/src/main.rs b/storescu/src/main.rs index 4b153772..0647bc90 100644 --- a/storescu/src/main.rs +++ b/storescu/src/main.rs @@ -141,6 +141,7 @@ enum Error { /// No TransferSyntax NoNegotiatedTransferSyntax, /// Transcoding error + #[cfg(feature = "transcode")] Transcode { source: dicom_pixeldata::TranscodeError, }, From 6ce171f624b3204db88884b1f12504119b2651a6 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 12:23:56 +0000 Subject: [PATCH 12/16] [ts-registry] Feature-gate deflate module on "deflate" --- transfer-syntax-registry/src/deflate.rs | 2 +- transfer-syntax-registry/src/lib.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/transfer-syntax-registry/src/deflate.rs b/transfer-syntax-registry/src/deflate.rs index b4ae340d..d247d736 100644 --- a/transfer-syntax-registry/src/deflate.rs +++ b/transfer-syntax-registry/src/deflate.rs @@ -5,7 +5,7 @@ use dicom_encoding::transfer_syntax::DataRWAdapter; use flate2; use flate2::Compression; -/// Immaterial type representing an adapter for deflated data. +/// An adapter for deflated data. #[derive(Debug)] pub struct FlateAdapter; diff --git a/transfer-syntax-registry/src/lib.rs b/transfer-syntax-registry/src/lib.rs index 7968992f..817a0b4a 100644 --- a/transfer-syntax-registry/src/lib.rs +++ b/transfer-syntax-registry/src/lib.rs @@ -114,6 +114,7 @@ pub use dicom_encoding::TransferSyntax; pub mod entries; mod adapters; +#[cfg(feature = "deflate")] mod deflate; #[cfg(feature = "inventory-registry")] From d9ded6e82601ac2eff50a79856ed1f2f540ccbb7 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 12:47:56 +0000 Subject: [PATCH 13/16] Update test submit_replace - only run it if JPIP Referenced Deflate is not fully supported yet --- transfer-syntax-registry/tests/submit_replace.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/transfer-syntax-registry/tests/submit_replace.rs b/transfer-syntax-registry/tests/submit_replace.rs index 1360f705..e78847da 100644 --- a/transfer-syntax-registry/tests/submit_replace.rs +++ b/transfer-syntax-registry/tests/submit_replace.rs @@ -1,8 +1,9 @@ //! Independent test for submission of a dummy TS implementation //! to replace a built-in stub. //! -//! Only applicable to the inventory-based registry. -#![cfg(feature = "inventory-registry")] +//! Only applicable to the inventory-based registry, +//! and only if JPIP Referenced Deflate is not yet supported. +#![cfg(all(feature = "inventory-registry", not(feature = "deflate")))] use dicom_encoding::{ submit_transfer_syntax, Codec, DataRWAdapter, Endianness, NeverPixelAdapter, TransferSyntax, From a40fc983d457b574f9e6f63c39f01c58d5f801bf Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 8 Dec 2024 12:53:07 +0000 Subject: [PATCH 14/16] Update test submit_replace_precondition --- .../tests/submit_replace_precondition.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/transfer-syntax-registry/tests/submit_replace_precondition.rs b/transfer-syntax-registry/tests/submit_replace_precondition.rs index 3c97910c..8fe231a7 100644 --- a/transfer-syntax-registry/tests/submit_replace_precondition.rs +++ b/transfer-syntax-registry/tests/submit_replace_precondition.rs @@ -4,15 +4,15 @@ //! Only applicable to the inventory-based registry. #![cfg(feature = "inventory-registry")] -use dicom_encoding::{Codec, TransferSyntaxIndex}; -use dicom_transfer_syntax_registry::TransferSyntaxRegistry; - /// Assert that this transfer syntax is provided built-in as a stub. /// -/// If this changes, please replace the transfer syntax to test against -/// and override. +/// This only applies if Cargo feature "deflate" is disabled. +#[cfg(not(feature = "deflate"))] #[test] fn registry_has_stub_ts_by_default() { + use dicom_encoding::{Codec, TransferSyntaxIndex}; + use dicom_transfer_syntax_registry::TransferSyntaxRegistry; + // this TS is provided by default, but not fully supported let ts = TransferSyntaxRegistry.get("1.2.840.10008.1.2.4.95"); assert!(ts.is_some()); From 394516a784c3d369587774238ab86a0142677700 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Sun, 15 Dec 2024 10:28:28 +0000 Subject: [PATCH 15/16] [ts-registry] Remove unnecessary use --- transfer-syntax-registry/src/deflate.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/transfer-syntax-registry/src/deflate.rs b/transfer-syntax-registry/src/deflate.rs index d247d736..4fc709fa 100644 --- a/transfer-syntax-registry/src/deflate.rs +++ b/transfer-syntax-registry/src/deflate.rs @@ -2,7 +2,6 @@ use std::io::{Read, Write}; use dicom_encoding::transfer_syntax::DataRWAdapter; -use flate2; use flate2::Compression; /// An adapter for deflated data. From 842d624736b2d1de3bdd9f3b9e49d19c62f556b9 Mon Sep 17 00:00:00 2001 From: Eduardo Pinho Date: Tue, 17 Dec 2024 08:18:18 +0000 Subject: [PATCH 16/16] [object] Remove more unnecessary 'static lifetime constraints --- object/src/file.rs | 2 +- object/src/mem.rs | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/object/src/file.rs b/object/src/file.rs index 7e1d7e3b..e8e986fd 100644 --- a/object/src/file.rs +++ b/object/src/file.rs @@ -154,7 +154,7 @@ impl OpenFileOptions { /// 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<'s: 'static, R: 's>(self, from: R) -> Result> + pub fn from_reader(self, from: R) -> Result> where R: Read, D: DataDictionary, diff --git a/object/src/mem.rs b/object/src/mem.rs index 28f9d1b3..4e682111 100644 --- a/object/src/mem.rs +++ b/object/src/mem.rs @@ -445,7 +445,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: 'static, S: 's>(src: S, dict: D) -> Result + pub fn from_reader_with_dict(src: S, dict: D) -> Result where S: Read, { @@ -465,9 +465,9 @@ 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: 'static, S: 's, R>(src: S, dict: D, ts_index: R) -> Result + pub fn from_reader_with(src: S, dict: D, ts_index: R) -> Result where - S: Read + 's, + S: Read, R: TransferSyntaxIndex, { Self::from_reader_with_all_options( @@ -480,7 +480,7 @@ where ) } - pub(crate) fn from_reader_with_all_options<'s: 'static, S, R>( + pub(crate) fn from_reader_with_all_options( src: S, dict: D, ts_index: R, @@ -489,7 +489,7 @@ where odd_length: OddLengthStrategy, ) -> Result where - S: Read + 's, + S: Read, R: TransferSyntaxIndex, { let mut file = BufReader::new(src);