rvlib/tools_data/
brush_data.rs

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