supervisely 6.73.452__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.
- supervisely/__init__.py +25 -1
- supervisely/annotation/annotation.py +8 -2
- supervisely/annotation/json_geometries_map.py +13 -12
- supervisely/api/annotation_api.py +6 -3
- supervisely/api/api.py +2 -0
- supervisely/api/app_api.py +10 -1
- supervisely/api/dataset_api.py +74 -12
- supervisely/api/entities_collection_api.py +10 -0
- supervisely/api/entity_annotation/figure_api.py +28 -0
- supervisely/api/entity_annotation/object_api.py +3 -3
- supervisely/api/entity_annotation/tag_api.py +63 -12
- supervisely/api/guides_api.py +210 -0
- supervisely/api/image_api.py +4 -0
- supervisely/api/labeling_job_api.py +83 -1
- supervisely/api/labeling_queue_api.py +33 -7
- supervisely/api/module_api.py +5 -0
- supervisely/api/project_api.py +71 -26
- supervisely/api/storage_api.py +3 -1
- supervisely/api/task_api.py +13 -2
- supervisely/api/team_api.py +4 -3
- supervisely/api/video/video_annotation_api.py +119 -3
- supervisely/api/video/video_api.py +65 -14
- supervisely/app/__init__.py +1 -1
- supervisely/app/content.py +23 -7
- supervisely/app/development/development.py +18 -2
- supervisely/app/fastapi/__init__.py +1 -0
- supervisely/app/fastapi/custom_static_files.py +1 -1
- supervisely/app/fastapi/multi_user.py +105 -0
- supervisely/app/fastapi/subapp.py +88 -42
- supervisely/app/fastapi/websocket.py +77 -9
- supervisely/app/singleton.py +21 -0
- supervisely/app/v1/app_service.py +18 -2
- supervisely/app/v1/constants.py +7 -1
- supervisely/app/widgets/__init__.py +6 -0
- supervisely/app/widgets/activity_feed/__init__.py +0 -0
- supervisely/app/widgets/activity_feed/activity_feed.py +239 -0
- supervisely/app/widgets/activity_feed/style.css +78 -0
- supervisely/app/widgets/activity_feed/template.html +22 -0
- supervisely/app/widgets/card/card.py +20 -0
- supervisely/app/widgets/classes_list_selector/classes_list_selector.py +121 -9
- supervisely/app/widgets/classes_list_selector/template.html +60 -93
- supervisely/app/widgets/classes_mapping/classes_mapping.py +13 -12
- supervisely/app/widgets/classes_table/classes_table.py +1 -0
- supervisely/app/widgets/deploy_model/deploy_model.py +56 -35
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +1 -1
- supervisely/app/widgets/experiment_selector/experiment_selector.py +8 -0
- supervisely/app/widgets/fast_table/fast_table.py +184 -60
- supervisely/app/widgets/fast_table/template.html +1 -1
- supervisely/app/widgets/heatmap/__init__.py +0 -0
- supervisely/app/widgets/heatmap/heatmap.py +564 -0
- supervisely/app/widgets/heatmap/script.js +533 -0
- supervisely/app/widgets/heatmap/style.css +233 -0
- supervisely/app/widgets/heatmap/template.html +21 -0
- supervisely/app/widgets/modal/__init__.py +0 -0
- supervisely/app/widgets/modal/modal.py +198 -0
- supervisely/app/widgets/modal/template.html +10 -0
- supervisely/app/widgets/object_class_view/object_class_view.py +3 -0
- supervisely/app/widgets/radio_tabs/radio_tabs.py +18 -2
- supervisely/app/widgets/radio_tabs/template.html +1 -0
- supervisely/app/widgets/select/select.py +6 -3
- supervisely/app/widgets/select_class/__init__.py +0 -0
- supervisely/app/widgets/select_class/select_class.py +363 -0
- supervisely/app/widgets/select_class/template.html +50 -0
- supervisely/app/widgets/select_cuda/select_cuda.py +22 -0
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +65 -7
- supervisely/app/widgets/select_tag/__init__.py +0 -0
- supervisely/app/widgets/select_tag/select_tag.py +352 -0
- supervisely/app/widgets/select_tag/template.html +64 -0
- supervisely/app/widgets/select_team/select_team.py +37 -4
- supervisely/app/widgets/select_team/template.html +4 -5
- supervisely/app/widgets/select_user/__init__.py +0 -0
- supervisely/app/widgets/select_user/select_user.py +270 -0
- supervisely/app/widgets/select_user/template.html +13 -0
- supervisely/app/widgets/select_workspace/select_workspace.py +59 -10
- supervisely/app/widgets/select_workspace/template.html +9 -12
- supervisely/app/widgets/table/table.py +68 -13
- supervisely/app/widgets/tree_select/tree_select.py +2 -0
- supervisely/aug/aug.py +6 -2
- supervisely/convert/base_converter.py +1 -0
- supervisely/convert/converter.py +2 -2
- supervisely/convert/image/image_converter.py +3 -1
- supervisely/convert/image/image_helper.py +48 -4
- supervisely/convert/image/label_studio/label_studio_converter.py +2 -0
- supervisely/convert/image/medical2d/medical2d_helper.py +2 -24
- supervisely/convert/image/multispectral/multispectral_converter.py +6 -0
- supervisely/convert/image/pascal_voc/pascal_voc_converter.py +8 -5
- supervisely/convert/image/pascal_voc/pascal_voc_helper.py +7 -0
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_converter.py +33 -3
- supervisely/convert/pointcloud/kitti_3d/kitti_3d_helper.py +12 -5
- supervisely/convert/pointcloud/las/las_converter.py +13 -1
- supervisely/convert/pointcloud/las/las_helper.py +110 -11
- supervisely/convert/pointcloud/nuscenes_conv/nuscenes_converter.py +27 -16
- supervisely/convert/pointcloud/pointcloud_converter.py +91 -3
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_converter.py +58 -22
- supervisely/convert/pointcloud_episodes/nuscenes_conv/nuscenes_helper.py +21 -47
- supervisely/convert/video/__init__.py +1 -0
- supervisely/convert/video/multi_view/__init__.py +0 -0
- supervisely/convert/video/multi_view/multi_view.py +543 -0
- supervisely/convert/video/sly/sly_video_converter.py +359 -3
- supervisely/convert/video/video_converter.py +22 -2
- supervisely/convert/volume/dicom/dicom_converter.py +13 -5
- supervisely/convert/volume/dicom/dicom_helper.py +30 -18
- supervisely/geometry/constants.py +1 -0
- supervisely/geometry/geometry.py +4 -0
- supervisely/geometry/helpers.py +5 -1
- supervisely/geometry/oriented_bbox.py +676 -0
- supervisely/geometry/rectangle.py +2 -1
- supervisely/io/env.py +76 -1
- supervisely/io/fs.py +21 -0
- supervisely/nn/benchmark/base_evaluator.py +104 -11
- supervisely/nn/benchmark/instance_segmentation/evaluator.py +1 -8
- supervisely/nn/benchmark/object_detection/evaluator.py +20 -4
- supervisely/nn/benchmark/object_detection/vis_metrics/pr_curve.py +10 -5
- supervisely/nn/benchmark/semantic_segmentation/evaluator.py +34 -16
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/confusion_matrix.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/frequently_confused.py +1 -1
- supervisely/nn/benchmark/semantic_segmentation/vis_metrics/overview.py +1 -1
- supervisely/nn/benchmark/visualization/evaluation_result.py +66 -4
- supervisely/nn/inference/cache.py +43 -18
- supervisely/nn/inference/gui/serving_gui_template.py +5 -2
- supervisely/nn/inference/inference.py +795 -199
- supervisely/nn/inference/inference_request.py +42 -9
- supervisely/nn/inference/predict_app/gui/classes_selector.py +83 -12
- supervisely/nn/inference/predict_app/gui/gui.py +676 -488
- supervisely/nn/inference/predict_app/gui/input_selector.py +205 -26
- supervisely/nn/inference/predict_app/gui/model_selector.py +2 -4
- supervisely/nn/inference/predict_app/gui/output_selector.py +46 -6
- supervisely/nn/inference/predict_app/gui/settings_selector.py +756 -59
- supervisely/nn/inference/predict_app/gui/tags_selector.py +1 -1
- supervisely/nn/inference/predict_app/gui/utils.py +236 -119
- supervisely/nn/inference/predict_app/predict_app.py +2 -2
- supervisely/nn/inference/session.py +43 -35
- supervisely/nn/inference/tracking/bbox_tracking.py +113 -34
- supervisely/nn/inference/tracking/tracker_interface.py +7 -2
- supervisely/nn/inference/uploader.py +139 -12
- supervisely/nn/live_training/__init__.py +7 -0
- supervisely/nn/live_training/api_server.py +111 -0
- supervisely/nn/live_training/artifacts_utils.py +243 -0
- supervisely/nn/live_training/checkpoint_utils.py +229 -0
- supervisely/nn/live_training/dynamic_sampler.py +44 -0
- supervisely/nn/live_training/helpers.py +14 -0
- supervisely/nn/live_training/incremental_dataset.py +146 -0
- supervisely/nn/live_training/live_training.py +497 -0
- supervisely/nn/live_training/loss_plateau_detector.py +111 -0
- supervisely/nn/live_training/request_queue.py +52 -0
- supervisely/nn/model/model_api.py +9 -0
- supervisely/nn/prediction_dto.py +12 -1
- supervisely/nn/tracker/base_tracker.py +11 -1
- supervisely/nn/tracker/botsort/botsort_config.yaml +0 -1
- supervisely/nn/tracker/botsort/tracker/mc_bot_sort.py +7 -4
- supervisely/nn/tracker/botsort_tracker.py +94 -65
- supervisely/nn/tracker/visualize.py +87 -90
- supervisely/nn/training/gui/classes_selector.py +16 -1
- supervisely/nn/training/train_app.py +28 -29
- supervisely/project/data_version.py +115 -51
- supervisely/project/download.py +1 -1
- supervisely/project/pointcloud_episode_project.py +37 -8
- supervisely/project/pointcloud_project.py +30 -2
- supervisely/project/project.py +14 -2
- supervisely/project/project_meta.py +27 -1
- supervisely/project/project_settings.py +32 -18
- supervisely/project/versioning/__init__.py +1 -0
- supervisely/project/versioning/common.py +20 -0
- supervisely/project/versioning/schema_fields.py +35 -0
- supervisely/project/versioning/video_schema.py +221 -0
- supervisely/project/versioning/volume_schema.py +87 -0
- supervisely/project/video_project.py +717 -15
- supervisely/project/volume_project.py +623 -5
- supervisely/template/experiment/experiment.html.jinja +4 -4
- supervisely/template/experiment/experiment_generator.py +14 -21
- supervisely/template/live_training/__init__.py +0 -0
- supervisely/template/live_training/header.html.jinja +96 -0
- supervisely/template/live_training/live_training.html.jinja +51 -0
- supervisely/template/live_training/live_training_generator.py +464 -0
- supervisely/template/live_training/sly-style.css +402 -0
- supervisely/template/live_training/template.html.jinja +18 -0
- supervisely/versions.json +28 -26
- supervisely/video/sampling.py +39 -20
- supervisely/video/video.py +40 -11
- supervisely/video_annotation/video_object.py +29 -4
- supervisely/volume/stl_converter.py +2 -0
- supervisely/worker_api/agent_rpc.py +24 -1
- supervisely/worker_api/rpc_servicer.py +31 -7
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/METADATA +56 -39
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/RECORD +189 -142
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/WHEEL +1 -1
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.452.dist-info → supervisely-6.73.513.dist-info/licenses}/LICENSE +0 -0
- {supervisely-6.73.452.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
|
|
@@ -737,8 +737,9 @@ class Rectangle(Geometry):
|
|
|
737
737
|
from supervisely.geometry.any_geometry import AnyGeometry
|
|
738
738
|
from supervisely.geometry.bitmap import Bitmap
|
|
739
739
|
from supervisely.geometry.polygon import Polygon
|
|
740
|
+
from supervisely.geometry.oriented_bbox import OrientedBBox
|
|
740
741
|
|
|
741
|
-
return [AlphaMask, AnyGeometry, Bitmap, Polygon]
|
|
742
|
+
return [AlphaMask, AnyGeometry, Bitmap, Polygon, Rectangle, OrientedBBox]
|
|
742
743
|
|
|
743
744
|
@classmethod
|
|
744
745
|
def _round_subpixel_coordinates(
|