label-studio-sdk 0.0.32__py3-none-any.whl → 1.0.0__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.
- label_studio_sdk/__init__.py +206 -6
- label_studio_sdk/_extensions/label_studio_tools/__init__.py +0 -0
- label_studio_sdk/_extensions/label_studio_tools/core/__init__.py +0 -0
- label_studio_sdk/_extensions/label_studio_tools/core/label_config.py +163 -0
- label_studio_sdk/_extensions/label_studio_tools/core/utils/__init__.py +0 -0
- label_studio_sdk/_extensions/label_studio_tools/core/utils/exceptions.py +2 -0
- label_studio_sdk/_extensions/label_studio_tools/core/utils/io.py +228 -0
- label_studio_sdk/_extensions/label_studio_tools/core/utils/params.py +45 -0
- label_studio_sdk/_extensions/label_studio_tools/etl/__init__.py +1 -0
- label_studio_sdk/_extensions/label_studio_tools/etl/beam.py +34 -0
- label_studio_sdk/_extensions/label_studio_tools/etl/example.py +17 -0
- label_studio_sdk/_extensions/label_studio_tools/etl/registry.py +67 -0
- label_studio_sdk/_extensions/label_studio_tools/postprocessing/__init__.py +0 -0
- label_studio_sdk/_extensions/label_studio_tools/postprocessing/video.py +97 -0
- label_studio_sdk/_legacy/__init__.py +11 -0
- label_studio_sdk/_legacy/client.py +471 -0
- label_studio_sdk/_legacy/exceptions.py +10 -0
- label_studio_sdk/_legacy/label_interface/__init__.py +1 -0
- label_studio_sdk/_legacy/label_interface/base.py +77 -0
- label_studio_sdk/_legacy/label_interface/control_tags.py +756 -0
- label_studio_sdk/_legacy/label_interface/data_examples.json +96 -0
- label_studio_sdk/_legacy/label_interface/interface.py +925 -0
- label_studio_sdk/_legacy/label_interface/label_tags.py +72 -0
- label_studio_sdk/_legacy/label_interface/object_tags.py +292 -0
- label_studio_sdk/_legacy/label_interface/region.py +43 -0
- label_studio_sdk/_legacy/objects.py +35 -0
- label_studio_sdk/{project.py → _legacy/project.py} +711 -258
- label_studio_sdk/_legacy/schema/label_config_schema.json +226 -0
- label_studio_sdk/{users.py → _legacy/users.py} +15 -13
- label_studio_sdk/{utils.py → _legacy/utils.py} +31 -30
- label_studio_sdk/{workspaces.py → _legacy/workspaces.py} +13 -11
- label_studio_sdk/actions/__init__.py +2 -0
- label_studio_sdk/actions/client.py +150 -0
- label_studio_sdk/annotations/__init__.py +2 -0
- label_studio_sdk/annotations/client.py +750 -0
- label_studio_sdk/client.py +164 -436
- label_studio_sdk/converter/__init__.py +7 -0
- label_studio_sdk/converter/audio.py +56 -0
- label_studio_sdk/converter/brush.py +452 -0
- label_studio_sdk/converter/converter.py +1175 -0
- label_studio_sdk/converter/exports/__init__.py +0 -0
- label_studio_sdk/converter/exports/csv.py +82 -0
- label_studio_sdk/converter/exports/csv2.py +103 -0
- label_studio_sdk/converter/funsd.py +85 -0
- label_studio_sdk/converter/imports/__init__.py +0 -0
- label_studio_sdk/converter/imports/coco.py +314 -0
- label_studio_sdk/converter/imports/colors.py +198 -0
- label_studio_sdk/converter/imports/label_config.py +45 -0
- label_studio_sdk/converter/imports/pathtrack.py +269 -0
- label_studio_sdk/converter/imports/yolo.py +236 -0
- label_studio_sdk/converter/main.py +202 -0
- label_studio_sdk/converter/utils.py +473 -0
- label_studio_sdk/core/__init__.py +33 -0
- label_studio_sdk/core/api_error.py +15 -0
- label_studio_sdk/core/client_wrapper.py +55 -0
- label_studio_sdk/core/datetime_utils.py +28 -0
- label_studio_sdk/core/file.py +38 -0
- label_studio_sdk/core/http_client.py +443 -0
- label_studio_sdk/core/jsonable_encoder.py +99 -0
- label_studio_sdk/core/pagination.py +87 -0
- label_studio_sdk/core/pydantic_utilities.py +28 -0
- label_studio_sdk/core/query_encoder.py +33 -0
- label_studio_sdk/core/remove_none_from_dict.py +11 -0
- label_studio_sdk/core/request_options.py +32 -0
- label_studio_sdk/data_manager.py +32 -23
- label_studio_sdk/environment.py +7 -0
- label_studio_sdk/errors/__init__.py +6 -0
- label_studio_sdk/errors/bad_request_error.py +8 -0
- label_studio_sdk/errors/internal_server_error.py +8 -0
- label_studio_sdk/export_storage/__init__.py +28 -0
- label_studio_sdk/export_storage/azure/__init__.py +5 -0
- label_studio_sdk/export_storage/azure/client.py +722 -0
- label_studio_sdk/export_storage/azure/types/__init__.py +6 -0
- label_studio_sdk/export_storage/azure/types/azure_create_response.py +52 -0
- label_studio_sdk/export_storage/azure/types/azure_update_response.py +52 -0
- label_studio_sdk/export_storage/client.py +107 -0
- label_studio_sdk/export_storage/gcs/__init__.py +5 -0
- label_studio_sdk/export_storage/gcs/client.py +722 -0
- label_studio_sdk/export_storage/gcs/types/__init__.py +6 -0
- label_studio_sdk/export_storage/gcs/types/gcs_create_response.py +52 -0
- label_studio_sdk/export_storage/gcs/types/gcs_update_response.py +52 -0
- label_studio_sdk/export_storage/local/__init__.py +5 -0
- label_studio_sdk/export_storage/local/client.py +688 -0
- label_studio_sdk/export_storage/local/types/__init__.py +6 -0
- label_studio_sdk/export_storage/local/types/local_create_response.py +47 -0
- label_studio_sdk/export_storage/local/types/local_update_response.py +47 -0
- label_studio_sdk/export_storage/redis/__init__.py +5 -0
- label_studio_sdk/export_storage/redis/client.py +714 -0
- label_studio_sdk/export_storage/redis/types/__init__.py +6 -0
- label_studio_sdk/export_storage/redis/types/redis_create_response.py +57 -0
- label_studio_sdk/export_storage/redis/types/redis_update_response.py +57 -0
- label_studio_sdk/export_storage/s3/__init__.py +5 -0
- label_studio_sdk/export_storage/s3/client.py +820 -0
- label_studio_sdk/export_storage/s3/types/__init__.py +6 -0
- label_studio_sdk/export_storage/s3/types/s3create_response.py +74 -0
- label_studio_sdk/export_storage/s3/types/s3update_response.py +74 -0
- label_studio_sdk/export_storage/types/__init__.py +5 -0
- label_studio_sdk/export_storage/types/export_storage_list_types_response_item.py +30 -0
- label_studio_sdk/files/__init__.py +2 -0
- label_studio_sdk/files/client.py +556 -0
- label_studio_sdk/import_storage/__init__.py +28 -0
- label_studio_sdk/import_storage/azure/__init__.py +5 -0
- label_studio_sdk/import_storage/azure/client.py +812 -0
- label_studio_sdk/import_storage/azure/types/__init__.py +6 -0
- label_studio_sdk/import_storage/azure/types/azure_create_response.py +72 -0
- label_studio_sdk/import_storage/azure/types/azure_update_response.py +72 -0
- label_studio_sdk/import_storage/client.py +107 -0
- label_studio_sdk/import_storage/gcs/__init__.py +5 -0
- label_studio_sdk/import_storage/gcs/client.py +812 -0
- label_studio_sdk/import_storage/gcs/types/__init__.py +6 -0
- label_studio_sdk/import_storage/gcs/types/gcs_create_response.py +72 -0
- label_studio_sdk/import_storage/gcs/types/gcs_update_response.py +72 -0
- label_studio_sdk/import_storage/local/__init__.py +5 -0
- label_studio_sdk/import_storage/local/client.py +690 -0
- label_studio_sdk/import_storage/local/types/__init__.py +6 -0
- label_studio_sdk/import_storage/local/types/local_create_response.py +47 -0
- label_studio_sdk/import_storage/local/types/local_update_response.py +47 -0
- label_studio_sdk/import_storage/redis/__init__.py +5 -0
- label_studio_sdk/import_storage/redis/client.py +768 -0
- label_studio_sdk/import_storage/redis/types/__init__.py +6 -0
- label_studio_sdk/import_storage/redis/types/redis_create_response.py +62 -0
- label_studio_sdk/import_storage/redis/types/redis_update_response.py +62 -0
- label_studio_sdk/import_storage/s3/__init__.py +5 -0
- label_studio_sdk/import_storage/s3/client.py +912 -0
- label_studio_sdk/import_storage/s3/types/__init__.py +6 -0
- label_studio_sdk/import_storage/s3/types/s3create_response.py +99 -0
- label_studio_sdk/import_storage/s3/types/s3update_response.py +99 -0
- label_studio_sdk/import_storage/types/__init__.py +5 -0
- label_studio_sdk/import_storage/types/import_storage_list_types_response_item.py +30 -0
- label_studio_sdk/ml/__init__.py +19 -0
- label_studio_sdk/ml/client.py +981 -0
- label_studio_sdk/ml/types/__init__.py +17 -0
- label_studio_sdk/ml/types/ml_create_request_auth_method.py +5 -0
- label_studio_sdk/ml/types/ml_create_response.py +78 -0
- label_studio_sdk/ml/types/ml_create_response_auth_method.py +5 -0
- label_studio_sdk/ml/types/ml_update_request_auth_method.py +5 -0
- label_studio_sdk/ml/types/ml_update_response.py +78 -0
- label_studio_sdk/ml/types/ml_update_response_auth_method.py +5 -0
- label_studio_sdk/predictions/__init__.py +2 -0
- label_studio_sdk/predictions/client.py +638 -0
- label_studio_sdk/projects/__init__.py +6 -0
- label_studio_sdk/projects/client.py +1053 -0
- label_studio_sdk/projects/exports/__init__.py +2 -0
- label_studio_sdk/projects/exports/client.py +930 -0
- label_studio_sdk/projects/types/__init__.py +7 -0
- label_studio_sdk/projects/types/projects_create_response.py +96 -0
- label_studio_sdk/projects/types/projects_import_tasks_response.py +71 -0
- label_studio_sdk/projects/types/projects_list_response.py +33 -0
- label_studio_sdk/py.typed +0 -0
- label_studio_sdk/tasks/__init__.py +5 -0
- label_studio_sdk/tasks/client.py +811 -0
- label_studio_sdk/tasks/types/__init__.py +6 -0
- label_studio_sdk/tasks/types/tasks_list_request_fields.py +5 -0
- label_studio_sdk/tasks/types/tasks_list_response.py +48 -0
- label_studio_sdk/types/__init__.py +115 -0
- label_studio_sdk/types/annotation.py +116 -0
- label_studio_sdk/types/annotation_filter_options.py +42 -0
- label_studio_sdk/types/annotation_last_action.py +19 -0
- label_studio_sdk/types/azure_blob_export_storage.py +112 -0
- label_studio_sdk/types/azure_blob_export_storage_status.py +7 -0
- label_studio_sdk/types/azure_blob_import_storage.py +113 -0
- label_studio_sdk/types/azure_blob_import_storage_status.py +7 -0
- label_studio_sdk/types/base_task.py +113 -0
- label_studio_sdk/types/base_user.py +42 -0
- label_studio_sdk/types/converted_format.py +36 -0
- label_studio_sdk/types/converted_format_status.py +5 -0
- label_studio_sdk/types/export.py +48 -0
- label_studio_sdk/types/export_convert.py +32 -0
- label_studio_sdk/types/export_create.py +54 -0
- label_studio_sdk/types/export_create_status.py +5 -0
- label_studio_sdk/types/export_status.py +5 -0
- label_studio_sdk/types/file_upload.py +30 -0
- label_studio_sdk/types/filter.py +53 -0
- label_studio_sdk/types/filter_group.py +35 -0
- label_studio_sdk/types/gcs_export_storage.py +112 -0
- label_studio_sdk/types/gcs_export_storage_status.py +7 -0
- label_studio_sdk/types/gcs_import_storage.py +113 -0
- label_studio_sdk/types/gcs_import_storage_status.py +7 -0
- label_studio_sdk/types/local_files_export_storage.py +97 -0
- label_studio_sdk/types/local_files_export_storage_status.py +7 -0
- label_studio_sdk/types/local_files_import_storage.py +92 -0
- label_studio_sdk/types/local_files_import_storage_status.py +7 -0
- label_studio_sdk/types/ml_backend.py +89 -0
- label_studio_sdk/types/ml_backend_auth_method.py +5 -0
- label_studio_sdk/types/ml_backend_state.py +5 -0
- label_studio_sdk/types/prediction.py +78 -0
- label_studio_sdk/types/project.py +198 -0
- label_studio_sdk/types/project_import.py +63 -0
- label_studio_sdk/types/project_import_status.py +5 -0
- label_studio_sdk/types/project_label_config.py +32 -0
- label_studio_sdk/types/project_sampling.py +7 -0
- label_studio_sdk/types/project_skip_queue.py +5 -0
- label_studio_sdk/types/redis_export_storage.py +117 -0
- label_studio_sdk/types/redis_export_storage_status.py +7 -0
- label_studio_sdk/types/redis_import_storage.py +112 -0
- label_studio_sdk/types/redis_import_storage_status.py +7 -0
- label_studio_sdk/types/s3export_storage.py +134 -0
- label_studio_sdk/types/s3export_storage_status.py +7 -0
- label_studio_sdk/types/s3import_storage.py +140 -0
- label_studio_sdk/types/s3import_storage_status.py +7 -0
- label_studio_sdk/types/serialization_option.py +36 -0
- label_studio_sdk/types/serialization_options.py +45 -0
- label_studio_sdk/types/task.py +157 -0
- label_studio_sdk/types/task_filter_options.py +49 -0
- label_studio_sdk/types/user_simple.py +37 -0
- label_studio_sdk/types/view.py +55 -0
- label_studio_sdk/types/webhook.py +67 -0
- label_studio_sdk/types/webhook_actions_item.py +21 -0
- label_studio_sdk/types/webhook_serializer_for_update.py +67 -0
- label_studio_sdk/types/webhook_serializer_for_update_actions_item.py +21 -0
- label_studio_sdk/users/__init__.py +5 -0
- label_studio_sdk/users/client.py +830 -0
- label_studio_sdk/users/types/__init__.py +6 -0
- label_studio_sdk/users/types/users_get_token_response.py +36 -0
- label_studio_sdk/users/types/users_reset_token_response.py +36 -0
- label_studio_sdk/version.py +4 -0
- label_studio_sdk/views/__init__.py +31 -0
- label_studio_sdk/views/client.py +564 -0
- label_studio_sdk/views/types/__init__.py +29 -0
- label_studio_sdk/views/types/views_create_request_data.py +43 -0
- label_studio_sdk/views/types/views_create_request_data_filters.py +43 -0
- label_studio_sdk/views/types/views_create_request_data_filters_conjunction.py +5 -0
- label_studio_sdk/views/types/views_create_request_data_filters_items_item.py +47 -0
- label_studio_sdk/views/types/views_create_request_data_ordering_item.py +38 -0
- label_studio_sdk/views/types/views_create_request_data_ordering_item_direction.py +5 -0
- label_studio_sdk/views/types/views_update_request_data.py +43 -0
- label_studio_sdk/views/types/views_update_request_data_filters.py +43 -0
- label_studio_sdk/views/types/views_update_request_data_filters_conjunction.py +5 -0
- label_studio_sdk/views/types/views_update_request_data_filters_items_item.py +47 -0
- label_studio_sdk/views/types/views_update_request_data_ordering_item.py +38 -0
- label_studio_sdk/views/types/views_update_request_data_ordering_item_direction.py +5 -0
- label_studio_sdk/webhooks/__init__.py +5 -0
- label_studio_sdk/webhooks/client.py +636 -0
- label_studio_sdk/webhooks/types/__init__.py +5 -0
- label_studio_sdk/webhooks/types/webhooks_update_request_actions_item.py +21 -0
- label_studio_sdk-1.0.0.dist-info/METADATA +307 -0
- label_studio_sdk-1.0.0.dist-info/RECORD +239 -0
- {label_studio_sdk-0.0.32.dist-info → label_studio_sdk-1.0.0.dist-info}/WHEEL +1 -2
- docs/__init__.py +0 -3
- label_studio_sdk-0.0.32.dist-info/LICENSE +0 -201
- label_studio_sdk-0.0.32.dist-info/METADATA +0 -22
- label_studio_sdk-0.0.32.dist-info/RECORD +0 -15
- label_studio_sdk-0.0.32.dist-info/top_level.txt +0 -3
- tests/test_client.py +0 -26
- {tests → label_studio_sdk/_extensions}/__init__.py +0 -0
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Original RLE JS code from https://github.com/thi-ng/umbrella/blob/develop/packages/rle-pack/src/index.ts
|
|
3
|
+
|
|
4
|
+
export const decode = (src: Uint8Array) => {
|
|
5
|
+
const input = new BitInputStream(src);
|
|
6
|
+
const num = input.read(32);
|
|
7
|
+
const wordSize = input.read(5) + 1;
|
|
8
|
+
const rleSizes = [0, 0, 0, 0].map(() => input.read(4) + 1);
|
|
9
|
+
const out = arrayForWordSize(wordSize, num);
|
|
10
|
+
let x, j;
|
|
11
|
+
for (let i = 0; i < num; ) {
|
|
12
|
+
x = input.readBit();
|
|
13
|
+
j = i + 1 + input.read(rleSizes[input.read(2)]);
|
|
14
|
+
if (x) {
|
|
15
|
+
out.fill(input.read(wordSize), i, j);
|
|
16
|
+
i = j;
|
|
17
|
+
} else {
|
|
18
|
+
for (; i < j; i++) {
|
|
19
|
+
out[i] = input.read(wordSize);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return out;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const arrayForWordSize = (ws: number, n: number) => {
|
|
27
|
+
return new (ws < 9 ? Uint8Array : ws < 17 ? Uint16Array : Uint32Array)(n);
|
|
28
|
+
};
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import os
|
|
32
|
+
import uuid
|
|
33
|
+
import numpy as np
|
|
34
|
+
import logging
|
|
35
|
+
|
|
36
|
+
from PIL import Image
|
|
37
|
+
from collections import defaultdict
|
|
38
|
+
from itertools import groupby
|
|
39
|
+
|
|
40
|
+
logger = logging.getLogger(__name__)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
### Brush Export ###
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class InputStream:
|
|
47
|
+
def __init__(self, data):
|
|
48
|
+
self.data = data
|
|
49
|
+
self.i = 0
|
|
50
|
+
|
|
51
|
+
def read(self, size):
|
|
52
|
+
out = self.data[self.i : self.i + size]
|
|
53
|
+
self.i += size
|
|
54
|
+
return int(out, 2)
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def access_bit(data, num):
|
|
58
|
+
"""from bytes array to bits by num position"""
|
|
59
|
+
base = int(num // 8)
|
|
60
|
+
shift = 7 - int(num % 8)
|
|
61
|
+
return (data[base] & (1 << shift)) >> shift
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def bytes2bit(data):
|
|
65
|
+
"""get bit string from bytes data"""
|
|
66
|
+
return "".join([str(access_bit(data, i)) for i in range(len(data) * 8)])
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def decode_rle(rle, print_params: bool = False):
|
|
70
|
+
"""from LS RLE to numpy uint8 3d image [width, height, channel]
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
print_params (bool, optional): If true, a RLE parameters print statement is suppressed
|
|
74
|
+
"""
|
|
75
|
+
input = InputStream(bytes2bit(rle))
|
|
76
|
+
num = input.read(32)
|
|
77
|
+
word_size = input.read(5) + 1
|
|
78
|
+
rle_sizes = [input.read(4) + 1 for _ in range(4)]
|
|
79
|
+
|
|
80
|
+
if print_params:
|
|
81
|
+
print(
|
|
82
|
+
"RLE params:", num, "values", word_size, "word_size", rle_sizes, "rle_sizes"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
i = 0
|
|
86
|
+
out = np.zeros(num, dtype=np.uint8)
|
|
87
|
+
while i < num:
|
|
88
|
+
x = input.read(1)
|
|
89
|
+
j = i + 1 + input.read(rle_sizes[input.read(2)])
|
|
90
|
+
if x:
|
|
91
|
+
val = input.read(word_size)
|
|
92
|
+
out[i:j] = val
|
|
93
|
+
i = j
|
|
94
|
+
else:
|
|
95
|
+
while i < j:
|
|
96
|
+
val = input.read(word_size)
|
|
97
|
+
out[i] = val
|
|
98
|
+
i += 1
|
|
99
|
+
return out
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def decode_from_annotation(from_name, results):
|
|
103
|
+
"""from LS annotation to {"tag_name + label_name": [numpy uint8 image (width x height)]}"""
|
|
104
|
+
layers = {}
|
|
105
|
+
counters = defaultdict(int)
|
|
106
|
+
for result in results:
|
|
107
|
+
key = (
|
|
108
|
+
"brushlabels"
|
|
109
|
+
if result["type"].lower() == "brushlabels"
|
|
110
|
+
else ("labels" if result["type"].lower() == "labels" else None)
|
|
111
|
+
)
|
|
112
|
+
if key is None or "rle" not in result:
|
|
113
|
+
continue
|
|
114
|
+
|
|
115
|
+
rle = result["rle"]
|
|
116
|
+
width = result["original_width"]
|
|
117
|
+
height = result["original_height"]
|
|
118
|
+
labels = result[key] if key in result else ["no_label"]
|
|
119
|
+
name = from_name + "-" + "-".join(labels)
|
|
120
|
+
|
|
121
|
+
# result count
|
|
122
|
+
i = str(counters[name])
|
|
123
|
+
counters[name] += 1
|
|
124
|
+
name += "-" + i
|
|
125
|
+
|
|
126
|
+
image = decode_rle(rle)
|
|
127
|
+
layers[name] = np.reshape(image, [height, width, 4])[:, :, 3]
|
|
128
|
+
return layers
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def save_brush_images_from_annotation(
|
|
132
|
+
task_id,
|
|
133
|
+
annotation_id,
|
|
134
|
+
completed_by,
|
|
135
|
+
from_name,
|
|
136
|
+
results,
|
|
137
|
+
out_dir,
|
|
138
|
+
out_format="numpy",
|
|
139
|
+
):
|
|
140
|
+
layers = decode_from_annotation(from_name, results)
|
|
141
|
+
if isinstance(completed_by, dict):
|
|
142
|
+
email = completed_by.get("email", "")
|
|
143
|
+
else:
|
|
144
|
+
email = str(completed_by)
|
|
145
|
+
email = "".join(
|
|
146
|
+
x for x in email if x.isalnum() or x == "@" or x == "."
|
|
147
|
+
) # sanitize filename
|
|
148
|
+
|
|
149
|
+
for name in layers:
|
|
150
|
+
sanitized_name = name.replace("/", "-").replace("\\", "-")
|
|
151
|
+
|
|
152
|
+
filename = os.path.join(
|
|
153
|
+
out_dir,
|
|
154
|
+
"task-"
|
|
155
|
+
+ str(task_id)
|
|
156
|
+
+ "-annotation-"
|
|
157
|
+
+ str(annotation_id)
|
|
158
|
+
+ "-by-"
|
|
159
|
+
+ email
|
|
160
|
+
+ "-"
|
|
161
|
+
+ sanitized_name,
|
|
162
|
+
)
|
|
163
|
+
image = layers[name]
|
|
164
|
+
logger.debug(f"Save image to {filename}")
|
|
165
|
+
if out_format == "numpy":
|
|
166
|
+
np.save(filename, image)
|
|
167
|
+
elif out_format == "png":
|
|
168
|
+
im = Image.fromarray(image)
|
|
169
|
+
im.save(filename + ".png")
|
|
170
|
+
else:
|
|
171
|
+
raise Exception("Unknown output format for brush converter")
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def convert_task(item, out_dir, out_format="numpy"):
|
|
175
|
+
"""Task with multiple annotations to brush images, out_format = numpy | png"""
|
|
176
|
+
for from_name, results in item["output"].items():
|
|
177
|
+
save_brush_images_from_annotation(
|
|
178
|
+
item["id"],
|
|
179
|
+
item["annotation_id"],
|
|
180
|
+
item["completed_by"],
|
|
181
|
+
from_name,
|
|
182
|
+
results,
|
|
183
|
+
out_dir,
|
|
184
|
+
out_format,
|
|
185
|
+
)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def convert_task_dir(items, out_dir, out_format="numpy"):
|
|
189
|
+
"""Directory with tasks and annotation to brush images, out_format = numpy | png"""
|
|
190
|
+
for item in items:
|
|
191
|
+
convert_task(item, out_dir, out_format)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# convert_task_dir('/ls/test/completions', '/ls/test/completions/output', 'numpy')
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
### Brush Import ###
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def bits2byte(arr_str, n=8):
|
|
201
|
+
"""Convert bits back to byte
|
|
202
|
+
|
|
203
|
+
:param arr_str: string with the bit array
|
|
204
|
+
:type arr_str: str
|
|
205
|
+
:param n: number of bits to separate the arr string into
|
|
206
|
+
:type n: int
|
|
207
|
+
:return rle:
|
|
208
|
+
:type rle: list
|
|
209
|
+
"""
|
|
210
|
+
rle = []
|
|
211
|
+
numbers = [arr_str[i : i + n] for i in range(0, len(arr_str), n)]
|
|
212
|
+
for i in numbers:
|
|
213
|
+
rle.append(int(i, 2))
|
|
214
|
+
return rle
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
# Shamelessly plagiarized from https://stackoverflow.com/a/32681075/6051733
|
|
218
|
+
def base_rle_encode(inarray):
|
|
219
|
+
"""run length encoding. Partial credit to R rle function.
|
|
220
|
+
Multi datatype arrays catered for including non Numpy
|
|
221
|
+
returns: tuple (runlengths, startpositions, values)"""
|
|
222
|
+
ia = np.asarray(inarray) # force numpy
|
|
223
|
+
n = len(ia)
|
|
224
|
+
if n == 0:
|
|
225
|
+
return None, None, None
|
|
226
|
+
else:
|
|
227
|
+
y = ia[1:] != ia[:-1] # pairwise unequal (string safe)
|
|
228
|
+
i = np.append(np.where(y), n - 1) # must include last element posi
|
|
229
|
+
z = np.diff(np.append(-1, i)) # run lengths
|
|
230
|
+
p = np.cumsum(np.append(0, z))[:-1] # positions
|
|
231
|
+
return z, p, ia[i]
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def encode_rle(arr, wordsize=8, rle_sizes=[3, 4, 8, 16]):
|
|
235
|
+
"""Encode a 1d array to rle
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
:param arr: flattened np.array from a 4d image (R, G, B, alpha)
|
|
239
|
+
:type arr: np.array
|
|
240
|
+
:param wordsize: wordsize bits for decoding, default is 8
|
|
241
|
+
:type wordsize: int
|
|
242
|
+
:param rle_sizes: list of ints which state how long a series is of the same number
|
|
243
|
+
:type rle_sizes: list
|
|
244
|
+
:return rle: run length encoded array
|
|
245
|
+
:type rle: list
|
|
246
|
+
|
|
247
|
+
"""
|
|
248
|
+
# Set length of array in 32 bits
|
|
249
|
+
num = len(arr)
|
|
250
|
+
numbits = f"{num:032b}"
|
|
251
|
+
|
|
252
|
+
# put in the wordsize in bits
|
|
253
|
+
wordsizebits = f"{wordsize - 1:05b}"
|
|
254
|
+
|
|
255
|
+
# put rle sizes in the bits
|
|
256
|
+
rle_bits = "".join([f"{x - 1:04b}" for x in rle_sizes])
|
|
257
|
+
|
|
258
|
+
# combine it into base string
|
|
259
|
+
base_str = numbits + wordsizebits + rle_bits
|
|
260
|
+
|
|
261
|
+
# start with creating the rle bite string
|
|
262
|
+
out_str = ""
|
|
263
|
+
for length_reeks, p, value in zip(*base_rle_encode(arr)):
|
|
264
|
+
# TODO: A nice to have but --> this can be optimized but works
|
|
265
|
+
if length_reeks == 1:
|
|
266
|
+
# we state with the first 0 that it has a length of 1
|
|
267
|
+
out_str += "0"
|
|
268
|
+
# We state now the index on the rle sizes
|
|
269
|
+
out_str += "00"
|
|
270
|
+
|
|
271
|
+
# the rle size value is 0 for an individual number
|
|
272
|
+
out_str += "000"
|
|
273
|
+
|
|
274
|
+
# put the value in a 8 bit string
|
|
275
|
+
out_str += f"{value:08b}"
|
|
276
|
+
state = "single_val"
|
|
277
|
+
|
|
278
|
+
elif length_reeks > 1:
|
|
279
|
+
state = "series"
|
|
280
|
+
# rle size = 3
|
|
281
|
+
if length_reeks <= 8:
|
|
282
|
+
# Starting with a 1 indicates that we have started a series
|
|
283
|
+
out_str += "1"
|
|
284
|
+
|
|
285
|
+
# index in rle size arr
|
|
286
|
+
out_str += "00"
|
|
287
|
+
|
|
288
|
+
# length of array to bits
|
|
289
|
+
out_str += f"{length_reeks - 1:03b}"
|
|
290
|
+
|
|
291
|
+
out_str += f"{value:08b}"
|
|
292
|
+
|
|
293
|
+
# rle size = 4
|
|
294
|
+
elif 8 < length_reeks <= 16:
|
|
295
|
+
# Starting with a 1 indicates that we have started a series
|
|
296
|
+
out_str += "1"
|
|
297
|
+
out_str += "01"
|
|
298
|
+
|
|
299
|
+
# length of array to bits
|
|
300
|
+
out_str += f"{length_reeks - 1:04b}"
|
|
301
|
+
|
|
302
|
+
out_str += f"{value:08b}"
|
|
303
|
+
|
|
304
|
+
# rle size = 8
|
|
305
|
+
elif 16 < length_reeks <= 256:
|
|
306
|
+
# Starting with a 1 indicates that we have started a series
|
|
307
|
+
out_str += "1"
|
|
308
|
+
|
|
309
|
+
out_str += "10"
|
|
310
|
+
|
|
311
|
+
# length of array to bits
|
|
312
|
+
out_str += f"{length_reeks - 1:08b}"
|
|
313
|
+
|
|
314
|
+
out_str += f"{value:08b}"
|
|
315
|
+
|
|
316
|
+
# rle size = 16 or longer
|
|
317
|
+
else:
|
|
318
|
+
length_temp = length_reeks
|
|
319
|
+
while length_temp > 2**16:
|
|
320
|
+
# Starting with a 1 indicates that we have started a series
|
|
321
|
+
out_str += "1"
|
|
322
|
+
|
|
323
|
+
out_str += "11"
|
|
324
|
+
out_str += f"{2 ** 16 - 1:016b}"
|
|
325
|
+
|
|
326
|
+
out_str += f"{value:08b}"
|
|
327
|
+
length_temp -= 2**16
|
|
328
|
+
|
|
329
|
+
# Starting with a 1 indicates that we have started a series
|
|
330
|
+
out_str += "1"
|
|
331
|
+
|
|
332
|
+
out_str += "11"
|
|
333
|
+
# length of array to bits
|
|
334
|
+
out_str += f"{length_temp - 1:016b}"
|
|
335
|
+
|
|
336
|
+
out_str += f"{value:08b}"
|
|
337
|
+
|
|
338
|
+
# make sure that we have an 8 fold lenght otherwise add 0's at the end
|
|
339
|
+
nzfill = 8 - len(base_str + out_str) % 8
|
|
340
|
+
total_str = base_str + out_str
|
|
341
|
+
total_str = total_str + nzfill * "0"
|
|
342
|
+
|
|
343
|
+
rle = bits2byte(total_str)
|
|
344
|
+
|
|
345
|
+
return rle
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def contour2rle(contours, contour_id, img_width, img_height):
|
|
349
|
+
"""
|
|
350
|
+
:param contours: list of contours
|
|
351
|
+
:type contours: list
|
|
352
|
+
:param contour_id: id of contour which you want to translate
|
|
353
|
+
:type contour_id: int
|
|
354
|
+
:param img_width: image shape width
|
|
355
|
+
:type img_width: int
|
|
356
|
+
:param img_height: image shape height
|
|
357
|
+
:type img_height: int
|
|
358
|
+
:return: list of ints in RLE format
|
|
359
|
+
"""
|
|
360
|
+
import cv2 # opencv
|
|
361
|
+
|
|
362
|
+
mask_im = np.zeros((img_width, img_height, 4))
|
|
363
|
+
mask_contours = cv2.drawContours(
|
|
364
|
+
mask_im, contours, contour_id, color=(0, 255, 0, 100), thickness=-1
|
|
365
|
+
)
|
|
366
|
+
rle_out = encode_rle(mask_contours.ravel().astype(int))
|
|
367
|
+
return rle_out
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
def mask2rle(mask):
|
|
371
|
+
"""Convert mask to RLE
|
|
372
|
+
|
|
373
|
+
:param mask: uint8 or int np.array mask with len(shape) == 2 like grayscale image
|
|
374
|
+
:return: list of ints in RLE format
|
|
375
|
+
"""
|
|
376
|
+
assert len(mask.shape) == 2, "mask must be 2D np.array"
|
|
377
|
+
assert mask.dtype == np.uint8 or mask.dtype == int, "mask must be uint8 or int"
|
|
378
|
+
array = mask.ravel()
|
|
379
|
+
array = np.repeat(array, 4) # must be 4 channels
|
|
380
|
+
rle = encode_rle(array)
|
|
381
|
+
return rle
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def image2rle(path):
|
|
385
|
+
"""Convert mask image (jpg, png) to RLE
|
|
386
|
+
|
|
387
|
+
1. Read image as grayscale
|
|
388
|
+
2. Flatten to 1d array
|
|
389
|
+
3. Threshold > 128
|
|
390
|
+
4. Encode
|
|
391
|
+
|
|
392
|
+
:param path: path to image with mask (jpg, png), this image will be thresholded with values > 128 to obtain mask,
|
|
393
|
+
so you can mark background as black and foreground as white
|
|
394
|
+
:return: list of ints in RLE format
|
|
395
|
+
"""
|
|
396
|
+
with Image.open(path).convert("L") as image:
|
|
397
|
+
mask = np.array((np.array(image) > 128) * 255, dtype=np.uint8)
|
|
398
|
+
array = mask.ravel()
|
|
399
|
+
array = np.repeat(array, 4)
|
|
400
|
+
rle = encode_rle(array)
|
|
401
|
+
return rle, image.size[0], image.size[1]
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
def image2annotation(
|
|
405
|
+
path,
|
|
406
|
+
label_name,
|
|
407
|
+
from_name,
|
|
408
|
+
to_name,
|
|
409
|
+
ground_truth=False,
|
|
410
|
+
model_version=None,
|
|
411
|
+
score=None,
|
|
412
|
+
):
|
|
413
|
+
"""Convert image with mask to brush RLE annotation
|
|
414
|
+
|
|
415
|
+
:param path: path to image with mask (jpg, png), this image will be thresholded with values > 128 to obtain mask,
|
|
416
|
+
so you can mark background as black and foreground as white
|
|
417
|
+
:param label_name: label name from labeling config (<Label>)
|
|
418
|
+
:param from_name: brush tag name (<BrushLabels>)
|
|
419
|
+
:param to_name: image tag name (<Image>)
|
|
420
|
+
:param ground_truth: ground truth annotation true/false
|
|
421
|
+
:param model_version: any string, only for predictions
|
|
422
|
+
:param score: model score as float, only for predictions
|
|
423
|
+
|
|
424
|
+
:return: dict with Label Studio Annotation or Prediction (Pre-annotation)
|
|
425
|
+
"""
|
|
426
|
+
rle, width, height = image2rle(path)
|
|
427
|
+
result = {
|
|
428
|
+
"result": [
|
|
429
|
+
{
|
|
430
|
+
"id": str(uuid.uuid4())[0:8],
|
|
431
|
+
"type": "brushlabels",
|
|
432
|
+
"value": {"rle": rle, "format": "rle", "brushlabels": [label_name]},
|
|
433
|
+
"origin": "manual",
|
|
434
|
+
"to_name": to_name,
|
|
435
|
+
"from_name": from_name,
|
|
436
|
+
"image_rotation": 0,
|
|
437
|
+
"original_width": width,
|
|
438
|
+
"original_height": height,
|
|
439
|
+
}
|
|
440
|
+
],
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
# prediction
|
|
444
|
+
if model_version:
|
|
445
|
+
result["model_version"] = model_version
|
|
446
|
+
result["score"] = score
|
|
447
|
+
|
|
448
|
+
# annotation
|
|
449
|
+
else:
|
|
450
|
+
result["ground_truth"] = ground_truth
|
|
451
|
+
|
|
452
|
+
return result
|