Skip to main content

wsi_dicom_annotations/annotations/pathology_geojson/
preview.rs

1use 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}