wsi-dicom-annotations 0.2.1

DICOM WSI ANN, SEG, SR, and Parametric Map interoperability
Documentation
use dicom_core::value::PrimitiveValue;
use dicom_core::{DataElement, VR};
use dicom_dictionary_std::tags;
use dicom_object::InMemDicomObject;

use crate::annotations::coded_content::code_item;
use crate::annotations::dicom_dataset::{put_text, sequence};
use crate::{DicomCode, Result};

use super::super::document::ParametricMapDocument;

pub(super) fn real_world_value_item(
    document: &ParametricMapDocument,
    channel_index: usize,
    channel_ordinal: u32,
) -> Result<InMemDicomObject> {
    let channel = &document.profile.channels[channel_index];
    let range = document.channel_ranges[channel_ordinal as usize - 1];
    let mut item = InMemDicomObject::new_empty();
    item.put(DataElement::new(
        tags::DOUBLE_FLOAT_REAL_WORLD_VALUE_FIRST_VALUE_MAPPED,
        VR::FD,
        PrimitiveValue::F64(vec![range.minimum].into()),
    ));
    item.put(DataElement::new(
        tags::DOUBLE_FLOAT_REAL_WORLD_VALUE_LAST_VALUE_MAPPED,
        VR::FD,
        PrimitiveValue::F64(vec![range.maximum].into()),
    ));
    item.put(DataElement::new(
        tags::REAL_WORLD_VALUE_INTERCEPT,
        VR::FD,
        PrimitiveValue::F64(vec![0.0].into()),
    ));
    item.put(DataElement::new(
        tags::REAL_WORLD_VALUE_SLOPE,
        VR::FD,
        PrimitiveValue::F64(vec![1.0].into()),
    ));
    put_text(&mut item, tags::LUT_EXPLANATION, VR::LO, &channel.name);
    put_text(
        &mut item,
        tags::LUT_LABEL,
        VR::SH,
        &format!("PARAM{channel_ordinal:04}"),
    );
    item.put(sequence(
        tags::MEASUREMENT_UNITS_CODE_SEQUENCE,
        vec![code_item(&channel.unit)],
    ));
    item.put(sequence(
        tags::QUANTITY_DEFINITION_SEQUENCE,
        quantity_definition_items(document, &channel.quantity)?,
    ));
    Ok(item)
}

fn quantity_definition_items(
    document: &ParametricMapDocument,
    quantity: &DicomCode,
) -> Result<Vec<InMemDicomObject>> {
    let algorithm = &document.profile.algorithm;
    let mut items = vec![coded_content_item(
        DicomCode::new("246205007", "SCT", "Quantity")?,
        quantity.clone(),
    )];
    items.push(text_content_item(
        DicomCode::new("111001", "DCM", "Algorithm Name")?,
        algorithm.name(),
    ));
    if let Some(name_code) = algorithm.name_code() {
        items.push(coded_content_item(
            DicomCode::new("111001", "DCM", "Algorithm Name")?,
            name_code.clone(),
        ));
    }
    items.push(text_content_item(
        DicomCode::new("111003", "DCM", "Algorithm Version")?,
        algorithm.version(),
    ));
    items.push(coded_content_item(
        DicomCode::new("111000", "DCM", "Algorithm Family")?,
        algorithm.family().clone(),
    ));
    if let Some(parameters) = algorithm.parameters() {
        items.push(text_content_item(
            DicomCode::new("111002", "DCM", "Algorithm Parameters")?,
            parameters,
        ));
    }
    if let Some(source) = algorithm.source() {
        items.push(text_content_item(
            DicomCode::new("122405", "DCM", "Algorithm Manufacturer")?,
            source,
        ));
    }
    Ok(items)
}

fn coded_content_item(name: DicomCode, value: DicomCode) -> InMemDicomObject {
    let mut item = InMemDicomObject::new_empty();
    put_text(&mut item, tags::VALUE_TYPE, VR::CS, "CODE");
    item.put(sequence(
        tags::CONCEPT_NAME_CODE_SEQUENCE,
        vec![code_item(&name)],
    ));
    item.put(sequence(
        tags::CONCEPT_CODE_SEQUENCE,
        vec![code_item(&value)],
    ));
    item
}

fn text_content_item(name: DicomCode, value: &str) -> InMemDicomObject {
    let mut item = InMemDicomObject::new_empty();
    put_text(&mut item, tags::VALUE_TYPE, VR::CS, "TEXT");
    item.put(sequence(
        tags::CONCEPT_NAME_CODE_SEQUENCE,
        vec![code_item(&name)],
    ));
    put_text(&mut item, tags::TEXT_VALUE, VR::UT, value);
    item
}

pub(super) fn add_algorithm_provenance(
    document: &ParametricMapDocument,
    object: &mut InMemDicomObject,
) -> Result<()> {
    let algorithm = &document.profile.algorithm;
    let mut equipment = InMemDicomObject::new_empty();
    equipment.put(sequence(
        tags::PURPOSE_OF_REFERENCE_CODE_SEQUENCE,
        vec![code_item(&DicomCode::new(
            "109102",
            "DCM",
            "Processing Equipment",
        )?)],
    ));
    put_text(
        &mut equipment,
        tags::MANUFACTURER,
        VR::LO,
        algorithm
            .source()
            .unwrap_or(document.producer.manufacturer()),
    );
    put_text(
        &mut equipment,
        tags::MANUFACTURER_MODEL_NAME,
        VR::LO,
        algorithm.name(),
    );
    put_text(
        &mut equipment,
        tags::SOFTWARE_VERSIONS,
        VR::LO,
        algorithm.version(),
    );
    put_text(
        &mut equipment,
        tags::CONTRIBUTION_DESCRIPTION,
        VR::ST,
        "Generated quantitative pathology pixel values",
    );
    object.put(sequence(
        tags::CONTRIBUTING_EQUIPMENT_SEQUENCE,
        vec![equipment],
    ));
    Ok(())
}