wsi-dicom-annotations 0.2.1

DICOM WSI ANN, SEG, SR, and Parametric Map interoperability
Documentation
use serde::{Deserialize, Serialize};

use crate::{Error, Result};

use super::model::{AlgorithmIdentification, DicomCode, DicomCodeQualifiers};

#[derive(Debug, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ProfileAlgorithm {
    family: ProfileCode,
    #[serde(default)]
    name_code: Option<ProfileCode>,
    name: String,
    version: String,
    #[serde(default)]
    parameters: Option<String>,
    #[serde(default)]
    source: Option<String>,
}

impl ProfileAlgorithm {
    pub(crate) fn into_algorithm(self) -> Result<AlgorithmIdentification> {
        let mut algorithm =
            AlgorithmIdentification::new(self.family.into_code()?, self.name, self.version)?;
        if let Some(code) = self.name_code {
            algorithm = algorithm.with_name_code(code.into_code()?);
        }
        if let Some(parameters) = self.parameters {
            algorithm = algorithm.with_parameters(parameters)?;
        }
        if let Some(source) = self.source {
            algorithm = algorithm.with_source(source)?;
        }
        Ok(algorithm)
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub(crate) struct ProfileCode {
    #[serde(default)]
    code_value: Option<String>,
    #[serde(default)]
    long_code_value: Option<String>,
    #[serde(default)]
    urn_code_value: Option<String>,
    #[serde(default)]
    coding_scheme_designator: String,
    #[serde(default)]
    coding_scheme_version: Option<String>,
    code_meaning: String,
    #[serde(default)]
    context_identifier: Option<String>,
    #[serde(default)]
    context_uid: Option<String>,
    #[serde(default)]
    mapping_resource: Option<String>,
    #[serde(default)]
    mapping_resource_uid: Option<String>,
    #[serde(default)]
    context_group_version: Option<String>,
    #[serde(default)]
    context_group_local_version: Option<String>,
    #[serde(default)]
    context_group_extension: Option<bool>,
    #[serde(default)]
    context_group_extension_creator_uid: Option<String>,
}

impl ProfileCode {
    pub(crate) fn from_code(code: &DicomCode) -> Self {
        let (code_value, long_code_value, urn_code_value) = match code.value_kind() {
            super::model::DicomCodeValueKind::Short => (Some(code.value().to_string()), None, None),
            super::model::DicomCodeValueKind::Long => (None, Some(code.value().to_string()), None),
            super::model::DicomCodeValueKind::Urn => (None, None, Some(code.value().to_string())),
        };
        Self {
            code_value,
            long_code_value,
            urn_code_value,
            coding_scheme_designator: code.scheme().to_string(),
            coding_scheme_version: code.coding_scheme_version().map(str::to_string),
            code_meaning: code.meaning().to_string(),
            context_identifier: code.context_identifier().map(str::to_string),
            context_uid: code.context_uid().map(str::to_string),
            mapping_resource: code.mapping_resource().map(str::to_string),
            mapping_resource_uid: code.mapping_resource_uid().map(str::to_string),
            context_group_version: code.context_group_version().map(str::to_string),
            context_group_local_version: code.context_group_local_version().map(str::to_string),
            context_group_extension: code.context_group_extension(),
            context_group_extension_creator_uid: code
                .context_group_extension_creator_uid()
                .map(str::to_string),
        }
    }

    pub(crate) fn into_code(self) -> Result<DicomCode> {
        let mut code = match (self.code_value, self.long_code_value, self.urn_code_value) {
            (Some(value), None, None) => {
                DicomCode::new(value, self.coding_scheme_designator, self.code_meaning)?
            }
            (None, Some(value), None) => {
                DicomCode::new_long(value, self.coding_scheme_designator, self.code_meaning)?
            }
            (None, None, Some(value)) => {
                DicomCode::new_urn(value, self.coding_scheme_designator, self.code_meaning)?
            }
            _ => {
                return Err(Error::InvalidInput(
                    "a mapped code requires exactly one of code_value, long_code_value, or urn_code_value"
                        .into(),
                ));
            }
        };
        code = code.with_optional_qualifiers(DicomCodeQualifiers {
            coding_scheme_version: self.coding_scheme_version,
            context_identifier: self.context_identifier,
            context_uid: self.context_uid,
            mapping_resource: self.mapping_resource,
            mapping_resource_uid: self.mapping_resource_uid,
            context_group_version: self.context_group_version,
            context_group_local_version: self.context_group_local_version,
            context_group_extension: self.context_group_extension,
            context_group_extension_creator_uid: self.context_group_extension_creator_uid,
        })?;
        Ok(code)
    }
}

pub(crate) fn profile_codes(codes: Vec<ProfileCode>) -> Result<Vec<DicomCode>> {
    codes.into_iter().map(ProfileCode::into_code).collect()
}