1use crate::kernels::geometry::Rect;
10use crate::kernels::layers::{Field, Learner};
11use crate::kernels::regions::{self, AtlasMask, AtlasRegion, FinishInput, Kind, RegionOut, Rgba};
12
13pub const UNASSIGNED_NAME: &str = "未指定区域 · 屏幕坐标观察";
16
17#[derive(Clone, Debug, PartialEq)]
19pub struct ManualRegion {
20 pub name: String,
21 pub kind: Kind,
22 pub rect: Rect,
23}
24
25struct NativeFrame {
27 data: Vec<u8>,
28 width: usize,
29 height: usize,
30}
31
32pub struct RegionLearner {
34 learner: Learner,
35 reference: Option<NativeFrame>,
36}
37
38impl RegionLearner {
39 pub fn new(width: usize, height: usize) -> RegionLearner {
41 RegionLearner {
42 learner: Learner::new(width, height),
43 reference: None,
44 }
45 }
46
47 pub fn accumulators(&self) -> &Learner {
49 &self.learner
50 }
51
52 pub fn add(
57 &mut self,
58 field: &Field<'_>,
59 prev: &[u8],
60 current: &[u8],
61 native: Option<(&[u8], &[u8], usize, usize)>,
62 ) -> bool {
63 let informative = self.learner.add(field, prev, current, native);
64 if let (true, Some((_, frame, width, height))) = (informative, native) {
65 match &mut self.reference {
66 Some(r) if r.width == width && r.height == height => r.data.copy_from_slice(frame),
67 slot => {
68 *slot = Some(NativeFrame {
69 data: frame.to_vec(),
70 width,
71 height,
72 })
73 }
74 }
75 }
76 informative
77 }
78
79 pub fn finish(
83 &self,
84 native_width: f64,
85 native_height: f64,
86 manual: &[ManualRegion],
87 factor: u32,
88 ) -> Vec<RegionOut> {
89 let l = &self.learner;
90 if !manual.is_empty() {
91 return manual_regions(
92 manual,
93 l.width,
94 l.height,
95 native_width,
96 native_height,
97 factor,
98 );
99 }
100 fn optional(values: &[f64]) -> Option<&[f64]> {
101 (!values.is_empty()).then_some(values)
102 }
103 let reference = self.reference.as_ref().map(|r| Rgba {
104 width: r.width,
105 height: r.height,
106 data: &r.data,
107 });
108 let input = FinishInput {
109 width: l.width,
110 height: l.height,
111 cell: l.cell,
112 informative_frames: l.informative_frames as f64,
113 native_frames: l.native_frames as f64,
114 row_change: &l.row_change,
115 col_change: &l.col_change,
116 col_moving: &l.col_moving,
117 col_mean: &l.col_mean,
118 col_gain: &l.col_gain,
119 horizontal_gain: &l.horizontal_gain,
120 split: &l.split,
121 evidence: &l.evidence,
122 activity: &l.activity,
123 observations: &l.observations,
124 native_row_change: optional(&l.native_row_change),
125 native_col_change: optional(&l.native_col_change),
126 reference,
127 };
128 regions::finish(&input, native_width, native_height, factor)
129 }
130}
131
132pub fn manual_uncovered(rects: &[Rect], native_width: f64, native_height: f64) -> bool {
135 if rects.is_empty() {
136 return true;
137 }
138 let (nw, nh) = (native_width as i64, native_height as i64);
139 (0..nh).any(|y| (0..nw).any(|x| !rects.iter().any(|r| r.contains(x as f64, y as f64))))
140}
141
142fn manual_regions(
143 manual: &[ManualRegion],
144 mask_width: usize,
145 mask_height: usize,
146 native_width: f64,
147 native_height: f64,
148 factor: u32,
149) -> Vec<RegionOut> {
150 let ignored: Vec<Rect> = manual
151 .iter()
152 .filter(|r| r.kind == Kind::Ignore)
153 .map(|r| r.rect)
154 .collect();
155 let region =
156 |id: usize, name: String, kind: Kind, rect: Rect, exclusions: Vec<Rect>| RegionOut {
157 id: format!("layer-{id}"),
158 name,
159 kind,
160 rect,
161 cells: None,
162 mask: None,
163 mask_width,
164 mask_height,
165 manual: true,
166 unassigned: false,
167 exclusions,
168 crop: None,
169 solid: false,
170 factor,
171 band_side: None,
172 };
173 let mut out: Vec<RegionOut> = manual
174 .iter()
175 .enumerate()
176 .map(|(i, r)| {
177 let exclusions = manual[i + 1..]
178 .iter()
179 .map(|later| later.rect)
180 .chain(ignored.iter().copied())
181 .collect();
182 region(i, r.name.clone(), r.kind, r.rect, exclusions)
183 })
184 .collect();
185 let rects: Vec<Rect> = manual.iter().map(|r| r.rect).collect();
186 if manual_uncovered(&rects, native_width, native_height) {
187 let full = Rect {
188 x: 0.0,
189 y: 0.0,
190 width: native_width,
191 height: native_height,
192 };
193 let mut unassigned = region(
194 out.len(),
195 UNASSIGNED_NAME.to_string(),
196 Kind::Fixed,
197 full,
198 rects,
199 );
200 unassigned.unassigned = true;
201 out.push(unassigned);
202 }
203 out
204}
205
206pub fn label_atlas(
211 regions: &[RegionOut],
212 width: usize,
213 height: usize,
214) -> Option<(Vec<u8>, Vec<u32>)> {
215 let defs: Vec<AtlasRegion> = regions
216 .iter()
217 .map(|r| AtlasRegion {
218 rect: r.rect,
219 exclusions: r.exclusions.clone(),
220 crop: if r.manual {
221 r.crop
222 } else {
223 Some(r.crop.unwrap_or(r.rect))
224 },
225 solid: r.solid,
226 mask: r
227 .mask
228 .as_deref()
229 .filter(|_| !r.solid)
230 .map(|data| AtlasMask {
231 width: r.mask_width,
232 height: r.mask_height,
233 factor: r.factor,
234 data,
235 }),
236 })
237 .collect();
238 let mut labels = vec![0u8; width * height];
239 let counts = regions::label_atlas(&defs, width, height, &mut labels)?;
240 Some((labels, counts))
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246 use crate::kernels::layers::FieldMotion;
247
248 fn rect(x: f64, y: f64, width: f64, height: f64) -> Rect {
249 Rect {
250 x,
251 y,
252 width,
253 height,
254 }
255 }
256
257 fn manual(name: &str, kind: Kind, r: Rect) -> ManualRegion {
258 ManualRegion {
259 name: name.to_string(),
260 kind,
261 rect: r,
262 }
263 }
264
265 #[test]
269 fn manual_regions_exclude_later_and_ignored_regions() {
270 let a = rect(0.0, 0.0, 25.0, 30.0);
271 let b = rect(15.0, 0.0, 25.0, 30.0);
272 let c = rect(0.0, 20.0, 40.0, 10.0);
273 let learner = RegionLearner::new(10, 8);
274 let out = learner.finish(
275 40.0,
276 30.0,
277 &[
278 manual("A", Kind::Moving, a),
279 manual("B", Kind::Ignore, b),
280 manual("C", Kind::Fixed, c),
281 ],
282 4,
283 );
284 assert_eq!(out.len(), 3, "a, b and c cover every pixel");
285 assert_eq!(out[0].id, "layer-0");
286 assert_eq!(out[0].exclusions, vec![b, c, b]);
287 assert_eq!(out[1].exclusions, vec![c, b]);
288 assert_eq!(out[2].exclusions, vec![b]);
289 assert!(out.iter().all(|r| r.manual && !r.unassigned));
290 assert_eq!(
291 (out[2].mask_width, out[2].mask_height, out[2].factor),
292 (10, 8, 4)
293 );
294 }
295
296 #[test]
297 fn a_gap_adds_the_unassigned_region_over_the_whole_frame() {
298 let a = rect(0.0, 0.0, 10.0, 10.0);
299 let out = RegionLearner::new(10, 10).finish(40.0, 30.0, &[manual("A", Kind::Moving, a)], 1);
300 assert_eq!(out.len(), 2);
301 let u = &out[1];
302 assert!(u.unassigned && u.manual);
303 assert_eq!(u.id, "layer-1");
304 assert_eq!(u.name, UNASSIGNED_NAME);
305 assert_eq!(u.kind, Kind::Fixed);
306 assert_eq!(u.rect, rect(0.0, 0.0, 40.0, 30.0));
307 assert_eq!(u.exclusions, vec![a]);
308 }
309
310 #[test]
311 fn coverage_counts_every_native_pixel() {
312 assert!(manual_uncovered(&[], 4.0, 4.0));
313 assert!(!manual_uncovered(&[rect(0.0, 0.0, 4.0, 4.0)], 4.0, 4.0));
314 assert!(manual_uncovered(&[rect(0.0, 0.0, 3.0, 4.0)], 4.0, 4.0));
316 assert!(!manual_uncovered(
317 &[rect(0.0, 0.0, 2.0, 4.0), rect(2.0, 0.0, 2.0, 4.0)],
318 4.0,
319 4.0
320 ));
321 }
322
323 fn field<'a>(motions: &'a [FieldMotion], cells: &'a [u8], confidence: &'a [u8]) -> Field<'a> {
324 Field {
325 motions,
326 labels: cells,
327 confidence,
328 dynamic: cells,
329 cols: 1,
330 rows: 1,
331 difference: 5.0,
332 unknown: false,
333 }
334 }
335
336 #[test]
339 fn the_reference_follows_the_last_accumulated_native_frame() {
340 let moving = [FieldMotion {
341 x: 0.0,
342 y: 3.0,
343 support: 20,
344 confidence: 0.9,
345 }];
346 let still = [FieldMotion {
347 x: 0.0,
348 y: 0.0,
349 support: 20,
350 confidence: 0.9,
351 }];
352 let (cells, confidence) = ([0u8], [255u8]);
353 let gray = vec![0u8; 24 * 24];
354 let (a, b, c) = (vec![1u8; 8 * 4], vec![2u8; 8 * 4], vec![3u8; 8 * 4]);
355 let mut learner = RegionLearner::new(24, 24);
356 assert!(learner.add(&field(&moving, &cells, &confidence), &gray, &gray, None));
357 assert!(
358 learner.reference.is_none(),
359 "no native frames, no reference"
360 );
361 assert!(learner.add(
362 &field(&moving, &cells, &confidence),
363 &gray,
364 &gray,
365 Some((&a, &b, 4, 2))
366 ));
367 assert_eq!(learner.reference.as_ref().unwrap().data, b);
368 assert!(!learner.add(
369 &field(&still, &cells, &confidence),
370 &gray,
371 &gray,
372 Some((&b, &c, 4, 2))
373 ));
374 assert_eq!(
375 learner.reference.as_ref().unwrap().data,
376 b,
377 "an ignored field keeps the reference"
378 );
379 }
380
381 #[test]
382 fn the_atlas_gives_each_pixel_to_the_first_region_that_holds_it() {
383 let out = RegionLearner::new(4, 4).finish(
384 8.0,
385 4.0,
386 &[
387 manual("A", Kind::Moving, rect(0.0, 0.0, 6.0, 4.0)),
388 manual("B", Kind::Fixed, rect(4.0, 0.0, 4.0, 4.0)),
389 ],
390 2,
391 );
392 let (labels, counts) = label_atlas(&out, 8, 4).unwrap();
393 assert_eq!(
394 &labels[..8],
395 &[1, 1, 1, 1, 2, 2, 2, 2],
396 "A excludes the later B"
397 );
398 assert_eq!(counts, vec![0, 16, 16]);
399 }
400}