dicom_core/ops.rs
1//! Module for the attribute operations API.
2//!
3//! This allows consumers to specify and implement
4//! operations on DICOM objects
5//! as part of a larger process,
6//! such as anonymization or transcoding.
7//!
8//! The most important type here is [`AttributeOp`],
9//! which indicates which attribute is affected ([`AttributeSelector`]),
10//! and the operation to apply ([`AttributeAction`]).
11//! All DICOM object types supporting this API
12//! implement the [`ApplyOp`] trait.
13//!
14//! # Example
15//!
16//! Given a DICOM object
17//! (opened using [`dicom_object`](https://docs.rs/dicom-object)),
18//! construct an [`AttributeOp`]
19//! and apply it using [`apply`](ApplyOp::apply).
20//!
21//! ```no_run
22//! # use dicom_core::Tag;
23//! use dicom_core::ops::*;
24//! # /* do not really import this
25//! use dicom_object::open_file;
26//! # */
27//!
28//! # struct DicomObj;
29//! # impl ApplyOp for DicomObj {
30//! # type Err = snafu::Whatever;
31//! # fn apply(&mut self, _: AttributeOp) -> Result<(), Self::Err> {
32//! # panic!("this is just a stub");
33//! # }
34//! # }
35//! # fn open_file(_: &str) -> Result<DicomObj, Box<dyn std::error::Error>> { Ok(DicomObj) }
36//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
37//! let mut obj = open_file("1/2/0003.dcm")?;
38//! // hide patient name
39//! obj.apply(AttributeOp::new(
40//! Tag(0x0010, 0x0010),
41//! AttributeAction::SetStr("Patient^Anonymous".into()),
42//! ))?;
43//! # Ok(())
44//! # }
45//! ```
46use std::{borrow::Cow, fmt::Write};
47
48use smallvec::{smallvec, SmallVec};
49
50use crate::{PrimitiveValue, Tag, VR};
51
52/// Descriptor for a single operation
53/// to apply over a DICOM data set.
54///
55/// This type is purely descriptive.
56/// It outlines a non-exhaustive set of possible changes around an attribute,
57/// as well as set some expectations regarding the outcome of certain actions
58/// against the attribute's previous state.
59///
60/// The operations themselves are provided
61/// alongside DICOM object or DICOM data set implementations,
62/// such as the `InMemDicomObject` from the [`dicom_object`] crate.
63///
64/// Attribute operations can only select shallow attributes,
65/// but the operation may be implemented when applied against nested data sets.
66///
67/// [`dicom_object`]: https://docs.rs/dicom_object
68#[derive(Debug, Clone, PartialEq)]
69pub struct AttributeOp {
70 /// the selector for the attribute to apply
71 pub selector: AttributeSelector,
72 /// the effective action to apply
73 pub action: AttributeAction,
74}
75
76impl AttributeOp {
77 /// Construct an attribute operation.
78 ///
79 /// This constructor function may be easier to use
80 /// than writing a public struct expression directly,
81 /// due to its automatic conversion of `selector`.
82 ///
83 /// # Example
84 ///
85 /// ```
86 /// # use dicom_core::Tag;
87 /// # use dicom_core::ops::{AttributeAction, AttributeOp};
88 /// let op = AttributeOp::new(
89 /// // ImageType
90 /// Tag(0x0008, 0x0008),
91 /// AttributeAction::SetStr("DERIVED\\SECONDARY\\DOSE_INFO".into()),
92 /// );
93 /// ```
94 pub fn new(selector: impl Into<AttributeSelector>, action: AttributeAction) -> Self {
95 AttributeOp {
96 selector: selector.into(),
97 action,
98 }
99 }
100}
101
102/// A single step of an attribute selection.
103///
104/// A selector step may either select an element directly at the root (`Tag`)
105/// or a specific item in a sequence to navigate into (`Nested`).
106///
107/// A full attribute selector can be specified
108/// by using a sequence of these steps
109/// (but should always end with the `Tag` variant,
110/// otherwise the operation would be unspecified).
111#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
112pub enum AttributeSelectorStep {
113 /// Select the element with the tag reachable at the root of this data set
114 Tag(Tag),
115 /// Select an item in a data set sequence,
116 /// as an intermediate step
117 Nested { tag: Tag, item: u32 },
118}
119
120impl From<Tag> for AttributeSelectorStep {
121 /// Creates an attribute selector step by data element tag.
122 fn from(value: Tag) -> Self {
123 AttributeSelectorStep::Tag(value)
124 }
125}
126
127impl From<(Tag, u32)> for AttributeSelectorStep {
128 /// Creates a sequence item selector step
129 /// by data element tag and item index.
130 fn from((tag, item): (Tag, u32)) -> Self {
131 AttributeSelectorStep::Nested { tag, item }
132 }
133}
134
135impl std::fmt::Display for AttributeSelectorStep {
136 /// Displays the attribute selector step:
137 /// `(GGGG,EEEE)` if `Tag`,
138 /// `(GGGG,EEEE)[i]` if `Nested`
139 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140 match self {
141 AttributeSelectorStep::Tag(tag) => std::fmt::Display::fmt(tag, f),
142 AttributeSelectorStep::Nested { tag, item } => write!(f, "{tag}[{item}]"),
143 }
144 }
145}
146
147/// An attribute selector.
148///
149/// This type defines the path to an element in a DICOM data set,
150/// even at an arbitrary depth of nested data sets.
151/// A selector may be perceived as a series of navigation steps
152/// to reach a certain data element,
153/// where all steps but the last one refer to data set sequences.
154///
155/// Attribute selectors can be created through
156/// one of the various [`From`] conversions,
157/// the dynamic constructor function [`new`],
158/// or through parsing.
159///
160/// # Syntax
161///
162/// A syntax is defined for the unambiguous conversion
163/// between a string and an `AttributeSelector` value,
164/// in both directions.
165/// Attribute selectors are defined by the syntax
166/// `( «key»([«item»])? . )* «key» `
167/// where:
168///
169/// - _`«key»`_ is either a DICOM tag in a supported textual form,
170/// or a tag keyword as accepted by the [data dictionary][dict] in use;
171/// - _`«item»`_ is an unsigned integer representing the item index,
172/// which is always surrounded by square brackets in the input;
173/// - _`[`_, _`]`_, and _`.`_ are literally their own characters
174/// as part of the input.
175///
176/// [dict]: crate::dictionary::DataDictionary
177///
178/// The first part in parentheses may appear zero or more times.
179/// The `[«item»]` part can be omitted,
180/// in which case it is assumed that the first item is selected.
181/// Whitespace is not admitted in any position.
182/// Displaying a selector through the [`Display`](std::fmt::Display) trait
183/// produces a string that is compliant with this syntax.
184///
185/// ### Examples of attribute selectors in text:
186///
187/// - `(0002,00010)`:
188/// selects _Transfer Syntax UID_
189/// - `00101010`:
190/// selects _Patient Age_
191/// - `0040A168[0].CodeValue`:
192/// selects _Code Value_ within the first item of _Concept Code Sequence_
193/// - `0040,A730[1].ContentSequence`:
194/// selects _Content Sequence_ in second item of _Content Sequence_
195/// - `SequenceOfUltrasoundRegions.RegionSpatialFormat`:
196/// _Region Spatial Format_ in first item of _Sequence of Ultrasound Regions_
197///
198/// # Example
199///
200/// In most cases, you might only wish to select an attribute
201/// that is sitting at the root of the data set.
202/// This can be done by converting a [DICOM tag](crate::Tag) via [`From<Tag>`]:
203///
204/// ```
205/// # use dicom_core::Tag;
206/// # use dicom_core::ops::AttributeSelector;
207/// // select Patient Name
208/// let selector = AttributeSelector::from(Tag(0x0010, 0x0010));
209/// ```
210///
211/// For working with nested data sets,
212/// `From` also supports converting
213/// an interleaved sequence of tags and item indices in a tuple.
214/// For instance,
215/// this is how we can select the second frame's acquisition date time
216/// from the per-frame functional groups sequence.
217///
218/// ```
219/// # use dicom_core::Tag;
220/// # use dicom_core::ops::AttributeSelector;
221/// let selector: AttributeSelector = (
222/// // Per-frame functional groups sequence
223/// Tag(0x5200, 0x9230),
224/// // item #1
225/// 1,
226/// // Frame Acquisition Date Time (DT)
227/// Tag(0x0018, 0x9074)
228/// ).into();
229/// ```
230///
231/// For a more dynamic construction,
232/// the [`new`] function supports an iterator of attribute selector steps
233/// (of type [`AttributeSelectorStep`]).
234/// Note that the function fails
235/// if the last step refers to a sequence item.
236///
237/// [`new`]: AttributeSelector::new
238///
239/// ```
240/// # use dicom_core::Tag;
241/// # use dicom_core::ops::{AttributeSelector, AttributeSelectorStep};
242/// let selector = AttributeSelector::new([
243/// // Per-frame functional groups sequence, item #1
244/// AttributeSelectorStep::Nested {
245/// tag: Tag(0x5200, 0x9230),
246/// // item #1
247/// item: 1,
248/// },
249/// // Frame Acquisition Date Time
250/// AttributeSelectorStep::Tag(Tag(0x0018, 0x9074)),
251/// ]).ok_or_else(|| "should be a valid sequence")?;
252/// # let selector: AttributeSelector = selector;
253/// # Result::<_, &'static str>::Ok(())
254/// ```
255///
256/// A data dictionary's [`parse_selector`][parse] method
257/// can be used if you want to describe these selectors in text.
258///
259/// ```no_run
260/// # // compile only: we don't have the std dict here
261/// # use dicom_core::{Tag, ops::AttributeSelector};
262/// use dicom_core::dictionary::DataDictionary;
263/// # use dicom_core::dictionary::stub::StubDataDictionary;
264/// # /* faking an import
265/// use dicom_dictionary_std::StandardDataDictionary;
266/// # */
267///
268/// # let StandardDataDictionary = StubDataDictionary;
269/// assert_eq!(
270/// StandardDataDictionary.parse_selector(
271/// "PerFrameFunctionalGroupsSequence[1].(0018,9074)"
272/// )?,
273/// AttributeSelector::from((
274/// // Per-frame functional groups sequence
275/// Tag(0x5200, 0x9230),
276/// // item #1
277/// 1,
278/// // Frame Acquisition Date Time (DT)
279/// Tag(0x0018, 0x9074)
280/// )),
281/// );
282/// # Result::<_, Box<dyn std::error::Error>>::Ok(())
283/// ```
284///
285/// [parse]: crate::dictionary::DataDictionary::parse_selector
286///
287/// Selectors can be decomposed back into its constituent steps
288/// by turning it into an iterator:
289///
290/// ```
291/// # use dicom_core::Tag;
292/// # use dicom_core::ops::{AttributeSelector, AttributeSelectorStep};
293/// # let selector = AttributeSelector::from(
294/// # (Tag(0x5200, 0x9230), 1, Tag(0x0018, 0x9074)));
295/// let steps: Vec<AttributeSelectorStep> = selector.into_iter().collect();
296///
297/// assert_eq!(
298/// &steps,
299/// &[
300/// AttributeSelectorStep::Nested {
301/// tag: Tag(0x5200, 0x9230),
302/// item: 1,
303/// },
304/// AttributeSelectorStep::Tag(Tag(0x0018, 0x9074)),
305/// ],
306/// );
307/// ```
308///
309#[derive(Debug, Clone, Eq, Hash, PartialEq)]
310pub struct AttributeSelector(SmallVec<[AttributeSelectorStep; 2]>);
311
312impl AttributeSelector {
313 /// Construct an attribute selector
314 /// from an arbitrary sequence of selector steps.
315 ///
316 /// Intermediate steps of variant [`Tag`][1]
317 /// (which do not specify an item index)
318 /// are automatically reinterpreted as item selectors for item index 0.
319 ///
320 /// Returns `None` if the sequence is empty
321 /// or the last step is not a tag selector step.
322 ///
323 /// [1]: AttributeSelectorStep::Tag
324 pub fn new(steps: impl IntoIterator<Item = AttributeSelectorStep>) -> Option<Self> {
325 let mut steps: SmallVec<_> = steps.into_iter().collect();
326 debug_assert!(steps.len() < 256);
327 let (last, rest) = steps.split_last_mut()?;
328 if matches!(last, AttributeSelectorStep::Nested { .. }) {
329 return None;
330 }
331 // transform intermediate `Tag` steps into the `Nested` variant
332 for step in rest {
333 if let AttributeSelectorStep::Tag(tag) = step {
334 *step = AttributeSelectorStep::Nested { tag: *tag, item: 0 };
335 }
336 }
337 Some(AttributeSelector(steps))
338 }
339
340 /// Split this attribute selector into the first step
341 /// and its remainder.
342 ///
343 /// If the first part of the tuple is the last step of the selector,
344 /// the first item will be of the variant [`Tag`](AttributeSelectorStep::Tag)
345 /// and the second item of the tuple will be `None`.
346 /// Otherwise,
347 /// the first item is guaranteed to be of the variant
348 /// [`Nested`](AttributeSelectorStep::Nested).
349 pub fn split_first(&self) -> (AttributeSelectorStep, Option<AttributeSelector>) {
350 match self.0.split_first() {
351 Some((first, rest)) => {
352 let rest = if rest.is_empty() {
353 None
354 } else {
355 Some(AttributeSelector(rest.into()))
356 };
357 (*first, rest)
358 }
359 None => {
360 unreachable!("invariant broken: attribute selector should have at least one step")
361 }
362 }
363 }
364
365 /// Return a non-empty iterator over the steps of attribute selection.
366 ///
367 /// The iterator is guaranteed to produce a series
368 /// starting with zero or more steps of the variant [`Nested`][1],
369 /// and terminated by one item guaranteed to be a [tag][2].
370 ///
371 /// [1]: AttributeSelectorStep::Nested
372 /// [2]: AttributeSelectorStep::Tag
373 pub fn iter(&self) -> impl Iterator<Item = &AttributeSelectorStep> {
374 self.into_iter()
375 }
376
377 /// Obtain a reference to the first attribute selection step.
378 pub fn first_step(&self) -> &AttributeSelectorStep {
379 // guaranteed not to be empty
380 self.0
381 .first()
382 .expect("invariant broken: attribute selector should have at least one step")
383 }
384
385 /// Obtain a reference to the last attribute selection step.
386 pub fn last_step(&self) -> &AttributeSelectorStep {
387 // guaranteed not to be empty
388 self.0
389 .last()
390 .expect("invariant broken: attribute selector should have at least one step")
391 }
392
393 /// Obtain the tag of the last attribute selection step.
394 pub fn last_tag(&self) -> Tag {
395 match self.last_step() {
396 AttributeSelectorStep::Tag(tag) => *tag,
397 _ => unreachable!("invariant broken: last attribute selector step should be Tag"),
398 }
399 }
400
401 /// Obtain the number of steps of the selector.
402 ///
403 /// Since selectors cannot be empty,
404 /// the number of steps is always larger than zero.
405 pub fn num_steps(&self) -> u32 {
406 self.0.len() as u32
407 }
408}
409
410impl IntoIterator for AttributeSelector {
411 type Item = AttributeSelectorStep;
412 type IntoIter = <SmallVec<[AttributeSelectorStep; 2]> as IntoIterator>::IntoIter;
413
414 /// Returns a non-empty iterator over the steps of attribute selection.
415 ///
416 /// The iterator is guaranteed to produce a series
417 /// starting with zero or more steps of the variant [`Nested`][1],
418 /// and terminated by one item guaranteed to be a [tag][2].
419 ///
420 /// [1]: AttributeSelectorStep::Nested
421 /// [2]: AttributeSelectorStep::Tag
422 fn into_iter(self) -> Self::IntoIter {
423 self.0.into_iter()
424 }
425}
426
427impl<'a> IntoIterator for &'a AttributeSelector {
428 type Item = &'a AttributeSelectorStep;
429 type IntoIter = <&'a SmallVec<[AttributeSelectorStep; 2]> as IntoIterator>::IntoIter;
430
431 /// Returns a non-empty iterator over the steps of attribute selection.
432 ///
433 /// The iterator is guaranteed to produce a series
434 /// starting with zero or more steps of the variant [`Nested`][1],
435 /// and terminated by one item guaranteed to be a [tag][2].
436 ///
437 /// [1]: AttributeSelectorStep::Nested
438 /// [2]: AttributeSelectorStep::Tag
439 fn into_iter(self) -> Self::IntoIter {
440 self.0.iter()
441 }
442}
443
444/// Creates an attribute selector for just a [`tag`](AttributeSelectorStep::Tag).
445impl From<Tag> for AttributeSelector {
446 /// Creates a simple attribute selector
447 /// by selecting the element at the data set root with the given DICOM tag.
448 fn from(tag: Tag) -> Self {
449 AttributeSelector(smallvec![tag.into()])
450 }
451}
452
453/// Creates an attribute selector for `tag[item].tag`
454impl From<(Tag, u32, Tag)> for AttributeSelector {
455 /// Creates an attribute selector
456 /// which navigates to the data set item at index `item`
457 /// in the sequence at the first DICOM tag (`tag0`),
458 /// then selects the element with the second DICOM tag (`tag1`).
459 fn from((tag0, item, tag1): (Tag, u32, Tag)) -> Self {
460 AttributeSelector(smallvec![(tag0, item).into(), tag1.into()])
461 }
462}
463
464/// Creates an attribute selector for `tag.tag`
465/// (where the first)
466impl From<(Tag, Tag)> for AttributeSelector {
467 /// Creates an attribute selector
468 /// which navigates to the first data set item
469 /// in the sequence at the first DICOM tag (`tag0`),
470 /// then selects the element with the second DICOM tag (`tag1`).
471 #[inline]
472 fn from((tag0, tag1): (Tag, Tag)) -> Self {
473 AttributeSelector(smallvec![(tag0, 0).into(), tag1.into()])
474 }
475}
476
477/// Creates an attribute selector for `tag[item].tag[item].tag`
478impl From<(Tag, u32, Tag, u32, Tag)> for AttributeSelector {
479 /// Creates an attribute selector
480 /// which navigates to data set item #`item0`
481 /// in the sequence at `tag0`,
482 /// navigates further down to item #`item1` in the sequence at `tag1`,
483 /// then selects the element at `tag2`.
484 fn from((tag0, item0, tag1, item1, tag2): (Tag, u32, Tag, u32, Tag)) -> Self {
485 AttributeSelector(smallvec![
486 (tag0, item0).into(),
487 (tag1, item1).into(),
488 tag2.into()
489 ])
490 }
491}
492
493/// Creates an attribute selector for `tag.tag[item].tag`
494impl From<(Tag, Tag, u32, Tag)> for AttributeSelector {
495 /// Creates an attribute selector
496 /// which navigates to the first data set item
497 /// in the sequence at `tag0`,
498 /// navigates further down to item #`item1` in the sequence at `tag1`,
499 /// then selects the element at `tag2`.
500 fn from((tag0, tag1, item1, tag2): (Tag, Tag, u32, Tag)) -> Self {
501 AttributeSelector(smallvec![
502 (tag0, 0).into(),
503 (tag1, item1).into(),
504 tag2.into()
505 ])
506 }
507}
508
509/// Creates an attribute selector for `tag[item].tag.tag`
510impl From<(Tag, u32, Tag, Tag)> for AttributeSelector {
511 /// Creates an attribute selector
512 /// which navigates to the data set item #`item0`
513 /// in the sequence at `tag0`,
514 /// navigates further down to the first item in the sequence at `tag1`,
515 /// then selects the element at `tag2`.
516 fn from((tag0, item0, tag1, tag2): (Tag, u32, Tag, Tag)) -> Self {
517 AttributeSelector(smallvec![
518 (tag0, item0).into(),
519 (tag1, 0).into(),
520 tag2.into()
521 ])
522 }
523}
524
525/// Creates an attribute selector for `tag.tag.tag`
526impl From<(Tag, Tag, Tag)> for AttributeSelector {
527 /// Creates an attribute selector
528 /// which navigates to the first data set item
529 /// in the sequence at `tag0`,
530 /// navigates further down to the first item in the sequence at `tag1`,
531 /// then selects the element at `tag2`.
532 fn from((tag0, tag1, tag2): (Tag, Tag, Tag)) -> Self {
533 AttributeSelector(smallvec![(tag0, 0).into(), (tag1, 0).into(), tag2.into()])
534 }
535}
536
537/// Creates an attribute selector for `tag[item].tag[item].tag[item].tag`
538impl From<(Tag, u32, Tag, u32, Tag, u32, Tag)> for AttributeSelector {
539 // you should get the gist at this point
540 fn from(
541 (tag0, item0, tag1, item1, tag2, item2, tag3): (Tag, u32, Tag, u32, Tag, u32, Tag),
542 ) -> Self {
543 AttributeSelector(smallvec![
544 (tag0, item0).into(),
545 (tag1, item1).into(),
546 (tag2, item2).into(),
547 tag3.into()
548 ])
549 }
550}
551
552impl std::fmt::Display for AttributeSelector {
553 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
554 let mut started = false;
555 for step in &self.0 {
556 if !started {
557 started = true;
558 } else {
559 // separate each step by a dot
560 f.write_char('.')?;
561 }
562 std::fmt::Display::fmt(step, f)?;
563 }
564 Ok(())
565 }
566}
567
568/// Descriptor for the kind of action to apply over an attribute.
569///
570/// See the [module-level documentation](crate::ops)
571/// for more details.
572#[non_exhaustive]
573#[derive(Debug, Clone, PartialEq)]
574pub enum AttributeAction {
575 /// Remove the attribute if it exists.
576 ///
577 /// Do nothing otherwise.
578 Remove,
579 /// If the attribute exists, clear its value to zero bytes.
580 Empty,
581 /// If the attribute exists,
582 /// set or provide a hint about the attribute's value representation.
583 ///
584 /// The underlying value is not modified.
585 /// Implementations are free to ignore this request if
586 /// it cannot be done or it does not make sense
587 /// for the given implementation.
588 SetVr(VR),
589 /// Fully reset the attribute with the given DICOM value,
590 /// creating it if it does not exist yet.
591 ///
592 /// For objects supporting nested data sets,
593 /// passing [`PrimitiveValue::Empty`] will create
594 /// an empty data set sequence.
595 Set(PrimitiveValue),
596 /// Fully reset a textual attribute with the given string,
597 /// creating it if it does not exist yet.
598 SetStr(Cow<'static, str>),
599 /// Provide the attribute with the given DICOM value,
600 /// if it does not exist yet.
601 ///
602 /// For objects supporting nested data sets,
603 /// passing [`PrimitiveValue::Empty`] will create
604 /// an empty data set sequence.
605 SetIfMissing(PrimitiveValue),
606 /// Provide the textual attribute with the given string,
607 /// creating it if it does not exist yet.
608 SetStrIfMissing(Cow<'static, str>),
609 /// Fully replace the value with the given DICOM value,
610 /// but only if the attribute already exists.
611 ///
612 /// For objects supporting nested data sets,
613 /// passing [`PrimitiveValue::Empty`] will clear the items
614 /// of an existing data set sequence.
615 Replace(PrimitiveValue),
616 /// Fully replace a textual value with the given string,
617 /// but only if the attribute already exists.
618 ReplaceStr(Cow<'static, str>),
619 /// Append a string as an additional textual value,
620 /// creating the attribute if it does not exist yet.
621 ///
622 /// New value items are recorded as separate text values,
623 /// meaning that they are delimited by a backslash (`\`) at encoding time,
624 /// regardless of the value representation.
625 PushStr(Cow<'static, str>),
626 /// Append a 32-bit signed integer as an additional numeric value,
627 /// creating the attribute if it does not exist yet.
628 PushI32(i32),
629 /// Append a 32-bit unsigned integer as an additional numeric value,
630 /// creating the attribute if it does not exist yet.
631 PushU32(u32),
632 /// Append a 16-bit signed integer as an additional numeric value,
633 /// creating the attribute if it does not exist yet.
634 PushI16(i16),
635 /// Append a 16-bit unsigned integer as an additional numeric value,
636 /// creating the attribute if it does not exist yet.
637 PushU16(u16),
638 /// Append a 32-bit floating point number as an additional numeric value,
639 /// creating the attribute if it does not exist yet.
640 PushF32(f32),
641 /// Append a 64-bit floating point number as an additional numeric value,
642 /// creating the attribute if it does not exist yet.
643 PushF64(f64),
644 /// Truncate a value or a sequence to the given number of items,
645 /// removing extraneous items from the end of the list.
646 ///
647 /// On primitive values, this truncates the value
648 /// by the number of individual value items
649 /// (note that bytes in a [`PrimitiveValue::U8`]
650 /// are treated as individual items).
651 /// On data set sequences and pixel data fragment sequences,
652 /// this operation is applied to
653 /// the data set items (or fragments) in the sequence.
654 ///
655 /// Does nothing if the attribute does not exist
656 /// or the cardinality of the element is already lower than or equal to
657 /// the given size.
658 Truncate(usize),
659}
660
661impl AttributeAction {
662 /// Report whether this is considered a _constructive_ action,
663 /// operations of which create new elements if they do not exist yet.
664 ///
665 /// The actions currently considered to be constructive are
666 /// all actions of the families `Set*`, `SetIfMissing`, and `Push*`.
667 pub fn is_constructive(&self) -> bool {
668 matches!(
669 self,
670 AttributeAction::Set(_)
671 | AttributeAction::SetStr(_)
672 | AttributeAction::SetIfMissing(_)
673 | AttributeAction::SetStrIfMissing(_)
674 | AttributeAction::PushF32(_)
675 | AttributeAction::PushF64(_)
676 | AttributeAction::PushI16(_)
677 | AttributeAction::PushI32(_)
678 | AttributeAction::PushStr(_)
679 | AttributeAction::PushU16(_)
680 | AttributeAction::PushU32(_)
681 )
682 }
683}
684
685/// Trait for applying DICOM attribute operations.
686///
687/// This is typically implemented by DICOM objects and other data set types
688/// to serve as a common API for attribute manipulation.
689pub trait ApplyOp {
690 /// The operation error type
691 type Err: std::error::Error + 'static;
692
693 /// Apply the given attribute operation on the receiving object.
694 ///
695 /// Effects may slightly differ between implementations,
696 /// but should always be compliant with
697 /// the expectations defined in [`AttributeAction`] variants.
698 ///
699 /// If the action to apply is unsupported,
700 /// or not possible for other reasons,
701 /// an error is returned and no changes to the receiver are made.
702 /// While not all kinds of operations may be possible,
703 /// generic DICOM data set holders will usually support all actions.
704 /// See the respective documentation of the implementing type
705 /// for more details.
706 fn apply(&mut self, op: AttributeOp) -> Result<(), Self::Err>;
707}
708
709#[cfg(test)]
710mod tests {
711 use crate::{
712 ops::{AttributeSelector, AttributeSelectorStep},
713 Tag,
714 };
715
716 #[test]
717 fn display_selectors() {
718 let selector: AttributeSelector = Tag(0x0014, 0x5100).into();
719 assert_eq!(selector.to_string(), "(0014,5100)");
720
721 let selector: AttributeSelector = (Tag(0x0018, 0x6011), 2, Tag(0x0018, 0x6012)).into();
722 assert_eq!(selector.to_string(), "(0018,6011)[2].(0018,6012)");
723
724 let selector = AttributeSelector::from((Tag(0x0040, 0xA730), 1, Tag(0x0040, 0xA730)));
725 assert_eq!(selector.to_string(), "(0040,A730)[1].(0040,A730)");
726 }
727
728 #[test]
729 fn split_selectors() {
730 let selector: AttributeSelector = Tag(0x0014, 0x5100).into();
731 assert_eq!(
732 selector.split_first(),
733 (AttributeSelectorStep::Tag(Tag(0x0014, 0x5100)), None)
734 );
735
736 let selector: AttributeSelector = (Tag(0x0018, 0x6011), 2, Tag(0x0018, 0x6012)).into();
737 assert_eq!(
738 selector.split_first(),
739 (
740 AttributeSelectorStep::Nested {
741 tag: Tag(0x0018, 0x6011),
742 item: 2
743 },
744 Some(Tag(0x0018, 0x6012).into())
745 )
746 );
747
748 // selector constructor automatically turns the first entry into `Nested`
749 let selector = AttributeSelector::from((Tag(0x0040, 0xA730), Tag(0x0040, 0xA730)));
750 assert_eq!(
751 selector.split_first(),
752 (
753 AttributeSelectorStep::Nested {
754 tag: Tag(0x0040, 0xA730),
755 item: 0
756 },
757 Some(Tag(0x0040, 0xA730).into())
758 )
759 );
760 }
761}