label-studio-sdk 0.0.30__py3-none-any.whl → 0.0.34__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 +4 -1
- label_studio_sdk/client.py +104 -85
- label_studio_sdk/data_manager.py +32 -23
- label_studio_sdk/exceptions.py +10 -0
- label_studio_sdk/label_interface/__init__.py +1 -0
- label_studio_sdk/label_interface/base.py +77 -0
- label_studio_sdk/label_interface/control_tags.py +756 -0
- label_studio_sdk/label_interface/interface.py +922 -0
- label_studio_sdk/label_interface/label_tags.py +72 -0
- label_studio_sdk/label_interface/object_tags.py +292 -0
- label_studio_sdk/label_interface/region.py +43 -0
- label_studio_sdk/objects.py +35 -0
- label_studio_sdk/project.py +725 -262
- label_studio_sdk/schema/label_config_schema.json +226 -0
- label_studio_sdk/users.py +15 -13
- label_studio_sdk/utils.py +31 -30
- label_studio_sdk/workspaces.py +13 -11
- {label_studio_sdk-0.0.30.dist-info → label_studio_sdk-0.0.34.dist-info}/METADATA +7 -5
- label_studio_sdk-0.0.34.dist-info/RECORD +37 -0
- {label_studio_sdk-0.0.30.dist-info → label_studio_sdk-0.0.34.dist-info}/WHEEL +1 -1
- {label_studio_sdk-0.0.30.dist-info → label_studio_sdk-0.0.34.dist-info}/top_level.txt +0 -1
- tests/test_client.py +21 -10
- tests/test_export.py +105 -0
- tests/test_interface/__init__.py +1 -0
- tests/test_interface/configs.py +137 -0
- tests/test_interface/mockups.py +22 -0
- tests/test_interface/test_compat.py +64 -0
- tests/test_interface/test_control_tags.py +55 -0
- tests/test_interface/test_data_generation.py +45 -0
- tests/test_interface/test_lpi.py +15 -0
- tests/test_interface/test_main.py +196 -0
- tests/test_interface/test_object_tags.py +36 -0
- tests/test_interface/test_region.py +36 -0
- tests/test_interface/test_validate_summary.py +35 -0
- tests/test_interface/test_validation.py +59 -0
- docs/__init__.py +0 -3
- label_studio_sdk-0.0.30.dist-info/RECORD +0 -15
- {label_studio_sdk-0.0.30.dist-info → label_studio_sdk-0.0.34.dist-info}/LICENSE +0 -0
tests/test_export.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
from unittest.mock import Mock
|
|
2
|
+
|
|
3
|
+
import requests_mock
|
|
4
|
+
|
|
5
|
+
from label_studio_sdk.client import Client
|
|
6
|
+
from label_studio_sdk.data_manager import Filters, Operator, Type, Column
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_client_headers():
|
|
10
|
+
mock_session = Mock(spec=["request"])
|
|
11
|
+
mock_session.request.return_value = Mock(status_code=200)
|
|
12
|
+
client = Client(
|
|
13
|
+
url="http://fake.url",
|
|
14
|
+
api_key="fake_key",
|
|
15
|
+
session=mock_session,
|
|
16
|
+
versions={"label-studio": "1.0.0"},
|
|
17
|
+
extra_headers={"Proxy-Authorization": "Bearer fake_bearer"},
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
client.check_connection()
|
|
21
|
+
args, kwargs = mock_session.request.call_args
|
|
22
|
+
assert kwargs["headers"] == {
|
|
23
|
+
"Authorization": f"Token fake_key",
|
|
24
|
+
"Proxy-Authorization": "Bearer fake_bearer",
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_client_no_extra_headers():
|
|
29
|
+
mock_session = Mock(spec=["request"])
|
|
30
|
+
mock_session.request.return_value = Mock(status_code=200)
|
|
31
|
+
client = Client(
|
|
32
|
+
url="http://fake.url",
|
|
33
|
+
api_key="fake_key",
|
|
34
|
+
session=mock_session,
|
|
35
|
+
versions={"label-studio": "1.0.0"},
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
client.check_connection()
|
|
39
|
+
args, kwargs = mock_session.request.call_args
|
|
40
|
+
assert kwargs["headers"] == {"Authorization": f"Token fake_key"}
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_project_export_with_filters():
|
|
44
|
+
with requests_mock.Mocker() as m:
|
|
45
|
+
m.get(
|
|
46
|
+
'http://fake.url/api/version',
|
|
47
|
+
json={"version": "1.0.0"},
|
|
48
|
+
status_code=200,
|
|
49
|
+
)
|
|
50
|
+
m.get(
|
|
51
|
+
'http://fake.url/api/projects/1',
|
|
52
|
+
json={"id": 1, "title": "fake_project"},
|
|
53
|
+
status_code=200,
|
|
54
|
+
)
|
|
55
|
+
m.post(
|
|
56
|
+
'http://fake.url/api/projects/1/exports',
|
|
57
|
+
json={"id": 1, "title": "fake_project"},
|
|
58
|
+
status_code=200,
|
|
59
|
+
)
|
|
60
|
+
m.post(
|
|
61
|
+
'http://fake.url/api/dm/views',
|
|
62
|
+
json={"id": 1, "title": "fake_project"},
|
|
63
|
+
status_code=200,
|
|
64
|
+
)
|
|
65
|
+
m.post(
|
|
66
|
+
'http://fake.url/api/projects/1/exports',
|
|
67
|
+
json={"id": 1, "title": "fake_project"},
|
|
68
|
+
status_code=200,
|
|
69
|
+
)
|
|
70
|
+
m.get(
|
|
71
|
+
'http://fake.url/api/projects/1/exports/1',
|
|
72
|
+
json={"id": 1, "title": "fake_project", "status": "completed"},
|
|
73
|
+
status_code=200,
|
|
74
|
+
)
|
|
75
|
+
m.get(
|
|
76
|
+
'http://fake.url/api/projects/1/exports/1/download?exportType=JSON',
|
|
77
|
+
headers={"Content-Disposition": "attachment; filename=fake_project.json"},
|
|
78
|
+
status_code=200,
|
|
79
|
+
)
|
|
80
|
+
m.delete(
|
|
81
|
+
'http://fake.url/api/dm/views/1',
|
|
82
|
+
status_code=200,
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
filters = Filters.create(
|
|
86
|
+
Filters.AND,
|
|
87
|
+
[
|
|
88
|
+
Filters.item(
|
|
89
|
+
Column.inner_id,
|
|
90
|
+
Operator.GREATER_OR_EQUAL,
|
|
91
|
+
Type.Number,
|
|
92
|
+
Filters.value(1),
|
|
93
|
+
),
|
|
94
|
+
Filters.item(
|
|
95
|
+
Column.inner_id,
|
|
96
|
+
Operator.LESS,
|
|
97
|
+
Type.Number,
|
|
98
|
+
Filters.value(100),
|
|
99
|
+
),
|
|
100
|
+
],
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
ls = Client(url='http://fake.url', api_key='fake_key')
|
|
104
|
+
project = ls.get_project(1)
|
|
105
|
+
project.export(filters=filters)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
FROM_NAME = "from_name"
|
|
2
|
+
FROM_NAME_PREFIX = "from"
|
|
3
|
+
TO_NAME = "to_name"
|
|
4
|
+
ANOTHER_NAME = "another_name"
|
|
5
|
+
ANOTHER_TO_NAME = "another_to_name"
|
|
6
|
+
|
|
7
|
+
VALUE = "$var"
|
|
8
|
+
VALUE_KEY = "var"
|
|
9
|
+
ANOTHER_VALUE = "$var2"
|
|
10
|
+
|
|
11
|
+
LABEL1 = "yes"
|
|
12
|
+
LABEL2 = "no"
|
|
13
|
+
|
|
14
|
+
CORRECT_TASK = {VALUE_KEY: "value"}
|
|
15
|
+
|
|
16
|
+
CORRECT_REGION = {
|
|
17
|
+
"from_name": FROM_NAME,
|
|
18
|
+
"to_name": TO_NAME,
|
|
19
|
+
"type": "choices",
|
|
20
|
+
"value": {"choices": [LABEL1]},
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
SIMPLE_CONF = f"""
|
|
24
|
+
<View>
|
|
25
|
+
<Text name="{TO_NAME}" value="{VALUE}" />
|
|
26
|
+
<Choices name="{FROM_NAME}" toName="{TO_NAME}">
|
|
27
|
+
<Choice value="{LABEL1}" />
|
|
28
|
+
<Choice value="{LABEL2}" />
|
|
29
|
+
</Choices>
|
|
30
|
+
</View>
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
SIMPLE_WRONG_CONF = f"""
|
|
34
|
+
<View>
|
|
35
|
+
<Text name="{TO_NAME}" value="{VALUE}" />
|
|
36
|
+
<Text name="{TO_NAME}" value="{VALUE}" />
|
|
37
|
+
<Choices name="{FROM_NAME}" toName="{ANOTHER_TO_NAME}">
|
|
38
|
+
<Choice value="{LABEL1}" />
|
|
39
|
+
<Choice value="{LABEL2}" />
|
|
40
|
+
</Choices>
|
|
41
|
+
</View>
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
CONF_WITH_COMMENT = (
|
|
45
|
+
'<!-- {"data": { "hello": "world" }, "predictions": [], "annotations": [] } -->'
|
|
46
|
+
+ f"""{SIMPLE_CONF}"""
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
CONF_COMPLEX = f"""
|
|
50
|
+
<View>
|
|
51
|
+
<Labels name="label" toName="text">
|
|
52
|
+
<Label value="PER" background="red"/>
|
|
53
|
+
<Label value="ORG" background="darkorange"/>
|
|
54
|
+
<Label value="LOC" background="orange"/>
|
|
55
|
+
<Label value="MISC" background="green"/>
|
|
56
|
+
</Labels>
|
|
57
|
+
|
|
58
|
+
<Text name="text" value="$text"/>
|
|
59
|
+
<Choices name="sentiment" toName="text"
|
|
60
|
+
choice="single" showInLine="true">
|
|
61
|
+
<Choice value="Positive"/>
|
|
62
|
+
<Choice value="Negative"/>
|
|
63
|
+
<Choice value="Neutral"/>
|
|
64
|
+
</Choices>
|
|
65
|
+
</View>
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
TWO_TONAMES = f"""
|
|
69
|
+
<View>
|
|
70
|
+
<Text name="{TO_NAME}" value="{VALUE}" />
|
|
71
|
+
<Text name="{ANOTHER_TO_NAME}" value="{VALUE}" />
|
|
72
|
+
<Choices name="{FROM_NAME}" toName="{TO_NAME},{ANOTHER_TO_NAME}">
|
|
73
|
+
<Choice value="{LABEL1}" />
|
|
74
|
+
<Choice value="{LABEL2}" />
|
|
75
|
+
</Choices>
|
|
76
|
+
</View>
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
TEXTAREA_CONF = f"""
|
|
80
|
+
<View>
|
|
81
|
+
<Textarea name="{FROM_NAME}" toName="{TO_NAME}" />
|
|
82
|
+
<Text name="{TO_NAME}" value="{VALUE}" />
|
|
83
|
+
|
|
84
|
+
<Choices name="{ANOTHER_NAME}" toName="{TO_NAME}">
|
|
85
|
+
<Choice value="{LABEL1}" />
|
|
86
|
+
<Choice value="{LABEL2}" />
|
|
87
|
+
</Choices>
|
|
88
|
+
</View>
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
VIDEO_CONF = f"""
|
|
92
|
+
<View>
|
|
93
|
+
<Labels name="videoLabels" toName="video">
|
|
94
|
+
<Label value="Car"/>
|
|
95
|
+
<Label value="Person"/>
|
|
96
|
+
</Labels>
|
|
97
|
+
<Video name="video" value="$video"/>
|
|
98
|
+
<VideoRectangle name="box" toName="video"/>
|
|
99
|
+
</View>
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
NO_DYNAMIC_LABELS_CONF = f"""
|
|
103
|
+
<View>
|
|
104
|
+
<Header value="Select label and click the image to start"/>
|
|
105
|
+
<Image name="image" value="$image" zoom="true"/>
|
|
106
|
+
<PolygonLabels name="label" toName="image"
|
|
107
|
+
strokeWidth="3" pointSize="small"
|
|
108
|
+
opacity="0.9" />
|
|
109
|
+
</View>
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
DYNAMIC_LABELS_CONF = f"""
|
|
113
|
+
<View>
|
|
114
|
+
<Header value="Select label and click the image to start"/>
|
|
115
|
+
<Image name="image" value="$image" zoom="true"/>
|
|
116
|
+
<PolygonLabels name="label" toName="image"
|
|
117
|
+
strokeWidth="3" pointSize="small"
|
|
118
|
+
opacity="0.9" value="$options" />
|
|
119
|
+
</View>
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
RECT_CONFIG = f"""
|
|
123
|
+
<View>
|
|
124
|
+
<Image name="{TO_NAME}" value="{VALUE}" />
|
|
125
|
+
<Rectangle name="{FROM_NAME}" toName="{TO_NAME}"></Rectangle>
|
|
126
|
+
</View>
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
EMPTY_VALUE_CONF = f"""
|
|
130
|
+
<View>
|
|
131
|
+
<Text name="text" value="$text"/>
|
|
132
|
+
<Labels name="type" toName="text">
|
|
133
|
+
<Label value="" />
|
|
134
|
+
<Label value="" />
|
|
135
|
+
</Labels>
|
|
136
|
+
</View>
|
|
137
|
+
"""
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
# Example of the output of summary table for a specific project
|
|
4
|
+
# project_id created_at all_data_columns common_data_columns created_annotations created_labels created_labels_drafts
|
|
5
|
+
# ---------- -------------------------- ----------------------------- --------------------- ----------------------------------------------------- ------------------------------------------------------------ ----------------------------------------------------
|
|
6
|
+
# 9 2024-03-02 20:47:43.710926 {"text": 20, "sentiment": 20} ["sentiment", "text"] {"label|text|labels": 2, "sentiment|text|choices": 2} {"label": {"PER": 1, "ORG": 1}, "sentiment": {"Positive": 1, {"label": {"MISC": 1}, "sentiment": {"Negative": 1}}
|
|
7
|
+
# "Negative": 1}}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SummaryMockup:
|
|
11
|
+
"""
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
created_labels = json.loads(
|
|
15
|
+
'{"label": {"PER": 1, "ORG": 1}, "sentiment": {"Positive": 1, "Negative": 1}}'
|
|
16
|
+
)
|
|
17
|
+
created_labels_drafts = json.loads(
|
|
18
|
+
'{"label": {"MISC": 1}, "sentiment": {"Negative": 1}}'
|
|
19
|
+
)
|
|
20
|
+
created_annotations = json.loads(
|
|
21
|
+
'{"label|text|labels": 2, "sentiment|text|choices": 2}'
|
|
22
|
+
)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
## Testing compatibility functions
|
|
2
|
+
##
|
|
3
|
+
## Compatibility function is a function that was implemented in
|
|
4
|
+
## label_studio.core.label_config or tools and is reimplemented within
|
|
5
|
+
## SDK using new LabelInterface
|
|
6
|
+
|
|
7
|
+
import pytest
|
|
8
|
+
|
|
9
|
+
from label_studio_sdk.label_interface import LabelInterface
|
|
10
|
+
from label_studio_sdk.objects import PredictionValue
|
|
11
|
+
|
|
12
|
+
from . import configs as c
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_get_first_tag_occurence_simple():
|
|
16
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
17
|
+
from_name, to_name, value = conf.get_first_tag_occurence("Choices", "Text")
|
|
18
|
+
|
|
19
|
+
assert from_name == c.FROM_NAME
|
|
20
|
+
assert to_name == c.TO_NAME
|
|
21
|
+
assert value == c.VALUE_KEY
|
|
22
|
+
|
|
23
|
+
with pytest.raises(ValueError):
|
|
24
|
+
conf.get_first_tag_occurence("Choices", "Image")
|
|
25
|
+
|
|
26
|
+
with pytest.raises(ValueError):
|
|
27
|
+
conf.get_first_tag_occurence("Labels", "Text")
|
|
28
|
+
|
|
29
|
+
with pytest.raises(ValueError):
|
|
30
|
+
conf.get_first_tag_occurence("Labels", "Image")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_get_first_tag_occurence_complex():
|
|
34
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
35
|
+
from_name, to_name, value = conf.get_first_tag_occurence(
|
|
36
|
+
"Choices",
|
|
37
|
+
("Image", "Text"),
|
|
38
|
+
name_filter=lambda s: s.startswith(c.FROM_NAME_PREFIX),
|
|
39
|
+
to_name_filter=lambda s: s == c.TO_NAME,
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
assert from_name == c.FROM_NAME
|
|
43
|
+
assert to_name == c.TO_NAME
|
|
44
|
+
assert value == c.VALUE_KEY
|
|
45
|
+
|
|
46
|
+
with pytest.raises(ValueError):
|
|
47
|
+
conf.get_first_tag_occurence(
|
|
48
|
+
"Choices",
|
|
49
|
+
("Image", "Text"),
|
|
50
|
+
name_filter=lambda s: s.startswith("wrong_prefix"),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
with pytest.raises(ValueError):
|
|
54
|
+
conf.get_first_tag_occurence(
|
|
55
|
+
"Choices", ("Image", "Text"), to_name_filter=lambda s: s == "wrong_name"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def test_is_video_object_tracking():
|
|
60
|
+
conf1 = LabelInterface(c.SIMPLE_CONF)
|
|
61
|
+
assert conf1.is_video_object_tracking() is False
|
|
62
|
+
|
|
63
|
+
conf2 = LabelInterface(c.VIDEO_CONF)
|
|
64
|
+
assert conf2.is_video_object_tracking() is True
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from lxml.etree import Element
|
|
3
|
+
|
|
4
|
+
from label_studio_sdk.label_interface import LabelInterface
|
|
5
|
+
from label_studio_sdk.label_interface.control_tags import ControlTag
|
|
6
|
+
|
|
7
|
+
import tests.test_interface.configs as c
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def test_parse():
|
|
11
|
+
tag = Element(
|
|
12
|
+
"tag",
|
|
13
|
+
{"name": "my_name", "toName": "name1,name2", "apiUrl": "http://myapi.com"},
|
|
14
|
+
)
|
|
15
|
+
control_tag = ControlTag.parse_node(tag)
|
|
16
|
+
|
|
17
|
+
assert isinstance(control_tag, ControlTag)
|
|
18
|
+
assert control_tag.tag == "tag"
|
|
19
|
+
assert control_tag.name == "my_name"
|
|
20
|
+
assert control_tag.to_name == ["name1", "name2"]
|
|
21
|
+
assert control_tag.dynamic_value == True
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_validate():
|
|
25
|
+
tag = Element("tag", {"name": "my_name", "toName": "name1,name2"})
|
|
26
|
+
is_control_tag1 = ControlTag.validate_node(tag)
|
|
27
|
+
|
|
28
|
+
assert is_control_tag1 == True
|
|
29
|
+
|
|
30
|
+
tag2 = Element("not_control_tag", {"name": "my_name"})
|
|
31
|
+
is_control_tag2 = ControlTag.validate_node(tag2)
|
|
32
|
+
|
|
33
|
+
assert is_control_tag2 == False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_textarea_label():
|
|
37
|
+
conf = LabelInterface(c.TEXTAREA_CONF)
|
|
38
|
+
|
|
39
|
+
region = conf.get_control(c.FROM_NAME).label(text=["Hello", "World"])
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_label_with_choices():
|
|
43
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
44
|
+
region = conf.get_control().label(label=c.LABEL1)
|
|
45
|
+
|
|
46
|
+
rjs = region.to_json()
|
|
47
|
+
assert isinstance(rjs, str)
|
|
48
|
+
|
|
49
|
+
rpy = json.loads(rjs)
|
|
50
|
+
assert rpy["from_name"] == c.FROM_NAME
|
|
51
|
+
assert rpy["to_name"] == c.TO_NAME
|
|
52
|
+
assert "value" in rpy
|
|
53
|
+
|
|
54
|
+
assert "choices" in rpy.get("value")
|
|
55
|
+
assert c.LABEL1 in rpy["value"]["choices"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from lxml.etree import Element
|
|
2
|
+
|
|
3
|
+
from label_studio_sdk.label_interface import LabelInterface
|
|
4
|
+
from label_studio_sdk.label_interface.object_tags import ObjectTag
|
|
5
|
+
import tests.test_interface.configs as c
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_generate_sample_task():
|
|
9
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
10
|
+
task = conf.generate_sample_task()
|
|
11
|
+
value = c.VALUE[1:]
|
|
12
|
+
|
|
13
|
+
print(task)
|
|
14
|
+
|
|
15
|
+
assert value in task
|
|
16
|
+
assert len(task[value])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_generate_url():
|
|
20
|
+
"""Quick check that each object tag generates the right data
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def url_validator(url):
|
|
24
|
+
assert url.startswith("https://") or url.startswith("http://")
|
|
25
|
+
|
|
26
|
+
# TODO need to add other validators
|
|
27
|
+
m = {
|
|
28
|
+
"Audio": url_validator,
|
|
29
|
+
"Image": url_validator,
|
|
30
|
+
# "Table": None,
|
|
31
|
+
"Text": url_validator,
|
|
32
|
+
"Video": url_validator,
|
|
33
|
+
# "HyperText": None,
|
|
34
|
+
# "List": None,
|
|
35
|
+
"Paragraphs": url_validator,
|
|
36
|
+
# "TimeSeries": url_validator
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
for tag_name, validator in m.items():
|
|
40
|
+
|
|
41
|
+
tag = Element(tag_name, {"name": "my_name", "value": "my_value"})
|
|
42
|
+
inst = ObjectTag.parse_node(tag)
|
|
43
|
+
|
|
44
|
+
res = inst.generate_example_value(mode="editor_preview", secure_mode=True)
|
|
45
|
+
validator(res)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from label_studio_sdk.label_interface.region import Region
|
|
2
|
+
from label_studio_sdk.label_interface.object_tags import ImageTag
|
|
3
|
+
from label_studio_sdk.label_interface.control_tags import RectangleTag
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_li():
|
|
7
|
+
"""Test using Label Interface to label things
|
|
8
|
+
"""
|
|
9
|
+
img = ImageTag(
|
|
10
|
+
name="img", tag="image", value="http://example.com/image.jpg", attr={}
|
|
11
|
+
)
|
|
12
|
+
rect = RectangleTag(name="rect", to_name=["img"], tag="rectangle", attr={})
|
|
13
|
+
rect.set_object(img)
|
|
14
|
+
|
|
15
|
+
region = rect.label(x=10, y=10, width=10, height=10, rotation=10)
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
"""
|
|
2
|
+
"""
|
|
3
|
+
import json
|
|
4
|
+
import pytest
|
|
5
|
+
import xmljson
|
|
6
|
+
import copy
|
|
7
|
+
|
|
8
|
+
from label_studio_sdk.objects import PredictionValue
|
|
9
|
+
from label_studio_sdk.label_interface import LabelInterface
|
|
10
|
+
from label_studio_sdk.label_interface.control_tags import (
|
|
11
|
+
ControlTag,
|
|
12
|
+
ChoicesTag,
|
|
13
|
+
LabelsTag,
|
|
14
|
+
Region,
|
|
15
|
+
)
|
|
16
|
+
from label_studio_sdk.exceptions import LabelStudioValidationErrorSentryIgnored
|
|
17
|
+
|
|
18
|
+
# from label_studio_sdk.label_config.regions import Region
|
|
19
|
+
import tests.test_interface.configs as c
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
## testing basic functionality
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def test_parse_configs():
|
|
26
|
+
conf1 = LabelInterface(c.SIMPLE_CONF)
|
|
27
|
+
conf2 = LabelInterface(c.VIDEO_CONF)
|
|
28
|
+
conf3 = LabelInterface(c.DYNAMIC_LABELS_CONF)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_accessors():
|
|
32
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
33
|
+
|
|
34
|
+
c1 = conf.get_control()
|
|
35
|
+
assert c1.name == c.FROM_NAME
|
|
36
|
+
|
|
37
|
+
c2 = conf.get_control(c.FROM_NAME)
|
|
38
|
+
assert c2.name == c.FROM_NAME
|
|
39
|
+
|
|
40
|
+
o1 = conf.get_object(c.TO_NAME)
|
|
41
|
+
assert o1.name == c.TO_NAME
|
|
42
|
+
|
|
43
|
+
o2 = c2.get_object(c.TO_NAME)
|
|
44
|
+
assert o2.name == c.TO_NAME
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_parse_two_to_names():
|
|
48
|
+
conf = LabelInterface(c.TWO_TONAMES)
|
|
49
|
+
ctrl = conf.get_control()
|
|
50
|
+
|
|
51
|
+
assert isinstance(ctrl, ControlTag)
|
|
52
|
+
|
|
53
|
+
with pytest.raises(Exception):
|
|
54
|
+
obj = conf.get_object()
|
|
55
|
+
|
|
56
|
+
obj1 = conf.get_object(c.TO_NAME)
|
|
57
|
+
assert obj1.name == c.TO_NAME
|
|
58
|
+
|
|
59
|
+
obj2 = conf.get_object(c.ANOTHER_TO_NAME)
|
|
60
|
+
assert obj2.name == c.ANOTHER_TO_NAME
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_parse_textarea():
|
|
64
|
+
conf = LabelInterface(c.TEXTAREA_CONF)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# def test_parse_config_to_json():
|
|
68
|
+
# json = LabelInterface.parse_config_to_json(c.SIMPLE_CONF)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_to_name_validation():
|
|
72
|
+
LabelInterface._to_name_validation(None, c.SIMPLE_CONF)
|
|
73
|
+
|
|
74
|
+
with pytest.raises(LabelStudioValidationErrorSentryIgnored):
|
|
75
|
+
LabelInterface._to_name_validation(None, c.SIMPLE_WRONG_CONF)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_unique_names_validation():
|
|
79
|
+
with pytest.raises(LabelStudioValidationErrorSentryIgnored):
|
|
80
|
+
LabelInterface._unique_names_validation(None, c.SIMPLE_WRONG_CONF)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_get_sample_task():
|
|
84
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
85
|
+
task, _, _ = conf._sample_task()
|
|
86
|
+
value = c.VALUE[1:]
|
|
87
|
+
|
|
88
|
+
assert value in task
|
|
89
|
+
assert len(task[value])
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
## various edge cases
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def test_config_essential_data_has_changed():
|
|
96
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
97
|
+
assert conf.config_essential_data_has_changed(c.SIMPLE_CONF) is False
|
|
98
|
+
|
|
99
|
+
new_conf = c.SIMPLE_CONF.replace(c.FROM_NAME, "wrong_name")
|
|
100
|
+
assert conf.config_essential_data_has_changed(new_conf) is True
|
|
101
|
+
|
|
102
|
+
new_conf_2 = c.SIMPLE_CONF.replace(c.LABEL1, "wrong_label")
|
|
103
|
+
assert conf.config_essential_data_has_changed(new_conf_2) is True
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def test_get_task_from_labeling_config():
|
|
107
|
+
task_data, annotations, predictions = LabelInterface.get_task_from_labeling_config(
|
|
108
|
+
c.CONF_WITH_COMMENT
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
# assert task_data == "some_data"
|
|
112
|
+
# assert annotations == "some_annotations"
|
|
113
|
+
# assert predictions == "some_predictions"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_find_tags():
|
|
117
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
118
|
+
tags = conf.find_tags(match_fn=lambda tag: tag.name == c.TO_NAME)
|
|
119
|
+
|
|
120
|
+
assert len(tags) > 0
|
|
121
|
+
assert tags[0].name == c.TO_NAME
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def test_find_tags_by_class():
|
|
125
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
126
|
+
tags = conf.find_tags_by_class(ChoicesTag)
|
|
127
|
+
|
|
128
|
+
assert len(tags) > 0
|
|
129
|
+
assert tags[0].name == c.FROM_NAME
|
|
130
|
+
|
|
131
|
+
tags2 = conf.find_tags_by_class(LabelsTag)
|
|
132
|
+
assert len(tags2) == 0
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
## testing generation
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def test_task_generation():
|
|
139
|
+
val = c.VALUE[1:]
|
|
140
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
141
|
+
task = conf.generate_sample_task()
|
|
142
|
+
|
|
143
|
+
assert val in task
|
|
144
|
+
print(task)
|
|
145
|
+
assert len(task.get(val))
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
## testing object tags
|
|
149
|
+
|
|
150
|
+
## testing control tags
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def test_label_with_choices():
|
|
154
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
155
|
+
region: Region = conf.get_control().label(label=c.LABEL1)
|
|
156
|
+
|
|
157
|
+
rjs = region.to_json()
|
|
158
|
+
assert isinstance(rjs, str)
|
|
159
|
+
|
|
160
|
+
rpy = json.loads(rjs)
|
|
161
|
+
assert rpy["from_name"] == c.FROM_NAME
|
|
162
|
+
assert rpy["to_name"] == c.TO_NAME
|
|
163
|
+
assert "value" in rpy
|
|
164
|
+
|
|
165
|
+
print(rpy)
|
|
166
|
+
|
|
167
|
+
assert "choices" in rpy.get("value")
|
|
168
|
+
assert c.LABEL1 in rpy["value"]["choices"]
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
## testing all other tags
|
|
172
|
+
|
|
173
|
+
## test other method
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def test_load_task():
|
|
177
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
178
|
+
var_name = c.VALUE[1:]
|
|
179
|
+
value = "test"
|
|
180
|
+
|
|
181
|
+
tree = conf.load_task({var_name: value})
|
|
182
|
+
|
|
183
|
+
assert isinstance(tree, LabelInterface)
|
|
184
|
+
assert tree.get_object(c.TO_NAME).value == value
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def test_load_random_task():
|
|
188
|
+
conf = LabelInterface(c.SIMPLE_CONF)
|
|
189
|
+
task, _, _ = conf._sample_task()
|
|
190
|
+
|
|
191
|
+
tree = conf.load_task(task)
|
|
192
|
+
assert len(tree.get_object(c.TO_NAME).value)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def test_empty_value_config():
|
|
196
|
+
conf = LabelInterface(c.EMPTY_VALUE_CONF)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from label_studio_sdk.label_interface.object_tags import ObjectTag
|
|
2
|
+
from lxml.etree import Element
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def test_parse():
|
|
6
|
+
tag = Element("tag", {"name": "my_name", "value": "my_value"})
|
|
7
|
+
object_tag = ObjectTag.parse_node(tag)
|
|
8
|
+
|
|
9
|
+
assert object_tag.name == "my_name"
|
|
10
|
+
assert object_tag.value == "my_value"
|
|
11
|
+
assert object_tag.value_type == None
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def test_validate():
|
|
15
|
+
tag = Element("tag", {"name": "my_name", "value": "$my_value"})
|
|
16
|
+
validation_result = ObjectTag.validate_node(tag)
|
|
17
|
+
|
|
18
|
+
assert validation_result == True
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_value_type():
|
|
22
|
+
tag = Element(
|
|
23
|
+
"tag", {"name": "my_name", "value": "my_value", "valueType": "string"}
|
|
24
|
+
)
|
|
25
|
+
object_tag = ObjectTag.parse_node(tag)
|
|
26
|
+
tag_value_type = object_tag.value_type
|
|
27
|
+
|
|
28
|
+
assert tag_value_type == "string"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_value_is_variable():
|
|
32
|
+
tag = Element("tag", {"name": "my_name", "value": "$my_var"})
|
|
33
|
+
object_tag = ObjectTag.parse_node(tag)
|
|
34
|
+
is_variable = object_tag.value_is_variable
|
|
35
|
+
|
|
36
|
+
assert is_variable == True
|