rvlib/tools_data/
bbox_data.rs

1use std::iter;
2
3use serde::{Deserialize, Serialize};
4
5#[cfg(test)]
6use super::annotations::InstanceAnnotations;
7use super::{
8    annotations::{BboxAnnotations, ClipboardData},
9    core::{
10        AnnotationsMap, CocoSegmentation, ExportAsCoco, InstanceExportData, LabelInfo,
11        OUTLINE_THICKNESS_CONVERSION,
12    },
13    InstanceAnnotate,
14};
15use crate::{
16    cfg::ExportPath,
17    file_util, implement_annotate, implement_annotations_getters,
18    tools_data::{annotations::SplitMode, core},
19    GeoFig,
20};
21use rvimage_domain::{rverr, BbF, Circle, PtF, RvResult, ShapeI, TPtF};
22
23/// filename -> (annotations per file, file dimensions)
24pub type BboxAnnoMap = AnnotationsMap<GeoFig>;
25
26#[derive(Clone, Copy, Deserialize, Serialize, Debug, PartialEq, Eq)]
27pub struct Options {
28    #[serde(skip)]
29    pub core: core::Options,
30    pub split_mode: SplitMode,
31    pub fill_alpha: u8,
32    pub outline_alpha: u8,
33    pub outline_thickness: u16,
34    pub drawing_distance: u8,
35}
36impl Default for Options {
37    fn default() -> Self {
38        Self {
39            core: core::Options::default(),
40            split_mode: SplitMode::default(),
41            fill_alpha: 30,
42            outline_alpha: 255,
43            outline_thickness: OUTLINE_THICKNESS_CONVERSION as u16,
44            drawing_distance: 10,
45        }
46    }
47}
48#[derive(Deserialize, Serialize, Clone, Debug, PartialEq)]
49pub struct BboxToolData {
50    pub label_info: LabelInfo,
51    pub annotations_map: BboxAnnoMap,
52    #[serde(skip)]
53    pub clipboard: Option<ClipboardData<GeoFig>>,
54    pub options: Options,
55    pub coco_file: ExportPath,
56    #[serde(skip)]
57    pub highlight_circles: Vec<Circle>,
58}
59
60impl BboxToolData {
61    implement_annotations_getters!(BboxAnnotations);
62
63    pub fn separate_data(self) -> (LabelInfo, BboxAnnoMap, ExportPath) {
64        (self.label_info, self.annotations_map, self.coco_file)
65    }
66
67    pub fn from_coco_export_data(input_data: InstanceExportData<GeoFig>) -> RvResult<Self> {
68        let label_info = input_data.label_info()?;
69        let mut out_data = Self {
70            label_info,
71            annotations_map: AnnotationsMap::new(),
72            clipboard: None,
73            options: Options {
74                core: core::Options {
75                    visible: true,
76                    ..Default::default()
77                },
78                ..Default::default()
79            },
80            coco_file: input_data.coco_file,
81            highlight_circles: vec![],
82        };
83        out_data.set_annotations_map(
84            input_data
85                .annotations
86                .into_iter()
87                .map(|(s, (geos, cat_ids, dims))| {
88                    (s, (BboxAnnotations::from_elts_cats(geos, cat_ids), dims))
89                })
90                .collect(),
91        )?;
92        Ok(out_data)
93    }
94
95    pub fn retain_fileannos_in_folder(&mut self, folder: &str) {
96        self.annotations_map
97            .retain(|f, _| file_util::url_encode(f).starts_with(folder));
98    }
99
100    pub fn new() -> Self {
101        let label_info = LabelInfo::default();
102
103        BboxToolData {
104            label_info,
105            annotations_map: AnnotationsMap::new(),
106            clipboard: None,
107            options: Options {
108                core: core::Options {
109                    visible: true,
110                    ..Default::default()
111                },
112                ..Default::default()
113            },
114            coco_file: ExportPath::default(),
115            highlight_circles: vec![],
116        }
117    }
118
119    pub fn set_annotations_map(&mut self, map: BboxAnnoMap) -> RvResult<()> {
120        for (_, (annos, _)) in map.iter() {
121            for cat_idx in annos.cat_idxs() {
122                let len = self.label_info.len();
123                if *cat_idx >= len {
124                    return Err(rverr!(
125                        "cat idx {} does not have a label, out of bounds, {}",
126                        cat_idx,
127                        len
128                    ));
129                }
130            }
131        }
132        self.annotations_map = map;
133        Ok(())
134    }
135}
136
137implement_annotate!(BboxToolData);
138
139impl Default for BboxToolData {
140    fn default() -> Self {
141        Self::new()
142    }
143}
144
145impl ExportAsCoco<GeoFig> for BboxToolData {
146    fn cocofile_conn(&self) -> ExportPath {
147        self.coco_file.clone()
148    }
149    fn separate_data(self) -> (core::Options, LabelInfo, AnnotationsMap<GeoFig>, ExportPath) {
150        (
151            self.options.core,
152            self.label_info,
153            self.annotations_map,
154            self.coco_file,
155        )
156    }
157    fn label_info(&self) -> &LabelInfo {
158        &self.label_info
159    }
160    #[cfg(test)]
161    fn anno_iter(&self) -> impl Iterator<Item = (&String, &(InstanceAnnotations<GeoFig>, ShapeI))> {
162        self.anno_iter()
163    }
164    fn core_options_mut(&mut self) -> &mut core::Options {
165        &mut self.options.core
166    }
167    fn new(
168        options: core::Options,
169        label_info: LabelInfo,
170        anno_map: AnnotationsMap<GeoFig>,
171        export_path: ExportPath,
172    ) -> Self {
173        Self {
174            label_info,
175            annotations_map: anno_map,
176            clipboard: None,
177            options: Options {
178                core: options,
179                ..Default::default()
180            },
181            coco_file: export_path,
182            highlight_circles: vec![],
183        }
184    }
185    fn set_annotations_map(&mut self, map: AnnotationsMap<GeoFig>) -> RvResult<()> {
186        self.set_annotations_map(map)
187    }
188    fn set_labelinfo(&mut self, info: LabelInfo) {
189        self.label_info = info;
190    }
191}
192
193impl InstanceAnnotate for GeoFig {
194    fn rot90_with_image_ntimes(self, shape: ShapeI, n: u8) -> RvResult<Self> {
195        Ok(match self {
196            Self::BB(bb) => Self::BB(bb.rot90_with_image_ntimes(shape, n)),
197            Self::Poly(poly) => Self::Poly(poly.rot90_with_image_ntimes(shape, n)),
198        })
199    }
200    fn is_contained_in_image(&self, shape: ShapeI) -> bool {
201        match self {
202            Self::BB(bb) => bb.is_contained_in_image(shape),
203            Self::Poly(poly) => poly.is_contained_in_image(shape),
204        }
205    }
206    fn enclosing_bb(&self) -> BbF {
207        match self {
208            Self::BB(bb) => *bb,
209            Self::Poly(poly) => poly.enclosing_bb(),
210        }
211    }
212    fn contains<P>(&self, point: P) -> bool
213    where
214        P: Into<PtF>,
215    {
216        match self {
217            Self::BB(bb) => bb.contains(point.into()),
218            Self::Poly(poly) => poly.contains(point),
219        }
220    }
221    fn dist_to_boundary(&self, point: PtF) -> TPtF {
222        match self {
223            Self::BB(bb) => bb.distance_to_boundary(point),
224            Self::Poly(poly) => poly.distance_to_boundary(point),
225        }
226    }
227    fn to_cocoseg(
228        &self,
229        shape_im: ShapeI,
230        is_export_absolute: bool,
231    ) -> RvResult<Option<core::CocoSegmentation>> {
232        Ok(Some(CocoSegmentation::Polygon(vec![
233            if is_export_absolute {
234                self.points()
235            } else {
236                self.points_normalized(TPtF::from(shape_im.w), TPtF::from(shape_im.h))
237            }
238            .iter()
239            .flat_map(|p| iter::once(p.x).chain(iter::once(p.y)))
240            .collect::<Vec<_>>(),
241        ])))
242    }
243}