supervisely 6.73.287__py3-none-any.whl → 6.73.289__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.
- supervisely/annotation/annotation.py +10 -2
- supervisely/api/annotation_api.py +26 -30
- supervisely/api/api.py +8 -4
- supervisely/api/entity_annotation/figure_api.py +7 -7
- supervisely/api/image_api.py +19 -19
- supervisely/app/widgets/bokeh/bokeh.py +213 -244
- supervisely/geometry/graph.py +7 -0
- supervisely/geometry/rectangle.py +3 -1
- supervisely/project/project.py +38 -37
- {supervisely-6.73.287.dist-info → supervisely-6.73.289.dist-info}/METADATA +1 -1
- {supervisely-6.73.287.dist-info → supervisely-6.73.289.dist-info}/RECORD +15 -15
- {supervisely-6.73.287.dist-info → supervisely-6.73.289.dist-info}/LICENSE +0 -0
- {supervisely-6.73.287.dist-info → supervisely-6.73.289.dist-info}/WHEEL +0 -0
- {supervisely-6.73.287.dist-info → supervisely-6.73.289.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.287.dist-info → supervisely-6.73.289.dist-info}/top_level.txt +0 -0
|
@@ -543,7 +543,7 @@ class Annotation:
|
|
|
543
543
|
image_id=take_with_default(image_id, self.image_id),
|
|
544
544
|
)
|
|
545
545
|
|
|
546
|
-
def _add_labels_impl(self, dest, labels):
|
|
546
|
+
def _add_labels_impl(self, dest: List, labels: List[Label]):
|
|
547
547
|
"""
|
|
548
548
|
The function _add_labels_impl extend list of the labels of the current Annotation object
|
|
549
549
|
:param dest: destination list of the Label class objects
|
|
@@ -554,7 +554,15 @@ class Annotation:
|
|
|
554
554
|
if self.img_size.count(None) == 0:
|
|
555
555
|
# image has resolution in DB
|
|
556
556
|
canvas_rect = Rectangle.from_size(self.img_size)
|
|
557
|
-
|
|
557
|
+
try:
|
|
558
|
+
dest.extend(label.crop(canvas_rect))
|
|
559
|
+
except Exception:
|
|
560
|
+
logger.error(
|
|
561
|
+
f"Cannot crop label of '{label.obj_class.name}' class "
|
|
562
|
+
"when extend list of the labels of the current Annotation object",
|
|
563
|
+
exc_info=True,
|
|
564
|
+
)
|
|
565
|
+
raise
|
|
558
566
|
else:
|
|
559
567
|
# image was uploaded by link and does not have resolution in DB
|
|
560
568
|
# add label without normalization and validation
|
|
@@ -1403,37 +1403,33 @@ class AnnotationApi(ModuleApi):
|
|
|
1403
1403
|
progress_cb(len(response.content))
|
|
1404
1404
|
|
|
1405
1405
|
result = response.json()
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1406
|
+
# Convert annotation to pixel coordinate system
|
|
1407
|
+
result[ApiField.ANNOTATION] = Annotation._to_pixel_coordinate_system_json(
|
|
1408
|
+
result[ApiField.ANNOTATION]
|
|
1409
|
+
)
|
|
1410
|
+
# check if there are any AlphaMask geometries in the batch
|
|
1411
|
+
additonal_geometries = defaultdict(int)
|
|
1412
|
+
labels = result[ApiField.ANNOTATION][AnnotationJsonFields.LABELS]
|
|
1413
|
+
for idx, label in enumerate(labels):
|
|
1414
|
+
if label[LabelJsonFields.GEOMETRY_TYPE] == AlphaMask.geometry_name():
|
|
1415
|
+
figure_id = label[LabelJsonFields.ID]
|
|
1416
|
+
additonal_geometries[figure_id] = idx
|
|
1417
|
+
|
|
1418
|
+
# if so, download them separately and update the annotation
|
|
1419
|
+
if len(additonal_geometries) > 0:
|
|
1420
|
+
figure_ids = list(additonal_geometries.keys())
|
|
1421
|
+
figures = await self._api.image.figure.download_geometries_batch_async(
|
|
1422
|
+
figure_ids,
|
|
1423
|
+
(progress_cb if progress_cb is not None and progress_cb_type == "size" else None),
|
|
1424
|
+
semaphore=semaphore,
|
|
1409
1425
|
)
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
# if so, download them separately and update the annotation
|
|
1419
|
-
if len(additonal_geometries) > 0:
|
|
1420
|
-
figure_ids = list(additonal_geometries.keys())
|
|
1421
|
-
figures = await self._api.image.figure.download_geometries_batch_async(
|
|
1422
|
-
figure_ids,
|
|
1423
|
-
(
|
|
1424
|
-
progress_cb
|
|
1425
|
-
if progress_cb is not None and progress_cb_type == "size"
|
|
1426
|
-
else None
|
|
1427
|
-
),
|
|
1428
|
-
semaphore=semaphore,
|
|
1429
|
-
)
|
|
1430
|
-
for figure_id, geometry in zip(figure_ids, figures):
|
|
1431
|
-
label_idx = additonal_geometries[figure_id]
|
|
1432
|
-
labels[label_idx].update({BITMAP: geometry})
|
|
1433
|
-
ann_info = self._convert_json_info(result)
|
|
1434
|
-
if progress_cb is not None and progress_cb_type == "number":
|
|
1435
|
-
progress_cb(1)
|
|
1436
|
-
return ann_info
|
|
1426
|
+
for figure_id, geometry in zip(figure_ids, figures):
|
|
1427
|
+
label_idx = additonal_geometries[figure_id]
|
|
1428
|
+
labels[label_idx].update({BITMAP: geometry})
|
|
1429
|
+
ann_info = self._convert_json_info(result)
|
|
1430
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
1431
|
+
progress_cb(1)
|
|
1432
|
+
return ann_info
|
|
1437
1433
|
|
|
1438
1434
|
async def download_batch_async(
|
|
1439
1435
|
self,
|
supervisely/api/api.py
CHANGED
|
@@ -66,10 +66,10 @@ import supervisely.io.env as sly_env
|
|
|
66
66
|
from supervisely._utils import camel_to_snake, is_community, is_development
|
|
67
67
|
from supervisely.api.module_api import ApiField
|
|
68
68
|
from supervisely.io.network_exceptions import (
|
|
69
|
+
RetryableRequestException,
|
|
69
70
|
process_requests_exception,
|
|
70
71
|
process_requests_exception_async,
|
|
71
72
|
process_unhandled_request,
|
|
72
|
-
RetryableRequestException,
|
|
73
73
|
)
|
|
74
74
|
from supervisely.project.project_meta import ProjectMeta
|
|
75
75
|
from supervisely.sly_logger import logger
|
|
@@ -380,6 +380,7 @@ class Api:
|
|
|
380
380
|
self.async_httpx_client: httpx.AsyncClient = None
|
|
381
381
|
self.httpx_client: httpx.Client = None
|
|
382
382
|
self._semaphore = None
|
|
383
|
+
self._instance_version = None
|
|
383
384
|
|
|
384
385
|
@classmethod
|
|
385
386
|
def normalize_server_address(cls, server_address: str) -> str:
|
|
@@ -515,11 +516,14 @@ class Api:
|
|
|
515
516
|
# '6.9.13'
|
|
516
517
|
"""
|
|
517
518
|
try:
|
|
518
|
-
|
|
519
|
+
if self._instance_version is None:
|
|
520
|
+
self._instance_version = (
|
|
521
|
+
self.post("instance.version", {}).json().get(ApiField.VERSION)
|
|
522
|
+
)
|
|
519
523
|
except Exception as e:
|
|
520
524
|
logger.warning(f"Failed to get instance version from server: {e}")
|
|
521
|
-
|
|
522
|
-
return
|
|
525
|
+
self._instance_version = "unknown"
|
|
526
|
+
return self._instance_version
|
|
523
527
|
|
|
524
528
|
def is_version_supported(self, version: Optional[str] = None) -> Union[bool, None]:
|
|
525
529
|
"""Check if the given version is lower or equal to the current Supervisely instance version.
|
|
@@ -642,13 +642,13 @@ class FigureApi(RemoveableBulkModuleApi):
|
|
|
642
642
|
response = await self._api.post_async(
|
|
643
643
|
"figures.bulk.download.geometry", {ApiField.IDS: batch_ids}
|
|
644
644
|
)
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
645
|
+
decoder = MultipartDecoder.from_response(response)
|
|
646
|
+
for part in decoder.parts:
|
|
647
|
+
content_utf8 = part.headers[b"Content-Disposition"].decode("utf-8")
|
|
648
|
+
# Find name="1245" preceded by a whitespace, semicolon or beginning of line.
|
|
649
|
+
# The regex has 2 capture group: one for the prefix and one for the actual name value.
|
|
650
|
+
figure_id = int(re.findall(r'(^|[\s;])name="(\d*)"', content_utf8)[0][1])
|
|
651
|
+
yield figure_id, part.content
|
|
652
652
|
|
|
653
653
|
async def download_geometries_batch_async(
|
|
654
654
|
self,
|
supervisely/api/image_api.py
CHANGED
|
@@ -4456,26 +4456,26 @@ class ImageApi(RemoveableBulkModuleApi):
|
|
|
4456
4456
|
json=json_body,
|
|
4457
4457
|
headers=headers,
|
|
4458
4458
|
)
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4459
|
+
decoder = MultipartDecoder.from_response(response)
|
|
4460
|
+
for part in decoder.parts:
|
|
4461
|
+
content_utf8 = part.headers[b"Content-Disposition"].decode("utf-8")
|
|
4462
|
+
# Find name="1245" preceded by a whitespace, semicolon or beginning of line.
|
|
4463
|
+
# The regex has 2 capture group: one for the prefix and one for the actual name value.
|
|
4464
|
+
img_id = int(re.findall(r'(^|[\s;])name="(\d*)"', content_utf8)[0][1])
|
|
4465
|
+
if check_hash:
|
|
4466
|
+
hhash = part.headers.get("x-content-checksum-sha256", None)
|
|
4467
|
+
if hhash is not None:
|
|
4468
|
+
downloaded_bytes_hash = get_bytes_hash(part)
|
|
4469
|
+
if hhash != downloaded_bytes_hash:
|
|
4470
|
+
raise RuntimeError(
|
|
4471
|
+
f"Downloaded hash of image with ID:{img_id} does not match the expected hash: {downloaded_bytes_hash} != {hhash}"
|
|
4472
|
+
)
|
|
4473
|
+
if progress_cb is not None and progress_cb_type == "number":
|
|
4474
|
+
progress_cb(1)
|
|
4475
|
+
elif progress_cb is not None and progress_cb_type == "size":
|
|
4476
|
+
progress_cb(len(part.content))
|
|
4477
4477
|
|
|
4478
|
-
|
|
4478
|
+
yield img_id, part.content
|
|
4479
4479
|
|
|
4480
4480
|
async def get_list_generator_async(
|
|
4481
4481
|
self,
|
|
@@ -4,17 +4,13 @@ import asyncio
|
|
|
4
4
|
import re
|
|
5
5
|
from abc import ABC, abstractmethod
|
|
6
6
|
from datetime import datetime
|
|
7
|
-
from typing import Any, Callable,
|
|
7
|
+
from typing import Any, Callable, List, Literal, Optional
|
|
8
8
|
from uuid import uuid4
|
|
9
9
|
|
|
10
10
|
from fastapi.responses import HTMLResponse
|
|
11
11
|
from pydantic import BaseModel
|
|
12
12
|
|
|
13
|
-
from supervisely._utils import is_production
|
|
14
|
-
from supervisely.api.api import Api
|
|
15
13
|
from supervisely.app.widgets import Widget
|
|
16
|
-
from supervisely.io.env import task_id
|
|
17
|
-
from supervisely.sly_logger import logger
|
|
18
14
|
|
|
19
15
|
|
|
20
16
|
class DebouncedEventHandler:
|
|
@@ -37,7 +33,6 @@ class DebouncedEventHandler:
|
|
|
37
33
|
|
|
38
34
|
class SelectedIds(BaseModel):
|
|
39
35
|
selected_ids: List[int]
|
|
40
|
-
plot_id: Optional[Union[str, int]] = None
|
|
41
36
|
|
|
42
37
|
|
|
43
38
|
class Bokeh(Widget):
|
|
@@ -77,14 +72,25 @@ class Bokeh(Widget):
|
|
|
77
72
|
|
|
78
73
|
from supervisely.app.widgets import Bokeh, IFrame
|
|
79
74
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
colors
|
|
85
|
-
|
|
75
|
+
data = {
|
|
76
|
+
"x": [1, 2, 3, 4, 5],
|
|
77
|
+
"y": [1, 2, 3, 4, 5],
|
|
78
|
+
"radius": [10, 20, 30, 40, 50],
|
|
79
|
+
"colors": ["red", "green", "blue", "yellow", "purple"],
|
|
80
|
+
"ids": [1, 2, 3, 4, 5],
|
|
81
|
+
"names": ["kiwi", "kiwi", "lemon", "lemon", "lemon"],
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
plot_lemon = Bokeh.Circle(name="lemon")
|
|
85
|
+
plot_kiwi = Bokeh.Circle(name="kiwi")
|
|
86
|
+
bokeh = Bokeh(
|
|
87
|
+
x_axis_visible=True,
|
|
88
|
+
y_axis_visible=True,
|
|
89
|
+
grid_visible=True,
|
|
90
|
+
show_legend=True,
|
|
86
91
|
)
|
|
87
|
-
bokeh
|
|
92
|
+
bokeh.add_data(**data)
|
|
93
|
+
bokeh.add_plots([plot_lemon, plot_kiwi])
|
|
88
94
|
|
|
89
95
|
# To allow the widget to be interacted with, you need to add it to the IFrame widget.
|
|
90
96
|
iframe = IFrame()
|
|
@@ -96,126 +102,72 @@ class Bokeh(Widget):
|
|
|
96
102
|
HTML_ROUTE = "bokeh.html"
|
|
97
103
|
|
|
98
104
|
class Plot(ABC):
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
105
|
+
def __init__(self, name: Optional[str] = None, **kwargs):
|
|
106
|
+
|
|
107
|
+
self._name = name or str(uuid4())
|
|
108
|
+
self.kwargs = kwargs
|
|
102
109
|
|
|
103
110
|
@abstractmethod
|
|
104
|
-
def
|
|
111
|
+
def add(self, plot, source) -> None:
|
|
105
112
|
pass
|
|
106
113
|
|
|
114
|
+
@property
|
|
115
|
+
def name(self) -> str:
|
|
116
|
+
return self._name
|
|
117
|
+
|
|
107
118
|
class Circle(Plot):
|
|
108
|
-
def
|
|
109
|
-
self,
|
|
110
|
-
x_coordinates: List[Union[int, float]],
|
|
111
|
-
y_coordinates: List[Union[int, float]],
|
|
112
|
-
radii: Optional[Union[Union[int, float], List[Union[int, float]]]] = None,
|
|
113
|
-
colors: Optional[Union[str, List[str]]] = None,
|
|
114
|
-
data: Optional[List[Any]] = None,
|
|
115
|
-
dynamic_selection: bool = False,
|
|
116
|
-
fill_alpha: float = 0.5,
|
|
117
|
-
line_color: Optional[str] = None,
|
|
118
|
-
legend_label: Optional[str] = None,
|
|
119
|
-
plot_id: Optional[Union[str, int]] = None,
|
|
120
|
-
):
|
|
121
|
-
if not colors:
|
|
122
|
-
colors = Bokeh._generate_colors(x_coordinates, y_coordinates)
|
|
123
|
-
elif isinstance(colors, str):
|
|
124
|
-
colors = [colors] * len(x_coordinates)
|
|
125
|
-
|
|
126
|
-
if not radii:
|
|
127
|
-
radii = Bokeh._generate_radii(x_coordinates, y_coordinates)
|
|
128
|
-
elif isinstance(radii, (int, float)):
|
|
129
|
-
radii = [radii] * len(x_coordinates)
|
|
130
|
-
|
|
131
|
-
if not len(x_coordinates) == len(y_coordinates) == len(radii) == len(colors):
|
|
132
|
-
raise ValueError(
|
|
133
|
-
"x_coordinates, y_coordinates, radii, and colors must have the same length"
|
|
134
|
-
)
|
|
135
|
-
|
|
136
|
-
if data is not None and len(data) != len(x_coordinates):
|
|
137
|
-
raise ValueError("data must have the same length as x_coordinates")
|
|
138
|
-
|
|
139
|
-
if data is None:
|
|
140
|
-
data = list(range(len(x_coordinates)))
|
|
141
|
-
|
|
142
|
-
self._x_coordinates = x_coordinates
|
|
143
|
-
self._y_coordinates = y_coordinates
|
|
144
|
-
self._radii = radii
|
|
145
|
-
self._colors = colors
|
|
146
|
-
self._data = data
|
|
147
|
-
self._source = None
|
|
148
|
-
self._dynamic_selection = dynamic_selection
|
|
149
|
-
self._fill_alpha = fill_alpha
|
|
150
|
-
self._line_color = line_color
|
|
151
|
-
self._plot_id = plot_id or uuid4().hex
|
|
152
|
-
self._legend_label = legend_label or str(self._plot_id)
|
|
153
|
-
|
|
154
|
-
def add(self, plot) -> None:
|
|
119
|
+
def add(self, plot, source) -> None:
|
|
155
120
|
from bokeh.models import ( # pylint: disable=import-error
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
)
|
|
159
|
-
|
|
160
|
-
data = dict(
|
|
161
|
-
x=self._x_coordinates,
|
|
162
|
-
y=self._y_coordinates,
|
|
163
|
-
radius=self._radii,
|
|
164
|
-
colors=self._colors,
|
|
165
|
-
ids=self._data,
|
|
121
|
+
CDSView,
|
|
122
|
+
GroupFilter,
|
|
166
123
|
)
|
|
167
|
-
self._source = ColumnDataSource(data=data)
|
|
168
124
|
|
|
169
|
-
|
|
125
|
+
filters = [GroupFilter(column_name="names", group=self.name, name=self.name)]
|
|
126
|
+
view = CDSView(source=source, filters=filters)
|
|
127
|
+
return plot.circle(
|
|
170
128
|
"x",
|
|
171
129
|
"y",
|
|
172
130
|
radius="radius",
|
|
173
131
|
fill_color="colors",
|
|
174
|
-
fill_alpha=
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
132
|
+
fill_alpha=0.5,
|
|
133
|
+
source=source,
|
|
134
|
+
line_color=None,
|
|
135
|
+
view=view,
|
|
178
136
|
)
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
}}
|
|
205
|
-
var xhr = new XMLHttpRequest();
|
|
206
|
-
xhr.open("POST", "{route_path}", true);
|
|
207
|
-
xhr.setRequestHeader("Content-Type", "application/json");
|
|
208
|
-
xhr.send(JSON.stringify({{selected_ids: selected_ids, plot_id: '{plot_id}'}}));
|
|
209
|
-
""".format(
|
|
210
|
-
route_path=route_path,
|
|
211
|
-
plot_id=self._plot_id,
|
|
212
|
-
),
|
|
137
|
+
|
|
138
|
+
class Scatter(Plot):
|
|
139
|
+
def add(self, plot, source) -> None:
|
|
140
|
+
from bokeh.models import ( # pylint: disable=import-error
|
|
141
|
+
CDSView,
|
|
142
|
+
GroupFilter,
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
filters = [GroupFilter(column_name="names", group=self.name, name=self.name)]
|
|
146
|
+
view = CDSView(source=source, filters=filters)
|
|
147
|
+
return plot.scatter(
|
|
148
|
+
"x",
|
|
149
|
+
"y",
|
|
150
|
+
size="radius",
|
|
151
|
+
color="colors",
|
|
152
|
+
fill_alpha=0.5,
|
|
153
|
+
source=source,
|
|
154
|
+
view=view,
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
class Line(Plot):
|
|
158
|
+
def add(self, plot, source) -> None:
|
|
159
|
+
from bokeh.models import ( # pylint: disable=import-error
|
|
160
|
+
CDSView,
|
|
161
|
+
GroupFilter,
|
|
213
162
|
)
|
|
214
|
-
|
|
163
|
+
|
|
164
|
+
filters = [GroupFilter(column_name="names", group=self.name, name=self.name)]
|
|
165
|
+
view = CDSView(source=source, filters=filters)
|
|
166
|
+
return plot.line("x", "y", source=source, view=view, line_width=2)
|
|
215
167
|
|
|
216
168
|
def __init__(
|
|
217
169
|
self,
|
|
218
|
-
plots: List[Plot],
|
|
170
|
+
plots: List[Plot] = None,
|
|
219
171
|
width: int = 1000,
|
|
220
172
|
height: int = 600,
|
|
221
173
|
tools: List[str] = [
|
|
@@ -223,7 +175,7 @@ class Bokeh(Widget):
|
|
|
223
175
|
"wheel_zoom",
|
|
224
176
|
"box_zoom",
|
|
225
177
|
"reset",
|
|
226
|
-
"save",
|
|
178
|
+
# "save",
|
|
227
179
|
"poly_select",
|
|
228
180
|
"tap",
|
|
229
181
|
"lasso_select",
|
|
@@ -244,8 +196,11 @@ class Bokeh(Widget):
|
|
|
244
196
|
if bokeh.__version__ != "3.1.1":
|
|
245
197
|
raise RuntimeError(f"Bokeh version {bokeh.__version__} is not supported. Use 3.1.1")
|
|
246
198
|
|
|
199
|
+
self._source_data = {"x": [], "y": [], "radius": [], "colors": [], "ids": [], "names": []}
|
|
200
|
+
self._source = None
|
|
247
201
|
self.widget_id = widget_id
|
|
248
|
-
self._plots = plots
|
|
202
|
+
self._plots = plots or []
|
|
203
|
+
self._view = None
|
|
249
204
|
|
|
250
205
|
self._width = width
|
|
251
206
|
self._height = height
|
|
@@ -259,56 +214,14 @@ class Bokeh(Widget):
|
|
|
259
214
|
self._legend_click_policy = legend_click_policy
|
|
260
215
|
|
|
261
216
|
super().__init__(widget_id=widget_id, file_path=__file__)
|
|
262
|
-
|
|
217
|
+
|
|
218
|
+
self._update()
|
|
263
219
|
|
|
264
220
|
server = self._sly_app.get_server()
|
|
265
221
|
|
|
266
222
|
@server.get(self.html_route)
|
|
267
223
|
def _html_response() -> None:
|
|
268
|
-
return HTMLResponse(content=self.
|
|
269
|
-
|
|
270
|
-
# TODO: support for offline mode
|
|
271
|
-
# JinjaWidgets().context.pop(self.widget_id, None) # remove the widget from index.html
|
|
272
|
-
|
|
273
|
-
def _load_chart(self, **kwargs):
|
|
274
|
-
from bokeh.models import Legend # pylint: disable=import-error
|
|
275
|
-
from bokeh.plotting import figure # pylint: disable=import-error
|
|
276
|
-
|
|
277
|
-
self._width = kwargs.get("width", self._width)
|
|
278
|
-
self._height = kwargs.get("height", self._height)
|
|
279
|
-
self._tools = kwargs.get("tools", self._tools)
|
|
280
|
-
self._toolbar_location = kwargs.get("toolbar_location", self._toolbar_location)
|
|
281
|
-
self._show_legend = kwargs.get("show_legend", self._show_legend)
|
|
282
|
-
self._legend_location = kwargs.get("legend_location", self._legend_location)
|
|
283
|
-
self._legend_click_policy = kwargs.get("legend_click_policy", self._legend_click_policy)
|
|
284
|
-
self._x_axis_visible = kwargs.get("x_axis_visible", self._x_axis_visible)
|
|
285
|
-
self._y_axis_visible = kwargs.get("y_axis_visible", self._y_axis_visible)
|
|
286
|
-
self._grid_visible = kwargs.get("grid_visible", self._grid_visible)
|
|
287
|
-
|
|
288
|
-
self._plot = figure(
|
|
289
|
-
width=self._width,
|
|
290
|
-
height=self._height,
|
|
291
|
-
tools=self._tools,
|
|
292
|
-
toolbar_location=self._toolbar_location,
|
|
293
|
-
)
|
|
294
|
-
|
|
295
|
-
if self._show_legend:
|
|
296
|
-
self._plot.add_layout(
|
|
297
|
-
Legend(click_policy=self._legend_click_policy),
|
|
298
|
-
self._legend_location,
|
|
299
|
-
)
|
|
300
|
-
|
|
301
|
-
self._plot.xaxis.visible = self._x_axis_visible
|
|
302
|
-
self._plot.yaxis.visible = self._y_axis_visible
|
|
303
|
-
self._plot.grid.visible = self._grid_visible
|
|
304
|
-
|
|
305
|
-
self._renderers = []
|
|
306
|
-
self._process_plots(self._plots)
|
|
307
|
-
self._update_html()
|
|
308
|
-
|
|
309
|
-
@property
|
|
310
|
-
def route_path(self) -> str:
|
|
311
|
-
return self.get_route_path(Bokeh.Routes.VALUE_CHANGED)
|
|
224
|
+
return HTMLResponse(content=self._get_html())
|
|
312
225
|
|
|
313
226
|
@property
|
|
314
227
|
def html_route(self) -> str:
|
|
@@ -318,48 +231,75 @@ class Bokeh(Widget):
|
|
|
318
231
|
def html_route_with_timestamp(self) -> str:
|
|
319
232
|
return f".{self.html_route}?t={datetime.now().timestamp()}"
|
|
320
233
|
|
|
321
|
-
def
|
|
322
|
-
|
|
323
|
-
self._process_plots(plots)
|
|
324
|
-
self._update_html()
|
|
234
|
+
def get_json_data(self):
|
|
235
|
+
return {}
|
|
325
236
|
|
|
326
|
-
def
|
|
327
|
-
|
|
328
|
-
self._renderers = []
|
|
329
|
-
self._plot.renderers = []
|
|
330
|
-
self._update_html()
|
|
237
|
+
def get_json_state(self):
|
|
238
|
+
return {}
|
|
331
239
|
|
|
332
|
-
def
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
self.
|
|
240
|
+
def _add_callbacks(self):
|
|
241
|
+
from bokeh.models import CustomJS # pylint: disable=import-error
|
|
242
|
+
|
|
243
|
+
route_path = self.get_route_path(Bokeh.Routes.VALUE_CHANGED)
|
|
244
|
+
callback = CustomJS(
|
|
245
|
+
args=dict(source=self._source),
|
|
246
|
+
code=f"""
|
|
247
|
+
var indices = source.selected.indices;
|
|
248
|
+
var selected_ids = [];
|
|
249
|
+
for (var i = 0; i < indices.length; i++) {{
|
|
250
|
+
selected_ids.push(source.data['ids'][indices[i]]);
|
|
251
|
+
}}
|
|
252
|
+
var xhr = new XMLHttpRequest();
|
|
253
|
+
xhr.open("POST", "{route_path}", true);
|
|
254
|
+
xhr.setRequestHeader("Content-Type", "application/json");
|
|
255
|
+
xhr.send(JSON.stringify({{selected_ids: selected_ids}}));
|
|
256
|
+
""",
|
|
257
|
+
)
|
|
258
|
+
self._source.selected.js_on_change("indices", callback)
|
|
336
259
|
|
|
337
|
-
def
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
self.
|
|
260
|
+
def _get_html(self) -> str:
|
|
261
|
+
return f"""<div>
|
|
262
|
+
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js"></script>
|
|
263
|
+
{self._script}
|
|
264
|
+
{self._div}
|
|
265
|
+
</div>"""
|
|
266
|
+
|
|
267
|
+
def _create_figure(self):
|
|
268
|
+
from bokeh.models import ( # pylint: disable=import-error
|
|
269
|
+
ColumnDataSource,
|
|
270
|
+
Legend,
|
|
271
|
+
)
|
|
272
|
+
from bokeh.plotting import figure # pylint: disable=import-error
|
|
273
|
+
|
|
274
|
+
self._plot = figure(width=self._width, height=self._height, tools=self._tools)
|
|
275
|
+
|
|
276
|
+
self._plot.xaxis.visible = self._x_axis_visible
|
|
277
|
+
self._plot.yaxis.visible = self._y_axis_visible
|
|
278
|
+
self._plot.grid.visible = self._grid_visible
|
|
342
279
|
|
|
343
|
-
|
|
280
|
+
self._source = ColumnDataSource(data=self._source_data)
|
|
281
|
+
self._add_callbacks()
|
|
282
|
+
legend_items = []
|
|
283
|
+
for plot in self._plots:
|
|
284
|
+
r = plot.add(self._plot, self._source)
|
|
285
|
+
legend_items.append((plot.name, [r]))
|
|
286
|
+
|
|
287
|
+
if self._show_legend:
|
|
288
|
+
self._plot.add_layout(
|
|
289
|
+
Legend(items=legend_items, click_policy=self._legend_click_policy),
|
|
290
|
+
self._legend_location,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
def _update_html(self):
|
|
344
294
|
from bokeh.embed import components # pylint: disable=import-error
|
|
345
295
|
|
|
346
|
-
script, self._div = components(self._plot
|
|
296
|
+
script, self._div = components(self._plot)
|
|
347
297
|
self._div_id = self._get_div_id(self._div)
|
|
348
298
|
self._script = self._update_script(script)
|
|
349
299
|
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"#%02x%02x%02x" % (int(r), int(g), 150)
|
|
354
|
-
for r, g in zip(
|
|
355
|
-
[50 + 2 * xi for xi in x_coordinates], [30 + 2 * yi for yi in y_coordinates]
|
|
356
|
-
)
|
|
357
|
-
]
|
|
358
|
-
return colors
|
|
359
|
-
|
|
360
|
-
@staticmethod
|
|
361
|
-
def _generate_radii(x_coordinates: List[int], y_coordinates: List[int]) -> List[int]:
|
|
362
|
-
return [1] * len(x_coordinates)
|
|
300
|
+
def _update(self):
|
|
301
|
+
self._create_figure()
|
|
302
|
+
self._update_html()
|
|
363
303
|
|
|
364
304
|
def _get_div_id(self, div: str) -> str:
|
|
365
305
|
match = re.search(r'id="([^"]+)"', div)
|
|
@@ -368,69 +308,98 @@ class Bokeh(Widget):
|
|
|
368
308
|
raise ValueError(f"No div id found in {div}")
|
|
369
309
|
|
|
370
310
|
def _update_script(self, script: str) -> str:
|
|
371
|
-
# TODO: Reimplement using regex.
|
|
372
311
|
insert_after = "const fn = function() {"
|
|
373
312
|
updated_script = ""
|
|
374
313
|
for line in script.split("\n"):
|
|
375
314
|
if line.strip().startswith(insert_after):
|
|
376
|
-
line
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
315
|
+
line += f"""
|
|
316
|
+
const el = document.querySelector('#{self._div_id}');
|
|
317
|
+
if (el === null) {{
|
|
318
|
+
setTimeout(fn, 200);
|
|
319
|
+
return;
|
|
320
|
+
}}
|
|
321
|
+
"""
|
|
381
322
|
updated_script += line + "\n"
|
|
382
323
|
return updated_script
|
|
383
324
|
|
|
384
|
-
def get_json_data(self):
|
|
385
|
-
return {}
|
|
386
|
-
|
|
387
|
-
def get_json_state(self):
|
|
388
|
-
return {}
|
|
389
|
-
|
|
390
325
|
def value_changed(self, func: Callable) -> Callable:
|
|
326
|
+
"""Registers a callback function that will be called when the chart is clicked."""
|
|
327
|
+
|
|
391
328
|
server = self._sly_app.get_server()
|
|
329
|
+
route_path = self.get_route_path(Bokeh.Routes.VALUE_CHANGED)
|
|
392
330
|
self._changes_handled = True
|
|
393
|
-
debounced_handler = DebouncedEventHandler(debounce_time=0.2)
|
|
331
|
+
debounced_handler = DebouncedEventHandler(debounce_time=0.2)
|
|
394
332
|
|
|
395
|
-
@server.post(
|
|
333
|
+
@server.post(route_path)
|
|
396
334
|
async def _click(data: SelectedIds) -> None:
|
|
397
|
-
debounced_handler.handle_event(data, func)
|
|
335
|
+
debounced_handler.handle_event(data.selected_ids, func)
|
|
398
336
|
|
|
399
337
|
return _click
|
|
400
338
|
|
|
401
|
-
def
|
|
402
|
-
|
|
403
|
-
<script type="text/javascript" src="https://cdn.bokeh.org/bokeh/release/bokeh-3.1.1.min.js"></script>
|
|
404
|
-
<script type="text/javascript"> {self._script} </script>
|
|
405
|
-
{self._div}
|
|
406
|
-
</div>"""
|
|
339
|
+
def clear(self) -> None:
|
|
340
|
+
"""Clears all data in the ColumnDataSource and removes plots."""
|
|
407
341
|
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
342
|
+
self._source_data = {key: [] for key in self._source_data.keys()}
|
|
343
|
+
self._plots = []
|
|
344
|
+
self._update()
|
|
345
|
+
|
|
346
|
+
def refresh(self) -> None:
|
|
347
|
+
"""Refreshes the chart by reloading the existing data and updating the HTML."""
|
|
348
|
+
|
|
349
|
+
self._update()
|
|
350
|
+
|
|
351
|
+
def update_chart_size(self, width: Optional[int] = None, height: Optional[int] = None):
|
|
352
|
+
"""Updates the size of the chart."""
|
|
353
|
+
|
|
354
|
+
if width:
|
|
355
|
+
self._width = width
|
|
356
|
+
if height:
|
|
357
|
+
self._height = height
|
|
358
|
+
self._update()
|
|
359
|
+
|
|
360
|
+
def add_data(
|
|
361
|
+
self,
|
|
362
|
+
x: List[float],
|
|
363
|
+
y: List[float],
|
|
364
|
+
radius: List[float],
|
|
365
|
+
colors: List[str],
|
|
366
|
+
ids: List[Any],
|
|
367
|
+
names: List[str],
|
|
368
|
+
append: bool = True,
|
|
369
|
+
):
|
|
370
|
+
"""Adds data to the chart."""
|
|
371
|
+
|
|
372
|
+
if append:
|
|
373
|
+
self._source.data["x"] += x
|
|
374
|
+
self._source.data["y"] += y
|
|
375
|
+
self._source.data["radius"] += radius
|
|
376
|
+
self._source.data["colors"] += colors
|
|
377
|
+
self._source.data["ids"] += ids
|
|
378
|
+
self._source.data["names"] += names
|
|
379
|
+
else:
|
|
380
|
+
self._source.data = {
|
|
381
|
+
"x": x,
|
|
382
|
+
"y": y,
|
|
383
|
+
"radius": radius,
|
|
384
|
+
"colors": colors,
|
|
385
|
+
"ids": ids,
|
|
386
|
+
"names": names,
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
def add_plot(self, plot: Plot):
|
|
390
|
+
"""Adds a plot to the chart."""
|
|
391
|
+
|
|
392
|
+
self._plots.append(plot)
|
|
393
|
+
self._update()
|
|
394
|
+
|
|
395
|
+
def add_plots(self, plots: List[Plot]):
|
|
396
|
+
"""Adds multiple plots to the chart."""
|
|
397
|
+
|
|
398
|
+
self._plots.extend(plots)
|
|
399
|
+
self._update()
|
|
428
400
|
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
self._load_chart()
|
|
401
|
+
def update_point_size(self, size: float):
|
|
402
|
+
"""Updates the size of the points in the chart."""
|
|
432
403
|
|
|
433
|
-
|
|
434
|
-
self.
|
|
435
|
-
self._height = height or self._height
|
|
436
|
-
self._load_chart()
|
|
404
|
+
self._source_data["radius"] = [size] * len(self._source_data["x"])
|
|
405
|
+
self._update()
|
supervisely/geometry/graph.py
CHANGED
|
@@ -9,6 +9,7 @@ from typing import Dict, List, Optional, Tuple, Union
|
|
|
9
9
|
import cv2
|
|
10
10
|
import numpy as np
|
|
11
11
|
|
|
12
|
+
from supervisely import logger
|
|
12
13
|
from supervisely.geometry.constants import (
|
|
13
14
|
CLASS_ID,
|
|
14
15
|
CREATED_AT,
|
|
@@ -215,6 +216,8 @@ class GraphNodes(Geometry):
|
|
|
215
216
|
updated_at=updated_at,
|
|
216
217
|
created_at=created_at,
|
|
217
218
|
)
|
|
219
|
+
if len(nodes) == 0:
|
|
220
|
+
raise ValueError("Empty list of nodes is not allowed for GraphNodes")
|
|
218
221
|
self._nodes = nodes
|
|
219
222
|
if isinstance(nodes, (list, tuple)):
|
|
220
223
|
self._nodes = {}
|
|
@@ -593,6 +596,10 @@ class GraphNodes(Geometry):
|
|
|
593
596
|
|
|
594
597
|
rectangle = figure.to_bbox()
|
|
595
598
|
"""
|
|
599
|
+
if self._nodes is None or len(self._nodes) == 0:
|
|
600
|
+
logger.warning(
|
|
601
|
+
f"Cannot create a bounding box from {self.name()} with empty nodes. Geometry ID: {self.sly_id} "
|
|
602
|
+
)
|
|
596
603
|
return Rectangle.from_geometries_list(
|
|
597
604
|
[Point.from_point_location(node.location) for node in self._nodes.values()]
|
|
598
605
|
)
|
|
@@ -476,7 +476,7 @@ class Rectangle(Geometry):
|
|
|
476
476
|
return cls(0, 0, size[0] - 1, size[1] - 1)
|
|
477
477
|
|
|
478
478
|
@classmethod
|
|
479
|
-
def from_geometries_list(cls, geometries: List[
|
|
479
|
+
def from_geometries_list(cls, geometries: List[Geometry]) -> Rectangle:
|
|
480
480
|
"""
|
|
481
481
|
Create Rectangle from given geometry objects.
|
|
482
482
|
|
|
@@ -494,6 +494,8 @@ class Rectangle(Geometry):
|
|
|
494
494
|
geom_objs = [sly.Point(100, 200), sly.Polyline([sly.PointLocation(730, 2104), sly.PointLocation(2479, 402)])]
|
|
495
495
|
figure_from_geom_objs = sly.Rectangle.from_geometries_list(geom_objs)
|
|
496
496
|
"""
|
|
497
|
+
if geometries is None or len(geometries) == 0:
|
|
498
|
+
raise ValueError("No geometries provided to create a Rectangle.")
|
|
497
499
|
bboxes = [g.to_bbox() for g in geometries]
|
|
498
500
|
top = min(bbox.top for bbox in bboxes)
|
|
499
501
|
left = min(bbox.left for bbox in bboxes)
|
supervisely/project/project.py
CHANGED
|
@@ -4780,26 +4780,6 @@ async def _download_project_async(
|
|
|
4780
4780
|
if semaphore is None:
|
|
4781
4781
|
semaphore = api.get_default_semaphore()
|
|
4782
4782
|
|
|
4783
|
-
# number of workers
|
|
4784
|
-
num_workers = min(kwargs.get("num_workers", semaphore._value), 10)
|
|
4785
|
-
|
|
4786
|
-
async def worker(queue: asyncio.Queue, stop_event: asyncio.Event):
|
|
4787
|
-
while not stop_event.is_set():
|
|
4788
|
-
task = await queue.get()
|
|
4789
|
-
if task is None:
|
|
4790
|
-
break
|
|
4791
|
-
try:
|
|
4792
|
-
await task
|
|
4793
|
-
except Exception as e:
|
|
4794
|
-
logger.error(f"Error in _download_project_async worker: {e}")
|
|
4795
|
-
stop_event.set()
|
|
4796
|
-
finally:
|
|
4797
|
-
queue.task_done()
|
|
4798
|
-
|
|
4799
|
-
queue = asyncio.Queue()
|
|
4800
|
-
stop_event = asyncio.Event()
|
|
4801
|
-
workers = [asyncio.create_task(worker(queue, stop_event)) for _ in range(num_workers)]
|
|
4802
|
-
|
|
4803
4783
|
dataset_ids = set(dataset_ids) if (dataset_ids is not None) else None
|
|
4804
4784
|
project_fs = None
|
|
4805
4785
|
meta = ProjectMeta.from_json(api.project.get_meta(project_id, with_settings=True))
|
|
@@ -4883,11 +4863,25 @@ async def _download_project_async(
|
|
|
4883
4863
|
ds_progress(1)
|
|
4884
4864
|
return to_download
|
|
4885
4865
|
|
|
4866
|
+
async def run_tasks_with_delay(tasks, delay=0.1):
|
|
4867
|
+
created_tasks = []
|
|
4868
|
+
for task in tasks:
|
|
4869
|
+
created_task = asyncio.create_task(task)
|
|
4870
|
+
created_tasks.append(created_task)
|
|
4871
|
+
await asyncio.sleep(delay)
|
|
4872
|
+
logger.debug(
|
|
4873
|
+
f"{len(created_tasks)} tasks have been created for dataset ID: {dataset.id}, Name: {dataset.name}"
|
|
4874
|
+
)
|
|
4875
|
+
return created_tasks
|
|
4876
|
+
|
|
4877
|
+
tasks = []
|
|
4886
4878
|
small_images = await check_items(small_images)
|
|
4887
4879
|
large_images = await check_items(large_images)
|
|
4880
|
+
|
|
4888
4881
|
if len(small_images) == 1:
|
|
4889
4882
|
large_images.append(small_images.pop())
|
|
4890
4883
|
for images_batch in batched(small_images, batch_size=batch_size):
|
|
4884
|
+
|
|
4891
4885
|
task = _download_project_items_batch_async(
|
|
4892
4886
|
api=api,
|
|
4893
4887
|
dataset_id=dataset_id,
|
|
@@ -4901,7 +4895,7 @@ async def _download_project_async(
|
|
|
4901
4895
|
only_image_tags=only_image_tags,
|
|
4902
4896
|
progress_cb=ds_progress,
|
|
4903
4897
|
)
|
|
4904
|
-
|
|
4898
|
+
tasks.append(task)
|
|
4905
4899
|
for image in large_images:
|
|
4906
4900
|
task = _download_project_item_async(
|
|
4907
4901
|
api=api,
|
|
@@ -4915,9 +4909,10 @@ async def _download_project_async(
|
|
|
4915
4909
|
only_image_tags=only_image_tags,
|
|
4916
4910
|
progress_cb=ds_progress,
|
|
4917
4911
|
)
|
|
4918
|
-
|
|
4912
|
+
tasks.append(task)
|
|
4919
4913
|
|
|
4920
|
-
|
|
4914
|
+
created_tasks = await run_tasks_with_delay(tasks)
|
|
4915
|
+
await asyncio.gather(*created_tasks)
|
|
4921
4916
|
|
|
4922
4917
|
if save_image_meta:
|
|
4923
4918
|
meta_dir = dataset_fs.meta_dir
|
|
@@ -4934,13 +4929,6 @@ async def _download_project_async(
|
|
|
4934
4929
|
if item_name not in items_names_set:
|
|
4935
4930
|
dataset_fs.delete_item(item_name)
|
|
4936
4931
|
|
|
4937
|
-
for _ in range(num_workers):
|
|
4938
|
-
await queue.put(None)
|
|
4939
|
-
await asyncio.gather(*workers)
|
|
4940
|
-
|
|
4941
|
-
if stop_event.is_set():
|
|
4942
|
-
raise RuntimeError("Download process was stopped due to an error in one of the workers.")
|
|
4943
|
-
|
|
4944
4932
|
try:
|
|
4945
4933
|
create_readme(dest_dir, project_id, api)
|
|
4946
4934
|
except Exception as e:
|
|
@@ -4964,7 +4952,7 @@ async def _download_project_item_async(
|
|
|
4964
4952
|
"""
|
|
4965
4953
|
if save_images:
|
|
4966
4954
|
logger.debug(
|
|
4967
|
-
f"Downloading 1 image in single mode: {img_info.
|
|
4955
|
+
f"Downloading 1 image in single mode with _download_project_item_async. ID: {img_info.id}, Name: {img_info.name}"
|
|
4968
4956
|
)
|
|
4969
4957
|
img_bytes = await api.image.download_bytes_single_async(
|
|
4970
4958
|
img_info.id, semaphore=semaphore, check_hash=True
|
|
@@ -4982,7 +4970,11 @@ async def _download_project_item_async(
|
|
|
4982
4970
|
force_metadata_for_links=not save_images,
|
|
4983
4971
|
)
|
|
4984
4972
|
ann_json = ann_info.annotation
|
|
4985
|
-
|
|
4973
|
+
try:
|
|
4974
|
+
tmp_ann = Annotation.from_json(ann_json, meta)
|
|
4975
|
+
except Exception:
|
|
4976
|
+
logger.error(f"Error while deserializing annotation for image with ID: {img_info.id}")
|
|
4977
|
+
raise
|
|
4986
4978
|
if None in tmp_ann.img_size:
|
|
4987
4979
|
tmp_ann = tmp_ann.clone(img_size=(img_info.height, img_info.width))
|
|
4988
4980
|
ann_json = tmp_ann.to_json()
|
|
@@ -5004,6 +4996,7 @@ async def _download_project_item_async(
|
|
|
5004
4996
|
)
|
|
5005
4997
|
if progress_cb is not None:
|
|
5006
4998
|
progress_cb(1)
|
|
4999
|
+
logger.debug(f"Single project item has been downloaded. Semaphore state: {semaphore._value}")
|
|
5007
5000
|
|
|
5008
5001
|
|
|
5009
5002
|
async def _download_project_items_batch_async(
|
|
@@ -5056,12 +5049,18 @@ async def _download_project_items_batch_async(
|
|
|
5056
5049
|
semaphore=semaphore,
|
|
5057
5050
|
force_metadata_for_links=not save_images,
|
|
5058
5051
|
)
|
|
5059
|
-
tmps_anns = [Annotation.from_json(ann_info.annotation, meta) for ann_info in ann_infos]
|
|
5060
5052
|
ann_jsons = []
|
|
5061
|
-
for
|
|
5062
|
-
|
|
5063
|
-
tmp_ann =
|
|
5064
|
-
|
|
5053
|
+
for img_info, ann_info in zip(img_infos, ann_infos):
|
|
5054
|
+
try:
|
|
5055
|
+
tmp_ann = Annotation.from_json(ann_info.annotation, meta)
|
|
5056
|
+
if None in tmp_ann.img_size:
|
|
5057
|
+
tmp_ann = tmp_ann.clone(img_size=(img_info.height, img_info.width))
|
|
5058
|
+
ann_jsons.append(tmp_ann.to_json())
|
|
5059
|
+
except Exception:
|
|
5060
|
+
logger.error(
|
|
5061
|
+
f"Error while deserializing annotation for image with ID: {img_info.id}"
|
|
5062
|
+
)
|
|
5063
|
+
raise
|
|
5065
5064
|
else:
|
|
5066
5065
|
ann_jsons = []
|
|
5067
5066
|
for img_info in img_infos:
|
|
@@ -5083,5 +5082,7 @@ async def _download_project_items_batch_async(
|
|
|
5083
5082
|
if progress_cb is not None:
|
|
5084
5083
|
progress_cb(1)
|
|
5085
5084
|
|
|
5085
|
+
logger.debug(f"Batch of project items has been downloaded. Semaphore state: {semaphore._value}")
|
|
5086
|
+
|
|
5086
5087
|
|
|
5087
5088
|
DatasetDict = Project.DatasetDict
|
|
@@ -5,7 +5,7 @@ supervisely/function_wrapper.py,sha256=R5YajTQ0GnRp2vtjwfC9hINkzQc0JiyGsu8TER373
|
|
|
5
5
|
supervisely/sly_logger.py,sha256=LG1wTyyctyEKuCuKM2IKf_SMPH7BzkTsFdO-0tnorzg,6225
|
|
6
6
|
supervisely/tiny_timer.py,sha256=hkpe_7FE6bsKL79blSs7WBaktuPavEVu67IpEPrfmjE,183
|
|
7
7
|
supervisely/annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
supervisely/annotation/annotation.py,sha256=
|
|
8
|
+
supervisely/annotation/annotation.py,sha256=5AG1AhebkmiYy2r7nKbz6TjdmCF4tuf9FtqUjLLs7aU,114659
|
|
9
9
|
supervisely/annotation/annotation_transforms.py,sha256=TlVy_gUbM-XH6GbLpZPrAi6pMIGTr7Ow02iSKOSTa-I,9582
|
|
10
10
|
supervisely/annotation/json_geometries_map.py,sha256=nL6AmMhFy02fw9ryBm75plKyOkDh61QdOToSuLAcz_Q,1659
|
|
11
11
|
supervisely/annotation/label.py,sha256=NpHZ5o2H6dI4KiII22o2HpiLXG1yekh-bEy8WvI2Ljg,37498
|
|
@@ -21,14 +21,14 @@ supervisely/annotation/tag_meta_mapper.py,sha256=RWeTrxJ64syodyhXIRSH007bX6Hr3B4
|
|
|
21
21
|
supervisely/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
22
|
supervisely/api/advanced_api.py,sha256=Nd5cCnHFWc3PSUrCtENxTGtDjS37_lCHXsgXvUI3Ti8,2054
|
|
23
23
|
supervisely/api/agent_api.py,sha256=ShWAIlXcWXcyI9fqVuP5GZVCigCMJmjnvdGUfLspD6Y,8890
|
|
24
|
-
supervisely/api/annotation_api.py,sha256=
|
|
25
|
-
supervisely/api/api.py,sha256=
|
|
24
|
+
supervisely/api/annotation_api.py,sha256=fVQJOg5SLcD_mRUmPaVsJIOVTGFhsabRXqve0LyUgrc,64743
|
|
25
|
+
supervisely/api/api.py,sha256=YBE6yi682H5dy3BBQtESmfC9hKZcbHyYRPNGLRldgSU,66014
|
|
26
26
|
supervisely/api/app_api.py,sha256=RsbVej8WxWVn9cNo5s3Fqd1symsCdsfOaKVBKEUapRY,71927
|
|
27
27
|
supervisely/api/dataset_api.py,sha256=GH7prDRJKyJlTv_7_Y-RkTwJN7ED4EkXNqqmi3iIdI4,41352
|
|
28
28
|
supervisely/api/file_api.py,sha256=v2FsD3oljwNPqcDgEJRe8Bu5k0PYKzVhqmRb5QFaHAQ,83422
|
|
29
29
|
supervisely/api/github_api.py,sha256=NIexNjEer9H5rf5sw2LEZd7C1WR-tK4t6IZzsgeAAwQ,623
|
|
30
30
|
supervisely/api/image_annotation_tool_api.py,sha256=YcUo78jRDBJYvIjrd-Y6FJAasLta54nnxhyaGyanovA,5237
|
|
31
|
-
supervisely/api/image_api.py,sha256=
|
|
31
|
+
supervisely/api/image_api.py,sha256=bSal6vB2c7Ct2qDarXTaTmXy7x0X1VlV8oTuT6YpY2o,191061
|
|
32
32
|
supervisely/api/import_storage_api.py,sha256=BDCgmR0Hv6OoiRHLCVPKt3iDxSVlQp1WrnKhAK_Zl84,460
|
|
33
33
|
supervisely/api/issues_api.py,sha256=BqDJXmNoTzwc3xe6_-mA7FDFC5QQ-ahGbXk_HmpkSeQ,17925
|
|
34
34
|
supervisely/api/labeling_job_api.py,sha256=odnzZjp29yM16Gq-FYkv-OA4WFMNJCLFo4qSikW2A7c,56280
|
|
@@ -49,7 +49,7 @@ supervisely/api/video_annotation_tool_api.py,sha256=3A9-U8WJzrTShP_n9T8U01M9FzGY
|
|
|
49
49
|
supervisely/api/workspace_api.py,sha256=5KAxpI9DKBmgF_pyQaXHpGT30HZ9wRtR6DP3FoYFZtY,9228
|
|
50
50
|
supervisely/api/entity_annotation/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
51
51
|
supervisely/api/entity_annotation/entity_annotation_api.py,sha256=K79KdDyepQv4FiNQHBj9V4-zLIemxK9WG1ig1bfBKb8,3083
|
|
52
|
-
supervisely/api/entity_annotation/figure_api.py,sha256=
|
|
52
|
+
supervisely/api/entity_annotation/figure_api.py,sha256=jNObHAjy2JdXvKLP5IeBWISDjrZn_Budxp9J3Odyhxo,24531
|
|
53
53
|
supervisely/api/entity_annotation/object_api.py,sha256=gbcNvN_KY6G80Me8fHKQgryc2Co7VU_kfFd1GYILZ4E,8875
|
|
54
54
|
supervisely/api/entity_annotation/tag_api.py,sha256=M-28m9h8R4k9Eqo6P1S0UH8_D5kqCwAvQLYY6_Yz4oM,11161
|
|
55
55
|
supervisely/api/pointcloud/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -129,7 +129,7 @@ supervisely/app/widgets/binded_input_number/__init__.py,sha256=47DEQpj8HBSa-_TIm
|
|
|
129
129
|
supervisely/app/widgets/binded_input_number/binded_input_number.py,sha256=RXTAGaMXtGOl4pprVLyQosr61t2rI_U_U8VNHhSyBhA,6251
|
|
130
130
|
supervisely/app/widgets/binded_input_number/template.html,sha256=uCEZ54BNC2itr39wxxThXw62WlJ9659cuz8osCm0WZE,162
|
|
131
131
|
supervisely/app/widgets/bokeh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
132
|
-
supervisely/app/widgets/bokeh/bokeh.py,sha256=
|
|
132
|
+
supervisely/app/widgets/bokeh/bokeh.py,sha256=H53AqKc3sGJU3aoYeIdFfJoeIquyxbIHCDV8CRBjrUs,13262
|
|
133
133
|
supervisely/app/widgets/bokeh/template.html,sha256=ntsh7xx4q9OHG62sa_r3INDxsXgvdPFIWTtYaWn_0t8,12
|
|
134
134
|
supervisely/app/widgets/button/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
135
135
|
supervisely/app/widgets/button/button.py,sha256=zDQinhOOjNNxdP2GIFrwTmVfGAeJJoKV6CT6C8KzQNI,10405
|
|
@@ -677,7 +677,7 @@ supervisely/geometry/cuboid.py,sha256=oxsRoTKuwTNxH4Vp6khyvw1TCrBagSWNV5HmQKJZHt
|
|
|
677
677
|
supervisely/geometry/cuboid_2d.py,sha256=-oXeKiUS2gguQ4GyIZYp1cNPPhOLsGOFZl7uI71BfZM,13438
|
|
678
678
|
supervisely/geometry/cuboid_3d.py,sha256=x472ZPHTZDIY5Dj8tKbLQG3BCukFPgSvPJlxfHdKi1w,4168
|
|
679
679
|
supervisely/geometry/geometry.py,sha256=dbXnct8hrr7Wour6yCrtAef22KSJ2uYRm1F5GE10_MM,15287
|
|
680
|
-
supervisely/geometry/graph.py,sha256=
|
|
680
|
+
supervisely/geometry/graph.py,sha256=RDdZtN_P7TKAg4s_QXluCGzdmhD-IeonvK4Pix924kk,24474
|
|
681
681
|
supervisely/geometry/helpers.py,sha256=2gdYMFWTAr836gVXcp-lkDQs9tdaV0ou33kj3mzJBQA,5132
|
|
682
682
|
supervisely/geometry/image_rotator.py,sha256=wrU8cXEUfuNcmPms2myUV4BpZqz_2oDArsEUFeiTpxs,6888
|
|
683
683
|
supervisely/geometry/main_tests.py,sha256=K3Olsz9igHDW2IfIA5JOpjoE8bZ3ex2PXvVR2ZCDrHU,27199
|
|
@@ -689,7 +689,7 @@ supervisely/geometry/point_location.py,sha256=vLu5pWdtAi-WVQUKgFO7skigTaR-mtWR0t
|
|
|
689
689
|
supervisely/geometry/pointcloud.py,sha256=cc4P_UNLGx5dWah3caRJytW7_mAi8UnYsJOa20mUy8s,1472
|
|
690
690
|
supervisely/geometry/polygon.py,sha256=cAgCR8ccdGtieJTnmDnupPALMEwerHIqMWx7k3OCzVQ,11594
|
|
691
691
|
supervisely/geometry/polyline.py,sha256=LjjD-YGVDw1TQ84_IOHqnq43JFuSnsGdGMx404olYDs,8258
|
|
692
|
-
supervisely/geometry/rectangle.py,sha256=
|
|
692
|
+
supervisely/geometry/rectangle.py,sha256=QaBcSPeH87rcwsSft1TavEdCe4NpvfHZztZMEmzIxGk,33869
|
|
693
693
|
supervisely/geometry/sliding_windows.py,sha256=VWtE3DS9AaIlS0ch0PY6wwtWU89J82icDRZ-F0LFrjM,1700
|
|
694
694
|
supervisely/geometry/sliding_windows_fuzzy.py,sha256=InvJlH6MEW55DM1IdoMHP2MLFLieTDZfHrZZEINLQOc,3626
|
|
695
695
|
supervisely/geometry/validation.py,sha256=G5vjtiXTCaTQvWegPIBiNw8pN_GiY86OUSRSsccdyLU,2139
|
|
@@ -1009,7 +1009,7 @@ supervisely/project/data_version.py,sha256=nknaWJSUCwoDyNG9_d1KA-GjzidhV9zd9Cn8c
|
|
|
1009
1009
|
supervisely/project/download.py,sha256=zb8sb4XZ6Qi3CP7fmtLRUAYzaxs_W0WnOfe2x3ZVRMs,24639
|
|
1010
1010
|
supervisely/project/pointcloud_episode_project.py,sha256=yiWdNBQiI6f1O9sr1pg8JHW6O-w3XUB1rikJNn3Oung,41866
|
|
1011
1011
|
supervisely/project/pointcloud_project.py,sha256=Kx1Vaes-krwG3BiRRtHRLQxb9G5m5bTHPN9IzRqmNWo,49399
|
|
1012
|
-
supervisely/project/project.py,sha256=
|
|
1012
|
+
supervisely/project/project.py,sha256=34fAbYV4VdfVSqMs0a5ggAIwELd8nPb-uGoaC1F7h4I,202299
|
|
1013
1013
|
supervisely/project/project_meta.py,sha256=26s8IiHC5Pg8B1AQi6_CrsWteioJP2in00cRNe8QlW0,51423
|
|
1014
1014
|
supervisely/project/project_settings.py,sha256=NLThzU_DCynOK6hkHhVdFyezwprn9UqlnrLDe_3qhkY,9347
|
|
1015
1015
|
supervisely/project/project_type.py,sha256=_3RqW2CnDBKFOvSIrQT1RJQaiHirs34_jiQS8CkwCpo,530
|
|
@@ -1071,9 +1071,9 @@ supervisely/worker_proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
|
|
|
1071
1071
|
supervisely/worker_proto/worker_api_pb2.py,sha256=VQfi5JRBHs2pFCK1snec3JECgGnua3Xjqw_-b3aFxuM,59142
|
|
1072
1072
|
supervisely/worker_proto/worker_api_pb2_grpc.py,sha256=3BwQXOaP9qpdi0Dt9EKG--Lm8KGN0C5AgmUfRv77_Jk,28940
|
|
1073
1073
|
supervisely_lib/__init__.py,sha256=7-3QnN8Zf0wj8NCr2oJmqoQWMKKPKTECvjH9pd2S5vY,159
|
|
1074
|
-
supervisely-6.73.
|
|
1075
|
-
supervisely-6.73.
|
|
1076
|
-
supervisely-6.73.
|
|
1077
|
-
supervisely-6.73.
|
|
1078
|
-
supervisely-6.73.
|
|
1079
|
-
supervisely-6.73.
|
|
1074
|
+
supervisely-6.73.289.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
1075
|
+
supervisely-6.73.289.dist-info/METADATA,sha256=RMgZIP_bwLgos0vF9tP2PFNzmKVI5h1VFkdK_bX7WKE,33573
|
|
1076
|
+
supervisely-6.73.289.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
|
|
1077
|
+
supervisely-6.73.289.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
|
|
1078
|
+
supervisely-6.73.289.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
|
|
1079
|
+
supervisely-6.73.289.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|