wsi-dicom-annotations 0.2.1

DICOM WSI ANN, SEG, SR, and Parametric Map interoperability
Documentation
use sha2::{Digest, Sha256};

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

pub(crate) fn update_text(digest: &mut Sha256, value: &str) {
    digest.update((value.len() as u64).to_le_bytes());
    digest.update(value.as_bytes());
}

pub(crate) fn update_code(digest: &mut Sha256, code: &DicomCode) {
    digest.update([code.value_kind() as u8]);
    for value in [
        code.value(),
        code.scheme(),
        code.coding_scheme_version().unwrap_or(""),
        code.meaning(),
        code.context_identifier().unwrap_or(""),
        code.context_uid().unwrap_or(""),
        code.mapping_resource().unwrap_or(""),
        code.mapping_resource_uid().unwrap_or(""),
        code.context_group_version().unwrap_or(""),
        code.context_group_local_version().unwrap_or(""),
        code.context_group_extension_creator_uid().unwrap_or(""),
    ] {
        update_text(digest, value);
    }
    digest.update([code.context_group_extension().map_or(2, u8::from)]);
}

pub(crate) fn update_algorithm(digest: &mut Sha256, algorithm: &AlgorithmIdentification) {
    update_code(digest, algorithm.family());
    digest.update([u8::from(algorithm.name_code().is_some())]);
    if let Some(name_code) = algorithm.name_code() {
        update_code(digest, name_code);
    }
    for value in [
        algorithm.name(),
        algorithm.version(),
        algorithm.parameters().unwrap_or(""),
        algorithm.source().unwrap_or(""),
    ] {
        update_text(digest, value);
    }
}

pub(crate) fn finish(digest: Sha256) -> String {
    format!("{:x}", digest.finalize())
}

pub(crate) fn deterministic_annotation_group_uid(
    group: &AnnotationGroup,
    namespace: &str,
    scope: &str,
    application_key: &str,
) -> String {
    let mut digest = Sha256::new();
    digest.update(b"wsi-dicom-annotations:annotation-group-uid:v1\0");
    for value in [namespace, scope, application_key] {
        update_text(&mut digest, value);
    }
    update_text(&mut digest, group.geometry().graphic_type().dicom_value());
    update_text(&mut digest, group.label());
    update_text(&mut digest, group.description());
    update_code(&mut digest, group.category());
    update_code(&mut digest, group.property_type());
    update_codes(&mut digest, group.property_type_modifiers());
    update_codes(&mut digest, group.anatomic_regions());
    update_codes(&mut digest, group.primary_anatomic_structures());
    update_text(&mut digest, group.generation_type().dicom_value());
    digest.update((group.algorithms().len() as u64).to_le_bytes());
    for algorithm in group.algorithms() {
        update_algorithm(&mut digest, algorithm);
    }
    for component in group.recommended_display_cielab() {
        digest.update(component.to_le_bytes());
    }
    digest.update([u8::from(group.applies_to_all_optical_paths())]);
    digest.update((group.referenced_optical_paths().len() as u64).to_le_bytes());
    for path in group.referenced_optical_paths() {
        update_text(&mut digest, path);
    }
    digest.update([u8::from(group.applies_to_all_z_planes())]);
    digest.update((group.common_z_coordinates().len() as u64).to_le_bytes());
    for coordinate in group.common_z_coordinates() {
        digest.update(coordinate.to_bits().to_le_bytes());
    }
    let bytes = digest.finalize();
    let value = u128::from_be_bytes(bytes[..16].try_into().expect("SHA-256 prefix is 16 bytes"));
    format!("2.25.{value}")
}

fn update_codes(digest: &mut Sha256, codes: &[DicomCode]) {
    digest.update((codes.len() as u64).to_le_bytes());
    for code in codes {
        update_code(digest, code);
    }
}