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