wsi_dicom_annotations/annotations/pathology_geojson/
preview.rs1use super::feature::ProfiledFeature;
2use super::geometry::ProfiledGeometry;
3use crate::Point2;
4
5#[derive(Debug, Clone)]
6pub struct PathologyPreview {
7 features: Vec<PathologyPreviewFeature>,
8}
9
10impl PathologyPreview {
11 pub(super) fn from_owned_features(features: Vec<ProfiledFeature>) -> Self {
12 Self {
13 features: features
14 .into_iter()
15 .map(PathologyPreviewFeature::from)
16 .collect(),
17 }
18 }
19
20 #[must_use]
21 pub fn features(&self) -> &[PathologyPreviewFeature] {
22 &self.features
23 }
24}
25
26#[derive(Debug, Clone)]
27pub struct PathologyPreviewFeature {
28 tracking_id: String,
29 label: String,
30 classification: String,
31 recommended_display_cielab: [u16; 3],
32 bounds: [f64; 4],
33 geometry: PathologyPreviewGeometry,
34}
35
36impl PathologyPreviewFeature {
37 #[must_use]
38 pub fn tracking_id(&self) -> &str {
39 &self.tracking_id
40 }
41
42 #[must_use]
43 pub fn label(&self) -> &str {
44 &self.label
45 }
46
47 #[must_use]
48 pub fn classification(&self) -> &str {
49 &self.classification
50 }
51
52 #[must_use]
53 pub const fn recommended_display_cielab(&self) -> [u16; 3] {
54 self.recommended_display_cielab
55 }
56
57 #[must_use]
58 pub const fn bounds(&self) -> [f64; 4] {
59 self.bounds
60 }
61
62 #[must_use]
63 pub const fn geometry(&self) -> &PathologyPreviewGeometry {
64 &self.geometry
65 }
66}
67
68impl From<ProfiledFeature> for PathologyPreviewFeature {
69 fn from(feature: ProfiledFeature) -> Self {
70 let bounds = geometry_bounds(&feature.geometry);
71 let geometry = match feature.geometry {
72 ProfiledGeometry::Points(points) => PathologyPreviewGeometry::Points(points),
73 ProfiledGeometry::Lines(lines) => PathologyPreviewGeometry::Lines(lines),
74 ProfiledGeometry::Polygons(polygons) => PathologyPreviewGeometry::Polygons(
75 polygons
76 .into_iter()
77 .map(|polygon| PathologyPreviewPolygon {
78 exterior: polygon.outer,
79 holes: polygon.holes,
80 })
81 .collect(),
82 ),
83 };
84 Self {
85 tracking_id: feature.tracking_id,
86 label: feature.name,
87 classification: feature.classification,
88 recommended_display_cielab: feature.semantics.finding.recommended_display_cielab(),
89 bounds,
90 geometry,
91 }
92 }
93}
94
95fn geometry_bounds(geometry: &ProfiledGeometry) -> [f64; 4] {
96 let mut bounds = [
97 f64::INFINITY,
98 f64::INFINITY,
99 f64::NEG_INFINITY,
100 f64::NEG_INFINITY,
101 ];
102 let mut observe = |point: &Point2| {
103 bounds[0] = bounds[0].min(point.x);
104 bounds[1] = bounds[1].min(point.y);
105 bounds[2] = bounds[2].max(point.x);
106 bounds[3] = bounds[3].max(point.y);
107 };
108 match geometry {
109 ProfiledGeometry::Points(points) => points.iter().for_each(&mut observe),
110 ProfiledGeometry::Lines(lines) => lines.iter().flatten().for_each(&mut observe),
111 ProfiledGeometry::Polygons(polygons) => polygons.iter().for_each(|polygon| {
112 polygon.outer.iter().for_each(&mut observe);
113 polygon.holes.iter().flatten().for_each(&mut observe);
114 }),
115 }
116 bounds
117}
118
119#[derive(Debug, Clone)]
120pub enum PathologyPreviewGeometry {
121 Points(Vec<Point2>),
122 Lines(Vec<Vec<Point2>>),
123 Polygons(Vec<PathologyPreviewPolygon>),
124}
125
126#[derive(Debug, Clone)]
127pub struct PathologyPreviewPolygon {
128 exterior: Vec<Point2>,
129 holes: Vec<Vec<Point2>>,
130}
131
132impl PathologyPreviewPolygon {
133 #[must_use]
134 pub fn exterior(&self) -> &[Point2] {
135 &self.exterior
136 }
137
138 #[must_use]
139 pub fn holes(&self) -> &[Vec<Point2>] {
140 &self.holes
141 }
142}