pub mod actions;
pub mod config;
mod dicom;
pub mod hasher;
pub mod processor;
mod test_utils;
use std::io::{Read, Write};
use crate::config::builder::ConfigBuilder;
use crate::processor::{DefaultProcessor, Error as ProcessingError};
pub use dicom_core::Tag;
pub use dicom_dictionary_std::tags;
use dicom_object::{DefaultDicomObject, FileDicomObject, OpenFileOptions, ReadError, WriteError};
use processor::Processor;
use thiserror::Error;
#[derive(Debug, Clone, PartialEq)]
pub struct AnonymizationResult {
pub original: DefaultDicomObject,
pub anonymized: DefaultDicomObject,
}
#[derive(Error, Debug, PartialEq)]
pub enum AnonymizationError {
#[error("Read error: {}", .0.to_lowercase())]
ReadError(String),
#[error("Write error: {}", .0.to_lowercase())]
WriteError(String),
#[error("{0}")]
ProcessingError(String),
}
impl From<ReadError> for AnonymizationError {
fn from(err: ReadError) -> Self {
AnonymizationError::ReadError(format!("{err}"))
}
}
impl From<WriteError> for AnonymizationError {
fn from(err: WriteError) -> Self {
AnonymizationError::WriteError(format!("{err}"))
}
}
impl From<ProcessingError> for AnonymizationError {
fn from(err: ProcessingError) -> Self {
AnonymizationError::ProcessingError(format!("{err}"))
}
}
pub type Result<T, E = AnonymizationError> = std::result::Result<T, E>;
impl AnonymizationResult {
pub fn write<W: Write>(&self, to: W) -> Result<()> {
self.anonymized.write_all(to)?;
Ok(())
}
}
pub struct Anonymizer {
processor: Box<dyn Processor + Send + Sync>,
}
impl Anonymizer {
pub fn new<T>(processor: T) -> Self
where
T: Processor + Send + Sync + 'static,
{
Self {
processor: Box::new(processor),
}
}
pub fn anonymize(&self, src: impl Read) -> Result<AnonymizationResult> {
let obj = OpenFileOptions::new().from_reader(src)?;
let mut new_obj = FileDicomObject::new_empty_with_meta(obj.meta().clone());
for elem in &obj {
let result = self.processor.process_element(&obj, elem);
match result {
Ok(None) => continue,
Ok(Some(processed_elem)) => {
new_obj.put(processed_elem.into_owned());
}
Err(err) => return Err(err.into()),
}
}
if let Ok(elem) = new_obj.element(tags::SOP_INSTANCE_UID) {
let sop_instance_uid = elem.value().clone();
let meta = new_obj.meta_mut();
if let Ok(sop_instance_uid_str) = sop_instance_uid.to_str() {
meta.media_storage_sop_instance_uid = sop_instance_uid_str.into_owned();
meta.update_information_group_length();
}
}
if let Ok(elem) = new_obj.element(tags::SOP_CLASS_UID) {
let sop_class_uid = elem.value().clone();
let meta = new_obj.meta_mut();
if let Ok(sop_class_uid_str) = sop_class_uid.to_str() {
meta.media_storage_sop_class_uid = sop_class_uid_str.into_owned();
meta.update_information_group_length();
}
}
Ok(AnonymizationResult {
original: obj,
anonymized: new_obj,
})
}
}
impl Default for Anonymizer {
fn default() -> Self {
let config = ConfigBuilder::default().build();
let processor = DefaultProcessor::new(config);
Self::new(processor)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tags;
use dicom_core::value::Value;
use dicom_core::{PrimitiveValue, VR};
use dicom_object::mem::InMemElement;
use dicom_object::InMemDicomObject;
use crate::config::builder::ConfigBuilder;
use crate::processor::DefaultProcessor;
use crate::test_utils::make_file_meta;
use crate::Tag;
#[test]
fn test_anonymizer() {
let meta = make_file_meta();
let mut obj: FileDicomObject<InMemDicomObject> = FileDicomObject::new_empty_with_meta(meta);
obj.put(InMemElement::new(
tags::PATIENT_NAME,
VR::PN,
Value::from("John Doe"),
));
obj.put(InMemElement::new(
tags::PATIENT_ID,
VR::LO,
Value::from("12345"),
));
obj.put(InMemElement::new(
Tag::from([0x0033, 0x1010]),
VR::LO,
Value::from("I am a private tag and should be removed"),
));
let mut file = Vec::new();
obj.write_all(&mut file).unwrap();
let config = ConfigBuilder::default().build();
let processor = DefaultProcessor::new(config);
let anonymizer = Anonymizer::new(processor);
let result = anonymizer.anonymize(file.as_slice()).unwrap();
assert!(result.anonymized.element(tags::PATIENT_NAME).is_ok());
assert_eq!(
result
.anonymized
.element(tags::PATIENT_NAME)
.unwrap()
.value(),
&Value::Primitive(PrimitiveValue::Str("6652061665".to_string()))
);
assert!(result.anonymized.element(tags::PATIENT_ID).is_ok());
assert_eq!(
result.anonymized.element(tags::PATIENT_ID).unwrap().value(),
&Value::Primitive(PrimitiveValue::from("6662505961"))
);
assert!(result.original.element(Tag::from([0x0033, 0x1010])).is_ok());
assert!(result
.anonymized
.element(Tag::from([0x0033, 0x1010]))
.is_err());
}
}