supervisely 6.73.420__py3-none-any.whl → 6.73.422__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 (76) hide show
  1. supervisely/api/api.py +10 -5
  2. supervisely/api/app_api.py +71 -4
  3. supervisely/api/module_api.py +4 -0
  4. supervisely/api/nn/deploy_api.py +15 -9
  5. supervisely/api/nn/ecosystem_models_api.py +201 -0
  6. supervisely/api/nn/neural_network_api.py +12 -3
  7. supervisely/api/project_api.py +35 -6
  8. supervisely/api/task_api.py +5 -1
  9. supervisely/app/widgets/__init__.py +8 -1
  10. supervisely/app/widgets/agent_selector/template.html +1 -0
  11. supervisely/app/widgets/deploy_model/__init__.py +0 -0
  12. supervisely/app/widgets/deploy_model/deploy_model.py +729 -0
  13. supervisely/app/widgets/dropdown_checkbox_selector/__init__.py +0 -0
  14. supervisely/app/widgets/dropdown_checkbox_selector/dropdown_checkbox_selector.py +87 -0
  15. supervisely/app/widgets/dropdown_checkbox_selector/template.html +12 -0
  16. supervisely/app/widgets/ecosystem_model_selector/__init__.py +0 -0
  17. supervisely/app/widgets/ecosystem_model_selector/ecosystem_model_selector.py +190 -0
  18. supervisely/app/widgets/experiment_selector/experiment_selector.py +447 -264
  19. supervisely/app/widgets/fast_table/fast_table.py +402 -74
  20. supervisely/app/widgets/fast_table/script.js +364 -96
  21. supervisely/app/widgets/fast_table/style.css +24 -0
  22. supervisely/app/widgets/fast_table/template.html +43 -3
  23. supervisely/app/widgets/radio_table/radio_table.py +10 -2
  24. supervisely/app/widgets/select/select.py +6 -4
  25. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +18 -0
  26. supervisely/app/widgets/tabs/tabs.py +22 -6
  27. supervisely/app/widgets/tabs/template.html +5 -1
  28. supervisely/nn/artifacts/__init__.py +1 -1
  29. supervisely/nn/artifacts/artifacts.py +10 -2
  30. supervisely/nn/artifacts/detectron2.py +1 -0
  31. supervisely/nn/artifacts/hrda.py +1 -0
  32. supervisely/nn/artifacts/mmclassification.py +20 -0
  33. supervisely/nn/artifacts/mmdetection.py +5 -3
  34. supervisely/nn/artifacts/mmsegmentation.py +1 -0
  35. supervisely/nn/artifacts/ritm.py +1 -0
  36. supervisely/nn/artifacts/rtdetr.py +1 -0
  37. supervisely/nn/artifacts/unet.py +1 -0
  38. supervisely/nn/artifacts/utils.py +3 -0
  39. supervisely/nn/artifacts/yolov5.py +2 -0
  40. supervisely/nn/artifacts/yolov8.py +1 -0
  41. supervisely/nn/benchmark/semantic_segmentation/metric_provider.py +18 -18
  42. supervisely/nn/experiments.py +9 -0
  43. supervisely/nn/inference/gui/serving_gui_template.py +39 -13
  44. supervisely/nn/inference/inference.py +160 -94
  45. supervisely/nn/inference/predict_app/__init__.py +0 -0
  46. supervisely/nn/inference/predict_app/gui/__init__.py +0 -0
  47. supervisely/nn/inference/predict_app/gui/classes_selector.py +91 -0
  48. supervisely/nn/inference/predict_app/gui/gui.py +710 -0
  49. supervisely/nn/inference/predict_app/gui/input_selector.py +165 -0
  50. supervisely/nn/inference/predict_app/gui/model_selector.py +79 -0
  51. supervisely/nn/inference/predict_app/gui/output_selector.py +139 -0
  52. supervisely/nn/inference/predict_app/gui/preview.py +93 -0
  53. supervisely/nn/inference/predict_app/gui/settings_selector.py +184 -0
  54. supervisely/nn/inference/predict_app/gui/tags_selector.py +110 -0
  55. supervisely/nn/inference/predict_app/gui/utils.py +282 -0
  56. supervisely/nn/inference/predict_app/predict_app.py +184 -0
  57. supervisely/nn/inference/uploader.py +9 -5
  58. supervisely/nn/model/prediction.py +2 -0
  59. supervisely/nn/model/prediction_session.py +20 -3
  60. supervisely/nn/training/gui/gui.py +131 -44
  61. supervisely/nn/training/gui/model_selector.py +8 -6
  62. supervisely/nn/training/gui/train_val_splits_selector.py +122 -70
  63. supervisely/nn/training/gui/training_artifacts.py +0 -5
  64. supervisely/nn/training/train_app.py +161 -44
  65. supervisely/template/experiment/experiment.html.jinja +74 -17
  66. supervisely/template/experiment/experiment_generator.py +258 -112
  67. supervisely/template/experiment/header.html.jinja +31 -13
  68. supervisely/template/experiment/sly-style.css +7 -2
  69. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/METADATA +3 -1
  70. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/RECORD +74 -56
  71. supervisely/app/widgets/experiment_selector/style.css +0 -27
  72. supervisely/app/widgets/experiment_selector/template.html +0 -61
  73. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/LICENSE +0 -0
  74. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/WHEEL +0 -0
  75. {supervisely-6.73.420.dist-info → supervisely-6.73.422.dist-info}/entry_points.txt +0 -0
  76. {supervisely-6.73.420.dist-info → supervisely-6.73.422.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 method: dict
734
+ :type params: dict
734
735
  :param retries: The number of attempts to connect to the server.
735
- :type method: int, optional
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 method: bool, optional
738
+ :type stream: bool, optional
738
739
  :param use_public_api:
739
- :type method: bool, optional
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(url, params=json_body, headers=self.headers, stream=stream)
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)
@@ -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(self, app_id, version, file_path, save_path):
1398
- """download_git_file"""
1399
- raise NotImplementedError()
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
 
@@ -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"
@@ -157,16 +157,15 @@ class DeployApi:
157
157
  "device": device,
158
158
  "model_source": ModelSource.CUSTOM,
159
159
  "model_files": {
160
- "checkpoint": Path(
161
- experiment_info.artifacts_dir, "checkpoints", checkpoint_name
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(f"Model file not found: '{full_file_path}'. Trying to find it by checkpoint path.")
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(f"Model file not found: '{full_file_path}'. Make sure that the file exists in the artifacts directory.")
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["id"] for workspace in workspaces]
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["id"] for workspace in workspaces]
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
- deploy_info = self._deploy_api.get_deploy_info(task["id"])
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"]
@@ -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
- # teamd_id = 1,
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
- # teamd_id = 1,
2179
+ # team_id = 1,
2151
2180
  # import_settings = {},
2152
2181
  # )
2153
2182
  # ]
@@ -1007,11 +1007,12 @@ class TaskApi(ModuleApiBase, ModuleWithStatus):
1007
1007
  Example of experiment_info:
1008
1008
 
1009
1009
  experiment_info = {
1010
- 'experiment_name': '247_Lemons_RT-DETRv2-M',
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