Skip to main content

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    #[snafu(display("Pixel Data is not encapsulated"))]
55    NotEncapsulated,
56
57    /// Pixel Data is missing
58    /// or the requested frame range is outside the object's frame range.
59    #[snafu(display("Frame of pixel data is missing or out of bounds"))]
60    FrameRangeOutOfBounds,
61
62    /// A required attribute is missing
63    /// from the DICOM object representing the image.
64    #[snafu(display("Missing required attribute `{}`", name))]
65    MissingAttribute { name: &'static str },
66}
67
68/// The possible error conditions when encoding (writing) pixel data.
69///
70/// Users of this type are free to handle errors based on their variant,
71/// but should not make decisions based on the display message,
72/// since that is not considered part of the API
73/// and may change on any new release.
74///
75/// Implementers of transfer syntaxes
76/// are recommended to choose the most fitting error variant
77/// for the tested condition.
78/// When no suitable variant is available,
79/// the [`Custom`](EncodeError::Custom) variant may be used.
80/// See also [`snafu`] for guidance on using context selectors.
81#[derive(Debug, Snafu)]
82#[non_exhaustive]
83#[snafu(visibility(pub), module)]
84pub enum EncodeError {
85    /// A custom error when encoding fails.
86    /// Read the `message` and the underlying `source`
87    /// for more details.
88    #[snafu(whatever, display("{}", message))]
89    Custom {
90        /// The error message.
91        message: String,
92        /// The underlying error cause, if any.
93        #[snafu(source(from(Box<dyn std::error::Error + Send + Sync + 'static>, Some)))]
94        source: Option<Box<dyn std::error::Error + Send + Sync + 'static>>,
95    },
96
97    /// Input pixel data is not native, should be decoded first.
98    #[snafu(display("Pixel Data is not native"))]
99    NotNative,
100
101    /// Pixel Data is missing
102    /// or the requested frame range is outside the object's frame range.
103    #[snafu(display("Frame of pixel data is missing or out of bounds"))]
104    FrameRangeOutOfBounds,
105
106    /// A required attribute is missing
107    /// from the DICOM object representing the image.
108    #[snafu(display("Missing required attribute `{}`", name))]
109    MissingAttribute { name: &'static str },
110}
111
112/// The result of decoding (reading) pixel data
113pub type DecodeResult<T, E = DecodeError> = Result<T, E>;
114
115/// The result of encoding (writing) pixel data
116pub type EncodeResult<T, E = EncodeError> = Result<T, E>;
117
118#[derive(Debug)]
119pub struct RawPixelData {
120    /// Either a byte slice/vector if native pixel data
121    /// or byte fragments if encapsulated
122    pub fragments: C<Vec<u8>>,
123
124    /// The offset table for the fragments,
125    /// or empty if there is none
126    pub offset_table: C<u32>,
127}
128
129/// A DICOM object trait to be interpreted as pixel data.
130///
131/// This trait extends the concept of DICOM object
132/// as defined in [`dicom_object`],
133/// in order to retrieve important pieces of the object
134/// for pixel data decoding into images or multi-dimensional arrays.
135///
136/// It is defined in this crate so that
137/// transfer syntax implementers only have to depend on `dicom_encoding`.
138///
139/// [`dicom_object`]: https://docs.rs/dicom_object
140pub trait PixelDataObject {
141    /// Return the object's transfer syntax UID.
142    fn transfer_syntax_uid(&self) -> &str;
143
144    /// Return the _Rows_, or `None` if it is not found
145    fn rows(&self) -> Option<u16>;
146
147    /// Return the _Columns_, or `None` if it is not found
148    fn cols(&self) -> Option<u16>;
149
150    /// Return the _Samples Per Pixel_, or `None` if it is not found
151    fn samples_per_pixel(&self) -> Option<u16>;
152
153    /// Return the _Bits Allocated_, or `None` if it is not defined
154    fn bits_allocated(&self) -> Option<u16>;
155
156    /// Return the _Bits Stored_, or `None` if it is not defined
157    fn bits_stored(&self) -> Option<u16>;
158
159    /// Return the _Photometric Interpretation_,
160    /// with trailing whitespace removed,
161    /// or `None` if it is not defined
162    fn photometric_interpretation(&self) -> Option<&str>;
163
164    /// Return the _Number Of Frames_,
165    /// or `None` if it is not defined by this object.
166    fn number_of_frames(&self) -> Option<u32>;
167
168    /// Returns the _number of pixel data fragments_,
169    /// excluding the basic offset table,
170    /// or `None` for native pixel data.
171    fn number_of_fragments(&self) -> Option<u32>;
172
173    /// Return a specific encoded pixel fragment by index
174    /// (where 0 is the first fragment after the basic offset table)
175    /// as a [`Cow<[u8]>`][1],
176    /// or `None` if no such fragment is available.
177    ///
178    /// In the case of native (non-encapsulated) pixel data,
179    /// the whole data may be obtained
180    /// by requesting fragment number 0.
181    ///
182    /// [1]: std::borrow::Cow
183    fn fragment(&self, fragment: usize) -> Option<Cow<'_, [u8]>>;
184
185    /// Return the object's offset table,
186    /// or `None` if no offset table is available.
187    fn offset_table(&self) -> Option<Cow<'_, [u32]>>;
188
189    /// Should return either a byte slice/vector if the pixel data is native
190    /// or the list of byte fragments and offset table if encapsulated.
191    ///
192    /// Returns `None` if no pixel data is found.
193    fn raw_pixel_data(&self) -> Option<RawPixelData>;
194
195    /// Return the pixel data of a specific frame as a byte slice/vector,
196    /// in its encoded form.
197    ///
198    /// Returns `None` if there is no such frame or there is no pixel data at all.
199    ///
200    /// _Note:_ If pixel data is uncompressed and Bits Allocated is 1,
201    /// the slice may include leading or trailing bits
202    /// belonging to other frames,
203    /// depending on the size of each frame.
204    fn frame_pixel_data(&self, frame: u32) -> Option<Cow<'_, [u8]>> {
205        match self.number_of_fragments() {
206            // Handle cases of single frame object
207            // and multi-frame with 1:1 fragment to frame.
208            // This is important not just for the shortcut,
209            // but because the basic offset table may be missing.
210            Some(number_of_fragments)
211                if number_of_fragments == self.number_of_frames().unwrap_or(1) =>
212            {
213                self.fragment(frame as usize)
214            }
215            // Other cases of multi-frame objects
216            Some(number_of_fragments) => {
217                // In this case we look up the basic offset table
218                // and gather all of the frame's fragments in a single vector.
219                // Note: not the most efficient way to do this,
220                // consider optimizing later with byte chunk readers
221                let offset_table = self.offset_table()?;
222                let base_offset = offset_table.get(frame as usize).copied();
223                let base_offset = if frame == 0 {
224                    base_offset.unwrap_or(0) as usize
225                } else {
226                    base_offset? as usize
227                };
228                let next_offset = offset_table.get(frame as usize + 1);
229
230                let mut offset = 0;
231                let mut frame_data = Vec::new();
232                for idx in 0..number_of_fragments as usize {
233                    let fragment = self.fragment(idx)?;
234                    // include it
235                    if offset >= base_offset {
236                        frame_data.extend_from_slice(&fragment);
237                    }
238                    offset += fragment.len() + 8;
239                    if let Some(&next_offset) = next_offset {
240                        if offset >= next_offset as usize {
241                            // next fragment is for the next frame
242                            break;
243                        }
244                    }
245                }
246
247                Some(Cow::Owned(frame_data))
248            }
249            // In the case of native pixel data, the whole data is considered as a single fragment.
250            None => {
251                let bits_allocated = self.bits_allocated()?;
252                let rows = self.rows()?;
253                let columns = self.cols()?;
254                let frame_size = determine_bytes_per_native_frame(
255                    rows,
256                    columns,
257                    self.samples_per_pixel()?,
258                    bits_allocated,
259                    self.photometric_interpretation()?,
260                );
261                let pixel_data = self.fragment(0)?;
262
263                // special case of 1 bit per sample:
264                // include starting byte even it leading bits are outside the frame
265                // and include next byte if it ends outside frame boundary
266                let (start, end) = if bits_allocated == 1 {
267                    let samples_per_frame = rows as usize * columns as usize;
268                    let start = frame as usize * samples_per_frame / 8;
269                    let end = ((frame as usize + 1) * samples_per_frame).div_ceil(8);
270                    (start, end)
271                } else {
272                    let start = frame as usize * frame_size;
273                    let end = start + frame_size;
274                    (start, end)
275                };
276                if end <= pixel_data.len() {
277                    match pixel_data {
278                        Cow::Borrowed(slice) => slice.get(start..end).map(Cow::Borrowed),
279                        Cow::Owned(vec) => vec.get(start..end).map(|s| Cow::Owned(s.to_vec())),
280                    }
281                } else {
282                    None
283                }
284            }
285        }
286    }
287}
288
289/// Custom options when encoding pixel data into an encapsulated form.
290#[derive(Debug, Default, Clone)]
291#[non_exhaustive]
292pub struct EncodeOptions {
293    /// The quality of the output image as a number between 0 and 100,
294    /// where 100 is the best quality that the encapsulated form can achieve
295    /// and smaller values represent smaller data size
296    /// with an increasingly higher error.
297    /// It is ignored if the transfer syntax only supports lossless compression.
298    /// If it does support lossless compression,
299    /// it is expected that a quality of 100 results in
300    /// a mathematically lossless encoding.
301    ///
302    /// If this option is not specified,
303    /// the output quality is decided automatically by the underlying adapter.
304    pub quality: Option<u8>,
305
306    /// The amount of effort that the encoder may take to encode the pixel data,
307    /// as a number between 0 and 100.
308    /// If supported, higher values result in better compression,
309    /// at the expense of more processing time.
310    /// Encoders are not required to support this option.
311    /// If this option is not specified,
312    /// the actual effort is decided by the underlying adapter.
313    pub effort: Option<u8>,
314}
315
316impl EncodeOptions {
317    pub fn new() -> Self {
318        Self::default()
319    }
320}
321
322/// Trait object responsible for decoding
323/// pixel data based on the transfer syntax.
324///
325/// A transfer syntax with support for decoding encapsulated pixel data
326/// would implement these methods.
327pub trait PixelDataReader {
328    /// Decode the given DICOM object
329    /// containing encapsulated pixel data
330    /// into native pixel data as a byte stream in little endian,
331    /// appending these bytes to the given vector `dst`.
332    ///
333    /// It is a necessary precondition that the object's pixel data
334    /// is encoded in accordance to the transfer syntax(es)
335    /// supported by this adapter.
336    /// A `NotEncapsulated` error is returned otherwise.
337    ///
338    /// The output is a sequence of native pixel values
339    /// which follow the image properties of the given object
340    /// _save for the photometric interpretation and planar configuration_.
341    /// If the image has 3 samples per pixel,
342    /// the output must be in RGB with each pixel contiguous in memory
343    /// (planar configuration of 0).
344    /// However, if the image is monochrome,
345    /// the output should retain the photometric interpretation of the source object
346    /// (so that images in _MONOCHROME1_ continue to be in _MONOCHROME1_
347    /// and images in _MONOCHROME2_ continue to be in _MONOCHROME2_).
348    fn decode(&self, src: &dyn PixelDataObject, dst: &mut Vec<u8>) -> DecodeResult<()> {
349        let frames = src.number_of_frames().unwrap_or(1);
350        for frame in 0..frames {
351            self.decode_frame(src, frame, dst)?;
352        }
353        Ok(())
354    }
355
356    /// Decode the given DICOM object
357    /// containing encapsulated pixel data
358    /// into native pixel data of a single frame
359    /// as a byte stream in little endian,
360    /// appending these bytes to the given vector `dst`.
361    ///
362    /// The frame index is 0-based.
363    ///
364    /// It is a necessary precondition that the object's pixel data
365    /// is encoded in accordance to the transfer syntax(es)
366    /// supported by this adapter.
367    /// A `NotEncapsulated` error is returned otherwise.
368    ///
369    /// The output is a sequence of native pixel values of a frame
370    /// which follow the image properties of the given object
371    /// _save for the photometric interpretation and planar configuration_.
372    /// If the image has 3 samples per pixel,
373    /// the output must be in RGB with each pixel contiguous in memory
374    /// (planar configuration of 0).
375    /// For pixel data with a single sample per pixel,
376    /// the output shall retain the photometric interpretation
377    /// declared in the original object
378    /// if it is one of _MONOCHROME1_, _MONOCHROME2_, or _PALETTE COLOR_.
379    /// For any other photometric interpretation,
380    /// the output shall be assumed to be in _MONOCHROME2_.
381    fn decode_frame(
382        &self,
383        src: &dyn PixelDataObject,
384        frame: u32,
385        dst: &mut Vec<u8>,
386    ) -> DecodeResult<()>;
387}
388
389/// Trait object responsible for encoding
390/// pixel data based on a certain transfer syntax.
391///
392/// A transfer syntax with support for creating compressed pixel data
393/// would implement these methods.
394pub trait PixelDataWriter {
395    /// Encode a DICOM object's image into the format supported by this adapter,
396    /// writing a byte stream of pixel data fragment values
397    /// to the given vector `dst`
398    /// and the offsets to each decoded frame into `offset_table`.
399    ///
400    /// New data is appended to `dst` and `offset_table`,
401    /// which are not cleared before writing.
402    ///
403    /// All implementations are required to support
404    /// writing the object's pixel data when it is in a _native encoding_.
405    /// If the given pixel data object is not in a native encoding,
406    /// and this writer does not support transcoding
407    /// from that encoding to the target transfer syntax,
408    /// a `NotNative` error is returned instead.
409    ///
410    /// When the operation is successful,
411    /// a listing of attribute changes is returned,
412    /// comprising the sequence of operations that the DICOM object
413    /// should consider upon assuming the new encoding.
414    fn encode(
415        &self,
416        src: &dyn PixelDataObject,
417        options: EncodeOptions,
418        dst: &mut Vec<Vec<u8>>,
419        offset_table: &mut Vec<u32>,
420    ) -> EncodeResult<Vec<AttributeOp>> {
421        let frames = src.number_of_frames().unwrap_or(1);
422        let mut out = Vec::new();
423        for frame in 0..frames {
424            let mut frame_data = Vec::new();
425            out = self.encode_frame(src, frame, options.clone(), &mut frame_data)?;
426            offset_table.push(frame_data.len() as u32 + 8 * (frame + 1));
427            dst.push(frame_data);
428        }
429        Ok(out)
430    }
431
432    /// Encode a single frame of a DICOM object's image
433    /// into the format supported by this adapter,
434    /// by writing a byte stream of pixel data values
435    /// into the given destination.
436    /// The bytes written comprise a single pixel data fragment
437    /// in its entirety.
438    ///
439    /// New data is appended to `dst`,
440    /// keeping all bytes previously present before writing.
441    ///
442    /// All implementations are required to support
443    /// writing the object's pixel data when it is in a _native encoding_.
444    /// If the given pixel data object is not in a native encoding,
445    /// and this writer does not support transcoding
446    /// from that encoding to the target transfer syntax,
447    /// a `NotNative` error is returned instead.
448    ///
449    /// When the operation is successful,
450    /// a listing of attribute changes is returned,
451    /// comprising the sequence of operations that the DICOM object
452    /// should consider upon assuming the new encoding.
453    fn encode_frame(
454        &self,
455        src: &dyn PixelDataObject,
456        frame: u32,
457        options: EncodeOptions,
458        dst: &mut Vec<u8>,
459    ) -> EncodeResult<Vec<AttributeOp>>;
460}
461
462/// Alias type for a dynamically dispatched pixel data reader.
463pub type DynPixelDataReader = Box<dyn PixelDataReader + Send + Sync + 'static>;
464
465/// Alias type for a dynamically dispatched pixel data writer.
466pub type DynPixelDataWriter = Box<dyn PixelDataWriter + Send + Sync + 'static>;
467
468/// An immaterial type representing an adapter which is never provided.
469///
470/// This type may be used as the type parameters `R` and `W`
471/// of [`TransferSyntax`](crate::transfer_syntax::TransferSyntax)
472/// when representing a transfer syntax which
473/// either does not support reading and writing imaging data,
474/// or when such support is not needed in the first place.
475#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
476pub enum NeverPixelAdapter {}
477
478impl PixelDataReader for NeverPixelAdapter {
479    fn decode(&self, _src: &dyn PixelDataObject, _dst: &mut Vec<u8>) -> DecodeResult<()> {
480        unreachable!()
481    }
482
483    fn decode_frame(
484        &self,
485        _src: &dyn PixelDataObject,
486        _frame: u32,
487        _dst: &mut Vec<u8>,
488    ) -> DecodeResult<()> {
489        unreachable!()
490    }
491}
492
493impl PixelDataWriter for NeverPixelAdapter {
494    fn encode(
495        &self,
496        _src: &dyn PixelDataObject,
497        _options: EncodeOptions,
498        _dst: &mut Vec<Vec<u8>>,
499        _offset_table: &mut Vec<u32>,
500    ) -> EncodeResult<Vec<AttributeOp>> {
501        unreachable!()
502    }
503
504    fn encode_frame(
505        &self,
506        _src: &dyn PixelDataObject,
507        _frame: u32,
508        _options: EncodeOptions,
509        _dst: &mut Vec<u8>,
510    ) -> EncodeResult<Vec<AttributeOp>> {
511        unreachable!()
512    }
513}
514
515impl PixelDataReader for crate::transfer_syntax::NeverAdapter {
516    fn decode(&self, _src: &dyn PixelDataObject, _dst: &mut Vec<u8>) -> DecodeResult<()> {
517        unreachable!()
518    }
519
520    fn decode_frame(
521        &self,
522        _src: &dyn PixelDataObject,
523        _frame: u32,
524        _dst: &mut Vec<u8>,
525    ) -> DecodeResult<()> {
526        unreachable!()
527    }
528}
529
530impl PixelDataWriter for crate::transfer_syntax::NeverAdapter {
531    fn encode(
532        &self,
533        _src: &dyn PixelDataObject,
534        _options: EncodeOptions,
535        _dst: &mut Vec<Vec<u8>>,
536        _offset_table: &mut Vec<u32>,
537    ) -> EncodeResult<Vec<AttributeOp>> {
538        unreachable!()
539    }
540
541    fn encode_frame(
542        &self,
543        _src: &dyn PixelDataObject,
544        _frame: u32,
545        _options: EncodeOptions,
546        _dst: &mut Vec<u8>,
547    ) -> EncodeResult<Vec<AttributeOp>> {
548        unreachable!()
549    }
550}
551
552/// Use the information in a pixel data object
553/// to determine the number of bytes needed to encode one frame.
554///
555/// Only makes sense if the object contains native pixel data.
556#[inline]
557fn determine_bytes_per_native_frame(
558    rows: u16,
559    columns: u16,
560    samples_per_pixel: u16,
561    bits_allocated: u16,
562    photometric_interpretation: &str,
563) -> usize {
564    // handle special case of 1 bit per sample
565    if bits_allocated == 1 {
566        return (rows as usize * columns as usize).div_ceil(8);
567    }
568
569    let real_samples_per_pixel =
570        if samples_per_pixel == 3 && photometric_interpretation == "YBR_FULL_422" {
571            2
572        } else {
573            samples_per_pixel
574        };
575    rows as usize
576        * columns as usize
577        * real_samples_per_pixel as usize
578        * (bits_allocated as usize).div_ceil(8)
579}
580
581#[cfg(test)]
582mod tests {
583    use std::borrow::Cow;
584
585    use dicom_core::value::{InMemFragment, PixelFragmentSequence};
586
587    use crate::adapters::RawPixelData;
588
589    use super::PixelDataObject;
590
591    /// Generates frames with solid pixel data values 0, 1, and so on.
592    fn generated_rgb_frames(columns: u16, rows: u16) -> impl Iterator<Item = Vec<u8>> {
593        (0..=255_u8).map(move |n| vec![n; columns as usize * rows as usize * 3])
594    }
595
596    pub(crate) struct TestDataObject {
597        pub ts_uid: &'static str,
598        pub rows: u16,
599        pub columns: u16,
600        pub bits_allocated: u16,
601        pub bits_stored: u16,
602        pub samples_per_pixel: u16,
603        pub photometric_interpretation: &'static str,
604        pub number_of_frames: u32,
605        pub flat_pixel_data: Option<Vec<u8>>,
606        pub pixel_data_sequence: Option<PixelFragmentSequence<InMemFragment>>,
607    }
608
609    impl PixelDataObject for TestDataObject {
610        fn transfer_syntax_uid(&self) -> &str {
611            &self.ts_uid
612        }
613
614        fn rows(&self) -> Option<u16> {
615            Some(self.rows)
616        }
617
618        fn cols(&self) -> Option<u16> {
619            Some(self.columns)
620        }
621
622        fn samples_per_pixel(&self) -> Option<u16> {
623            Some(self.samples_per_pixel)
624        }
625
626        fn bits_allocated(&self) -> Option<u16> {
627            Some(self.bits_allocated)
628        }
629
630        fn bits_stored(&self) -> Option<u16> {
631            Some(self.bits_stored)
632        }
633
634        fn photometric_interpretation(&self) -> Option<&str> {
635            Some(self.photometric_interpretation)
636        }
637
638        fn number_of_frames(&self) -> Option<u32> {
639            Some(self.number_of_frames)
640        }
641
642        fn number_of_fragments(&self) -> Option<u32> {
643            self.pixel_data_sequence
644                .as_ref()
645                .map(|v| v.fragments().len() as u32)
646        }
647
648        fn fragment(&self, fragment: usize) -> Option<Cow<'_, [u8]>> {
649            match (&self.flat_pixel_data, &self.pixel_data_sequence) {
650                (Some(_), Some(_)) => {
651                    panic!("Invalid pixel data object (both flat and fragment sequence)")
652                }
653                (_, Some(v)) => v
654                    .fragments()
655                    .get(fragment)
656                    .map(|f| Cow::Borrowed(f.as_slice())),
657                (Some(v), _) => {
658                    if fragment == 0 {
659                        Some(Cow::Borrowed(v))
660                    } else {
661                        None
662                    }
663                }
664                (None, None) => None,
665            }
666        }
667
668        fn offset_table(&self) -> Option<Cow<'_, [u32]>> {
669            match &self.pixel_data_sequence {
670                Some(v) => Some(Cow::Borrowed(v.offset_table())),
671                _ => None,
672            }
673        }
674
675        fn raw_pixel_data(&self) -> Option<RawPixelData> {
676            match (&self.flat_pixel_data, &self.pixel_data_sequence) {
677                (Some(_), Some(_)) => {
678                    panic!("Invalid pixel data object (both flat and fragment sequence)")
679                }
680                (Some(v), _) => Some(RawPixelData {
681                    fragments: vec![v.clone()].into(),
682                    offset_table: Default::default(),
683                }),
684                (_, Some(v)) => Some(RawPixelData {
685                    fragments: v.fragments().into(),
686                    offset_table: v.offset_table().into(),
687                }),
688                _ => None,
689            }
690        }
691    }
692
693    /// Frame pixel data can be retrieved from an object
694    /// with native pixel data.
695    #[test]
696    fn frame_pixel_data_in_object_flat() {
697        let rows = 10;
698        let columns = 10;
699        let number_of_frames = 4;
700
701        let obj = TestDataObject {
702            ts_uid: "1.2.840.10008.1.2.1",
703            rows,
704            columns,
705            bits_allocated: 8,
706            bits_stored: 8,
707            samples_per_pixel: 3,
708            photometric_interpretation: "RGB",
709            number_of_frames,
710            flat_pixel_data: Some(
711                generated_rgb_frames(columns, rows)
712                    .take(number_of_frames as usize)
713                    .flatten()
714                    .collect(),
715            ),
716            pixel_data_sequence: None,
717        };
718
719        assert_eq!(obj.frame_pixel_data(0), Some(vec![0; 10 * 10 * 3].into()));
720        assert_eq!(obj.frame_pixel_data(1), Some(vec![1; 10 * 10 * 3].into()));
721        assert_eq!(obj.frame_pixel_data(2), Some(vec![2; 10 * 10 * 3].into()));
722        assert_eq!(obj.frame_pixel_data(3), Some(vec![3; 10 * 10 * 3].into()));
723        assert_eq!(obj.frame_pixel_data(4), None);
724    }
725
726    /// Frame pixel data can be retrieved from an object
727    /// with encapsulated pixel data.
728    #[test]
729    fn frame_pixel_data_in_object_encapsulated() {
730        let obj = TestDataObject {
731            ts_uid: "9.9.999.9999.9.9.99",
732            rows: 512,
733            columns: 512,
734            bits_allocated: 16,
735            bits_stored: 12,
736            samples_per_pixel: 1,
737            photometric_interpretation: "MONOCHROME2",
738            number_of_frames: 3,
739            flat_pixel_data: None,
740            pixel_data_sequence: Some(PixelFragmentSequence::new(
741                // offset table: 1 frame with 2 fragments + 2 frames with 1 fragment
742                vec![0, 16 + 20, 16 + 20 + 24],
743                vec![
744                    // fragment 0 (frame 0)
745                    vec![0x33; 16],
746                    // fragment 1 (frame 0)
747                    vec![0x55; 20],
748                    // fragment 2 (frame 1)
749                    vec![0x77; 24],
750                    // fragment 3 (frame 2)
751                    vec![0x99; 36],
752                ],
753            )),
754        };
755
756        // the first two fragments will be combined
757        let frame_1: Vec<u8> = IntoIterator::into_iter([0x33; 16])
758            .chain(IntoIterator::into_iter([0x55; 20]))
759            .collect();
760        assert_eq!(obj.frame_pixel_data(0), Some(frame_1.into()));
761
762        assert_eq!(obj.frame_pixel_data(1), Some(vec![0x77; 24].into()));
763        assert_eq!(obj.frame_pixel_data(2), Some(vec![0x99; 36].into()));
764    }
765}