Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix interface for stability and consistency #36

Draft
wants to merge 9 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
include cvu/detector/yolov5/backends/weights/*.json
include cvu/detector/weights.json
include cvu/utils/backend/*.json
2 changes: 2 additions & 0 deletions cvu/detector/interface/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .core import IDetector
from .model import IDetectorModel
61 changes: 61 additions & 0 deletions cvu/detector/interface/core.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
"""Includes interface for CVU Detectors' core.
"""
import abc
from typing import List, Union

import numpy as np

from cvu.interface.core import ICore
from cvu.detector.predictions import Predictions


class IDetector(ICore, metaclass=abc.ABCMeta):
"""Interface which will be implemented for every CVU Detector.
A core defines one complete method/solution for certain use cases.
For example, YoloV5 is a detector core of Object Detection use cases.
"""

@abc.abstractmethod
def __init__(self, classes: Union[str, List[str]], backend: str,
weight: str, device: str, *args, **kwargs) -> None:
"""Initiate Core.

Args:
classes (Union[str, List[str]]): name of classes to be detected.
It can be set to individual classes like 'coco', 'person', 'cat' etc.
Alternatively, it can be a list of classes such as ['person', 'cat'].
For default weights, 'classes' is used to filter out objects
according to provided argument from coco class (unless specified otherwise).

backend (str): name of the backend to be used for inference purposes.

weight (str): path to weight files (according to selected backend).

device (str): name of the device to be used. Valid
devices can be "cpu", "gpu", "tpu", "auto".

auto_install (bool): auto install missing requirements for the selected
backend.
"""
...

@abc.abstractmethod
def __call__(self, inputs: np.ndarray, **kwargs) -> Predictions:
"""Run object detection on given image

Args:
inputs (np.ndarray): image in BGR format.

Returns:
Predictions: detected objects.
"""
...

@abc.abstractmethod
def __repr__(self) -> str:
"""Returns Backend and Model Information

Returns:
str: formatted string with method and config info.
"""
...
39 changes: 39 additions & 0 deletions cvu/detector/interface/model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"""This module contains interface definition of the detector model
"""
import abc

import numpy as np


class IDetectorModel(metaclass=abc.ABCMeta):
"""Interface of the detector model

A model performs inference, using a certain backend runtime,
on a numpy array, and returns result after performing NMS.

Inputs are expected to be normalized in channels-first order
with/without batch axis.
"""

@abc.abstractmethod
def __call__(self, inputs: np.ndarray) -> np.ndarray:
"""Performs model inference on given inputs, and returns
inference's output after NMS.

Args:
inputs (np.ndarray): normalized in channels-first format,
with batch axis.

Returns:
np.ndarray: inference's output after NMS
"""
...

@abc.abstractmethod
def __repr__(self) -> str:
"""Represents model with method and configuration informations.

Returns:
str: formatted string with method and config info.
"""
...
12 changes: 12 additions & 0 deletions cvu/detector/weights.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"yolov5":{
"yolov5s":{
"onnx": "1piC3ZGuc4D8MMJQQRK3dgaCa66-4Ucxi",
"tensorflow":"1SA7hT4jUx9szqJePZ8NssMPSCelRvYpo",
"tensorrt":"1piC3ZGuc4D8MMJQQRK3dgaCa66-4Ucxi",
"tflite": "1oeKZm81Gz5OejmcgDA0biemkPWCY6qNu",
"torch":"1cWeCusb2IB-x-h_IZs7t3Y_rs9GkzQsI",
"torch.cuda":"16BFVGsEYAXsupFoCXxlOhSBovoMH6juK"
}
}
}
4 changes: 4 additions & 0 deletions cvu/detector/yolo/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
"""This module contains core Yolo Implementation with various
functional backends.
"""
from .core import Yolo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""This module contains implementation of Yolov5 model
"""This module contains implementation of Yolo model
for various backends.

A model (aka backend) basically performs inference on a given input numpy array,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""This file contains Yolov5's IModel implementation in ONNX.
"""This file contains Yolo's IDetectorModel implementation in ONNX.
This model (onnx-backend) performs inference using ONNXRUNTIME,
on a given input numpy array, and returns result after performing
nms and other backend specific postprocessings.
Expand All @@ -13,14 +13,12 @@
import numpy as np
import onnxruntime

from cvu.interface.model import IModel
from cvu.utils.general import get_path
from cvu.detector.yolov5.backends.common import download_weights
from cvu.detector.interface import IDetectorModel
from cvu.postprocess.nms.yolov5 import non_max_suppression_np


class Yolov5(IModel):
"""Implements IModel for Yolov5 using ONNX.
class Yolo(IDetectorModel):
"""Implements IDetectorModel for Yolo using ONNX.

This model (onnx-backend) performs inference, using ONNXRUNTIME,
on a numpy array, and returns result after performing NMS.
Expand All @@ -29,13 +27,11 @@ class Yolov5(IModel):
with/without batch axis.
"""

def __init__(self, weight: str = "yolov5s", device: str = 'auto') -> None:
def __init__(self, weight: str, device: str = 'auto') -> None:
"""Initiate Model

Args:
weight (str, optional): path to onnx weight file. Alternatively,
it also accepts identifiers (such as yolvo5s, yolov5m, etc.) to load
pretrained models. Defaults to "yolov5s".
weight (str): path to onnx weight file.

device (str, optional): name of the device to be used. Valid devices can be
"cpu", "gpu", "auto". Defaults to "auto" which tries to use the device
Expand All @@ -50,26 +46,18 @@ def _load_model(self, weight: str) -> None:
"""Internally loads ONNX

Args:
weight (str): path to ONNX weight file or predefined-identifiers
(such as yolvo5s, yolov5m, etc.)
weight (str): path to ONNX weight file
"""
if not os.path.exists(weight):
raise FileNotFoundError(f"Unable to locate model weights {weight}")

if weight.endswith("torchscript"):
# convert to onnx
convert_to_onnx = importlib.import_module(
".convert_to_onnx","cvu.utils.backend_onnx")
".convert_to_onnx", "cvu.utils.backend_onnx")
weight = convert_to_onnx.onnx_from_torchscript(
weight, save_path=weight.replace("torchscript", "onnx"))

# attempt to load predefined weights
elif not os.path.exists(weight):

# get path to pretrained weights
weight += '.onnx'
weight = get_path(__file__, "weights", weight)

# download weights if not already downloaded
download_weights(weight, "onnx")

# load model
if self._device == "cpu":
# load model on cpu
Expand Down Expand Up @@ -132,7 +120,7 @@ def __repr__(self) -> str:
Returns:
str: information string
"""
return f"Yolov5s ONNX-{self._device}"
return f"Yolo ONNX-{self._device}"

@staticmethod
def _postprocess(outputs: np.ndarray) -> List[np.ndarray]:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""This file contains Yolov5's IModel implementation in Tensorflow.
"""This file contains Yolo's IDetectorModel implementation in Tensorflow.
This model (tensorflow-backend) performs inference using SavedModel,
on a given input numpy array, and returns result after performing
nms and other backend specific postprocessings.
Expand All @@ -14,29 +14,25 @@
import tensorflow as tf
from tensorflow.keras import mixed_precision

from cvu.interface.model import IModel
from cvu.utils.general import get_path
from cvu.detector.yolov5.backends.common import download_weights
from cvu.detector.interface import IDetectorModel
from cvu.postprocess.bbox import denormalize
from cvu.postprocess.backend_tf.nms.yolov5 import non_max_suppression_tf


class Yolov5(IModel):
"""Implements IModel for Yolov5 using Tensorflow.
class Yolo(IDetectorModel):
"""Implements IDetectorModel for Yolo using Tensorflow.

This model (tensorflow-backend) performs inference, using SavedModel,
on a numpy array, and returns result after performing NMS.

Inputs are expected to be normalized in channels-last order with batch axis.
"""

def __init__(self, weight: str = "yolov5s", device='auto') -> None:
def __init__(self, weight: str, device='auto') -> None:
"""Initiate Model

Args:
weight (str, optional): path to SavedModel weight files. Alternatively,
it also accepts identifiers (such as yolvo5s, yolov5m, etc.) to load
pretrained models. Defaults to "yolov5s".
weight (str): path to SavedModel weight files.

device (str, optional): name of the device to be used. Valid devices can be
"cpu", "gpu", "auto", "tpu". Defaults to "auto" which tries to use the
Expand Down Expand Up @@ -92,18 +88,10 @@ def _load_model(self, weight: str) -> None:
"""Internally loads SavedModel

Args:
weight (str): path to SavedModel weight files or predefined-identifiers
(such as yolvo5s, yolov5m, etc.)
weight (str): path to SavedModel weight files
"""
# attempt to load predefined weights
if not os.path.exists(weight):
weight += '_tensorflow'

# get path to pretrained weights
weight = get_path(__file__, "weights", weight)

# download weights if not already downloaded
download_weights(weight, "tensorflow", unzip=True)
raise FileNotFoundError(f"Unable to locate model weights {weight}")

# set load_options needed for TPU (if needed)
load_options = None
Expand Down Expand Up @@ -143,7 +131,7 @@ def __repr__(self) -> str:
Returns:
str: information string
"""
return f"Yolov5-Tensorflow: {self._device}"
return f"Yolo Tensorflow: {self._device}"

@staticmethod
def _postprocess(outputs: np.ndarray) -> List[np.ndarray]:
Expand Down
Loading