|
| 1 | +import copy |
1 | 2 | import logging
|
2 | 3 | import uuid
|
3 |
| -from typing import Any, Dict, Generator, Iterable |
| 4 | +from collections import defaultdict, deque |
| 5 | +from typing import Any, Deque, Dict, Generator, Iterable, List, Set, Union |
| 6 | + |
| 7 | +from labelbox.data.annotation_types.annotation import ObjectAnnotation |
| 8 | +from labelbox.data.annotation_types.classification.classification import ( |
| 9 | + ClassificationAnnotation,) |
| 10 | +from labelbox.data.annotation_types.metrics.confusion_matrix import ( |
| 11 | + ConfusionMatrixMetric,) |
| 12 | +from labelbox.data.annotation_types.metrics.scalar import ScalarMetric |
| 13 | +from labelbox.data.annotation_types.video import VideoMaskAnnotation |
4 | 14 |
|
5 | 15 | from ...annotation_types.collection import LabelCollection, LabelGenerator
|
6 | 16 | from ...annotation_types.relationship import RelationshipAnnotation
|
@@ -42,51 +52,69 @@ def serialize(
|
42 | 52 | Returns:
|
43 | 53 | A generator for accessing the ndjson representation of the data
|
44 | 54 | """
|
45 |
| - used_annotation_uuids = set() |
46 |
| - for label in labels: |
47 |
| - annotation_uuid_to_generated_uuid_lookup = {} |
48 |
| - # UUIDs are private properties used to enhance UX when defining relationships. |
49 |
| - # They are created for all annotations, but only utilized for relationships. |
50 |
| - # To avoid overwriting, UUIDs must be unique across labels. |
51 |
| - # Non-relationship annotation UUIDs are dropped (server-side generation will occur). |
52 |
| - # For relationship annotations, new UUIDs are generated and stored in a lookup table. |
53 |
| - for annotation in label.annotations: |
54 |
| - if isinstance(annotation, RelationshipAnnotation): |
55 |
| - source_uuid = annotation.value.source._uuid |
56 |
| - target_uuid = annotation.value.target._uuid |
| 55 | + used_uuids: Set[uuid.UUID] = set() |
57 | 56 |
|
58 |
| - if (len( |
59 |
| - used_annotation_uuids.intersection( |
60 |
| - {source_uuid, target_uuid})) > 0): |
61 |
| - new_source_uuid = uuid.uuid4() |
62 |
| - new_target_uuid = uuid.uuid4() |
63 |
| - |
64 |
| - annotation_uuid_to_generated_uuid_lookup[ |
65 |
| - source_uuid] = new_source_uuid |
66 |
| - annotation_uuid_to_generated_uuid_lookup[ |
67 |
| - target_uuid] = new_target_uuid |
68 |
| - annotation.value.source._uuid = new_source_uuid |
69 |
| - annotation.value.target._uuid = new_target_uuid |
70 |
| - else: |
71 |
| - annotation_uuid_to_generated_uuid_lookup[ |
72 |
| - source_uuid] = source_uuid |
73 |
| - annotation_uuid_to_generated_uuid_lookup[ |
74 |
| - target_uuid] = target_uuid |
75 |
| - used_annotation_uuids.add(annotation._uuid) |
| 57 | + relationship_uuids: Dict[uuid.UUID, |
| 58 | + Deque[uuid.UUID]] = defaultdict(deque) |
76 | 59 |
|
| 60 | + # UUIDs are private properties used to enhance UX when defining relationships. |
| 61 | + # They are created for all annotations, but only utilized for relationships. |
| 62 | + # To avoid overwriting, UUIDs must be unique across labels. |
| 63 | + # Non-relationship annotation UUIDs are regenerated when they are reused. |
| 64 | + # For relationship annotations, during first pass, we update the UUIDs of the source and target annotations. |
| 65 | + # During the second pass, we update the UUIDs of the annotations referenced by the relationship annotations. |
| 66 | + for label in labels: |
| 67 | + uuid_safe_annotations: List[Union[ |
| 68 | + ClassificationAnnotation, |
| 69 | + ObjectAnnotation, |
| 70 | + VideoMaskAnnotation, |
| 71 | + ScalarMetric, |
| 72 | + ConfusionMatrixMetric, |
| 73 | + RelationshipAnnotation, |
| 74 | + ]] = [] |
| 75 | + # First pass to get all RelatiohnshipAnnotaitons |
| 76 | + # and update the UUIDs of the source and target annotations |
| 77 | + for relationship_annotation in ( |
| 78 | + annotation for annotation in label.annotations |
| 79 | + if isinstance(annotation, RelationshipAnnotation)): |
| 80 | + if relationship_annotation in uuid_safe_annotations: |
| 81 | + relationship_annotation = copy.deepcopy( |
| 82 | + relationship_annotation) |
| 83 | + new_source_uuid = uuid.uuid4() |
| 84 | + new_target_uuid = uuid.uuid4() |
| 85 | + relationship_uuids[relationship_annotation.value.source. |
| 86 | + _uuid].append(new_source_uuid) |
| 87 | + relationship_uuids[relationship_annotation.value.target. |
| 88 | + _uuid].append(new_target_uuid) |
| 89 | + relationship_annotation.value.source._uuid = new_source_uuid |
| 90 | + relationship_annotation.value.target._uuid = new_target_uuid |
| 91 | + if relationship_annotation._uuid in used_uuids: |
| 92 | + relationship_annotation._uuid = uuid.uuid4() |
| 93 | + used_uuids.add(relationship_annotation._uuid) |
| 94 | + uuid_safe_annotations.append(relationship_annotation) |
| 95 | + # Second pass to update UUIDs for annotations referenced by RelationshipAnnotations |
77 | 96 | for annotation in label.annotations:
|
78 |
| - if (not isinstance(annotation, RelationshipAnnotation) and |
79 |
| - hasattr(annotation, "_uuid")): |
80 |
| - annotation._uuid = annotation_uuid_to_generated_uuid_lookup.get( |
81 |
| - annotation._uuid, annotation._uuid) |
| 97 | + if not isinstance(annotation, RelationshipAnnotation): |
| 98 | + if hasattr(annotation, "_uuid"): |
| 99 | + if annotation in uuid_safe_annotations: |
| 100 | + annotation = copy.deepcopy(annotation) |
| 101 | + next_uuids = relationship_uuids[annotation._uuid] |
| 102 | + if len(next_uuids) > 0: |
| 103 | + annotation._uuid = next_uuids.popleft() |
82 | 104 |
|
83 |
| - for example in NDLabel.from_common(labels): |
84 |
| - annotation_uuid = getattr(example, "uuid", None) |
| 105 | + if annotation._uuid in used_uuids: |
| 106 | + annotation._uuid = uuid.uuid4() |
| 107 | + used_uuids.add(annotation._uuid) |
| 108 | + uuid_safe_annotations.append(annotation) |
| 109 | + label.annotations = uuid_safe_annotations |
| 110 | + for example in NDLabel.from_common([label]): |
| 111 | + annotation_uuid = getattr(example, "uuid", None) |
85 | 112 |
|
86 |
| - res = example.dict( |
87 |
| - by_alias=True, |
88 |
| - exclude={"uuid"} if annotation_uuid == "None" else None) |
89 |
| - for k, v in list(res.items()): |
90 |
| - if k in IGNORE_IF_NONE and v is None: |
91 |
| - del res[k] |
92 |
| - yield res |
| 113 | + res = example.dict( |
| 114 | + by_alias=True, |
| 115 | + exclude={"uuid"} if annotation_uuid == "None" else None, |
| 116 | + ) |
| 117 | + for k, v in list(res.items()): |
| 118 | + if k in IGNORE_IF_NONE and v is None: |
| 119 | + del res[k] |
| 120 | + yield res |
0 commit comments