dicom_encoding/
adapters.rs

1//! Core module for building pixel data adapters.
2//!
3//! This module contains the core types and traits
4//! for consumers and implementers of
5//! transfer syntaxes with encapsulated pixel data.
6//!
7//! Complete DICOM object types
8//! (such as `FileDicomObject<InMemDicomObject>`)
9//! implement the [`PixelDataObject`] trait.
10//! Transfer syntaxes which define an encapsulated pixel data encoding
11//! need to provide suitable implementations of
12//! [`PixelDataReader`] and [`PixelDataWriter`]
13//! to be able to decode and encode imaging data, respectively.
14
15use dicom_core::{ops::AttributeOp, value::C};
16use snafu::Snafu;
17use std::borrow::Cow;
18
19/// The possible error conditions when decoding (reading) pixel data.
20///
21/// Users of this type are free to handle errors based on their variant,
22/// but should not make decisions based on the display message,
23/// since that is not considered part of the API
24/// and may change on any new release.
25///
26/// Implementers of transfer syntaxes
27/// are recommended to choose the most fitting error variant
28/// for the tested condition.
29/// When no suitable variant is available,
30/// the [`Custom`](DecodeError::Custom) variant may be used.
31/// See also [`snafu`] for guidance on using context selectors.
32#[derive(Debug, Snafu)]
33#[non_exhaustive]
34#[snafu(visibility(pub), module)]
35pub enum DecodeError {
36    /// A custom error occurred when decoding,
37    /// reported as a dynamic error value with a message.
38    ///
39    /// The [`whatever!`](snafu::whatever) macro can be used
40    /// to easily create an error of this kind.
41    #[snafu(whatever, display("{}", message))]
42    Custom {
43        /// The error message.
44        message: String,
45        /// The underlying error cause, if any.
46        #[snafu(source(from(Box<dyn std::error::Error + Send + Sync + 'static>, Some)))]
47        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
48    },
49
50    /// The input pixel data is not encapsulated.
51    ///
52    /// Either the image needs no decoding
53    /// or the compressed imaging data was in a flat pixel data element by mistake.
54    NotEncapsulated,
55
56    /// The requested frame range is outside the given object's frame range.
57    FrameRangeOutOfBounds,
58
59    /// A required attribute is missing
60    /// from the DICOM object representing the image.
61    #[snafu(display("Missing required attribute `{}`", name))]
62    MissingAttribute { name: &'static str },
63}
64
65/// The possible error conditions when encoding (writing) pixel data.
66///
67/// Users of this type are free to handle errors based on their variant,
68/// but should not make decisions based on the display message,
69/// since that is not considered part of the API
70/// and may change on any new release.
71///
72/// Implementers of transfer syntaxes
73/// are recommended to choose the most fitting error variant
74/// for the tested condition.
75/// When no suitable variant is available,
76/// the [`Custom`](EncodeError::Custom) variant may be used.
77/// See also [`snafu`] for guidance on using context selectors.
78#[derive(Debug, Snafu)]
79#[non_exhaustive]
80#[snafu(visibility(pub), module)]
81pub enum EncodeError {
82    /// A custom error when encoding fails.
83    /// Read the `message` and the underlying `source`
84    /// for more details.
85    #[snafu(whatever, display("{}", message))]
86    Custom {
87        /// The error message.
88        message: String,
89        /// The underlying error cause, if any.
90        #[snafu(source(from(Box<dyn std::error::Error + Send + Sync + 'static>, Some)))]
91        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
92    },
93
94    /// Input pixel data is not native, should be decoded first.
95    NotNative,
96
97    /// The requested frame range is outside the given object's frame range.
98    FrameRangeOutOfBounds,
99
100    /// A required attribute is missing
101    /// from the DICOM object representing the image.
102    #[snafu(display("Missing required attribute `{}`", name))]
103    MissingAttribute { name: &'static str },
104}
105
106/// The result of decoding (reading) pixel data
107pub type DecodeResult<T, E = DecodeError> = Result<T, E>;
108
109/// The result of encoding (writing) pixel data
110pub type EncodeResult<T, E = EncodeError> = Result<T, E>;
111
112#[derive(Debug)]
113pub struct RawPixelData {
114    /// Either a byte slice/vector if native pixel data
115    /// or byte fragments if encapsulated
116    pub fragments: C<Vec<u8>>,
117
118    /// The offset table for the fragments,
119    /// or empty if there is none
120    pub offset_table: C<u32>,
121}
122
123/// A DICOM object trait to be interpreted as pixel data.
124///
125/// This trait extends the concept of DICOM object
126/// as defined in [`dicom_object`],
127/// in order to retrieve important pieces of the object
128/// for pixel data decoding into images or multi-dimensional arrays.
129///
130/// It is defined in this crate so that
131/// transfer syntax implementers only have to depend on `dicom_encoding`.
132///
133/// [`dicom_object`]: https://docs.rs/dicom_object
134pub trait PixelDataObject {
135    /// Return the object's transfer syntax UID.
136    fn transfer_syntax_uid(&self) -> &str;
137
138    /// Return the _Rows_, or `None` if it is not found
139    fn rows(&self) -> Option<u16>;
140
141    /// Return the _Columns_, or `None` if it is not found
142    fn cols(&self) -> Option<u16>;
143
144    /// Return the _Samples Per Pixel_, or `None` if it is not found
145    fn samples_per_pixel(&self) -> Option<u16>;
146
147    /// Return the _Bits Allocated_, or `None` if it is not defined
148    fn bits_allocated(&self) -> Option<u16>;
149
150    /// Return the _Bits Stored_, or `None` if it is not defined
151    fn bits_stored(&self) -> Option<u16>;
152
153    /// Return the _Photometric Interpretation_,
154    /// with trailing whitespace removed,
155    /// or `None` if it is not defined
156    fn photometric_interpretation(&self) -> Option<&str>;
157
158    /// Return the _Number Of Frames_, or `None` if it is not defined
159    fn number_of_frames(&self) -> Option<u32>;
160
161    /// Returns the _Number of Fragments_, or `None` for native pixel data
162    fn number_of_fragments(&self) -> Option<u32>;
163
164    /// Return a specific encoded pixel fragment by index
165    /// (where 0 is the first fragment after the basic offset table)
166    /// as a [`Cow<[u8]>`][1],
167    /// or `None` if no such fragment is available.
168    ///
169    /// In the case of native (non-encapsulated) pixel data,
170    /// the whole data may be obtained
171    /// by requesting fragment number 0.
172    ///
173    /// [1]: std::borrow::Cow
174    fn fragment(&self, fragment: usize) -> Option<Cow<[u8]>>;
175
176    /// Return the object's offset table,
177    /// or `None` if no offset table is available.
178    fn offset_table(&self) -> Option<Cow<[u32]>>;
179
180    /// Should return either a byte slice/vector if the pixel data is native
181    /// or the list of byte fragments and offset table if encapsulated.
182    ///
183    /// Returns `None` if no pixel data is found.
184    fn raw_pixel_data(&self) -> Option<RawPixelData>;
185}
186
187/// Custom options when encoding pixel data into an encapsulated form.
188#[derive(Debug, Default, Clone)]
189#[non_exhaustive]
190pub struct EncodeOptions {
191    /// The quality of the output image as a number between 0 and 100,
192    /// where 100 is the best quality that the encapsulated form can achieve
193    /// and smaller values represent smaller data size
194    /// with an increasingly higher error.
195    /// It is ignored if the transfer syntax only supports lossless compression.
196    /// If it does support lossless compression,
197    /// it is expected that a quality of 100 results in
198    /// a mathematically lossless encoding.
199    ///
200    /// If this option is not specified,
201    /// the output quality is decided automatically by the underlying adapter.
202    pub quality: Option<u8>,
203
204    /// The amount of effort that the encoder may take to encode the pixel data,
205    /// as a number between 0 and 100.
206    /// If supported, higher values result in better compression,
207    /// at the expense of more processing time.
208    /// Encoders are not required to support this option.
209    /// If this option is not specified,
210    /// the actual effort is decided by the underlying adapter.
211    pub effort: Option<u8>,
212}
213
214impl EncodeOptions {
215    pub fn new() -> Self {
216        Self::default()
217    }
218}
219
220/// Trait object responsible for decoding
221/// pixel data based on the transfer syntax.
222///
223/// A transfer syntax with support for decoding encapsulated pixel data
224/// would implement these methods.
225pub trait PixelDataReader {
226    /// Decode the given DICOM object
227    /// containing encapsulated pixel data
228    /// into native pixel data as a byte stream in little endian,
229    /// appending these bytes to the given vector `dst`.
230    ///
231    /// It is a necessary precondition that the object's pixel data
232    /// is encoded in accordance to the transfer syntax(es)
233    /// supported by this adapter.
234    /// A `NotEncapsulated` error is returned otherwise.
235    ///
236    /// The output is a sequence of native pixel values
237    /// which follow the image properties of the given object
238    /// _save for the photometric interpretation and planar configuration_.
239    /// If the image has 3 samples per pixel,
240    /// the output must be in RGB with each pixel contiguous in memory
241    /// (planar configuration of 0).
242    /// However, if the image is monochrome,
243    /// the output should retain the photometric interpretation of the source object
244    /// (so that images in _MONOCHROME1_ continue to be in _MONOCHROME1_
245    /// and images in _MONOCHROME2_ continue to be in _MONOCHROME2_).
246    fn decode(&self, src: &dyn PixelDataObject, dst: &mut Vec<u8>) -> DecodeResult<()> {
247        let frames = src.number_of_frames().unwrap_or(1);
248        for frame in 0..frames {
249            self.decode_frame(src, frame, dst)?;
250        }
251        Ok(())
252    }
253
254    /// Decode the given DICOM object
255    /// containing encapsulated pixel data
256    /// into native pixel data of a single frame
257    /// as a byte stream in little endian,
258    /// appending these bytes to the given vector `dst`.
259    ///
260    /// The frame index is 0-based.
261    ///
262    /// It is a necessary precondition that the object's pixel data
263    /// is encoded in accordance to the transfer syntax(es)
264    /// supported by this adapter.
265    /// A `NotEncapsulated` error is returned otherwise.
266    ///
267    /// The output is a sequence of native pixel values of a frame
268    /// which follow the image properties of the given object
269    /// _save for the photometric interpretation and planar configuration_.
270    /// If the image has 3 samples per pixel,
271    /// the output must be in RGB with each pixel contiguous in memory
272    /// (planar configuration of 0).
273    /// However, if the image is monochrome,
274    /// the output should retain the photometric interpretation of the source object
275    /// (so that images in _MONOCHROME1_ continue to be in _MONOCHROME1_
276    /// and images in _MONOCHROME2_ continue to be in _MONOCHROME2_).
277    fn decode_frame(
278        &self,
279        src: &dyn PixelDataObject,
280        frame: u32,
281        dst: &mut Vec<u8>,
282    ) -> DecodeResult<()>;
283}
284
285/// Trait object responsible for encoding
286/// pixel data based on a certain transfer syntax.
287///
288/// A transfer syntax with support for creating compressed pixel data
289/// would implement these methods.
290pub trait PixelDataWriter {
291    /// Encode a DICOM object's image into the format supported by this adapter,
292    /// writing a byte stream of pixel data fragment values
293    /// to the given vector `dst`
294    /// and the offsets to each decoded frame into `offset_table`.
295    ///
296    /// New data is appended to `dst` and `offset_table`,
297    /// which are not cleared before writing.
298    ///
299    /// All implementations are required to support
300    /// writing the object's pixel data when it is in a _native encoding_.
301    /// If the given pixel data object is not in a native encoding,
302    /// and this writer does not support transcoding
303    /// from that encoding to the target transfer syntax,
304    /// a `NotNative` error is returned instead.
305    ///
306    /// When the operation is successful,
307    /// a listing of attribute changes is returned,
308    /// comprising the sequence of operations that the DICOM object
309    /// should consider upon assuming the new encoding.
310    fn encode(
311        &self,
312        src: &dyn PixelDataObject,
313        options: EncodeOptions,
314        dst: &mut Vec<Vec<u8>>,
315        offset_table: &mut Vec<u32>,
316    ) -> EncodeResult<Vec<AttributeOp>> {
317        let frames = src.number_of_frames().unwrap_or(1);
318        let mut out = Vec::new();
319        for frame in 0..frames {
320            let mut frame_data = Vec::new();
321            out = self.encode_frame(src, frame, options.clone(), &mut frame_data)?;
322            offset_table.push(frame_data.len() as u32 + 8 * (frame + 1));
323            dst.push(frame_data);
324        }
325        Ok(out)
326    }
327
328    /// Encode a single frame of a DICOM object's image
329    /// into the format supported by this adapter,
330    /// by writing a byte stream of pixel data values
331    /// into the given destination.
332    /// The bytes written comprise a single pixel data fragment
333    /// in its entirety.
334    ///
335    /// New data is appended to `dst`,
336    /// keeping all bytes previously present before writing.
337    ///
338    /// All implementations are required to support
339    /// writing the object's pixel data when it is in a _native encoding_.
340    /// If the given pixel data object is not in a native encoding,
341    /// and this writer does not support transcoding
342    /// from that encoding to the target transfer syntax,
343    /// a `NotNative` error is returned instead.
344    ///
345    /// When the operation is successful,
346    /// a listing of attribute changes is returned,
347    /// comprising the sequence of operations that the DICOM object
348    /// should consider upon assuming the new encoding.
349    fn encode_frame(
350        &self,
351        src: &dyn PixelDataObject,
352        frame: u32,
353        options: EncodeOptions,
354        dst: &mut Vec<u8>,
355    ) -> EncodeResult<Vec<AttributeOp>>;
356}
357
358/// Alias type for a dynamically dispatched pixel data reader.
359pub type DynPixelDataReader = Box<dyn PixelDataReader + Send + Sync + 'static>;
360
361/// Alias type for a dynamically dispatched pixel data writer.
362pub type DynPixelDataWriter = Box<dyn PixelDataWriter + Send + Sync + 'static>;
363
364/// An immaterial type representing an adapter which is never provided.
365///
366/// This type may be used as the type parameters `R` and `W`
367/// of [`TransferSyntax`](crate::transfer_syntax::TransferSyntax)
368/// when representing a transfer syntax which
369/// either does not support reading and writing imaging data,
370/// or when such support is not needed in the first place.
371#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
372pub enum NeverPixelAdapter {}
373
374impl PixelDataReader for NeverPixelAdapter {
375    fn decode(&self, _src: &dyn PixelDataObject, _dst: &mut Vec<u8>) -> DecodeResult<()> {
376        unreachable!()
377    }
378
379    fn decode_frame(
380        &self,
381        _src: &dyn PixelDataObject,
382        _frame: u32,
383        _dst: &mut Vec<u8>,
384    ) -> DecodeResult<()> {
385        unreachable!()
386    }
387}
388
389impl PixelDataWriter for NeverPixelAdapter {
390    fn encode(
391        &self,
392        _src: &dyn PixelDataObject,
393        _options: EncodeOptions,
394        _dst: &mut Vec<Vec<u8>>,
395        _offset_table: &mut Vec<u32>,
396    ) -> EncodeResult<Vec<AttributeOp>> {
397        unreachable!()
398    }
399
400    fn encode_frame(
401        &self,
402        _src: &dyn PixelDataObject,
403        _frame: u32,
404        _options: EncodeOptions,
405        _dst: &mut Vec<u8>,
406    ) -> EncodeResult<Vec<AttributeOp>> {
407        unreachable!()
408    }
409}
410
411impl PixelDataReader for crate::transfer_syntax::NeverAdapter {
412    fn decode(&self, _src: &dyn PixelDataObject, _dst: &mut Vec<u8>) -> DecodeResult<()> {
413        unreachable!()
414    }
415
416    fn decode_frame(
417        &self,
418        _src: &dyn PixelDataObject,
419        _frame: u32,
420        _dst: &mut Vec<u8>,
421    ) -> DecodeResult<()> {
422        unreachable!()
423    }
424}
425
426impl PixelDataWriter for crate::transfer_syntax::NeverAdapter {
427    fn encode(
428        &self,
429        _src: &dyn PixelDataObject,
430        _options: EncodeOptions,
431        _dst: &mut Vec<Vec<u8>>,
432        _offset_table: &mut Vec<u32>,
433    ) -> EncodeResult<Vec<AttributeOp>> {
434        unreachable!()
435    }
436
437    fn encode_frame(
438        &self,
439        _src: &dyn PixelDataObject,
440        _frame: u32,
441        _options: EncodeOptions,
442        _dst: &mut Vec<u8>,
443    ) -> EncodeResult<Vec<AttributeOp>> {
444        unreachable!()
445    }
446}