diff --git a/label_studio_ml/examples/yolonas/.env b/label_studio_ml/examples/yolonas/.env new file mode 100644 index 000000000..bdb9d9f1d --- /dev/null +++ b/label_studio_ml/examples/yolonas/.env @@ -0,0 +1,12 @@ +CHECKPOINT_FILE="/home/testuser/app/model.pth" +PORT=9090 +YOLO_LABELS=/home/testuser/app/labels.txt +IOU_THRESHOLD=0.25 +SCORE_THRESHOLD=0.4 +IMG_SIZE=1280 +DEVICE=cpu +ENDPOINT_URL= +AWS_ACCESS_KEY_ID=minio +AWS_SECRET_ACCESS_KEY= +LABEL_STUDIO_HOSTNAME= +YOLO_MODEL_TYPE=yolo_nas_m \ No newline at end of file diff --git a/label_studio_ml/examples/yolonas/Dockerfile b/label_studio_ml/examples/yolonas/Dockerfile new file mode 100644 index 000000000..9dd7bde9a --- /dev/null +++ b/label_studio_ml/examples/yolonas/Dockerfile @@ -0,0 +1,5 @@ +FROM bodbe/yolonas +USER testuser +WORKDIR /home/testuser/app +COPY _wsgi.py ./ +COPY model.py ./ \ No newline at end of file diff --git a/label_studio_ml/examples/yolonas/Dockerfile.full b/label_studio_ml/examples/yolonas/Dockerfile.full new file mode 100644 index 000000000..bf5079e79 --- /dev/null +++ b/label_studio_ml/examples/yolonas/Dockerfile.full @@ -0,0 +1,63 @@ +FROM nvidia/cuda:11.2.2-cudnn8-devel-ubuntu18.04 + +ENV TZ 'Europe/Moscow' +RUN echo $TZ > /etc/timezone + +RUN apt-get update && apt-get install -y locales sudo +RUN sed -i -e 's/# ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/' /etc/locale.gen && \ + dpkg-reconfigure --frontend=noninteractive locales && \ + update-locale LANG=ru_RU.UTF-8 +ENV LANG ru_RU.UTF-8 +ENV LANGUAGE ru_RU +ENV LC_ALL ru_RU.UTF-8 + +RUN apt-get update && apt-get install -y python3.8 python3.8-dev python3-pip git +#RUN apt-get upgrade python3-pip +RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 1 + +RUN apt-get install -y ffmpeg libsm6 libxext6 libgl1-mesa-glx libgl1 +RUN apt-get install -y wget nano sudo +RUN wget https://bootstrap.pypa.io/get-pip.py && python3.8 get-pip.py +RUN apt-get clean + +RUN useradd -ms /bin/bash testuser && \ + echo "testuser:testuser" | chpasswd && \ + usermod -aG sudo testuser && \ + chmod 777 -R /root + +WORKDIR /home/testuser + +USER testuser +ENV PATH="/home/testuser/.local/bin:${PATH}" + +RUN mkdir -p /home/testuser/.jupyter/lab/user-settings/@jupyterlab/terminal-extension +COPY --chown=testuser plugin.jupyterlab-settings /home/testuser/.jupyter/lab/user-settings/@jupyterlab/terminal-extension/ +COPY --chown=testuser jupyter_lab_config.py /home/testuser/.jupyter/ + +RUN openssl req -x509 -sha256 -nodes -days 3650 -newkey rsa:4096 -keyout .jupyter/jupyter.key -out .jupyter/jupyter.pem \ + -subj "/C=RU/ST=Uranopolis/L=Karyes/O=Space/OU=DS/CN=agion.oros" + +RUN python3.8 -m pip install nvidia-pyindex +RUN python3.8 -m pip install pytorch-quantization==2.1.2 +COPY requirements.txt . +RUN python3.8 -m pip install -U -r requirements.txt +RUN python3.8 -m pip install super-gradients==3.1.2 + +############ +## add-ons for late package installs +############ +#RUN python3.8 -m pip install redis rq label-studio-ml + +# setting up token string +# you must run the build with command +# docker build --build-arg token_string= . -t torch_custom +ARG token_string +ENV JUPYTER_TOKEN=${token_string} + +ENV SHELL="/bin/bash" + +RUN mkdir /home/testuser/.clearml && mkdir /home/testuser/.ssh +RUN ln -s /usr/bin/python3 ~/.local/bin/python +WORKDIR /home/testuser + +CMD ["jupyter", "lab"] diff --git a/label_studio_ml/examples/yolonas/README.md b/label_studio_ml/examples/yolonas/README.md new file mode 100644 index 000000000..e72bea7bc --- /dev/null +++ b/label_studio_ml/examples/yolonas/README.md @@ -0,0 +1,46 @@ +# YOLONAS ML Backend for Label Studio + +## Intro +Use Deci AI [YOLONAS](https://github.com/Deci-AI/super-gradients/blob/master/YOLONAS.md) model with Label Studio. + +## Setup +### 0. Important things to note + - This ML backend is designed to work in **docker**. You can run in on host but this manual does not cover that. + - There is no easy way to run ML backend with GPU support - you get an error `RuntimeError: Cannot re-initialize CUDA in forked subprocess. To use CUDA with multiprocessing, you must use the 'spawn' start method` + - Single image inference on CPU takes about a second. + - Base docker image has a `jupyter lab` command. So you can comment a `command` in `docker-compose.yml`, uncomment `8888:8888` for port mapping and use it as jupyter lab with password `change-me` over https. + - Main tested scenario has been for s3 cloud storage with custom endpoint url. Other storage options are not guaranteed to work. +### 1. Clone this repo +### 2. Get model weights +### 3. Adjust variables +Adjust these variables in `.env` file. +``` +CHECKPOINT_FILE="/home/testuser/app/model.pth" +PORT=9090 +YOLO_LABELS=/home/testuser/app/labels.txt +IOU_THRESHOLD=0.25 +SCORE_THRESHOLD=0.4 +IMG_SIZE=1280 +DEVICE=cpu +ENDPOINT_URL= +AWS_ACCESS_KEY_ID=minio +AWS_SECRET_ACCESS_KEY= +LABEL_STUDIO_HOSTNAME= +YOLO_MODEL_TYPE=yolo_nas_m +``` + +`YOLO_LABELS=/home/testuser/app/labels.txt` file with labels - each label on new line. +Labels should be the same in labeling interface and in this file. +If yolo labels differ you need to provide `LABELS_FILE` variable with mapping from Label studio label to yolo label like `{"airplane": "Boeing"}` + +### 4. Build docker image +Run `docker compose build` to build an image. +Base image `bodbe/yolonas` is built with [Dockerfile.full](Dockerfile.full). + +### 5. Run ML Backend +Run `docker compose up -d` + +### 6. How to run on GPU + - Update DEVICE variable in `.env` file to `cuda:0` + - Uncomment `deploy` section in `docker-compose.yml` + - Change `command` section in `docker-compose.yml` to `bash -c "python _wsgi.py" diff --git a/label_studio_ml/examples/yolonas/_wsgi.py b/label_studio_ml/examples/yolonas/_wsgi.py new file mode 100644 index 000000000..524a0d24d --- /dev/null +++ b/label_studio_ml/examples/yolonas/_wsgi.py @@ -0,0 +1,126 @@ +import os +import argparse +import logging +import logging.config + +logging.config.dictConfig({ + "version": 1, + "formatters": { + "standard": { + "format": "[%(asctime)s] [%(levelname)s] [%(name)s::%(funcName)s::%(lineno)d] %(message)s" + } + }, + "handlers": { + "console": { + "class": "logging.StreamHandler", + "level": "DEBUG", + "stream": "ext://sys.stdout", + "formatter": "standard" + } + }, + "root": { + "level": "ERROR", + "handlers": [ + "console" + ], + "propagate": True + } +}) + +from label_studio_ml.api import init_app +from model import ObjectDetectorModel + + +_DEFAULT_CONFIG_PATH = os.path.join(os.path.dirname(__file__), 'config.json') + + +def get_kwargs_from_config(config_path=_DEFAULT_CONFIG_PATH): + if not os.path.exists(config_path): + return dict() + with open(config_path) as f: + config = json.load(f) + assert isinstance(config, dict) + return config + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Label studio') + parser.add_argument( + '-p', '--port', dest='port', type=int, default=9090, + help='Server port') + parser.add_argument( + '--host', dest='host', type=str, default='0.0.0.0', + help='Server host') + parser.add_argument( + '--kwargs', '--with', dest='kwargs', metavar='KEY=VAL', nargs='+', type=lambda kv: kv.split('='), + help='Additional LabelStudioMLBase model initialization kwargs') + parser.add_argument( + '-d', '--debug', dest='debug', action='store_true', + help='Switch debug mode') + parser.add_argument( + '--log-level', dest='log_level', choices=['DEBUG', 'INFO', 'WARNING', 'ERROR'], default=None, + help='Logging level') + parser.add_argument( + '--model-dir', dest='model_dir', default=os.path.dirname(__file__), + help='Directory where models are stored (relative to the project directory)') + parser.add_argument( + '--check', dest='check', action='store_true', + help='Validate model instance before launching server') + + args = parser.parse_args() + + # setup logging level + if args.log_level: + logging.root.setLevel(args.log_level) + + def isfloat(value): + try: + float(value) + return True + except ValueError: + return False + + def parse_kwargs(): + param = dict() + for k, v in args.kwargs: + if v.isdigit(): + param[k] = int(v) + elif v == 'True' or v == 'true': + param[k] = True + elif v == 'False' or v == 'False': + param[k] = False + elif isfloat(v): + param[k] = float(v) + else: + param[k] = v + return param + + kwargs = get_kwargs_from_config() + + if args.kwargs: + kwargs.update(parse_kwargs()) + + if args.check: + print('Check "' + ObjectDetectorModel.__name__ + '" instance creation..') + model = ObjectDetectorModel(**kwargs) + + app = init_app( + model_class=ObjectDetectorModel, + model_dir=os.environ.get('MODEL_DIR', args.model_dir), + redis_queue=os.environ.get('RQ_QUEUE_NAME', 'default'), + redis_host=os.environ.get('REDIS_HOST', 'localhost'), + redis_port=os.environ.get('REDIS_PORT', 6379), + **kwargs + ) + + app.run(host=args.host, port=args.port, debug=args.debug) + +else: + # for uWSGI use + app = init_app( + model_class=ObjectDetectorModel, + model_dir=os.environ.get('MODEL_DIR', os.path.dirname(__file__)), + redis_queue=os.environ.get('RQ_QUEUE_NAME', 'default'), + redis_host=os.environ.get('REDIS_HOST', 'localhost'), + redis_port=os.environ.get('REDIS_PORT', 6379) + ) \ No newline at end of file diff --git a/label_studio_ml/examples/yolonas/docker-compose.yml b/label_studio_ml/examples/yolonas/docker-compose.yml new file mode 100644 index 000000000..381cff63a --- /dev/null +++ b/label_studio_ml/examples/yolonas/docker-compose.yml @@ -0,0 +1,57 @@ +version: "3.8" + +services: + redis: + image: redis:alpine + container_name: redis + hostname: redis + volumes: + - "./data/redis:/data" + expose: + - 6379 + server: + build: . + container_name: server + environment: +# file with checkpoint from yolo nas + - CHECKPOINT_FILE=${CHECKPOINT_FILE:-/home/testuser/app/model.pth} +# file with labels - each label on new line +# labels should be the same in labeling interface and in this file +# otherwise need to provide LABELS_FILE variable with mapping from +# Label studio label to Yolo label like {"airplane": "Boeing"} + - YOLO_LABELS=${YOLO_LABELS:-/home/testuser/app/labels.txt} + - LABELS_FILE=${LABELS_FILE:-} + - IOU_THRESHOLD=${IOU_THRESHOLD:-0.25} + - SCORE_THRESHOLD=${SCORE_THRESHOLD:-0.45} +# target resolution to feed to yolo + - IMG_SIZE=${IMG_SIZE:-1280} + - DEVICE=${DEVICE:-cpu} +# custom endpoint url for s3 storage that holds files in Label Studio + - ENDPOINT_URL=${ENDPOINT_URL:-} + - AWS_ACCESS_KEY_ID=${AWS_ACCESS_KEY_ID:-} + - AWS_SECRET_ACCESS_KEY=${AWS_SECRET_ACCESS_KEY} + - LABEL_STUDIO_HOSTNAME=${LABEL_STUDIO_HOSTNAME} +# yolo model specification + - YOLO_MODEL_TYPE=${YOLO_MODEL_TYPE:-yolo_nas_m} + - RQ_QUEUE_NAME=default + - REDIS_HOST=redis + - REDIS_PORT=6379 + - MODEL_DIR=./ + ports: +# uncomment to use jupyter lab +# - 8888:8888 + - ${PORT}:${PORT:-9090} + restart: always + volumes: + - ./model.pth:${CHECKPOINT_FILE} + - ./labels.txt:${YOLO_LABELS} + command: bash -c "exec gunicorn --preload --bind :${PORT} --workers 1 --threads 8 --timeout 0 _wsgi:app" +# uncomment if you wand a GPU access inside your docker +# command: bash -c "python _wsgi.py" +# deploy: +# resources: +# reservations: +# devices: +# - driver: nvidia +# device_ids: [ '0'] +# capabilities: [ gpu ] diff --git a/label_studio_ml/examples/yolonas/jupyter_lab_config.py b/label_studio_ml/examples/yolonas/jupyter_lab_config.py new file mode 100644 index 000000000..39b9c3bba --- /dev/null +++ b/label_studio_ml/examples/yolonas/jupyter_lab_config.py @@ -0,0 +1,996 @@ +# Configuration file for lab. + +#------------------------------------------------------------------------------ +# Application(SingletonConfigurable) configuration +#------------------------------------------------------------------------------ +## This is an application. + +## The date format used by logging formatters for %(asctime)s +# Default: '%Y-%m-%d %H:%M:%S' +# c.Application.log_datefmt = '%Y-%m-%d %H:%M:%S' + +## The Logging format template +# Default: '[%(name)s]%(highlevel)s %(message)s' +# c.Application.log_format = '[%(name)s]%(highlevel)s %(message)s' + +## Set the log level by value or name. +# Choices: any of [0, 10, 20, 30, 40, 50, 'DEBUG', 'INFO', 'WARN', 'ERROR', 'CRITICAL'] +# Default: 30 +# c.Application.log_level = 30 + +## Instead of starting the Application, dump configuration to stdout +# Default: False +# c.Application.show_config = False + +## Instead of starting the Application, dump configuration to stdout (as JSON) +# Default: False +# c.Application.show_config_json = False + +#------------------------------------------------------------------------------ +# JupyterApp(Application) configuration +#------------------------------------------------------------------------------ +## Base class for Jupyter applications + +## Answer yes to any prompts. +# Default: False +# c.JupyterApp.answer_yes = False + +## Full path of a config file. +# Default: '' +# c.JupyterApp.config_file = '' + +## Specify a config file to load. +# Default: '' +# c.JupyterApp.config_file_name = '' + +## Generate default config file. +# Default: False +# c.JupyterApp.generate_config = False + +## The date format used by logging formatters for %(asctime)s +# See also: Application.log_datefmt +# c.JupyterApp.log_datefmt = '%Y-%m-%d %H:%M:%S' + +## The Logging format template +# See also: Application.log_format +# c.JupyterApp.log_format = '[%(name)s]%(highlevel)s %(message)s' + +## Set the log level by value or name. +# See also: Application.log_level +# c.JupyterApp.log_level = 30 + +## Instead of starting the Application, dump configuration to stdout +# See also: Application.show_config +# c.JupyterApp.show_config = False + +## Instead of starting the Application, dump configuration to stdout (as JSON) +# See also: Application.show_config_json +# c.JupyterApp.show_config_json = False + +#------------------------------------------------------------------------------ +# ExtensionApp(JupyterApp) configuration +#------------------------------------------------------------------------------ +## Base class for configurable Jupyter Server Extension Applications. +# +# ExtensionApp subclasses can be initialized two ways: +# 1. Extension is listed as a jpserver_extension, and ServerApp calls +# its load_jupyter_server_extension classmethod. This is the +# classic way of loading a server extension. +# 2. Extension is launched directly by calling its `launch_instance` +# class method. This method can be set as a entry_point in +# the extensions setup.py + +## Answer yes to any prompts. +# See also: JupyterApp.answer_yes +# c.ExtensionApp.answer_yes = False + +## Full path of a config file. +# See also: JupyterApp.config_file +# c.ExtensionApp.config_file = '' + +## Specify a config file to load. +# See also: JupyterApp.config_file_name +# c.ExtensionApp.config_file_name = '' + +# Default: '' +# c.ExtensionApp.default_url = '' + +## Generate default config file. +# See also: JupyterApp.generate_config +# c.ExtensionApp.generate_config = False + +## Handlers appended to the server. +# Default: [] +# c.ExtensionApp.handlers = [] + +## The date format used by logging formatters for %(asctime)s +# See also: Application.log_datefmt +# c.ExtensionApp.log_datefmt = '%Y-%m-%d %H:%M:%S' + +## The Logging format template +# See also: Application.log_format +# c.ExtensionApp.log_format = '[%(name)s]%(highlevel)s %(message)s' + +## Set the log level by value or name. +# See also: Application.log_level +# c.ExtensionApp.log_level = 30 + +## Whether to open in a browser after starting. The specific browser used is +# platform dependent and determined by the python standard library `webbrowser` +# module, unless it is overridden using the --browser (ServerApp.browser) +# configuration option. +# Default: False +# c.ExtensionApp.open_browser = False + +## Settings that will passed to the server. +# Default: {} +# c.ExtensionApp.settings = {} + +## Instead of starting the Application, dump configuration to stdout +# See also: Application.show_config +# c.ExtensionApp.show_config = False + +## Instead of starting the Application, dump configuration to stdout (as JSON) +# See also: Application.show_config_json +# c.ExtensionApp.show_config_json = False + +## paths to search for serving static files. +# +# This allows adding javascript/css to be available from the notebook server +# machine, or overriding individual files in the IPython +# Default: [] +# c.ExtensionApp.static_paths = [] + +## Url where the static assets for the extension are served. +# Default: '' +# c.ExtensionApp.static_url_prefix = '' + +## Paths to search for serving jinja templates. +# +# Can be used to override templates from notebook.templates. +# Default: [] +# c.ExtensionApp.template_paths = [] + +#------------------------------------------------------------------------------ +# LabServerApp(ExtensionApp) configuration +#------------------------------------------------------------------------------ +## A Lab Server Application that runs out-of-the-box + +## "A list of comma-separated URIs to get the allowed extensions list +# +# .. versionchanged:: 2.0.0 +# `LabServerApp.whitetlist_uris` renamed to `allowed_extensions_uris` +# Default: '' +# c.LabServerApp.allowed_extensions_uris = '' + +## Answer yes to any prompts. +# See also: JupyterApp.answer_yes +# c.LabServerApp.answer_yes = False + +## The application settings directory. +# Default: '' +# c.LabServerApp.app_settings_dir = '' + +## The url path for the application. +# Default: '/lab' +# c.LabServerApp.app_url = '/lab' + +## Deprecated, use `LabServerApp.blocked_extensions_uris` +# Default: '' +# c.LabServerApp.blacklist_uris = '' + +## A list of comma-separated URIs to get the blocked extensions list +# +# .. versionchanged:: 2.0.0 +# `LabServerApp.blacklist_uris` renamed to `blocked_extensions_uris` +# Default: '' +# c.LabServerApp.blocked_extensions_uris = '' + +## Whether to cache files on the server. This should be `True` except in dev +# mode. +# Default: True +# c.LabServerApp.cache_files = True + +## Full path of a config file. +# See also: JupyterApp.config_file +# c.LabServerApp.config_file = '' + +## Specify a config file to load. +# See also: JupyterApp.config_file_name +# c.LabServerApp.config_file_name = '' + +## Extra paths to look for federated JupyterLab extensions +# Default: [] +# c.LabServerApp.extra_labextensions_path = [] + +## Generate default config file. +# See also: JupyterApp.generate_config +# c.LabServerApp.generate_config = False + +## Handlers appended to the server. +# See also: ExtensionApp.handlers +# c.LabServerApp.handlers = [] + +## Options to pass to the jinja2 environment for this +# Default: {} +# c.LabServerApp.jinja2_options = {} + +## The standard paths to look in for federated JupyterLab extensions +# Default: [] +# c.LabServerApp.labextensions_path = [] + +## The url for federated JupyterLab extensions +# Default: '' +# c.LabServerApp.labextensions_url = '' + +## The interval delay in seconds to refresh the lists +# Default: 3600 +# c.LabServerApp.listings_refresh_seconds = 3600 + +## The optional kwargs to use for the listings HTTP requests as +# described on https://2.python-requests.org/en/v2.7.0/api/#requests.request +# Default: {} +# c.LabServerApp.listings_request_options = {} + +## The listings url. +# Default: '' +# c.LabServerApp.listings_url = '' + +## The date format used by logging formatters for %(asctime)s +# See also: Application.log_datefmt +# c.LabServerApp.log_datefmt = '%Y-%m-%d %H:%M:%S' + +## The Logging format template +# See also: Application.log_format +# c.LabServerApp.log_format = '[%(name)s]%(highlevel)s %(message)s' + +## Set the log level by value or name. +# See also: Application.log_level +# c.LabServerApp.log_level = 30 + +## Whether to open in a browser after starting. +# See also: ExtensionApp.open_browser +# c.LabServerApp.open_browser = False + +## The optional location of the settings schemas directory. If given, a handler +# will be added for settings. +# Default: '' +# c.LabServerApp.schemas_dir = '' + +## Settings that will passed to the server. +# See also: ExtensionApp.settings +# c.LabServerApp.settings = {} + +## The url path of the settings handler. +# Default: '' +# c.LabServerApp.settings_url = '' + +## Instead of starting the Application, dump configuration to stdout +# See also: Application.show_config +# c.LabServerApp.show_config = False + +## Instead of starting the Application, dump configuration to stdout (as JSON) +# See also: Application.show_config_json +# c.LabServerApp.show_config_json = False + +## The optional location of local static files. If given, a static file handler +# will be added. +# Default: '' +# c.LabServerApp.static_dir = '' + +## paths to search for serving static files. +# See also: ExtensionApp.static_paths +# c.LabServerApp.static_paths = [] + +## Url where the static assets for the extension are served. +# See also: ExtensionApp.static_url_prefix +# c.LabServerApp.static_url_prefix = '' + +## Paths to search for serving jinja templates. +# See also: ExtensionApp.template_paths +# c.LabServerApp.template_paths = [] + +## The application templates directory. +# Default: '' +# c.LabServerApp.templates_dir = '' + +## The optional location of the themes directory. If given, a handler will be +# added for themes. +# Default: '' +# c.LabServerApp.themes_dir = '' + +## The theme url. +# Default: '' +# c.LabServerApp.themes_url = '' + +## The url path of the translations handler. +# Default: '' +# c.LabServerApp.translations_api_url = '' + +## The url path of the tree handler. +# Default: '' +# c.LabServerApp.tree_url = '' + +## The optional location of the user settings directory. +# Default: '' +# c.LabServerApp.user_settings_dir = '' + +## Deprecated, use `LabServerApp.allowed_extensions_uris` +# Default: '' +# c.LabServerApp.whitelist_uris = '' + +## The url path of the workspaces API. +# Default: '' +# c.LabServerApp.workspaces_api_url = '' + +## The optional location of the saved workspaces directory. If given, a handler +# will be added for workspaces. +# Default: '' +# c.LabServerApp.workspaces_dir = '' + +#------------------------------------------------------------------------------ +# LabApp(LabServerApp) configuration +#------------------------------------------------------------------------------ +## +# See also: LabServerApp.allowed_extensions_uris +# c.LabApp.allowed_extensions_uris = '' + +## Answer yes to any prompts. +# See also: JupyterApp.answer_yes +# c.LabApp.answer_yes = False + +## The app directory to launch JupyterLab from. +# Default: None +# c.LabApp.app_dir = None + +## The application settings directory. +# Default: '' +# c.LabApp.app_settings_dir = '' + +## The url path for the application. +# Default: '/lab' +# c.LabApp.app_url = '/lab' + +## Deprecated, use `LabServerApp.blocked_extensions_uris` +# See also: LabServerApp.blacklist_uris +# c.LabApp.blacklist_uris = '' + +## +# See also: LabServerApp.blocked_extensions_uris +# c.LabApp.blocked_extensions_uris = '' + +## Whether to cache files on the server. This should be `True` except in dev +# mode. +# Default: True +# c.LabApp.cache_files = True + +## Full path of a config file. +# See also: JupyterApp.config_file +# c.LabApp.config_file = '' + +## Specify a config file to load. +# See also: JupyterApp.config_file_name +# c.LabApp.config_file_name = '' + +## Whether to start the app in core mode. In this mode, JupyterLab will run using +# the JavaScript assets that are within the installed JupyterLab Python package. +# In core mode, third party extensions are disabled. The `--dev-mode` flag is an +# alias to this to be used when the Python package itself is installed in +# development mode (`pip install -e .`). +# Default: False +# c.LabApp.core_mode = False + +## The default URL to redirect to from `/` +# Default: '/lab' +# c.LabApp.default_url = '/lab' + +## Whether to start the app in dev mode. Uses the unpublished local JavaScript +# packages in the `dev_mode` folder. In this case JupyterLab will show a red +# stripe at the top of the page. It can only be used if JupyterLab is installed +# as `pip install -e .`. +# Default: False +# c.LabApp.dev_mode = False + +## Whether to expose the global app instance to browser via window.jupyterlab +# Default: False +# c.LabApp.expose_app_in_browser = False + +## Whether to load prebuilt extensions in dev mode. This may be useful to run and +# test prebuilt extensions in development installs of JupyterLab. APIs in a +# JupyterLab development install may be incompatible with published packages, so +# prebuilt extensions compiled against published packages may not work +# correctly. +# Default: False +# c.LabApp.extensions_in_dev_mode = False + +## Extra paths to look for federated JupyterLab extensions +# Default: [] +# c.LabApp.extra_labextensions_path = [] + +## Generate default config file. +# See also: JupyterApp.generate_config +# c.LabApp.generate_config = False + +## Handlers appended to the server. +# See also: ExtensionApp.handlers +# c.LabApp.handlers = [] + +## Options to pass to the jinja2 environment for this +# Default: {} +# c.LabApp.jinja2_options = {} + +## The standard paths to look in for federated JupyterLab extensions +# Default: [] +# c.LabApp.labextensions_path = [] + +## The url for federated JupyterLab extensions +# Default: '' +# c.LabApp.labextensions_url = '' + +## The interval delay in seconds to refresh the lists +# See also: LabServerApp.listings_refresh_seconds +# c.LabApp.listings_refresh_seconds = 3600 + +## The optional kwargs to use for the listings HTTP requests as +# described on https://2.python-requests.org/en/v2.7.0/api/#requests.request +# See also: LabServerApp.listings_request_options +# c.LabApp.listings_request_options = {} + +## The listings url. +# Default: '' +# c.LabApp.listings_url = '' + +## The date format used by logging formatters for %(asctime)s +# See also: Application.log_datefmt +# c.LabApp.log_datefmt = '%Y-%m-%d %H:%M:%S' + +## The Logging format template +# See also: Application.log_format +# c.LabApp.log_format = '[%(name)s]%(highlevel)s %(message)s' + +## Set the log level by value or name. +# See also: Application.log_level +# c.LabApp.log_level = 30 + +## Whether to open in a browser after starting. +# See also: ExtensionApp.open_browser +# c.LabApp.open_browser = False + +## The override url for static lab assets, typically a CDN. +# Default: '' +# c.LabApp.override_static_url = '' + +## The override url for static lab theme assets, typically a CDN. +# Default: '' +# c.LabApp.override_theme_url = '' + +## The optional location of the settings schemas directory. If given, a handler +# will be added for settings. +# Default: '' +# c.LabApp.schemas_dir = '' + +## Settings that will passed to the server. +# See also: ExtensionApp.settings +# c.LabApp.settings = {} + +## The url path of the settings handler. +# Default: '' +# c.LabApp.settings_url = '' + +## Instead of starting the Application, dump configuration to stdout +# See also: Application.show_config +# c.LabApp.show_config = False + +## Instead of starting the Application, dump configuration to stdout (as JSON) +# See also: Application.show_config_json +# c.LabApp.show_config_json = False + +## The optional location of local static files. If given, a static file handler +# will be added. +# Default: '' +# c.LabApp.static_dir = '' + +## paths to search for serving static files. +# See also: ExtensionApp.static_paths +# c.LabApp.static_paths = [] + +## Url where the static assets for the extension are served. +# See also: ExtensionApp.static_url_prefix +# c.LabApp.static_url_prefix = '' + +## Paths to search for serving jinja templates. +# See also: ExtensionApp.template_paths +# c.LabApp.template_paths = [] + +## The application templates directory. +# Default: '' +# c.LabApp.templates_dir = '' + +## The optional location of the themes directory. If given, a handler will be +# added for themes. +# Default: '' +# c.LabApp.themes_dir = '' + +## The theme url. +# Default: '' +# c.LabApp.themes_url = '' + +## The url path of the translations handler. +# Default: '' +# c.LabApp.translations_api_url = '' + +## The url path of the tree handler. +# Default: '' +# c.LabApp.tree_url = '' + +## The directory for user settings. +# Default: '/home/sergei/.jupyter/lab/user-settings' +# c.LabApp.user_settings_dir = '/home/sergei/.jupyter/lab/user-settings' + +## Whether to serve the app in watch mode +# Default: False +# c.LabApp.watch = False + +## Deprecated, use `LabServerApp.allowed_extensions_uris` +# See also: LabServerApp.whitelist_uris +# c.LabApp.whitelist_uris = '' + +## The url path of the workspaces API. +# Default: '' +# c.LabApp.workspaces_api_url = '' + +## The directory for workspaces +# Default: '/home/sergei/.jupyter/lab/workspaces' +# c.LabApp.workspaces_dir = '/home/sergei/.jupyter/lab/workspaces' + +#------------------------------------------------------------------------------ +# ServerApp(JupyterApp) configuration +#------------------------------------------------------------------------------ +## Set the Access-Control-Allow-Credentials: true header +# Default: False +# c.ServerApp.allow_credentials = False + +## Set the Access-Control-Allow-Origin header +# +# Use '*' to allow any origin to access your server. +# +# Takes precedence over allow_origin_pat. +# Default: '' +# c.ServerApp.allow_origin = '' + +## Use a regular expression for the Access-Control-Allow-Origin header +# +# Requests from an origin matching the expression will get replies with: +# +# Access-Control-Allow-Origin: origin +# +# where `origin` is the origin of the request. +# +# Ignored if allow_origin is set. +# Default: '' +# c.ServerApp.allow_origin_pat = '' + +## Allow password to be changed at login for the Jupyter server. +# +# While loggin in with a token, the Jupyter server UI will give the opportunity +# to the user to enter a new password at the same time that will replace the +# token login mechanism. +# +# This can be set to false to prevent changing password from the UI/API. +# Default: True +# c.ServerApp.allow_password_change = True + +## Allow requests where the Host header doesn't point to a local server +# +# By default, requests get a 403 forbidden response if the 'Host' header shows +# that the browser thinks it's on a non-local domain. Setting this option to +# True disables this check. +# +# This protects against 'DNS rebinding' attacks, where a remote web server +# serves you a page and then changes its DNS to send later requests to a local +# IP, bypassing same-origin checks. +# +# Local IP addresses (such as 127.0.0.1 and ::1) are allowed as local, along +# with hostnames configured in local_hostnames. +# Default: False +c.ServerApp.allow_remote_access = True + +## Whether to allow the user to run the server as root. +# Default: False +# c.ServerApp.allow_root = False + +## Answer yes to any prompts. +# See also: JupyterApp.answer_yes +# c.ServerApp.answer_yes = False + +## " Require authentication to access prometheus metrics. +# Default: True +# c.ServerApp.authenticate_prometheus = True + +## Reload the webapp when changes are made to any Python src files. +# Default: False +# c.ServerApp.autoreload = False + +## The base URL for the Jupyter server. +# +# Leading and trailing slashes can be omitted, and will automatically be added. +# Default: '/' +# c.ServerApp.base_url = '/' + +## Specify what command to use to invoke a web browser when starting the server. +# If not specified, the default browser will be determined by the `webbrowser` +# standard library module, which allows setting of the BROWSER environment +# variable to override it. +# Default: '' +# c.ServerApp.browser = '' + +## The full path to an SSL/TLS certificate file. +# Default: '' +c.ServerApp.certfile = u'/home/testuser/.jupyter/jupyter.pem' + +## The full path to a certificate authority certificate for SSL/TLS client +# authentication. +# Default: '' +# c.ServerApp.client_ca = '' + +## Full path of a config file. +# See also: JupyterApp.config_file +# c.ServerApp.config_file = '' + +## Specify a config file to load. +# See also: JupyterApp.config_file_name +# c.ServerApp.config_file_name = '' + +## The config manager class to use +# Default: 'jupyter_server.services.config.manager.ConfigManager' +# c.ServerApp.config_manager_class = 'jupyter_server.services.config.manager.ConfigManager' + +## The content manager class to use. +# Default: 'jupyter_server.services.contents.largefilemanager.LargeFileManager' +# c.ServerApp.contents_manager_class = 'jupyter_server.services.contents.largefilemanager.LargeFileManager' + +## Extra keyword arguments to pass to `set_secure_cookie`. See tornado's +# set_secure_cookie docs for details. +# Default: {} +# c.ServerApp.cookie_options = {} + +## The random bytes used to secure cookies. By default this is a new random +# number every time you start the server. Set it to a value in a config file to +# enable logins to persist across server sessions. +# +# Note: Cookie secrets should be kept private, do not share config files with +# cookie_secret stored in plaintext (you can read the value from a file). +# Default: b'' +# c.ServerApp.cookie_secret = b'' + +## The file where the cookie secret is stored. +# Default: '' +# c.ServerApp.cookie_secret_file = '' + +## Override URL shown to users. +# +# Replace actual URL, including protocol, address, port and base URL, with the +# given value when displaying URL to the users. Do not change the actual +# connection URL. If authentication token is enabled, the token is added to the +# custom URL automatically. +# +# This option is intended to be used when the URL to display to the user cannot +# be determined reliably by the Jupyter server (proxified or containerized +# setups for example). +# Default: '' +# c.ServerApp.custom_display_url = '' + +## The default URL to redirect to from `/` +# Default: '/' +# c.ServerApp.default_url = '/' + +## Disable cross-site-request-forgery protection +# +# Jupyter notebook 4.3.1 introduces protection from cross-site request +# forgeries, requiring API requests to either: +# +# - originate from pages served by this server (validated with XSRF cookie and +# token), or - authenticate with a token +# +# Some anonymous compute resources still desire the ability to run code, +# completely without authentication. These services can disable all +# authentication and security checks, with the full knowledge of what that +# implies. +# Default: False +# c.ServerApp.disable_check_xsrf = False + +## handlers that should be loaded at higher priority than the default services +# Default: [] +# c.ServerApp.extra_services = [] + +## Extra paths to search for serving static files. +# +# This allows adding javascript/css to be available from the Jupyter server +# machine, or overriding individual files in the IPython +# Default: [] +# c.ServerApp.extra_static_paths = [] + +## Extra paths to search for serving jinja templates. +# +# Can be used to override templates from jupyter_server.templates. +# Default: [] +# c.ServerApp.extra_template_paths = [] + +## Open the named file when the application is launched. +# Default: '' +# c.ServerApp.file_to_run = '' + +## The URL prefix where files are opened directly. +# Default: 'notebooks' +# c.ServerApp.file_url_prefix = 'notebooks' + +## Generate default config file. +# See also: JupyterApp.generate_config +# c.ServerApp.generate_config = False + +## Extra keyword arguments to pass to `get_secure_cookie`. See tornado's +# get_secure_cookie docs for details. +# Default: {} +# c.ServerApp.get_secure_cookie_kwargs = {} + +## (bytes/sec) Maximum rate at which stream output can be sent on iopub before +# they are limited. +# Default: 1000000 +# c.ServerApp.iopub_data_rate_limit = 1000000 + +## (msgs/sec) Maximum rate at which messages can be sent on iopub before they are +# limited. +# Default: 1000 +# c.ServerApp.iopub_msg_rate_limit = 1000 + +## The IP address the Jupyter server will listen on. +# Default: 'localhost' +c.ServerApp.ip = '*' + +## Supply extra arguments that will be passed to Jinja environment. +# Default: {} +# c.ServerApp.jinja_environment_options = {} + +## Extra variables to supply to jinja templates when rendering. +# Default: {} +# c.ServerApp.jinja_template_vars = {} + +## Dict of Python modules to load as Jupyter server extensions.Entry values can +# be used to enable and disable the loading ofthe extensions. The extensions +# will be loaded in alphabetical order. +# Default: {} +# c.ServerApp.jpserver_extensions = {} + +## The kernel manager class to use. +# Default: 'jupyter_server.services.kernels.kernelmanager.AsyncMappingKernelManager' +# c.ServerApp.kernel_manager_class = 'jupyter_server.services.kernels.kernelmanager.AsyncMappingKernelManager' + +## The kernel spec manager class to use. Should be a subclass of +# `jupyter_client.kernelspec.KernelSpecManager`. +# +# The Api of KernelSpecManager is provisional and might change without warning +# between this version of Jupyter and the next stable one. +# Default: 'jupyter_client.kernelspec.KernelSpecManager' +# c.ServerApp.kernel_spec_manager_class = 'jupyter_client.kernelspec.KernelSpecManager' + +## The full path to a private key file for usage with SSL/TLS. +# Default: '' +c.ServerApp.keyfile = u'/home/testuser/.jupyter/jupyter.key' + +## Hostnames to allow as local when allow_remote_access is False. +# +# Local IP addresses (such as 127.0.0.1 and ::1) are automatically accepted as +# local as well. +# Default: ['localhost'] +# c.ServerApp.local_hostnames = ['localhost'] + +## The date format used by logging formatters for %(asctime)s +# See also: Application.log_datefmt +# c.ServerApp.log_datefmt = '%Y-%m-%d %H:%M:%S' + +## The Logging format template +# See also: Application.log_format +# c.ServerApp.log_format = '[%(name)s]%(highlevel)s %(message)s' + +## Set the log level by value or name. +# See also: Application.log_level +# c.ServerApp.log_level = 30 + +## The login handler class to use. +# Default: 'jupyter_server.auth.login.LoginHandler' +# c.ServerApp.login_handler_class = 'jupyter_server.auth.login.LoginHandler' + +## The logout handler class to use. +# Default: 'jupyter_server.auth.logout.LogoutHandler' +# c.ServerApp.logout_handler_class = 'jupyter_server.auth.logout.LogoutHandler' + +## Sets the maximum allowed size of the client request body, specified in the +# Content-Length request header field. If the size in a request exceeds the +# configured value, a malformed HTTP message is returned to the client. +# +# Note: max_body_size is applied even in streaming mode. +# Default: 536870912 +# c.ServerApp.max_body_size = 536870912 + +## Gets or sets the maximum amount of memory, in bytes, that is allocated for use +# by the buffer manager. +# Default: 536870912 +# c.ServerApp.max_buffer_size = 536870912 + +## Gets or sets a lower bound on the open file handles process resource limit. +# This may need to be increased if you run into an OSError: [Errno 24] Too many +# open files. This is not applicable when running on Windows. +# Default: 0 +# c.ServerApp.min_open_files_limit = 0 + +## DEPRECATED, use root_dir. +# Default: '' +# c.ServerApp.notebook_dir = '' + +## Whether to open in a browser after starting. The specific browser used is +# platform dependent and determined by the python standard library `webbrowser` +# module, unless it is overridden using the --browser (ServerApp.browser) +# configuration option. +# Default: False +c.ServerApp.open_browser = False + +## Hashed password to use for web authentication. +# +# To generate, type in a python/IPython shell: +# +# from jupyter_server.auth import passwd; passwd() +# +# The string should be of the form type:salt:hashed-password. +# Default: '' +# c.ServerApp.password = '' + +## Forces users to use a password for the Jupyter server. This is useful in a +# multi user environment, for instance when everybody in the LAN can access each +# other's machine through ssh. +# +# In such a case, serving on localhost is not secure since any user can connect +# to the Jupyter server via ssh. +# Default: False +# c.ServerApp.password_required = False + +## The port the server will listen on (env: JUPYTER_PORT). +# Default: 0 +# c.ServerApp.port = 0 + +## The number of additional ports to try if the specified port is not available +# (env: JUPYTER_PORT_RETRIES). +# Default: 50 +# c.ServerApp.port_retries = 50 + +## DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib. +# Default: 'disabled' +# c.ServerApp.pylab = 'disabled' + +## If True, display controls to shut down the Jupyter server, such as menu items +# or buttons. +# Default: True +# c.ServerApp.quit_button = True + +## (sec) Time window used to check the message and data rate limits. +# Default: 3 +# c.ServerApp.rate_limit_window = 3 + +## Reraise exceptions encountered loading server extensions? +# Default: False +# c.ServerApp.reraise_server_extension_failures = False + +## The directory to use for notebooks and kernels. +# Default: '' +# c.ServerApp.root_dir = '' + +## The session manager class to use. +# Default: 'jupyter_server.services.sessions.sessionmanager.SessionManager' +# c.ServerApp.session_manager_class = 'jupyter_server.services.sessions.sessionmanager.SessionManager' + +## Instead of starting the Application, dump configuration to stdout +# See also: Application.show_config +# c.ServerApp.show_config = False + +## Instead of starting the Application, dump configuration to stdout (as JSON) +# See also: Application.show_config_json +# c.ServerApp.show_config_json = False + +## Shut down the server after N seconds with no kernels or terminals running and +# no activity. This can be used together with culling idle kernels +# (MappingKernelManager.cull_idle_timeout) to shutdown the Jupyter server when +# it's not in use. This is not precisely timed: it may shut down up to a minute +# later. 0 (the default) disables this automatic shutdown. +# Default: 0 +# c.ServerApp.shutdown_no_activity_timeout = 0 + +## The UNIX socket the Jupyter server will listen on. +# Default: '' +# c.ServerApp.sock = '' + +## The permissions mode for UNIX socket creation (default: 0600). +# Default: '0600' +# c.ServerApp.sock_mode = '0600' + +## Supply SSL options for the tornado HTTPServer. See the tornado docs for +# details. +# Default: {} +# c.ServerApp.ssl_options = {} + +## Supply overrides for terminado. Currently only supports "shell_command". +# Default: {} +# c.ServerApp.terminado_settings = {} + +## Set to False to disable terminals. +# +# This does *not* make the server more secure by itself. Anything the user can +# in a terminal, they can also do in a notebook. +# +# Terminals may also be automatically disabled if the terminado package is not +# available. +# Default: True +# c.ServerApp.terminals_enabled = True + +## Token used for authenticating first-time connections to the server. +# +# The token can be read from the file referenced by JUPYTER_TOKEN_FILE or set +# directly with the JUPYTER_TOKEN environment variable. +# +# When no password is enabled, the default is to generate a new, random token. +# +# Setting to an empty string disables authentication altogether, which is NOT +# RECOMMENDED. +# Default: '' +# c.ServerApp.token = '' + +## Supply overrides for the tornado.web.Application that the Jupyter server uses. +# Default: {} +# c.ServerApp.tornado_settings = {} + +## Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded- +# For headerssent by the upstream reverse proxy. Necessary if the proxy handles +# SSL +# Default: False +# c.ServerApp.trust_xheaders = False + +## Disable launching browser by redirect file For versions of notebook > 5.7.2, a +# security feature measure was added that prevented the authentication token +# used to launch the browser from being visible. This feature makes it difficult +# for other users on a multi-user system from running code in your Jupyter +# session as you. However, some environments (like Windows Subsystem for Linux +# (WSL) and Chromebooks), launching a browser using a redirect file can lead the +# browser failing to load. This is because of the difference in file +# structures/paths between the runtime and the browser. +# +# Disabling this setting to False will disable this behavior, allowing the +# browser to launch by using a URL and visible token (as before). +# Default: True +# c.ServerApp.use_redirect_file = True + +## Specify where to open the server on startup. This is the `new` argument passed +# to the standard library method `webbrowser.open`. The behaviour is not +# guaranteed, but depends on browser support. Valid values are: +# +# - 2 opens a new tab, +# - 1 opens a new window, +# - 0 opens in an existing window. +# +# See the `webbrowser.open` documentation for details. +# Default: 2 +# c.ServerApp.webbrowser_open_new = 2 + +## Set the tornado compression options for websocket connections. +# +# This value will be returned from +# :meth:`WebSocketHandler.get_compression_options`. None (default) will disable +# compression. A dict (even an empty one) will enable compression. +# +# See the tornado docs for WebSocketHandler.get_compression_options for details. +# Default: None +# c.ServerApp.websocket_compression_options = None + +## The base URL for websockets, if it differs from the HTTP server (hint: it +# almost certainly doesn't). +# +# Should be in the form of an HTTP origin: ws[s]://hostname[:port] +# Default: '' +# c.ServerApp.websocket_url = '' diff --git a/label_studio_ml/examples/yolonas/labels.txt b/label_studio_ml/examples/yolonas/labels.txt new file mode 100644 index 000000000..80e3ad44f --- /dev/null +++ b/label_studio_ml/examples/yolonas/labels.txt @@ -0,0 +1,5 @@ +Provide here labels that match Label Studio labeling setup. +Each label name on separate line, like: +car +airplane +cat \ No newline at end of file diff --git a/label_studio_ml/examples/yolonas/model.py b/label_studio_ml/examples/yolonas/model.py new file mode 100644 index 000000000..0ad8097da --- /dev/null +++ b/label_studio_ml/examples/yolonas/model.py @@ -0,0 +1,216 @@ +# based on https://github.com/heartexlabs/label-studio/discussions/1623 +# Provides Label Studio ML backend for yolo nas with working custom s3 endpoint for file storage + +import os +import logging +import boto3 +import io +import json +import torch +from PIL import Image +from label_studio_ml.model import LabelStudioMLBase +from label_studio_ml.utils import get_image_size, \ + get_single_tag_keys, DATA_UNDEFINED_NAME +from label_studio_tools.core.utils.io import get_data_dir +from botocore.exceptions import ClientError +from urllib.parse import urlparse +from io import BytesIO +from super_gradients.training import models +import super_gradients.training.processing.processing as sg_processing + + +class MissingYoloLables(Exception): + def __init__(self, value): + self.value = value + + def __str__(self): + return repr(self.value) + + +logger = logging.getLogger(__name__) + + +class ObjectDetectorModel(LabelStudioMLBase): + + def __init__(self, + checkpoint_file=None, + image_dir=None, + labels_file=None, + yolo_labels='labels.txt', + score_threshold=0.45, + iou_threshold=0.25, + img_size=1280, + device='cpu', **kwargs): + """ + Load YoloNAS model from checkpoint into memory. + Set mappings from yolov classes to target labels + :param checkpoint_file: Absolute path to yolov serialized model + :param image_dir: Directory where images are stored (should be used only in case you use direct file upload into Label Studio instead of URLs) + :param labels_file: file with mappings from yolo labels to custom labels {"airplane": "Boeing"} + :param yolo_labels: file with yolo label names, plain text with each label on a new line + :param score_threshold: score threshold to remove predictions below one + :param iou_threshold: IoU threshold for yolo NMS + :param device: device (cpu, cuda:0, cuda:1, ...) + :param kwargs: endpoint_url - endpoint URL for custom s3 storage + yolo_model_type - type of yolo nas model : yolo_nas_s|yolo_nas_m + """ + + super(ObjectDetectorModel, self).__init__(**kwargs) + self.checkpoint_file = os.environ.get('CHECKPOINT_FILE', None) or checkpoint_file + self.labels_file = os.environ.get('LABELS_FILE', None) or labels_file + self.yolo_labels = os.environ.get('YOLO_LABELS', None) or yolo_labels + self.iou_threshold = float(os.environ.get('IOU_THRESHOLD', None) or iou_threshold) + self.score_thresh = float(os.environ.get('SCORE_THRESHOLD', None) or score_threshold) + self.img_size = int(os.environ.get('IMG_SIZE', None) or img_size) + self.device = os.environ.get('DEVICE', 'cuda' if torch.cuda.is_available() else 'cpu') + endpoint_url = kwargs.get('endpoint_url') + self.endpoint_url = os.environ.get('ENDPOINT_URL', None) or endpoint_url + if self.endpoint_url: + logger.info(f'Using s3 endpoint url {self.endpoint_url}') + yolo_model_type = kwargs.get('yolo_model_type') + self.yolo_model_type = os.environ.get('YOLO_MODEL_TYPE', None) or yolo_model_type + if self.yolo_model_type is None: + self.yolo_model_type == 'yolo_nas_m' + + # read yolo labels from file + if self.yolo_labels and os.path.exists(self.yolo_labels): + with open(self.yolo_labels, 'r') as f: + yolo_labels_list = f.readlines() + yolo_labels_list = list(map(lambda x: x.strip(), yolo_labels_list)) + yolo_labels_list = list(map(lambda x: x if x[-1] != '\n' else x[:-1], yolo_labels_list)) + self.yolo_labels = yolo_labels_list + logger.info('Using labels...') + logger.info(", ".join(self.yolo_labels)) + else: + raise MissingYoloLables + + # default Label Studio image upload folder + upload_dir = os.path.join(get_data_dir(), 'media', 'upload') + image_dir = os.environ.get('IMAGE_DIR', None) or image_dir + self.image_dir = image_dir or upload_dir + logger.debug(f'{self.__class__.__name__} reads images from {self.image_dir}') + + # create a label map + if self.labels_file and os.path.exists(self.labels_file): + self.label_map = json_load(self.labels_file) + else: + self.label_map = {} + + self.from_name, self.to_name, self.value, self.labels_in_config = get_single_tag_keys( + self.parsed_label_config, 'RectangleLabels', 'Image') + schema = list(self.parsed_label_config.values())[0] + self.labels_in_config = set(self.labels_in_config) + + # Collect label maps from `predicted_values="airplane,car"` attribute in