Skip to main content

wsi_dicom_annotations/annotations/scheme/
concept_key.rs

1use sha2::{Digest, Sha256};
2
3use super::model::AnnotationClassGeometry;
4use crate::annotations::model::{DicomCode, DicomCodeValueKind};
5use crate::annotations::semantic_digest::update_text;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash)]
8pub struct AnnotationClassConceptKey(pub(super) String);
9
10impl AnnotationClassConceptKey {
11    #[must_use]
12    pub fn as_str(&self) -> &str {
13        &self.0
14    }
15}
16
17impl std::fmt::Display for AnnotationClassConceptKey {
18    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
19        formatter.write_str(&self.0)
20    }
21}
22
23/// Builds the stable semantic identity used to match imported concepts to a
24/// controlled annotation class.
25///
26/// Display meanings and other presentation metadata are deliberately ignored.
27#[must_use]
28pub fn annotation_class_concept_key(
29    geometry: AnnotationClassGeometry,
30    category: &DicomCode,
31    property_type: &DicomCode,
32    modifiers: &[DicomCode],
33) -> AnnotationClassConceptKey {
34    let mut digest = Sha256::new();
35    digest.update(b"frames-annotation-class-concept-v1\0");
36    digest.update([match geometry {
37        AnnotationClassGeometry::Region => 0,
38        AnnotationClassGeometry::Point => 1,
39    }]);
40    update_concept_code(&mut digest, category);
41    update_concept_code(&mut digest, property_type);
42    let mut modifiers = modifiers.iter().map(code_identity).collect::<Vec<_>>();
43    modifiers.sort();
44    digest.update((modifiers.len() as u64).to_le_bytes());
45    for modifier in modifiers {
46        update_text(&mut digest, &modifier);
47    }
48    AnnotationClassConceptKey(format!("sha256:{:x}", digest.finalize()))
49}
50
51fn update_concept_code(digest: &mut Sha256, code: &DicomCode) {
52    update_text(digest, &code_identity(code));
53}
54
55pub(super) fn code_identity(code: &DicomCode) -> String {
56    let kind = match code.value_kind() {
57        DicomCodeValueKind::Short => "short",
58        DicomCodeValueKind::Long => "long",
59        DicomCodeValueKind::Urn => "urn",
60    };
61    format!(
62        "{kind}\u{1f}{}\u{1f}{}\u{1f}{}",
63        code.scheme(),
64        code.value(),
65        code.coding_scheme_version().unwrap_or("")
66    )
67}