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

@@ -25,6 +25,7 @@ import supervisely.io.json as sly_json
25
25
  from supervisely import (
26
26
  Api,
27
27
  Application,
28
+ Dataset,
28
29
  DatasetInfo,
29
30
  OpenMode,
30
31
  Project,
@@ -32,6 +33,7 @@ from supervisely import (
32
33
  ProjectMeta,
33
34
  WorkflowMeta,
34
35
  WorkflowSettings,
36
+ batched,
35
37
  download_project,
36
38
  is_development,
37
39
  is_production,
@@ -340,22 +342,6 @@ class TrainApp:
340
342
  """
341
343
  return self.gui.model_selector.get_model_info()
342
344
 
343
- @property
344
- def model_meta(self) -> ProjectMeta:
345
- """
346
- Returns the model metadata.
347
-
348
- :return: Model metadata.
349
- :rtype: dict
350
- """
351
- project_meta_json = self.project_meta.to_json()
352
- model_meta = {
353
- "classes": [
354
- item for item in project_meta_json["classes"] if item["title"] in self.classes
355
- ]
356
- }
357
- return ProjectMeta.from_json(model_meta)
358
-
359
345
  @property
360
346
  def device(self) -> str:
361
347
  """
@@ -505,8 +491,7 @@ class TrainApp:
505
491
  self._download_project()
506
492
  # Step 3. Split Project
507
493
  self._split_project()
508
- # Step 4. Convert Supervisely to X format
509
- # Step 5. Download Model files
494
+ # Step 4. Download Model files
510
495
  self._download_model()
511
496
 
512
497
  def _finalize(self, experiment_info: dict) -> None:
@@ -528,13 +513,14 @@ class TrainApp:
528
513
  experiment_info = self._preprocess_artifacts(experiment_info)
529
514
 
530
515
  # Step3. Postprocess splits
531
- splits_data = self._postprocess_splits()
516
+ train_splits_data = self._postprocess_splits()
532
517
 
533
518
  # Step 3. Upload artifacts
534
519
  self._set_text_status("uploading")
535
520
  remote_dir, file_info = self._upload_artifacts()
536
521
 
537
522
  # Step 4. Run Model Benchmark
523
+ model_meta = self.create_model_meta(experiment_info["task_type"])
538
524
  mb_eval_lnk_file_info, mb_eval_report, mb_eval_report_id, eval_metrics = (
539
525
  None,
540
526
  None,
@@ -543,6 +529,15 @@ class TrainApp:
543
529
  )
544
530
  if self.is_model_benchmark_enabled:
545
531
  try:
532
+ # Convert GT project
533
+ if self._app_options.get("auto_convert_classes", True):
534
+ self._set_text_status("convert_gt_project")
535
+ gt_project_id, bm_splits_data = self._convert_and_split_gt_project(
536
+ experiment_info["task_type"]
537
+ )
538
+ else:
539
+ gt_project_id, bm_splits_data = None, train_splits_data
540
+
546
541
  self._set_text_status("benchmark")
547
542
  (
548
543
  mb_eval_lnk_file_info,
@@ -550,7 +545,12 @@ class TrainApp:
550
545
  mb_eval_report_id,
551
546
  eval_metrics,
552
547
  ) = self._run_model_benchmark(
553
- self.output_dir, remote_dir, experiment_info, splits_data
548
+ self.output_dir,
549
+ remote_dir,
550
+ experiment_info,
551
+ bm_splits_data,
552
+ model_meta,
553
+ gt_project_id,
554
554
  )
555
555
  except Exception as e:
556
556
  logger.error(f"Model benchmark failed: {e}")
@@ -571,8 +571,8 @@ class TrainApp:
571
571
  )
572
572
  self._generate_app_state(remote_dir, experiment_info)
573
573
  self._generate_hyperparameters(remote_dir, experiment_info)
574
- self._generate_train_val_splits(remote_dir, splits_data)
575
- self._generate_model_meta(remote_dir, experiment_info)
574
+ self._generate_train_val_splits(remote_dir, train_splits_data)
575
+ self._generate_model_meta(remote_dir, model_meta)
576
576
  self._upload_demo_files(remote_dir)
577
577
 
578
578
  # Step 7. Set output widgets
@@ -1200,9 +1200,14 @@ class TrainApp:
1200
1200
  logger.debug("Validation successful")
1201
1201
  return True, None
1202
1202
 
1203
- def _postprocess_splits(self) -> dict:
1203
+ def _postprocess_splits(self, project_id: Optional[int] = None) -> dict:
1204
1204
  """
1205
1205
  Processes the train and val splits to generate the necessary data for the experiment_info.json file.
1206
+
1207
+ :param project_id: ID of the ground truth project for model benchmark. Provide only when cv task convertion is required.
1208
+ :type project_id: Optional[int]
1209
+ :return: Splits data.
1210
+ :rtype: dict
1206
1211
  """
1207
1212
  val_dataset_ids = None
1208
1213
  val_images_ids = None
@@ -1212,10 +1217,30 @@ class TrainApp:
1212
1217
  split_method = self.gui.train_val_splits_selector.get_split_method()
1213
1218
  train_set, val_set = self._train_split, self._val_split
1214
1219
  if split_method == "Based on datasets":
1215
- val_dataset_ids = self.gui.train_val_splits_selector.get_val_dataset_ids()
1216
- train_dataset_ids = self.gui.train_val_splits_selector.get_train_dataset_ids
1220
+ if project_id is None:
1221
+ val_dataset_ids = self.gui.train_val_splits_selector.get_val_dataset_ids()
1222
+ train_dataset_ids = self.gui.train_val_splits_selector.get_train_dataset_ids()
1223
+ else:
1224
+ src_datasets_map = {
1225
+ dataset.id: dataset
1226
+ for _, dataset in self._api.dataset.tree(self.project_info.id)
1227
+ }
1228
+ val_dataset_ids = self.gui.train_val_splits_selector.get_val_dataset_ids()
1229
+ train_dataset_ids = self.gui.train_val_splits_selector.get_train_dataset_ids()
1230
+
1231
+ train_dataset_names = [src_datasets_map[ds_id].name for ds_id in train_dataset_ids]
1232
+ val_dataset_names = [src_datasets_map[ds_id].name for ds_id in val_dataset_ids]
1233
+
1234
+ gt_datasets_map = {
1235
+ dataset.name: dataset.id for _, dataset in self._api.dataset.tree(project_id)
1236
+ }
1237
+ train_dataset_ids = [gt_datasets_map[ds_name] for ds_name in train_dataset_names]
1238
+ val_dataset_ids = [gt_datasets_map[ds_name] for ds_name in val_dataset_names]
1217
1239
  else:
1218
- dataset_infos = [dataset for _, dataset in self._api.dataset.tree(self.project_id)]
1240
+ if project_id is None:
1241
+ project_id = self.project_id
1242
+
1243
+ dataset_infos = [dataset for _, dataset in self._api.dataset.tree(project_id)]
1219
1244
  ds_infos_dict = {}
1220
1245
  for dataset in dataset_infos:
1221
1246
  if dataset.parent_id is not None:
@@ -1232,18 +1257,19 @@ class TrainApp:
1232
1257
  image_infos = []
1233
1258
  for dataset_name, image_names in image_names_per_dataset.items():
1234
1259
  ds_info = ds_infos_dict[dataset_name]
1235
- image_infos.extend(
1236
- self._api.image.get_list(
1237
- ds_info.id,
1238
- filters=[
1239
- {
1240
- "field": "name",
1241
- "operator": "in",
1242
- "value": image_names,
1243
- }
1244
- ],
1260
+ for names_batch in batched(image_names, 200):
1261
+ image_infos.extend(
1262
+ self._api.image.get_list(
1263
+ ds_info.id,
1264
+ filters=[
1265
+ {
1266
+ "field": "name",
1267
+ "operator": "in",
1268
+ "value": names_batch,
1269
+ }
1270
+ ],
1271
+ )
1245
1272
  )
1246
- )
1247
1273
  return image_infos
1248
1274
 
1249
1275
  val_image_infos = get_image_infos_by_split(ds_infos_dict, val_set)
@@ -1373,7 +1399,7 @@ class TrainApp:
1373
1399
  f"Uploading '{self._train_val_split_file}' to Team Files",
1374
1400
  )
1375
1401
 
1376
- def _generate_model_meta(self, remote_dir: str, experiment_info: dict) -> None:
1402
+ def _generate_model_meta(self, remote_dir: str, model_meta: ProjectMeta) -> None:
1377
1403
  """
1378
1404
  Generates and uploads the model_meta.json file to the output directory.
1379
1405
 
@@ -1382,17 +1408,31 @@ class TrainApp:
1382
1408
  :param experiment_info: Information about the experiment results.
1383
1409
  :type experiment_info: dict
1384
1410
  """
1385
- # @TODO: Handle tags for classification tasks
1386
1411
  local_path = join(self.output_dir, self._model_meta_file)
1387
1412
  remote_path = join(remote_dir, self._model_meta_file)
1388
1413
 
1389
- sly_json.dump_json_file(self.model_meta.to_json(), local_path)
1414
+ sly_json.dump_json_file(model_meta.to_json(), local_path)
1390
1415
  self._upload_file_to_team_files(
1391
1416
  local_path,
1392
1417
  remote_path,
1393
1418
  f"Uploading '{self._model_meta_file}' to Team Files",
1394
1419
  )
1395
1420
 
1421
+ def create_model_meta(self, task_type: str):
1422
+ """
1423
+ Convert project meta according to task type.
1424
+ """
1425
+ names_to_delete = [
1426
+ c.name for c in self.project_meta.obj_classes if c.name not in self.classes
1427
+ ]
1428
+ model_meta = self.project_meta.delete_obj_classes(names_to_delete)
1429
+
1430
+ if task_type == TaskType.OBJECT_DETECTION:
1431
+ model_meta, _ = model_meta.to_detection_task(True)
1432
+ elif task_type in [TaskType.INSTANCE_SEGMENTATION, TaskType.SEMANTIC_SEGMENTATION]:
1433
+ model_meta, _ = model_meta.to_segmentation_task() # @TODO: check background class
1434
+ return model_meta
1435
+
1396
1436
  def _generate_experiment_info(
1397
1437
  self,
1398
1438
  remote_dir: str,
@@ -1674,8 +1714,9 @@ class TrainApp:
1674
1714
  self.gui.training_artifacts.model_benchmark_report_thumbnail.show()
1675
1715
  self.gui.training_artifacts.model_benchmark_report_field.show()
1676
1716
  else:
1677
- self.gui.training_artifacts.model_benchmark_fail_text.show()
1678
- self.gui.training_artifacts.model_benchmark_report_field.show()
1717
+ if self.gui.hyperparameters_selector.get_model_benchmark_checkbox_value():
1718
+ self.gui.training_artifacts.model_benchmark_fail_text.show()
1719
+ self.gui.training_artifacts.model_benchmark_report_field.show()
1679
1720
  # ---------------------------- #
1680
1721
 
1681
1722
  # Set instruction to GUI
@@ -1739,6 +1780,8 @@ class TrainApp:
1739
1780
  remote_artifacts_dir: str,
1740
1781
  experiment_info: dict,
1741
1782
  splits_data: dict,
1783
+ model_meta: ProjectInfo,
1784
+ gt_project_id: int = None,
1742
1785
  ) -> tuple:
1743
1786
  """
1744
1787
  Runs the Model Benchmark evaluation process. Model benchmark runs only in production mode.
@@ -1751,6 +1794,10 @@ class TrainApp:
1751
1794
  :type experiment_info: dict
1752
1795
  :param splits_data: Information about the train and val splits.
1753
1796
  :type splits_data: dict
1797
+ :param model_meta: Model meta with object classes.
1798
+ :type model_meta: ProjectInfo
1799
+ :param gt_project_id: Ground truth project ID with converted shapes.
1800
+ :type gt_project_id: int
1754
1801
  :return: Evaluation report, report ID and evaluation metrics.
1755
1802
  :rtype: tuple
1756
1803
  """
@@ -1766,6 +1813,7 @@ class TrainApp:
1766
1813
  supported_task_types = [
1767
1814
  TaskType.OBJECT_DETECTION,
1768
1815
  TaskType.INSTANCE_SEGMENTATION,
1816
+ TaskType.SEMANTIC_SEGMENTATION,
1769
1817
  ]
1770
1818
  task_type = experiment_info["task_type"]
1771
1819
  if task_type not in supported_task_types:
@@ -1806,7 +1854,7 @@ class TrainApp:
1806
1854
  "artifacts_dir": remote_artifacts_dir,
1807
1855
  "model_name": experiment_info["model_name"],
1808
1856
  "framework_name": self.framework_name,
1809
- "model_meta": self.model_meta.to_json(),
1857
+ "model_meta": model_meta.to_json(),
1810
1858
  }
1811
1859
 
1812
1860
  logger.info(f"Deploy parameters: {self._benchmark_params}")
@@ -1826,12 +1874,15 @@ class TrainApp:
1826
1874
  train_images_ids = splits_data["train"]["images_ids"]
1827
1875
 
1828
1876
  bm = None
1877
+ if gt_project_id is None:
1878
+ gt_project_id = self.project_info.id
1879
+
1829
1880
  if task_type == TaskType.OBJECT_DETECTION:
1830
1881
  eval_params = ObjectDetectionEvaluator.load_yaml_evaluation_params()
1831
1882
  eval_params = yaml.safe_load(eval_params)
1832
1883
  bm = ObjectDetectionBenchmark(
1833
1884
  self._api,
1834
- self.project_info.id,
1885
+ gt_project_id,
1835
1886
  output_dir=benchmark_dir,
1836
1887
  gt_dataset_ids=benchmark_dataset_ids,
1837
1888
  gt_images_ids=benchmark_images_ids,
@@ -1845,7 +1896,7 @@ class TrainApp:
1845
1896
  eval_params = yaml.safe_load(eval_params)
1846
1897
  bm = InstanceSegmentationBenchmark(
1847
1898
  self._api,
1848
- self.project_info.id,
1899
+ gt_project_id,
1849
1900
  output_dir=benchmark_dir,
1850
1901
  gt_dataset_ids=benchmark_dataset_ids,
1851
1902
  gt_images_ids=benchmark_images_ids,
@@ -1859,7 +1910,7 @@ class TrainApp:
1859
1910
  eval_params = yaml.safe_load(eval_params)
1860
1911
  bm = SemanticSegmentationBenchmark(
1861
1912
  self._api,
1862
- self.project_info.id,
1913
+ gt_project_id,
1863
1914
  output_dir=benchmark_dir,
1864
1915
  gt_dataset_ids=benchmark_dataset_ids,
1865
1916
  gt_images_ids=benchmark_images_ids,
@@ -2279,6 +2330,7 @@ class TrainApp:
2279
2330
  "metadata",
2280
2331
  "export_onnx",
2281
2332
  "export_trt",
2333
+ "convert_gt_project",
2282
2334
  ],
2283
2335
  ):
2284
2336
 
@@ -2312,6 +2364,8 @@ class TrainApp:
2312
2364
  self.gui.training_process.validator_text.set("Validating experiment...", "info")
2313
2365
  elif status == "metadata":
2314
2366
  self.gui.training_process.validator_text.set("Generating training metadata...", "info")
2367
+ elif status == "convert_gt_project":
2368
+ self.gui.training_process.validator_text.set("Converting GT project...", "info")
2315
2369
 
2316
2370
  def _set_ws_progress_status(
2317
2371
  self,
@@ -2390,3 +2444,54 @@ class TrainApp:
2390
2444
  for runtime, path in export_weights.items()
2391
2445
  }
2392
2446
  return remote_export_weights
2447
+
2448
+ def _convert_and_split_gt_project(self, task_type: str):
2449
+ # 1. Convert GT project to cv task
2450
+ Project.download(
2451
+ self._api, self.project_info.id, "tmp_project", save_images=False, save_image_info=True
2452
+ )
2453
+ project = Project("tmp_project", OpenMode.READ)
2454
+
2455
+ pr_prefix = ""
2456
+ if task_type == TaskType.OBJECT_DETECTION:
2457
+ Project.to_detection_task(project.directory, inplace=True)
2458
+ pr_prefix = "[detection]: "
2459
+ # @TODO: dont convert segmentation?
2460
+ elif (
2461
+ task_type == TaskType.INSTANCE_SEGMENTATION
2462
+ or task_type == TaskType.SEMANTIC_SEGMENTATION
2463
+ ):
2464
+ Project.to_segmentation_task(project.directory, inplace=True)
2465
+ pr_prefix = "[segmentation]: "
2466
+
2467
+ gt_project_info = self._api.project.create(
2468
+ self.workspace_id,
2469
+ f"{pr_prefix}{self.project_info.name}",
2470
+ description=(
2471
+ f"Converted ground truth project for trainig session: '{self.task_id}'. "
2472
+ f"Original project id: '{self.project_info.id}. "
2473
+ "Removing this project will affect model benchmark evaluation report."
2474
+ ),
2475
+ change_name_if_conflict=True,
2476
+ )
2477
+
2478
+ # 3. Upload gt project to benchmark workspace
2479
+ project = Project("tmp_project", OpenMode.READ)
2480
+ self._api.project.update_meta(gt_project_info.id, project.meta)
2481
+ for dataset in project.datasets:
2482
+ dataset: Dataset
2483
+ ds_info = self._api.dataset.create(
2484
+ gt_project_info.id, dataset.name, change_name_if_conflict=True
2485
+ )
2486
+ for batch_names in batched(dataset.get_items_names(), 100):
2487
+ img_infos = [dataset.get_item_info(name) for name in batch_names]
2488
+ img_ids = [img_info.id for img_info in img_infos]
2489
+ anns = [dataset.get_ann(name, project.meta) for name in batch_names]
2490
+
2491
+ img_infos = self._api.image.copy_batch(ds_info.id, img_ids)
2492
+ img_ids = [img_info.id for img_info in img_infos]
2493
+ self._api.annotation.upload_anns(img_ids, anns)
2494
+
2495
+ # 4. Match splits with original project
2496
+ gt_split_data = self._postprocess_splits(gt_project_info.id)
2497
+ return gt_project_info.id, gt_split_data