supervisely 6.73.249__py3-none-any.whl → 6.73.251__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.

Potentially problematic release.


This version of supervisely might be problematic. Click here for more details.

Files changed (34) hide show
  1. supervisely/api/api.py +19 -3
  2. supervisely/app/widgets/experiment_selector/experiment_selector.py +16 -8
  3. supervisely/nn/benchmark/base_benchmark.py +17 -2
  4. supervisely/nn/benchmark/base_evaluator.py +28 -6
  5. supervisely/nn/benchmark/instance_segmentation/benchmark.py +1 -1
  6. supervisely/nn/benchmark/instance_segmentation/evaluator.py +14 -0
  7. supervisely/nn/benchmark/object_detection/benchmark.py +1 -1
  8. supervisely/nn/benchmark/object_detection/evaluator.py +43 -13
  9. supervisely/nn/benchmark/object_detection/metric_provider.py +7 -0
  10. supervisely/nn/benchmark/semantic_segmentation/evaluator.py +33 -7
  11. supervisely/nn/benchmark/utils/detection/utlis.py +6 -4
  12. supervisely/nn/experiments.py +23 -16
  13. supervisely/nn/inference/gui/serving_gui_template.py +2 -35
  14. supervisely/nn/inference/inference.py +71 -8
  15. supervisely/nn/training/__init__.py +2 -0
  16. supervisely/nn/training/gui/classes_selector.py +14 -14
  17. supervisely/nn/training/gui/gui.py +28 -13
  18. supervisely/nn/training/gui/hyperparameters_selector.py +90 -41
  19. supervisely/nn/training/gui/input_selector.py +8 -6
  20. supervisely/nn/training/gui/model_selector.py +7 -5
  21. supervisely/nn/training/gui/train_val_splits_selector.py +8 -9
  22. supervisely/nn/training/gui/training_logs.py +17 -17
  23. supervisely/nn/training/gui/training_process.py +41 -36
  24. supervisely/nn/training/loggers/__init__.py +22 -0
  25. supervisely/nn/training/loggers/base_train_logger.py +8 -5
  26. supervisely/nn/training/loggers/tensorboard_logger.py +4 -11
  27. supervisely/nn/training/train_app.py +276 -90
  28. supervisely/project/project.py +6 -0
  29. {supervisely-6.73.249.dist-info → supervisely-6.73.251.dist-info}/METADATA +8 -3
  30. {supervisely-6.73.249.dist-info → supervisely-6.73.251.dist-info}/RECORD +34 -34
  31. {supervisely-6.73.249.dist-info → supervisely-6.73.251.dist-info}/LICENSE +0 -0
  32. {supervisely-6.73.249.dist-info → supervisely-6.73.251.dist-info}/WHEEL +0 -0
  33. {supervisely-6.73.249.dist-info → supervisely-6.73.251.dist-info}/entry_points.txt +0 -0
  34. {supervisely-6.73.249.dist-info → supervisely-6.73.251.dist-info}/top_level.txt +0 -0
supervisely/api/api.py CHANGED
@@ -1011,6 +1011,7 @@ class Api:
1011
1011
  headers: Optional[Dict[str, str]] = None,
1012
1012
  retries: Optional[int] = None,
1013
1013
  raise_error: Optional[bool] = False,
1014
+ timeout: httpx._types.TimeoutTypes = 60,
1014
1015
  ) -> httpx.Response:
1015
1016
  """
1016
1017
  Performs POST request to server with given parameters using httpx.
@@ -1031,6 +1032,8 @@ class Api:
1031
1032
  :type retries: int, optional
1032
1033
  :param raise_error: Define, if you'd like to raise error if connection is failed.
1033
1034
  :type raise_error: bool, optional
1035
+ :param timeout: Overall timeout for the request.
1036
+ :type timeout: float, optional
1034
1037
  :return: Response object
1035
1038
  :rtype: :class:`httpx.Response`
1036
1039
  """
@@ -1057,6 +1060,7 @@ class Api:
1057
1060
  json=json,
1058
1061
  params=params,
1059
1062
  headers=headers,
1063
+ timeout=timeout,
1060
1064
  )
1061
1065
  if response.status_code != httpx.codes.OK:
1062
1066
  self._check_version()
@@ -1098,6 +1102,7 @@ class Api:
1098
1102
  params: httpx._types.QueryParamTypes,
1099
1103
  retries: Optional[int] = None,
1100
1104
  use_public_api: Optional[bool] = True,
1105
+ timeout: httpx._types.TimeoutTypes = 60,
1101
1106
  ) -> httpx.Response:
1102
1107
  """
1103
1108
  Performs GET request to server with given parameters.
@@ -1110,6 +1115,8 @@ class Api:
1110
1115
  :type retries: int, optional
1111
1116
  :param use_public_api: Define if public API should be used. Default is True.
1112
1117
  :type use_public_api: bool, optional
1118
+ :param timeout: Overall timeout for the request.
1119
+ :type timeout: float, optional
1113
1120
  :return: Response object
1114
1121
  :rtype: :class:`Response<Response>`
1115
1122
  """
@@ -1131,7 +1138,12 @@ class Api:
1131
1138
  for retry_idx in range(retries):
1132
1139
  response = None
1133
1140
  try:
1134
- response = self.httpx_client.get(url, params=request_params, headers=self.headers)
1141
+ response = self.httpx_client.get(
1142
+ url,
1143
+ params=request_params,
1144
+ headers=self.headers,
1145
+ timeout=timeout,
1146
+ )
1135
1147
  if response.status_code != httpx.codes.OK:
1136
1148
  Api._raise_for_status_httpx(response)
1137
1149
  return response
@@ -1170,7 +1182,7 @@ class Api:
1170
1182
  raise_error: Optional[bool] = False,
1171
1183
  chunk_size: int = 8192,
1172
1184
  use_public_api: Optional[bool] = True,
1173
- timeout: httpx._types.TimeoutTypes = 15,
1185
+ timeout: httpx._types.TimeoutTypes = 60,
1174
1186
  ) -> Generator:
1175
1187
  """
1176
1188
  Performs streaming GET or POST request to server with given parameters.
@@ -1325,6 +1337,7 @@ class Api:
1325
1337
  headers: Optional[Dict[str, str]] = None,
1326
1338
  retries: Optional[int] = None,
1327
1339
  raise_error: Optional[bool] = False,
1340
+ timeout: httpx._types.TimeoutTypes = 60,
1328
1341
  ) -> httpx.Response:
1329
1342
  """
1330
1343
  Performs POST request to server with given parameters using httpx.
@@ -1345,6 +1358,8 @@ class Api:
1345
1358
  :type retries: int, optional
1346
1359
  :param raise_error: Define, if you'd like to raise error if connection is failed.
1347
1360
  :type raise_error: bool, optional
1361
+ :param timeout: Overall timeout for the request.
1362
+ :type timeout: float, optional
1348
1363
  :return: Response object
1349
1364
  :rtype: :class:`httpx.Response`
1350
1365
  """
@@ -1371,6 +1386,7 @@ class Api:
1371
1386
  json=json,
1372
1387
  params=params,
1373
1388
  headers=headers,
1389
+ timeout=timeout,
1374
1390
  )
1375
1391
  if response.status_code != httpx.codes.OK:
1376
1392
  self._check_version()
@@ -1417,7 +1433,7 @@ class Api:
1417
1433
  range_end: Optional[int] = None,
1418
1434
  chunk_size: int = 8192,
1419
1435
  use_public_api: Optional[bool] = True,
1420
- timeout: httpx._types.TimeoutTypes = 15,
1436
+ timeout: httpx._types.TimeoutTypes = 60,
1421
1437
  ) -> AsyncGenerator:
1422
1438
  """
1423
1439
  Performs asynchronous streaming GET or POST request to server with given parameters.
@@ -1,6 +1,7 @@
1
1
  import os
2
2
  from collections import defaultdict
3
3
  from concurrent.futures import ThreadPoolExecutor, as_completed
4
+ from dataclasses import asdict
4
5
  from typing import Any, Callable, Dict, List, Union
5
6
 
6
7
  from supervisely import env, logger
@@ -91,7 +92,7 @@ class ExperimentSelector(Widget):
91
92
  self._session_link = self._generate_session_link()
92
93
 
93
94
  # col 6 benchmark report
94
- self._benchmark_report = None # experiment_infos.benchmark_report_path
95
+ self._benchmark_report_id = experiment_info.evaluation_report_id
95
96
 
96
97
  # widgets
97
98
  self._task_widget = self._create_task_widget()
@@ -191,7 +192,7 @@ class ExperimentSelector(Widget):
191
192
  task_widget = Container(
192
193
  [
193
194
  Text(
194
- f"<i class='zmdi zmdi-folder' style='color: #7f858e'></i> <a href='{self._task_link}'>{self._task_id}</a>",
195
+ f"<i class='zmdi zmdi-folder' style='color: #7f858e'></i> <a href='{self._task_link}' target='_blank'>{self._task_id}</a>",
195
196
  "text",
196
197
  ),
197
198
  Text(
@@ -237,22 +238,29 @@ class ExperimentSelector(Widget):
237
238
 
238
239
  def _create_session_widget(self) -> Text:
239
240
  session_link_widget = Text(
240
- f"<a href='{self._session_link}'>Preview</a> <i class='zmdi zmdi-open-in-new'></i>",
241
+ f"<a href='{self._session_link}' target='_blank'>Preview</a> <i class='zmdi zmdi-open-in-new'></i>",
241
242
  "text",
242
243
  )
243
244
  return session_link_widget
244
245
 
245
246
  def _create_benchmark_widget(self) -> Text:
246
- if self._benchmark_report is None:
247
- self._benchmark_report = "No benchmark report available"
247
+ if self._benchmark_report_id is None:
248
+ self._benchmark_report_id = "No evaluation report available"
248
249
  benchmark_widget = Text(
249
- "<span class='field-description text-muted' style='color: #7f858e'>No benchmark report available</span>",
250
+ "<span class='field-description text-muted' style='color: #7f858e'>No evaluation report available</span>",
250
251
  "text",
251
252
  font_size=13,
252
253
  )
253
254
  else:
255
+ if is_development():
256
+ benchmark_report_link = abs_url(
257
+ f"/model-benchmark?id={self._benchmark_report_id}"
258
+ )
259
+ else:
260
+ benchmark_report_link = f"/model-benchmark?id={self._benchmark_report_id}"
261
+
254
262
  benchmark_widget = Text(
255
- f"<a href='{self._benchmark_report}'>Benchmark Report</a> <i class='zmdi zmdi-chart'></i>",
263
+ f"<i class='zmdi zmdi-chart' style='color: #7f858e'></i> <a href='{benchmark_report_link}' target='_blank'>evaluation report</a>",
256
264
  "text",
257
265
  )
258
266
  return benchmark_widget
@@ -432,7 +440,7 @@ class ExperimentSelector(Widget):
432
440
  if len(self._rows) == 0:
433
441
  return
434
442
  selected_row = self.get_selected_row()
435
- selected_row_json = selected_row._experiment_info._asdict()
443
+ selected_row_json = asdict(selected_row._experiment_info)
436
444
  return selected_row_json
437
445
 
438
446
  def get_selected_checkpoint_path(self) -> str:
@@ -74,6 +74,8 @@ class BaseBenchmark:
74
74
  self.train_info = None
75
75
  self.evaluator_app_info = None
76
76
  self.evaluation_params = evaluation_params
77
+ self._eval_results = None
78
+ self.report_id = None
77
79
  self._validate_evaluation_params()
78
80
 
79
81
  def _get_evaluator_class(self) -> type:
@@ -87,6 +89,11 @@ class BaseBenchmark:
87
89
  def hardware(self) -> str:
88
90
  return self._hardware
89
91
 
92
+ @property
93
+ def key_metrics(self):
94
+ eval_results = self.get_eval_result()
95
+ return eval_results.key_metrics
96
+
90
97
  def run_evaluation(
91
98
  self,
92
99
  model_session: Union[int, str, SessionJSON],
@@ -482,8 +489,10 @@ class BaseBenchmark:
482
489
  f"Visualizer class is not defined in {self.__class__.__name__}. "
483
490
  "It should be defined in the subclass of BaseBenchmark (e.g. ObjectDetectionBenchmark)."
484
491
  )
485
- eval_result = self.evaluator.get_eval_result()
486
- vis = self.visualizer_cls(self.api, [eval_result], self.get_layout_results_dir(), self.pbar) # pylint: disable=not-callable
492
+ eval_result = self.get_eval_result()
493
+ vis = self.visualizer_cls( # pylint: disable=not-callable
494
+ self.api, [eval_result], self.get_layout_results_dir(), self.pbar
495
+ )
487
496
  with self.pbar(message="Visualizations: Rendering layout", total=1) as p:
488
497
  vis.visualize()
489
498
  p.update(1)
@@ -566,6 +575,7 @@ class BaseBenchmark:
566
575
  def upload_report_link(self, remote_dir: str):
567
576
  template_path = os.path.join(remote_dir, "template.vue")
568
577
  vue_template_info = self.api.file.get_info_by_path(self.team_id, template_path)
578
+ self.report_id = vue_template_info.id
569
579
 
570
580
  report_link = "/model-benchmark?id=" + str(vue_template_info.id)
571
581
  local_path = os.path.join(self.get_layout_results_dir(), "open.lnk")
@@ -608,3 +618,8 @@ class BaseBenchmark:
608
618
  return sum(ds.items_count for ds in self.gt_dataset_infos)
609
619
  else:
610
620
  return self.gt_project_info.items_count
621
+
622
+ def get_eval_result(self):
623
+ if self._eval_results is None:
624
+ self._eval_results = self.evaluator.get_eval_result()
625
+ return self._eval_results
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import pickle
3
5
  from typing import Dict, List, Optional, Union
@@ -9,23 +11,34 @@ from supervisely.task.progress import tqdm_sly
9
11
 
10
12
 
11
13
  class BaseEvalResult:
12
- def __init__(self, directory: str):
14
+ def __init__(self, directory: Optional[str] = None):
13
15
  self.directory = directory
14
16
  self.inference_info: Dict = None
15
17
  self.speedtest_info: Dict = None
16
18
  self.eval_data: Dict = None
17
19
  self.mp = None
18
20
 
19
- self._read_eval_data()
21
+ if self.directory is not None:
22
+ self._read_files(self.directory)
23
+ self._prepare_data()
24
+
25
+ @classmethod
26
+ def from_evaluator(cls, evaulator: BaseEvaluator) -> BaseEvalResult:
27
+ """Method to customize loading of the evaluation result."""
28
+ raise NotImplementedError()
20
29
 
21
30
  @property
22
31
  def cv_task(self):
23
32
  return self.inference_info.get("task_type")
24
33
 
25
34
  @property
26
- def name(self) -> str:
27
- model_name = self.inference_info.get("model_name", self.directory)
28
- return self.inference_info.get("deploy_params", {}).get("checkpoint_name", model_name)
35
+ def name(self) -> Union[str, None]:
36
+ deploy_params = self.inference_info.get("deploy_params", {})
37
+ return (
38
+ deploy_params.get("checkpoint_name")
39
+ or deploy_params.get("model_name")
40
+ or self.inference_info.get("model_name")
41
+ )
29
42
 
30
43
  @property
31
44
  def gt_project_id(self) -> int:
@@ -59,7 +72,16 @@ class BaseEvalResult:
59
72
  def classes_whitelist(self):
60
73
  return self.inference_info.get("inference_settings", {}).get("classes", []) # TODO: check
61
74
 
62
- def _read_eval_data(self):
75
+ def _read_files(self, path: str) -> None:
76
+ """Read all necessary files from the directory"""
77
+ raise NotImplementedError()
78
+
79
+ def _prepare_data(self) -> None:
80
+ """Prepare data to allow easy access to the data"""
81
+ raise NotImplementedError()
82
+
83
+ @property
84
+ def key_metrics(self):
63
85
  raise NotImplementedError()
64
86
 
65
87
 
@@ -29,5 +29,5 @@ class InstanceSegmentationBenchmark(ObjectDetectionBenchmark):
29
29
  ):
30
30
  assert try_set_conf_auto(
31
31
  self.session, CONF_THRES
32
- ), f"Unable to set the confidence threshold to {CONF_THRES} for evalation."
32
+ ), f"Unable to set the confidence threshold to {CONF_THRES} for evaluation."
33
33
  return super()._run_inference(output_project_id, batch_size, cache_project_on_agent)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  from pathlib import Path
3
5
 
@@ -13,6 +15,18 @@ from supervisely.nn.benchmark.utils import calculate_metrics, read_coco_datasets
13
15
  class InstanceSegmentationEvalResult(ObjectDetectionEvalResult):
14
16
  mp_cls = MetricProvider
15
17
 
18
+ @classmethod
19
+ def from_evaluator(
20
+ cls, evaulator: InstanceSegmentationEvaluator
21
+ ) -> InstanceSegmentationEvalResult:
22
+ """Method to customize loading of the evaluation result."""
23
+ eval_result = cls()
24
+ eval_result.eval_data = evaulator.eval_data
25
+ eval_result.coco_gt = evaulator.cocoGt
26
+ eval_result.coco_dt = evaulator.cocoDt
27
+ eval_result._prepare_data()
28
+ return eval_result
29
+
16
30
 
17
31
  class InstanceSegmentationEvaluator(ObjectDetectionEvaluator):
18
32
  EVALUATION_PARAMS_YAML_PATH = f"{Path(__file__).parent}/evaluation_params.yaml"
@@ -27,5 +27,5 @@ class ObjectDetectionBenchmark(BaseBenchmark):
27
27
  ):
28
28
  assert try_set_conf_auto(
29
29
  self.session, CONF_THRES
30
- ), f"Unable to set the confidence threshold to {CONF_THRES} for evalation."
30
+ ), f"Unable to set the confidence threshold to {CONF_THRES} for evaluation."
31
31
  return super()._run_inference(output_project_id, batch_size, cache_project_on_agent)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import pickle
3
5
  from pathlib import Path
@@ -18,25 +20,39 @@ from supervisely.nn.benchmark.visualization.vis_click_data import ClickData, IdM
18
20
  class ObjectDetectionEvalResult(BaseEvalResult):
19
21
  mp_cls = MetricProvider
20
22
 
21
- def _read_eval_data(self):
22
- from pycocotools.coco import COCO # pylint: disable=import-error
23
+ def _read_files(self, path: str) -> None:
24
+ """Read all necessary files from the directory"""
25
+
26
+ self.coco_gt = Path(path) / "cocoGt.json"
27
+ self.coco_dt = Path(path) / "cocoDt.json"
23
28
 
24
- gt_path = str(Path(self.directory) / "cocoGt.json")
25
- dt_path = str(Path(self.directory) / "cocoDt.json")
26
- coco_gt, coco_dt = COCO(gt_path), COCO(dt_path)
27
- self.coco_gt = coco_gt
28
- self.coco_dt = coco_dt
29
- self.eval_data = None
30
- with open(Path(self.directory, "eval_data.pkl"), "rb") as f:
31
- self.eval_data = pickle.load(f)
29
+ if self.coco_gt.exists() and self.coco_dt.exists():
30
+ self.coco_gt, self.coco_dt = read_coco_datasets(self.coco_gt, self.coco_dt)
32
31
 
33
- inference_info_path = Path(self.directory) / "inference_info.json"
34
- self.inference_info = load_json_file(str(inference_info_path))
32
+ eval_data_path = Path(path) / "eval_data.pkl"
33
+ if eval_data_path.exists():
34
+ with open(Path(path, "eval_data.pkl"), "rb") as f:
35
+ self.eval_data = pickle.load(f)
35
36
 
36
- speedtest_info_path = Path(self.directory).parent / "speedtest" / "speedtest.json"
37
+ inference_info_path = Path(path) / "inference_info.json"
38
+ if inference_info_path.exists():
39
+ self.inference_info = load_json_file(str(inference_info_path))
40
+
41
+ speedtest_info_path = Path(path).parent / "speedtest" / "speedtest.json"
37
42
  if speedtest_info_path.exists():
38
43
  self.speedtest_info = load_json_file(str(speedtest_info_path))
39
44
 
45
+ def _prepare_data(self) -> None:
46
+ """Prepare data to allow easy access to the most important parts"""
47
+
48
+ from pycocotools.coco import COCO # pylint: disable=import-error
49
+
50
+ if not hasattr(self, "coco_gt") or not hasattr(self, "coco_dt"):
51
+ raise ValueError("GT and DT datasets are not provided")
52
+
53
+ if not isinstance(self.coco_gt, COCO) and not isinstance(self.coco_dt, COCO):
54
+ self.coco_gt, self.coco_dt = read_coco_datasets(self.coco_gt, self.coco_dt)
55
+
40
56
  self.mp = MetricProvider(
41
57
  self.eval_data["matches"],
42
58
  self.eval_data["coco_metrics"],
@@ -62,6 +78,20 @@ class ObjectDetectionEvalResult(BaseEvalResult):
62
78
 
63
79
  self.click_data = ClickData(self.mp.m, gt_id_mapper, dt_id_mapper)
64
80
 
81
+ @classmethod
82
+ def from_evaluator(cls, evaulator: ObjectDetectionEvaluator) -> ObjectDetectionEvalResult:
83
+ """Method to customize loading of the evaluation result."""
84
+ eval_result = cls()
85
+ eval_result.eval_data = evaulator.eval_data
86
+ eval_result.coco_gt = evaulator.cocoGt
87
+ eval_result.coco_dt = evaulator.cocoDt
88
+ eval_result._prepare_data()
89
+ return eval_result
90
+
91
+ @property
92
+ def key_metrics(self):
93
+ return self.mp.key_metrics()
94
+
65
95
 
66
96
  class ObjectDetectionEvaluator(BaseEvaluator):
67
97
  EVALUATION_PARAMS_YAML_PATH = f"{Path(__file__).parent}/evaluation_params.yaml"
@@ -164,6 +164,13 @@ class MetricProvider:
164
164
  f"AP{iou_name}_by_class": ap_custom_by_class,
165
165
  }
166
166
 
167
+ def key_metrics(self):
168
+ iou_name = int(self.iou_threshold * 100)
169
+ json_metrics = self.json_metrics()
170
+ json_metrics.pop("AP_by_class")
171
+ json_metrics.pop(f"AP{iou_name}_by_class")
172
+ return json_metrics
173
+
167
174
  def metric_table(self):
168
175
  table = self.json_metrics()
169
176
  iou_name = int(self.iou_threshold * 100)
@@ -1,3 +1,5 @@
1
+ from __future__ import annotations
2
+
1
3
  import os
2
4
  import pickle
3
5
  import shutil
@@ -24,16 +26,40 @@ from supervisely.sly_logger import logger
24
26
  class SemanticSegmentationEvalResult(BaseEvalResult):
25
27
  mp_cls = MetricProvider
26
28
 
27
- def _read_eval_data(self):
28
- self.eval_data = pickle.load(open(Path(self.directory, "eval_data.pkl"), "rb"))
29
- self.inference_info = load_json_file(Path(self.directory, "inference_info.json"))
30
- speedtest_info_path = Path(self.directory).parent / "speedtest" / "speedtest.json"
31
- self.speedtest_info = None
29
+ def _read_files(self, path: str) -> None:
30
+ """Read all necessary files from the directory"""
31
+
32
+ eval_data_path = Path(path) / "eval_data.pkl"
33
+ if eval_data_path.exists():
34
+ with open(Path(path, "eval_data.pkl"), "rb") as f:
35
+ self.eval_data = pickle.load(f)
36
+
37
+ inference_info_path = Path(path) / "inference_info.json"
38
+ if inference_info_path.exists():
39
+ self.inference_info = load_json_file(str(inference_info_path))
40
+
41
+ speedtest_info_path = Path(path).parent / "speedtest" / "speedtest.json"
32
42
  if speedtest_info_path.exists():
33
- self.speedtest_info = load_json_file(speedtest_info_path)
43
+ self.speedtest_info = load_json_file(str(speedtest_info_path))
44
+
45
+ def _prepare_data(self) -> None:
46
+ """Prepare data to allow easy access to the most important parts"""
34
47
 
35
48
  self.mp = MetricProvider(self.eval_data)
36
- # self.mp.calculate()
49
+
50
+ @classmethod
51
+ def from_evaluator(
52
+ cls, evaulator: SemanticSegmentationEvaluator
53
+ ) -> SemanticSegmentationEvalResult:
54
+ """Method to customize loading of the evaluation result."""
55
+ eval_result = cls()
56
+ eval_result.eval_data = evaulator.eval_data
57
+ eval_result._prepare_data()
58
+ return eval_result
59
+
60
+ @property
61
+ def key_metrics(self):
62
+ return self.mp.json_metrics()
37
63
 
38
64
 
39
65
  class SemanticSegmentationEvaluator(BaseEvaluator):
@@ -1,3 +1,5 @@
1
+ from pathlib import Path
2
+
1
3
  from supervisely.io.json import load_json_file
2
4
  from supervisely.nn.inference import SessionJSON
3
5
 
@@ -15,10 +17,10 @@ def try_set_conf_auto(session: SessionJSON, conf: float):
15
17
  def read_coco_datasets(cocoGt_json, cocoDt_json):
16
18
  from pycocotools.coco import COCO # pylint: disable=import-error
17
19
 
18
- if isinstance(cocoGt_json, str):
19
- cocoGt_json = load_json_file(cocoGt_json)
20
- if isinstance(cocoDt_json, str):
21
- cocoDt_json = load_json_file(cocoDt_json)
20
+ if isinstance(cocoGt_json, (str, Path)):
21
+ cocoGt_json = load_json_file(str(cocoGt_json))
22
+ if isinstance(cocoDt_json, (str, Path)):
23
+ cocoDt_json = load_json_file(str(cocoDt_json))
22
24
  cocoGt = COCO()
23
25
  cocoGt.dataset = cocoGt_json
24
26
  cocoGt.createIndex()
@@ -1,15 +1,18 @@
1
1
  from concurrent.futures import ThreadPoolExecutor
2
+ from dataclasses import dataclass, fields
2
3
  from json import JSONDecodeError
3
4
  from os.path import dirname, join
4
- from typing import Any, Dict, List, NamedTuple
5
+ from typing import Any, Dict, List
5
6
 
6
7
  import requests
7
8
 
8
9
  from supervisely import logger
9
10
  from supervisely.api.api import Api, ApiField
11
+ from supervisely.api.file_api import FileInfo
10
12
 
11
13
 
12
- class ExperimentInfo(NamedTuple):
14
+ @dataclass
15
+ class ExperimentInfo:
13
16
  experiment_name: str
14
17
  """Name of the experiment. Defined by the user in the training app"""
15
18
  framework_name: str
@@ -22,12 +25,14 @@ class ExperimentInfo(NamedTuple):
22
25
  """Project ID in Supervisely"""
23
26
  task_id: int
24
27
  """Task ID in Supervisely"""
25
- model_files: Dict[str, str]
28
+ model_files: dict
26
29
  """Dictionary with paths to model files that needs to be downloaded for training"""
27
30
  checkpoints: List[str]
28
31
  """List of relative paths to checkpoints"""
29
32
  best_checkpoint: str
30
33
  """Name of the best checkpoint. Defined by the user in the training app"""
34
+ export: dict
35
+ """Dictionary with exported weights in different formats"""
31
36
  app_state: str
32
37
  """Path to file with settings that were used in the app"""
33
38
  model_meta: str
@@ -42,7 +47,7 @@ class ExperimentInfo(NamedTuple):
42
47
  """Date and time when the experiment was started"""
43
48
  evaluation_report_id: int
44
49
  """ID of the evaluation report"""
45
- eval_metrics: Dict[str, Any]
50
+ evaluation_metrics: dict
46
51
  """Evaluation metrics"""
47
52
 
48
53
 
@@ -74,7 +79,7 @@ def get_experiment_infos(api: Api, team_id: int, framework_name: str) -> List[Ex
74
79
  experiment_infos = []
75
80
 
76
81
  file_infos = api.file.list(team_id, experiments_folder, recursive=True, return_type="fileinfo")
77
- sorted_file_infos = []
82
+ sorted_experiment_paths = []
78
83
  for file_info in file_infos:
79
84
  if not file_info.path.endswith(metadata_name):
80
85
  continue
@@ -82,32 +87,34 @@ def get_experiment_infos(api: Api, team_id: int, framework_name: str) -> List[Ex
82
87
  experiment_dir = dirname(file_info.path)
83
88
  if experiment_dir.endswith(framework_name):
84
89
  experiment_path = join(experiment_dir, metadata_name)
85
- sorted_file_infos.append(experiment_path)
90
+ sorted_experiment_paths.append(experiment_path)
86
91
 
87
- def fetch_experiment_data(file_info):
92
+ def fetch_experiment_data(experiment_path: str):
88
93
  try:
89
94
  response = api.post(
90
95
  "file-storage.download",
91
- {ApiField.TEAM_ID: team_id, ApiField.PATH: file_info},
96
+ {ApiField.TEAM_ID: team_id, ApiField.PATH: experiment_path},
92
97
  stream=True,
93
98
  )
94
99
  response.raise_for_status()
95
100
  response_json = response.json()
96
- required_fields = {field for field in ExperimentInfo._fields}
97
- if not required_fields.issubset(response_json.keys()):
98
- logger.debug(
99
- f"Missing required fields in JSON from '{experiment_path}': {required_fields - response_json.keys()}"
100
- )
101
+ required_fields = {field.name for field in fields(ExperimentInfo)}
102
+ missing_fields = required_fields - response_json.keys()
103
+ if missing_fields:
104
+ logger.debug(f"Missing fields: {missing_fields} for '{experiment_path}'")
101
105
  return None
102
106
  return ExperimentInfo(**response_json)
103
107
  except requests.exceptions.RequestException as e:
104
- logger.debug(f"Failed to fetch train metadata from '{experiment_path}': {e}")
108
+ logger.debug(f"Request failed for '{experiment_path}': {e}")
105
109
  except JSONDecodeError as e:
106
- logger.debug(f"Failed to decode JSON from '{experiment_path}': {e}")
110
+ logger.debug(f"JSON decode failed for '{experiment_path}': {e}")
111
+ except TypeError as e:
112
+ logger.error(f"TypeError for '{experiment_path}': {e}")
107
113
  return None
108
114
 
115
+ # Error
109
116
  with ThreadPoolExecutor() as executor:
110
- experiment_infos = list(executor.map(fetch_experiment_data, sorted_file_infos))
117
+ experiment_infos = list(executor.map(fetch_experiment_data, sorted_experiment_paths))
111
118
 
112
119
  experiment_infos = [info for info in experiment_infos if info is not None]
113
120
  return experiment_infos
@@ -30,7 +30,7 @@ class ServingGUITemplate(ServingGUI):
30
30
  def __init__(
31
31
  self,
32
32
  framework_name: str,
33
- models: Optional[str] = None,
33
+ models: Optional[list] = None,
34
34
  app_options: Optional[str] = None,
35
35
  ):
36
36
  if not isinstance(framework_name, str):
@@ -41,7 +41,7 @@ class ServingGUITemplate(ServingGUI):
41
41
  self.team_id = sly_env.team_id()
42
42
 
43
43
  self.framework_name = framework_name
44
- self.models = self._load_models(models) if models else []
44
+ self.models = models
45
45
  self.app_options = self._load_app_options(app_options) if app_options else {}
46
46
 
47
47
  base_widgets = self._initialize_layout()
@@ -177,39 +177,6 @@ class ServingGUITemplate(ServingGUI):
177
177
  "runtime": self.runtime,
178
178
  }
179
179
 
180
- # Loaders
181
- def _load_models(self, models: str) -> List[Dict[str, Any]]:
182
- """
183
- Loads models from the provided file or list of model configurations.
184
- """
185
- if isinstance(models, str):
186
- if sly_fs.file_exists(models) and sly_fs.get_file_ext(models) == ".json":
187
- models = sly_json.load_json_file(models)
188
- else:
189
- raise ValueError("File not found or invalid file format.")
190
- else:
191
- raise ValueError(
192
- "Invalid models file. Please provide a valid '.json' file with list of model configurations."
193
- )
194
-
195
- if not isinstance(models, list):
196
- raise ValueError("models parameters must be a list of dicts")
197
- for item in models:
198
- if not isinstance(item, dict):
199
- raise ValueError(f"Each item in models must be a dict.")
200
- model_meta = item.get("meta")
201
- if model_meta is None:
202
- raise ValueError(
203
- "Model metadata not found. Please update provided models parameter to include key 'meta'."
204
- )
205
- model_files = model_meta.get("model_files")
206
- if model_files is None:
207
- raise ValueError(
208
- "Model files not found in model metadata. "
209
- "Please update provided models oarameter to include key 'model_files' in 'meta' key."
210
- )
211
- return models
212
-
213
180
  def _load_app_options(self, app_options: str = None) -> Dict[str, Any]:
214
181
  """
215
182
  Loads the app_options parameter to ensure it is in the correct format.