supervisely 6.73.357__py3-none-any.whl → 6.73.359__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 (44) hide show
  1. supervisely/_utils.py +12 -0
  2. supervisely/api/annotation_api.py +3 -0
  3. supervisely/api/api.py +2 -2
  4. supervisely/api/app_api.py +27 -2
  5. supervisely/api/entity_annotation/tag_api.py +0 -1
  6. supervisely/api/nn/__init__.py +0 -0
  7. supervisely/api/nn/deploy_api.py +821 -0
  8. supervisely/api/nn/neural_network_api.py +248 -0
  9. supervisely/api/task_api.py +26 -467
  10. supervisely/app/fastapi/subapp.py +1 -0
  11. supervisely/nn/__init__.py +2 -1
  12. supervisely/nn/artifacts/artifacts.py +5 -5
  13. supervisely/nn/benchmark/object_detection/metric_provider.py +3 -0
  14. supervisely/nn/experiments.py +28 -5
  15. supervisely/nn/inference/cache.py +178 -114
  16. supervisely/nn/inference/gui/gui.py +18 -35
  17. supervisely/nn/inference/gui/serving_gui.py +3 -1
  18. supervisely/nn/inference/inference.py +1421 -1265
  19. supervisely/nn/inference/inference_request.py +412 -0
  20. supervisely/nn/inference/object_detection_3d/object_detection_3d.py +31 -24
  21. supervisely/nn/inference/session.py +2 -2
  22. supervisely/nn/inference/tracking/base_tracking.py +45 -79
  23. supervisely/nn/inference/tracking/bbox_tracking.py +220 -155
  24. supervisely/nn/inference/tracking/mask_tracking.py +274 -250
  25. supervisely/nn/inference/tracking/tracker_interface.py +23 -0
  26. supervisely/nn/inference/uploader.py +164 -0
  27. supervisely/nn/model/__init__.py +0 -0
  28. supervisely/nn/model/model_api.py +259 -0
  29. supervisely/nn/model/prediction.py +311 -0
  30. supervisely/nn/model/prediction_session.py +632 -0
  31. supervisely/nn/tracking/__init__.py +1 -0
  32. supervisely/nn/tracking/boxmot.py +114 -0
  33. supervisely/nn/tracking/tracking.py +24 -0
  34. supervisely/nn/training/train_app.py +61 -19
  35. supervisely/nn/utils.py +43 -3
  36. supervisely/task/progress.py +12 -2
  37. supervisely/video/video.py +107 -1
  38. {supervisely-6.73.357.dist-info → supervisely-6.73.359.dist-info}/METADATA +2 -1
  39. {supervisely-6.73.357.dist-info → supervisely-6.73.359.dist-info}/RECORD +43 -32
  40. supervisely/api/neural_network_api.py +0 -202
  41. {supervisely-6.73.357.dist-info → supervisely-6.73.359.dist-info}/LICENSE +0 -0
  42. {supervisely-6.73.357.dist-info → supervisely-6.73.359.dist-info}/WHEEL +0 -0
  43. {supervisely-6.73.357.dist-info → supervisely-6.73.359.dist-info}/entry_points.txt +0 -0
  44. {supervisely-6.73.357.dist-info → supervisely-6.73.359.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,311 @@
1
+ from __future__ import annotations
2
+
3
+ import atexit
4
+ import os
5
+ import tempfile
6
+ from os import PathLike
7
+ from pathlib import Path
8
+ from typing import Dict, List, Optional, Union
9
+
10
+ import numpy as np
11
+ import requests
12
+
13
+ from supervisely._utils import get_valid_kwargs, logger, rand_str
14
+ from supervisely.annotation.annotation import Annotation
15
+ from supervisely.annotation.label import Label
16
+ from supervisely.annotation.tag import Tag
17
+ from supervisely.annotation.tag_meta import TagValueType
18
+ from supervisely.api.api import Api
19
+ from supervisely.convert.image.sly.sly_image_helper import get_meta_from_annotation
20
+ from supervisely.geometry.bitmap import Bitmap
21
+ from supervisely.geometry.rectangle import Rectangle
22
+ from supervisely.imaging.image import read as read_image
23
+ from supervisely.imaging.image import read_bytes as read_image_bytes
24
+ from supervisely.imaging.image import write as write_image
25
+ from supervisely.io.fs import (
26
+ clean_dir,
27
+ dir_empty,
28
+ ensure_base_path,
29
+ get_file_ext,
30
+ mkdir,
31
+ )
32
+ from supervisely.project.project_meta import ProjectMeta
33
+ from supervisely.video.video import VideoFrameReader
34
+
35
+
36
+ class Prediction:
37
+ _temp_dir = os.path.join(tempfile.gettempdir(), "prediction_files")
38
+ __cleanup_registered = False
39
+
40
+ def __init__(
41
+ self,
42
+ annotation_json: Dict,
43
+ source: Union[str, int] = None,
44
+ model_meta: Optional[Union[ProjectMeta, Dict]] = None,
45
+ name: Optional[str] = None,
46
+ path: Optional[str] = None,
47
+ url: Optional[str] = None,
48
+ project_id: Optional[int] = None,
49
+ dataset_id: Optional[int] = None,
50
+ image_id: Optional[int] = None,
51
+ video_id: Optional[int] = None,
52
+ frame_index: Optional[int] = None,
53
+ api: Optional["Api"] = None,
54
+ **kwargs,
55
+ ):
56
+ self.source = source
57
+ if isinstance(annotation_json, Annotation):
58
+ annotation_json = annotation_json.to_json()
59
+ self.annotation_json = annotation_json
60
+ self.model_meta = model_meta
61
+ if isinstance(self.model_meta, dict):
62
+ self.model_meta = ProjectMeta.from_json(self.model_meta)
63
+
64
+ self.name = name
65
+ self.path = path
66
+ self.url = url
67
+ self.project_id = project_id
68
+ self.dataset_id = dataset_id
69
+ self.image_id = image_id
70
+ self.video_id = video_id
71
+ self.frame_index = frame_index
72
+ self.extra_data = {}
73
+ if kwargs:
74
+ self.extra_data.update(kwargs)
75
+ self.api = api
76
+
77
+ self._annotation = None
78
+ self._boxes = None
79
+ self._masks = None
80
+ self._classes = None
81
+ self._scores = None
82
+
83
+ if self.path is None and isinstance(self.source, (str, PathLike)):
84
+ self.path = str(self.source)
85
+
86
+ def _init_geometries(self):
87
+
88
+ def _get_confidence(label: Label):
89
+ for tag_name in ["confidence", "conf", "score"]:
90
+ conf_tag: Tag = label.tags.get(tag_name, None)
91
+ if conf_tag is not None and conf_tag.meta.value_type != str(
92
+ TagValueType.ANY_NUMBER
93
+ ):
94
+ conf_tag = None
95
+ if conf_tag is not None:
96
+ return conf_tag.value
97
+ return 1
98
+
99
+ self._boxes = []
100
+ self._masks = []
101
+ self._classes = []
102
+ self._scores = []
103
+ for label in self.annotation.labels:
104
+ self._classes.append(label.obj_class.name)
105
+ self._scores.append(_get_confidence(label))
106
+ if isinstance(label.geometry, Rectangle):
107
+ rect = label.geometry
108
+ else:
109
+ rect = label.geometry.to_bbox()
110
+ if isinstance(label.geometry, Bitmap):
111
+ self._masks.append(label.geometry.get_mask(self.annotation.img_size))
112
+
113
+ self._boxes.append(
114
+ np.array(
115
+ [
116
+ rect.top,
117
+ rect.left,
118
+ rect.bottom,
119
+ rect.right,
120
+ ]
121
+ )
122
+ )
123
+ self._boxes = np.array(self._boxes)
124
+ self._masks = np.array(self._masks)
125
+
126
+ @property
127
+ def boxes(self):
128
+ if self._boxes is None:
129
+ self._init_geometries()
130
+ return self._boxes
131
+
132
+ @property
133
+ def masks(self):
134
+ if self._masks is None:
135
+ self._init_geometries()
136
+ return self._masks
137
+
138
+ @property
139
+ def classes(self):
140
+ if self._classes is None:
141
+ self._init_geometries()
142
+ return self._classes
143
+
144
+ @property
145
+ def scores(self):
146
+ if self._scores is None:
147
+ self._init_geometries()
148
+ return self._scores
149
+
150
+ @property
151
+ def annotation(self) -> Annotation:
152
+ if self._annotation is None:
153
+ if self.model_meta is None:
154
+ raise ValueError("Model meta is not provided. Cannot create annotation.")
155
+ model_meta = get_meta_from_annotation(self.annotation_json, self.model_meta)
156
+ self._annotation = Annotation.from_json(self.annotation_json, model_meta)
157
+ return self._annotation
158
+
159
+ @annotation.setter
160
+ def annotation(self, annotation: Union[Annotation, Dict]):
161
+ if isinstance(annotation, Annotation):
162
+ self._annotation = annotation
163
+ self.annotation_json = annotation.to_json()
164
+ elif isinstance(annotation, dict):
165
+ self._annotation = None
166
+ self.annotation_json = annotation
167
+ else:
168
+ raise ValueError("Annotation must be either a dict or an Annotation object.")
169
+
170
+ @property
171
+ def class_idxs(self) -> np.ndarray:
172
+ if self.model_meta is None:
173
+ raise ValueError("Model meta is not provided. Cannot create class indexes.")
174
+ cls_name_to_idx = {
175
+ obj_class.name: i for i, obj_class in enumerate(self.model_meta.obj_classes)
176
+ }
177
+ return np.array([cls_name_to_idx[class_name] for class_name in self.classes])
178
+
179
+ @classmethod
180
+ def from_json(cls, json_data: Dict, **kwargs) -> "Prediction":
181
+ kwargs = {**json_data, **kwargs}
182
+ if "annotation_json" in kwargs:
183
+ annotation_json = kwargs.pop("annotation_json")
184
+ elif "annotation" in kwargs:
185
+ annotation_json = kwargs.pop("annotation")
186
+ else:
187
+ raise ValueError("Annotation JSON is required.")
188
+ kwargs = get_valid_kwargs(
189
+ kwargs,
190
+ Prediction.__init__,
191
+ exclude=["self", "annotation_json"],
192
+ )
193
+ return cls(annotation_json, **kwargs)
194
+
195
+ def to_json(self):
196
+ return {
197
+ "source": self.source,
198
+ "annotation": self.annotation_json,
199
+ "name": self.name,
200
+ "path": self.path,
201
+ "url": self.url,
202
+ "project_id": self.project_id,
203
+ "dataset_id": self.dataset_id,
204
+ "image_id": self.image_id,
205
+ "video_id": self.video_id,
206
+ "frame_index": self.frame_index,
207
+ **self.extra_data,
208
+ }
209
+
210
+ def _clear_temp_files(self):
211
+ if not dir_empty(self._temp_dir):
212
+ clean_dir(self._temp_dir)
213
+
214
+ def load_image(self) -> np.ndarray:
215
+ api = self.api
216
+ if self.frame_index is None:
217
+ if self.path is not None:
218
+ return read_image(self.path)
219
+ if self.url is not None:
220
+ ext = get_file_ext(self.url)
221
+ if ext == "":
222
+ ext = ".jpg"
223
+ r = requests.get(self.url, stream=True, timeout=60)
224
+ r.raise_for_status()
225
+ return read_image_bytes(r.content)
226
+ if self.image_id is not None:
227
+ try:
228
+ if api is None:
229
+ api = Api()
230
+ return api.image.download_np(self.image_id)
231
+ except Exception as e:
232
+ raise RuntimeError("Failed to load image by ID") from e
233
+ raise ValueError("Cannot load image. No path, URL, image ID, or frame_index provided.")
234
+ if self.video_id is not None:
235
+ if self.frame_index is None:
236
+ raise ValueError("Frame index is not provided for video.")
237
+ try:
238
+ if api is None:
239
+ api = Api()
240
+ return api.video.frame.download_np(self.video_id, self.frame_index)
241
+ except Exception as e:
242
+ raise RuntimeError("Failed to load frame by video ID") from e
243
+ if self.path is not None:
244
+ return next(VideoFrameReader(self.path, frame_indexes=[self.frame_index]))
245
+ if self.url is not None:
246
+ video_name = Path(self.url).name
247
+ video_path = Path(self._temp_dir) / video_name
248
+ mkdir(self._temp_dir)
249
+ if not video_path.exists():
250
+ with requests.get(self.url, stream=True, timeout=10 * 60) as r:
251
+ r.raise_for_status()
252
+ with open(video_path, "wb") as f:
253
+ for chunk in r.iter_content(chunk_size=8192):
254
+ f.write(chunk)
255
+ return next(VideoFrameReader(video_path, frame_indexes=[self.frame_index]))
256
+ raise ValueError("Cannot load frame. No path, URL or video ID provided.")
257
+
258
+ def visualize(
259
+ self,
260
+ save_path: Optional[str] = None,
261
+ save_dir: Optional[str] = None,
262
+ color: Optional[List[int]] = None,
263
+ thickness: Optional[int] = None,
264
+ opacity: Optional[float] = 0.5,
265
+ draw_tags: Optional[bool] = False,
266
+ fill_rectangles: Optional[bool] = True,
267
+ ) -> np.ndarray:
268
+ if save_dir is not None and save_path is not None:
269
+ raise ValueError("Only one of save_path or save_dir can be provided.")
270
+
271
+ mkdir(self._temp_dir)
272
+ if not Prediction.__cleanup_registered:
273
+ atexit.register(self._clear_temp_files)
274
+ Prediction.__cleanup_registered = True
275
+
276
+ img = self.load_image()
277
+ self.annotation.draw_pretty(
278
+ bitmap=img,
279
+ color=color,
280
+ thickness=thickness,
281
+ opacity=opacity,
282
+ draw_tags=draw_tags,
283
+ output_path=None,
284
+ fill_rectangles=fill_rectangles,
285
+ )
286
+ if save_dir is not None:
287
+ save_path = save_dir
288
+ if save_path is None:
289
+ return img
290
+ if Path(save_path).suffix == "":
291
+ # is a directory
292
+ if self.name is not None:
293
+ name = self.name
294
+ if self.frame_index is not None:
295
+ name = f"{name}_{self.frame_index}"
296
+ elif self.image_id is not None:
297
+ name = str(self.image_id)
298
+ elif self.video_id is not None:
299
+ name = str(self.video_id)
300
+ elif self.path is not None:
301
+ name = Path(self.path).name
302
+ if self.frame_index is not None:
303
+ name = f"{name}_{self.frame_index}"
304
+ else:
305
+ name = f"{rand_str(6)}"
306
+ name = f"vis_{name}.png"
307
+ save_path = os.path.join(save_path, name)
308
+ ensure_base_path(save_path)
309
+ write_image(save_path, img)
310
+ logger.info("Visualization for prediction saved to %s", save_path)
311
+ return img