Skip to main content

rvlib/tools_data/
brush_data.rs

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_rowmajor, rle_bb_to_image_colmajor, 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    // we might want to show this while it is being drawn,
65    // (line, cat_idx)
66    #[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_rowmajor(&self.mask, self.bb.w, self.bb.h);
226
227            let rle_im = trace_ok_warn(rle_bb_to_image_colmajor(&rle_bb, self.bb, shape_im));
228            Ok(rle_im.map(|rle_im| {
229                CocoSegmentation::Rle(CocoRle {
230                    counts: rle_im,
231                    // Coco stores the size as [height, width]
232                    size: (shape_im.h, shape_im.w),
233                    intensity: Some(self.intensity),
234                })
235            }))
236        } else {
237            Err(rverr!(
238                "bb {:?} not contained in image {shape_im:?}",
239                self.bb
240            ))
241        }
242    }
243    /// Returns the distance to the boundary of the mask
244    ///
245    /// *Arguments*:
246    /// p: in image coordinates
247    fn dist_to_boundary(&self, p: PtF) -> TPtF {
248        let mut min_dist = TPtF::MAX;
249        let to_coord = |x| {
250            if x > 0.0 { x as TPtI } else { TPtI::MAX }
251        };
252        // we need this to check whether p is a foreground pixel in case
253        // it inside the bounding box of the canvas
254        let point_pixel_inside = PtI {
255            x: to_coord(p.x - TPtF::from(self.bb.x)),
256            y: to_coord(p.y - TPtF::from(self.bb.y)),
257        };
258        let point_pixel_value = access_mask_rel(
259            &self.mask,
260            point_pixel_inside.x,
261            point_pixel_inside.y,
262            self.bb.w,
263            self.bb.h,
264        );
265        for y in 1..self.bb.h {
266            for x in 1..self.bb.w {
267                let neighbors_fg_mask = [
268                    access_mask_rel(&self.mask, x + 1, y, self.bb.w, self.bb.h),
269                    access_mask_rel(&self.mask, x - 1, y, self.bb.w, self.bb.h),
270                    access_mask_rel(&self.mask, x, y + 1, self.bb.w, self.bb.h),
271                    access_mask_rel(&self.mask, x, y - 1, self.bb.w, self.bb.h),
272                ];
273                if neighbors_fg_mask.iter().any(|&b| b != point_pixel_value) {
274                    let x = TPtF::from(x + self.bb.x);
275                    let y = TPtF::from(y + self.bb.y);
276                    let dist = p.dist_square(&PtF { x, y }).sqrt();
277                    if dist < min_dist {
278                        min_dist = dist;
279                    }
280                }
281            }
282        }
283        min_dist
284    }
285}
286
287implement_annotate!(BrushToolData);
288
289#[cfg(test)]
290use rvimage_domain::{BbI, Line};
291#[test]
292fn test_canvas() {
293    let orig_shape = ShapeI::new(30, 30);
294    let bl = BrushLine {
295        line: Line {
296            points: vec![PtF { x: 5.0, y: 5.0 }, PtF { x: 15.0, y: 15.0 }],
297        },
298        intensity: 0.5,
299        thickness: 3.0,
300    };
301    let canv = Canvas::new(&bl, orig_shape, None).unwrap();
302    assert!(canv.contains(PtF { x: 5.0, y: 5.0 }));
303    assert!(!canv.contains(PtF { x: 0.0, y: 0.0 }));
304    assert!(canv.contains(PtF { x: 14.9, y: 14.9 }));
305    assert!(!canv.contains(PtF { x: 0.0, y: 9.9 }));
306    assert!(!canv.contains(PtF { x: 15.0, y: 15.0 }));
307    let d = canv.dist_to_boundary(PtF { x: 5.0, y: 5.0 });
308    assert!((d - 1.0).abs() < 1e-8);
309    let dist = canv.dist_to_boundary(PtF { x: 5.0, y: 15.0 });
310    assert!(5.0 < dist && dist < 7.0);
311    for y in canv.bb.y_range() {
312        for x in canv.bb.x_range() {
313            _ = access_mask_abs(&canv.mask, canv.bb, PtI { x, y });
314        }
315    }
316    let canv = Canvas::new(&bl, orig_shape, None).unwrap();
317    let canv_rot = canv.clone().rot90_with_image_ntimes(orig_shape, 1).unwrap();
318    let bl_rot = BrushLine {
319        line: Line {
320            points: vec![
321                PtF { x: 5.0, y: 5.0 }.rot90_with_image_ntimes(orig_shape, 1),
322                PtF { x: 15.0, y: 15.0 }.rot90_with_image_ntimes(orig_shape, 1),
323            ],
324        },
325        intensity: 0.5,
326        thickness: 3.0,
327    };
328    let canv_rot_ref = Canvas::new(&bl_rot, orig_shape, None).unwrap();
329    let inter = canv_rot
330        .enclosing_bb()
331        .intersect(canv_rot_ref.enclosing_bb());
332    assert!(
333        (inter.w - canv_rot.enclosing_bb().w).abs() <= 1.0
334            && (inter.h - canv_rot.enclosing_bb().h).abs() <= 1.0
335    );
336    let canv = Canvas::new(&bl, orig_shape, None).unwrap();
337    assert_eq!(
338        canv,
339        canv.clone().rot90_with_image_ntimes(orig_shape, 0).unwrap()
340    );
341}
342
343#[test]
344fn test_canvas_rot() {
345    let canv = Canvas {
346        mask: vec![0, 0, 0, 1],
347        bb: BbI::from_arr(&[0, 0, 4, 1]),
348        intensity: 0.5,
349    };
350    let canv_rot = canv
351        .clone()
352        .rot90_with_image_ntimes(ShapeI::new(4, 1), 1)
353        .unwrap();
354    let canv_ref = Canvas {
355        mask: vec![1, 0, 0, 0],
356        bb: BbI::from_arr(&[0, 0, 1, 4]),
357        intensity: 0.5,
358    };
359    assert_eq!(canv_rot, canv_ref);
360}