1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4#[serde(rename_all = "camelCase")]
5pub struct InterpretResult {
6 pub summary: String,
7 pub diff_count: u32,
8 pub total_regions: usize,
9 pub regions: Vec<ChangeRegion>,
10 pub severity: ChangeSeverity,
11 pub diff_percentage: f64,
12 pub width: u32,
13 pub height: u32,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
17#[serde(rename_all = "camelCase")]
18pub struct ChangeRegion {
19 pub bbox: BoundingBox,
20 pub pixel_count: u32,
21 pub percentage: f64,
22 pub position: SpatialPosition,
23 pub shape: ChangeShape,
24 pub shape_stats: ShapeStats,
25 pub change_type: ChangeType,
26 pub signals: ClassificationSignals,
27 pub confidence: f32,
28 pub color_delta: ColorDeltaStats,
29 pub gradient: GradientStats,
30 pub chroma: ChromaStats,
31}
32
33#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
34#[serde(rename_all = "kebab-case")]
35pub enum ChangeType {
36 RenderingNoise,
37 ContentChange,
38 Addition,
39 Deletion,
40 Shift,
41 ColorChange,
42}
43
44#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
45#[serde(rename_all = "camelCase")]
46pub struct ClassificationSignals {
47 pub blends_with_bg_in_img1: bool,
48 pub blends_with_bg_in_img2: bool,
49 pub low_color_delta: bool,
50 pub low_edge_change: bool,
51 pub dense_fill: bool,
52 pub sparse_fill: bool,
53 pub tiny_region: bool,
54 pub edges_correlated: bool,
55 pub luminance_ncc: f32,
59 pub structure_asymmetry: f32,
63 pub bg_distance_img1: f32,
67 pub bg_distance_img2: f32,
69 pub confidence: f32,
70}
71
72#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
77#[serde(rename_all = "camelCase")]
78pub struct ChromaStats {
79 pub mean_abs_dy: f32,
81 pub mean_dy: f32,
84 pub mean_abs_di: f32,
86 pub mean_abs_dq: f32,
88 pub mean_abs_dc: f32,
90 pub chroma_cos: f32,
93 pub sat1: f32,
95 pub sat2: f32,
97 pub chroma_rough: f32,
101}
102
103#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
104#[serde(rename_all = "camelCase")]
105pub struct ColorDeltaStats {
106 pub mean_delta: f32,
107 pub max_delta: f32,
108 pub delta_stddev: f32,
111}
112
113#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
114#[serde(rename_all = "camelCase")]
115pub struct GradientStats {
116 pub edge_score: f32,
117 pub edge_score_img2: f32,
118 pub edge_correlation: f32,
119}
120
121#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
122#[serde(rename_all = "camelCase")]
123pub struct ShapeStats {
124 pub fill_ratio: f64,
125 pub border_ratio: f64,
126 pub inner_fill_ratio: f64,
127 pub center_density: f64,
128 pub row_occupancy: f64,
129 pub col_occupancy: f64,
130}
131
132#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
133#[serde(rename_all = "kebab-case")]
134pub enum ChangeShape {
135 SolidRegion,
136 ContourFrame,
137 SparseDistributed,
138 EdgeDominated,
139 MixedRegion,
140}
141
142#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
143pub struct BoundingBox {
144 pub x: u32,
145 pub y: u32,
146 pub width: u32,
147 pub height: u32,
148}
149
150impl BoundingBox {
151 pub fn expand(&mut self, px: u32, py: u32) {
153 let right = self.x + self.width;
154 let bottom = self.y + self.height;
155 if px < self.x {
156 self.width += self.x - px;
157 self.x = px;
158 }
159 if py < self.y {
160 self.height += self.y - py;
161 self.y = py;
162 }
163 if px + 1 > right {
164 self.width = px + 1 - self.x;
165 }
166 if py + 1 > bottom {
167 self.height = py + 1 - self.y;
168 }
169 }
170}
171
172#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
173#[serde(rename_all = "kebab-case")]
174pub enum ChangeSeverity {
175 Low,
176 Medium,
177 High,
178}
179
180#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
181#[serde(rename_all = "kebab-case")]
182pub enum SpatialPosition {
183 TopLeft,
184 Top,
185 TopRight,
186 Left,
187 Center,
188 Right,
189 BottomLeft,
190 Bottom,
191 BottomRight,
192}
193
194impl std::fmt::Display for SpatialPosition {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 match self {
197 Self::TopLeft => write!(f, "top-left"),
198 Self::Top => write!(f, "top"),
199 Self::TopRight => write!(f, "top-right"),
200 Self::Left => write!(f, "left"),
201 Self::Center => write!(f, "center"),
202 Self::Right => write!(f, "right"),
203 Self::BottomLeft => write!(f, "bottom-left"),
204 Self::Bottom => write!(f, "bottom"),
205 Self::BottomRight => write!(f, "bottom-right"),
206 }
207 }
208}
209
210impl std::fmt::Display for ChangeShape {
211 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
212 match self {
213 Self::SolidRegion => write!(f, "solid-region"),
214 Self::ContourFrame => write!(f, "contour-frame"),
215 Self::SparseDistributed => write!(f, "sparse-distributed"),
216 Self::EdgeDominated => write!(f, "edge-dominated"),
217 Self::MixedRegion => write!(f, "mixed-region"),
218 }
219 }
220}
221
222impl std::fmt::Display for ChangeSeverity {
223 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
224 match self {
225 Self::Low => write!(f, "low"),
226 Self::Medium => write!(f, "medium"),
227 Self::High => write!(f, "high"),
228 }
229 }
230}
231
232impl std::fmt::Display for ChangeType {
233 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
234 match self {
235 Self::RenderingNoise => write!(f, "rendering-noise"),
236 Self::ContentChange => write!(f, "content-change"),
237 Self::Addition => write!(f, "addition"),
238 Self::Deletion => write!(f, "deletion"),
239 Self::Shift => write!(f, "shift"),
240 Self::ColorChange => write!(f, "color-change"),
241 }
242 }
243}