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