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}