supervisely 6.73.438__py3-none-any.whl → 6.73.513__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 (203) hide show
  1. supervisely/__init__.py +137 -1
  2. supervisely/_utils.py +81 -0
  3. supervisely/annotation/annotation.py +8 -2
  4. supervisely/annotation/json_geometries_map.py +14 -11
  5. supervisely/annotation/label.py +80 -3
  6. supervisely/api/annotation_api.py +14 -11
  7. supervisely/api/api.py +59 -38
  8. supervisely/api/app_api.py +11 -2
  9. supervisely/api/dataset_api.py +74 -12
  10. supervisely/api/entities_collection_api.py +10 -0
  11. supervisely/api/entity_annotation/figure_api.py +52 -4
  12. supervisely/api/entity_annotation/object_api.py +3 -3
  13. supervisely/api/entity_annotation/tag_api.py +63 -12
  14. supervisely/api/guides_api.py +210 -0
  15. supervisely/api/image_api.py +72 -1
  16. supervisely/api/labeling_job_api.py +83 -1
  17. supervisely/api/labeling_queue_api.py +33 -7
  18. supervisely/api/module_api.py +9 -0
  19. supervisely/api/project_api.py +71 -26
  20. supervisely/api/storage_api.py +3 -1
  21. supervisely/api/task_api.py +13 -2
  22. supervisely/api/team_api.py +4 -3
  23. supervisely/api/video/video_annotation_api.py +119 -3
  24. supervisely/api/video/video_api.py +65 -14
  25. supervisely/api/video/video_figure_api.py +24 -11
  26. supervisely/app/__init__.py +1 -1
  27. supervisely/app/content.py +23 -7
  28. supervisely/app/development/development.py +18 -2
  29. supervisely/app/fastapi/__init__.py +1 -0
  30. supervisely/app/fastapi/custom_static_files.py +1 -1
  31. supervisely/app/fastapi/multi_user.py +105 -0
  32. supervisely/app/fastapi/subapp.py +88 -42
  33. supervisely/app/fastapi/websocket.py +77 -9
  34. supervisely/app/singleton.py +21 -0
  35. supervisely/app/v1/app_service.py +18 -2
  36. supervisely/app/v1/constants.py +7 -1
  37. supervisely/app/widgets/__init__.py +6 -0
  38. supervisely/app/widgets/activity_feed/__init__.py +0 -0
  39. supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
  40. supervisely/app/widgets/activity_feed/style.css +78 -0
  41. supervisely/app/widgets/activity_feed/template.html +22 -0
  42. supervisely/app/widgets/card/card.py +20 -0
  43. supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
  44. supervisely/app/widgets/classes_list_selector/template.html +60 -93
  45. supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
  46. supervisely/app/widgets/classes_table/classes_table.py +1 -0
  47. supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
  48. supervisely/app/widgets/dialog/dialog.py +12 -0
  49. supervisely/app/widgets/dialog/template.html +2 -1
  50. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
  51. supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
  52. supervisely/app/widgets/fast_table/fast_table.py +184 -60
  53. supervisely/app/widgets/fast_table/template.html +1 -1
  54. supervisely/app/widgets/heatmap/__init__.py +0 -0
  55. supervisely/app/widgets/heatmap/heatmap.py +564 -0
  56. supervisely/app/widgets/heatmap/script.js +533 -0
  57. supervisely/app/widgets/heatmap/style.css +233 -0
  58. supervisely/app/widgets/heatmap/template.html +21 -0
  59. supervisely/app/widgets/modal/__init__.py +0 -0
  60. supervisely/app/widgets/modal/modal.py +198 -0
  61. supervisely/app/widgets/modal/template.html +10 -0
  62. supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
  63. supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
  64. supervisely/app/widgets/radio_tabs/template.html +1 -0
  65. supervisely/app/widgets/select/select.py +6 -3
  66. supervisely/app/widgets/select_class/__init__.py +0 -0
  67. supervisely/app/widgets/select_class/select_class.py +363 -0
  68. supervisely/app/widgets/select_class/template.html +50 -0
  69. supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
  70. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
  71. supervisely/app/widgets/select_tag/__init__.py +0 -0
  72. supervisely/app/widgets/select_tag/select_tag.py +352 -0
  73. supervisely/app/widgets/select_tag/template.html +64 -0
  74. supervisely/app/widgets/select_team/select_team.py +37 -4
  75. supervisely/app/widgets/select_team/template.html +4 -5
  76. supervisely/app/widgets/select_user/__init__.py +0 -0
  77. supervisely/app/widgets/select_user/select_user.py +270 -0
  78. supervisely/app/widgets/select_user/template.html +13 -0
  79. supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
  80. supervisely/app/widgets/select_workspace/template.html +9 -12
  81. supervisely/app/widgets/table/table.py +68 -13
  82. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  83. supervisely/aug/aug.py +6 -2
  84. supervisely/convert/base_converter.py +1 -0
  85. supervisely/convert/converter.py +2 -2
  86. supervisely/convert/image/csv/csv_converter.py +24 -15
  87. supervisely/convert/image/image_converter.py +3 -1
  88. supervisely/convert/image/image_helper.py +48 -4
  89. supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
  90. supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
  91. supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
  92. supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
  93. supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
  94. supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
  95. supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
  96. supervisely/convert/pointcloud/las/las_converter.py +13 -1
  97. supervisely/convert/pointcloud/las/las_helper.py +110 -11
  98. supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
  99. supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
  100. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
  101. supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
  102. supervisely/convert/video/__init__.py +1 -0
  103. supervisely/convert/video/multi_view/__init__.py +0 -0
  104. supervisely/convert/video/multi_view/multi_view.py +543 -0
  105. supervisely/convert/video/sly/sly_video_converter.py +359 -3
  106. supervisely/convert/video/video_converter.py +24 -4
  107. supervisely/convert/volume/dicom/dicom_converter.py +13 -5
  108. supervisely/convert/volume/dicom/dicom_helper.py +30 -18
  109. supervisely/geometry/constants.py +1 -0
  110. supervisely/geometry/geometry.py +4 -0
  111. supervisely/geometry/helpers.py +5 -1
  112. supervisely/geometry/oriented_bbox.py +676 -0
  113. supervisely/geometry/polyline_3d.py +110 -0
  114. supervisely/geometry/rectangle.py +2 -1
  115. supervisely/io/env.py +76 -1
  116. supervisely/io/fs.py +21 -0
  117. supervisely/nn/benchmark/base_evaluator.py +104 -11
  118. supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
  119. supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
  120. supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
  121. supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
  122. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
  123. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
  124. supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
  125. supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
  126. supervisely/nn/inference/cache.py +43 -18
  127. supervisely/nn/inference/gui/serving_gui_template.py +5 -2
  128. supervisely/nn/inference/inference.py +916 -222
  129. supervisely/nn/inference/inference_request.py +55 -10
  130. supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
  131. supervisely/nn/inference/predict_app/gui/gui.py +676 -488
  132. supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
  133. supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
  134. supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
  135. supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
  136. supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
  137. supervisely/nn/inference/predict_app/gui/utils.py +236 -119
  138. supervisely/nn/inference/predict_app/predict_app.py +2 -2
  139. supervisely/nn/inference/session.py +43 -35
  140. supervisely/nn/inference/tracking/bbox_tracking.py +118 -35
  141. supervisely/nn/inference/tracking/point_tracking.py +5 -1
  142. supervisely/nn/inference/tracking/tracker_interface.py +10 -1
  143. supervisely/nn/inference/uploader.py +139 -12
  144. supervisely/nn/live_training/__init__.py +7 -0
  145. supervisely/nn/live_training/api_server.py +111 -0
  146. supervisely/nn/live_training/artifacts_utils.py +243 -0
  147. supervisely/nn/live_training/checkpoint_utils.py +229 -0
  148. supervisely/nn/live_training/dynamic_sampler.py +44 -0
  149. supervisely/nn/live_training/helpers.py +14 -0
  150. supervisely/nn/live_training/incremental_dataset.py +146 -0
  151. supervisely/nn/live_training/live_training.py +497 -0
  152. supervisely/nn/live_training/loss_plateau_detector.py +111 -0
  153. supervisely/nn/live_training/request_queue.py +52 -0
  154. supervisely/nn/model/model_api.py +9 -0
  155. supervisely/nn/model/prediction.py +2 -1
  156. supervisely/nn/model/prediction_session.py +26 -14
  157. supervisely/nn/prediction_dto.py +19 -1
  158. supervisely/nn/tracker/base_tracker.py +11 -1
  159. supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
  160. supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
  161. supervisely/nn/tracker/botsort_tracker.py +94 -65
  162. supervisely/nn/tracker/utils.py +4 -5
  163. supervisely/nn/tracker/visualize.py +93 -93
  164. supervisely/nn/training/gui/classes_selector.py +16 -1
  165. supervisely/nn/training/gui/train_val_splits_selector.py +52 -31
  166. supervisely/nn/training/train_app.py +46 -31
  167. supervisely/project/data_version.py +115 -51
  168. supervisely/project/download.py +1 -1
  169. supervisely/project/pointcloud_episode_project.py +37 -8
  170. supervisely/project/pointcloud_project.py +30 -2
  171. supervisely/project/project.py +14 -2
  172. supervisely/project/project_meta.py +27 -1
  173. supervisely/project/project_settings.py +32 -18
  174. supervisely/project/versioning/__init__.py +1 -0
  175. supervisely/project/versioning/common.py +20 -0
  176. supervisely/project/versioning/schema_fields.py +35 -0
  177. supervisely/project/versioning/video_schema.py +221 -0
  178. supervisely/project/versioning/volume_schema.py +87 -0
  179. supervisely/project/video_project.py +717 -15
  180. supervisely/project/volume_project.py +623 -5
  181. supervisely/template/experiment/experiment.html.jinja +4 -4
  182. supervisely/template/experiment/experiment_generator.py +14 -21
  183. supervisely/template/live_training/__init__.py +0 -0
  184. supervisely/template/live_training/header.html.jinja +96 -0
  185. supervisely/template/live_training/live_training.html.jinja +51 -0
  186. supervisely/template/live_training/live_training_generator.py +464 -0
  187. supervisely/template/live_training/sly-style.css +402 -0
  188. supervisely/template/live_training/template.html.jinja +18 -0
  189. supervisely/versions.json +28 -26
  190. supervisely/video/sampling.py +39 -20
  191. supervisely/video/video.py +41 -12
  192. supervisely/video_annotation/video_figure.py +38 -4
  193. supervisely/video_annotation/video_object.py +29 -4
  194. supervisely/volume/stl_converter.py +2 -0
  195. supervisely/worker_api/agent_rpc.py +24 -1
  196. supervisely/worker_api/rpc_servicer.py +31 -7
  197. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/METADATA +58 -40
  198. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/RECORD +203 -155
  199. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
  200. supervisely_lib/__init__.py +6 -1
  201. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
  202. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
  203. {supervisely-6.73.438.dist-info → supervisely-6.73.513.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,676 @@
1
+ # coding: utf-8
2
+
3
+ # docs
4
+ from __future__ import annotations
5
+
6
+ from copy import deepcopy
7
+ from math import ceil, floor
8
+ from typing import Dict, List, Optional, Tuple, Union
9
+
10
+ import cv2
11
+ import numpy as np
12
+
13
+ from supervisely.geometry.constants import (
14
+ ANGLE,
15
+ CLASS_ID,
16
+ CREATED_AT,
17
+ ID,
18
+ LABELER_LOGIN,
19
+ POINTS,
20
+ UPDATED_AT,
21
+ )
22
+ from supervisely.geometry.geometry import Geometry
23
+ from supervisely.geometry.point_location import PointLocation, points_to_row_col_list
24
+ from supervisely.geometry.polygon import Polygon
25
+ from supervisely.geometry.rectangle import Rectangle
26
+
27
+
28
+ class OrientedBBox(Rectangle):
29
+ """
30
+ OrientedBBox geometry for a single :class:`Label<supervisely.annotation.label.Label>`. :class:`OrientedBBox<OrientedBBox>` class object is immutable.
31
+
32
+ :param top: Minimal vertical value of OrientedBBox object.
33
+ :type top: int or float
34
+ :param left: Minimal horizontal value of OrientedBBox object.
35
+ :type left: int or float
36
+ :param bottom: Maximal vertical value of OrientedBBox object.
37
+ :type bottom: int or float
38
+ :param right: Maximal vertical value of OrientedBBox object.
39
+ :type right: int or float
40
+ :param angle: Angle of rotation in radians. Positive values mean clockwise rotation.
41
+ :type angle: int or float, optional
42
+ :param sly_id: OrientedBBox ID in Supervisely server.
43
+ :type sly_id: int, optional
44
+ :param class_id: ID of :class:`ObjClass<supervisely.annotation.obj_class.ObjClass>` to which OrientedBBox belongs.
45
+ :type class_id: int, optional
46
+ :param labeler_login: Login of the user who created OrientedBBox.
47
+ :type labeler_login: str, optional
48
+ :param updated_at: Date and Time when OrientedBBox was modified last. Date Format: Year:Month:Day:Hour:Minute:Seconds. Example: '2021-01-22T19:37:50.158Z'.
49
+ :type updated_at: str, optional
50
+ :param created_at: Date and Time when OrientedBBox was created. Date Format is the same as in "updated_at" parameter.
51
+ :type created_at: str, optional
52
+ :raises: :class:`ValueError`. OrientedBBox top argument must have less or equal value then bottom, left argument must have less or equal value then right
53
+
54
+ :Usage example:
55
+
56
+ .. code-block:: python
57
+
58
+ import supervisely as sly
59
+
60
+ import math
61
+
62
+ top = 100
63
+ left = 100
64
+ bottom = 700
65
+ right = 900
66
+ angle = math.pi / 12 # 15 degrees in radians
67
+ figure = sly.OrientedBBox(top, left, bottom, right, angle=angle)
68
+ """
69
+
70
+ @staticmethod
71
+ def geometry_name():
72
+ """ """
73
+ return "oriented_bbox"
74
+
75
+ def __init__(
76
+ self,
77
+ top: int,
78
+ left: int,
79
+ bottom: int,
80
+ right: int,
81
+ angle: Union[int, float] = 0,
82
+ sly_id: Optional[int] = None,
83
+ class_id: Optional[int] = None,
84
+ labeler_login: Optional[str] = None,
85
+ updated_at: Optional[str] = None,
86
+ created_at: Optional[str] = None,
87
+ ):
88
+
89
+ super().__init__(
90
+ top=top,
91
+ left=left,
92
+ bottom=bottom,
93
+ right=right,
94
+ sly_id=sly_id,
95
+ class_id=class_id,
96
+ labeler_login=labeler_login,
97
+ updated_at=updated_at,
98
+ created_at=created_at,
99
+ )
100
+ self._angle = angle
101
+ if self._angle is None:
102
+ self._angle = 0
103
+
104
+ @property
105
+ def angle(self) -> Union[int, float]:
106
+ """
107
+ Angle of rotation in radians. Positive values mean clockwise rotation.
108
+
109
+ :return: Angle of rotation in radians
110
+ :rtype: int or float
111
+ :Usage example:
112
+
113
+ .. code-block:: python
114
+
115
+ angle = oriented_bbox.angle
116
+ """
117
+ return self._angle
118
+
119
+ def to_json(self) -> Dict:
120
+ """
121
+ Convert the OrientedBBox to a json dict. Read more about `Supervisely format <https://docs.supervisely.com/data-organization/00_ann_format_navi>`_.
122
+
123
+ :return: Json format as a dict
124
+ :rtype: :class:`dict`
125
+ :Usage example:
126
+
127
+ .. code-block:: python
128
+
129
+ figure_json = figure.to_json()
130
+ print(figure_json)
131
+ # Output: {
132
+ # "points": [
133
+ # [100, 100],
134
+ # [900, 700]
135
+ # ],
136
+ # "angle": 0.2618 # radians (15 degrees)
137
+ # }
138
+ """
139
+ packed_obj = {
140
+ POINTS: points_to_row_col_list(self._points, flip_row_col_order=True),
141
+ ANGLE: self._angle,
142
+ }
143
+ self._add_creation_info(packed_obj)
144
+ return packed_obj
145
+
146
+ @classmethod
147
+ def from_json(cls, data: Dict) -> OrientedBBox:
148
+ """
149
+ Convert a json dict to OrientedBBox. Read more about `Supervisely format <https://docs.supervisely.com/data-organization/00_ann_format_navi>`_.
150
+
151
+ :param data: OrientedBBox in json format as a dict.
152
+ :type data: dict
153
+ :return: OrientedBBox object
154
+ :rtype: :class:`OrientedBBox<OrientedBBox>`
155
+ :Usage example:
156
+
157
+ .. code-block:: python
158
+
159
+ import supervisely as sly
160
+
161
+ import math
162
+
163
+ figure_json = {
164
+ "points": [
165
+ [100, 100],
166
+ [900, 700]
167
+ ],
168
+ "angle": math.pi / 12 # 15 degrees in radians
169
+ }
170
+ figure = sly.OrientedBBox.from_json(figure_json)
171
+ """
172
+ if POINTS not in data:
173
+ raise ValueError("Input data must contain {} field.".format(POINTS))
174
+ if ANGLE not in data:
175
+ raise ValueError("Input data must contain {} field.".format(ANGLE))
176
+ labeler_login = data.get(LABELER_LOGIN, None)
177
+ updated_at = data.get(UPDATED_AT, None)
178
+ created_at = data.get(CREATED_AT, None)
179
+ sly_id = data.get(ID, None)
180
+ class_id = data.get(CLASS_ID, None)
181
+ angle = data.get(ANGLE, 0)
182
+
183
+ exterior = data[POINTS]
184
+ if len(exterior) != 2:
185
+ raise ValueError(
186
+ '"exterior" field must contain exactly two points to create OrientedBBox object.'
187
+ )
188
+ [top, bottom] = sorted([exterior[0][1], exterior[1][1]])
189
+ [left, right] = sorted([exterior[0][0], exterior[1][0]])
190
+
191
+ return cls(
192
+ top=top,
193
+ left=left,
194
+ bottom=bottom,
195
+ right=right,
196
+ angle=angle,
197
+ sly_id=sly_id,
198
+ class_id=class_id,
199
+ labeler_login=labeler_login,
200
+ updated_at=updated_at,
201
+ created_at=created_at,
202
+ )
203
+
204
+ def to_bbox(self) -> Rectangle:
205
+ """
206
+ Convert the OrientedBBox to the axis-aligned :class:`Rectangle<supervisely.geometry.rectangle.Rectangle>` that fully contains the OrientedBBox.
207
+
208
+ :return: Axis-aligned Rectangle object
209
+ :rtype: :class:`Rectangle<supervisely.geometry.rectangle.Rectangle>`
210
+ :Usage example:
211
+ .. code-block:: python
212
+
213
+ axis_aligned_bbox = oriented_bbox.to_bbox()
214
+ """
215
+ two_pi = 2 * np.pi
216
+ if self._angle % two_pi == 0:
217
+ return Rectangle(
218
+ top=self.top,
219
+ left=self.left,
220
+ bottom=self.bottom,
221
+ right=self.right,
222
+ sly_id=self.sly_id,
223
+ class_id=self.class_id,
224
+ labeler_login=self.labeler_login,
225
+ updated_at=self.updated_at,
226
+ created_at=self.created_at,
227
+ )
228
+
229
+ cos_angle = abs(np.cos(self._angle))
230
+ sin_angle = abs(np.sin(self._angle))
231
+
232
+ new_w = self.width * cos_angle + self.height * sin_angle
233
+ new_h = self.width * sin_angle + self.height * cos_angle
234
+
235
+ new_left = self.center.col - new_w / 2.0
236
+ new_right = self.center.col + new_w / 2.0
237
+ new_top = self.center.row - new_h / 2.0
238
+ new_bottom = self.center.row + new_h / 2.0
239
+
240
+ return Rectangle(
241
+ top=new_top,
242
+ left=new_left,
243
+ bottom=new_bottom,
244
+ right=new_right,
245
+ sly_id=self.sly_id,
246
+ class_id=self.class_id,
247
+ labeler_login=self.labeler_login,
248
+ updated_at=self.updated_at,
249
+ created_at=self.created_at,
250
+ )
251
+
252
+ def _transform(self, transform_fn):
253
+ """ """
254
+ transformed_corners = [transform_fn(p) for p in self.corners]
255
+ rows, cols = zip(*points_to_row_col_list(transformed_corners))
256
+ return OrientedBBox(
257
+ top=round(min(rows)),
258
+ left=round(min(cols)),
259
+ bottom=round(max(rows)),
260
+ right=round(max(cols)),
261
+ angle=self._angle,
262
+ )
263
+
264
+ def contains_point_location(self, point: PointLocation) -> bool:
265
+ """
266
+ Check if the OrientedBBox contains the given point.
267
+
268
+ :param point: PointLocation object
269
+ :type point: :class:`PointLocation<supervisely.geometry.point_location.PointLocation>`
270
+ :return: True if the point is inside the OrientedBBox, False otherwise
271
+ :rtype: bool
272
+ :Usage example:
273
+
274
+ .. code-block:: python
275
+
276
+ point = sly.PointLocation(150, 200)
277
+ is_inside = oriented_bbox.contains_point_location(point)
278
+ """
279
+ # Rotate point in the opposite direction around the center of the oriented bbox
280
+ cos_angle = np.cos(-self._angle)
281
+ sin_angle = np.sin(-self._angle)
282
+
283
+ # Translate point to origin
284
+ translated_x = point.col - self.center.col
285
+ translated_y = point.row - self.center.row
286
+
287
+ # Rotate point
288
+ rotated_x = translated_x * cos_angle - translated_y * sin_angle
289
+ rotated_y = translated_x * sin_angle + translated_y * cos_angle
290
+
291
+ # Translate point back
292
+ final_x = rotated_x + self.center.col
293
+ final_y = rotated_y + self.center.row
294
+
295
+ # Check if the rotated point is within the axis-aligned bbox
296
+ return self.left <= final_x <= self.right and self.top <= final_y <= self.bottom
297
+
298
+ def contains_point(self, geometry: Geometry) -> bool:
299
+ """
300
+ Check if the OrientedBBox contains the given point.
301
+
302
+ :param geometry: PointLocation object
303
+ :type geometry: :class:`Geometry<supervisely.geometry.geometry.Geometry>`
304
+ :return: True if the point is inside the OrientedBBox, False otherwise
305
+ :rtype: bool
306
+ :Usage example:
307
+
308
+ .. code-block:: python
309
+
310
+ point = sly.PointLocation(150, 200)
311
+ is_inside = oriented_bbox.contains_point(point)
312
+ """
313
+ if isinstance(geometry, PointLocation):
314
+ return self.contains_point_location(geometry)
315
+ elif isinstance(geometry, Rectangle):
316
+ return self.contains_rectangle(geometry)
317
+ elif isinstance(geometry, OrientedBBox):
318
+ return self.contains_obb(geometry)
319
+ else:
320
+ raise TypeError(
321
+ "Unsupported geometry type for contains_point method. "
322
+ "Supported types are PointLocation, Rectangle, and OrientedBBox."
323
+ )
324
+
325
+ def contains_obb(self, obb: OrientedBBox) -> bool:
326
+ """
327
+ Check if the OrientedBBox contains the given OrientedBBox.
328
+
329
+ :param obb: OrientedBBox object
330
+ :type obb: :class:`OrientedBBox<supervisely.geometry.oriented_bbox.OrientedBBox>`
331
+ :return: True if the OrientedBBox is inside the OrientedBBox, False otherwise
332
+ :rtype: bool
333
+ :Usage example:
334
+
335
+ .. code-block:: python
336
+
337
+ obb2 = sly.OrientedBBox(150, 200, 400, 500, angle=10)
338
+ is_inside = obb1.contains_obb(obb2)
339
+ """
340
+ # Get the corners of the obb
341
+ corners = obb.calculate_rotated_corners()
342
+
343
+ # Check if all corners are inside the current obb
344
+ for corner in corners:
345
+ if not self.contains_point_location(corner):
346
+ return False
347
+ return True
348
+
349
+ @staticmethod
350
+ def _calculate_rotated_corners(obb: OrientedBBox) -> List[PointLocation]:
351
+ """
352
+ Get the corners of the OrientedBBox.
353
+
354
+ :return: List of corners as (x, y) tuples
355
+ :rtype: List[Tuple[float, float]]
356
+ :Usage example:
357
+
358
+ .. code-block:: python
359
+
360
+ corners = oriented_bbox.calculate_rotated_corners()
361
+ """
362
+ cos_angle = np.cos(obb._angle)
363
+ sin_angle = np.sin(obb._angle)
364
+
365
+ rotated_corners = []
366
+ for corner in obb.corners: # [Top-left, Top-right, Bottom-right, Bottom-left]
367
+ # First translate to origin (subtract center)
368
+ translated_x = corner.col - obb.center.col
369
+ translated_y = corner.row - obb.center.row
370
+
371
+ # Then rotate
372
+ rotated_x = translated_x * cos_angle - translated_y * sin_angle
373
+ rotated_y = translated_x * sin_angle + translated_y * cos_angle
374
+
375
+ # Then translate back (add center)
376
+ final_x = rotated_x + obb.center.col
377
+ final_y = rotated_y + obb.center.row
378
+
379
+ rotated_corners.append(PointLocation(row=final_y, col=final_x))
380
+
381
+ return rotated_corners
382
+
383
+ def calculate_rotated_corners(self) -> List[PointLocation]:
384
+ """
385
+ Get the corners of the OrientedBBox.
386
+
387
+ :return: List of corners as PointLocation objects
388
+ :rtype: List[:class:`PointLocation<supervisely.geometry.point_location.PointLocation>`]
389
+ :Usage example:
390
+
391
+ .. code-block:: python
392
+
393
+ corners = oriented_bbox.calculate_rotated_corners()
394
+ """
395
+ return self._calculate_rotated_corners(self)
396
+
397
+ def contains_rectangle(self, rectangle: Rectangle) -> bool:
398
+ """
399
+ Check if the OrientedBBox contains the given Rectangle.
400
+
401
+ :param rectangle: Rectangle object
402
+ :type rectangle: :class:`Rectangle<supervisely.geometry.rectangle.Rectangle>`
403
+ :return: True if the Rectangle is inside the OrientedBBox, False otherwise
404
+ :rtype: bool
405
+ :Usage example:
406
+
407
+ .. code-block:: python
408
+
409
+ rectangle = sly.Rectangle(150, 200, 400, 500)
410
+ is_inside = obb.contains_rectangle(rectangle)
411
+ """
412
+ # Get the corners of the rectangle
413
+ corners = rectangle.corners # [Top-left, Top-right, Bottom-right, Bottom-left]
414
+
415
+ # Check if all corners are inside the current obb
416
+ for corner in corners:
417
+ if not self.contains_point_location(corner):
418
+ return False
419
+ return True
420
+
421
+ @classmethod
422
+ def from_bbox(cls, bbox: Rectangle) -> OrientedBBox:
423
+ """
424
+ Create OrientedBBox from given Rectangle.
425
+
426
+ :param bbox: Rectangle object.
427
+ :type bbox: :class:`Rectangle<supervisely.geometry.rectangle.Rectangle>`
428
+ :return: OrientedBBox object
429
+ :rtype: :class:`OrientedBBox<OrientedBBox>`
430
+
431
+ :Usage Example:
432
+
433
+ .. code-block:: python
434
+
435
+ import supervisely as sly
436
+
437
+ axis_aligned_bbox = sly.Rectangle(100, 100, 700, 900)
438
+ figure_from_bbox = sly.OrientedBBox.from_bbox(axis_aligned_bbox)
439
+ """
440
+ return cls(
441
+ top=bbox.top,
442
+ left=bbox.left,
443
+ bottom=bbox.bottom,
444
+ right=bbox.right,
445
+ angle=0,
446
+ )
447
+
448
+ @classmethod
449
+ def from_array(cls, arr: np.ndarray, angle: Union[int, float] = 0) -> OrientedBBox:
450
+ """
451
+ Create OrientedBBox with given array shape.
452
+
453
+ :param arr: Numpy array.
454
+ :type arr: np.ndarray
455
+ :return: OrientedBBox object
456
+ :rtype: :class:`OrientedBBox<OrientedBBox>`
457
+
458
+ :Usage Example:
459
+
460
+ .. code-block:: python
461
+
462
+ import supervisely as sly
463
+
464
+ np_array = np.zeros((300, 400))
465
+ figure_from_np = sly.OrientedBBox.from_array(np_array, angle=15)
466
+ """
467
+ return cls(top=0, left=0, bottom=arr.shape[0] - 1, right=arr.shape[1] - 1, angle=angle)
468
+
469
+ def get_cropped_numpy_slice(self, data: np.ndarray) -> np.ndarray:
470
+ """
471
+ Slice of given numpy array with OrientedBBox align bbox.
472
+
473
+ :param data: Numpy array.
474
+ :type data: np.ndarray
475
+ :return: Sliced numpy array
476
+ :rtype: :class:`np.ndarray<np.ndarray>`
477
+
478
+ :Usage Example:
479
+
480
+ .. code-block:: python
481
+
482
+ np_slice = np.zeros((200, 500))
483
+ mask_slice = figure.get_cropped_numpy_slice(np_slice)
484
+ print(mask_slice.shape)
485
+ """
486
+ axis_aligned_bbox = self.to_bbox()
487
+ top = max(0, floor(axis_aligned_bbox.top))
488
+ left = max(0, floor(axis_aligned_bbox.left))
489
+ bottom = min(data.shape[0], ceil(axis_aligned_bbox.bottom))
490
+ right = min(data.shape[1], ceil(axis_aligned_bbox.right))
491
+ return data[top:bottom, left:right]
492
+
493
+ @classmethod
494
+ def allowed_transforms(cls):
495
+ """ """
496
+ from supervisely.geometry.alpha_mask import AlphaMask
497
+ from supervisely.geometry.any_geometry import AnyGeometry
498
+ from supervisely.geometry.bitmap import Bitmap
499
+
500
+ return [AlphaMask, AnyGeometry, Bitmap, Polygon, OrientedBBox]
501
+
502
+ def crop(self, clip: OrientedBBox | Rectangle) -> List[OrientedBBox]:
503
+ """Crop the OrientedBBox by another OrientedBBox using the Sutherland-Hodgman algorithm."""
504
+ subject_corners = self._calculate_rotated_corners(self)
505
+ if isinstance(clip, Rectangle):
506
+ if all([clip.contains_point_location(corner) for corner in subject_corners]):
507
+ return [self]
508
+ clip_corners = clip.corners
509
+ else:
510
+ if clip.contains_obb(self):
511
+ return [self]
512
+ clip_corners = self._calculate_rotated_corners(clip)
513
+
514
+ def inside(p: PointLocation, edge_start: PointLocation, edge_end: PointLocation) -> bool:
515
+ cx1, cy1 = edge_start.col, edge_start.row
516
+ cx2, cy2 = edge_end.col, edge_end.row
517
+ px, py = p.col, p.row
518
+ return (cx2 - cx1) * (py - cy1) > (cy2 - cy1) * (px - cx1)
519
+
520
+ def compute_intersection(
521
+ p1: PointLocation,
522
+ p2: PointLocation,
523
+ edge_start: PointLocation,
524
+ edge_end: PointLocation,
525
+ ) -> Optional[PointLocation]:
526
+ x1, y1 = p1.col, p1.row
527
+ x2, y2 = p2.col, p2.row
528
+ x3, y3 = edge_start.col, edge_start.row
529
+ x4, y4 = edge_end.col, edge_end.row
530
+ denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4)
531
+ if abs(denom) < 1e-10: return None
532
+ t = ((x1 - x3) * (y3 - y4) - (y1 - y3) * (x3 - x4)) / denom
533
+ return PointLocation(row=y1 + t * (y2 - y1), col=x1 + t * (x2 - x1))
534
+
535
+ output = subject_corners[:]
536
+ n = len(clip_corners)
537
+
538
+ for i in range(n):
539
+ edge_start = clip_corners[i]
540
+ edge_end = clip_corners[(i + 1) % n]
541
+ input_list = output
542
+ output = []
543
+
544
+ m = len(input_list)
545
+ for j in range(m):
546
+ curr = input_list[j]
547
+ prev = input_list[(j - 1) % m]
548
+
549
+ curr_inside = inside(curr, edge_start, edge_end)
550
+ prev_inside = inside(prev, edge_start, edge_end)
551
+
552
+ if curr_inside:
553
+ if not prev_inside:
554
+ inter = compute_intersection(prev, curr, edge_start, edge_end)
555
+ if inter: output.append(inter)
556
+ output.append(curr)
557
+ elif prev_inside:
558
+ inter = compute_intersection(prev, curr, edge_start, edge_end)
559
+ if inter: output.append(inter)
560
+
561
+ if len(output) < 3: return []
562
+
563
+ polygon = Polygon(output, [])
564
+ bbox = polygon.to_bbox()
565
+ return [OrientedBBox.from_bbox(bbox)]
566
+
567
+ def _draw_contour_impl(self, bitmap, color, thickness=1, config=None):
568
+ """ """
569
+ corners = self.calculate_rotated_corners()
570
+ pts = np.array([[int(corner.col), int(corner.row)] for corner in corners], np.int32)
571
+ pts = pts.reshape((-1, 1, 2))
572
+ cv2.polylines(bitmap, [pts], isClosed=True, color=color, thickness=thickness)
573
+
574
+ def _draw_impl(self, bitmap: np.ndarray, color, thickness=1, config=None):
575
+ """ """
576
+ corners = self.calculate_rotated_corners()
577
+ pts = np.array([[int(corner.col), int(corner.row)] for corner in corners], np.int32)
578
+ pts = pts.reshape((-1, 1, 2))
579
+ cv2.fillPoly(bitmap, [pts], color)
580
+
581
+ @classmethod
582
+ def _to_pixel_coordinate_system_json(cls, data: Dict, image_size: List[int]) -> Dict:
583
+ """
584
+ Convert OrientedBBox from subpixel precision to pixel precision by subtracting a subpixel offset from the coordinates.
585
+
586
+ Points order in json format: [[left, top], [right, bottom]]
587
+
588
+ In the labeling tool, labels are created with subpixel precision,
589
+ which means that the coordinates of the oriented bounding box corners (top, left and bottom, right) can have decimal values representing fractions of a pixel.
590
+ However, in Supervisely SDK, geometry coordinates are represented using pixel precision, where the coordinates are integers representing whole pixels.
591
+
592
+ Example:
593
+ Step 1. Input coordinates:
594
+ - top = 1.55, left = 1.74, bottom = 4.63, right = 3.76
595
+
596
+ Step 2. Round the coordinates (still remain in subpixel precision):
597
+ - top = 1, left = 2, bottom = 5, right = 4
598
+ - top will be rounded down to 2, left will be rounded down to 2, bottom will be rounded up to 6, right will be rounded down to 6
599
+
600
+ Draw coordinates in pixel coordinate system:
601
+ 0 1 2 3 4 5
602
+ 0 +---+---+---+---+---+
603
+ | | | | | |
604
+ 1 +---+---+---+---+---+
605
+ | | | x | x | |
606
+ 2 +---+---+---+---+---+
607
+ | | | x | x | |
608
+ 3 +---+---+---+---+---+
609
+ | | | x | x | |
610
+ 4 +---+---+---+---+---+
611
+ | | | x | x | |
612
+ 5 +---+---+---+---+---+
613
+ x x
614
+
615
+ Step 3. Convert to pixel coordinates by subtracting a subpixel offset:
616
+ - top = 1, left = 2, bottom = 4, right = 3
617
+
618
+ Draw coordinates in pixel coordinate system:
619
+ 0 1 2 3 4 5
620
+ 0 +---+---+---+---+---+
621
+ | | | | | |
622
+ 1 +---+---+---+---+---+
623
+ | | | x | x | |
624
+ 2 +---+---+---+---+---+
625
+ | | | x | x | |
626
+ 3 +---+---+---+---+---+
627
+ | | | x | x | |
628
+ 4 +---+---+---+---+---+
629
+ | | | x | x | |
630
+ 5 +---+---+---+---+---+
631
+
632
+ :param data: Json data with geometry config.
633
+ :type data: :class:`dict`
634
+ :param image_size: Image size in pixels (height, width).
635
+ :type image_size: List[int]
636
+ :return: Json data with coordinates converted to pixel coordinate system.
637
+ :rtype: :class:`dict`
638
+ """
639
+ data = deepcopy(data) # Avoid modifying the original data
640
+
641
+ points = data[POINTS]
642
+ [top, bottom] = sorted([points[0][1], points[1][1]])
643
+ [left, right] = sorted([points[0][0], points[1][0]])
644
+
645
+ top, left, bottom, right = cls._round_subpixel_coordinates(top, left, bottom, right)
646
+ right = max(left, right - 1)
647
+ bottom = max(top, bottom - 1)
648
+ data[POINTS] = [[left, top], [right, bottom]]
649
+ return data
650
+
651
+ @classmethod
652
+ def _to_subpixel_coordinate_system_json(cls, data: Dict) -> Dict:
653
+ """
654
+ Convert OrientedBBox from pixel precision to subpixel precision by adding a subpixel offset to the coordinates.
655
+
656
+ Points order in json format: [[left, top], [right, bottom]]
657
+
658
+ In the labeling tool, labels are created with subpixel precision,
659
+ which means that the coordinates of the oriented bounding box corners (top, left and bottom, right) can have decimal values representing fractions of a pixel.
660
+ However, in Supervisely SDK, geometry coordinates are represented using pixel precision, where the coordinates are integers representing whole pixels.
661
+
662
+ :param data: Json data with geometry config.
663
+ :type data: :class:`dict`
664
+ :return: Json data with coordinates converted to subpixel coordinate system.
665
+ :rtype: :class:`dict`
666
+ """
667
+ data = deepcopy(data) # Avoid modifying the original data
668
+
669
+ points = data[POINTS]
670
+ [top, bottom] = sorted([points[0][1], points[1][1]])
671
+ [left, right] = sorted([points[0][0], points[1][0]])
672
+
673
+ right = max(left, right + 1)
674
+ bottom = max(top, bottom + 1)
675
+ data[POINTS] = [[left, top], [right, bottom]]
676
+ return data