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