Source code for datumaro.cli.contexts.model

# Copyright (C) 2019-2023 Intel Corporation
#
# SPDX-License-Identifier: MIT

import argparse
import logging as log
import os
import os.path as osp

from datumaro.components.errors import ProjectNotFoundError
from datumaro.components.project import Environment
from datumaro.util.os_util import rmtree
from datumaro.util.scope import on_error_do, scope_add, scoped

from ..util import MultilineFormatter, add_subparser
from ..util.errors import CliException
from ..util.project import (
    generate_next_file_name,
    generate_next_name,
    load_project,
    parse_full_revpath,
)


[docs] def build_add_parser(parser_ctor=argparse.ArgumentParser): builtins = sorted(Environment().launchers) parser = parser_ctor( help="Add model to project", description=""" Adds an executable model into a project. A model requires a launcher to be executed. Each launcher has its own options, which are passed after the '--' separator, pass '-- -h' for more info. |n List of builtin launchers: {}|n |n Examples:|n - Add an OpenVINO model into a project:|n |s|s%(prog)s -l openvino -- -d model.xml -w model.bin -i parse_outs.py """.format( ", ".join(builtins) ), formatter_class=MultilineFormatter, ) parser.add_argument( "-n", "--name", default=None, help="Name of the model to be added (default: generate automatically)", ) parser.add_argument("-l", "--launcher", required=True, help="Model launcher") parser.add_argument( "--copy", action="store_true", help="Copy model data into project (default: %(default)s)" ) parser.add_argument( "--no-check", action="store_true", help="Don't check model loading (default: %(default)s)" ) parser.add_argument( "-p", "--project", dest="project_dir", help="Directory of the project to operate on (default: current dir)", ) parser.add_argument( "extra_args", nargs=argparse.REMAINDER, default=None, help="Additional arguments for exporter (pass '-- -h' for help)", ) parser.set_defaults(command=add_command) return parser
[docs] def get_add_sensitive_args(): return { add_command: ["project_dir", "name", "extra_args"], }
[docs] @scoped def add_command(args): show_plugin_help = "-h" in args.extra_args or "--help" in args.extra_args project = None try: project = scope_add(load_project(args.project_dir)) except ProjectNotFoundError: if not show_plugin_help and args.project_dir: raise if project is not None: env = project.env else: env = Environment() name = args.name if name: if name in project.models: raise CliException("Model '%s' already exists" % name) else: name = generate_next_name(list(project.models), "model", sep="-", default=0) try: launcher = env.launchers[args.launcher] except KeyError: raise CliException("Launcher '%s' is not found" % args.launcher) cli_plugin = getattr(launcher, "cli_plugin", launcher) model_args = cli_plugin.parse_cmdline(args.extra_args) if args.copy: log.info("Copying model data") model_dir = project.model_data_dir(name) os.makedirs(model_dir, exist_ok=False) on_error_do(rmtree, model_dir, ignore_errors=True) try: cli_plugin.copy_model(model_dir, model_args) except (AttributeError, NotImplementedError): raise NotImplementedError( "Can't copy: copying is not available for '%s' models. " % args.launcher ) project.add_model(name, launcher=args.launcher, options=model_args) on_error_do(project.remove_model, name, ignore_errors=True) if not args.no_check: log.info("Checking the model...") project.make_model(name) project.save() log.info("Model '%s' with launcher '%s' has been added to project", name, args.launcher) return 0
[docs] def build_remove_parser(parser_ctor=argparse.ArgumentParser): parser = parser_ctor( help="Remove model from project", description="Remove a model from a project" ) parser.add_argument("name", help="Name of the model to be removed") parser.add_argument( "-p", "--project", dest="project_dir", help="Directory of the project to operate on (default: current dir)", ) parser.set_defaults(command=remove_command) return parser
[docs] def get_remove_sensitive_args(): return { remove_command: [ "project_dir", "name", ], }
[docs] @scoped def remove_command(args): project = scope_add(load_project(args.project_dir)) project.remove_model(args.name) project.save() log.info(f"Model {args.name} has been removed from the project.") return 0
[docs] def build_run_parser(parser_ctor=argparse.ArgumentParser): parser = parser_ctor( help="Launches model inference", description=""" Launches model inference on a dataset.|n |n Target dataset is specified by a revpath. The full syntax is:|n - Dataset paths:|n |s|s- <dataset path>[ :<format> ]|n - Revision paths:|n |s|s- <project path> [ @<rev> ] [ :<target> ]|n |s|s- <rev> [ :<target> ]|n |s|s- <target>|n |n Both forms use the -p/--project as a context for plugins and models. When not specified, the current project's working tree is used.|n """, formatter_class=MultilineFormatter, ) parser.add_argument( "target", nargs="?", default="project", help="Target dataset revpath (default: %(default)s)" ) parser.add_argument( "-o", "--output-dir", dest="dst_dir", help="Directory to save output (default: auto-generated)", ) parser.add_argument( "-m", "--model", dest="model_name", required=True, help="Model to apply to the project" ) parser.add_argument( "-p", "--project", dest="project_dir", help="Directory of the project to operate on (default: current dir)", ) parser.add_argument( "--overwrite", action="store_true", help="Overwrite output directory if exists" ) parser.set_defaults(command=run_command) return parser
[docs] def get_run_sensitive_args(): return { run_command: [ "target", "dst_dir", "model_name", "project_dir", ], }
[docs] @scoped def run_command(args): dst_dir = args.dst_dir if dst_dir: if not args.overwrite and osp.isdir(dst_dir) and os.listdir(dst_dir): raise CliException( "Directory '%s' already exists " "(pass --overwrite overwrite)" % dst_dir ) else: dst_dir = generate_next_file_name("%s-inference" % args.model_name) dst_dir = osp.abspath(dst_dir) project = scope_add(load_project(args.project_dir)) dataset, target_project = parse_full_revpath(args.target, project) if target_project: scope_add(target_project) model = project.make_model(args.model_name) inference = dataset.run_model(model) inference.save(dst_dir) log.info("Inference results have been saved to '%s'" % dst_dir) return 0
[docs] def build_info_parser(parser_ctor=argparse.ArgumentParser): parser = parser_ctor( help="Print model information in this project", description="Print model information in this project", formatter_class=MultilineFormatter, ) parser.add_argument("-n", "--name", help="Model name") parser.add_argument("-v", "--verbose", action="store_true", help="Show details") parser.add_argument( "-p", "--project", dest="project_dir", help="Directory of the project to operate on (default: current dir)", ) parser.set_defaults(command=info_command) return parser
[docs] def get_info_sensitive_args(): return { info_command: [ "name", "project_dir", ], }
[docs] @scoped def info_command(args): project = scope_add(load_project(args.project_dir)) if args.name: print(project.models[args.name]) else: for name, conf in project.models.items(): print(name) if args.verbose: print(dict(conf))
[docs] def build_parser(parser_ctor=argparse.ArgumentParser): parser = parser_ctor() subparsers = parser.add_subparsers() add_subparser(subparsers, "add", build_add_parser) add_subparser(subparsers, "remove", build_remove_parser) add_subparser(subparsers, "run", build_run_parser) add_subparser(subparsers, "info", build_info_parser) return parser
[docs] def get_sensitive_args(): return { **get_add_sensitive_args(), **get_remove_sensitive_args(), **get_run_sensitive_args(), **get_info_sensitive_args(), }