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 pixel_fixed: &l.pixel_fixed,
125 pixel_moving: &l.pixel_moving,
126 native_row_change: optional(&l.native_row_change),
127 native_col_change: optional(&l.native_col_change),
128 row_fixed: Some(&l.row_fixed),
129 row_moving: Some(&l.row_moving),
130 reference,
131 };
132 regions::finish(&input, native_width, native_height, factor)
133 }
134}
135
136pub fn manual_uncovered(rects: &[Rect], native_width: f64, native_height: f64) -> bool {
139 if rects.is_empty() {
140 return true;
141 }
142 let (nw, nh) = (native_width as i64, native_height as i64);
143 (0..nh).any(|y| (0..nw).any(|x| !rects.iter().any(|r| r.contains(x as f64, y as f64))))
144}
145
146fn manual_regions(
147 manual: &[ManualRegion],
148 mask_width: usize,
149 mask_height: usize,
150 native_width: f64,
151 native_height: f64,
152 factor: u32,
153) -> Vec<RegionOut> {
154 let ignored: Vec<Rect> = manual
155 .iter()
156 .filter(|r| r.kind == Kind::Ignore)
157 .map(|r| r.rect)
158 .collect();
159 let region =
160 |id: usize, name: String, kind: Kind, rect: Rect, exclusions: Vec<Rect>| RegionOut {
161 id: format!("layer-{id}"),
162 name,
163 kind,
164 rect,
165 cells: None,
166 mask: None,
167 mask_width,
168 mask_height,
169 manual: true,
170 unassigned: false,
171 exclusions,
172 crop: None,
173 solid: false,
174 factor,
175 band_side: None,
176 };
177 let mut out: Vec<RegionOut> = manual
178 .iter()
179 .enumerate()
180 .map(|(i, r)| {
181 let exclusions = manual[i + 1..]
182 .iter()
183 .map(|later| later.rect)
184 .chain(ignored.iter().copied())
185 .collect();
186 region(i, r.name.clone(), r.kind, r.rect, exclusions)
187 })
188 .collect();
189 let rects: Vec<Rect> = manual.iter().map(|r| r.rect).collect();
190 if manual_uncovered(&rects, native_width, native_height) {
191 let full = Rect {
192 x: 0.0,
193 y: 0.0,
194 width: native_width,
195 height: native_height,
196 };
197 let mut unassigned = region(
198 out.len(),
199 UNASSIGNED_NAME.to_string(),
200 Kind::Fixed,
201 full,
202 rects,
203 );
204 unassigned.unassigned = true;
205 out.push(unassigned);
206 }
207 out
208}
209
210pub fn label_atlas(
215 regions: &[RegionOut],
216 width: usize,
217 height: usize,
218) -> Option<(Vec<u8>, Vec<u32>)> {
219 let defs: Vec<AtlasRegion> = regions
220 .iter()
221 .map(|r| AtlasRegion {
222 rect: r.rect,
223 exclusions: r.exclusions.clone(),
224 crop: if r.manual {
225 r.crop
226 } else {
227 Some(r.crop.unwrap_or(r.rect))
228 },
229 solid: r.solid,
230 mask: r
231 .mask
232 .as_deref()
233 .filter(|_| !r.solid)
234 .map(|data| AtlasMask {
235 width: r.mask_width,
236 height: r.mask_height,
237 factor: r.factor,
238 data,
239 }),
240 })
241 .collect();
242 let mut labels = vec![0u8; width * height];
243 let counts = regions::label_atlas(&defs, width, height, &mut labels)?;
244 Some((labels, counts))
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use crate::kernels::layers::FieldMotion;
251
252 fn rect(x: f64, y: f64, width: f64, height: f64) -> Rect {
253 Rect {
254 x,
255 y,
256 width,
257 height,
258 }
259 }
260
261 fn manual(name: &str, kind: Kind, r: Rect) -> ManualRegion {
262 ManualRegion {
263 name: name.to_string(),
264 kind,
265 rect: r,
266 }
267 }
268
269 #[test]
273 fn manual_regions_exclude_later_and_ignored_regions() {
274 let a = rect(0.0, 0.0, 25.0, 30.0);
275 let b = rect(15.0, 0.0, 25.0, 30.0);
276 let c = rect(0.0, 20.0, 40.0, 10.0);
277 let learner = RegionLearner::new(10, 8);
278 let out = learner.finish(
279 40.0,
280 30.0,
281 &[
282 manual("A", Kind::Moving, a),
283 manual("B", Kind::Ignore, b),
284 manual("C", Kind::Fixed, c),
285 ],
286 4,
287 );
288 assert_eq!(out.len(), 3, "a, b and c cover every pixel");
289 assert_eq!(out[0].id, "layer-0");
290 assert_eq!(out[0].exclusions, vec![b, c, b]);
291 assert_eq!(out[1].exclusions, vec![c, b]);
292 assert_eq!(out[2].exclusions, vec![b]);
293 assert!(out.iter().all(|r| r.manual && !r.unassigned));
294 assert_eq!(
295 (out[2].mask_width, out[2].mask_height, out[2].factor),
296 (10, 8, 4)
297 );
298 }
299
300 #[test]
301 fn a_gap_adds_the_unassigned_region_over_the_whole_frame() {
302 let a = rect(0.0, 0.0, 10.0, 10.0);
303 let out = RegionLearner::new(10, 10).finish(40.0, 30.0, &[manual("A", Kind::Moving, a)], 1);
304 assert_eq!(out.len(), 2);
305 let u = &out[1];
306 assert!(u.unassigned && u.manual);
307 assert_eq!(u.id, "layer-1");
308 assert_eq!(u.name, UNASSIGNED_NAME);
309 assert_eq!(u.kind, Kind::Fixed);
310 assert_eq!(u.rect, rect(0.0, 0.0, 40.0, 30.0));
311 assert_eq!(u.exclusions, vec![a]);
312 }
313
314 #[test]
315 fn coverage_counts_every_native_pixel() {
316 assert!(manual_uncovered(&[], 4.0, 4.0));
317 assert!(!manual_uncovered(&[rect(0.0, 0.0, 4.0, 4.0)], 4.0, 4.0));
318 assert!(manual_uncovered(&[rect(0.0, 0.0, 3.0, 4.0)], 4.0, 4.0));
320 assert!(!manual_uncovered(
321 &[rect(0.0, 0.0, 2.0, 4.0), rect(2.0, 0.0, 2.0, 4.0)],
322 4.0,
323 4.0
324 ));
325 }
326
327 fn field<'a>(motions: &'a [FieldMotion], cells: &'a [u8], confidence: &'a [u8]) -> Field<'a> {
328 Field {
329 motions,
330 labels: cells,
331 confidence,
332 dynamic: cells,
333 cols: 1,
334 rows: 1,
335 difference: 5.0,
336 unknown: false,
337 }
338 }
339
340 #[test]
343 fn the_reference_follows_the_last_accumulated_native_frame() {
344 let moving = [FieldMotion {
345 x: 0.0,
346 y: 3.0,
347 support: 20,
348 confidence: 0.9,
349 }];
350 let still = [FieldMotion {
351 x: 0.0,
352 y: 0.0,
353 support: 20,
354 confidence: 0.9,
355 }];
356 let (cells, confidence) = ([0u8], [255u8]);
357 let gray = vec![0u8; 24 * 24];
358 let (a, b, c) = (vec![1u8; 8 * 4], vec![2u8; 8 * 4], vec![3u8; 8 * 4]);
359 let mut learner = RegionLearner::new(24, 24);
360 assert!(learner.add(&field(&moving, &cells, &confidence), &gray, &gray, None));
361 assert!(
362 learner.reference.is_none(),
363 "no native frames, no reference"
364 );
365 assert!(learner.add(
366 &field(&moving, &cells, &confidence),
367 &gray,
368 &gray,
369 Some((&a, &b, 4, 2))
370 ));
371 assert_eq!(learner.reference.as_ref().unwrap().data, b);
372 assert!(!learner.add(
373 &field(&still, &cells, &confidence),
374 &gray,
375 &gray,
376 Some((&b, &c, 4, 2))
377 ));
378 assert_eq!(
379 learner.reference.as_ref().unwrap().data,
380 b,
381 "an ignored field keeps the reference"
382 );
383 }
384
385 #[test]
386 fn the_atlas_gives_each_pixel_to_the_first_region_that_holds_it() {
387 let out = RegionLearner::new(4, 4).finish(
388 8.0,
389 4.0,
390 &[
391 manual("A", Kind::Moving, rect(0.0, 0.0, 6.0, 4.0)),
392 manual("B", Kind::Fixed, rect(4.0, 0.0, 4.0, 4.0)),
393 ],
394 2,
395 );
396 let (labels, counts) = label_atlas(&out, 8, 4).unwrap();
397 assert_eq!(
398 &labels[..8],
399 &[1, 1, 1, 1, 2, 2, 2, 2],
400 "A excludes the later B"
401 );
402 assert_eq!(counts, vec![0, 16, 16]);
403 }
404}