1#[cfg(test)]
2use super::annotations::InstanceAnnotations;
3use super::{
4 annotations::{BrushAnnotations, ClipboardData},
5 core::{
6 self, AccessInstanceData, AnnotationsMap, CocoRle, CocoSegmentation, ExportAsCoco,
7 LabelInfo,
8 },
9 InstanceAnnotate, InstanceExportData,
10};
11use crate::{cfg::ExportPath, result::trace_ok_warn, BrushLine};
12use crate::{implement_annotate, implement_annotations_getters};
13use rvimage_domain::{
14 access_mask_abs, access_mask_rel, mask_to_rle, rle_bb_to_image, rverr, BbF, Canvas, PtF, PtI,
15 PtS, RvResult, ShapeI, TPtF, TPtI, TPtS, BB,
16};
17
18use serde::{Deserialize, Serialize};
19
20pub type BrushAnnoMap = AnnotationsMap<Canvas>;
21
22pub const MAX_THICKNESS: f64 = 100.0;
23pub const MIN_THICKNESS: f64 = 1.0;
24pub const MAX_INTENSITY: f64 = 1.0;
25pub const MIN_INTENSITY: f64 = 0.01;
26const fn default_alpha() -> u8 {
27 255
28}
29const fn default_perfilecrowd() -> bool {
30 false
31}
32#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
33pub struct Options {
34 pub thickness: TPtF,
35 pub intensity: TPtF,
36 #[serde(skip)]
37 pub is_selection_change_needed: bool,
38 #[serde(skip)]
39 pub core: core::Options,
40 #[serde(default = "default_alpha")]
41 pub fill_alpha: u8,
42 #[serde(default = "default_perfilecrowd")]
43 pub per_file_crowd: bool,
44}
45impl Default for Options {
46 fn default() -> Self {
47 Self {
48 thickness: 15.0,
49 intensity: 0.5,
50 is_selection_change_needed: false,
51 core: core::Options::default(),
52 fill_alpha: default_alpha(),
53 per_file_crowd: default_perfilecrowd(),
54 }
55 }
56}
57
58#[derive(Serialize, Clone, Debug, PartialEq, Default)]
59pub struct BrushToolData {
60 pub annotations_map: BrushAnnoMap,
61 #[serde(skip)]
64 pub tmp_line: Option<(BrushLine, usize)>,
65 pub options: Options,
66 pub label_info: LabelInfo,
67 #[serde(skip)]
68 pub clipboard: Option<ClipboardData<Canvas>>,
69 pub coco_file: ExportPath,
70}
71impl BrushToolData {
72 implement_annotations_getters!(BrushAnnotations);
73 pub fn from_coco_export_data(input_data: InstanceExportData<Canvas>) -> RvResult<Self> {
74 let label_info = input_data.label_info()?;
75 let mut out_data = Self {
76 tmp_line: None,
77
78 label_info,
79 annotations_map: AnnotationsMap::new(),
80 clipboard: None,
81 options: Options {
82 core: core::Options {
83 visible: true,
84 ..Default::default()
85 },
86 ..Default::default()
87 },
88 coco_file: input_data.coco_file,
89 };
90 out_data.set_annotations_map(
91 input_data
92 .annotations
93 .into_iter()
94 .map(|(s, (canvases, cat_ids, dims))| {
95 (
96 s,
97 (BrushAnnotations::from_elts_cats(canvases, cat_ids), dims),
98 )
99 })
100 .collect(),
101 )?;
102 Ok(out_data)
103 }
104}
105impl Eq for BrushToolData {}
106
107impl<'de> Deserialize<'de> for BrushToolData {
108 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
109 where
110 D: serde::Deserializer<'de>,
111 {
112 #[derive(Deserialize)]
113 struct BrushToolDataDe {
114 annotations_map: BrushAnnoMap,
115 options: Options,
116 label_info: LabelInfo,
117 coco_file: Option<ExportPath>,
118 }
119
120 let read = BrushToolDataDe::deserialize(deserializer)?;
121 Ok(Self {
122 annotations_map: read.annotations_map,
123 tmp_line: None,
124 options: read.options,
125 label_info: read.label_info,
126 clipboard: None,
127 coco_file: read.coco_file.unwrap_or_default(),
128 })
129 }
130}
131
132impl AccessInstanceData<Canvas> for BrushToolData {
133 fn annotations_map(&self) -> &AnnotationsMap<Canvas> {
134 &self.annotations_map
135 }
136 fn label_info(&self) -> &LabelInfo {
137 &self.label_info
138 }
139}
140impl ExportAsCoco<Canvas> for BrushToolData {
141 fn cocofile_conn(&self) -> ExportPath {
142 self.coco_file.clone()
143 }
144 fn separate_data(self) -> (core::Options, LabelInfo, AnnotationsMap<Canvas>, ExportPath) {
145 (
146 self.options.core,
147 self.label_info,
148 self.annotations_map,
149 self.coco_file,
150 )
151 }
152 fn core_options_mut(&mut self) -> &mut core::Options {
153 &mut self.options.core
154 }
155 fn new(
156 options: core::Options,
157 label_info: LabelInfo,
158 anno_map: AnnotationsMap<Canvas>,
159 export_path: ExportPath,
160 ) -> Self {
161 Self {
162 annotations_map: anno_map,
163 tmp_line: None,
164 options: Options {
165 core: options,
166 ..Default::default()
167 },
168 label_info,
169 clipboard: None,
170 coco_file: export_path,
171 }
172 }
173 fn set_annotations_map(&mut self, map: AnnotationsMap<Canvas>) -> RvResult<()> {
174 for (_, (annos, _)) in map.iter() {
175 for cat_idx in annos.cat_idxs() {
176 let len = self.label_info.len();
177 if *cat_idx >= len {
178 return Err(rverr!(
179 "cat idx {cat_idx} does not have a label, out of bounds, {len}"
180 ));
181 }
182 }
183 }
184 self.annotations_map = map;
185 Ok(())
186 }
187 fn set_labelinfo(&mut self, info: LabelInfo) {
188 self.label_info = info;
189 }
190 #[cfg(test)]
191 fn anno_iter(&self) -> impl Iterator<Item = (&String, &(InstanceAnnotations<Canvas>, ShapeI))> {
192 self.anno_iter()
193 }
194}
195
196impl InstanceAnnotate for Canvas {
197 fn is_contained_in_image(&self, shape: crate::ShapeI) -> bool {
198 self.bb.is_contained_in_image(shape)
199 }
200 fn contains<P>(&self, point: P) -> bool
201 where
202 P: Into<PtF>,
203 {
204 let p_tmp: PtF = point.into();
205 let p_idx: PtI = p_tmp.into();
206 access_mask_abs(&self.mask, self.bb, p_idx) > 0
207 }
208 fn enclosing_bb(&self) -> BbF {
209 self.bb.into()
210 }
211 fn rot90_with_image_ntimes(self, shape: ShapeI, n: u8) -> RvResult<Self> {
212 let bb = self.bb;
213 let bb_s: BB<TPtS> = BB::from(self.bb);
214 let bb_rot = bb_s.rot90_with_image_ntimes(shape, n);
215 if bb_rot.x < 0 || bb_rot.y < 0 {
216 return Err(rverr!("rotated bb {bb_rot:?} has negative coordinates",));
217 }
218 let mut new_mask = self.mask.clone();
219 for y in 0..bb.h {
220 for x in 0..bb.w {
221 let p_mask = PtI { x, y };
222 let p_im = p_mask + bb.min();
223 let p_im_rot = PtS::from(p_im).rot90_with_image_ntimes(shape, n);
224 let p_newmask = p_im_rot - bb_rot.min();
225 let p_newmask: PtI = p_newmask.into();
226 new_mask[p_newmask.y as usize * bb_rot.w as usize + p_newmask.x as usize] =
227 self.mask[p_mask.y as usize * bb.w as usize + p_mask.x as usize];
228 }
229 }
230 Ok(Self {
231 mask: new_mask,
232 bb: bb_rot.into(),
233 intensity: self.intensity,
234 })
235 }
236 fn to_cocoseg(
237 &self,
238 shape_im: ShapeI,
239 _is_export_absolute: bool,
240 ) -> RvResult<Option<core::CocoSegmentation>> {
241 if self.bb.is_contained_in_image(shape_im) {
242 let rle_bb = mask_to_rle(&self.mask, self.bb.w, self.bb.h);
243
244 let rle_im = trace_ok_warn(rle_bb_to_image(&rle_bb, self.bb, shape_im));
245 Ok(rle_im.map(|rle_im| {
246 CocoSegmentation::Rle(CocoRle {
247 counts: rle_im,
248 size: (shape_im.w, shape_im.h),
249 intensity: Some(self.intensity),
250 })
251 }))
252 } else {
253 Err(rverr!(
254 "bb {:?} not contained in image {shape_im:?}",
255 self.bb
256 ))
257 }
258 }
259 fn dist_to_boundary(&self, p: PtF) -> TPtF {
264 let mut min_dist = TPtF::MAX;
265 let to_coord = |x| {
266 if x > 0.0 {
267 x as TPtI
268 } else {
269 TPtI::MAX
270 }
271 };
272 let point_pixel_inside = PtI {
275 x: to_coord(p.x - TPtF::from(self.bb.x)),
276 y: to_coord(p.y - TPtF::from(self.bb.y)),
277 };
278 let point_pixel_value = access_mask_rel(
279 &self.mask,
280 point_pixel_inside.x,
281 point_pixel_inside.y,
282 self.bb.w,
283 self.bb.h,
284 );
285 for y in 1..self.bb.h {
286 for x in 1..self.bb.w {
287 let neighbors_fg_mask = [
288 access_mask_rel(&self.mask, x + 1, y, self.bb.w, self.bb.h),
289 access_mask_rel(&self.mask, x - 1, y, self.bb.w, self.bb.h),
290 access_mask_rel(&self.mask, x, y + 1, self.bb.w, self.bb.h),
291 access_mask_rel(&self.mask, x, y - 1, self.bb.w, self.bb.h),
292 ];
293 if neighbors_fg_mask.iter().any(|&b| b != point_pixel_value) {
294 let x = TPtF::from(x + self.bb.x);
295 let y = TPtF::from(y + self.bb.y);
296 let dist = p.dist_square(&PtF { x, y }).sqrt();
297 if dist < min_dist {
298 min_dist = dist;
299 }
300 }
301 }
302 }
303 min_dist
304 }
305}
306
307implement_annotate!(BrushToolData);
308
309#[cfg(test)]
310use rvimage_domain::{BbI, Line};
311#[test]
312fn test_canvas() {
313 let orig_shape = ShapeI::new(30, 30);
314 let bl = BrushLine {
315 line: Line {
316 points: vec![PtF { x: 5.0, y: 5.0 }, PtF { x: 15.0, y: 15.0 }],
317 },
318 intensity: 0.5,
319 thickness: 3.0,
320 };
321 let canv = Canvas::new(&bl, orig_shape, None).unwrap();
322 assert!(canv.contains(PtF { x: 5.0, y: 5.0 }));
323 assert!(!canv.contains(PtF { x: 0.0, y: 0.0 }));
324 assert!(canv.contains(PtF { x: 14.9, y: 14.9 }));
325 assert!(!canv.contains(PtF { x: 0.0, y: 9.9 }));
326 assert!(!canv.contains(PtF { x: 15.0, y: 15.0 }));
327 let d = canv.dist_to_boundary(PtF { x: 5.0, y: 5.0 });
328 assert!((d - 1.0).abs() < 1e-8);
329 let dist = canv.dist_to_boundary(PtF { x: 5.0, y: 15.0 });
330 assert!(5.0 < dist && dist < 7.0);
331 for y in canv.bb.y_range() {
332 for x in canv.bb.x_range() {
333 _ = access_mask_abs(&canv.mask, canv.bb, PtI { x, y });
334 }
335 }
336 let canv = Canvas::new(&bl, orig_shape, None).unwrap();
337 let canv_rot = canv.clone().rot90_with_image_ntimes(orig_shape, 1).unwrap();
338 let bl_rot = BrushLine {
339 line: Line {
340 points: vec![
341 PtF { x: 5.0, y: 5.0 }.rot90_with_image_ntimes(orig_shape, 1),
342 PtF { x: 15.0, y: 15.0 }.rot90_with_image_ntimes(orig_shape, 1),
343 ],
344 },
345 intensity: 0.5,
346 thickness: 3.0,
347 };
348 let canv_rot_ref = Canvas::new(&bl_rot, orig_shape, None).unwrap();
349 let inter = canv_rot
350 .enclosing_bb()
351 .intersect(canv_rot_ref.enclosing_bb());
352 assert!(
353 (inter.w - canv_rot.enclosing_bb().w).abs() <= 1.0
354 && (inter.h - canv_rot.enclosing_bb().h).abs() <= 1.0
355 );
356 let canv = Canvas::new(&bl, orig_shape, None).unwrap();
357 assert_eq!(
358 canv,
359 canv.clone().rot90_with_image_ntimes(orig_shape, 0).unwrap()
360 );
361}
362
363#[test]
364fn test_canvas_rot() {
365 let canv = Canvas {
366 mask: vec![0, 0, 0, 1],
367 bb: BbI::from_arr(&[0, 0, 4, 1]),
368 intensity: 0.5,
369 };
370 let canv_rot = canv
371 .clone()
372 .rot90_with_image_ntimes(ShapeI::new(4, 1), 1)
373 .unwrap();
374 let canv_ref = Canvas {
375 mask: vec![1, 0, 0, 0],
376 bb: BbI::from_arr(&[0, 0, 1, 4]),
377 intensity: 0.5,
378 };
379 assert_eq!(canv_rot, canv_ref);
380}