wsi_dicom_annotations/annotations/scheme/
concept_key.rs1use 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#[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}