supervisely 6.73.419__py3-none-any.whl → 6.73.421__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/api/api.py +10 -5
- supervisely/api/app_api.py +71 -4
- supervisely/api/module_api.py +4 -0
- supervisely/api/nn/deploy_api.py +15 -9
- supervisely/api/nn/ecosystem_models_api.py +201 -0
- supervisely/api/nn/neural_network_api.py +12 -3
- supervisely/api/project_api.py +35 -6
- supervisely/api/task_api.py +5 -1
- supervisely/app/widgets/__init__.py +8 -1
- supervisely/app/widgets/agent_selector/template.html +1 -0
- supervisely/app/widgets/deploy_model/__init__.py +0 -0
- supervisely/app/widgets/deploy_model/deploy_model.py +729 -0
- supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
- supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
- supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
- supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
- supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +190 -0
- supervisely/app/widgets/experiment_selector/experiment_selector.py +447 -264
- supervisely/app/widgets/fast_table/fast_table.py +402 -74
- supervisely/app/widgets/fast_table/script.js +364 -96
- supervisely/app/widgets/fast_table/style.css +24 -0
- supervisely/app/widgets/fast_table/template.html +43 -3
- supervisely/app/widgets/radio_table/radio_table.py +10 -2
- supervisely/app/widgets/select/select.py +6 -4
- supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +18 -0
- supervisely/app/widgets/tabs/tabs.py +22 -6
- supervisely/app/widgets/tabs/template.html +5 -1
- supervisely/nn/artifacts/__init__.py +1 -1
- supervisely/nn/artifacts/artifacts.py +10 -2
- supervisely/nn/artifacts/detectron2.py +1 -0
- supervisely/nn/artifacts/hrda.py +1 -0
- supervisely/nn/artifacts/mmclassification.py +20 -0
- supervisely/nn/artifacts/mmdetection.py +5 -3
- supervisely/nn/artifacts/mmsegmentation.py +1 -0
- supervisely/nn/artifacts/ritm.py +1 -0
- supervisely/nn/artifacts/rtdetr.py +1 -0
- supervisely/nn/artifacts/unet.py +1 -0
- supervisely/nn/artifacts/utils.py +3 -0
- supervisely/nn/artifacts/yolov5.py +2 -0
- supervisely/nn/artifacts/yolov8.py +1 -0
- supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
- supervisely/nn/experiments.py +9 -0
- supervisely/nn/inference/gui/serving_gui_template.py +39 -13
- supervisely/nn/inference/inference.py +160 -94
- supervisely/nn/inference/predict_app/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
- supervisely/nn/inference/predict_app/gui/classes_selector.py +91 -0
- supervisely/nn/inference/predict_app/gui/gui.py +710 -0
- supervisely/nn/inference/predict_app/gui/input_selector.py +165 -0
- supervisely/nn/inference/predict_app/gui/model_selector.py +79 -0
- supervisely/nn/inference/predict_app/gui/output_selector.py +139 -0
- supervisely/nn/inference/predict_app/gui/preview.py +93 -0
- supervisely/nn/inference/predict_app/gui/settings_selector.py +184 -0
- supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
- supervisely/nn/inference/predict_app/gui/utils.py +282 -0
- supervisely/nn/inference/predict_app/predict_app.py +184 -0
- supervisely/nn/inference/uploader.py +9 -5
- supervisely/nn/model/prediction.py +2 -0
- supervisely/nn/model/prediction_session.py +20 -3
- supervisely/nn/training/gui/gui.py +131 -44
- supervisely/nn/training/gui/model_selector.py +8 -6
- supervisely/nn/training/gui/train_val_splits_selector.py +122 -70
- supervisely/nn/training/gui/training_artifacts.py +0 -5
- supervisely/nn/training/train_app.py +161 -44
- supervisely/project/project.py +211 -73
- supervisely/template/experiment/experiment.html.jinja +74 -17
- supervisely/template/experiment/experiment_generator.py +258 -112
- supervisely/template/experiment/header.html.jinja +31 -13
- supervisely/template/experiment/sly-style.css +7 -2
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/METADATA +3 -1
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/RECORD +75 -57
- supervisely/app/widgets/experiment_selector/style.css +0 -27
- supervisely/app/widgets/experiment_selector/template.html +0 -61
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/LICENSE +0 -0
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/WHEEL +0 -0
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/entry_points.txt +0 -0
- {supervisely-6.73.419.dist-info → supervisely-6.73.421.dist-info}/top_level.txt +0 -0
supervisely/api/api.py
CHANGED
|
@@ -723,6 +723,7 @@ class Api:
|
|
|
723
723
|
retries: Optional[int] = None,
|
|
724
724
|
stream: Optional[bool] = False,
|
|
725
725
|
use_public_api: Optional[bool] = True,
|
|
726
|
+
data: Optional[Dict] = None,
|
|
726
727
|
) -> requests.Response:
|
|
727
728
|
"""
|
|
728
729
|
Performs GET request to server with given parameters.
|
|
@@ -730,13 +731,15 @@ class Api:
|
|
|
730
731
|
:param method:
|
|
731
732
|
:type method: str
|
|
732
733
|
:param params: Dictionary to send in the body of the :class:`Request`.
|
|
733
|
-
:type
|
|
734
|
+
:type params: dict
|
|
734
735
|
:param retries: The number of attempts to connect to the server.
|
|
735
|
-
:type
|
|
736
|
+
:type retries: int, optional
|
|
736
737
|
:param stream: Define, if you'd like to get the raw socket response from the server.
|
|
737
|
-
:type
|
|
738
|
+
:type stream: bool, optional
|
|
738
739
|
:param use_public_api:
|
|
739
|
-
:type
|
|
740
|
+
:type use_public_api: bool, optional
|
|
741
|
+
:param data: Dictionary to send in the body of the :class:`Request`.
|
|
742
|
+
:type data: dict, optional
|
|
740
743
|
:return: Response object
|
|
741
744
|
:rtype: :class:`Response<Response>`
|
|
742
745
|
"""
|
|
@@ -756,7 +759,9 @@ class Api:
|
|
|
756
759
|
json_body = params
|
|
757
760
|
if type(params) is dict:
|
|
758
761
|
json_body = {**params, **self.additional_fields}
|
|
759
|
-
response = requests.get(
|
|
762
|
+
response = requests.get(
|
|
763
|
+
url, params=json_body, data=data, headers=self.headers, stream=stream
|
|
764
|
+
)
|
|
760
765
|
|
|
761
766
|
if response.status_code != requests.codes.ok: # pylint: disable=no-member
|
|
762
767
|
Api._raise_for_status(response)
|
supervisely/api/app_api.py
CHANGED
|
@@ -1394,9 +1394,75 @@ class AppApi(TaskApi):
|
|
|
1394
1394
|
"""get_url"""
|
|
1395
1395
|
return f"/apps/sessions/{task_id}"
|
|
1396
1396
|
|
|
1397
|
-
def download_git_file(
|
|
1398
|
-
|
|
1399
|
-
|
|
1397
|
+
def download_git_file(
|
|
1398
|
+
self,
|
|
1399
|
+
module_id,
|
|
1400
|
+
save_path,
|
|
1401
|
+
app_id=None,
|
|
1402
|
+
version=None,
|
|
1403
|
+
file_path=None,
|
|
1404
|
+
file_key=None,
|
|
1405
|
+
log_progress=True,
|
|
1406
|
+
ext_logger=None,
|
|
1407
|
+
):
|
|
1408
|
+
"""
|
|
1409
|
+
Download a file from app repository. File should be added in the app config under `files` key.
|
|
1410
|
+
|
|
1411
|
+
:param module_id: ID of the module
|
|
1412
|
+
:type module_id: int
|
|
1413
|
+
:param save_path: Path to save the file
|
|
1414
|
+
:type save_path: str
|
|
1415
|
+
:param app_id: ID of the app
|
|
1416
|
+
:type app_id: int
|
|
1417
|
+
:param version: Version of the app
|
|
1418
|
+
:type version: str
|
|
1419
|
+
:param file_path: Path to the file in the app github repository
|
|
1420
|
+
:type file_path: str
|
|
1421
|
+
:param file_key: Key of the file in the app github repository
|
|
1422
|
+
:type file_key: str
|
|
1423
|
+
:param log_progress: If True, will log the progress of the download
|
|
1424
|
+
:type log_progress: bool
|
|
1425
|
+
:param ext_logger: Logger to use for logging
|
|
1426
|
+
:type ext_logger: Logger
|
|
1427
|
+
:return: None
|
|
1428
|
+
:rtype: None
|
|
1429
|
+
"""
|
|
1430
|
+
if file_path is None and file_key is None:
|
|
1431
|
+
raise ValueError("Either file_path or file_key must be provided")
|
|
1432
|
+
payload = {
|
|
1433
|
+
ApiField.MODULE_ID: module_id,
|
|
1434
|
+
}
|
|
1435
|
+
if version is not None:
|
|
1436
|
+
payload[ApiField.VERSION] = version
|
|
1437
|
+
if app_id is not None:
|
|
1438
|
+
payload[ApiField.APP_ID] = app_id
|
|
1439
|
+
if file_path is not None:
|
|
1440
|
+
payload[ApiField.FILE_PATH] = file_path
|
|
1441
|
+
if file_key is not None:
|
|
1442
|
+
payload[ApiField.FILE_KEY] = file_key
|
|
1443
|
+
|
|
1444
|
+
response = self._api.post("ecosystem.file.download", payload, stream=True)
|
|
1445
|
+
progress = None
|
|
1446
|
+
if log_progress:
|
|
1447
|
+
if ext_logger is None:
|
|
1448
|
+
ext_logger = logger
|
|
1449
|
+
|
|
1450
|
+
length = None
|
|
1451
|
+
# Content-Length
|
|
1452
|
+
if "Content-Length" in response.headers:
|
|
1453
|
+
length = int(response.headers["Content-Length"])
|
|
1454
|
+
progress = Progress("Downloading: ", length, ext_logger=ext_logger, is_size=True)
|
|
1455
|
+
|
|
1456
|
+
mb1 = 1024 * 1024
|
|
1457
|
+
ensure_base_path(save_path)
|
|
1458
|
+
with open(save_path, "wb") as fd:
|
|
1459
|
+
log_size = 0
|
|
1460
|
+
for chunk in response.iter_content(chunk_size=mb1):
|
|
1461
|
+
fd.write(chunk)
|
|
1462
|
+
log_size += len(chunk)
|
|
1463
|
+
if log_progress and log_size > mb1 and progress is not None:
|
|
1464
|
+
progress.iters_done_report(log_size)
|
|
1465
|
+
log_size = 0
|
|
1400
1466
|
|
|
1401
1467
|
def download_git_archive(
|
|
1402
1468
|
self,
|
|
@@ -1418,6 +1484,7 @@ class AppApi(TaskApi):
|
|
|
1418
1484
|
payload[ApiField.APP_ID] = app_id
|
|
1419
1485
|
|
|
1420
1486
|
response = self._api.post("ecosystem.file.download", payload, stream=True)
|
|
1487
|
+
progress = None
|
|
1421
1488
|
if log_progress:
|
|
1422
1489
|
if ext_logger is None:
|
|
1423
1490
|
ext_logger = logger
|
|
@@ -1435,7 +1502,7 @@ class AppApi(TaskApi):
|
|
|
1435
1502
|
for chunk in response.iter_content(chunk_size=mb1):
|
|
1436
1503
|
fd.write(chunk)
|
|
1437
1504
|
log_size += len(chunk)
|
|
1438
|
-
if log_progress and log_size > mb1:
|
|
1505
|
+
if log_progress and log_size > mb1 and progress is not None:
|
|
1439
1506
|
progress.iters_done_report(log_size)
|
|
1440
1507
|
log_size = 0
|
|
1441
1508
|
|
supervisely/api/module_api.py
CHANGED
|
@@ -691,6 +691,10 @@ class ApiField:
|
|
|
691
691
|
""""""
|
|
692
692
|
UPDATE_STRATEGY = "updateStrategy"
|
|
693
693
|
""""""
|
|
694
|
+
FILE_PATH = "filePath"
|
|
695
|
+
""""""
|
|
696
|
+
FILE_KEY = "fileKey"
|
|
697
|
+
""""""
|
|
694
698
|
LOCAL_ENTITIES_COUNT = "localEntitiesCount"
|
|
695
699
|
""""""
|
|
696
700
|
REMOTE_ENTITIES_COUNT = "remoteEntitiesCount"
|
supervisely/api/nn/deploy_api.py
CHANGED
|
@@ -157,16 +157,15 @@ class DeployApi:
|
|
|
157
157
|
"device": device,
|
|
158
158
|
"model_source": ModelSource.CUSTOM,
|
|
159
159
|
"model_files": {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
).as_posix(),
|
|
163
|
-
"config": Path(
|
|
164
|
-
experiment_info.artifacts_dir, experiment_info.model_files["config"]
|
|
165
|
-
).as_posix(),
|
|
160
|
+
key: Path(experiment_info.artifacts_dir, value).as_posix()
|
|
161
|
+
for key, value in experiment_info.model_files.items()
|
|
166
162
|
},
|
|
167
163
|
"model_info": experiment_info.to_json(),
|
|
168
164
|
"runtime": runtime,
|
|
169
165
|
}
|
|
166
|
+
deploy_params["model_files"]["checkpoint"] = Path(
|
|
167
|
+
experiment_info.artifacts_dir, "checkpoints", checkpoint_name
|
|
168
|
+
).as_posix()
|
|
170
169
|
self._load_model_from_api(session_id, deploy_params)
|
|
171
170
|
|
|
172
171
|
def _find_agent(self, team_id: int = None, public=True, gpu=True):
|
|
@@ -239,6 +238,7 @@ class DeployApi:
|
|
|
239
238
|
RTDETR,
|
|
240
239
|
Detectron2,
|
|
241
240
|
MMClassification,
|
|
241
|
+
MMPretrain,
|
|
242
242
|
MMDetection,
|
|
243
243
|
MMDetection3,
|
|
244
244
|
MMSegmentation,
|
|
@@ -262,6 +262,7 @@ class DeployApi:
|
|
|
262
262
|
RTDETR(team_id).framework_name: RTDETR(team_id).serve_slug,
|
|
263
263
|
Detectron2(team_id).framework_name: Detectron2(team_id).serve_slug,
|
|
264
264
|
MMClassification(team_id).framework_name: MMClassification(team_id).serve_slug,
|
|
265
|
+
MMPretrain(team_id).framework_name: MMPretrain(team_id).serve_slug,
|
|
265
266
|
MMDetection(team_id).framework_name: MMDetection(team_id).serve_slug,
|
|
266
267
|
MMDetection3(team_id).framework_name: MMDetection3(team_id).serve_slug,
|
|
267
268
|
MMSegmentation(team_id).framework_name: MMSegmentation(team_id).serve_slug,
|
|
@@ -547,7 +548,6 @@ class DeployApi:
|
|
|
547
548
|
_attempt_delay_sec = 1
|
|
548
549
|
_attempts = timeout // _attempt_delay_sec
|
|
549
550
|
|
|
550
|
-
# @TODO: Run app in team?
|
|
551
551
|
if workspace_id is None:
|
|
552
552
|
workspace_id = env.workspace_id()
|
|
553
553
|
kwargs = get_valid_kwargs(
|
|
@@ -768,10 +768,14 @@ class DeployApi:
|
|
|
768
768
|
for file_key, file_path in experiment_info.model_files.items():
|
|
769
769
|
full_file_path = os.path.join(experiment_info.artifacts_dir, file_path)
|
|
770
770
|
if not self._api.file.exists(team_id, full_file_path):
|
|
771
|
-
logger.debug(
|
|
771
|
+
logger.debug(
|
|
772
|
+
f"Model file not found: '{full_file_path}'. Trying to find it by checkpoint path."
|
|
773
|
+
)
|
|
772
774
|
full_file_path = os.path.join(artifacts_dir, file_path)
|
|
773
775
|
if not self._api.file.exists(team_id, full_file_path):
|
|
774
|
-
raise ValueError(
|
|
776
|
+
raise ValueError(
|
|
777
|
+
f"Model file not found: '{full_file_path}'. Make sure that the file exists in the artifacts directory."
|
|
778
|
+
)
|
|
775
779
|
deploy_params["model_files"][file_key] = full_file_path
|
|
776
780
|
logger.debug(f"Model file added: {full_file_path}")
|
|
777
781
|
return module_id, serve_app_name, deploy_params
|
|
@@ -845,6 +849,7 @@ class DeployApi:
|
|
|
845
849
|
RTDETR,
|
|
846
850
|
Detectron2,
|
|
847
851
|
MMClassification,
|
|
852
|
+
MMPretrain,
|
|
848
853
|
MMDetection,
|
|
849
854
|
MMDetection3,
|
|
850
855
|
MMSegmentation,
|
|
@@ -863,6 +868,7 @@ class DeployApi:
|
|
|
863
868
|
frameworks = {
|
|
864
869
|
"/detectron2": Detectron2,
|
|
865
870
|
"/mmclassification": MMClassification,
|
|
871
|
+
"/mmclassification-v2": MMPretrain,
|
|
866
872
|
"/mmdetection": MMDetection,
|
|
867
873
|
"/mmdetection-3": MMDetection3,
|
|
868
874
|
"/mmsegmentation": MMSegmentation,
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
from typing import List, Literal
|
|
2
|
+
|
|
3
|
+
from supervisely.api.api import Api
|
|
4
|
+
from supervisely.api.module_api import ApiField, ModuleApi
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class ModelApiField:
|
|
8
|
+
NAME = "name"
|
|
9
|
+
FRAMEWORK = "framework"
|
|
10
|
+
TASK_TYPE = "task"
|
|
11
|
+
MODALITY = "modality"
|
|
12
|
+
TRAIN_MODULE_ID = "trainModuleId"
|
|
13
|
+
SERVE_MODULE_ID = "serveModuleId"
|
|
14
|
+
ARCHITECTURE = "architecture"
|
|
15
|
+
PRETRAINED = "pretrained"
|
|
16
|
+
NUM_CLASSES = "numClasses"
|
|
17
|
+
SIZE = "size"
|
|
18
|
+
PARAMS_M = "paramsM"
|
|
19
|
+
GFLOPS = "GFLOPs"
|
|
20
|
+
TAGS = "tags"
|
|
21
|
+
RUNTIMES = "runtimes"
|
|
22
|
+
FILES = "files"
|
|
23
|
+
SPEED_TESTS = "speedTests"
|
|
24
|
+
EVALUATION = "evaluation"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class EcosystemModelsApi(ModuleApi):
|
|
28
|
+
|
|
29
|
+
def __init__(self, api: Api):
|
|
30
|
+
self._api = api
|
|
31
|
+
|
|
32
|
+
def _convert_json_info(self, json_info):
|
|
33
|
+
return json_info
|
|
34
|
+
|
|
35
|
+
def get_list_all_pages(
|
|
36
|
+
self,
|
|
37
|
+
method,
|
|
38
|
+
data,
|
|
39
|
+
progress_cb=None,
|
|
40
|
+
convert_json_info_cb=None,
|
|
41
|
+
limit: int = None,
|
|
42
|
+
return_first_response: bool = False,
|
|
43
|
+
):
|
|
44
|
+
"""
|
|
45
|
+
Get list of all or limited quantity entities from the Supervisely server.
|
|
46
|
+
|
|
47
|
+
:param method: Request method name
|
|
48
|
+
:type method: str
|
|
49
|
+
:param data: Dictionary with request body info
|
|
50
|
+
:type data: dict
|
|
51
|
+
:param progress_cb: Function for tracking download progress.
|
|
52
|
+
:type progress_cb: Progress, optional
|
|
53
|
+
:param convert_json_info_cb: Function for convert json info
|
|
54
|
+
:type convert_json_info_cb: Callable, optional
|
|
55
|
+
:param limit: Number of entity to retrieve
|
|
56
|
+
:type limit: int, optional
|
|
57
|
+
:param return_first_response: Specify if return first response
|
|
58
|
+
:type return_first_response: bool, optional
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
if convert_json_info_cb is None:
|
|
62
|
+
convert_func = self._convert_json_info
|
|
63
|
+
else:
|
|
64
|
+
convert_func = convert_json_info_cb
|
|
65
|
+
|
|
66
|
+
if ApiField.SORT not in data:
|
|
67
|
+
data = self._add_sort_param(data)
|
|
68
|
+
first_response = self._api.get(method, {}, data=data).json()
|
|
69
|
+
total = first_response["total"]
|
|
70
|
+
per_page = first_response["perPage"]
|
|
71
|
+
pages_count = first_response["pagesCount"]
|
|
72
|
+
|
|
73
|
+
limit_exceeded = False
|
|
74
|
+
results = first_response["entities"]
|
|
75
|
+
if limit is not None and len(results) > limit:
|
|
76
|
+
limit_exceeded = True
|
|
77
|
+
|
|
78
|
+
if progress_cb is not None:
|
|
79
|
+
progress_cb(len(results))
|
|
80
|
+
if (pages_count == 1 and len(results) == total) or limit_exceeded is True:
|
|
81
|
+
pass
|
|
82
|
+
else:
|
|
83
|
+
for page_idx in range(2, pages_count + 1):
|
|
84
|
+
temp_resp = self._api.get(
|
|
85
|
+
method, {}, data={**data, "page": page_idx, "per_page": per_page}
|
|
86
|
+
)
|
|
87
|
+
temp_items = temp_resp.json()["entities"]
|
|
88
|
+
results.extend(temp_items)
|
|
89
|
+
if progress_cb is not None:
|
|
90
|
+
progress_cb(len(temp_items))
|
|
91
|
+
if limit is not None and len(results) > limit:
|
|
92
|
+
limit_exceeded = True
|
|
93
|
+
break
|
|
94
|
+
|
|
95
|
+
if len(results) != total and limit is None:
|
|
96
|
+
raise RuntimeError(
|
|
97
|
+
"Method {!r}: error during pagination, some items are missed".format(method)
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
if limit is not None:
|
|
101
|
+
results = results[:limit]
|
|
102
|
+
if return_first_response:
|
|
103
|
+
return [convert_func(item) for item in results], first_response
|
|
104
|
+
return [convert_func(item) for item in results]
|
|
105
|
+
|
|
106
|
+
def list_models(self, local=False):
|
|
107
|
+
method = "ecosystem.models.list"
|
|
108
|
+
data = {"localModels": local}
|
|
109
|
+
return self.get_list_all_pages(method, data=data)
|
|
110
|
+
|
|
111
|
+
def add(
|
|
112
|
+
self,
|
|
113
|
+
name: str,
|
|
114
|
+
framework: str,
|
|
115
|
+
task_type: str,
|
|
116
|
+
tain_module_id: int,
|
|
117
|
+
serve_module_id: int,
|
|
118
|
+
modality: Literal["images", "videos"] = "images",
|
|
119
|
+
architecture: str = None,
|
|
120
|
+
pretrained: bool = False,
|
|
121
|
+
num_classes: int = None,
|
|
122
|
+
size: int = None,
|
|
123
|
+
params_m: int = None,
|
|
124
|
+
gflops: float = None,
|
|
125
|
+
tags: List[str] = None,
|
|
126
|
+
runtimes: List[str] = None,
|
|
127
|
+
files: List[str] = None,
|
|
128
|
+
speed_tests: List = None,
|
|
129
|
+
evaluation: dict = None,
|
|
130
|
+
):
|
|
131
|
+
method = "ecosystem.models.add"
|
|
132
|
+
data = {
|
|
133
|
+
ModelApiField.NAME: name,
|
|
134
|
+
ModelApiField.FRAMEWORK: framework,
|
|
135
|
+
ModelApiField.TASK_TYPE: task_type,
|
|
136
|
+
ModelApiField.MODALITY: modality,
|
|
137
|
+
ModelApiField.TRAIN_MODULE_ID: tain_module_id,
|
|
138
|
+
ModelApiField.SERVE_MODULE_ID: serve_module_id,
|
|
139
|
+
}
|
|
140
|
+
optional_fields = {
|
|
141
|
+
ModelApiField.ARCHITECTURE: architecture,
|
|
142
|
+
ModelApiField.PRETRAINED: pretrained,
|
|
143
|
+
ModelApiField.NUM_CLASSES: num_classes,
|
|
144
|
+
ModelApiField.SIZE: size,
|
|
145
|
+
ModelApiField.PARAMS_M: params_m,
|
|
146
|
+
ModelApiField.GFLOPS: gflops,
|
|
147
|
+
ModelApiField.TAGS: tags,
|
|
148
|
+
ModelApiField.RUNTIMES: runtimes,
|
|
149
|
+
ModelApiField.FILES: files,
|
|
150
|
+
ModelApiField.SPEED_TESTS: speed_tests,
|
|
151
|
+
ModelApiField.EVALUATION: evaluation,
|
|
152
|
+
}
|
|
153
|
+
for key, value in optional_fields.items():
|
|
154
|
+
if value is not None:
|
|
155
|
+
data[key] = value
|
|
156
|
+
return self._api.post(method, data=data)
|
|
157
|
+
|
|
158
|
+
def update_model(
|
|
159
|
+
self,
|
|
160
|
+
model_id: int,
|
|
161
|
+
name: str = None,
|
|
162
|
+
framework: str = None,
|
|
163
|
+
task_type: str = None,
|
|
164
|
+
tain_module_id: int = None,
|
|
165
|
+
serve_module_id: int = None,
|
|
166
|
+
modality: Literal["images", "videos"] = None,
|
|
167
|
+
architecture: str = None,
|
|
168
|
+
pretrained: bool = False,
|
|
169
|
+
num_classes: int = None,
|
|
170
|
+
size: int = None,
|
|
171
|
+
params_m: int = None,
|
|
172
|
+
gflops: float = None,
|
|
173
|
+
tags: List[str] = None,
|
|
174
|
+
runtimes: List[str] = None,
|
|
175
|
+
files: List[str] = None,
|
|
176
|
+
speed_tests: List = None,
|
|
177
|
+
evaluation: dict = None,
|
|
178
|
+
):
|
|
179
|
+
data = {
|
|
180
|
+
ModelApiField.NAME: name,
|
|
181
|
+
ModelApiField.FRAMEWORK: framework,
|
|
182
|
+
ModelApiField.TASK_TYPE: task_type,
|
|
183
|
+
ModelApiField.MODALITY: modality,
|
|
184
|
+
ModelApiField.TRAIN_MODULE_ID: tain_module_id,
|
|
185
|
+
ModelApiField.SERVE_MODULE_ID: serve_module_id,
|
|
186
|
+
ModelApiField.ARCHITECTURE: architecture,
|
|
187
|
+
ModelApiField.PRETRAINED: pretrained,
|
|
188
|
+
ModelApiField.NUM_CLASSES: num_classes,
|
|
189
|
+
ModelApiField.SIZE: size,
|
|
190
|
+
ModelApiField.PARAMS_M: params_m,
|
|
191
|
+
ModelApiField.GFLOPS: gflops,
|
|
192
|
+
ModelApiField.TAGS: tags,
|
|
193
|
+
ModelApiField.RUNTIMES: runtimes,
|
|
194
|
+
ModelApiField.FILES: files,
|
|
195
|
+
ModelApiField.SPEED_TESTS: speed_tests,
|
|
196
|
+
ModelApiField.EVALUATION: evaluation,
|
|
197
|
+
}
|
|
198
|
+
data = {k: v for k, v in data.items() if v is not None}
|
|
199
|
+
method = "ecosystem.models.update"
|
|
200
|
+
data["id"] = model_id
|
|
201
|
+
return self._api.post(method, data=data)
|
|
@@ -23,9 +23,11 @@ class NeuralNetworkApi:
|
|
|
23
23
|
|
|
24
24
|
def __init__(self, api: "Api"):
|
|
25
25
|
from supervisely.api.nn.deploy_api import DeployApi
|
|
26
|
+
from supervisely.api.nn.ecosystem_models_api import EcosystemModelsApi
|
|
26
27
|
|
|
27
28
|
self._api = api
|
|
28
29
|
self._deploy_api = DeployApi(api)
|
|
30
|
+
self.ecosystem_models_api = EcosystemModelsApi(api)
|
|
29
31
|
|
|
30
32
|
def deploy(
|
|
31
33
|
self,
|
|
@@ -168,7 +170,7 @@ class NeuralNetworkApi:
|
|
|
168
170
|
workspaces = [workspace_id]
|
|
169
171
|
elif team_id is not None:
|
|
170
172
|
workspaces = self._api.workspace.get_list(team_id)
|
|
171
|
-
workspaces = [workspace
|
|
173
|
+
workspaces = [workspace.id for workspace in workspaces]
|
|
172
174
|
else:
|
|
173
175
|
workspace_id = env.workspace_id(raise_not_found=False)
|
|
174
176
|
if workspace_id is None:
|
|
@@ -178,7 +180,7 @@ class NeuralNetworkApi:
|
|
|
178
180
|
"Workspace ID and Team ID are not specified and cannot be found in the environment."
|
|
179
181
|
)
|
|
180
182
|
workspaces = self._api.workspace.get_list(team_id)
|
|
181
|
-
workspaces = [workspace
|
|
183
|
+
workspaces = [workspace.id for workspace in workspaces]
|
|
182
184
|
else:
|
|
183
185
|
workspaces = [workspace_id]
|
|
184
186
|
|
|
@@ -202,7 +204,14 @@ class NeuralNetworkApi:
|
|
|
202
204
|
# get deploy infos and filter results
|
|
203
205
|
result = []
|
|
204
206
|
for task in all_tasks:
|
|
205
|
-
|
|
207
|
+
try:
|
|
208
|
+
deploy_info = self._deploy_api.get_deploy_info(task["id"])
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.warning(
|
|
211
|
+
f"Failed to get deploy info for task {task['id']}: {e}",
|
|
212
|
+
exc_info=True,
|
|
213
|
+
)
|
|
214
|
+
continue
|
|
206
215
|
if model is not None:
|
|
207
216
|
checkpoint = deploy_info["checkpoint_name"]
|
|
208
217
|
deployed_model = deploy_info["model_name"]
|
supervisely/api/project_api.py
CHANGED
|
@@ -264,9 +264,10 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
264
264
|
|
|
265
265
|
def get_list(
|
|
266
266
|
self,
|
|
267
|
-
workspace_id: int,
|
|
267
|
+
workspace_id: Optional[int] = None,
|
|
268
268
|
filters: Optional[List[Dict[str, str]]] = None,
|
|
269
269
|
fields: List[str] = [],
|
|
270
|
+
team_id: Optional[int] = None,
|
|
270
271
|
) -> List[ProjectInfo]:
|
|
271
272
|
"""
|
|
272
273
|
List of Projects in the given Workspace.
|
|
@@ -275,12 +276,13 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
275
276
|
If you need version information, use :func:`get_info_by_id`.
|
|
276
277
|
|
|
277
278
|
:param workspace_id: Workspace ID in which the Projects are located.
|
|
278
|
-
:type workspace_id: int
|
|
279
|
+
:type workspace_id: int, optional
|
|
279
280
|
:param filters: List of params to sort output Projects.
|
|
280
281
|
:type filters: List[dict], optional
|
|
281
282
|
:param fields: The list of api fields which will be returned with the response. You must specify all fields you want to receive, not just additional ones.
|
|
282
283
|
:type fields: List[str]
|
|
283
|
-
|
|
284
|
+
:param team_id: Team ID in which the Projects are located.
|
|
285
|
+
:type team_id: int, optional
|
|
284
286
|
:return: List of all projects with information for the given Workspace. See :class:`info_sequence<info_sequence>`
|
|
285
287
|
:rtype: :class: `List[ProjectInfo]`
|
|
286
288
|
:Usage example:
|
|
@@ -357,6 +359,11 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
357
359
|
# ]
|
|
358
360
|
|
|
359
361
|
"""
|
|
362
|
+
if team_id is not None and workspace_id is not None:
|
|
363
|
+
raise ValueError(
|
|
364
|
+
"team_id and workspace_id cannot be used together. Please provide only one of them."
|
|
365
|
+
)
|
|
366
|
+
|
|
360
367
|
method = "projects.list"
|
|
361
368
|
|
|
362
369
|
debug_message = "While getting list of projects, the following fields are not available: "
|
|
@@ -367,11 +374,33 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
367
374
|
self.debug_messages_sent["get_list_versions"] = True
|
|
368
375
|
logger.debug(debug_message + "version. ")
|
|
369
376
|
|
|
377
|
+
default_fields = [
|
|
378
|
+
ApiField.ID,
|
|
379
|
+
ApiField.WORKSPACE_ID,
|
|
380
|
+
ApiField.TITLE,
|
|
381
|
+
ApiField.DESCRIPTION,
|
|
382
|
+
ApiField.SIZE,
|
|
383
|
+
ApiField.README,
|
|
384
|
+
ApiField.TYPE,
|
|
385
|
+
ApiField.CREATED_AT,
|
|
386
|
+
ApiField.UPDATED_AT,
|
|
387
|
+
ApiField.CUSTOM_DATA,
|
|
388
|
+
ApiField.GROUP_ID,
|
|
389
|
+
ApiField.CREATED_BY_ID[0][0],
|
|
390
|
+
]
|
|
391
|
+
|
|
392
|
+
if fields:
|
|
393
|
+
merged_fields = list(set(default_fields + fields))
|
|
394
|
+
fields = list(dict.fromkeys(merged_fields))
|
|
395
|
+
|
|
370
396
|
data = {
|
|
371
|
-
ApiField.WORKSPACE_ID: workspace_id,
|
|
372
397
|
ApiField.FILTER: filters or [],
|
|
373
398
|
ApiField.FIELDS: fields,
|
|
374
399
|
}
|
|
400
|
+
if workspace_id is not None:
|
|
401
|
+
data[ApiField.WORKSPACE_ID] = workspace_id
|
|
402
|
+
if team_id is not None:
|
|
403
|
+
data[ApiField.GROUP_ID] = team_id
|
|
375
404
|
|
|
376
405
|
return self.get_list_all_pages(method, data)
|
|
377
406
|
|
|
@@ -2129,7 +2158,7 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2129
2158
|
# reference_image_url = None,
|
|
2130
2159
|
# custom_data = None,
|
|
2131
2160
|
# backup_archive = None,
|
|
2132
|
-
#
|
|
2161
|
+
# team_id = 1,
|
|
2133
2162
|
# import_settings = {},
|
|
2134
2163
|
# ),
|
|
2135
2164
|
# ProjectInfo(id = 23,
|
|
@@ -2147,7 +2176,7 @@ class ProjectApi(CloneableModuleApi, UpdateableModule, RemoveableModuleApi):
|
|
|
2147
2176
|
# reference_image_url = None,
|
|
2148
2177
|
# custom_data = None,
|
|
2149
2178
|
# backup_archive = None),
|
|
2150
|
-
#
|
|
2179
|
+
# team_id = 1,
|
|
2151
2180
|
# import_settings = {},
|
|
2152
2181
|
# )
|
|
2153
2182
|
# ]
|
supervisely/api/task_api.py
CHANGED
|
@@ -1007,11 +1007,12 @@ class TaskApi(ModuleApiBase, ModuleWithStatus):
|
|
|
1007
1007
|
Example of experiment_info:
|
|
1008
1008
|
|
|
1009
1009
|
experiment_info = {
|
|
1010
|
-
'experiment_name': '
|
|
1010
|
+
'experiment_name': '247 Lemons RT-DETRv2-M',
|
|
1011
1011
|
'framework_name': 'RT-DETRv2',
|
|
1012
1012
|
'model_name': 'RT-DETRv2-M',
|
|
1013
1013
|
'task_type': 'object detection',
|
|
1014
1014
|
'project_id': 76,
|
|
1015
|
+
'project_version': {'id': 222, 'version': 4},
|
|
1015
1016
|
'task_id': 247,
|
|
1016
1017
|
'model_files': {'config': 'model_config.yml'},
|
|
1017
1018
|
'checkpoints': ['checkpoints/best.pth', 'checkpoints/checkpoint0025.pth', 'checkpoints/checkpoint0050.pth', 'checkpoints/last.pth'],
|
|
@@ -1022,10 +1023,13 @@ class TaskApi(ModuleApiBase, ModuleWithStatus):
|
|
|
1022
1023
|
'train_val_split': 'train_val_split.json',
|
|
1023
1024
|
'train_size': 4,
|
|
1024
1025
|
'val_size': 2,
|
|
1026
|
+
'train_collection_id': 530,
|
|
1027
|
+
'val_collection_id': 531,
|
|
1025
1028
|
'hyperparameters': 'hyperparameters.yaml',
|
|
1026
1029
|
'hyperparameters_id': 45234,
|
|
1027
1030
|
'artifacts_dir': '/experiments/76_Lemons/247_RT-DETRv2/',
|
|
1028
1031
|
'datetime': '2025-01-22 18:13:43',
|
|
1032
|
+
'experiment_report_id': 87654,
|
|
1029
1033
|
'evaluation_report_id': 12961,
|
|
1030
1034
|
'evaluation_report_link': 'https://app.supervisely.com/model-benchmark?id=12961',
|
|
1031
1035
|
'evaluation_metrics': {
|
|
@@ -151,4 +151,11 @@ from supervisely.app.widgets.experiment_selector.experiment_selector import Expe
|
|
|
151
151
|
from supervisely.app.widgets.bokeh.bokeh import Bokeh
|
|
152
152
|
from supervisely.app.widgets.run_app_button.run_app_button import RunAppButton
|
|
153
153
|
from supervisely.app.widgets.select_collection.select_collection import SelectCollection
|
|
154
|
-
from supervisely.app.widgets.sampling.sampling import Sampling
|
|
154
|
+
from supervisely.app.widgets.sampling.sampling import Sampling
|
|
155
|
+
from supervisely.app.widgets.deploy_model.deploy_model import DeployModel
|
|
156
|
+
from supervisely.app.widgets.dropdown_checkbox_selector.dropdown_checkbox_selector import (
|
|
157
|
+
DropdownCheckboxSelector,
|
|
158
|
+
)
|
|
159
|
+
from supervisely.app.widgets.ecosystem_model_selector.ecosystem_model_selector import (
|
|
160
|
+
EcosystemModelSelector,
|
|
161
|
+
)
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
:group-id="data.{{{ widget.widget_id }}}.teamId"
|
|
6
6
|
:options="state.{{{ widget.widget_id }}}.options"
|
|
7
7
|
:is-community="data.{{{ widget.widget_id }}}.isCommunity"
|
|
8
|
+
:disabled="data.{{{ widget.widget_id }}}.disabled"
|
|
8
9
|
{% if widget._changes_handled == true %}
|
|
9
10
|
@input="state.{{{ widget.widget_id }}}.agentId = $event; post('/{{{ widget.widget_id }}}/value_changed')"
|
|
10
11
|
{% else %}
|
|
File without changes
|