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 /// For pixel data with a single sample per pixel,
274 /// the output shall retain the photometric interpretation
275 /// declared in the original object
276 /// if it is one of _MONOCHROME1_, _MONOCHROME2_, or _PALETTE COLOR_.
277 /// For any other photometric interpretation,
278 /// the output shall be assumed to be in _MONOCHROME2_.
279 fn decode_frame(
280 &self,
281 src: &dyn PixelDataObject,
282 frame: u32,
283 dst: &mut Vec<u8>,
284 ) -> DecodeResult<()>;
285}
286
287/// Trait object responsible for encoding
288/// pixel data based on a certain transfer syntax.
289///
290/// A transfer syntax with support for creating compressed pixel data
291/// would implement these methods.
292pub trait PixelDataWriter {
293 /// Encode a DICOM object's image into the format supported by this adapter,
294 /// writing a byte stream of pixel data fragment values
295 /// to the given vector `dst`
296 /// and the offsets to each decoded frame into `offset_table`.
297 ///
298 /// New data is appended to `dst` and `offset_table`,
299 /// which are not cleared before writing.
300 ///
301 /// All implementations are required to support
302 /// writing the object's pixel data when it is in a _native encoding_.
303 /// If the given pixel data object is not in a native encoding,
304 /// and this writer does not support transcoding
305 /// from that encoding to the target transfer syntax,
306 /// a `NotNative` error is returned instead.
307 ///
308 /// When the operation is successful,
309 /// a listing of attribute changes is returned,
310 /// comprising the sequence of operations that the DICOM object
311 /// should consider upon assuming the new encoding.
312 fn encode(
313 &self,
314 src: &dyn PixelDataObject,
315 options: EncodeOptions,
316 dst: &mut Vec<Vec<u8>>,
317 offset_table: &mut Vec<u32>,
318 ) -> EncodeResult<Vec<AttributeOp>> {
319 let frames = src.number_of_frames().unwrap_or(1);
320 let mut out = Vec::new();
321 for frame in 0..frames {
322 let mut frame_data = Vec::new();
323 out = self.encode_frame(src, frame, options.clone(), &mut frame_data)?;
324 offset_table.push(frame_data.len() as u32 + 8 * (frame + 1));
325 dst.push(frame_data);
326 }
327 Ok(out)
328 }
329
330 /// Encode a single frame of a DICOM object's image
331 /// into the format supported by this adapter,
332 /// by writing a byte stream of pixel data values
333 /// into the given destination.
334 /// The bytes written comprise a single pixel data fragment
335 /// in its entirety.
336 ///
337 /// New data is appended to `dst`,
338 /// keeping all bytes previously present before writing.
339 ///
340 /// All implementations are required to support
341 /// writing the object's pixel data when it is in a _native encoding_.
342 /// If the given pixel data object is not in a native encoding,
343 /// and this writer does not support transcoding
344 /// from that encoding to the target transfer syntax,
345 /// a `NotNative` error is returned instead.
346 ///
347 /// When the operation is successful,
348 /// a listing of attribute changes is returned,
349 /// comprising the sequence of operations that the DICOM object
350 /// should consider upon assuming the new encoding.
351 fn encode_frame(
352 &self,
353 src: &dyn PixelDataObject,
354 frame: u32,
355 options: EncodeOptions,
356 dst: &mut Vec<u8>,
357 ) -> EncodeResult<Vec<AttributeOp>>;
358}
359
360/// Alias type for a dynamically dispatched pixel data reader.
361pub type DynPixelDataReader = Box<dyn PixelDataReader + Send + Sync + 'static>;
362
363/// Alias type for a dynamically dispatched pixel data writer.
364pub type DynPixelDataWriter = Box<dyn PixelDataWriter + Send + Sync + 'static>;
365
366/// An immaterial type representing an adapter which is never provided.
367///
368/// This type may be used as the type parameters `R` and `W`
369/// of [`TransferSyntax`](crate::transfer_syntax::TransferSyntax)
370/// when representing a transfer syntax which
371/// either does not support reading and writing imaging data,
372/// or when such support is not needed in the first place.
373#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
374pub enum NeverPixelAdapter {}
375
376impl PixelDataReader for NeverPixelAdapter {
377 fn decode(&self, _src: &dyn PixelDataObject, _dst: &mut Vec<u8>) -> DecodeResult<()> {
378 unreachable!()
379 }
380
381 fn decode_frame(
382 &self,
383 _src: &dyn PixelDataObject,
384 _frame: u32,
385 _dst: &mut Vec<u8>,
386 ) -> DecodeResult<()> {
387 unreachable!()
388 }
389}
390
391impl PixelDataWriter for NeverPixelAdapter {
392 fn encode(
393 &self,
394 _src: &dyn PixelDataObject,
395 _options: EncodeOptions,
396 _dst: &mut Vec<Vec<u8>>,
397 _offset_table: &mut Vec<u32>,
398 ) -> EncodeResult<Vec<AttributeOp>> {
399 unreachable!()
400 }
401
402 fn encode_frame(
403 &self,
404 _src: &dyn PixelDataObject,
405 _frame: u32,
406 _options: EncodeOptions,
407 _dst: &mut Vec<u8>,
408 ) -> EncodeResult<Vec<AttributeOp>> {
409 unreachable!()
410 }
411}
412
413impl PixelDataReader for crate::transfer_syntax::NeverAdapter {
414 fn decode(&self, _src: &dyn PixelDataObject, _dst: &mut Vec<u8>) -> DecodeResult<()> {
415 unreachable!()
416 }
417
418 fn decode_frame(
419 &self,
420 _src: &dyn PixelDataObject,
421 _frame: u32,
422 _dst: &mut Vec<u8>,
423 ) -> DecodeResult<()> {
424 unreachable!()
425 }
426}
427
428impl PixelDataWriter for crate::transfer_syntax::NeverAdapter {
429 fn encode(
430 &self,
431 _src: &dyn PixelDataObject,
432 _options: EncodeOptions,
433 _dst: &mut Vec<Vec<u8>>,
434 _offset_table: &mut Vec<u32>,
435 ) -> EncodeResult<Vec<AttributeOp>> {
436 unreachable!()
437 }
438
439 fn encode_frame(
440 &self,
441 _src: &dyn PixelDataObject,
442 _frame: u32,
443 _options: EncodeOptions,
444 _dst: &mut Vec<u8>,
445 ) -> EncodeResult<Vec<AttributeOp>> {
446 unreachable!()
447 }
448}