diff --git a/encoding/src/transfer_syntax/mod.rs b/encoding/src/transfer_syntax/mod.rs index 39bfe16ac..6932e74d7 100644 --- a/encoding/src/transfer_syntax/mod.rs +++ b/encoding/src/transfer_syntax/mod.rs @@ -292,9 +292,10 @@ macro_rules! submit_ele_transfer_syntax { pub enum Codec { /// No codec is required for this transfer syntax. /// - /// Pixel data, if any, should be in its native, unencapsulated form. + /// Pixel data, if any, should be in its _native_, unencapsulated format. None, - /// Pixel data for this transfer syntax is encapsulated. + /// Pixel data for this transfer syntax is encapsulated + /// and likely subjected to a specific encoding process. /// The first part of the tuple struct contains the pixel data decoder, /// whereas the second item is for the pixel data encoder. /// diff --git a/transfer-syntax-registry/src/adapters/mod.rs b/transfer-syntax-registry/src/adapters/mod.rs index 3173a8677..bd7980870 100644 --- a/transfer-syntax-registry/src/adapters/mod.rs +++ b/transfer-syntax-registry/src/adapters/mod.rs @@ -16,6 +16,8 @@ pub mod jpeg; #[cfg(feature = "rle")] pub mod rle_lossless; +pub mod uncompressed; + /// **Note:** This module is a stub. /// Enable the `jpeg` feature to use this module. #[cfg(not(feature = "jpeg"))] diff --git a/transfer-syntax-registry/src/adapters/uncompressed.rs b/transfer-syntax-registry/src/adapters/uncompressed.rs new file mode 100644 index 000000000..337f5835f --- /dev/null +++ b/transfer-syntax-registry/src/adapters/uncompressed.rs @@ -0,0 +1,109 @@ +//! Support for encapsulated uncompressed via pixel data adapter. + +use dicom_core::{ + ops::{AttributeAction, AttributeOp}, + PrimitiveValue, Tag, +}; +use dicom_encoding::{ + adapters::{ + decode_error, encode_error, DecodeResult, EncodeOptions, EncodeResult, PixelDataObject, + PixelDataReader, PixelDataWriter, + }, + snafu::OptionExt, +}; + +/// Adapter for [Encapsulated Uncompressed Explicit VR Little Endian][1] +/// [1]: https://dicom.nema.org/medical/dicom/2023c/output/chtml/part05/sect_A.4.11.html +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct UncompressedAdapter; + +impl PixelDataReader for UncompressedAdapter { + fn decode(&self, src: &dyn PixelDataObject, dst: &mut Vec) -> DecodeResult<()> { + // just flatten all fragments into the output vector + let pixeldata = src + .raw_pixel_data() + .context(decode_error::MissingAttributeSnafu { name: "Pixel Data" })?; + + for fragment in pixeldata.fragments { + dst.extend_from_slice(&fragment); + } + + Ok(()) + } + + fn decode_frame( + &self, + src: &dyn PixelDataObject, + frame: u32, + dst: &mut Vec, + ) -> DecodeResult<()> { + // just copy the specific fragment into the output vector + let pixeldata = src + .raw_pixel_data() + .context(decode_error::MissingAttributeSnafu { name: "Pixel Data" })?; + + let fragment = pixeldata + .fragments + .get(frame as usize) + .context(decode_error::FrameRangeOutOfBoundsSnafu)?; + + dst.extend_from_slice(&fragment); + + Ok(()) + } +} + +impl PixelDataWriter for UncompressedAdapter { + fn encode_frame( + &self, + src: &dyn PixelDataObject, + frame: u32, + _options: EncodeOptions, + dst: &mut Vec, + ) -> EncodeResult> { + let cols = src + .cols() + .context(encode_error::MissingAttributeSnafu { name: "Columns" })?; + let rows = src + .rows() + .context(encode_error::MissingAttributeSnafu { name: "Rows" })?; + let samples_per_pixel = + src.samples_per_pixel() + .context(encode_error::MissingAttributeSnafu { + name: "SamplesPerPixel", + })?; + let bits_allocated = src + .bits_allocated() + .context(encode_error::MissingAttributeSnafu { + name: "BitsAllocated", + })?; + + let bytes_per_sample = (bits_allocated / 8) as usize; + let frame_size = + cols as usize * rows as usize * samples_per_pixel as usize * bytes_per_sample; + + // identify frame data using the frame index + let pixeldata_uncompressed = &src + .raw_pixel_data() + .context(encode_error::MissingAttributeSnafu { name: "Pixel Data" })? + .fragments[0]; + + let len_before = pixeldata_uncompressed.len(); + + let frame_data = pixeldata_uncompressed + .get(frame_size * frame as usize..frame_size * (frame as usize + 1)) + .whatever_context("Frame index out of bounds")?; + + // Copy the the data to the output + dst.extend_from_slice(&frame_data); + + // provide attribute changes + Ok(vec![ + // Encapsulated Pixel Data Value Total Length + AttributeOp::new( + Tag(0x7FE0, 0x0003), + AttributeAction::Set(PrimitiveValue::from(len_before as u64)), + ), + ]) + } +} diff --git a/transfer-syntax-registry/src/entries.rs b/transfer-syntax-registry/src/entries.rs index 67cd529b8..b635ada8e 100644 --- a/transfer-syntax-registry/src/entries.rs +++ b/transfer-syntax-registry/src/entries.rs @@ -21,7 +21,7 @@ //! hence expanding support for those transfer syntaxes //! to the registry. -use crate::create_ts_stub; +use crate::{adapters::uncompressed::UncompressedAdapter, create_ts_stub}; use byteordered::Endianness; use dicom_encoding::transfer_syntax::{AdapterFreeTransferSyntax as Ts, Codec}; @@ -62,6 +62,17 @@ pub const EXPLICIT_VR_BIG_ENDIAN: Ts = Ts::new( Codec::None, ); +/// **Fully implemented:** Encapsulated Uncompressed Explicit VR Little Endian +pub const ENCAPSULATED_UNCOMPRESSED_EXPLICIT_VR_LITTLE_ENDIAN: TransferSyntax< + NeverAdapter, + UncompressedAdapter, + UncompressedAdapter, +> = TransferSyntax::new_ele( + "1.2.840.10008.1.2.1.98", + "Encapsulated Uncompressed Explicit VR Little Endian", + Codec::EncapsulatedPixelData(Some(UncompressedAdapter), Some(UncompressedAdapter)), +); + // -- transfer syntaxes with pixel data adapters, fully supported -- /// **Implemented:** RLE Lossless diff --git a/transfer-syntax-registry/src/lib.rs b/transfer-syntax-registry/src/lib.rs index a17fb2821..2e6003e68 100644 --- a/transfer-syntax-registry/src/lib.rs +++ b/transfer-syntax-registry/src/lib.rs @@ -200,11 +200,13 @@ lazy_static! { }; use self::entries::*; - let built_in_ts: [TransferSyntax; 36] = [ + let built_in_ts: [TransferSyntax; 37] = [ IMPLICIT_VR_LITTLE_ENDIAN.erased(), EXPLICIT_VR_LITTLE_ENDIAN.erased(), EXPLICIT_VR_BIG_ENDIAN.erased(), + ENCAPSULATED_UNCOMPRESSED_EXPLICIT_VR_LITTLE_ENDIAN.erased(), + DEFLATED_EXPLICIT_VR_LITTLE_ENDIAN.erased(), JPIP_REFERENCED_DEFLATE.erased(), JPEG_BASELINE.erased(),