Compare commits
21
Commits
0.29.0
...
0.27.0.post0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eba1d64d91 | ||
|
|
f50ddca3a1 | ||
|
|
5b129a0850 | ||
|
|
fd4845a5cf | ||
|
|
8c104311d3 | ||
|
|
650ca0ae2f | ||
|
|
bc521bc2d0 | ||
|
|
ee1400a40c | ||
|
|
7f3dcaedbd | ||
|
|
0ba57b2f57 | ||
|
|
ae6aa369ae | ||
|
|
e36fbe061d | ||
|
|
224c7569a1 | ||
|
|
c9761515f3 | ||
|
|
d72089bb46 | ||
|
|
219161f1f9 | ||
|
|
99e772c466 | ||
|
|
a61440ee0b | ||
|
|
9bf9685165 | ||
|
|
e37b32f5ca | ||
|
|
7ecfc9f475 |
@@ -3,9 +3,12 @@ name: Publish Supervision Pre-Releases to PyPI
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[0-9]+.[0-9]+[0-9]+.[0-9]+a[0-9]"
|
||||
- "[0-9]+.[0-9]+[0-9]+.[0-9]+b[0-9]"
|
||||
- "[0-9]+.[0-9]+[0-9]+.[0-9]+rc[0-9]"
|
||||
- "[0-9]+.[0-9]+.[0-9]+a[0-9]+"
|
||||
- "[0-9]+.[0-9]+.[0-9]+b[0-9]+"
|
||||
- "[0-9]+.[0-9]+.[0-9]+rc[0-9]+"
|
||||
- "[0-9]+.[0-9]+.[0-9].+a[0-9]+"
|
||||
- "[0-9]+.[0-9]+.[0-9].+b[0-9]+"
|
||||
- "[0-9]+.[0-9]+.[0-9].+rc[0-9]+"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {} # Explicitly remove all permissions by default
|
||||
|
||||
@@ -3,7 +3,8 @@ name: Publish Supervision Releases to PyPI
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "[0-9]+.[0-9]+[0-9]+.[0-9]"
|
||||
- "[0-9]+.[0-9]+.[0-9]"
|
||||
- "[0-9]+.[0-9]+.[0-9].post[0-9]+"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {} # Explicitly remove all permissions by default
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
name: 🧪 Docs Test WorkFlow 📚
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
name: 🔧 Pytest/Test Workflow
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, develop]
|
||||
pull_request:
|
||||
branches: [main, develop]
|
||||
|
||||
@@ -23,12 +25,14 @@ jobs:
|
||||
with:
|
||||
python-version: ${{ matrix.python-version }}
|
||||
activate-environment: true
|
||||
|
||||
enable-cache: true # Recommended: caches the uv download/builds
|
||||
|
||||
- name: 🚀 Install Packages
|
||||
run: uv pip install -r pyproject.toml --group dev --group docs --extra metrics
|
||||
# CHANGED: 'uv sync' enforces the versions in uv.lock
|
||||
run: uv sync --group dev --group docs --extra metrics
|
||||
|
||||
- name: 🧪 Run the Import test
|
||||
# 'uv run' executes inside the environment created by 'uv sync'
|
||||
run: uv run python -c "import supervision; from supervision import assets; from supervision import metrics; print(supervision.__version__)"
|
||||
|
||||
- name: 🧪 Run the Test
|
||||
|
||||
@@ -395,7 +395,7 @@ increase the size and diversity of training datasets, leading to improved model
|
||||
performance and generalization.
|
||||
|
||||
```bash
|
||||
pip install augmentation
|
||||
pip install albumentations
|
||||
```
|
||||
|
||||
Albumentations provides a flexible and powerful API for image augmentation. The core of
|
||||
|
||||
+4
-3
@@ -2,7 +2,7 @@
|
||||
name = "supervision"
|
||||
description = "A set of easy-to-use utils that will come in handy in any Computer Vision project"
|
||||
license = { text = "MIT" }
|
||||
version = "0.27.0"
|
||||
version = "0.27.0.post0"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
authors = [
|
||||
@@ -46,7 +46,8 @@ dependencies = [
|
||||
"pillow>=9.4",
|
||||
"requests>=2.26.0",
|
||||
"tqdm>=4.62.3",
|
||||
"opencv-python>=4.5.5.64"
|
||||
"opencv-python>=4.5.5.64",
|
||||
"pyDeprecate>=0.4.0"
|
||||
]
|
||||
|
||||
[project.urls]
|
||||
@@ -73,7 +74,7 @@ dev = [
|
||||
docs = [
|
||||
"mkdocs-material[imaging]>=9.5.5",
|
||||
"mkdocstrings>=0.25.2,<0.31.0",
|
||||
"mkdocstrings-python>=1.10.9",
|
||||
"mkdocstrings-python>=1.10.9, <2.0.0", # todo: breaking changes in 2.x
|
||||
"mike>=2.0.0",
|
||||
"mkdocs-jupyter>=0.24.3",
|
||||
"mkdocs-git-committers-plugin-2>=2.4.1; python_version >= '3.9' and python_version < '4'",
|
||||
|
||||
@@ -7,6 +7,7 @@ from functools import reduce
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from deprecate import deprecated
|
||||
|
||||
from supervision.config import (
|
||||
CLASS_NAME_DATA_FIELD,
|
||||
@@ -51,7 +52,7 @@ from supervision.detection.vlm import (
|
||||
validate_vlm_parameters,
|
||||
)
|
||||
from supervision.geometry.core import Position
|
||||
from supervision.utils.internal import deprecated, get_instance_variables
|
||||
from supervision.utils.internal import get_instance_variables
|
||||
from supervision.validators import validate_detections_fields
|
||||
|
||||
|
||||
@@ -818,8 +819,9 @@ class Detections:
|
||||
|
||||
@classmethod
|
||||
@deprecated(
|
||||
"`Detections.from_lmm` property is deprecated and will be removed in "
|
||||
"`supervision-0.31.0`. Use Detections.from_vlm instead."
|
||||
target=None,
|
||||
deprecated_in="0.26.0",
|
||||
remove_in="0.30.0",
|
||||
)
|
||||
def from_lmm(cls, lmm: LMM | str, result: str | dict, **kwargs: Any) -> Detections:
|
||||
"""
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from deprecate import deprecated
|
||||
|
||||
from supervision.detection.utils.iou_and_nms import box_iou_batch
|
||||
|
||||
@@ -94,10 +95,17 @@ def pad_boxes(xyxy: np.ndarray, px: int, py: int | None = None) -> np.ndarray:
|
||||
return result
|
||||
|
||||
|
||||
@deprecated(
|
||||
target=None,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
args_mapping={"normalized_xyxy": "xyxy"},
|
||||
)
|
||||
def denormalize_boxes(
|
||||
xyxy: np.ndarray,
|
||||
resolution_wh: tuple[int, int],
|
||||
normalization_factor: float = 1.0,
|
||||
normalized_xyxy: np.ndarray = None,
|
||||
) -> np.ndarray:
|
||||
"""
|
||||
Convert normalized bounding box coordinates to absolute pixel coordinates.
|
||||
|
||||
@@ -9,17 +9,18 @@ from enum import Enum
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from deprecate import deprecated
|
||||
from PIL import Image
|
||||
|
||||
from supervision.detection.utils.boxes import denormalize_boxes
|
||||
from supervision.detection.utils.converters import polygon_to_mask, polygon_to_xyxy
|
||||
from supervision.utils.internal import deprecated
|
||||
from supervision.validators import validate_resolution
|
||||
|
||||
|
||||
@deprecated(
|
||||
"`LMM` enum is deprecated and will be removed in "
|
||||
"`supervision-0.31.0`. Use VLM instead."
|
||||
target=None,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
)
|
||||
class LMM(Enum):
|
||||
"""
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'supervision.keypoint' module is deprecated in `0.27.0` and will be removed "
|
||||
"in `0.30.0`. Please use 'supervision.key_points' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
from supervision.key_points.annotators import (
|
||||
EdgeAnnotator,
|
||||
VertexAnnotator,
|
||||
VertexLabelAnnotator,
|
||||
)
|
||||
from supervision.key_points.core import KeyPoints
|
||||
@@ -0,0 +1,15 @@
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'supervision.keypoint' module is deprecated in `0.27.0` and will be removed "
|
||||
"in `0.30.0`. Please use 'supervision.key_points' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
from supervision.key_points.annotators import ( # noqa: F401, E402
|
||||
BaseKeyPointAnnotator,
|
||||
EdgeAnnotator,
|
||||
VertexAnnotator,
|
||||
VertexLabelAnnotator,
|
||||
)
|
||||
@@ -0,0 +1,10 @@
|
||||
import warnings
|
||||
|
||||
warnings.warn(
|
||||
"The 'supervision.keypoint' module is deprecated in `0.27.0` and will be removed "
|
||||
"in `0.30.0`. Please use 'supervision.key_points' instead.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
from supervision.key_points.core import KeyPoints # noqa: F401, E402
|
||||
@@ -6,11 +6,11 @@ from dataclasses import dataclass
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
from deprecate import deprecated
|
||||
|
||||
from supervision.dataset.core import DetectionDataset
|
||||
from supervision.detection.core import Detections
|
||||
from supervision.detection.utils.iou_and_nms import box_iou_batch
|
||||
from supervision.utils.internal import deprecated
|
||||
|
||||
|
||||
def detections_to_tensor(
|
||||
@@ -495,10 +495,9 @@ class ConfusionMatrix:
|
||||
|
||||
|
||||
@deprecated(
|
||||
"`MeanAveragePrecision` is deprecated and will be removed in "
|
||||
"`supervision-0.31.0`. Use "
|
||||
"`supervision.metrics.mean_average_precision.MeanAveragePrecision` instead. The "
|
||||
"deprecated implementation provides results that are inconsistent with pycocotools."
|
||||
target=None,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
)
|
||||
@dataclass(frozen=True)
|
||||
class MeanAveragePrecision:
|
||||
|
||||
@@ -2,6 +2,7 @@ from functools import wraps
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
from deprecate import deprecated
|
||||
from PIL import Image
|
||||
|
||||
from supervision.draw.base import ImageType
|
||||
@@ -32,6 +33,15 @@ def ensure_cv2_image_for_class_method(annotate_func):
|
||||
return wrapper
|
||||
|
||||
|
||||
@deprecated(
|
||||
target=ensure_cv2_image_for_class_method,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
)
|
||||
def ensure_cv2_image_for_annotation(annotate_func):
|
||||
pass
|
||||
|
||||
|
||||
def ensure_cv2_image_for_standalone_function(image_processing_fun):
|
||||
"""
|
||||
Decorates image processing functions that accept np.ndarray, converting `image` to
|
||||
@@ -79,6 +89,24 @@ def ensure_pil_image_for_class_method(annotate_func):
|
||||
return wrapper
|
||||
|
||||
|
||||
@deprecated(
|
||||
target=ensure_pil_image_for_class_method,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
)
|
||||
def ensure_pil_image_for_annotation(annotate_func):
|
||||
pass
|
||||
|
||||
|
||||
@deprecated(
|
||||
target=ensure_cv2_image_for_standalone_function,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
)
|
||||
def ensure_cv2_image_for_processing(image_processing_fun):
|
||||
pass
|
||||
|
||||
|
||||
def images_to_cv2(images: list[ImageType]) -> list[np.ndarray]:
|
||||
"""
|
||||
Converts images provided either as Pillow images or OpenCV
|
||||
|
||||
+375
-3
@@ -1,19 +1,33 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import itertools
|
||||
import math
|
||||
import os
|
||||
import shutil
|
||||
from collections.abc import Callable
|
||||
from functools import partial
|
||||
from typing import Literal
|
||||
|
||||
import cv2
|
||||
import numpy as np
|
||||
import numpy.typing as npt
|
||||
from deprecate import deprecated
|
||||
from PIL import Image
|
||||
|
||||
from supervision.annotators.base import ImageType
|
||||
from supervision.draw.color import Color, unify_to_bgr
|
||||
from supervision.draw.utils import calculate_optimal_text_scale, draw_text
|
||||
from supervision.geometry.core import Point
|
||||
from supervision.utils.conversion import (
|
||||
cv2_to_pillow,
|
||||
ensure_cv2_image_for_standalone_function,
|
||||
images_to_cv2,
|
||||
)
|
||||
from supervision.utils.internal import deprecated
|
||||
from supervision.utils.iterables import create_batches, fill
|
||||
|
||||
RelativePosition = Literal["top", "bottom"]
|
||||
|
||||
MAX_COLUMNS_FOR_SINGLE_ROW_GRID = 3
|
||||
|
||||
|
||||
def crop_image(
|
||||
@@ -284,8 +298,9 @@ def letterbox_image(
|
||||
|
||||
|
||||
@deprecated(
|
||||
"`overlay_image` function is deprecated and will be removed in "
|
||||
"`supervision-0.32.0`. Use `draw_image` instead."
|
||||
target=None,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
)
|
||||
def overlay_image(
|
||||
image: npt.NDArray[np.uint8],
|
||||
@@ -627,3 +642,360 @@ class ImageSink:
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
pass
|
||||
|
||||
|
||||
@deprecated(
|
||||
target=None,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
)
|
||||
def create_tiles(
|
||||
images: list[ImageType],
|
||||
grid_size: tuple[int | None, int | None] | None = None,
|
||||
single_tile_size: tuple[int, int] | None = None,
|
||||
tile_scaling: Literal["min", "max", "avg"] = "avg",
|
||||
tile_padding_color: tuple[int, int, int] | Color = Color.from_hex("#D9D9D9"),
|
||||
tile_margin: int = 10,
|
||||
tile_margin_color: tuple[int, int, int] | Color = Color.from_hex("#BFBEBD"),
|
||||
return_type: Literal["auto", "cv2", "pillow"] = "auto",
|
||||
titles: list[str | None] | None = None,
|
||||
titles_anchors: Point | list[Point | None] | None = None,
|
||||
titles_color: tuple[int, int, int] | Color = Color.from_hex("#262523"),
|
||||
titles_scale: float | None = None,
|
||||
titles_thickness: int = 1,
|
||||
titles_padding: int = 10,
|
||||
titles_text_font: int = cv2.FONT_HERSHEY_SIMPLEX,
|
||||
titles_background_color: tuple[int, int, int] | Color = Color.from_hex("#D9D9D9"),
|
||||
default_title_placement: RelativePosition = "top",
|
||||
) -> ImageType:
|
||||
"""
|
||||
Creates tiles mosaic from input images, automating grid placement and
|
||||
converting images to common resolution maintaining aspect ratio. It is
|
||||
also possible to render text titles on tiles, using optional set of
|
||||
parameters specifying text drawing (see parameters description).
|
||||
|
||||
Automated grid placement will try to maintain square shape of grid
|
||||
(with size being the nearest integer square root of #images), up to two exceptions:
|
||||
* if there are up to 3 images - images will be displayed in single row
|
||||
* if square-grid placement causes last row to be empty - number of rows is trimmed
|
||||
until last row has at least one image
|
||||
|
||||
Args:
|
||||
images (List[ImageType]): Images to create tiles. Elements can be either
|
||||
np.ndarray or PIL.Image, common representation will be agreed by the
|
||||
function.
|
||||
grid_size (Optional[Tuple[Optional[int], Optional[int]]]): Expected grid
|
||||
size in format (n_rows, n_cols). If not given - automated grid placement
|
||||
will be applied. One may also provide only one out of two elements of the
|
||||
tuple - then grid will be created with either n_rows or n_cols fixed,
|
||||
leaving the other dimension to be adjusted by the number of images
|
||||
single_tile_size (Optional[Tuple[int, int]]): sizeof a single tile element
|
||||
provided in (width, height) format. If not given - size of tile will be
|
||||
automatically calculated based on `tile_scaling` parameter.
|
||||
tile_scaling (Literal["min", "max", "avg"]): If `single_tile_size` is not
|
||||
given - parameter will be used to calculate tile size - using
|
||||
min / max / avg size of image provided in `images` list.
|
||||
tile_padding_color (Union[Tuple[int, int, int], sv.Color]): Color to be used in
|
||||
images letterbox procedure (while standardising tiles sizes) as a padding.
|
||||
If tuple provided - should be BGR.
|
||||
tile_margin (int): size of margin between tiles (in pixels)
|
||||
tile_margin_color (Union[Tuple[int, int, int], sv.Color]): Color of tile margin.
|
||||
If tuple provided - should be BGR.
|
||||
return_type (Literal["auto", "cv2", "pillow"]): Parameter dictates the format of
|
||||
return image. One may choose specific type ("cv2" or "pillow") to enforce
|
||||
conversion. "auto" mode takes a majority vote between types of elements in
|
||||
`images` list - resolving draws in favour of OpenCV format. "auto" can be
|
||||
safely used when all input images are of the same type.
|
||||
titles (Optional[List[Optional[str]]]): Optional titles to be added to tiles.
|
||||
Elements of that list may be empty - then specific tile (in order presented
|
||||
in `images` parameter) will not be filled with title. It is possible to
|
||||
provide list of titles shorter than `images` - then remaining titles will
|
||||
be assumed empty.
|
||||
titles_anchors (Optional[Union[Point, List[Optional[Point]]]]): Parameter to
|
||||
specify anchor points for titles. It is possible to specify anchor either
|
||||
globally or for specific tiles (following order of `images`).
|
||||
If not given (either globally, or for specific element of the list),
|
||||
it will be calculated automatically based on `default_title_placement`.
|
||||
titles_color (Union[Tuple[int, int, int], Color]): Color of titles text.
|
||||
If tuple provided - should be BGR.
|
||||
titles_scale (Optional[float]): Scale of titles. If not provided - value will
|
||||
be calculated using `calculate_optimal_text_scale(...)`.
|
||||
titles_thickness (int): Thickness of titles text.
|
||||
titles_padding (int): Size of titles padding.
|
||||
titles_text_font (int): Font to be used to render titles. Must be integer
|
||||
constant representing OpenCV font.
|
||||
(See docs: https://docs.opencv.org/4.x/d6/d6e/group__imgproc__draw.html)
|
||||
titles_background_color (Union[Tuple[int, int, int], Color]): Color of title
|
||||
text padding.
|
||||
default_title_placement (Literal["top", "bottom"]): Parameter specifies title
|
||||
anchor placement in case if explicit anchor is not provided.
|
||||
|
||||
Returns:
|
||||
ImageType: Image with all input images located in tails grid. The output type is
|
||||
determined by `return_type` parameter.
|
||||
|
||||
Raises:
|
||||
ValueError: In case when input images list is empty, provided `grid_size` is too
|
||||
small to fit all images, `tile_scaling` mode is invalid.
|
||||
"""
|
||||
if len(images) == 0:
|
||||
raise ValueError("Could not create image tiles from empty list of images.")
|
||||
if return_type == "auto":
|
||||
return_type = _negotiate_tiles_format(images=images)
|
||||
tile_padding_color = unify_to_bgr(color=tile_padding_color)
|
||||
tile_margin_color = unify_to_bgr(color=tile_margin_color)
|
||||
images = images_to_cv2(images=images)
|
||||
if single_tile_size is None:
|
||||
single_tile_size = _aggregate_images_shape(images=images, mode=tile_scaling)
|
||||
resized_images = [
|
||||
letterbox_image(
|
||||
image=i, resolution_wh=single_tile_size, color=tile_padding_color
|
||||
)
|
||||
for i in images
|
||||
]
|
||||
grid_size = _establish_grid_size(images=images, grid_size=grid_size)
|
||||
if len(images) > grid_size[0] * grid_size[1]:
|
||||
raise ValueError(
|
||||
f"Could not place {len(images)} in grid with size: {grid_size}."
|
||||
)
|
||||
if titles is not None:
|
||||
titles = fill(sequence=titles, desired_size=len(images), content=None)
|
||||
titles_anchors = (
|
||||
[titles_anchors]
|
||||
if not issubclass(type(titles_anchors), list)
|
||||
else titles_anchors
|
||||
)
|
||||
titles_anchors = fill(
|
||||
sequence=titles_anchors, desired_size=len(images), content=None
|
||||
)
|
||||
titles_color = unify_to_bgr(color=titles_color)
|
||||
titles_background_color = unify_to_bgr(color=titles_background_color)
|
||||
tiles = _generate_tiles(
|
||||
images=resized_images,
|
||||
grid_size=grid_size,
|
||||
single_tile_size=single_tile_size,
|
||||
tile_padding_color=tile_padding_color,
|
||||
tile_margin=tile_margin,
|
||||
tile_margin_color=tile_margin_color,
|
||||
titles=titles,
|
||||
titles_anchors=titles_anchors,
|
||||
titles_color=titles_color,
|
||||
titles_scale=titles_scale,
|
||||
titles_thickness=titles_thickness,
|
||||
titles_padding=titles_padding,
|
||||
titles_text_font=titles_text_font,
|
||||
titles_background_color=titles_background_color,
|
||||
default_title_placement=default_title_placement,
|
||||
)
|
||||
if return_type == "pillow":
|
||||
tiles = cv2_to_pillow(image=tiles)
|
||||
return tiles
|
||||
|
||||
|
||||
def _negotiate_tiles_format(images: list[ImageType]) -> Literal["cv2", "pillow"]:
|
||||
number_of_np_arrays = sum(issubclass(type(i), np.ndarray) for i in images)
|
||||
if number_of_np_arrays >= (len(images) // 2):
|
||||
return "cv2"
|
||||
return "pillow"
|
||||
|
||||
|
||||
def _calculate_aggregated_images_shape(
|
||||
images: list[np.ndarray], aggregator: Callable[[list[int]], float]
|
||||
) -> tuple[int, int]:
|
||||
height = round(aggregator([i.shape[0] for i in images]))
|
||||
width = round(aggregator([i.shape[1] for i in images]))
|
||||
return width, height
|
||||
|
||||
|
||||
SHAPE_AGGREGATION_FUN = {
|
||||
"min": partial(_calculate_aggregated_images_shape, aggregator=np.min),
|
||||
"max": partial(_calculate_aggregated_images_shape, aggregator=np.max),
|
||||
"avg": partial(_calculate_aggregated_images_shape, aggregator=np.average),
|
||||
}
|
||||
|
||||
|
||||
def _aggregate_images_shape(
|
||||
images: list[np.ndarray], mode: Literal["min", "max", "avg"]
|
||||
) -> tuple[int, int]:
|
||||
if mode not in SHAPE_AGGREGATION_FUN:
|
||||
raise ValueError(
|
||||
f"Could not aggregate images shape - provided unknown mode: {mode}. "
|
||||
f"Supported modes: {list(SHAPE_AGGREGATION_FUN.keys())}."
|
||||
)
|
||||
return SHAPE_AGGREGATION_FUN[mode](images)
|
||||
|
||||
|
||||
def _establish_grid_size(
|
||||
images: list[np.ndarray], grid_size: tuple[int | None, int | None] | None
|
||||
) -> tuple[int, int]:
|
||||
if grid_size is None or all(e is None for e in grid_size):
|
||||
return _negotiate_grid_size(images=images)
|
||||
if grid_size[0] is None:
|
||||
return math.ceil(len(images) / grid_size[1]), grid_size[1]
|
||||
if grid_size[1] is None:
|
||||
return grid_size[0], math.ceil(len(images) / grid_size[0])
|
||||
return grid_size
|
||||
|
||||
|
||||
def _negotiate_grid_size(images: list[np.ndarray]) -> tuple[int, int]:
|
||||
if len(images) <= MAX_COLUMNS_FOR_SINGLE_ROW_GRID:
|
||||
return 1, len(images)
|
||||
nearest_sqrt = math.ceil(np.sqrt(len(images)))
|
||||
proposed_columns = nearest_sqrt
|
||||
proposed_rows = nearest_sqrt
|
||||
while proposed_columns * (proposed_rows - 1) >= len(images):
|
||||
proposed_rows -= 1
|
||||
return proposed_rows, proposed_columns
|
||||
|
||||
|
||||
def _generate_tiles(
|
||||
images: list[np.ndarray],
|
||||
grid_size: tuple[int, int],
|
||||
single_tile_size: tuple[int, int],
|
||||
tile_padding_color: tuple[int, int, int],
|
||||
tile_margin: int,
|
||||
tile_margin_color: tuple[int, int, int],
|
||||
titles: list[str | None] | None,
|
||||
titles_anchors: list[Point | None],
|
||||
titles_color: tuple[int, int, int],
|
||||
titles_scale: float | None,
|
||||
titles_thickness: int,
|
||||
titles_padding: int,
|
||||
titles_text_font: int,
|
||||
titles_background_color: tuple[int, int, int],
|
||||
default_title_placement: RelativePosition,
|
||||
) -> np.ndarray:
|
||||
images = _draw_texts(
|
||||
images=images,
|
||||
titles=titles,
|
||||
titles_anchors=titles_anchors,
|
||||
titles_color=titles_color,
|
||||
titles_scale=titles_scale,
|
||||
titles_thickness=titles_thickness,
|
||||
titles_padding=titles_padding,
|
||||
titles_text_font=titles_text_font,
|
||||
titles_background_color=titles_background_color,
|
||||
default_title_placement=default_title_placement,
|
||||
)
|
||||
rows, columns = grid_size
|
||||
tiles_elements = list(create_batches(sequence=images, batch_size=columns))
|
||||
while len(tiles_elements[-1]) < columns:
|
||||
tiles_elements[-1].append(
|
||||
_generate_color_image(shape=single_tile_size, color=tile_padding_color)
|
||||
)
|
||||
while len(tiles_elements) < rows:
|
||||
tiles_elements.append(
|
||||
[_generate_color_image(shape=single_tile_size, color=tile_padding_color)]
|
||||
* columns
|
||||
)
|
||||
return _merge_tiles_elements(
|
||||
tiles_elements=tiles_elements,
|
||||
grid_size=grid_size,
|
||||
single_tile_size=single_tile_size,
|
||||
tile_margin=tile_margin,
|
||||
tile_margin_color=tile_margin_color,
|
||||
)
|
||||
|
||||
|
||||
def _draw_texts(
|
||||
images: list[np.ndarray],
|
||||
titles: list[str | None] | None,
|
||||
titles_anchors: list[Point | None],
|
||||
titles_color: tuple[int, int, int],
|
||||
titles_scale: float | None,
|
||||
titles_thickness: int,
|
||||
titles_padding: int,
|
||||
titles_text_font: int,
|
||||
titles_background_color: tuple[int, int, int],
|
||||
default_title_placement: RelativePosition,
|
||||
) -> list[np.ndarray]:
|
||||
if titles is None:
|
||||
return images
|
||||
titles_anchors = _prepare_default_titles_anchors(
|
||||
images=images,
|
||||
titles_anchors=titles_anchors,
|
||||
default_title_placement=default_title_placement,
|
||||
)
|
||||
if titles_scale is None:
|
||||
image_height, image_width = images[0].shape[:2]
|
||||
titles_scale = calculate_optimal_text_scale(
|
||||
resolution_wh=(image_width, image_height)
|
||||
)
|
||||
result = []
|
||||
for image, text, anchor in zip(images, titles, titles_anchors):
|
||||
if text is None:
|
||||
result.append(image)
|
||||
continue
|
||||
processed_image = draw_text(
|
||||
scene=image,
|
||||
text=text,
|
||||
text_anchor=anchor,
|
||||
text_color=Color.from_bgr_tuple(titles_color),
|
||||
text_scale=titles_scale,
|
||||
text_thickness=titles_thickness,
|
||||
text_padding=titles_padding,
|
||||
text_font=titles_text_font,
|
||||
background_color=Color.from_bgr_tuple(titles_background_color),
|
||||
)
|
||||
result.append(processed_image)
|
||||
return result
|
||||
|
||||
|
||||
def _prepare_default_titles_anchors(
|
||||
images: list[np.ndarray],
|
||||
titles_anchors: list[Point | None],
|
||||
default_title_placement: RelativePosition,
|
||||
) -> list[Point]:
|
||||
result = []
|
||||
for image, anchor in zip(images, titles_anchors):
|
||||
if anchor is not None:
|
||||
result.append(anchor)
|
||||
continue
|
||||
image_height, image_width = image.shape[:2]
|
||||
if default_title_placement == "top":
|
||||
default_anchor = Point(x=image_width / 2, y=image_height * 0.1)
|
||||
else:
|
||||
default_anchor = Point(x=image_width / 2, y=image_height * 0.9)
|
||||
result.append(default_anchor)
|
||||
return result
|
||||
|
||||
|
||||
def _merge_tiles_elements(
|
||||
tiles_elements: list[list[np.ndarray]],
|
||||
grid_size: tuple[int, int],
|
||||
single_tile_size: tuple[int, int],
|
||||
tile_margin: int,
|
||||
tile_margin_color: tuple[int, int, int],
|
||||
) -> np.ndarray:
|
||||
vertical_padding = (
|
||||
np.ones((single_tile_size[1], tile_margin, 3)) * tile_margin_color
|
||||
)
|
||||
merged_rows = [
|
||||
np.concatenate(
|
||||
list(
|
||||
itertools.chain.from_iterable(
|
||||
zip(row, [vertical_padding] * grid_size[1])
|
||||
)
|
||||
)[:-1],
|
||||
axis=1,
|
||||
)
|
||||
for row in tiles_elements
|
||||
]
|
||||
row_width = merged_rows[0].shape[1]
|
||||
horizontal_padding = (
|
||||
np.ones((tile_margin, row_width, 3), dtype=np.uint8) * tile_margin_color
|
||||
)
|
||||
rows_with_paddings = []
|
||||
for row in merged_rows:
|
||||
rows_with_paddings.append(row)
|
||||
rows_with_paddings.append(horizontal_padding)
|
||||
return np.concatenate(
|
||||
rows_with_paddings[:-1],
|
||||
axis=0,
|
||||
).astype(np.uint8)
|
||||
|
||||
|
||||
def _generate_color_image(
|
||||
shape: tuple[int, int], color: tuple[int, int, int]
|
||||
) -> np.ndarray:
|
||||
return np.ones((*shape[::-1], 3), dtype=np.uint8) * color
|
||||
|
||||
@@ -113,30 +113,6 @@ def deprecated_parameter(
|
||||
return decorator
|
||||
|
||||
|
||||
def deprecated(reason: str):
|
||||
def decorator(cls_or_func):
|
||||
if inspect.isclass(cls_or_func):
|
||||
original_init = cls_or_func.__init__
|
||||
|
||||
@functools.wraps(original_init)
|
||||
def new_init(self, *args, **kwargs):
|
||||
warn_deprecated(f"{cls_or_func.__name__} is deprecated: {reason}")
|
||||
original_init(self, *args, **kwargs)
|
||||
|
||||
cls_or_func.__init__ = new_init
|
||||
return cls_or_func
|
||||
else:
|
||||
|
||||
@functools.wraps(cls_or_func)
|
||||
def wrapper(*args, **kwargs):
|
||||
warn_deprecated(f"{cls_or_func.__name__} is deprecated: {reason}")
|
||||
return cls_or_func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from deprecate import deprecated
|
||||
|
||||
|
||||
def validate_xyxy(xyxy: Any) -> None:
|
||||
@@ -66,6 +67,15 @@ def validate_key_point_confidence(confidence: Any, n: int, m: int) -> None:
|
||||
)
|
||||
|
||||
|
||||
@deprecated(
|
||||
target=validate_key_point_confidence,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
)
|
||||
def validate_keypoint_confidence(confidence: Any, n: int, m: int) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def validate_tracker_id(tracker_id: Any, n: int) -> None:
|
||||
expected_shape = f"({n},)"
|
||||
actual_shape = str(getattr(tracker_id, "shape", None))
|
||||
@@ -140,6 +150,17 @@ def validate_key_points_fields(
|
||||
validate_data(data, n)
|
||||
|
||||
|
||||
@deprecated(
|
||||
target=validate_key_points_fields,
|
||||
deprecated_in="0.27.0",
|
||||
remove_in="0.31.0",
|
||||
)
|
||||
def validate_keypoints_fields(
|
||||
xy: Any, class_id: Any, confidence: Any, data: dict[str, Any]
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
|
||||
def validate_resolution(resolution: Any) -> tuple[int, int]:
|
||||
if not (isinstance(resolution, tuple) and len(resolution) == 2):
|
||||
raise ValueError(
|
||||
|
||||
@@ -417,31 +417,27 @@ def test_drop_extra_matches(
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"recall, precision, expected_result, exception",
|
||||
"recall, precision, expected_result",
|
||||
[
|
||||
(
|
||||
np.array([1.0]),
|
||||
np.array([1.0]),
|
||||
1.0,
|
||||
DoesNotRaise(),
|
||||
), # perfect recall and precision
|
||||
(
|
||||
np.array([0.0]),
|
||||
np.array([0.0]),
|
||||
0.0,
|
||||
DoesNotRaise(),
|
||||
), # no recall and precision
|
||||
(
|
||||
np.array([0.0, 0.2, 0.2, 0.8, 0.8, 1.0]),
|
||||
np.array([0.7, 0.8, 0.4, 0.5, 0.1, 0.2]),
|
||||
0.5,
|
||||
DoesNotRaise(),
|
||||
),
|
||||
(
|
||||
np.array([0.0, 0.5, 0.5, 1.0]),
|
||||
np.array([0.75, 0.75, 0.75, 0.75]),
|
||||
0.75,
|
||||
DoesNotRaise(),
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -449,10 +445,10 @@ def test_compute_average_precision(
|
||||
recall: np.ndarray,
|
||||
precision: np.ndarray,
|
||||
expected_result: float,
|
||||
exception: Exception,
|
||||
) -> None:
|
||||
with exception:
|
||||
result = MeanAveragePrecision.compute_average_precision(
|
||||
with DoesNotRaise():
|
||||
# FIX: Use .__func__ to support Python 3.9
|
||||
result = MeanAveragePrecision.compute_average_precision.__func__(
|
||||
recall=recall, precision=precision
|
||||
)
|
||||
assert_almost_equal(result, expected_result, tolerance=0.01)
|
||||
assert_almost_equal(result, expected_result, tolerance=0.01)
|
||||
|
||||
Reference in New Issue
Block a user