rvlib/tools_data/
core.rs

1use serde::de::DeserializeOwned;
2use serde::{Deserialize, Serialize};
3use std::collections::HashMap;
4use std::fmt::{Debug, Display};
5use tracing::info;
6
7use crate::{cfg::ExportPath, util::Visibility, ShapeI};
8use rvimage_domain::{
9    rle_image_to_bb, rle_to_mask, rverr, Canvas, GeoFig, Point, Polygon, RvResult,
10};
11use rvimage_domain::{BbF, PtF, TPtF, TPtI};
12
13use super::annotations::InstanceAnnotations;
14use super::label_map::LabelMap;
15
16pub const OUTLINE_THICKNESS_CONVERSION: TPtF = 10.0;
17
18const DEFAULT_LABEL: &str = "rvimage_fg";
19
20fn color_dist(c1: [u8; 3], c2: [u8; 3]) -> f32 {
21    let square_d = |i| (f32::from(c1[i]) - f32::from(c2[i])).powi(2);
22    (square_d(0) + square_d(1) + square_d(2)).sqrt()
23}
24
25#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
26pub enum ImportMode {
27    Merge,
28    #[default]
29    Replace,
30}
31
32#[derive(Default, Clone, Copy, Debug, PartialEq, Eq)]
33pub struct ImportExportTrigger {
34    export_triggered: bool,
35    import_triggered: bool,
36    import_mode: ImportMode,
37}
38impl ImportExportTrigger {
39    pub fn import_triggered(self) -> bool {
40        self.import_triggered
41    }
42    pub fn import_mode(self) -> ImportMode {
43        self.import_mode
44    }
45    pub fn export_triggered(self) -> bool {
46        self.export_triggered
47    }
48    pub fn untrigger_export(&mut self) {
49        self.export_triggered = false;
50    }
51    pub fn untrigger_import(&mut self) {
52        self.import_triggered = false;
53    }
54    pub fn trigger_export(&mut self) {
55        self.export_triggered = true;
56    }
57    pub fn trigger_import(&mut self) {
58        self.import_triggered = true;
59    }
60    pub fn use_merge_import(&mut self) {
61        self.import_mode = ImportMode::Merge;
62    }
63    pub fn use_replace_import(&mut self) {
64        self.import_mode = ImportMode::Replace;
65    }
66    pub fn merge_mode(self) -> bool {
67        self.import_mode == ImportMode::Merge
68    }
69    pub fn from_export_triggered(export_triggered: bool) -> Self {
70        Self {
71            export_triggered,
72            ..Default::default()
73        }
74    }
75}
76
77pub type AnnotationsMap<T> = LabelMap<InstanceAnnotations<T>>;
78
79fn sort<T>(annos: InstanceAnnotations<T>, access_x_or_y: fn(BbF) -> TPtF) -> InstanceAnnotations<T>
80where
81    T: InstanceAnnotate,
82{
83    let (elts, cat_idxs, selected_mask) = annos.separate_data();
84    let mut tmp_tuples = elts
85        .into_iter()
86        .zip(cat_idxs)
87        .zip(selected_mask)
88        .collect::<Vec<_>>();
89    tmp_tuples.sort_by(|((elt1, _), _), ((elt2, _), _)| {
90        match access_x_or_y(elt1.enclosing_bb()).partial_cmp(&access_x_or_y(elt2.enclosing_bb())) {
91            Some(o) => o,
92            None => {
93                tracing::error!(
94                    "there is a NAN in an annotation box {:?}, {:?}",
95                    elt1.enclosing_bb(),
96                    elt2.enclosing_bb()
97                );
98                std::cmp::Ordering::Equal
99            }
100        }
101    });
102    InstanceAnnotations::from_tuples(tmp_tuples)
103}
104
105/// Small little labels to be displayed in a box below instance annotations
106#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
107pub enum InstanceLabelDisplay {
108    #[default]
109    None,
110    // count from left to right
111    IndexLr,
112    // count from top to bottom
113    IndexTb,
114    // category label
115    CatLabel,
116}
117
118impl InstanceLabelDisplay {
119    pub fn next(self) -> Self {
120        match self {
121            Self::None => Self::IndexLr,
122            Self::IndexLr => Self::IndexTb,
123            Self::IndexTb => Self::CatLabel,
124            Self::CatLabel => Self::None,
125        }
126    }
127    pub fn sort<T>(self, annos: InstanceAnnotations<T>) -> InstanceAnnotations<T>
128    where
129        T: InstanceAnnotate,
130    {
131        match self {
132            Self::None | Self::CatLabel => annos,
133            Self::IndexLr => sort(annos, |bb| bb.x),
134            Self::IndexTb => sort(annos, |bb| bb.y),
135        }
136    }
137}
138impl Display for InstanceLabelDisplay {
139    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
140        match self {
141            Self::None => write!(f, "None"),
142            Self::IndexLr => write!(f, "Index-Left-Right"),
143            Self::IndexTb => write!(f, "Index-Top-Bottom"),
144            Self::CatLabel => write!(f, "Category-Label"),
145        }
146    }
147}
148
149#[allow(clippy::struct_excessive_bools)]
150#[derive(Clone, Copy, Debug, PartialEq, Eq)]
151pub struct Options {
152    pub visible: bool,
153    pub is_colorchange_triggered: bool,
154    pub is_redraw_annos_triggered: bool,
155    pub is_export_absolute: bool,
156    pub import_export_trigger: ImportExportTrigger,
157    pub is_history_update_triggered: bool,
158    pub track_changes: bool,
159    pub erase: bool,
160    pub label_propagation: Option<usize>,
161    pub label_deletion: Option<usize>,
162    pub auto_paste: bool,
163    pub instance_label_display: InstanceLabelDisplay,
164    pub doublecheck_cocoexport_shape: bool,
165}
166impl Default for Options {
167    fn default() -> Self {
168        Self {
169            visible: true,
170            is_colorchange_triggered: false,
171            is_redraw_annos_triggered: false,
172            is_export_absolute: false,
173            import_export_trigger: ImportExportTrigger::default(),
174            is_history_update_triggered: false,
175            track_changes: false,
176            erase: false,
177            label_propagation: None,
178            label_deletion: None,
179            auto_paste: false,
180            instance_label_display: InstanceLabelDisplay::None,
181            doublecheck_cocoexport_shape: true,
182        }
183    }
184}
185impl Options {
186    pub fn trigger_redraw_and_hist(mut self) -> Self {
187        self.is_history_update_triggered = true;
188        self.is_redraw_annos_triggered = true;
189        self
190    }
191}
192
193const N: usize = 1;
194#[derive(Serialize, Deserialize, Clone, Debug, Default, PartialEq)]
195pub struct VisibleInactiveToolsState {
196    // should the tool's annotations be shown in the background
197    show_mask: [bool; N],
198}
199impl VisibleInactiveToolsState {
200    pub fn new() -> Self {
201        Self::default()
202    }
203    #[allow(clippy::needless_lifetimes)]
204    pub fn iter<'a>(&'a self) -> impl Iterator<Item = bool> + 'a {
205        self.show_mask.iter().copied()
206    }
207    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut bool> {
208        self.show_mask.iter_mut()
209    }
210    pub fn hide_all(&mut self) {
211        for show in &mut self.show_mask {
212            *show = false;
213        }
214    }
215    pub fn set_show(&mut self, idx: usize, is_visible: bool) {
216        self.show_mask[idx] = is_visible;
217    }
218}
219
220pub fn random_clr() -> [u8; 3] {
221    let r = rand::random::<u8>();
222    let g = rand::random::<u8>();
223    let b = rand::random::<u8>();
224    [r, g, b]
225}
226
227fn argmax_clr_dist(picklist: &[[u8; 3]], legacylist: &[[u8; 3]]) -> [u8; 3] {
228    let (idx, _) = picklist
229        .iter()
230        .enumerate()
231        .map(|(i, pickclr)| {
232            let min_dist = legacylist
233                .iter()
234                .map(|legclr| color_dist(*legclr, *pickclr))
235                .min_by(|a, b| a.partial_cmp(b).unwrap())
236                .unwrap_or(0.0);
237            (i, min_dist)
238        })
239        .max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
240        .unwrap();
241    picklist[idx]
242}
243
244pub fn new_color(colors: &[[u8; 3]]) -> [u8; 3] {
245    let mut new_clr_proposals = [[0u8, 0u8, 0u8]; 10];
246    for new_clr in &mut new_clr_proposals {
247        *new_clr = random_clr();
248    }
249    argmax_clr_dist(&new_clr_proposals, colors)
250}
251
252pub fn new_random_colors(n: usize) -> Vec<[u8; 3]> {
253    let mut colors = vec![random_clr()];
254    for _ in 0..(n - 1) {
255        let color = new_color(&colors);
256        colors.push(color);
257    }
258    colors
259}
260
261fn get_visibility(visible: bool, show_only_current: bool, cat_idx_current: usize) -> Visibility {
262    if visible && show_only_current {
263        Visibility::Only(cat_idx_current)
264    } else if visible {
265        Visibility::All
266    } else {
267        Visibility::None
268    }
269}
270
271pub fn vis_from_lfoption(label_info: Option<&LabelInfo>, visible: bool) -> Visibility {
272    if let Some(label_info) = label_info {
273        label_info.visibility(visible)
274    } else if visible {
275        Visibility::All
276    } else {
277        Visibility::None
278    }
279}
280
281pub fn merge<T>(
282    annos1: AnnotationsMap<T>,
283    li1: LabelInfo,
284    annos2: AnnotationsMap<T>,
285    li2: LabelInfo,
286) -> (AnnotationsMap<T>, LabelInfo)
287where
288    T: InstanceAnnotate,
289{
290    let (li, idx_map) = li1.merge(li2);
291    let mut annotations_map = annos1;
292
293    for (k, (v2, s)) in annos2 {
294        if let Some((v1, _)) = annotations_map.get_mut(&k) {
295            let (elts, cat_idxs, _) = v2.separate_data();
296            v1.extend(
297                elts.into_iter(),
298                cat_idxs.into_iter().map(|old_idx| idx_map[old_idx]),
299                s,
300                InstanceLabelDisplay::default(),
301            );
302            v1.deselect_all();
303        } else {
304            let (elts, cat_idxs, _) = v2.separate_data();
305            let cat_idxs = cat_idxs
306                .into_iter()
307                .map(|old_idx| idx_map[old_idx])
308                .collect::<Vec<_>>();
309            let v2 =
310                InstanceAnnotations::new_relaxed(elts, cat_idxs, InstanceLabelDisplay::default());
311            annotations_map.insert(k, (v2, s));
312        }
313    }
314    (annotations_map, li)
315}
316
317#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)]
318pub struct LabelInfo {
319    pub new_label: String,
320    labels: Vec<String>,
321    colors: Vec<[u8; 3]>,
322    cat_ids: Vec<u32>,
323    pub cat_idx_current: usize,
324    pub show_only_current: bool,
325}
326impl LabelInfo {
327    /// Merges two `LabelInfo`s. Returns the merged `LabelInfo` and a vector that maps
328    /// the indices of the second `LabelInfo` to the indices of the merged `LabelInfo`.
329    pub fn merge(mut self, other: Self) -> (Self, Vec<usize>) {
330        let mut idx_map = vec![];
331        for other_label in other.labels {
332            let self_cat_idx = self.labels.iter().position(|slab| slab == &other_label);
333            if let Some(scidx) = self_cat_idx {
334                idx_map.push(scidx);
335            } else {
336                self.labels.push(other_label);
337                self.colors.push(new_color(&self.colors));
338                self.cat_ids.push(self.labels.len() as u32);
339                idx_map.push(self.labels.len() - 1);
340            }
341        }
342        (self, idx_map)
343    }
344
345    pub fn visibility(&self, visible: bool) -> Visibility {
346        get_visibility(visible, self.show_only_current, self.cat_idx_current)
347    }
348    pub fn new_random_colors(&mut self) {
349        info!("new random colors for annotations");
350        self.colors = new_random_colors(self.colors.len());
351    }
352    pub fn push(
353        &mut self,
354        label: String,
355        color: Option<[u8; 3]>,
356        cat_id: Option<u32>,
357    ) -> RvResult<()> {
358        if self.labels.contains(&label) {
359            Err(rverr!("label '{}' already exists", label))
360        } else {
361            info!("adding label '{label}'");
362            self.labels.push(label);
363            if let Some(clr) = color {
364                if self.colors.contains(&clr) {
365                    return Err(rverr!("color '{:?}' already exists", clr));
366                }
367                self.colors.push(clr);
368            } else {
369                let new_clr = new_color(&self.colors);
370                self.colors.push(new_clr);
371            }
372            if let Some(cat_id) = cat_id {
373                if self.cat_ids.contains(&cat_id) {
374                    return Err(rverr!("cat id '{:?}' already exists", cat_id));
375                }
376                self.cat_ids.push(cat_id);
377            } else if let Some(max_id) = self.cat_ids.iter().max() {
378                self.cat_ids.push(max_id + 1);
379            } else {
380                self.cat_ids.push(1);
381            }
382            Ok(())
383        }
384    }
385    pub fn rename_label(&mut self, idx: usize, label: String) -> RvResult<()> {
386        if self.labels.contains(&label) {
387            Err(rverr!("label '{label}' already exists"))
388        } else {
389            self.labels[idx] = label;
390            Ok(())
391        }
392    }
393    pub fn from_iter(it: impl Iterator<Item = ((String, [u8; 3]), u32)>) -> RvResult<Self> {
394        let mut info = Self::empty();
395        for ((label, color), cat_id) in it {
396            info.push(label, Some(color), Some(cat_id))?;
397        }
398        Ok(info)
399    }
400    pub fn is_empty(&self) -> bool {
401        self.labels.is_empty()
402    }
403    pub fn len(&self) -> usize {
404        self.labels.len()
405    }
406    pub fn remove(&mut self, idx: usize) -> (String, [u8; 3], u32) {
407        let removed_items = (
408            self.labels.remove(idx),
409            self.colors.remove(idx),
410            self.cat_ids.remove(idx),
411        );
412        info!("label '{}' removed", removed_items.0);
413        removed_items
414    }
415    pub fn find_default(&mut self) -> Option<&mut String> {
416        self.labels.iter_mut().find(|lab| lab == &DEFAULT_LABEL)
417    }
418    pub fn colors(&self) -> &Vec<[u8; 3]> {
419        &self.colors
420    }
421
422    pub fn labels(&self) -> &Vec<String> {
423        &self.labels
424    }
425
426    pub fn cat_ids(&self) -> &Vec<u32> {
427        &self.cat_ids
428    }
429
430    pub fn separate_data(self) -> (Vec<String>, Vec<[u8; 3]>, Vec<u32>) {
431        (self.labels, self.colors, self.cat_ids)
432    }
433
434    pub fn empty() -> Self {
435        Self {
436            new_label: DEFAULT_LABEL.to_string(),
437            labels: vec![],
438            colors: vec![],
439            cat_ids: vec![],
440            cat_idx_current: 0,
441            show_only_current: false,
442        }
443    }
444    pub fn remove_catidx<'a, T>(&mut self, cat_idx: usize, annotaions_map: &mut AnnotationsMap<T>)
445    where
446        T: InstanceAnnotate + PartialEq + Default + 'a,
447    {
448        if self.len() > 1 {
449            self.remove(cat_idx);
450            if self.cat_idx_current >= cat_idx.max(1) {
451                self.cat_idx_current -= 1;
452            }
453            for (anno, _) in annotaions_map.values_mut() {
454                let indices_for_rm = anno
455                    .cat_idxs()
456                    .iter()
457                    .enumerate()
458                    .filter(|(_, geo_cat_idx)| **geo_cat_idx == cat_idx)
459                    .map(|(idx, _)| idx)
460                    .collect::<Vec<_>>();
461                anno.remove_multiple(&indices_for_rm);
462                anno.reduce_cat_idxs(cat_idx);
463            }
464        }
465    }
466}
467
468impl Default for LabelInfo {
469    fn default() -> Self {
470        let new_label = DEFAULT_LABEL.to_string();
471        let new_color = [255, 255, 255];
472        let labels = vec![new_label.clone()];
473        let colors = vec![new_color];
474        let cat_ids = vec![1];
475        Self {
476            new_label,
477            labels,
478            colors,
479            cat_ids,
480            cat_idx_current: 0,
481            show_only_current: false,
482        }
483    }
484}
485
486#[derive(Deserialize, Serialize, Debug, Clone, PartialEq)]
487pub struct InstanceExportData<A> {
488    pub labels: Vec<String>,
489    pub colors: Vec<[u8; 3]>,
490    pub cat_ids: Vec<u32>,
491    // filename, bounding boxes, classes of the boxes, dimensions of the image
492    pub annotations: HashMap<String, (Vec<A>, Vec<usize>, ShapeI)>,
493    pub coco_file: ExportPath,
494    pub is_export_absolute: bool,
495}
496
497impl<A> InstanceExportData<A>
498where
499    A: InstanceAnnotate,
500{
501    pub fn from_tools_data(
502        options: &Options,
503        label_info: LabelInfo,
504        coco_file: ExportPath,
505        annotations_map: AnnotationsMap<A>,
506    ) -> Self {
507        let is_export_absolute = options.is_export_absolute;
508        let annotations = annotations_map
509            .into_iter()
510            .map(|(filename, (annos, shape))| {
511                let (bbs, labels, _) = annos.separate_data();
512                (filename, (bbs, labels, shape))
513            })
514            .collect::<HashMap<_, _>>();
515        let (labels, colors, cat_ids) = label_info.separate_data();
516        InstanceExportData {
517            labels,
518            colors,
519            cat_ids,
520            annotations,
521            coco_file,
522            is_export_absolute,
523        }
524    }
525    pub fn label_info(&self) -> RvResult<LabelInfo> {
526        LabelInfo::from_iter(
527            self.labels
528                .clone()
529                .into_iter()
530                .zip(self.colors.clone())
531                .zip(self.cat_ids.clone()),
532        )
533    }
534}
535
536#[derive(Serialize, Deserialize, Debug, PartialEq)]
537pub struct CocoRle {
538    pub counts: Vec<TPtI>,
539    pub size: (TPtI, TPtI),
540    pub intensity: Option<TPtF>,
541}
542
543impl CocoRle {
544    pub fn to_canvas(&self, bb: BbF) -> RvResult<Canvas> {
545        let bb = bb.into();
546        let rle_bb = rle_image_to_bb(&self.counts, bb, ShapeI::from(self.size))?;
547        let mask = rle_to_mask(&rle_bb, bb.w, bb.h);
548        let intensity = self.intensity.unwrap_or(1.0);
549        Ok(Canvas {
550            bb,
551            mask,
552            intensity,
553        })
554    }
555}
556
557#[derive(Debug, Serialize, Deserialize, PartialEq)]
558#[serde(untagged)]
559pub enum CocoSegmentation {
560    Polygon(Vec<Vec<TPtF>>),
561    Rle(CocoRle),
562}
563
564pub fn polygon_to_geofig(
565    poly: &[Vec<TPtF>],
566    w_factor: f64,
567    h_factor: f64,
568    bb: BbF,
569    mut warn: impl FnMut(&str),
570) -> RvResult<GeoFig> {
571    if poly.len() > 1 {
572        return Err(rverr!(
573            "multiple polygons per box not supported. ignoring all but first."
574        ));
575    }
576    let n_points = poly[0].len();
577    let coco_data = &poly[0];
578
579    let poly_points = (0..n_points)
580        .step_by(2)
581        .filter_map(|idx| {
582            let p = Point {
583                x: (coco_data[idx] * w_factor),
584                y: (coco_data[idx + 1] * h_factor),
585            };
586            if bb.contains(p) {
587                Some(p)
588            } else {
589                None
590            }
591        })
592        .collect();
593    let poly = Polygon::from_vec(poly_points);
594    if let Ok(poly) = poly {
595        let encl_bb = poly.enclosing_bb();
596        if encl_bb.w * encl_bb.h < 1e-6 && bb.w * bb.h > 1e-6 {
597            warn(&format!(
598                "polygon has no area. using bb. bb: {bb:?}, poly: {encl_bb:?}"
599            ));
600            Ok(GeoFig::BB(bb))
601        } else {
602            if !bb.all_corners_close(encl_bb) {
603                let msg = format!("bounding box and polygon enclosing box do not match. using bb. bb: {bb:?}, poly: {encl_bb:?}");
604                warn(&msg);
605            }
606            // check if the poly is just a bounding box
607            if poly.points().len() == 4
608                                // all points are bb corners
609                                && poly.points_iter().all(|p| {
610                                    encl_bb.points_iter().any(|p_encl| p == p_encl)})
611                                // all points are different
612                                && poly
613                                    .points_iter()
614                                    .all(|p| poly.points_iter().filter(|p_| p == *p_).count() == 1)
615            {
616                Ok(GeoFig::BB(bb))
617            } else {
618                Ok(GeoFig::Poly(poly))
619            }
620        }
621    } else if n_points > 0 {
622        Err(rverr!(
623            "Segmentation invalid, could not be created from polygon with {n_points} points"
624        ))
625    } else {
626        // polygon might be empty, we continue with the BB
627        Ok(GeoFig::BB(bb))
628    }
629}
630
631#[macro_export]
632macro_rules! implement_annotate {
633    ($tooldata:ident) => {
634        impl $crate::tools_data::core::Annotate for $tooldata {
635            fn has_annos(&self, relative_path: &str) -> bool {
636                if let Some(v) = self.get_annos(relative_path) {
637                    !v.is_empty()
638                } else {
639                    false
640                }
641            }
642        }
643    };
644}
645
646pub trait Annotate {
647    /// Has the image with the given path annotations of the
648    /// trait-implementing tool?
649    fn has_annos(&self, relative_path: &str) -> bool;
650}
651
652pub trait InstanceAnnotate:
653    Clone + Default + Debug + PartialEq + Serialize + DeserializeOwned
654{
655    fn is_contained_in_image(&self, shape: ShapeI) -> bool;
656    fn contains<P>(&self, point: P) -> bool
657    where
658        P: Into<PtF>;
659    fn dist_to_boundary(&self, p: PtF) -> TPtF;
660    /// # Errors
661    /// Can fail if a bounding box ends up with negative coordinates after rotation
662    fn rot90_with_image_ntimes(self, shape: ShapeI, n: u8) -> RvResult<Self>;
663    fn enclosing_bb(&self) -> BbF;
664    /// # Errors
665    /// Can fail if a bounding box is not on the image.
666    fn to_cocoseg(
667        &self,
668        shape_im: ShapeI,
669        is_export_absolute: bool,
670    ) -> RvResult<Option<CocoSegmentation>>;
671}
672pub trait AccessInstanceData<T: InstanceAnnotate> {
673    fn annotations_map(&self) -> &AnnotationsMap<T>;
674    fn label_info(&self) -> &LabelInfo;
675}
676pub trait ExportAsCoco<A>: AccessInstanceData<A>
677where
678    A: InstanceAnnotate + 'static,
679{
680    fn cocofile_conn(&self) -> ExportPath;
681    fn separate_data(self) -> (Options, LabelInfo, AnnotationsMap<A>, ExportPath);
682    #[cfg(test)]
683    fn anno_iter(&self) -> impl Iterator<Item = (&String, &(InstanceAnnotations<A>, ShapeI))>;
684    fn set_annotations_map(&mut self, map: AnnotationsMap<A>) -> RvResult<()>;
685    fn set_labelinfo(&mut self, info: LabelInfo);
686    fn core_options_mut(&mut self) -> &mut Options;
687    fn new(
688        options: Options,
689        label_info: LabelInfo,
690        anno_map: AnnotationsMap<A>,
691        export_path: ExportPath,
692    ) -> Self;
693}
694
695#[cfg(test)]
696use crate::tools_data::brush_data;
697#[cfg(test)]
698use rvimage_domain::{BrushLine, Line};
699#[test]
700fn test_argmax() {
701    let picklist = [
702        [200, 200, 200u8],
703        [1, 7, 3],
704        [0, 0, 1],
705        [45, 43, 52],
706        [1, 10, 15],
707    ];
708    let legacylist = [
709        [17, 16, 15],
710        [199, 199, 201u8],
711        [50, 50, 50u8],
712        [255, 255, 255u8],
713    ];
714    assert_eq!(argmax_clr_dist(&picklist, &legacylist), [0, 0, 1]);
715}
716
717#[test]
718fn test_labelinfo_merge() {
719    let li1 = LabelInfo::default();
720    let mut li2 = LabelInfo::default();
721    li2.new_random_colors();
722    let (mut li_merged, _) = li1.clone().merge(li2);
723    assert_eq!(li1, li_merged);
724    li_merged
725        .push("new_label".into(), Some([0, 0, 1]), None)
726        .unwrap();
727    let (li_merged, _) = li_merged.merge(li1);
728    let li_reference = LabelInfo {
729        new_label: DEFAULT_LABEL.to_string(),
730        labels: vec![DEFAULT_LABEL.to_string(), "new_label".to_string()],
731        colors: vec![[255, 255, 255], [0, 0, 1]],
732        cat_ids: vec![1, 2],
733        cat_idx_current: 0,
734        show_only_current: false,
735    };
736    assert_eq!(li_merged, li_reference);
737    assert_eq!(li_merged.clone().merge(li_merged.clone()).0, li_reference);
738    let li = LabelInfo {
739        new_label: DEFAULT_LABEL.to_string(),
740        labels: vec!["somelabel".to_string(), "new_label".to_string()],
741        colors: vec![[255, 255, 255], [0, 1, 1]],
742        cat_ids: vec![1, 2],
743        cat_idx_current: 0,
744        show_only_current: false,
745    };
746    let li_merged_ = li_merged.clone().merge(li.clone());
747    let li_reference = (
748        LabelInfo {
749            new_label: DEFAULT_LABEL.to_string(),
750            labels: vec![
751                DEFAULT_LABEL.to_string(),
752                "new_label".to_string(),
753                "somelabel".to_string(),
754            ],
755            colors: vec![[255, 255, 255], [0, 0, 1], li_merged_.0.colors[2]],
756            cat_ids: vec![1, 2, 3],
757            cat_idx_current: 0,
758            show_only_current: false,
759        },
760        vec![2, 1],
761    );
762    assert_ne!([255, 255, 255], li_merged_.0.colors[2]);
763    assert_eq!(li_merged_, li_reference);
764    let li_merged = li.merge(li_merged);
765    let li_reference = LabelInfo {
766        new_label: DEFAULT_LABEL.to_string(),
767        labels: vec![
768            "somelabel".to_string(),
769            "new_label".to_string(),
770            DEFAULT_LABEL.to_string(),
771        ],
772        colors: vec![[255, 255, 255], [0, 1, 1], li_merged.0.colors[2]],
773        cat_ids: vec![1, 2, 3],
774        cat_idx_current: 0,
775        show_only_current: false,
776    };
777    assert_eq!(li_merged.0, li_reference);
778}
779
780#[test]
781fn test_merge_annos() {
782    let orig_shape = ShapeI::new(100, 100);
783    let li1 = LabelInfo {
784        new_label: "x".to_string(),
785        labels: vec!["somelabel".to_string(), "x".to_string()],
786        colors: vec![[255, 255, 255], [0, 1, 1]],
787        cat_ids: vec![1, 2],
788        cat_idx_current: 0,
789        show_only_current: false,
790    };
791    let li2 = LabelInfo {
792        new_label: "x".to_string(),
793        labels: vec![
794            "somelabel".to_string(),
795            "new_label".to_string(),
796            "x".to_string(),
797        ],
798        colors: vec![[255, 255, 255], [0, 1, 2], [1, 1, 1]],
799        cat_ids: vec![1, 2, 3],
800        cat_idx_current: 0,
801        show_only_current: false,
802    };
803    let mut annos_map1: super::brush_data::BrushAnnoMap = AnnotationsMap::new();
804
805    let mut line = Line::new();
806    line.push(PtF { x: 5.0, y: 5.0 });
807    let anno1 = Canvas::new(
808        &BrushLine {
809            line: line.clone(),
810            thickness: 1.0,
811            intensity: 1.0,
812        },
813        orig_shape,
814        None,
815    )
816    .unwrap();
817    annos_map1.insert(
818        "file1".to_string(),
819        (
820            InstanceAnnotations::new(vec![anno1.clone()], vec![1], vec![true]).unwrap(),
821            orig_shape,
822        ),
823    );
824    let mut annos_map2: brush_data::BrushAnnoMap = AnnotationsMap::new();
825    let anno2 = Canvas::new(
826        &BrushLine {
827            line,
828            thickness: 2.0,
829            intensity: 2.0,
830        },
831        orig_shape,
832        None,
833    )
834    .unwrap();
835
836    annos_map2.insert(
837        "file1".to_string(),
838        (
839            InstanceAnnotations::new(vec![anno2.clone()], vec![1], vec![true]).unwrap(),
840            orig_shape,
841        ),
842    );
843    annos_map2.insert(
844        "file2".to_string(),
845        (
846            InstanceAnnotations::new(vec![anno2.clone()], vec![1], vec![true]).unwrap(),
847            orig_shape,
848        ),
849    );
850    let (merged_map, merged_li) = merge(annos_map1, li1, annos_map2, li2.clone());
851    let merged_li_ref = LabelInfo {
852        new_label: "x".to_string(),
853        labels: vec![
854            "somelabel".to_string(),
855            "x".to_string(),
856            "new_label".to_string(),
857        ],
858        colors: vec![[255, 255, 255], [0, 1, 1], merged_li.colors[2]],
859        cat_ids: vec![1, 2, 3],
860        cat_idx_current: 0,
861        show_only_current: false,
862    };
863
864    assert_eq!(merged_li, merged_li_ref);
865    let map_ref = [
866        (
867            "file1".to_string(),
868            (
869                InstanceAnnotations::new(
870                    vec![anno1, anno2.clone()],
871                    vec![1, 2],
872                    vec![false, false],
873                )
874                .unwrap(),
875                orig_shape,
876            ),
877        ),
878        (
879            "file2".to_string(),
880            (
881                InstanceAnnotations::new(vec![anno2], vec![2], vec![false]).unwrap(),
882                orig_shape,
883            ),
884        ),
885    ]
886    .into_iter()
887    .collect::<AnnotationsMap<Canvas>>();
888    for (k, (v, s)) in merged_map.iter() {
889        assert_eq!(map_ref[k].0, *v);
890        assert_eq!(map_ref[k].1, *s);
891    }
892}