supervisely 6.73.264__py3-none-any.whl → 6.73.266__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of supervisely might be problematic. Click here for more details.

@@ -72,6 +72,7 @@ class DatasetInfo(NamedTuple):
72
72
  workspace_id: int
73
73
  parent_id: Union[int, None]
74
74
  custom_data: dict
75
+ created_by: int
75
76
 
76
77
  @property
77
78
  def image_preview_url(self):
@@ -147,6 +148,7 @@ class DatasetApi(UpdateableModule, RemoveableModuleApi):
147
148
  ApiField.WORKSPACE_ID,
148
149
  ApiField.PARENT_ID,
149
150
  ApiField.CUSTOM_DATA,
151
+ ApiField.CREATED_BY_ID[0][0],
150
152
  ]
151
153
 
152
154
  @staticmethod
@@ -164,6 +164,9 @@ class ImageInfo(NamedTuple):
164
164
  #: 'createdAt': '2021-03-05T14:15:39.923Z', 'updatedAt': '2021-03-05T14:15:39.923Z'}, {...}]".
165
165
  tags: List[Dict]
166
166
 
167
+ #: :class:`str`: Id of a user who created the image.
168
+ created_by: str
169
+
167
170
  @property
168
171
  def preview_url(self):
169
172
  """
@@ -233,6 +236,7 @@ class ImageApi(RemoveableBulkModuleApi):
233
236
  ApiField.PATH_ORIGINAL,
234
237
  ApiField.FULL_STORAGE_URL,
235
238
  ApiField.TAGS,
239
+ ApiField.CREATED_BY_ID[0][0],
236
240
  ]
237
241
 
238
242
  @staticmethod
@@ -1,23 +1,96 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import re
4
5
  from abc import ABC, abstractmethod
5
6
  from datetime import datetime
6
7
  from typing import Any, Callable, Dict, List, Literal, Optional, Union
8
+ from uuid import uuid4
7
9
 
8
10
  from fastapi.responses import HTMLResponse
9
11
  from pydantic import BaseModel
10
- from supervisely.app.widgets import Widget
12
+
11
13
  from supervisely._utils import is_production
12
- from supervisely.io.env import task_id
13
14
  from supervisely.api.api import Api
15
+ from supervisely.app.widgets import Widget
16
+ from supervisely.io.env import task_id
17
+ from supervisely.sly_logger import logger
18
+
19
+
20
+ class DebouncedEventHandler:
21
+ def __init__(self, debounce_time: float = 0.1):
22
+ self._event_queue = []
23
+ self._debounce_time = debounce_time
24
+ self._task = None
25
+
26
+ async def _process_events(self, func: Callable):
27
+ await asyncio.sleep(self._debounce_time)
28
+ aggregated_events = self._event_queue.copy()
29
+ self._event_queue.clear()
30
+ func(aggregated_events)
31
+
32
+ def handle_event(self, event_data, func: Callable):
33
+ self._event_queue.append(event_data)
34
+ if self._task is None or self._task.done():
35
+ self._task = asyncio.create_task(self._process_events(func))
14
36
 
15
37
 
16
38
  class SelectedIds(BaseModel):
17
- selected_ids: List[Any]
39
+ selected_ids: List[int]
40
+ plot_id: Optional[Union[str, int]] = None
18
41
 
19
42
 
20
43
  class Bokeh(Widget):
44
+ """
45
+ Bokeh widget for creating interactive plots.
46
+
47
+ Note:
48
+ Only Bokeh version 3.1.1 is supported.
49
+
50
+ :param plots: List of plots to be displayed.
51
+ :type plots: List[Plot]
52
+ :param width: Width of the chart in pixels.
53
+ :type width: int
54
+ :param height: Height of the chart in pixels.
55
+ :type height: int
56
+ :param tools: List of tools to be displayed on the chart.
57
+ :type tools: List[str]
58
+ :param toolbar_location: Location of the toolbar.
59
+ :type toolbar_location: Literal["above", "below", "left", "right"]
60
+ :param x_axis_visible: If True, x-axis will be visible.
61
+ :type x_axis_visible: bool
62
+ :param y_axis_visible: If True, y-axis will be visible.
63
+ :type y_axis_visible: bool
64
+ :param grid_visible: If True, grid will be visible.
65
+ :type grid_visible: bool
66
+ :param widget_id: Unique widget identifier.
67
+ :type widget_id: str
68
+ :param show_legend: If True, ckickable legend widget will be displayed.
69
+ :type show_legend: bool
70
+ :param legend_location: Location of the clickable legend widget.
71
+ :type legend_location: Literal["left", "top", "right", "bottom"]
72
+ :param legend_click_policy: Click policy of the clickable legend widget.
73
+ :type legend_click_policy: Literal["hide", "mute"]
74
+
75
+ :Usage example:
76
+ .. code-block:: python
77
+
78
+ from supervisely.app.widgets import Bokeh, IFrame
79
+
80
+ plot = Bokeh.Circle(
81
+ x_coordinates=[1, 2, 3, 4, 5],
82
+ y_coordinates=[1, 2, 3, 4, 5],
83
+ radii=10,
84
+ colors="red",
85
+ legend_label="Circle plot",
86
+ )
87
+ bokeh = Bokeh(plots=[plot], width=1000, height=600)
88
+
89
+ # To allow the widget to be interacted with, you need to add it to the IFrame widget.
90
+ iframe = IFrame()
91
+ iframe.set(bokeh.html_route_with_timestamp, height="650px", width="100%")
92
+ """
93
+
21
94
  class Routes:
22
95
  VALUE_CHANGED = "value_changed"
23
96
  HTML_ROUTE = "bokeh.html"
@@ -42,6 +115,8 @@ class Bokeh(Widget):
42
115
  dynamic_selection: bool = False,
43
116
  fill_alpha: float = 0.5,
44
117
  line_color: Optional[str] = None,
118
+ legend_label: Optional[str] = None,
119
+ plot_id: Optional[Union[str, int]] = None,
45
120
  ):
46
121
  if not colors:
47
122
  colors = Bokeh._generate_colors(x_coordinates, y_coordinates)
@@ -73,6 +148,8 @@ class Bokeh(Widget):
73
148
  self._dynamic_selection = dynamic_selection
74
149
  self._fill_alpha = fill_alpha
75
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)
76
153
 
77
154
  def add(self, plot) -> None:
78
155
  from bokeh.models import ( # pylint: disable=import-error
@@ -97,6 +174,7 @@ class Bokeh(Widget):
97
174
  fill_alpha=self._fill_alpha,
98
175
  line_color=self._line_color,
99
176
  source=self._source,
177
+ legend_label=self._legend_label,
100
178
  )
101
179
  if not self._dynamic_selection:
102
180
  for tool in plot.tools:
@@ -127,9 +205,10 @@ class Bokeh(Widget):
127
205
  var xhr = new XMLHttpRequest();
128
206
  xhr.open("POST", "{route_path}", true);
129
207
  xhr.setRequestHeader("Content-Type", "application/json");
130
- xhr.send(JSON.stringify({{selected_ids: selected_ids}}));
208
+ xhr.send(JSON.stringify({{selected_ids: selected_ids, plot_id: '{plot_id}'}}));
131
209
  """.format(
132
- route_path=route_path
210
+ route_path=route_path,
211
+ plot_id=self._plot_id,
133
212
  ),
134
213
  )
135
214
  self._source.selected.js_on_change("indices", callback)
@@ -154,13 +233,31 @@ class Bokeh(Widget):
154
233
  y_axis_visible: bool = False,
155
234
  grid_visible: bool = False,
156
235
  widget_id: Optional[str] = None,
236
+ show_legend: bool = False,
237
+ legend_location: Literal["left", "top", "right", "bottom"] = "right",
238
+ legend_click_policy: Literal["hide", "mute"] = "hide",
157
239
  **kwargs,
158
240
  ):
241
+ import bokeh # pylint: disable=import-error
242
+
243
+ # check Bokeh version compatibility (only 3.1.1 is supported)
244
+ if bokeh.__version__ != "3.1.1":
245
+ raise RuntimeError(f"Bokeh version {bokeh.__version__} is not supported. Use 3.1.1")
246
+
247
+ from bokeh.models import Legend # pylint: disable=import-error
159
248
  from bokeh.plotting import figure # pylint: disable=import-error
160
249
 
161
250
  self.widget_id = widget_id
162
251
  self._plots = plots
163
252
  self._plot = figure(width=width, height=height, tools=tools, toolbar_location="above")
253
+
254
+ self._legend_location = legend_location
255
+ self._legend_click_policy = legend_click_policy
256
+ if show_legend:
257
+ self._plot.add_layout(
258
+ Legend(click_policy=self._legend_click_policy),
259
+ self._legend_location,
260
+ )
164
261
  self._renderers = []
165
262
 
166
263
  self._plot.xaxis.visible = x_axis_visible
@@ -177,6 +274,7 @@ class Bokeh(Widget):
177
274
  def _html_response() -> None:
178
275
  return HTMLResponse(content=self.get_html())
179
276
 
277
+ # TODO: support for offline mode
180
278
  # JinjaWidgets().context.pop(self.widget_id, None) # remove the widget from index.html
181
279
 
182
280
  @property
@@ -263,10 +361,11 @@ class Bokeh(Widget):
263
361
  def value_changed(self, func: Callable) -> Callable:
264
362
  server = self._sly_app.get_server()
265
363
  self._changes_handled = True
364
+ debounced_handler = DebouncedEventHandler(debounce_time=0.2) # TODO: check if it's enough
266
365
 
267
366
  @server.post(self.route_path)
268
- def _click(selected_ids: SelectedIds) -> None:
269
- func(selected_ids.selected_ids)
367
+ async def _click(data: SelectedIds) -> None:
368
+ debounced_handler.handle_event(data, func)
270
369
 
271
370
  return _click
272
371
 
@@ -276,3 +375,28 @@ class Bokeh(Widget):
276
375
  <script type="text/javascript"> {self._script} </script>
277
376
  {self._div}
278
377
  </div>"""
378
+
379
+ def update_radii(self, new_radii: Union[List[Union[list, int, float]], int, float]) -> None:
380
+ if isinstance(new_radii, (int, float)):
381
+ new_radii = [new_radii] * len(self._plots)
382
+ elif len(new_radii) != len(self._plots):
383
+ logger.warning(
384
+ f"{len(new_radii)} != {len(self._plots)}: new_radii will be broadcasted to all plots"
385
+ )
386
+ new_radii = [new_radii[0]] * len(self._plots)
387
+ for idx, radii in enumerate(new_radii):
388
+ self.update_radii_by_plot_idx(idx, radii)
389
+
390
+ def update_radii_by_plot_idx(self, plot_idx: int, new_radii: List[Union[int, float]]) -> None:
391
+ coords_length = len(self._plots[plot_idx]._x_coordinates)
392
+ if isinstance(new_radii, (int, float)):
393
+ new_radii = [new_radii] * coords_length
394
+ elif len(new_radii) != coords_length:
395
+ logger.warning(
396
+ f"{len(new_radii)} != {coords_length}: new_radii will be broadcasted to all plots"
397
+ )
398
+ new_radii = [new_radii[0]] * coords_length
399
+
400
+ self._plots[plot_idx]._radii = new_radii
401
+ self._plots[plot_idx]._source.data["radius"] = new_radii
402
+ self._update_html()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: supervisely
3
- Version: 6.73.264
3
+ Version: 6.73.266
4
4
  Summary: Supervisely Python SDK.
5
5
  Home-page: https://github.com/supervisely/supervisely
6
6
  Author: Supervisely
@@ -24,11 +24,11 @@ supervisely/api/agent_api.py,sha256=ShWAIlXcWXcyI9fqVuP5GZVCigCMJmjnvdGUfLspD6Y,
24
24
  supervisely/api/annotation_api.py,sha256=kB9l0NhQEkunGDC9fWjNzf5DdhqRF1tv-RRnIbkV2k0,64941
25
25
  supervisely/api/api.py,sha256=0dgPx_eizoCEFzfT8YH9uh1kq-OJwjrV5fBGD7uZ7E4,65840
26
26
  supervisely/api/app_api.py,sha256=RsbVej8WxWVn9cNo5s3Fqd1symsCdsfOaKVBKEUapRY,71927
27
- supervisely/api/dataset_api.py,sha256=eovT6l62jgjlRyCZ6IvoudUBfDxv9Hjj3Ap8IuCLd7I,41290
27
+ supervisely/api/dataset_api.py,sha256=GH7prDRJKyJlTv_7_Y-RkTwJN7ED4EkXNqqmi3iIdI4,41352
28
28
  supervisely/api/file_api.py,sha256=7yWt8lRQ4UfLmnMZ9T18UXzu8jihrtHtcqi6GZJG-0w,83414
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=2cki-IzA5jnN3QqqdSIbIbHJhDWxFGYxXY94WqBOoio,176836
31
+ supervisely/api/image_api.py,sha256=lLt8z_OE7cXwb94_UKxWiSKxe28a4meMrVM7dhHIWZY,176956
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
@@ -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=9s9Vx8f-NI-2hr9xjuYapFMsZkFF_n65WAWeMXgqTWY,9563
132
+ supervisely/app/widgets/bokeh/bokeh.py,sha256=R55DhPESNjkbCtqeg-Pxh13QC8IUzKUpKA5DHRLV6Tc,14661
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
@@ -1062,9 +1062,9 @@ supervisely/worker_proto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZ
1062
1062
  supervisely/worker_proto/worker_api_pb2.py,sha256=VQfi5JRBHs2pFCK1snec3JECgGnua3Xjqw_-b3aFxuM,59142
1063
1063
  supervisely/worker_proto/worker_api_pb2_grpc.py,sha256=3BwQXOaP9qpdi0Dt9EKG--Lm8KGN0C5AgmUfRv77_Jk,28940
1064
1064
  supervisely_lib/__init__.py,sha256=7-3QnN8Zf0wj8NCr2oJmqoQWMKKPKTECvjH9pd2S5vY,159
1065
- supervisely-6.73.264.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
1066
- supervisely-6.73.264.dist-info/METADATA,sha256=E6LgmO5fqJR74uJ6_FaxDXbPmh9pDGtQa4eegsSlkNI,33573
1067
- supervisely-6.73.264.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
1068
- supervisely-6.73.264.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
1069
- supervisely-6.73.264.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
1070
- supervisely-6.73.264.dist-info/RECORD,,
1065
+ supervisely-6.73.266.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
1066
+ supervisely-6.73.266.dist-info/METADATA,sha256=oIV7OabXk96Urcydrkm2Zn5ak_AZUmtQ-_FrvG_9Zd8,33573
1067
+ supervisely-6.73.266.dist-info/WHEEL,sha256=P9jw-gEje8ByB7_hXoICnHtVCrEwMQh-630tKvQWehc,91
1068
+ supervisely-6.73.266.dist-info/entry_points.txt,sha256=U96-5Hxrp2ApRjnCoUiUhWMqijqh8zLR03sEhWtAcms,102
1069
+ supervisely-6.73.266.dist-info/top_level.txt,sha256=kcFVwb7SXtfqZifrZaSE3owHExX4gcNYe7Q2uoby084,28
1070
+ supervisely-6.73.266.dist-info/RECORD,,