rvlib/tools_data/
brush_data.rs

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    // we might want to show this while it is being drawn,
62    // (line, cat_idx)
63    #[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    /// Returns the distance to the boundary of the mask
260    ///
261    /// *Arguments*:
262    /// p: in image coordinates
263    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        // we need this to check whether p is a foreground pixel in case
273        // it inside the bounding box of the canvas
274        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}