Skip to main content

wsi_dicom_annotations/annotations/scheme/
json.rs

1use serde::{Deserialize, Serialize};
2
3use super::color::parse_rgb;
4use super::model::{AnnotationClass, AnnotationClassGeometry, AnnotationScheme};
5use super::{MAX_CLASSES, MAX_SCHEME_BYTES, SCHEME_SCHEMA_VERSION};
6use crate::annotations::json::validate_unique_object_keys;
7use crate::annotations::profile::ProfileCode;
8use crate::{Error, Result};
9
10impl Serialize for AnnotationScheme {
11    fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
12    where
13        S: serde::Serializer,
14    {
15        RawAnnotationScheme::from_scheme(self).serialize(serializer)
16    }
17}
18
19impl<'de> Deserialize<'de> for AnnotationScheme {
20    fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
21    where
22        D: serde::Deserializer<'de>,
23    {
24        RawAnnotationScheme::deserialize(deserializer)?
25            .into_scheme()
26            .map_err(serde::de::Error::custom)
27    }
28}
29
30impl AnnotationScheme {
31    pub fn from_json(json: &[u8]) -> Result<Self> {
32        if json.len() > MAX_SCHEME_BYTES {
33            return Err(Error::InvalidInput(
34                "annotation scheme exceeds the 4 MiB input limit".into(),
35            ));
36        }
37        validate_unique_object_keys(json, "annotation scheme")?;
38        let raw: RawAnnotationScheme = serde_json::from_slice(json).map_err(|error| {
39            Error::InvalidInput(format!("annotation scheme is not valid JSON: {error}"))
40        })?;
41        raw.into_scheme()
42    }
43
44    pub fn to_json(&self) -> Result<Vec<u8>> {
45        serde_json::to_vec_pretty(&RawAnnotationScheme::from_scheme(self)).map_err(|error| {
46            Error::InvalidInput(format!(
47                "annotation scheme could not be serialized: {error}"
48            ))
49        })
50    }
51}
52
53#[derive(Debug, Serialize, Deserialize)]
54#[serde(deny_unknown_fields)]
55struct RawAnnotationScheme {
56    schema_version: u32,
57    scheme_id: String,
58    scheme_version: u32,
59    display_name: String,
60    classes: Vec<RawAnnotationClass>,
61    #[serde(default, skip_serializing_if = "Vec::is_empty")]
62    finding_sites: Vec<ProfileCode>,
63}
64
65impl RawAnnotationScheme {
66    fn into_scheme(self) -> Result<AnnotationScheme> {
67        if self.schema_version != SCHEME_SCHEMA_VERSION {
68            return Err(Error::Unsupported(format!(
69                "annotation scheme schema version {} is not supported",
70                self.schema_version
71            )));
72        }
73        if self.classes.len() > MAX_CLASSES {
74            return Err(Error::InvalidInput(format!(
75                "annotation scheme cannot define more than {MAX_CLASSES} classes"
76            )));
77        }
78        let classes = self
79            .classes
80            .into_iter()
81            .map(RawAnnotationClass::into_class)
82            .collect::<Result<Vec<_>>>()?;
83        let finding_sites = self
84            .finding_sites
85            .into_iter()
86            .map(ProfileCode::into_code)
87            .collect::<Result<Vec<_>>>()?;
88        AnnotationScheme::new(
89            self.scheme_id,
90            self.scheme_version,
91            self.display_name,
92            classes,
93            finding_sites,
94        )
95    }
96
97    fn from_scheme(scheme: &AnnotationScheme) -> Self {
98        Self {
99            schema_version: scheme.schema_version,
100            scheme_id: scheme.id.clone(),
101            scheme_version: scheme.version,
102            display_name: scheme.display_name.clone(),
103            classes: scheme
104                .classes
105                .iter()
106                .map(RawAnnotationClass::from_class)
107                .collect(),
108            finding_sites: scheme
109                .finding_sites
110                .iter()
111                .map(ProfileCode::from_code)
112                .collect(),
113        }
114    }
115}
116
117#[derive(Debug, Serialize, Deserialize)]
118#[serde(deny_unknown_fields)]
119struct RawAnnotationClass {
120    id: String,
121    label: String,
122    geometry: AnnotationClassGeometry,
123    display_color: String,
124    category: ProfileCode,
125    property_type: ProfileCode,
126    #[serde(default, skip_serializing_if = "Vec::is_empty")]
127    property_type_modifiers: Vec<ProfileCode>,
128}
129
130impl RawAnnotationClass {
131    fn into_class(self) -> Result<AnnotationClass> {
132        AnnotationClass::new(
133            self.id,
134            self.label,
135            self.geometry,
136            parse_rgb(&self.display_color)?,
137            self.category.into_code()?,
138            self.property_type.into_code()?,
139            self.property_type_modifiers
140                .into_iter()
141                .map(ProfileCode::into_code)
142                .collect::<Result<Vec<_>>>()?,
143        )
144    }
145
146    fn from_class(class: &AnnotationClass) -> Self {
147        Self {
148            id: class.id.clone(),
149            label: class.label.clone(),
150            geometry: class.geometry,
151            display_color: format!(
152                "#{:02X}{:02X}{:02X}",
153                class.display_color[0], class.display_color[1], class.display_color[2]
154            ),
155            category: ProfileCode::from_code(&class.category),
156            property_type: ProfileCode::from_code(&class.property_type),
157            property_type_modifiers: class
158                .property_type_modifiers
159                .iter()
160                .map(ProfileCode::from_code)
161                .collect(),
162        }
163    }
164}