diff --git a/serve/config.json b/serve/config.json index cea7ea6..26e25ee 100644 --- a/serve/config.json +++ b/serve/config.json @@ -11,8 +11,8 @@ "serve" ], "description": "Deploy model as REST API service", - "docker_image": "supervisely/mmseg:1.3.12", - "instance_version": "6.9.22", + "docker_image": "supervisely/mmseg:1.3.13", + "instance_version": "6.11.10", "entrypoint": "python -m uvicorn main:m.app --app-dir ./serve/src --host 0.0.0.0 --port 8000 --ws websockets", "port": 8000, "task_location": "application_sessions", diff --git a/serve/dev_requirements.txt b/serve/dev_requirements.txt index 4bea572..cab2799 100644 --- a/serve/dev_requirements.txt +++ b/serve/dev_requirements.txt @@ -1,6 +1,6 @@ # git+https://github.com/supervisely/supervisely.git@some-test-branch -supervisely==6.73.119 +supervisely==6.73.178 openmim ffmpeg-python==0.2.0 diff --git a/serve/src/main.py b/serve/src/main.py index 2d7a4b1..6784aa1 100644 --- a/serve/src/main.py +++ b/serve/src/main.py @@ -33,6 +33,7 @@ Widget, ) from supervisely.io.fs import silent_remove +import workflow as w root_source_path = str(Path(__file__).parents[2]) app_source_path = str(Path(__file__).parents[1]) @@ -285,6 +286,8 @@ def load_on_device( ] self._model_meta = sly.ProjectMeta(obj_classes=sly.ObjClassCollection(obj_classes)) print(f"✅ Model has been successfully loaded on {device.upper()} device") + if model_source == "Custom models": + w.workflow_input(self.api, custom_weights_link) def get_info(self) -> dict: info = super().get_info() diff --git a/serve/src/workflow.py b/serve/src/workflow.py new file mode 100644 index 0000000..ce7e88a --- /dev/null +++ b/serve/src/workflow.py @@ -0,0 +1,20 @@ +# Description: This file contains functionality for workflow feature + +import supervisely as sly + + +def workflow_input(api: sly.Api, checkpoint_url: str): + try: + node_settings = sly.WorkflowSettings(title="Serve Custom Model") + meta = sly.WorkflowMeta(node_settings=node_settings) + sly.logger.debug(f"Workflow Input: Checkpoint URL - {checkpoint_url}") + if checkpoint_url and api.file.exists(sly.env.team_id(), checkpoint_url): + api.app.workflow.add_input_file(checkpoint_url, model_weight=True, meta=meta) + else: + sly.logger.debug(f"Checkpoint {checkpoint_url} not found in Team Files. Cannot set workflow input") + except Exception as e: + sly.logger.debug(f"Failed to add input to the workflow: {repr(e)}") + + +def workflow_output(api: sly.Api): + raise NotImplementedError("Method is not implemented yet") \ No newline at end of file diff --git a/train/config.json b/train/config.json index 15e3303..16b05bc 100644 --- a/train/config.json +++ b/train/config.json @@ -1,33 +1,33 @@ { - "name": "Train MMSegmentation", - "type": "app", - "categories": [ - "neural network", - "images", - "videos", - "semantic segmentation", - "segmentation & tracking", - "train" - ], - "description": "Dashboard to configure, start and monitor training", - "docker_image": "supervisely/mmseg:1.3.12", - "min_instance_version": "6.9.22", - "main_script": "train/src/main.py", - "gui_template": "train/src/gui.html", - "task_location": "workspace_tasks", - "need_gpu": true, - "gpu": "required", - "isolate": true, - "icon": "https://i.imgur.com/GaEFmkH.png", - "icon_cover": true, - "context_menu": { - "target": ["images_project"], - "context_root": "Neural Networks", - "context_category": "MM Segmentation" - }, - "poster": "https://user-images.githubusercontent.com/48245050/182847473-9a35f213-c27b-4abd-bd64-c73bf80fb056.jpg", - "community_agent": false, - "license": { - "type": "Apache-2.0" - } + "name": "Train MMSegmentation", + "type": "app", + "categories": [ + "neural network", + "images", + "videos", + "semantic segmentation", + "segmentation & tracking", + "train" + ], + "description": "Dashboard to configure, start and monitor training", + "docker_image": "supervisely/mmseg:1.3.13", + "min_instance_version": "6.11.10", + "main_script": "train/src/main.py", + "gui_template": "train/src/gui.html", + "task_location": "workspace_tasks", + "need_gpu": true, + "gpu": "required", + "isolate": true, + "icon": "https://i.imgur.com/GaEFmkH.png", + "icon_cover": true, + "context_menu": { + "target": ["images_project"], + "context_root": "Neural Networks", + "context_category": "MM Segmentation" + }, + "poster": "https://user-images.githubusercontent.com/48245050/182847473-9a35f213-c27b-4abd-bd64-c73bf80fb056.jpg", + "community_agent": false, + "license": { + "type": "Apache-2.0" + } } diff --git a/train/dev_requirements.txt b/train/dev_requirements.txt index 4bea572..cab2799 100644 --- a/train/dev_requirements.txt +++ b/train/dev_requirements.txt @@ -1,6 +1,6 @@ # git+https://github.com/supervisely/supervisely.git@some-test-branch -supervisely==6.73.119 +supervisely==6.73.178 openmim ffmpeg-python==0.2.0 diff --git a/train/src/sly_globals.py b/train/src/sly_globals.py index 32036cd..dc0d1f4 100644 --- a/train/src/sly_globals.py +++ b/train/src/sly_globals.py @@ -62,3 +62,5 @@ shutil.copytree(f"/tmp/mmseg/mmsegmentation-{mmseg_ver}/configs", configs_dir) models_cnt = len(os.listdir(configs_dir)) - 1 sly.logger.info(f"Found {models_cnt} folders in {configs_dir} directory.") + +sly_mmseg_generated_metadata = None # for project Workflow purposes \ No newline at end of file diff --git a/train/src/ui/monitoring.py b/train/src/ui/monitoring.py index edf3c64..09aa02e 100644 --- a/train/src/ui/monitoring.py +++ b/train/src/ui/monitoring.py @@ -12,6 +12,7 @@ from mmseg.datasets import build_dataset from mmseg.models import build_segmentor from init_cfg import init_cfg +import workflow as w # ! required to be left here despite not being used import sly_imgaugs @@ -216,7 +217,7 @@ def upload_monitor(monitor, api: sly.Api, task_id, progress: sly.Progress): ) # generate metadata file - g.sly_mmseg.generate_metadata( + g.sly_mmseg_generated_metadata = g.sly_mmseg.generate_metadata( app_name=g.sly_mmseg.app_name, task_id=g.task_id, artifacts_folder=remote_artifacts_dir, @@ -347,7 +348,10 @@ def train(api: sly.Api, task_id, context, state, app_logger): ] g.api.app.set_fields(g.task_id, fields) - # stop application + w.workflow_input(api, g.project_info, state) + w.workflow_output(api, g.sly_mmseg_generated_metadata, state) + + # stop application g.my_app.stop() except Exception as e: g.api.app.set_field(task_id, "state.started", False) diff --git a/train/src/workflow.py b/train/src/workflow.py new file mode 100644 index 0000000..7e3e7db --- /dev/null +++ b/train/src/workflow.py @@ -0,0 +1,81 @@ +# Description: This file contains versioning and workflow features + +import supervisely as sly + +from supervisely.api.file_api import FileInfo + +def workflow_input(api: sly.Api, project_info: sly.ProjectInfo, state: dict = None): + try: + if project_info.type != sly.ProjectType.IMAGES.__str__(): + sly.logger.info(f"{project_info.type =} is not '{sly.ProjectType.IMAGES.__str__()}'. Project version will not be created.") + project_version_id = None + else: + project_version_id = api.project.version.create( + project_info, "Train MMSegmentation", f"This backup was created automatically by Supervisely before the Train MMSegmentation task with ID: {api.task_id}" + ) + except Exception as e: + sly.logger.warning(f"Failed to create a project version: {repr(e)}") + project_version_id = None + + try: + file_info = None + if project_version_id is None: + project_version_id = project_info.version.get("id", None) if project_info.version else None + api.app.workflow.add_input_project(project_info.id, version_id=project_version_id) + if state.get("weightsInitialization", None) == "custom": + file_path = state.get("weightsPath", None) + if file_path is None: + sly.logger.debug("Workflow Input: weights file path is not specified. Cannot add input file to the workflow.") + file_info = api.file.get_info_by_path(sly.env.team_id(), file_path) + if file_info is not None: + api.app.workflow.add_input_file(file_info, model_weight=True) + sly.logger.debug(f"Workflow Input: Project ID - {project_info.id}, Project Version ID - {project_version_id}, Input File - {True if file_info else False}") + except Exception as e: + sly.logger.debug(f"Failed to add input to the workflow: {repr(e)}") + + +def workflow_output(api: sly.Api, mmseg_generated_metadata: dict, state:dict): + try: + checkpoints_list = mmseg_generated_metadata.get("checkpoints", []) + if len(checkpoints_list) == 0: + sly.logger.debug("Workflow Output: No checkpoints found. Cannot set workflow output.") + best_filename_info = None + else: + best_checkpoints = [] + new_infos = [FileInfo(*checkpoint) for checkpoint in checkpoints_list] + for info in new_infos: + best_checkpoints.append(info) if "best" in info.name else None + if len(best_checkpoints) > 1: + best_filename_info = sorted(best_checkpoints, key=lambda x: x.name, reverse=True)[0] + else: + best_filename_info = best_checkpoints[0] + + + module_id = api.task.get_info_by_id(api.task_id).get("meta", {}).get("app", {}).get("id") + + if state.get("weightsInitialization", None) == "custom": + node_custom_title = "Train Custom Model" + else: + node_custom_title = None + if best_filename_info: + node_settings = sly.WorkflowSettings( + title=node_custom_title, + url = f"/apps/{module_id}/sessions/{api.task_id}" if module_id else f"apps/sessions/{api.task_id}", + url_title= "Show Results", + ) + relation_settings = sly.WorkflowSettings( + title="Checkpoints", + icon="folder", + icon_color = "#FFA500", + icon_bg_color ="#FFE8BE", + url=f"/files/{best_filename_info.id}/true", + url_title = "Open Folder", + ) + meta = sly.WorkflowMeta(relation_settings, node_settings) + api.app.workflow.add_output_file(best_filename_info, model_weight=True, meta=meta) + sly.logger.debug(f"Workflow Output: Node custom title - {node_custom_title}, Best filename - {best_filename_info}") + sly.logger.debug(f"Workflow Output: Meta \n {meta.as_dict}") + else: + sly.logger.debug(f"File {best_filename_info} not found in Team Files. Cannot set workflow output.") + except Exception as e: + sly.logger.debug(f"Failed to add output to the workflow: {repr(e)}") \ No newline at end of file