Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: Add functionality for mutating element values #471

Merged
merged 6 commits into from
Apr 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 174 additions & 1 deletion object/src/mem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,14 @@ where
self.entries.get(&tag)
}

// Get a mutable reference to a particular DICOM attribute from this object by tag.
//
// Should be private as it would allow a user to change the tag of an
// element and diverge from the dictionary
fn get_mut(&mut self, tag: Tag) -> Option<&mut InMemElement<D>> {
self.entries.get_mut(&tag)
}

/// Retrieve a particular DICOM element that might not exist by its name.
///
/// If the element does not exist,
Expand Down Expand Up @@ -799,7 +807,7 @@ where
self.len = Length::UNDEFINED;
}

/// Obtain a temporary mutable reference to a DICOM value,
/// Obtain a temporary mutable reference to a DICOM value by tag,
/// so that mutations can be applied within.
///
/// If found, this method resets all related lengths recorded
Expand Down Expand Up @@ -841,6 +849,67 @@ where
}
}

/// Obtain a temporary mutable reference to a DICOM value by AttributeSelector,
/// so that mutations can be applied within.
///
/// If found, this method resets all related lengths recorded
/// and returns `true`.
/// Returns `false` otherwise.
///
/// See the documentation of [`AttributeSelector`] for more information
/// on how to write attribute selectors.
///
/// NOTE: Also consider using the [`apply`].
///
/// # Example
///
/// ```
/// # use dicom_core::{DataElement, VR, dicom_value, value::DataSetSequence};
/// # use dicom_dictionary_std::tags;
/// # use dicom_object::InMemDicomObject;
/// # use dicom_core::ops::{AttributeAction, AttributeOp, ApplyOp};
/// let mut dcm = InMemDicomObject::from_element_iter([
/// DataElement::new(
/// tags::OTHER_PATIENT_I_DS_SEQUENCE,
/// VR::SQ,
/// DataSetSequence::from(vec![InMemDicomObject::from_element_iter([
/// DataElement::new(
/// tags::PATIENT_ID,
/// VR::LO,
/// dicom_value!(Str, "1234")
/// )])
/// ])
/// ),
/// ]);
/// let selector = (
/// tags::OTHER_PATIENT_I_DS_SEQUENCE,
/// 0,
/// tags::PATIENT_ID
/// );
///
/// // update referenced SOP instance UID for deidentification potentially
/// dcm.update_value_at(*&selector, |e| {
/// let mut v = e.primitive_mut().unwrap();
/// *v = dicom_value!(Str, "abcd");
/// });
///
/// assert_eq!(
/// dcm.entry_at(*&selector).unwrap().value().to_str().unwrap(),
/// "abcd"
/// );
/// ```
pub fn update_value_at(
Enet4 marked this conversation as resolved.
Show resolved Hide resolved
&mut self,
selector: impl Into<AttributeSelector>,
f: impl FnMut(&mut Value<InMemDicomObject<D>, InMemFragment>),
) -> Result<(), AtAccessError> {
self.entry_at_mut(selector)
.map(|e| e.update_value(f))
.map(|_| {
self.len = Length::UNDEFINED;
})
}

/// Obtain the DICOM value by finding the element
/// that matches the given selector.
///
Expand Down Expand Up @@ -924,8 +993,112 @@ where
));
}

/// Get a DataElement by AttributeSelector
///
/// If the element or other intermediate elements do not exist, the method will return an error.
///
/// See the documentation of [`AttributeSelector`] for more information
/// on how to write attribute selectors.
///
/// If you only need the value, use [`value_at`].
pub fn entry_at(
naterichman marked this conversation as resolved.
Show resolved Hide resolved
&self,
selector: impl Into<AttributeSelector>,
) -> Result<&InMemElement<D>, AtAccessError> {
let selector: AttributeSelector = selector.into();

let mut obj = self;
for (i, step) in selector.iter().enumerate() {
match step {
// reached the leaf
AttributeSelectorStep::Tag(tag) => {
return obj.get(*tag).with_context(|| MissingLeafElementSnafu {
selector: selector.clone(),
})
}
// navigate further down
AttributeSelectorStep::Nested { tag, item } => {
let e = obj
.entries
.get(tag)
.with_context(|| crate::MissingSequenceSnafu {
selector: selector.clone(),
step_index: i as u32,
})?;

// get items
let items = e.items().with_context(|| NotASequenceSnafu {
selector: selector.clone(),
step_index: i as u32,
})?;

// if item.length == i and action is a constructive action, append new item
obj =
items
.get(*item as usize)
.with_context(|| crate::MissingSequenceSnafu {
selector: selector.clone(),
step_index: i as u32,
})?;
}
}
}

unreachable!()
}

// Get a mutable reference to a particular entry by AttributeSelector
//
// Should be private for the same reason as `self.get_mut`
fn entry_at_mut(
&mut self,
selector: impl Into<AttributeSelector>,
) -> Result<&mut InMemElement<D>, AtAccessError> {
let selector: AttributeSelector = selector.into();

let mut obj = self;
for (i, step) in selector.iter().enumerate() {
match step {
// reached the leaf
AttributeSelectorStep::Tag(tag) => {
return obj.get_mut(*tag).with_context(|| MissingLeafElementSnafu {
selector: selector.clone(),
})
}
// navigate further down
AttributeSelectorStep::Nested { tag, item } => {
let e =
obj.entries
.get_mut(tag)
.with_context(|| crate::MissingSequenceSnafu {
selector: selector.clone(),
step_index: i as u32,
})?;

// get items
let items = e.items_mut().with_context(|| NotASequenceSnafu {
selector: selector.clone(),
step_index: i as u32,
})?;

// if item.length == i and action is a constructive action, append new item
obj = items.get_mut(*item as usize).with_context(|| {
crate::MissingSequenceSnafu {
selector: selector.clone(),
step_index: i as u32,
}
})?;
}
}
}

unreachable!()
}

/// Apply the given attribute operation on this object.
///
/// For more complex updates, see [`update_value_at`].
///
/// See the [`dicom_core::ops`] module
/// for more information.
///
Expand Down
Loading