label-studio-sdk 1.0.2__py3-none-any.whl → 1.0.4__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of label-studio-sdk might be problematic. Click here for more details.
- label_studio_sdk/__init__.py +20 -1
- label_studio_sdk/actions/client.py +8 -8
- label_studio_sdk/annotations/client.py +24 -24
- label_studio_sdk/base_client.py +3 -0
- label_studio_sdk/core/client_wrapper.py +1 -1
- label_studio_sdk/core/http_client.py +36 -8
- label_studio_sdk/core/request_options.py +2 -2
- label_studio_sdk/export_storage/__init__.py +2 -1
- label_studio_sdk/export_storage/azure/client.py +28 -28
- label_studio_sdk/export_storage/client.py +7 -4
- label_studio_sdk/export_storage/gcs/client.py +28 -28
- label_studio_sdk/export_storage/local/client.py +28 -28
- label_studio_sdk/export_storage/redis/client.py +28 -28
- label_studio_sdk/export_storage/s3/client.py +28 -28
- label_studio_sdk/export_storage/s3s/__init__.py +2 -0
- label_studio_sdk/export_storage/s3s/client.py +836 -0
- label_studio_sdk/files/client.py +24 -24
- label_studio_sdk/import_storage/__init__.py +2 -1
- label_studio_sdk/import_storage/azure/client.py +28 -28
- label_studio_sdk/import_storage/client.py +7 -4
- label_studio_sdk/import_storage/gcs/client.py +28 -28
- label_studio_sdk/import_storage/local/client.py +28 -28
- label_studio_sdk/import_storage/redis/client.py +28 -28
- label_studio_sdk/import_storage/s3/client.py +28 -28
- label_studio_sdk/import_storage/s3s/__init__.py +2 -0
- label_studio_sdk/import_storage/s3s/client.py +1054 -0
- label_studio_sdk/label_interface/base.py +2 -2
- label_studio_sdk/label_interface/control_tags.py +32 -18
- label_studio_sdk/label_interface/create.py +241 -0
- label_studio_sdk/label_interface/interface.py +68 -0
- label_studio_sdk/label_interface/object_tags.py +26 -10
- label_studio_sdk/label_interface/objects.py +5 -5
- label_studio_sdk/ml/client.py +36 -36
- label_studio_sdk/predictions/client.py +24 -24
- label_studio_sdk/projects/__init__.py +8 -2
- label_studio_sdk/projects/client.py +232 -69
- label_studio_sdk/projects/client_ext.py +16 -1
- label_studio_sdk/projects/exports/client.py +38 -38
- label_studio_sdk/projects/types/__init__.py +2 -1
- label_studio_sdk/projects/types/projects_update_response.py +96 -0
- label_studio_sdk/tasks/client.py +70 -60
- label_studio_sdk/tasks/client_ext.py +4 -0
- label_studio_sdk/types/__init__.py +16 -0
- label_studio_sdk/types/base_task.py +4 -2
- label_studio_sdk/types/base_task_file_upload.py +5 -0
- label_studio_sdk/types/base_task_updated_by.py +5 -0
- label_studio_sdk/types/data_manager_task_serializer.py +3 -2
- label_studio_sdk/types/data_manager_task_serializer_annotators_item.py +5 -0
- label_studio_sdk/types/s3s_export_storage.py +80 -0
- label_studio_sdk/types/s3s_import_storage.py +129 -0
- label_studio_sdk/types/s3s_import_storage_status.py +7 -0
- label_studio_sdk/types/task.py +3 -2
- label_studio_sdk/types/task_annotators_item.py +5 -0
- label_studio_sdk/types/workspace.py +77 -0
- label_studio_sdk/users/client.py +32 -32
- label_studio_sdk/views/client.py +24 -24
- label_studio_sdk/webhooks/client.py +24 -24
- label_studio_sdk/workspaces/__init__.py +6 -0
- label_studio_sdk/workspaces/client.py +569 -0
- label_studio_sdk/workspaces/members/__init__.py +5 -0
- label_studio_sdk/workspaces/members/client.py +297 -0
- label_studio_sdk/workspaces/members/types/__init__.py +6 -0
- label_studio_sdk/workspaces/members/types/members_create_response.py +32 -0
- label_studio_sdk/workspaces/members/types/members_list_response_item.py +32 -0
- {label_studio_sdk-1.0.2.dist-info → label_studio_sdk-1.0.4.dist-info}/METADATA +11 -12
- {label_studio_sdk-1.0.2.dist-info → label_studio_sdk-1.0.4.dist-info}/RECORD +67 -46
- {label_studio_sdk-1.0.2.dist-info → label_studio_sdk-1.0.4.dist-info}/WHEEL +0 -0
|
@@ -145,6 +145,14 @@ class ControlTag(LabelStudioTag):
|
|
|
145
145
|
|
|
146
146
|
return tag_class(**tag_info)
|
|
147
147
|
|
|
148
|
+
def collect_attrs(self):
|
|
149
|
+
"""Return tag attrs as a single dict"""
|
|
150
|
+
return {
|
|
151
|
+
**self.attr,
|
|
152
|
+
"name": self.name,
|
|
153
|
+
"toName": self.to_name
|
|
154
|
+
}
|
|
155
|
+
|
|
148
156
|
def get_object(self, name=None):
|
|
149
157
|
"""
|
|
150
158
|
This method retrieves the object tag that the control tag maps to.
|
|
@@ -473,7 +481,7 @@ class ChoicesValue(BaseModel):
|
|
|
473
481
|
|
|
474
482
|
class ChoicesTag(ControlTag):
|
|
475
483
|
""" """
|
|
476
|
-
|
|
484
|
+
tag: str = "Choices"
|
|
477
485
|
_label_attr_name: str = "choices"
|
|
478
486
|
_value_class: Type[ChoicesValue] = ChoicesValue
|
|
479
487
|
|
|
@@ -484,7 +492,7 @@ class LabelsValue(SpanSelection):
|
|
|
484
492
|
|
|
485
493
|
class LabelsTag(ControlTag):
|
|
486
494
|
""" """
|
|
487
|
-
|
|
495
|
+
tag: str = "Labels"
|
|
488
496
|
_label_attr_name: str = "labels"
|
|
489
497
|
_value_class: Type[LabelsValue] = LabelsValue
|
|
490
498
|
|
|
@@ -534,7 +542,7 @@ class BrushLabelsValue(BrushValue):
|
|
|
534
542
|
|
|
535
543
|
class BrushTag(ControlTag):
|
|
536
544
|
""" """
|
|
537
|
-
|
|
545
|
+
tag: str = "Brush"
|
|
538
546
|
_value_class: Type[BrushValue] = BrushValue
|
|
539
547
|
|
|
540
548
|
# def validate_value(self, value) -> bool:
|
|
@@ -546,7 +554,7 @@ class BrushTag(ControlTag):
|
|
|
546
554
|
|
|
547
555
|
class BrushLabelsTag(BrushTag):
|
|
548
556
|
""" """
|
|
549
|
-
|
|
557
|
+
tag: str = "BrushLabels"
|
|
550
558
|
_label_attr_name: str = "brushlabels"
|
|
551
559
|
_value_class: Type[BrushLabelsValue] = BrushLabelsValue
|
|
552
560
|
|
|
@@ -565,13 +573,13 @@ class EllipseLabelsValue(EllipseValue):
|
|
|
565
573
|
|
|
566
574
|
class EllipseTag(ControlTag):
|
|
567
575
|
""" """
|
|
568
|
-
|
|
576
|
+
tag: str = "Ellipse"
|
|
569
577
|
_value_class: Type[EllipseValue] = EllipseValue
|
|
570
578
|
|
|
571
579
|
|
|
572
580
|
class EllipseLabelsTag(ControlTag):
|
|
573
581
|
""" """
|
|
574
|
-
|
|
582
|
+
tag: str = "EllipseLabels"
|
|
575
583
|
_label_attr_name: str = "ellipselabels"
|
|
576
584
|
_value_class: Type[EllipseLabelsValue] = EllipseLabelsValue
|
|
577
585
|
|
|
@@ -587,13 +595,13 @@ class KeyPointLabelsValue(KeyPointValue):
|
|
|
587
595
|
|
|
588
596
|
class KeyPointTag(ControlTag):
|
|
589
597
|
""" """
|
|
590
|
-
|
|
598
|
+
tag: str = "KeyPoint"
|
|
591
599
|
_value_class: Type[KeyPointValue] = KeyPointValue
|
|
592
600
|
|
|
593
601
|
|
|
594
602
|
class KeyPointLabelsTag(ControlTag):
|
|
595
603
|
""" """
|
|
596
|
-
|
|
604
|
+
tag: str = "KeyPointLabels"
|
|
597
605
|
_label_attr_name: str = "keypointlabels"
|
|
598
606
|
_value_class: Type[KeyPointLabelsValue] = KeyPointLabelsValue
|
|
599
607
|
|
|
@@ -608,13 +616,13 @@ class PolygonLabelsValue(PolygonValue):
|
|
|
608
616
|
|
|
609
617
|
class PolygonTag(ControlTag):
|
|
610
618
|
""" """
|
|
611
|
-
|
|
619
|
+
tag: str = "Polygon"
|
|
612
620
|
_value_class: Type[PolygonValue] = PolygonValue
|
|
613
621
|
|
|
614
622
|
|
|
615
623
|
class PolygonLabelsTag(ControlTag):
|
|
616
624
|
""" """
|
|
617
|
-
|
|
625
|
+
tag: str = "PolygonLabels"
|
|
618
626
|
_label_attr_name: str = "polygonlabels"
|
|
619
627
|
_value_class: Type[PolygonLabelsValue] = PolygonLabelsValue
|
|
620
628
|
|
|
@@ -633,13 +641,13 @@ class RectangleLabelsValue(RectangleValue):
|
|
|
633
641
|
|
|
634
642
|
class RectangleTag(ControlTag):
|
|
635
643
|
""" """
|
|
636
|
-
|
|
644
|
+
tag: str = "Rectangle"
|
|
637
645
|
_value_class: Type[RectangleValue] = RectangleValue
|
|
638
646
|
|
|
639
647
|
|
|
640
648
|
class RectangleLabelsTag(ControlTag):
|
|
641
649
|
""" """
|
|
642
|
-
|
|
650
|
+
tag: str = "RectangleLabels"
|
|
643
651
|
_label_attr_name: str = "rectanglelabels"
|
|
644
652
|
_value_class: Type[RectangleLabelsValue] = RectangleLabelsValue
|
|
645
653
|
|
|
@@ -663,6 +671,7 @@ class VideoRectangleValue(BaseModel):
|
|
|
663
671
|
|
|
664
672
|
class VideoRectangleTag(ControlTag):
|
|
665
673
|
""" """
|
|
674
|
+
tag: str = "VideoRectangle"
|
|
666
675
|
_label_attr_name: str = "labels"
|
|
667
676
|
_value_class: Type[VideoRectangleValue] = VideoRectangleValue
|
|
668
677
|
|
|
@@ -673,6 +682,7 @@ class NumberValue(BaseModel):
|
|
|
673
682
|
|
|
674
683
|
class NumberTag(ControlTag):
|
|
675
684
|
""" """
|
|
685
|
+
tag: str = "Number"
|
|
676
686
|
_value_class: Type[NumberValue] = NumberValue
|
|
677
687
|
|
|
678
688
|
|
|
@@ -682,6 +692,7 @@ class DateTimeValue(BaseModel):
|
|
|
682
692
|
|
|
683
693
|
class DateTimeTag(ControlTag):
|
|
684
694
|
""" """
|
|
695
|
+
tag: str = "DateTime"
|
|
685
696
|
_value_class: Type[DateTimeValue] = DateTimeValue
|
|
686
697
|
|
|
687
698
|
|
|
@@ -691,7 +702,7 @@ class HyperTextLabelsValue(SpanSelectionOffsets):
|
|
|
691
702
|
|
|
692
703
|
class HyperTextLabelsTag(ControlTag):
|
|
693
704
|
""" """
|
|
694
|
-
|
|
705
|
+
tag: str = "HyperTextLabels"
|
|
695
706
|
_label_attr_name: str = "htmllabels"
|
|
696
707
|
_value_class: Type[HyperTextLabelsValue] = HyperTextLabelsValue
|
|
697
708
|
|
|
@@ -702,7 +713,7 @@ class PairwiseValue(BaseModel):
|
|
|
702
713
|
|
|
703
714
|
class PairwiseTag(ControlTag):
|
|
704
715
|
""" """
|
|
705
|
-
|
|
716
|
+
tag: str = "Pairwise"
|
|
706
717
|
_value_class: Type[PairwiseValue] = PairwiseValue
|
|
707
718
|
|
|
708
719
|
def label(self, side):
|
|
@@ -717,7 +728,7 @@ class ParagraphLabelsValue(SpanSelectionOffsets):
|
|
|
717
728
|
|
|
718
729
|
class ParagraphLabelsTag(ControlTag):
|
|
719
730
|
""" """
|
|
720
|
-
|
|
731
|
+
tag: str = "ParagraphsLabels"
|
|
721
732
|
_label_attr_name: str = "paragraphlabels"
|
|
722
733
|
_value_class: Type[ParagraphLabelsValue] = ParagraphLabelsValue
|
|
723
734
|
|
|
@@ -736,6 +747,7 @@ class RankerValue(BaseModel):
|
|
|
736
747
|
|
|
737
748
|
class RankerTag(ControlTag):
|
|
738
749
|
""" """
|
|
750
|
+
tag: str = "Ranker"
|
|
739
751
|
_value_class: Type[RankerValue] = RankerValue
|
|
740
752
|
|
|
741
753
|
|
|
@@ -745,11 +757,13 @@ class RatingValue(BaseModel):
|
|
|
745
757
|
|
|
746
758
|
class RatingTag(ControlTag):
|
|
747
759
|
""" """
|
|
760
|
+
tag: str = "Rating"
|
|
748
761
|
_value_class: Type[RatingValue] = RatingValue
|
|
749
762
|
|
|
750
763
|
|
|
751
764
|
class RelationsTag(ControlTag):
|
|
752
765
|
""" """
|
|
766
|
+
tag: str = "Relations"
|
|
753
767
|
def validate_value(self, ) -> bool:
|
|
754
768
|
""" """
|
|
755
769
|
raise NotImplemented("""Should not be called directly, instead
|
|
@@ -768,7 +782,7 @@ class TaxonomyValue(BaseModel):
|
|
|
768
782
|
|
|
769
783
|
class TaxonomyTag(ControlTag):
|
|
770
784
|
""" """
|
|
771
|
-
|
|
785
|
+
tag: str = "Taxonomy"
|
|
772
786
|
_value_class: Type[TaxonomyValue] = TaxonomyValue
|
|
773
787
|
|
|
774
788
|
|
|
@@ -778,7 +792,7 @@ class TextAreaValue(BaseModel):
|
|
|
778
792
|
|
|
779
793
|
class TextAreaTag(ControlTag):
|
|
780
794
|
""" """
|
|
781
|
-
|
|
795
|
+
tag: str = "TextArea"
|
|
782
796
|
_value_class: Type[TextAreaValue] = TextAreaValue
|
|
783
797
|
|
|
784
798
|
|
|
@@ -789,6 +803,6 @@ class TimeSeriesValue(SpanSelection):
|
|
|
789
803
|
|
|
790
804
|
class TimeSeriesLabelsTag(ControlTag):
|
|
791
805
|
""" """
|
|
792
|
-
|
|
806
|
+
tag: str = "TimeSeriesLabels"
|
|
793
807
|
_label_attr_name: str = "timeserieslabels"
|
|
794
808
|
_value_class: Type[TimeSeriesValue] = TimeSeriesValue
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"""
|
|
3
|
+
from typing import Dict, Optional, List, Tuple, Any, Callable, Union
|
|
4
|
+
import xml.etree.ElementTree as ET
|
|
5
|
+
from xml.dom import minidom
|
|
6
|
+
|
|
7
|
+
from label_studio_sdk.label_interface.base import LabelStudioTag
|
|
8
|
+
|
|
9
|
+
import label_studio_sdk.label_interface.object_tags as OT
|
|
10
|
+
import label_studio_sdk.label_interface.control_tags as CT
|
|
11
|
+
|
|
12
|
+
## syntatic sugar helper functions
|
|
13
|
+
|
|
14
|
+
def labels(labels, tag_type="Labels", **kwargs):
|
|
15
|
+
"""
|
|
16
|
+
Converts labels to a tuple structure.
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
labels: The labels to be converted.
|
|
20
|
+
tag_type: Tag type to assign to each label.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
A tuple containing labels as per the tag type.
|
|
24
|
+
"""
|
|
25
|
+
return (tag_type, kwargs, _convert_to_tuple(labels, tag_type="Label"))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def taxonomy(labels, **kwargs):
|
|
29
|
+
"""
|
|
30
|
+
Constructs a taxonomy.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
labels: The labels to construct the taxonomy.
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
A tuple containing the taxonomy.
|
|
37
|
+
"""
|
|
38
|
+
result = []
|
|
39
|
+
|
|
40
|
+
for arg in labels:
|
|
41
|
+
if isinstance(arg, tuple):
|
|
42
|
+
parent, *children = _convert_to_tuple(arg)
|
|
43
|
+
parent_children = (parent[0], parent[1], tuple(children))
|
|
44
|
+
result.append(parent_children)
|
|
45
|
+
else:
|
|
46
|
+
result.append(("Choice", {"value": arg}, {}))
|
|
47
|
+
|
|
48
|
+
return ("Taxonomy", kwargs, tuple(result))
|
|
49
|
+
|
|
50
|
+
def choices(labels, **kwargs):
|
|
51
|
+
"""
|
|
52
|
+
Constructs a choices tuple.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
labels: The labels to be converted.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
A tuple containing the choices.
|
|
59
|
+
"""
|
|
60
|
+
return ("Choices", kwargs, _convert_to_tuple(labels))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _prettify(element):
|
|
64
|
+
"""
|
|
65
|
+
Returns a pretty-printed XML string for the Element.
|
|
66
|
+
"""
|
|
67
|
+
rough_string = ET.tostring(element, 'utf-8')
|
|
68
|
+
reparsed = minidom.parseString(rough_string)
|
|
69
|
+
return reparsed.toprettyxml(indent=" ")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _convert_to_tuple(args, tag_type="Choice"):
|
|
73
|
+
"""
|
|
74
|
+
Converts arguments to a tuple structure of choices.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
args: The labels to be converted.
|
|
78
|
+
tag_type: Tag type to assign to each label.
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
A tuple containing all labels in specified format.
|
|
82
|
+
"""
|
|
83
|
+
if not args:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
return tuple(((tag_type, {"value": arg}, {})) for arg in args)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _create_nested_elements(parent: ET.Element, elements: List[Union[str, Dict[str, str], List]]) -> None:
|
|
90
|
+
"""
|
|
91
|
+
Adds nested elements to the parent element in the Element Tree.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
parent: The parent ET.Element object.
|
|
95
|
+
elements: A list containing element definitions.
|
|
96
|
+
"""
|
|
97
|
+
for element in elements:
|
|
98
|
+
if isinstance(element, str):
|
|
99
|
+
ET.SubElement(parent, element)
|
|
100
|
+
elif isinstance(element, dict):
|
|
101
|
+
for k, v in element.items():
|
|
102
|
+
parent.set(k, v)
|
|
103
|
+
elif isinstance(element, list) or isinstance(element, tuple):
|
|
104
|
+
create_element(element, parent=parent)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _convert(name: str, tag: Union[str, list, tuple, LabelStudioTag]) -> tuple:
|
|
108
|
+
"""
|
|
109
|
+
Converts tags from str, list, tuple, or class instance to a tuple format.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
name: Name of the tag.
|
|
113
|
+
tag: Tag in str, list, tuple or class instance format.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
A tuple version of the input tag.
|
|
117
|
+
|
|
118
|
+
Raises:
|
|
119
|
+
TypeError: If input tag is not a str, list, tuple, or LabelStudioTag.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
if isinstance(tag, LabelStudioTag):
|
|
123
|
+
tag.name = tag.name or name
|
|
124
|
+
child_tag_type = "Choice" if tag.tag in ["Choices", "Taxonomy"] else "Label"
|
|
125
|
+
el = tag.tag, tag.collect_attrs(), _convert_to_tuple(getattr(tag, "labels", None), tag_type=child_tag_type)
|
|
126
|
+
elif isinstance(tag, (list, tuple)):
|
|
127
|
+
el = (*tag, ()) if len(tag) < 3 else tag
|
|
128
|
+
elif isinstance(tag, str):
|
|
129
|
+
el = tag, {}, ()
|
|
130
|
+
else:
|
|
131
|
+
raise TypeError("Input tag must be one of str, list, tuple, LabelStudioTag")
|
|
132
|
+
|
|
133
|
+
el[1].setdefault("name", name)
|
|
134
|
+
|
|
135
|
+
if el[0].lower() in OT._TAG_TO_CLASS and not el[1].get("value"):
|
|
136
|
+
el[1]["value"] = "$" + name
|
|
137
|
+
|
|
138
|
+
return el
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def _find_first_object_tag(tags):
|
|
142
|
+
"""
|
|
143
|
+
Finds the first object tag in the input tags dictionary.
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
tags: A dictionary of tags.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
The first object tag found, or None.
|
|
150
|
+
"""
|
|
151
|
+
for name, tag in tags.items():
|
|
152
|
+
tag_tuple = _convert(name, tag)
|
|
153
|
+
tag_name = tag_tuple[0]
|
|
154
|
+
|
|
155
|
+
if tag_name.lower() in OT._TAG_TO_CLASS:
|
|
156
|
+
return tag_tuple
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def create_element(element: List[Union[str, Dict[str, str], List]], parent=None) -> ET.Element:
|
|
160
|
+
"""
|
|
161
|
+
Creates an XML element.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
element: List of tag, attributes and children to feed into the element.
|
|
165
|
+
parent: Parent element to append the new element to (Optional).
|
|
166
|
+
|
|
167
|
+
Returns:
|
|
168
|
+
An ElementTree element.
|
|
169
|
+
"""
|
|
170
|
+
tag, attrs, children = element
|
|
171
|
+
el = ET.SubElement(parent, tag) if parent is not None else ET.Element(tag)
|
|
172
|
+
|
|
173
|
+
for k, v in attrs.items():
|
|
174
|
+
el.set(k, v)
|
|
175
|
+
|
|
176
|
+
if children:
|
|
177
|
+
_create_nested_elements(el, children)
|
|
178
|
+
|
|
179
|
+
return el
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def tree_from_tuples(*elements):
|
|
183
|
+
"""
|
|
184
|
+
Creates an ElementTree from the input element definitions, and return a string for it.
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
elements: Variable length argument list of elements to add to the ElementTree.
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
XML configuration
|
|
191
|
+
"""
|
|
192
|
+
view = ET.Element('View')
|
|
193
|
+
for element in elements:
|
|
194
|
+
el = create_element(element)
|
|
195
|
+
view.append(el)
|
|
196
|
+
|
|
197
|
+
return view
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def tree_to_string(tree, pretty=True):
|
|
201
|
+
"""
|
|
202
|
+
"""
|
|
203
|
+
if pretty:
|
|
204
|
+
pp = _prettify(tree)
|
|
205
|
+
return pp.replace('<?xml version="1.0" ?>\n', '')
|
|
206
|
+
else:
|
|
207
|
+
return ET.tostring(tree, encoding='unicode')
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def convert_tags_description(tags: Dict[str, Any],
|
|
211
|
+
mapping: Optional[Dict[str, str]]=None) -> List[Union[str, Dict[str, str], List]]:
|
|
212
|
+
"""
|
|
213
|
+
Convert tags into a structured format.
|
|
214
|
+
|
|
215
|
+
Args:
|
|
216
|
+
tags: A dictionary of tags.
|
|
217
|
+
mapping: Optional mapping of tag name transformations.
|
|
218
|
+
|
|
219
|
+
Returns:
|
|
220
|
+
A list of the structured tags.
|
|
221
|
+
"""
|
|
222
|
+
elements = []
|
|
223
|
+
first_object_tag = _find_first_object_tag(tags)
|
|
224
|
+
|
|
225
|
+
for name, tag in tags.items():
|
|
226
|
+
el = _convert(name, tag)
|
|
227
|
+
|
|
228
|
+
# Set `toName` property of the tag if tag name is in
|
|
229
|
+
# CT._TAG_TO_CLASS and `toName` key is not in tag attributes
|
|
230
|
+
# The value of `toName` key is set based on whether `name` of
|
|
231
|
+
# tag is in the mapping dictionary or the `name` of the first
|
|
232
|
+
# object tag if it's not in mapping
|
|
233
|
+
if el[0].lower() in CT._TAG_TO_CLASS and ("toName" not in el[1] or el[1]["toName"] is None):
|
|
234
|
+
if mapping and el[1].get("name") in mapping:
|
|
235
|
+
el[1]["toName"] = mapping.get(el[1]["name"])
|
|
236
|
+
else:
|
|
237
|
+
el[1]["toName"] = first_object_tag[1]["name"]
|
|
238
|
+
|
|
239
|
+
elements.append(el)
|
|
240
|
+
|
|
241
|
+
return elements
|
|
@@ -22,6 +22,7 @@ from label_studio_sdk._legacy.exceptions import (
|
|
|
22
22
|
LabelStudioValidationErrorSentryIgnored,
|
|
23
23
|
)
|
|
24
24
|
|
|
25
|
+
from .base import LabelStudioTag
|
|
25
26
|
from .control_tags import (
|
|
26
27
|
ControlTag,
|
|
27
28
|
ChoicesTag,
|
|
@@ -30,6 +31,7 @@ from .control_tags import (
|
|
|
30
31
|
from .object_tags import ObjectTag
|
|
31
32
|
from .label_tags import LabelTag
|
|
32
33
|
from .objects import AnnotationValue, TaskValue, PredictionValue
|
|
34
|
+
from . import create as CE
|
|
33
35
|
|
|
34
36
|
|
|
35
37
|
dir_path = os.path.dirname(os.path.realpath(__file__))
|
|
@@ -189,6 +191,67 @@ class LabelInterface:
|
|
|
189
191
|
```
|
|
190
192
|
"""
|
|
191
193
|
|
|
194
|
+
@classmethod
|
|
195
|
+
def create(cls, tags, mapping=None, title=None, style=None, pretty=True, *args, **kwargs):
|
|
196
|
+
""" Simple way of create UI config, it helps you not to thing much about the name/toName mapping
|
|
197
|
+
|
|
198
|
+
LabelInterface.create_simple({
|
|
199
|
+
"txt": "Text",
|
|
200
|
+
"chc": choices("positive", "negative")
|
|
201
|
+
})
|
|
202
|
+
"""
|
|
203
|
+
tuples = CE.convert_tags_description(tags, mapping=mapping)
|
|
204
|
+
|
|
205
|
+
if isinstance(title, str):
|
|
206
|
+
tuples = (("Header", { "value": title }, {}),) + tuples
|
|
207
|
+
|
|
208
|
+
# in case we have either title or style, then we can iterate
|
|
209
|
+
# through the tuples and modify the tree
|
|
210
|
+
if isinstance(title, dict) or isinstance(style, dict):
|
|
211
|
+
new_tuples = []
|
|
212
|
+
for t in tuples:
|
|
213
|
+
tag, attributes, children = t
|
|
214
|
+
name = attributes.get("name", None)
|
|
215
|
+
|
|
216
|
+
# prepend Header tag to the list
|
|
217
|
+
if isinstance(title, dict) and name in title:
|
|
218
|
+
title_tag = ("Header", { "value": title.get(name) }, {})
|
|
219
|
+
new_tuples.append(title_tag)
|
|
220
|
+
|
|
221
|
+
# modify the style of the element by wrapping it into
|
|
222
|
+
# a View with style
|
|
223
|
+
if isinstance(style, dict) and name in style:
|
|
224
|
+
parent_tag = ("View", { "style": style.get(name) }, (t,))
|
|
225
|
+
new_tuples.append(parent_tag)
|
|
226
|
+
else:
|
|
227
|
+
new_tuples.append(t)
|
|
228
|
+
|
|
229
|
+
tuples = new_tuples
|
|
230
|
+
|
|
231
|
+
tree = CE.tree_from_tuples(*tuples)
|
|
232
|
+
|
|
233
|
+
return CE.tree_to_string(tree, pretty=pretty)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
@classmethod
|
|
237
|
+
def create_instance(cls, *args, **kwargs):
|
|
238
|
+
"""Create instance is a shortcut to create a config and then
|
|
239
|
+
parse it right away returning the LabelInterface object
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
li = LabelInterface.create_instance({ "txt": "Text", "lbl": labels(("person", "org")) })
|
|
243
|
+
lbl = li.get_control("lbl")
|
|
244
|
+
reg = lbl.label("person", start=0, end=10)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
The above returns a region that could be serialized to Label
|
|
248
|
+
Studio JSON format and uploaded to Label Studio
|
|
249
|
+
|
|
250
|
+
"""
|
|
251
|
+
config = cls.create(*args, **kwargs)
|
|
252
|
+
return cls(config=config, **kwargs)
|
|
253
|
+
|
|
254
|
+
|
|
192
255
|
def __init__(self, config: str, tags_mapping=None, *args, **kwargs):
|
|
193
256
|
"""
|
|
194
257
|
Initialize a LabelInterface instance using a config string.
|
|
@@ -240,6 +303,11 @@ class LabelInterface:
|
|
|
240
303
|
|
|
241
304
|
##### NEW API
|
|
242
305
|
|
|
306
|
+
@property
|
|
307
|
+
def config(self):
|
|
308
|
+
"""Returns the XML configuration string"""
|
|
309
|
+
return self._config
|
|
310
|
+
|
|
243
311
|
@property
|
|
244
312
|
def controls(self):
|
|
245
313
|
"""Returns list of control tags"""
|
|
@@ -144,7 +144,14 @@ class ObjectTag(LabelStudioTag):
|
|
|
144
144
|
"""Check if value has variable"""
|
|
145
145
|
pattern = re.compile(r"^\$[^, ]+$")
|
|
146
146
|
return bool(pattern.fullmatch(self.value))
|
|
147
|
-
|
|
147
|
+
|
|
148
|
+
def collect_attrs(self):
|
|
149
|
+
"""Return tag attrs as a single dict"""
|
|
150
|
+
return {
|
|
151
|
+
**self.attr,
|
|
152
|
+
"name": self.name,
|
|
153
|
+
"value": '$' + self.value if self.value is not None else None
|
|
154
|
+
}
|
|
148
155
|
|
|
149
156
|
# and have generate_example in each
|
|
150
157
|
def generate_example_value(self, mode="upload", secure_mode=False):
|
|
@@ -174,7 +181,8 @@ class ObjectTag(LabelStudioTag):
|
|
|
174
181
|
|
|
175
182
|
class AudioTag(ObjectTag):
|
|
176
183
|
""" """
|
|
177
|
-
|
|
184
|
+
tag: str = "Audio"
|
|
185
|
+
|
|
178
186
|
def _generate_example(self, examples, only_urls=False):
|
|
179
187
|
""" """
|
|
180
188
|
return examples.get("Audio")
|
|
@@ -182,7 +190,8 @@ class AudioTag(ObjectTag):
|
|
|
182
190
|
|
|
183
191
|
class ImageTag(ObjectTag):
|
|
184
192
|
""" """
|
|
185
|
-
|
|
193
|
+
tag: str = "Image"
|
|
194
|
+
|
|
186
195
|
def _generate_example(self, examples, only_urls=False):
|
|
187
196
|
""" """
|
|
188
197
|
return examples.get("Image")
|
|
@@ -190,7 +199,8 @@ class ImageTag(ObjectTag):
|
|
|
190
199
|
|
|
191
200
|
class TableTag(ObjectTag):
|
|
192
201
|
""" """
|
|
193
|
-
|
|
202
|
+
tag: str = "Table"
|
|
203
|
+
|
|
194
204
|
def _generate_example(self, examples, only_urls=False):
|
|
195
205
|
""" """
|
|
196
206
|
return examples.get("Table")
|
|
@@ -198,7 +208,8 @@ class TableTag(ObjectTag):
|
|
|
198
208
|
|
|
199
209
|
class TextTag(ObjectTag):
|
|
200
210
|
""" """
|
|
201
|
-
|
|
211
|
+
tag: str = "Text"
|
|
212
|
+
|
|
202
213
|
def _generate_example(self, examples, only_urls=False):
|
|
203
214
|
""" """
|
|
204
215
|
if only_urls:
|
|
@@ -209,7 +220,8 @@ class TextTag(ObjectTag):
|
|
|
209
220
|
|
|
210
221
|
class VideoTag(ObjectTag):
|
|
211
222
|
""" """
|
|
212
|
-
|
|
223
|
+
tag: str = "Video"
|
|
224
|
+
|
|
213
225
|
def _generate_example(self, examples, only_urls=False):
|
|
214
226
|
""" """
|
|
215
227
|
return examples.get("Video")
|
|
@@ -217,7 +229,8 @@ class VideoTag(ObjectTag):
|
|
|
217
229
|
|
|
218
230
|
class HyperTextTag(ObjectTag):
|
|
219
231
|
""" """
|
|
220
|
-
|
|
232
|
+
tag: str = "HyperText"
|
|
233
|
+
|
|
221
234
|
def _generate_example(self, examples, only_urls=False):
|
|
222
235
|
""" """
|
|
223
236
|
examples = data_examples(mode="upload")
|
|
@@ -229,7 +242,8 @@ class HyperTextTag(ObjectTag):
|
|
|
229
242
|
|
|
230
243
|
class ListTag(ObjectTag):
|
|
231
244
|
""" """
|
|
232
|
-
|
|
245
|
+
tag: str = "List"
|
|
246
|
+
|
|
233
247
|
def _generate_example(self, examples, only_urls=False):
|
|
234
248
|
""" """
|
|
235
249
|
examples = data_examples(mode="upload")
|
|
@@ -238,7 +252,8 @@ class ListTag(ObjectTag):
|
|
|
238
252
|
|
|
239
253
|
class ParagraphsTag(ObjectTag):
|
|
240
254
|
""" """
|
|
241
|
-
|
|
255
|
+
tag: str = "Paragraphs"
|
|
256
|
+
|
|
242
257
|
def _generate_example(self, examples, only_urls=False):
|
|
243
258
|
""" """
|
|
244
259
|
# Paragraphs special case - replace nameKey/textKey if presented
|
|
@@ -259,7 +274,8 @@ class ParagraphsTag(ObjectTag):
|
|
|
259
274
|
|
|
260
275
|
class TimeSeriesTag(ObjectTag):
|
|
261
276
|
""" """
|
|
262
|
-
|
|
277
|
+
tag: str = "TimeSeries"
|
|
278
|
+
|
|
263
279
|
def _generate_example(self, examples, only_urls=False):
|
|
264
280
|
""" """
|
|
265
281
|
p = self.attr
|
|
@@ -20,13 +20,13 @@ def serialize_regions(result):
|
|
|
20
20
|
|
|
21
21
|
class PredictionValue(BaseModel):
|
|
22
22
|
""" """
|
|
23
|
-
|
|
24
23
|
model_version: Optional[Any] = None
|
|
25
24
|
score: Optional[float] = 0.00
|
|
26
25
|
result: Optional[List[Union[Dict[str, Any], Region]]]
|
|
27
26
|
|
|
28
27
|
class Config:
|
|
29
|
-
|
|
28
|
+
populate_by_name = True
|
|
29
|
+
protected_namespaces = ()
|
|
30
30
|
|
|
31
31
|
@field_serializer('result')
|
|
32
32
|
def serialize_result(self, result):
|
|
@@ -45,7 +45,7 @@ class AnnotationValue(BaseModel):
|
|
|
45
45
|
result: Optional[List[Union[Dict[str, Any], Region]]]
|
|
46
46
|
|
|
47
47
|
class Config:
|
|
48
|
-
|
|
48
|
+
populate_by_name = True
|
|
49
49
|
|
|
50
50
|
@field_serializer('result')
|
|
51
51
|
def serialize_result(self, result):
|
|
@@ -56,5 +56,5 @@ class TaskValue(BaseModel):
|
|
|
56
56
|
""" """
|
|
57
57
|
|
|
58
58
|
data: Optional[dict]
|
|
59
|
-
annotations: Optional[List[AnnotationValue]]
|
|
60
|
-
predictions: Optional[List[PredictionValue]]
|
|
59
|
+
annotations: Optional[List[AnnotationValue]] = Field(default_factory=list)
|
|
60
|
+
predictions: Optional[List[PredictionValue]] = Field(default_factory=list)
|