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.
Files changed (245) hide show
  1. label_studio_sdk/__init__.py +206 -6
  2. label_studio_sdk/_extensions/label_studio_tools/__init__.py +0 -0
  3. label_studio_sdk/_extensions/label_studio_tools/core/__init__.py +0 -0
  4. label_studio_sdk/_extensions/label_studio_tools/core/label_config.py +163 -0
  5. label_studio_sdk/_extensions/label_studio_tools/core/utils/__init__.py +0 -0
  6. label_studio_sdk/_extensions/label_studio_tools/core/utils/exceptions.py +2 -0
  7. label_studio_sdk/_extensions/label_studio_tools/core/utils/io.py +228 -0
  8. label_studio_sdk/_extensions/label_studio_tools/core/utils/params.py +45 -0
  9. label_studio_sdk/_extensions/label_studio_tools/etl/__init__.py +1 -0
  10. label_studio_sdk/_extensions/label_studio_tools/etl/beam.py +34 -0
  11. label_studio_sdk/_extensions/label_studio_tools/etl/example.py +17 -0
  12. label_studio_sdk/_extensions/label_studio_tools/etl/registry.py +67 -0
  13. label_studio_sdk/_extensions/label_studio_tools/postprocessing/__init__.py +0 -0
  14. label_studio_sdk/_extensions/label_studio_tools/postprocessing/video.py +97 -0
  15. label_studio_sdk/_legacy/__init__.py +11 -0
  16. label_studio_sdk/_legacy/client.py +471 -0
  17. label_studio_sdk/_legacy/exceptions.py +10 -0
  18. label_studio_sdk/_legacy/label_interface/__init__.py +1 -0
  19. label_studio_sdk/_legacy/label_interface/base.py +77 -0
  20. label_studio_sdk/_legacy/label_interface/control_tags.py +756 -0
  21. label_studio_sdk/_legacy/label_interface/data_examples.json +96 -0
  22. label_studio_sdk/_legacy/label_interface/interface.py +925 -0
  23. label_studio_sdk/_legacy/label_interface/label_tags.py +72 -0
  24. label_studio_sdk/_legacy/label_interface/object_tags.py +292 -0
  25. label_studio_sdk/_legacy/label_interface/region.py +43 -0
  26. label_studio_sdk/_legacy/objects.py +35 -0
  27. label_studio_sdk/{project.py → _legacy/project.py} +711 -258
  28. label_studio_sdk/_legacy/schema/label_config_schema.json +226 -0
  29. label_studio_sdk/{users.py → _legacy/users.py} +15 -13
  30. label_studio_sdk/{utils.py → _legacy/utils.py} +31 -30
  31. label_studio_sdk/{workspaces.py → _legacy/workspaces.py} +13 -11
  32. label_studio_sdk/actions/__init__.py +2 -0
  33. label_studio_sdk/actions/client.py +150 -0
  34. label_studio_sdk/annotations/__init__.py +2 -0
  35. label_studio_sdk/annotations/client.py +750 -0
  36. label_studio_sdk/client.py +164 -436
  37. label_studio_sdk/converter/__init__.py +7 -0
  38. label_studio_sdk/converter/audio.py +56 -0
  39. label_studio_sdk/converter/brush.py +452 -0
  40. label_studio_sdk/converter/converter.py +1175 -0
  41. label_studio_sdk/converter/exports/__init__.py +0 -0
  42. label_studio_sdk/converter/exports/csv.py +82 -0
  43. label_studio_sdk/converter/exports/csv2.py +103 -0
  44. label_studio_sdk/converter/funsd.py +85 -0
  45. label_studio_sdk/converter/imports/__init__.py +0 -0
  46. label_studio_sdk/converter/imports/coco.py +314 -0
  47. label_studio_sdk/converter/imports/colors.py +198 -0
  48. label_studio_sdk/converter/imports/label_config.py +45 -0
  49. label_studio_sdk/converter/imports/pathtrack.py +269 -0
  50. label_studio_sdk/converter/imports/yolo.py +236 -0
  51. label_studio_sdk/converter/main.py +202 -0
  52. label_studio_sdk/converter/utils.py +473 -0
  53. label_studio_sdk/core/__init__.py +33 -0
  54. label_studio_sdk/core/api_error.py +15 -0
  55. label_studio_sdk/core/client_wrapper.py +55 -0
  56. label_studio_sdk/core/datetime_utils.py +28 -0
  57. label_studio_sdk/core/file.py +38 -0
  58. label_studio_sdk/core/http_client.py +443 -0
  59. label_studio_sdk/core/jsonable_encoder.py +99 -0
  60. label_studio_sdk/core/pagination.py +87 -0
  61. label_studio_sdk/core/pydantic_utilities.py +28 -0
  62. label_studio_sdk/core/query_encoder.py +33 -0
  63. label_studio_sdk/core/remove_none_from_dict.py +11 -0
  64. label_studio_sdk/core/request_options.py +32 -0
  65. label_studio_sdk/data_manager.py +32 -23
  66. label_studio_sdk/environment.py +7 -0
  67. label_studio_sdk/errors/__init__.py +6 -0
  68. label_studio_sdk/errors/bad_request_error.py +8 -0
  69. label_studio_sdk/errors/internal_server_error.py +8 -0
  70. label_studio_sdk/export_storage/__init__.py +28 -0
  71. label_studio_sdk/export_storage/azure/__init__.py +5 -0
  72. label_studio_sdk/export_storage/azure/client.py +722 -0
  73. label_studio_sdk/export_storage/azure/types/__init__.py +6 -0
  74. label_studio_sdk/export_storage/azure/types/azure_create_response.py +52 -0
  75. label_studio_sdk/export_storage/azure/types/azure_update_response.py +52 -0
  76. label_studio_sdk/export_storage/client.py +107 -0
  77. label_studio_sdk/export_storage/gcs/__init__.py +5 -0
  78. label_studio_sdk/export_storage/gcs/client.py +722 -0
  79. label_studio_sdk/export_storage/gcs/types/__init__.py +6 -0
  80. label_studio_sdk/export_storage/gcs/types/gcs_create_response.py +52 -0
  81. label_studio_sdk/export_storage/gcs/types/gcs_update_response.py +52 -0
  82. label_studio_sdk/export_storage/local/__init__.py +5 -0
  83. label_studio_sdk/export_storage/local/client.py +688 -0
  84. label_studio_sdk/export_storage/local/types/__init__.py +6 -0
  85. label_studio_sdk/export_storage/local/types/local_create_response.py +47 -0
  86. label_studio_sdk/export_storage/local/types/local_update_response.py +47 -0
  87. label_studio_sdk/export_storage/redis/__init__.py +5 -0
  88. label_studio_sdk/export_storage/redis/client.py +714 -0
  89. label_studio_sdk/export_storage/redis/types/__init__.py +6 -0
  90. label_studio_sdk/export_storage/redis/types/redis_create_response.py +57 -0
  91. label_studio_sdk/export_storage/redis/types/redis_update_response.py +57 -0
  92. label_studio_sdk/export_storage/s3/__init__.py +5 -0
  93. label_studio_sdk/export_storage/s3/client.py +820 -0
  94. label_studio_sdk/export_storage/s3/types/__init__.py +6 -0
  95. label_studio_sdk/export_storage/s3/types/s3create_response.py +74 -0
  96. label_studio_sdk/export_storage/s3/types/s3update_response.py +74 -0
  97. label_studio_sdk/export_storage/types/__init__.py +5 -0
  98. label_studio_sdk/export_storage/types/export_storage_list_types_response_item.py +30 -0
  99. label_studio_sdk/files/__init__.py +2 -0
  100. label_studio_sdk/files/client.py +556 -0
  101. label_studio_sdk/import_storage/__init__.py +28 -0
  102. label_studio_sdk/import_storage/azure/__init__.py +5 -0
  103. label_studio_sdk/import_storage/azure/client.py +812 -0
  104. label_studio_sdk/import_storage/azure/types/__init__.py +6 -0
  105. label_studio_sdk/import_storage/azure/types/azure_create_response.py +72 -0
  106. label_studio_sdk/import_storage/azure/types/azure_update_response.py +72 -0
  107. label_studio_sdk/import_storage/client.py +107 -0
  108. label_studio_sdk/import_storage/gcs/__init__.py +5 -0
  109. label_studio_sdk/import_storage/gcs/client.py +812 -0
  110. label_studio_sdk/import_storage/gcs/types/__init__.py +6 -0
  111. label_studio_sdk/import_storage/gcs/types/gcs_create_response.py +72 -0
  112. label_studio_sdk/import_storage/gcs/types/gcs_update_response.py +72 -0
  113. label_studio_sdk/import_storage/local/__init__.py +5 -0
  114. label_studio_sdk/import_storage/local/client.py +690 -0
  115. label_studio_sdk/import_storage/local/types/__init__.py +6 -0
  116. label_studio_sdk/import_storage/local/types/local_create_response.py +47 -0
  117. label_studio_sdk/import_storage/local/types/local_update_response.py +47 -0
  118. label_studio_sdk/import_storage/redis/__init__.py +5 -0
  119. label_studio_sdk/import_storage/redis/client.py +768 -0
  120. label_studio_sdk/import_storage/redis/types/__init__.py +6 -0
  121. label_studio_sdk/import_storage/redis/types/redis_create_response.py +62 -0
  122. label_studio_sdk/import_storage/redis/types/redis_update_response.py +62 -0
  123. label_studio_sdk/import_storage/s3/__init__.py +5 -0
  124. label_studio_sdk/import_storage/s3/client.py +912 -0
  125. label_studio_sdk/import_storage/s3/types/__init__.py +6 -0
  126. label_studio_sdk/import_storage/s3/types/s3create_response.py +99 -0
  127. label_studio_sdk/import_storage/s3/types/s3update_response.py +99 -0
  128. label_studio_sdk/import_storage/types/__init__.py +5 -0
  129. label_studio_sdk/import_storage/types/import_storage_list_types_response_item.py +30 -0
  130. label_studio_sdk/ml/__init__.py +19 -0
  131. label_studio_sdk/ml/client.py +981 -0
  132. label_studio_sdk/ml/types/__init__.py +17 -0
  133. label_studio_sdk/ml/types/ml_create_request_auth_method.py +5 -0
  134. label_studio_sdk/ml/types/ml_create_response.py +78 -0
  135. label_studio_sdk/ml/types/ml_create_response_auth_method.py +5 -0
  136. label_studio_sdk/ml/types/ml_update_request_auth_method.py +5 -0
  137. label_studio_sdk/ml/types/ml_update_response.py +78 -0
  138. label_studio_sdk/ml/types/ml_update_response_auth_method.py +5 -0
  139. label_studio_sdk/predictions/__init__.py +2 -0
  140. label_studio_sdk/predictions/client.py +638 -0
  141. label_studio_sdk/projects/__init__.py +6 -0
  142. label_studio_sdk/projects/client.py +1053 -0
  143. label_studio_sdk/projects/exports/__init__.py +2 -0
  144. label_studio_sdk/projects/exports/client.py +930 -0
  145. label_studio_sdk/projects/types/__init__.py +7 -0
  146. label_studio_sdk/projects/types/projects_create_response.py +96 -0
  147. label_studio_sdk/projects/types/projects_import_tasks_response.py +71 -0
  148. label_studio_sdk/projects/types/projects_list_response.py +33 -0
  149. label_studio_sdk/py.typed +0 -0
  150. label_studio_sdk/tasks/__init__.py +5 -0
  151. label_studio_sdk/tasks/client.py +811 -0
  152. label_studio_sdk/tasks/types/__init__.py +6 -0
  153. label_studio_sdk/tasks/types/tasks_list_request_fields.py +5 -0
  154. label_studio_sdk/tasks/types/tasks_list_response.py +48 -0
  155. label_studio_sdk/types/__init__.py +115 -0
  156. label_studio_sdk/types/annotation.py +116 -0
  157. label_studio_sdk/types/annotation_filter_options.py +42 -0
  158. label_studio_sdk/types/annotation_last_action.py +19 -0
  159. label_studio_sdk/types/azure_blob_export_storage.py +112 -0
  160. label_studio_sdk/types/azure_blob_export_storage_status.py +7 -0
  161. label_studio_sdk/types/azure_blob_import_storage.py +113 -0
  162. label_studio_sdk/types/azure_blob_import_storage_status.py +7 -0
  163. label_studio_sdk/types/base_task.py +113 -0
  164. label_studio_sdk/types/base_user.py +42 -0
  165. label_studio_sdk/types/converted_format.py +36 -0
  166. label_studio_sdk/types/converted_format_status.py +5 -0
  167. label_studio_sdk/types/export.py +48 -0
  168. label_studio_sdk/types/export_convert.py +32 -0
  169. label_studio_sdk/types/export_create.py +54 -0
  170. label_studio_sdk/types/export_create_status.py +5 -0
  171. label_studio_sdk/types/export_status.py +5 -0
  172. label_studio_sdk/types/file_upload.py +30 -0
  173. label_studio_sdk/types/filter.py +53 -0
  174. label_studio_sdk/types/filter_group.py +35 -0
  175. label_studio_sdk/types/gcs_export_storage.py +112 -0
  176. label_studio_sdk/types/gcs_export_storage_status.py +7 -0
  177. label_studio_sdk/types/gcs_import_storage.py +113 -0
  178. label_studio_sdk/types/gcs_import_storage_status.py +7 -0
  179. label_studio_sdk/types/local_files_export_storage.py +97 -0
  180. label_studio_sdk/types/local_files_export_storage_status.py +7 -0
  181. label_studio_sdk/types/local_files_import_storage.py +92 -0
  182. label_studio_sdk/types/local_files_import_storage_status.py +7 -0
  183. label_studio_sdk/types/ml_backend.py +89 -0
  184. label_studio_sdk/types/ml_backend_auth_method.py +5 -0
  185. label_studio_sdk/types/ml_backend_state.py +5 -0
  186. label_studio_sdk/types/prediction.py +78 -0
  187. label_studio_sdk/types/project.py +198 -0
  188. label_studio_sdk/types/project_import.py +63 -0
  189. label_studio_sdk/types/project_import_status.py +5 -0
  190. label_studio_sdk/types/project_label_config.py +32 -0
  191. label_studio_sdk/types/project_sampling.py +7 -0
  192. label_studio_sdk/types/project_skip_queue.py +5 -0
  193. label_studio_sdk/types/redis_export_storage.py +117 -0
  194. label_studio_sdk/types/redis_export_storage_status.py +7 -0
  195. label_studio_sdk/types/redis_import_storage.py +112 -0
  196. label_studio_sdk/types/redis_import_storage_status.py +7 -0
  197. label_studio_sdk/types/s3export_storage.py +134 -0
  198. label_studio_sdk/types/s3export_storage_status.py +7 -0
  199. label_studio_sdk/types/s3import_storage.py +140 -0
  200. label_studio_sdk/types/s3import_storage_status.py +7 -0
  201. label_studio_sdk/types/serialization_option.py +36 -0
  202. label_studio_sdk/types/serialization_options.py +45 -0
  203. label_studio_sdk/types/task.py +157 -0
  204. label_studio_sdk/types/task_filter_options.py +49 -0
  205. label_studio_sdk/types/user_simple.py +37 -0
  206. label_studio_sdk/types/view.py +55 -0
  207. label_studio_sdk/types/webhook.py +67 -0
  208. label_studio_sdk/types/webhook_actions_item.py +21 -0
  209. label_studio_sdk/types/webhook_serializer_for_update.py +67 -0
  210. label_studio_sdk/types/webhook_serializer_for_update_actions_item.py +21 -0
  211. label_studio_sdk/users/__init__.py +5 -0
  212. label_studio_sdk/users/client.py +830 -0
  213. label_studio_sdk/users/types/__init__.py +6 -0
  214. label_studio_sdk/users/types/users_get_token_response.py +36 -0
  215. label_studio_sdk/users/types/users_reset_token_response.py +36 -0
  216. label_studio_sdk/version.py +4 -0
  217. label_studio_sdk/views/__init__.py +31 -0
  218. label_studio_sdk/views/client.py +564 -0
  219. label_studio_sdk/views/types/__init__.py +29 -0
  220. label_studio_sdk/views/types/views_create_request_data.py +43 -0
  221. label_studio_sdk/views/types/views_create_request_data_filters.py +43 -0
  222. label_studio_sdk/views/types/views_create_request_data_filters_conjunction.py +5 -0
  223. label_studio_sdk/views/types/views_create_request_data_filters_items_item.py +47 -0
  224. label_studio_sdk/views/types/views_create_request_data_ordering_item.py +38 -0
  225. label_studio_sdk/views/types/views_create_request_data_ordering_item_direction.py +5 -0
  226. label_studio_sdk/views/types/views_update_request_data.py +43 -0
  227. label_studio_sdk/views/types/views_update_request_data_filters.py +43 -0
  228. label_studio_sdk/views/types/views_update_request_data_filters_conjunction.py +5 -0
  229. label_studio_sdk/views/types/views_update_request_data_filters_items_item.py +47 -0
  230. label_studio_sdk/views/types/views_update_request_data_ordering_item.py +38 -0
  231. label_studio_sdk/views/types/views_update_request_data_ordering_item_direction.py +5 -0
  232. label_studio_sdk/webhooks/__init__.py +5 -0
  233. label_studio_sdk/webhooks/client.py +636 -0
  234. label_studio_sdk/webhooks/types/__init__.py +5 -0
  235. label_studio_sdk/webhooks/types/webhooks_update_request_actions_item.py +21 -0
  236. label_studio_sdk-1.0.0.dist-info/METADATA +307 -0
  237. label_studio_sdk-1.0.0.dist-info/RECORD +239 -0
  238. {label_studio_sdk-0.0.32.dist-info → label_studio_sdk-1.0.0.dist-info}/WHEEL +1 -2
  239. docs/__init__.py +0 -3
  240. label_studio_sdk-0.0.32.dist-info/LICENSE +0 -201
  241. label_studio_sdk-0.0.32.dist-info/METADATA +0 -22
  242. label_studio_sdk-0.0.32.dist-info/RECORD +0 -15
  243. label_studio_sdk-0.0.32.dist-info/top_level.txt +0 -3
  244. tests/test_client.py +0 -26
  245. {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