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}
31
32#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
33#[serde(rename_all = "kebab-case")]
34pub enum ChangeType {
35 RenderingNoise,
36 ContentChange,
37 Addition,
38 Deletion,
39 Shift,
40 ColorChange,
41}
42
43#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
44#[serde(rename_all = "camelCase")]
45pub struct ClassificationSignals {
46 pub blends_with_bg_in_img1: bool,
47 pub blends_with_bg_in_img2: bool,
48 pub low_color_delta: bool,
49 pub low_edge_change: bool,
50 pub dense_fill: bool,
51 pub sparse_fill: bool,
52 pub tiny_region: bool,
53 pub edges_correlated: bool,
54 pub luminance_ncc: f32,
58 pub structure_asymmetry: f32,
62 pub confidence: f32,
63}
64
65#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
66#[serde(rename_all = "camelCase")]
67pub struct ColorDeltaStats {
68 pub mean_delta: f32,
69 pub max_delta: f32,
70 pub delta_stddev: f32,
73}
74
75#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
76#[serde(rename_all = "camelCase")]
77pub struct GradientStats {
78 pub edge_score: f32,
79 pub edge_score_img2: f32,
80 pub edge_correlation: f32,
81}
82
83#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq)]
84#[serde(rename_all = "camelCase")]
85pub struct ShapeStats {
86 pub fill_ratio: f64,
87 pub border_ratio: f64,
88 pub inner_fill_ratio: f64,
89 pub center_density: f64,
90 pub row_occupancy: f64,
91 pub col_occupancy: f64,
92}
93
94#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
95#[serde(rename_all = "kebab-case")]
96pub enum ChangeShape {
97 SolidRegion,
98 ContourFrame,
99 SparseDistributed,
100 EdgeDominated,
101 MixedRegion,
102}
103
104#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
105pub struct BoundingBox {
106 pub x: u32,
107 pub y: u32,
108 pub width: u32,
109 pub height: u32,
110}
111
112impl BoundingBox {
113 pub fn expand(&mut self, px: u32, py: u32) {
115 let right = self.x + self.width;
116 let bottom = self.y + self.height;
117 if px < self.x {
118 self.width += self.x - px;
119 self.x = px;
120 }
121 if py < self.y {
122 self.height += self.y - py;
123 self.y = py;
124 }
125 if px + 1 > right {
126 self.width = px + 1 - self.x;
127 }
128 if py + 1 > bottom {
129 self.height = py + 1 - self.y;
130 }
131 }
132}
133
134#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
135#[serde(rename_all = "kebab-case")]
136pub enum ChangeSeverity {
137 Low,
138 Medium,
139 High,
140}
141
142#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, Hash)]
143#[serde(rename_all = "kebab-case")]
144pub enum SpatialPosition {
145 TopLeft,
146 Top,
147 TopRight,
148 Left,
149 Center,
150 Right,
151 BottomLeft,
152 Bottom,
153 BottomRight,
154}
155
156impl std::fmt::Display for SpatialPosition {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 match self {
159 Self::TopLeft => write!(f, "top-left"),
160 Self::Top => write!(f, "top"),
161 Self::TopRight => write!(f, "top-right"),
162 Self::Left => write!(f, "left"),
163 Self::Center => write!(f, "center"),
164 Self::Right => write!(f, "right"),
165 Self::BottomLeft => write!(f, "bottom-left"),
166 Self::Bottom => write!(f, "bottom"),
167 Self::BottomRight => write!(f, "bottom-right"),
168 }
169 }
170}
171
172impl std::fmt::Display for ChangeShape {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 match self {
175 Self::SolidRegion => write!(f, "solid-region"),
176 Self::ContourFrame => write!(f, "contour-frame"),
177 Self::SparseDistributed => write!(f, "sparse-distributed"),
178 Self::EdgeDominated => write!(f, "edge-dominated"),
179 Self::MixedRegion => write!(f, "mixed-region"),
180 }
181 }
182}
183
184impl std::fmt::Display for ChangeSeverity {
185 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
186 match self {
187 Self::Low => write!(f, "low"),
188 Self::Medium => write!(f, "medium"),
189 Self::High => write!(f, "high"),
190 }
191 }
192}
193
194impl std::fmt::Display for ChangeType {
195 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
196 match self {
197 Self::RenderingNoise => write!(f, "rendering-noise"),
198 Self::ContentChange => write!(f, "content-change"),
199 Self::Addition => write!(f, "addition"),
200 Self::Deletion => write!(f, "deletion"),
201 Self::Shift => write!(f, "shift"),
202 Self::ColorChange => write!(f, "color-change"),
203 }
204 }
205}