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}