dicom_parser/dataset/
write.rs

1//! Module for the data set writer
2//!
3//! This module contains a mid-level abstraction for printing DICOM data sets
4//! sequentially.
5//! The [`DataSetWriter`] receives data tokens to be encoded and written
6//! to a writer.
7//! In this process, the writer will also adapt values
8//! to the necessary DICOM encoding rules.
9use crate::dataset::{DataToken, SeqTokenType};
10use crate::stateful::encode::StatefulEncoder;
11use dicom_core::header::Header;
12use dicom_core::{DataElementHeader, Length, Tag, VR};
13use dicom_encoding::encode::EncodeTo;
14use dicom_encoding::text::SpecificCharacterSet;
15use dicom_encoding::transfer_syntax::DynEncoder;
16use dicom_encoding::TransferSyntax;
17use snafu::{Backtrace, OptionExt, ResultExt, Snafu};
18use std::io::Write;
19
20#[derive(Debug, Snafu)]
21#[non_exhaustive]
22pub enum Error {
23    /// Unsupported transfer syntax for encoding
24    #[snafu(display("Unsupported transfer syntax {} ({})", ts_uid, ts_alias))]
25    UnsupportedTransferSyntax {
26        ts_uid: &'static str,
27        ts_alias: &'static str,
28        backtrace: Backtrace,
29    },
30    /// Character set known, but not supported
31    #[snafu(display("Unsupported character set {:?}", charset))]
32    UnsupportedCharacterSet {
33        charset: SpecificCharacterSet,
34        backtrace: Backtrace,
35    },
36    /// An element value token appeared without an introducing element header
37    #[snafu(display("Unexpected token {:?} without element header", token))]
38    UnexpectedToken {
39        token: DataToken,
40        backtrace: Backtrace,
41    },
42    #[snafu(display("Could not write element header tagged {}", tag))]
43    WriteHeader {
44        tag: Tag,
45        #[snafu(backtrace)]
46        source: crate::stateful::encode::Error,
47    },
48    #[snafu(display("Could not write item header"))]
49    WriteItemHeader {
50        #[snafu(backtrace)]
51        source: crate::stateful::encode::Error,
52    },
53
54    #[snafu(display("Could not write sequence delimiter"))]
55    WriteSequenceDelimiter {
56        #[snafu(backtrace)]
57        source: crate::stateful::encode::Error,
58    },
59
60    #[snafu(display("Could not write item delimiter"))]
61    WriteItemDelimiter {
62        #[snafu(backtrace)]
63        source: crate::stateful::encode::Error,
64    },
65
66    #[snafu(display("Could not write element value"))]
67    WriteValue {
68        #[snafu(backtrace)]
69        source: crate::stateful::encode::Error,
70    },
71    #[snafu(display("Could not flush buffer"))]
72    FlushBuffer {
73        source: std::io::Error,
74        backtrace: Backtrace,
75    },
76}
77
78pub type Result<T> = std::result::Result<T, Error>;
79
80/// A writer-specific token representing a sequence or item start.
81#[derive(Debug)]
82struct SeqToken {
83    /// Whether it is the start of a sequence or the start of an item.
84    typ: SeqTokenType,
85    /// The length of the value, as indicated by the starting element,
86    /// can be unknown.
87    len: Length,
88}
89
90/// A strategy for writing data set sequences and items
91/// when the writer encounters a sequence or item with explicit (defined) length.
92#[derive(Debug, Default, Copy, Clone, Eq, Hash, PartialEq)]
93#[non_exhaustive]
94pub enum ExplicitLengthSqItemStrategy {
95    /// All explicit length items and sequences
96    /// are converted to [`Length::UNDEFINED`].
97    ///
98    /// This means that even if you create or read a data set with explicit length
99    /// items / sequences, the resulting output of the writer will have undefined
100    /// lengths for all items and sequences,
101    /// expect for encapsulated pixel data fragments.
102    ///
103    /// This is, as of yet, the safest way to handle
104    /// explicit length items and sequences, and thus the default behavior.
105    #[default]
106    SetUndefined,
107    /// Explicit length items and sequences are written without any change,
108    /// left as they were encountered in the data set.
109    ///
110    /// Item and sequence lengths in the data set will not be recalculated!
111    /// As a consequence, if the content of a sequence or item with explicit length
112    /// is manipulated after it was created or read from a source
113    /// (thus possibly changing its real size),
114    /// this strategy will not update the length of that sequence or item,
115    /// producing invalid output.
116    NoChange,
117    // TODO(#692) Explicit length items and sequences could as well be recalculated, as is the behavior
118    // of some DICOM libraries. Because recalculation is expensive and leaving sequences and items
119    // with length undefined is DICOM compliant, this strategy is not implemented yet.
120    // Recalculate,
121}
122
123/// The set of options for the data set writer.
124#[derive(Debug, Default, Copy, Clone, Eq, Hash, PartialEq)]
125#[non_exhaustive]
126pub struct DataSetWriterOptions {
127    /// What to do with sequences and items with explicit lengths.
128    pub explicit_length_sq_item_strategy: ExplicitLengthSqItemStrategy,
129}
130
131impl DataSetWriterOptions {
132    /// Replace the write strategy for explicit length sequences and items of the options.
133    pub fn explicit_length_sq_item_strategy(
134        mut self,
135        exp_length: ExplicitLengthSqItemStrategy,
136    ) -> Self {
137        self.explicit_length_sq_item_strategy = exp_length;
138        self
139    }
140}
141
142/// A stateful device for printing a DICOM data set in sequential order.
143/// This is analogous to the `DatasetReader` type for converting data
144/// set tokens to bytes.
145#[derive(Debug)]
146pub struct DataSetWriter<W, E, T = SpecificCharacterSet> {
147    printer: StatefulEncoder<W, E, T>,
148    seq_tokens: Vec<SeqToken>,
149    last_de: Option<DataElementHeader>,
150    options: DataSetWriterOptions,
151}
152
153impl<'w, W: 'w> DataSetWriter<W, DynEncoder<'w, W>>
154where
155    W: Write,
156{
157    /// Create a new data set writer
158    /// with the given transfer syntax specifier.
159    ///
160    /// Uses the default [DataSetWriterOptions] for the writer.
161    pub fn with_ts(to: W, ts: &TransferSyntax) -> Result<Self> {
162        Self::with_ts_options(to, ts, DataSetWriterOptions::default())
163    }
164
165    /// Create a new data set writer
166    /// with the given transfer syntax specifier
167    /// and the specific character.
168    ///
169    /// Uses the default [DataSetWriterOptions] for the writer.
170    pub fn with_ts_cs(to: W, ts: &TransferSyntax, charset: SpecificCharacterSet) -> Result<Self> {
171        Self::with_ts_cs_options(to, ts, charset, DataSetWriterOptions::default())
172    }
173
174    /// Create a new data set writer
175    /// with the given transfer syntax specifier
176    /// and options.
177    pub fn with_ts_options(
178        to: W,
179        ts: &TransferSyntax,
180        options: DataSetWriterOptions,
181    ) -> Result<Self> {
182        let encoder = ts.encoder_for().context(UnsupportedTransferSyntaxSnafu {
183            ts_uid: ts.uid(),
184            ts_alias: ts.name(),
185        })?;
186        Ok(DataSetWriter::new_with_codec_options(
187            to,
188            encoder,
189            SpecificCharacterSet::default(),
190            options,
191        ))
192    }
193
194    /// Create a new data set writer
195    /// with the given transfer syntax specifier,
196    /// specific character set and options.
197    pub fn with_ts_cs_options(
198        to: W,
199        ts: &TransferSyntax,
200        charset: SpecificCharacterSet,
201        options: DataSetWriterOptions,
202    ) -> Result<Self> {
203        let encoder = ts.encoder_for().context(UnsupportedTransferSyntaxSnafu {
204            ts_uid: ts.uid(),
205            ts_alias: ts.name(),
206        })?;
207        Ok(DataSetWriter::new_with_codec_options(
208            to, encoder, charset, options,
209        ))
210    }
211}
212
213impl<W, E> DataSetWriter<W, E> {
214    /// Create a new dataset writer with the given encoder,
215    /// which prints to the given writer.
216    #[inline]
217    pub fn new(to: W, encoder: E) -> Self {
218        DataSetWriter::new_with_options(to, encoder, DataSetWriterOptions::default())
219    }
220
221    /// Create a new dataset writer with the given encoder,
222    /// which prints to the given writer.
223    #[inline]
224    pub fn new_with_options(to: W, encoder: E, options: DataSetWriterOptions) -> Self {
225        DataSetWriter {
226            printer: StatefulEncoder::new(to, encoder, SpecificCharacterSet::default()),
227            seq_tokens: Vec::new(),
228            last_de: None,
229            options,
230        }
231    }
232}
233
234impl<W, E, T> DataSetWriter<W, E, T> {
235    /// Create a new dataset writer with the given encoder and text codec,
236    /// which prints to the given writer.
237    #[inline]
238    pub fn new_with_codec(to: W, encoder: E, text: T) -> Self {
239        DataSetWriter::new_with_codec_options(to, encoder, text, DataSetWriterOptions::default())
240    }
241
242    /// Create a new dataset writer with the given encoder and text codec,
243    /// which prints to the given writer.
244    #[inline]
245    pub fn new_with_codec_options(
246        to: W,
247        encoder: E,
248        text: T,
249        options: DataSetWriterOptions,
250    ) -> Self {
251        DataSetWriter {
252            printer: StatefulEncoder::new(to, encoder, text),
253            seq_tokens: Vec::new(),
254            last_de: None,
255            options,
256        }
257    }
258}
259
260impl<W, E> DataSetWriter<W, E>
261where
262    W: Write,
263    E: EncodeTo<W>,
264{
265    /// Feed the given sequence of tokens which are part of the same data set.
266    #[inline]
267    pub fn write_sequence<I>(&mut self, tokens: I) -> Result<()>
268    where
269        I: IntoIterator<Item = DataToken>,
270    {
271        for token in tokens {
272            self.write(token)?;
273        }
274
275        Ok(())
276    }
277
278    /// Feed the given data set token for writing the data set.
279    pub fn write(&mut self, token: DataToken) -> Result<()> {
280        match token {
281            DataToken::SequenceStart { tag, len, .. } => {
282                match self.options.explicit_length_sq_item_strategy {
283                    ExplicitLengthSqItemStrategy::SetUndefined => {
284                        self.seq_tokens.push(SeqToken {
285                            typ: SeqTokenType::Sequence,
286                            len: Length::UNDEFINED,
287                        });
288                        self.write_impl(&DataToken::SequenceStart {
289                            tag,
290                            len: Length::UNDEFINED,
291                        })?;
292                    }
293                    ExplicitLengthSqItemStrategy::NoChange => {
294                        self.seq_tokens.push(SeqToken {
295                            typ: SeqTokenType::Sequence,
296                            len,
297                        });
298                        self.write_impl(&token)?;
299                    }
300                }
301                Ok(())
302            }
303            DataToken::ItemStart { len } => {
304                match self.options.explicit_length_sq_item_strategy {
305                    ExplicitLengthSqItemStrategy::SetUndefined => {
306                        // only set undefined length in dataset sequence items
307                        // (not pixel data fragments, those always have an explicit length)
308                        let len = if self
309                            .last_de
310                            .map(|h| h.is_encapsulated_pixeldata())
311                            .unwrap_or(false)
312                        {
313                            len
314                        } else {
315                            Length::UNDEFINED
316                        };
317                        self.seq_tokens.push(SeqToken {
318                            typ: SeqTokenType::Item,
319                            len,
320                        });
321                        self.write_impl(&DataToken::ItemStart { len })?;
322                    }
323                    ExplicitLengthSqItemStrategy::NoChange => {
324                        self.seq_tokens.push(SeqToken {
325                            typ: SeqTokenType::Item,
326                            len,
327                        });
328                        self.write_impl(&token)?;
329                    }
330                }
331                Ok(())
332            }
333            DataToken::ItemEnd => {
334                // only write if it's an unknown length item
335                if let Some(seq_start) = self.seq_tokens.pop() {
336                    if seq_start.typ == SeqTokenType::Item && seq_start.len.is_undefined() {
337                        self.write_impl(&token)?;
338                    }
339                }
340                Ok(())
341            }
342            DataToken::SequenceEnd => {
343                // only write if it's an unknown length sequence
344                if let Some(seq_start) = self.seq_tokens.pop() {
345                    if seq_start.typ == SeqTokenType::Sequence && seq_start.len.is_undefined() {
346                        self.write_impl(&token)?;
347                    }
348                }
349                Ok(())
350            }
351            DataToken::ElementHeader(de) => {
352                // save the header for later
353                self.last_de = Some(de);
354
355                // postpone writing the header until the value token is given
356                Ok(())
357            }
358            token @ DataToken::PixelSequenceStart => {
359                // save the header so we know that
360                // we're in encapsulated pixel data
361                self.last_de = Some(DataElementHeader {
362                    tag: Tag(0x7fe0, 0x0010),
363                    vr: VR::OB,
364                    len: Length::UNDEFINED,
365                });
366
367                self.seq_tokens.push(SeqToken {
368                    typ: SeqTokenType::Sequence,
369                    len: Length::UNDEFINED,
370                });
371                self.write_impl(&token)
372            }
373            token @ DataToken::ItemValue(_)
374            | token @ DataToken::PrimitiveValue(_)
375            | token @ DataToken::OffsetTable(_) => self.write_impl(&token),
376        }
377    }
378
379    fn write_impl(&mut self, token: &DataToken) -> Result<()> {
380        match token {
381            DataToken::ElementHeader(header) => {
382                self.printer
383                    .encode_element_header(*header)
384                    .context(WriteHeaderSnafu { tag: header.tag })?;
385            }
386            DataToken::SequenceStart { tag, len } => {
387                self.printer
388                    .encode_element_header(DataElementHeader::new(*tag, VR::SQ, *len))
389                    .context(WriteHeaderSnafu { tag: *tag })?;
390            }
391            DataToken::PixelSequenceStart => {
392                let tag = Tag(0x7fe0, 0x0010);
393                self.printer
394                    .encode_element_header(DataElementHeader::new(tag, VR::OB, Length::UNDEFINED))
395                    .context(WriteHeaderSnafu { tag })?;
396            }
397            DataToken::SequenceEnd => {
398                self.printer
399                    .encode_sequence_delimiter()
400                    .context(WriteSequenceDelimiterSnafu)?;
401            }
402            DataToken::ItemStart { len } => {
403                self.printer
404                    .encode_item_header(len.0)
405                    .context(WriteItemHeaderSnafu)?;
406            }
407            DataToken::ItemEnd => {
408                self.printer
409                    .encode_item_delimiter()
410                    .context(WriteItemDelimiterSnafu)?;
411            }
412            DataToken::PrimitiveValue(ref value) => {
413                let last_de = self.last_de.take().with_context(|| UnexpectedTokenSnafu {
414                    token: token.clone(),
415                })?;
416
417                self.printer
418                    .encode_primitive_element(&last_de, value)
419                    .context(WriteValueSnafu)?;
420                self.last_de = None;
421            }
422            DataToken::OffsetTable(table) => {
423                self.printer
424                    .encode_offset_table(table)
425                    .context(WriteValueSnafu)?;
426            }
427            DataToken::ItemValue(data) => {
428                self.printer.write_bytes(data).context(WriteValueSnafu)?;
429            }
430        }
431        Ok(())
432    }
433
434    /// Flush the inner writer
435    pub fn flush(&mut self) -> Result<()> {
436        self.printer.flush().context(FlushBufferSnafu)
437    }
438}
439
440#[cfg(test)]
441mod tests {
442    use super::super::DataToken;
443    use super::{DataSetWriter, DataSetWriterOptions, ExplicitLengthSqItemStrategy};
444    use dicom_core::{
445        header::{DataElementHeader, Length},
446        value::PrimitiveValue,
447        Tag, VR,
448    };
449    use dicom_encoding::encode::{explicit_le::ExplicitVRLittleEndianEncoder, EncoderFor};
450
451    fn validate_dataset_writer<I>(
452        tokens: I,
453        ground_truth: &[u8],
454        writer_options: DataSetWriterOptions,
455    ) where
456        I: IntoIterator<Item = DataToken>,
457    {
458        let mut raw_out: Vec<u8> = vec![];
459        let encoder = EncoderFor::new(ExplicitVRLittleEndianEncoder::default());
460        let mut dset_writer =
461            DataSetWriter::new_with_options(&mut raw_out, encoder, writer_options);
462
463        dset_writer.write_sequence(tokens).unwrap();
464
465        assert_eq!(raw_out, ground_truth);
466    }
467
468    #[test]
469    fn write_sequence_explicit() {
470        let tokens = vec![
471            DataToken::SequenceStart {
472                tag: Tag(0x0018, 0x6011),
473                len: Length(46),
474            },
475            DataToken::ItemStart { len: Length(20) },
476            DataToken::ElementHeader(DataElementHeader {
477                tag: Tag(0x0018, 0x6012),
478                vr: VR::US,
479                len: Length(2),
480            }),
481            DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
482            DataToken::ElementHeader(DataElementHeader {
483                tag: Tag(0x0018, 0x6014),
484                vr: VR::US,
485                len: Length(2),
486            }),
487            DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
488            DataToken::ItemEnd,
489            DataToken::ItemStart { len: Length(10) },
490            DataToken::ElementHeader(DataElementHeader {
491                tag: Tag(0x0018, 0x6012),
492                vr: VR::US,
493                len: Length(2),
494            }),
495            DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
496            DataToken::ItemEnd,
497            DataToken::SequenceEnd,
498            DataToken::ElementHeader(DataElementHeader {
499                tag: Tag(0x0020, 0x4000),
500                vr: VR::LT,
501                len: Length(4),
502            }),
503            DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
504        ];
505
506        #[rustfmt::skip]
507        static GROUND_TRUTH_LENGTH_UNDEFINED: &[u8] = &[
508            0x18, 0x00, 0x11, 0x60, // sequence tag: (0018,6011) SequenceOfUltrasoundRegions
509            b'S', b'Q', // VR 
510            0x00, 0x00, // reserved
511            0xff, 0xff, 0xff, 0xff, // seq length: UNDEFINED
512            // -- 12 --
513            0xfe, 0xff, 0x00, 0xe0, // item start tag
514            0xff, 0xff, 0xff, 0xff, // item length: UNDEFINED
515            // -- 20 --
516            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x01, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 1
517            // -- 30 --
518            0x18, 0x00, 0x14, 0x60, b'U', b'S', 0x02, 0x00, 0x02, 0x00, // (0018, 6012) RegionDataType, len = 2, value = 2
519            // -- 40 --
520            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
521            0xfe, 0xff, 0x00, 0xe0, // item start tag
522            0xff, 0xff, 0xff, 0xff, // item length: UNDEFINED
523            // -- 48 --
524            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x04, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 4
525            // -- 58 --
526            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
527            0xfe, 0xff, 0xdd, 0xe0, 0x00, 0x00, 0x00, 0x00, // sequence end
528            0x20, 0x00, 0x00, 0x40, b'L', b'T', 0x04, 0x00, // (0020,4000) ImageComments, len = 4  
529            b'T', b'E', b'S', b'T', // value = "TEST"
530        ];
531
532        #[rustfmt::skip]
533        static GROUND_TRUTH_NO_CHANGE: &[u8] = &[
534            0x18, 0x00, 0x11, 0x60, // sequence tag: (0018,6011) SequenceOfUltrasoundRegions
535            b'S', b'Q', // VR 
536            0x00, 0x00, // reserved
537            0x2e, 0x00, 0x00, 0x00, // length: 28 + 18 = 46 (#= 2)
538            // -- 12 --
539            0xfe, 0xff, 0x00, 0xe0, // item start tag
540            0x14, 0x00, 0x00, 0x00, // item length: 20 (#= 2)
541            // -- 20 --
542            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x01, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 1
543            // -- 30 --
544            0x18, 0x00, 0x14, 0x60, b'U', b'S', 0x02, 0x00, 0x02, 0x00, // (0018, 6012) RegionDataType, len = 2, value = 2
545            // -- 40 --
546            0xfe, 0xff, 0x00, 0xe0, // item start tag
547            0x0a, 0x00, 0x00, 0x00, // item length: 10 (#= 1)
548            // -- 48 --
549            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x04, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 4
550            // -- 58 --
551            0x20, 0x00, 0x00, 0x40, b'L', b'T', 0x04, 0x00, // (0020,4000) ImageComments, len = 4  
552            b'T', b'E', b'S', b'T', // value = "TEST"
553        ];
554
555        let no_change = DataSetWriterOptions {
556            explicit_length_sq_item_strategy: ExplicitLengthSqItemStrategy::NoChange,
557        };
558        validate_dataset_writer(tokens.clone(), GROUND_TRUTH_NO_CHANGE, no_change);
559        validate_dataset_writer(
560            tokens,
561            GROUND_TRUTH_LENGTH_UNDEFINED,
562            DataSetWriterOptions::default(),
563        );
564    }
565
566    #[test]
567    fn write_element_overrides_len() {
568        let tokens = vec![
569            DataToken::ElementHeader(DataElementHeader {
570                // Specific Character Set (0008,0005)
571                tag: Tag(0x0008, 0x0005),
572                vr: VR::CS,
573                len: Length(10),
574            }),
575            DataToken::PrimitiveValue(PrimitiveValue::from("ISO_IR 100")),
576            DataToken::ElementHeader(DataElementHeader {
577                // Referring Physician's Name (0008,0090)
578                tag: Tag(0x0008, 0x0090),
579                vr: VR::PN,
580                // deliberately incorrect length
581                len: Length("Simões^João".len() as u32),
582            }),
583            DataToken::PrimitiveValue(PrimitiveValue::from("Simões^João")),
584        ];
585
586        #[rustfmt::skip]
587        static GROUND_TRUTH: &[u8] = &[
588            // Specific Character Set (0008,0005)
589            0x08, 0x00, 0x05, 0x00, //
590            b'C', b'S', // VR
591            0x0a, 0x00, // length: 10
592            b'I', b'S', b'O', b'_', b'I', b'R', b' ', b'1', b'0', b'0', // value = "ISO_IR 100"
593            // Referring Physician's Name (0008,0090)
594            0x08, 0x00, 0x90, 0x00, //
595            b'P', b'N', // VR
596            0x0c, 0x00, // length: 12
597            // value = "Simões^João "
598            b'S', b'i', b'm', 0xF5, b'e', b's', b'^', b'J', b'o', 0xE3, b'o', b' '
599        ];
600        validate_dataset_writer(tokens, GROUND_TRUTH, DataSetWriterOptions::default());
601    }
602
603    #[test]
604    fn write_sequence_implicit() {
605        let tokens = vec![
606            DataToken::SequenceStart {
607                tag: Tag(0x0018, 0x6011),
608                len: Length::UNDEFINED,
609            },
610            DataToken::ItemStart {
611                len: Length::UNDEFINED,
612            },
613            DataToken::ElementHeader(DataElementHeader {
614                tag: Tag(0x0018, 0x6012),
615                vr: VR::US,
616                len: Length(2),
617            }),
618            DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
619            DataToken::ElementHeader(DataElementHeader {
620                tag: Tag(0x0018, 0x6014),
621                vr: VR::US,
622                len: Length(2),
623            }),
624            DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
625            DataToken::ItemEnd,
626            DataToken::ItemStart {
627                len: Length::UNDEFINED,
628            },
629            DataToken::ElementHeader(DataElementHeader {
630                tag: Tag(0x0018, 0x6012),
631                vr: VR::US,
632                len: Length(2),
633            }),
634            DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
635            DataToken::ItemEnd,
636            DataToken::SequenceEnd,
637            DataToken::ElementHeader(DataElementHeader {
638                tag: Tag(0x0020, 0x4000),
639                vr: VR::LT,
640                len: Length(4),
641            }),
642            DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
643        ];
644
645        #[rustfmt::skip]
646        static GROUND_TRUTH: &[u8] = &[
647            0x18, 0x00, 0x11, 0x60, // sequence tag: (0018,6011) SequenceOfUltrasoundRegions
648            b'S', b'Q', // VR 
649            0x00, 0x00, // reserved
650            0xff, 0xff, 0xff, 0xff, // length: undefined
651            // -- 12 --
652            0xfe, 0xff, 0x00, 0xe0, // item start tag
653            0xff, 0xff, 0xff, 0xff, // item length: undefined
654            // -- 20 --
655            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x01, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 1
656            // -- 30 --
657            0x18, 0x00, 0x14, 0x60, b'U', b'S', 0x02, 0x00, 0x02, 0x00, // (0018, 6012) RegionDataType, len = 2, value = 2
658            // -- 40 --
659            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
660            // -- 48 --
661            0xfe, 0xff, 0x00, 0xe0, // item start tag
662            0xff, 0xff, 0xff, 0xff, // item length: undefined
663            // -- 56 --
664            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x04, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 4
665            // -- 66 --
666            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
667            // -- 74 --
668            0xfe, 0xff, 0xdd, 0xe0, 0x00, 0x00, 0x00, 0x00, // sequence end
669            // -- 82 --
670            0x20, 0x00, 0x00, 0x40, b'L', b'T', 0x04, 0x00, // (0020,4000) ImageComments, len = 4  
671            b'T', b'E', b'S', b'T', // value = "TEST"
672        ];
673
674        let no_change = DataSetWriterOptions {
675            explicit_length_sq_item_strategy: ExplicitLengthSqItemStrategy::NoChange,
676        };
677        validate_dataset_writer(tokens.clone(), GROUND_TRUTH, no_change);
678        validate_dataset_writer(tokens, GROUND_TRUTH, DataSetWriterOptions::default());
679    }
680
681    #[test]
682    fn write_sequence_explicit_with_implicit_item_len() {
683        let tokens = vec![
684            DataToken::SequenceStart {
685                tag: Tag(0x0018, 0x6011),
686                len: Length(60),
687            },
688            DataToken::ItemStart {
689                len: Length::UNDEFINED,
690            },
691            DataToken::ElementHeader(DataElementHeader {
692                tag: Tag(0x0018, 0x6012),
693                vr: VR::US,
694                len: Length(2),
695            }),
696            DataToken::PrimitiveValue(PrimitiveValue::U16([1].as_ref().into())),
697            DataToken::ElementHeader(DataElementHeader {
698                tag: Tag(0x0018, 0x6014),
699                vr: VR::US,
700                len: Length(2),
701            }),
702            DataToken::PrimitiveValue(PrimitiveValue::U16([2].as_ref().into())),
703            DataToken::ItemEnd,
704            DataToken::ItemStart {
705                len: Length::UNDEFINED,
706            },
707            DataToken::ElementHeader(DataElementHeader {
708                tag: Tag(0x0018, 0x6012),
709                vr: VR::US,
710                len: Length(2),
711            }),
712            DataToken::PrimitiveValue(PrimitiveValue::U16([4].as_ref().into())),
713            DataToken::ItemEnd,
714            DataToken::SequenceEnd,
715            DataToken::ElementHeader(DataElementHeader {
716                tag: Tag(0x0020, 0x4000),
717                vr: VR::LT,
718                len: Length(4),
719            }),
720            DataToken::PrimitiveValue(PrimitiveValue::Str("TEST".into())),
721        ];
722
723        #[rustfmt::skip]
724        static GROUND_TRUTH_LENGTH_UNDEFINED: &[u8] = &[
725            0x18, 0x00, 0x11, 0x60, // sequence tag: (0018,6011) SequenceOfUltrasoundRegions
726            b'S', b'Q', // VR 
727            0x00, 0x00, // reserved
728            0xff, 0xff, 0xff, 0xff, // length: UNDEFINED
729            0xfe, 0xff, 0x00, 0xe0, // item start tag
730            0xff, 0xff, 0xff, 0xff, // item length: undefined
731            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x01, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 1
732            0x18, 0x00, 0x14, 0x60, b'U', b'S', 0x02, 0x00, 0x02, 0x00, // (0018, 6012) RegionDataType, len = 2, value = 2
733            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
734            0xfe, 0xff, 0x00, 0xe0, // item start tag
735            0xff, 0xff, 0xff, 0xff, // item length: undefined
736            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x04, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 4
737            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
738            0xfe, 0xff, 0xdd, 0xe0, 0x00, 0x00, 0x00, 0x00, // sequence end
739            0x20, 0x00, 0x00, 0x40, b'L', b'T', 0x04, 0x00, // (0020,4000) ImageComments, len = 4  
740            b'T', b'E', b'S', b'T', // value = "TEST"
741        ];
742
743        #[rustfmt::skip]
744        static GROUND_TRUTH_NO_CHANGE: &[u8] = &[
745            0x18, 0x00, 0x11, 0x60, // sequence tag: (0018,6011) SequenceOfUltrasoundRegions
746            b'S', b'Q', // VR 
747            0x00, 0x00, // reserved
748            0x3c, 0x00, 0x00, 0x00, // length: 60
749            // -- 12 --
750            0xfe, 0xff, 0x00, 0xe0, // item start tag
751            0xff, 0xff, 0xff, 0xff, // item length: undefined
752            // -- 20 --
753            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x01, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 1
754            // -- 30 --
755            0x18, 0x00, 0x14, 0x60, b'U', b'S', 0x02, 0x00, 0x02, 0x00, // (0018, 6012) RegionDataType, len = 2, value = 2
756            // -- 40 --
757            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
758            // -- 48 --
759            0xfe, 0xff, 0x00, 0xe0, // item start tag
760            0xff, 0xff, 0xff, 0xff, // item length: undefined
761            // -- 56 --
762            0x18, 0x00, 0x12, 0x60, b'U', b'S', 0x02, 0x00, 0x04, 0x00, // (0018, 6012) RegionSpatialformat, len = 2, value = 4
763            // -- 66 --
764            0xfe, 0xff, 0x0d, 0xe0, 0x00, 0x00, 0x00, 0x00, // item end
765            // -- 74 --
766            0x20, 0x00, 0x00, 0x40, b'L', b'T', 0x04, 0x00, // (0020,4000) ImageComments, len = 4  
767            b'T', b'E', b'S', b'T', // value = "TEST"
768        ];
769        let no_change = DataSetWriterOptions {
770            explicit_length_sq_item_strategy: ExplicitLengthSqItemStrategy::NoChange,
771        };
772
773        validate_dataset_writer(tokens.clone(), GROUND_TRUTH_NO_CHANGE, no_change);
774        validate_dataset_writer(
775            tokens,
776            GROUND_TRUTH_LENGTH_UNDEFINED,
777            DataSetWriterOptions::default(),
778        );
779    }
780
781    #[test]
782    fn write_encapsulated_pixeldata() {
783        let tokens = vec![
784            DataToken::PixelSequenceStart,
785            DataToken::ItemStart { len: Length(0) },
786            DataToken::ItemEnd,
787            DataToken::ItemStart { len: Length(32) },
788            DataToken::ItemValue(vec![0x99; 32]),
789            DataToken::ItemEnd,
790            DataToken::SequenceEnd,
791            DataToken::ElementHeader(DataElementHeader::new(
792                Tag(0xfffc, 0xfffc),
793                VR::OB,
794                Length(8),
795            )),
796            DataToken::PrimitiveValue(PrimitiveValue::U8([0x00; 8].as_ref().into())),
797        ];
798
799        // the encoded data should be equivalent because
800        // pixel data fragment items always have an explicit length (PS3.5 A.4)
801
802        #[rustfmt::skip]
803        static GROUND_TRUTH: &[u8] = &[
804            0xe0, 0x7f, 0x10, 0x00, // (7FE0, 0010) PixelData
805            b'O', b'B', // VR 
806            0x00, 0x00, // reserved
807            0xff, 0xff, 0xff, 0xff, // length: undefined
808            // -- 12 -- Basic offset table
809            0xfe, 0xff, 0x00, 0xe0, // item start tag
810            0x00, 0x00, 0x00, 0x00, // item length: 0
811            // -- 20 -- First fragment of pixel data
812            0xfe, 0xff, 0x00, 0xe0, // item start tag
813            0x20, 0x00, 0x00, 0x00, // item length: 32
814            // -- 28 -- Compressed Fragment
815            0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
816            0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
817            0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
818            0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99, 0x99,
819            // -- 60 -- End of pixel data
820            0xfe, 0xff, 0xdd, 0xe0, // sequence end tag
821            0x00, 0x00, 0x00, 0x00,
822            // -- 68 -- padding
823            0xfc, 0xff, 0xfc, 0xff, // (fffc,fffc) DataSetTrailingPadding
824            b'O', b'B', // VR
825            0x00, 0x00, // reserved
826            0x08, 0x00, 0x00, 0x00, // length: 8
827            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
828        ];
829        let no_change = DataSetWriterOptions {
830            explicit_length_sq_item_strategy: ExplicitLengthSqItemStrategy::NoChange,
831        };
832        validate_dataset_writer(tokens.clone(), GROUND_TRUTH, no_change);
833        validate_dataset_writer(tokens, GROUND_TRUTH, DataSetWriterOptions::default());
834    }
835}