wsi_dicom_annotations/annotations/scheme/
json.rs1use 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}