supervisely 6.73.242__py3-none-any.whl → 6.73.244__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 (57) hide show
  1. supervisely/__init__.py +1 -1
  2. supervisely/_utils.py +18 -0
  3. supervisely/app/widgets/__init__.py +1 -0
  4. supervisely/app/widgets/card/card.py +3 -0
  5. supervisely/app/widgets/classes_table/classes_table.py +15 -1
  6. supervisely/app/widgets/custom_models_selector/custom_models_selector.py +25 -7
  7. supervisely/app/widgets/custom_models_selector/template.html +1 -1
  8. supervisely/app/widgets/experiment_selector/__init__.py +0 -0
  9. supervisely/app/widgets/experiment_selector/experiment_selector.py +500 -0
  10. supervisely/app/widgets/experiment_selector/style.css +27 -0
  11. supervisely/app/widgets/experiment_selector/template.html +82 -0
  12. supervisely/app/widgets/pretrained_models_selector/pretrained_models_selector.py +25 -3
  13. supervisely/app/widgets/random_splits_table/random_splits_table.py +41 -17
  14. supervisely/app/widgets/select_dataset_tree/select_dataset_tree.py +12 -5
  15. supervisely/app/widgets/train_val_splits/train_val_splits.py +99 -10
  16. supervisely/app/widgets/tree_select/tree_select.py +2 -0
  17. supervisely/nn/__init__.py +3 -1
  18. supervisely/nn/artifacts/artifacts.py +10 -0
  19. supervisely/nn/artifacts/detectron2.py +2 -0
  20. supervisely/nn/artifacts/hrda.py +3 -0
  21. supervisely/nn/artifacts/mmclassification.py +2 -0
  22. supervisely/nn/artifacts/mmdetection.py +6 -3
  23. supervisely/nn/artifacts/mmsegmentation.py +2 -0
  24. supervisely/nn/artifacts/ritm.py +3 -1
  25. supervisely/nn/artifacts/rtdetr.py +2 -0
  26. supervisely/nn/artifacts/unet.py +2 -0
  27. supervisely/nn/artifacts/yolov5.py +3 -0
  28. supervisely/nn/artifacts/yolov8.py +7 -1
  29. supervisely/nn/experiments.py +113 -0
  30. supervisely/nn/inference/gui/__init__.py +3 -1
  31. supervisely/nn/inference/gui/gui.py +31 -232
  32. supervisely/nn/inference/gui/serving_gui.py +223 -0
  33. supervisely/nn/inference/gui/serving_gui_template.py +240 -0
  34. supervisely/nn/inference/inference.py +225 -24
  35. supervisely/nn/training/__init__.py +0 -0
  36. supervisely/nn/training/gui/__init__.py +1 -0
  37. supervisely/nn/training/gui/classes_selector.py +100 -0
  38. supervisely/nn/training/gui/gui.py +539 -0
  39. supervisely/nn/training/gui/hyperparameters_selector.py +117 -0
  40. supervisely/nn/training/gui/input_selector.py +70 -0
  41. supervisely/nn/training/gui/model_selector.py +95 -0
  42. supervisely/nn/training/gui/train_val_splits_selector.py +200 -0
  43. supervisely/nn/training/gui/training_logs.py +93 -0
  44. supervisely/nn/training/gui/training_process.py +114 -0
  45. supervisely/nn/training/gui/utils.py +128 -0
  46. supervisely/nn/training/loggers/__init__.py +0 -0
  47. supervisely/nn/training/loggers/base_train_logger.py +58 -0
  48. supervisely/nn/training/loggers/tensorboard_logger.py +46 -0
  49. supervisely/nn/training/train_app.py +2038 -0
  50. supervisely/nn/utils.py +5 -0
  51. supervisely/project/project.py +1 -1
  52. {supervisely-6.73.242.dist-info → supervisely-6.73.244.dist-info}/METADATA +3 -1
  53. {supervisely-6.73.242.dist-info → supervisely-6.73.244.dist-info}/RECORD +57 -35
  54. {supervisely-6.73.242.dist-info → supervisely-6.73.244.dist-info}/LICENSE +0 -0
  55. {supervisely-6.73.242.dist-info → supervisely-6.73.244.dist-info}/WHEEL +0 -0
  56. {supervisely-6.73.242.dist-info → supervisely-6.73.244.dist-info}/entry_points.txt +0 -0
  57. {supervisely-6.73.242.dist-info → supervisely-6.73.244.dist-info}/top_level.txt +0 -0
@@ -1,5 +1,6 @@
1
1
  from os.path import join
2
2
  from re import compile as re_compile
3
+ from typing import List
3
4
 
4
5
  from supervisely.nn.artifacts.artifacts import BaseTrainArtifacts
5
6
 
@@ -8,7 +9,7 @@ class YOLOv8(BaseTrainArtifacts):
8
9
  def __init__(self, team_id: int):
9
10
  super().__init__(team_id)
10
11
 
11
- self._app_name = "Train YOLOv8"
12
+ self._app_name = "Train YOLOv8 | v9 | v10 | v11"
12
13
  self._framework_folder = "/yolov8_train"
13
14
  self._weights_folder = "weights"
14
15
  self._task_type = None
@@ -17,6 +18,11 @@ class YOLOv8(BaseTrainArtifacts):
17
18
  self._pattern = re_compile(
18
19
  r"^/yolov8_train/(object detection|instance segmentation|pose estimation)/[^/]+/\d+/?$"
19
20
  )
21
+ self._available_task_types: List[str] = [
22
+ "object detection",
23
+ "instance segmentation",
24
+ "pose estimation",
25
+ ]
20
26
 
21
27
  def get_task_id(self, artifacts_folder: str) -> str:
22
28
  parts = artifacts_folder.split("/")
@@ -0,0 +1,113 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+ from json import JSONDecodeError
3
+ from os.path import dirname, join
4
+ from typing import Any, Dict, List, NamedTuple
5
+
6
+ import requests
7
+
8
+ from supervisely import logger
9
+ from supervisely.api.api import Api, ApiField
10
+
11
+
12
+ class ExperimentInfo(NamedTuple):
13
+ experiment_name: str
14
+ """Name of the experiment. Defined by the user in the training app"""
15
+ framework_name: str
16
+ """Name of the framework used in the experiment"""
17
+ model_name: str
18
+ """Name of the model used in the experiment. Defined by the user in the training app"""
19
+ task_type: str
20
+ """Task type of the experiment"""
21
+ project_id: int
22
+ """Project ID in Supervisely"""
23
+ task_id: int
24
+ """Task ID in Supervisely"""
25
+ model_files: Dict[str, str]
26
+ """Dictionary with paths to model files that needs to be downloaded for training"""
27
+ checkpoints: List[str]
28
+ """List of relative paths to checkpoints"""
29
+ best_checkpoint: str
30
+ """Name of the best checkpoint. Defined by the user in the training app"""
31
+ app_state: str
32
+ """Path to file with settings that were used in the app"""
33
+ model_meta: str
34
+ """Path to file with model metadata such as model name, project id, project name and classes used for training"""
35
+ train_val_split: str
36
+ """Path to train and validation splits, which contains IDs of the images used in each split"""
37
+ hyperparameters: str
38
+ """Path to .yaml file with hyperparameters used in the experiment"""
39
+ artifacts_dir: str
40
+ """Path to the directory with artifacts"""
41
+ datetime: str
42
+ """Date and time when the experiment was started"""
43
+ evaluation_report_id: int
44
+ """ID of the evaluation report"""
45
+ eval_metrics: Dict[str, Any]
46
+ """Evaluation metrics"""
47
+
48
+
49
+ def get_experiment_infos(api: Api, team_id: int, framework_name: str) -> List[ExperimentInfo]:
50
+ """
51
+ Get experiments from the specified framework folder for Train v2
52
+
53
+ :param api: Supervisely API client
54
+ :type api: Api
55
+ :param team_id: Team ID
56
+ :type team_id: int
57
+ :param framework_name: Name of the framework
58
+ :type framework_name: str
59
+ :return: List of ExperimentInfo objects
60
+ :rtype: List[ExperimentInfo]
61
+ :Usage example:
62
+
63
+ .. code-block:: python
64
+
65
+ import supervisely as sly
66
+
67
+ api = sly.Api.from_env()
68
+ team_id = sly.env.team_id()
69
+ framework_name = "rt-detr"
70
+ experiment_infos = sly.nn.training.experiments.get_experiment_infos(api, team_id, framework_name)
71
+ """
72
+ metadata_name = "experiment_info.json"
73
+ experiments_folder = "/experiments"
74
+ experiment_infos = []
75
+
76
+ file_infos = api.file.list(team_id, experiments_folder, recursive=True, return_type="fileinfo")
77
+ sorted_file_infos = []
78
+ for file_info in file_infos:
79
+ if not file_info.path.endswith(metadata_name):
80
+ continue
81
+
82
+ experiment_dir = dirname(file_info.path)
83
+ if experiment_dir.endswith(framework_name):
84
+ experiment_path = join(experiment_dir, metadata_name)
85
+ sorted_file_infos.append(experiment_path)
86
+
87
+ def fetch_experiment_data(file_info):
88
+ try:
89
+ response = api.post(
90
+ "file-storage.download",
91
+ {ApiField.TEAM_ID: team_id, ApiField.PATH: file_info},
92
+ stream=True,
93
+ )
94
+ response.raise_for_status()
95
+ 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
+ return None
102
+ return ExperimentInfo(**response_json)
103
+ except requests.exceptions.RequestException as e:
104
+ logger.debug(f"Failed to fetch train metadata from '{experiment_path}': {e}")
105
+ except JSONDecodeError as e:
106
+ logger.debug(f"Failed to decode JSON from '{experiment_path}': {e}")
107
+ return None
108
+
109
+ with ThreadPoolExecutor() as executor:
110
+ experiment_infos = list(executor.map(fetch_experiment_data, sorted_file_infos))
111
+
112
+ experiment_infos = [info for info in experiment_infos if info is not None]
113
+ return experiment_infos
@@ -1 +1,3 @@
1
- from supervisely.nn.inference.gui.gui import BaseInferenceGUI, InferenceGUI, ServingGUI
1
+ from supervisely.nn.inference.gui.gui import BaseInferenceGUI, InferenceGUI
2
+ from supervisely.nn.inference.gui.serving_gui import ServingGUI
3
+ from supervisely.nn.inference.gui.serving_gui_template import ServingGUITemplate
@@ -5,10 +5,9 @@
5
5
  from functools import wraps
6
6
  from typing import Callable, Dict, List, Optional, Union
7
7
 
8
- import yaml
9
-
10
8
  import supervisely.app.widgets as Widgets
11
9
  import supervisely.io.env as env
10
+ import yaml
12
11
  from supervisely import Api
13
12
  from supervisely._utils import abs_url, is_debug_with_sly_net, is_development
14
13
  from supervisely.api.file_api import FileApi
@@ -97,7 +96,9 @@ class InferenceGUI(BaseInferenceGUI):
97
96
  self._serve_button = Widgets.Button("SERVE")
98
97
  self._success_label = Widgets.DoneLabel()
99
98
  self._success_label.hide()
100
- self._download_progress = Widgets.Progress("Downloading model...", hide_on_finish=True)
99
+ self._download_progress = Widgets.Progress(
100
+ "Downloading model...", hide_on_finish=True
101
+ )
101
102
  self._download_progress.hide()
102
103
  self._change_model_button = Widgets.Button(
103
104
  "STOP AND CHOOSE ANOTHER MODEL", button_type="danger"
@@ -123,7 +124,9 @@ class InferenceGUI(BaseInferenceGUI):
123
124
  self._model_classes_widget = Widgets.ClassesTable(selectable=False)
124
125
  self._model_classes_plug = Widgets.Text("No classes provided")
125
126
  self._model_classes_widget_container = Widgets.Field(
126
- content=Widgets.Container([self._model_classes_widget, self._model_classes_plug]),
127
+ content=Widgets.Container(
128
+ [self._model_classes_widget, self._model_classes_plug]
129
+ ),
127
130
  title="Model classes",
128
131
  description="List of classes model predicts",
129
132
  )
@@ -152,7 +155,9 @@ class InferenceGUI(BaseInferenceGUI):
152
155
 
153
156
  self._model_full_info_card.collapse()
154
157
  self._additional_ui_content = []
155
- self.get_ui = self.__add_content_and_model_info_to_default_ui(self._model_full_info_card)
158
+ self.get_ui = self.__add_content_and_model_info_to_default_ui(
159
+ self._model_full_info_card
160
+ )
156
161
 
157
162
  tabs_titles = []
158
163
  tabs_contents = []
@@ -166,7 +171,9 @@ class InferenceGUI(BaseInferenceGUI):
166
171
  def update_table(selected_model):
167
172
  cols = [
168
173
  model_key
169
- for model_key in self._models[selected_model]["checkpoints"][0].keys()
174
+ for model_key in self._models[selected_model]["checkpoints"][
175
+ 0
176
+ ].keys()
170
177
  ]
171
178
  rows = [
172
179
  [value for param_name, value in model.items()]
@@ -255,7 +262,9 @@ class InferenceGUI(BaseInferenceGUI):
255
262
  custom_tab_content = Widgets.Container(custom_tab_widgets)
256
263
  tabs_titles.append("Custom models")
257
264
  tabs_contents.append(custom_tab_content)
258
- tabs_descriptions.append("Models trained in Supervisely and located in Team Files")
265
+ tabs_descriptions.append(
266
+ "Models trained in Supervisely and located in Team Files"
267
+ )
259
268
 
260
269
  self._tabs = Widgets.RadioTabs(
261
270
  titles=tabs_titles,
@@ -263,7 +272,9 @@ class InferenceGUI(BaseInferenceGUI):
263
272
  descriptions=tabs_descriptions,
264
273
  )
265
274
 
266
- self.on_change_model_callbacks: List[CallbackT] = [InferenceGUI._hide_info_after_change]
275
+ self.on_change_model_callbacks: List[CallbackT] = [
276
+ InferenceGUI._hide_info_after_change
277
+ ]
267
278
  self.on_serve_callbacks: List[CallbackT] = []
268
279
 
269
280
  @self.serve_button.click
@@ -334,7 +345,9 @@ class InferenceGUI(BaseInferenceGUI):
334
345
 
335
346
  table_subtitles, cols = self._get_table_subtitles(cols)
336
347
  if self._models_table is None:
337
- self._models_table = Widgets.RadioTable(cols, rows, subtitles=table_subtitles)
348
+ self._models_table = Widgets.RadioTable(
349
+ cols, rows, subtitles=table_subtitles
350
+ )
338
351
  else:
339
352
  self._models_table.set_data(cols, rows, subtitles=table_subtitles)
340
353
 
@@ -470,15 +483,21 @@ class InferenceGUI(BaseInferenceGUI):
470
483
  try:
471
484
  classes = inference.get_classes()
472
485
  except NotImplementedError:
473
- logger.warn(f"get_classes() function not implemented for {type(inference)} object.")
486
+ logger.warn(
487
+ f"get_classes() function not implemented for {type(inference)} object."
488
+ )
474
489
  except AttributeError:
475
- logger.warn("Probably, get_classes() function not working without model deploy.")
490
+ logger.warn(
491
+ "Probably, get_classes() function not working without model deploy."
492
+ )
476
493
  except Exception as exc:
477
494
  logger.warn("Skip getting classes info due to exception")
478
495
  logger.exception(exc)
479
496
 
480
497
  if classes is None or len(classes) == 0:
481
- logger.warn(f"get_classes() function return {classes}; skip classes processing.")
498
+ logger.warn(
499
+ f"get_classes() function return {classes}; skip classes processing."
500
+ )
482
501
  return None
483
502
  return classes
484
503
 
@@ -518,223 +537,3 @@ class InferenceGUI(BaseInferenceGUI):
518
537
  return wrapper
519
538
 
520
539
  return decorator(self.get_ui)
521
-
522
-
523
- class ServingGUI:
524
- def __init__(self) -> None:
525
- device_values = []
526
- device_names = []
527
- try:
528
- import torch
529
-
530
- if torch.cuda.is_available():
531
- gpus = torch.cuda.device_count()
532
- for i in range(gpus):
533
- device_values.append(f"cuda:{i}")
534
- device_names.append(f"{torch.cuda.get_device_name(i)} (cuda:{i})")
535
- except:
536
- pass
537
- device_values.append("cpu")
538
- device_names.append("CPU")
539
-
540
- self._device_select = Widgets.SelectString(
541
- values=device_values,
542
- labels=device_names,
543
- width_percent=30,
544
- )
545
- self._device_field = Widgets.Field(self._device_select, title="Device")
546
- self._serve_button = Widgets.Button("SERVE")
547
- self._success_label = Widgets.DoneLabel()
548
- self._success_label.hide()
549
- self._download_progress = Widgets.Progress("Downloading model...", hide_on_finish=True)
550
- self._download_progress.hide()
551
- self._change_model_button = Widgets.Button(
552
- "STOP AND CHOOSE ANOTHER MODEL", button_type="danger"
553
- )
554
- self._change_model_button.hide()
555
-
556
- self._model_inference_settings_widget = Widgets.Editor(
557
- readonly=True, restore_default_button=False
558
- )
559
- self._model_inference_settings_container = Widgets.Field(
560
- self._model_inference_settings_widget,
561
- title="Inference settings",
562
- description="Model allows user to configure the following parameters on prediction phase",
563
- )
564
-
565
- self._model_info_widget = Widgets.ModelInfo()
566
- self._model_info_widget_container = Widgets.Field(
567
- self._model_info_widget,
568
- title="Session Info",
569
- description="Basic information about the deployed model",
570
- )
571
-
572
- self._model_classes_widget = Widgets.ClassesTable(selectable=False)
573
- self._model_classes_plug = Widgets.Text("No classes provided")
574
- self._model_classes_widget_container = Widgets.Field(
575
- content=Widgets.Container([self._model_classes_widget, self._model_classes_plug]),
576
- title="Model classes",
577
- description="List of classes model predicts",
578
- )
579
-
580
- self._model_full_info = Widgets.Container(
581
- [
582
- self._model_info_widget_container,
583
- self._model_inference_settings_container,
584
- self._model_classes_widget_container,
585
- ]
586
- )
587
- self._model_full_info.hide()
588
- self._before_deploy_msg = Widgets.Text("Deploy model to see the information.")
589
-
590
- self._model_full_info_card = Widgets.Card(
591
- title="Full model info",
592
- description="Inference settings, session parameters and model classes",
593
- collapsable=True,
594
- content=Widgets.Container(
595
- [
596
- self._model_full_info,
597
- self._before_deploy_msg,
598
- ]
599
- ),
600
- )
601
-
602
- self._model_full_info_card.collapse()
603
- self._additional_ui_content = []
604
- self.get_ui = self.__add_content_and_model_info_to_default_ui(self._model_full_info_card)
605
-
606
- self.on_change_model_callbacks: List[CallbackT] = [ServingGUI._hide_info_after_change]
607
- self.on_serve_callbacks: List[CallbackT] = []
608
-
609
- @self.serve_button.click
610
- def serve_model():
611
- self.deploy_with_current_params()
612
-
613
- @self._change_model_button.click
614
- def change_model():
615
- for cb in self.on_change_model_callbacks:
616
- cb(self)
617
- self.change_model()
618
-
619
- def deploy_with_current_params(self):
620
- for cb in self.on_serve_callbacks:
621
- cb(self)
622
- self.set_deployed()
623
-
624
- def change_model(self):
625
- self._success_label.text = ""
626
- self._success_label.hide()
627
- self._serve_button.show()
628
- self._device_select.enable()
629
- self._change_model_button.hide()
630
- Progress("model deployment canceled", 1).iter_done_report()
631
-
632
- def _hide_info_after_change(self):
633
- self._model_full_info_card.collapse()
634
- self._model_full_info.hide()
635
- self._before_deploy_msg.show()
636
-
637
- def get_device(self) -> str:
638
- return self._device_select.get_value()
639
-
640
- @property
641
- def serve_button(self) -> Widgets.Button:
642
- return self._serve_button
643
-
644
- @property
645
- def download_progress(self) -> Widgets.Progress:
646
- return self._download_progress
647
-
648
- def set_deployed(self, device: str = None):
649
- if device is not None:
650
- self._device_select.set_value(device)
651
- self._success_label.text = f"Model has been successfully loaded on {self._device_select.get_value().upper()} device"
652
- self._success_label.show()
653
- self._serve_button.hide()
654
- self._device_select.disable()
655
- self._change_model_button.show()
656
- Progress("Model deployed", 1).iter_done_report()
657
-
658
- def show_deployed_model_info(self, inference):
659
- self.set_inference_settings(inference)
660
- self.set_project_meta(inference)
661
- self.set_model_info(inference)
662
- self._before_deploy_msg.hide()
663
- self._model_full_info.show()
664
- self._model_full_info_card.uncollapse()
665
-
666
- def set_inference_settings(self, inference):
667
- if len(inference.custom_inference_settings_dict.keys()) == 0:
668
- inference_settings_str = "# inference settings dict is empty"
669
- else:
670
- inference_settings_str = yaml.dump(inference.custom_inference_settings_dict)
671
- self._model_inference_settings_widget.set_text(inference_settings_str, "yaml")
672
- self._model_inference_settings_widget.show()
673
-
674
- def set_project_meta(self, inference):
675
- if self._get_classes_from_inference(inference) is None:
676
- logger.warn("Skip loading project meta.")
677
- self._model_classes_widget.hide()
678
- self._model_classes_plug.show()
679
- return
680
-
681
- self._model_classes_widget.set_project_meta(inference.model_meta)
682
- self._model_classes_plug.hide()
683
- self._model_classes_widget.show()
684
-
685
- def set_model_info(self, inference):
686
- info = inference.get_human_readable_info(replace_none_with="Not provided")
687
- self._model_info_widget.set_model_info(inference.task_id, info)
688
-
689
- def _get_classes_from_inference(self, inference) -> Optional[List[str]]:
690
- classes = None
691
- try:
692
- classes = inference.get_classes()
693
- except NotImplementedError:
694
- logger.warn(f"get_classes() function not implemented for {type(inference)} object.")
695
- except AttributeError:
696
- logger.warn("Probably, get_classes() function not working without model deploy.")
697
- except Exception as exc:
698
- logger.warn("Skip getting classes info due to exception")
699
- logger.exception(exc)
700
-
701
- if classes is None or len(classes) == 0:
702
- logger.warn(f"get_classes() function return {classes}; skip classes processing.")
703
- return None
704
- return classes
705
-
706
- def get_ui(self) -> Widgets.Widget:
707
- return Widgets.Container(
708
- [
709
- self._device_field,
710
- self._download_progress,
711
- self._success_label,
712
- self._serve_button,
713
- self._change_model_button,
714
- ],
715
- gap=3,
716
- )
717
-
718
- def add_content_to_default_ui(
719
- self, widgets: Union[Widgets.Widget, List[Widgets.Widget]]
720
- ) -> None:
721
- if isinstance(widgets, List):
722
- self._additional_ui_content.extend(widgets)
723
- else:
724
- self._additional_ui_content.append(widgets)
725
-
726
- def __add_content_and_model_info_to_default_ui(
727
- self,
728
- model_info_widget: Widgets.Widget,
729
- ) -> Callable:
730
- def decorator(get_ui):
731
- @wraps(get_ui)
732
- def wrapper(*args, **kwargs):
733
- ui = get_ui(*args, **kwargs)
734
- content = [ui, *self._additional_ui_content, model_info_widget]
735
- ui_with_info = Widgets.Container(content)
736
- return ui_with_info
737
-
738
- return wrapper
739
-
740
- return decorator(self.get_ui)