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#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
107pub enum InstanceLabelDisplay {
108 #[default]
109 None,
110 IndexLr,
112 IndexTb,
114 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 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 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 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 if poly.points().len() == 4
608 && poly.points_iter().all(|p| {
610 encl_bb.points_iter().any(|p_encl| p == p_encl)})
611 && 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 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 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 fn rot90_with_image_ntimes(self, shape: ShapeI, n: u8) -> RvResult<Self>;
663 fn enclosing_bb(&self) -> BbF;
664 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}