Module sagemaker_defect_detection.utils.visualize
None
None
View Source
from typing import Iterable, List, Union, Tuple
import numpy as np
import torch
from matplotlib import pyplot as plt
import cv2
import torch
TEXT_COLOR = (255, 255, 255)  # White
CLASSES = {
    "crazing": "Cr",
    "inclusion": "In",
    "pitted_surface": "PS",
    "patches": "Pa",
    "rolled-in_scale": "RS",
    "scratches": "Sc",
}
CATEGORY_ID_TO_NAME = {i: name for i, name in enumerate(CLASSES.keys(), start=1)}
def unnormalize_to_hwc(
    image: torch.Tensor, mean: List[float] = [0.485, 0.456, 0.406], std: List[float] = [0.229, 0.224, 0.225]
) -> np.ndarray:
    """
    Unnormalizes and a normlized image tensor [0, 1] CHW -> HWC [0, 255]
    Parameters
    ----------
    image : torch.Tensor
        Normalized image
    mean : List[float], optional
        RGB averages used in normalization, by default [0.485, 0.456, 0.406] from imagenet1k
    std : List[float], optional
        RGB standard deviations used in normalization, by default [0.229, 0.224, 0.225] from imagenet1k
    Returns
    -------
    np.ndarray
        Unnormalized image as numpy array
    """
    image = image.numpy().transpose(1, 2, 0)  # HWC
    image = (image * std + mean).clip(0, 1)
    image = (image * 255).astype(np.uint8)
    return image
def visualize_bbox(img: np.ndarray, bbox: np.ndarray, class_name: str, color, thickness: int = 2) -> np.ndarray:
    """
    Uses cv2 to draw colored bounding boxes and class names in an image
    Parameters
    ----------
    img : np.ndarray
        [description]
    bbox : np.ndarray
        [description]
    class_name : str
        Class name
    color : tuple
        BGR tuple
    thickness : int, optional
        Bouding box thickness, by default 2
    """
    x_min, y_min, x_max, y_max = tuple(map(int, bbox))
    cv2.rectangle(img, (x_min, y_min), (x_max, y_max), color=color, thickness=thickness)
    ((text_width, text_height), _) = cv2.getTextSize(class_name, cv2.FONT_HERSHEY_SIMPLEX, 0.35, 1)
    cv2.rectangle(img, (x_min, y_min - int(1.3 * text_height)), (x_min + text_width, y_min), color, -1)
    cv2.putText(
        img,
        text=class_name,
        org=(x_min, y_min - int(0.3 * text_height)),
        fontFace=cv2.FONT_HERSHEY_SIMPLEX,
        fontScale=0.35,
        color=TEXT_COLOR,
        lineType=cv2.LINE_AA,
    )
    return img
def visualize(
    image: np.ndarray,
    bboxes: Iterable[Union[torch.Tensor, np.ndarray]] = [],
    category_ids: Iterable[Union[torch.Tensor, np.ndarray]] = [],
    colors: Iterable[Tuple[int, int, int]] = [],
    titles: Iterable[str] = [],
    category_id_to_name=CATEGORY_ID_TO_NAME,
    dpi=150,
) -> None:
    """
    Applies the bounding boxes and category ids to an image
    Parameters
    ----------
    image : np.ndarray
        Image as numpy array
    bboxes : Iterable[Union[torch.Tensor, np.ndarray]], optional
        Bouding boxes, by default []
    category_ids : Iterable[Union[torch.Tensor, np.ndarray]], optional
        Category ids, by default []
    colors : Iterable[Tuple[int, int, int]], optional
        Colors for each bounding box, by default [()]
    titles : Iterable[str], optional
        Titles for each image, by default []
    category_id_to_name : Dict[str, str], optional
        Dictionary of category ids to names, by default CATEGORY_ID_TO_NAME
    dpi : int, optional
        DPI for clarity, by default 150
    """
    bboxes, category_ids, colors, titles = list(map(list, [bboxes, category_ids, colors, titles]))  # type: ignore
    n = len(bboxes)
    assert (
        n == len(category_ids) == len(colors) == len(titles) - 1
    ), f"number of bboxes, category ids, colors and titles (minus one) do not match"
    plt.figure(dpi=dpi)
    ncols = n + 1
    plt.subplot(1, ncols, 1)
    img = image.copy()
    plt.axis("off")
    plt.title(titles[0])
    plt.imshow(image)
    if not len(bboxes):
        return
    titles = titles[1:]
    for i in range(2, ncols + 1):
        img = image.copy()
        plt.subplot(1, ncols, i)
        plt.axis("off")
        j = i - 2
        plt.title(titles[j])
        for bbox, category_id in zip(bboxes[j], category_ids[j]):  # type: ignore
            if isinstance(bbox, torch.Tensor):
                bbox = bbox.numpy()
            if isinstance(category_id, torch.Tensor):
                category_id = category_id.numpy()
            if isinstance(category_id, np.ndarray):
                category_id = category_id.item()
            class_name = category_id_to_name[category_id]
            img = visualize_bbox(img, bbox, class_name, color=colors[j])
        plt.imshow(img)
    return
Variables
CATEGORY_ID_TO_NAME
CLASSES
TEXT_COLOR
Functions
unnormalize_to_hwc
def unnormalize_to_hwc(
    image: torch.Tensor,
    mean: List[float] = [0.485, 0.456, 0.406],
    std: List[float] = [0.229, 0.224, 0.225]
) -> numpy.ndarray
Unnormalizes and a normlized image tensor [0, 1] CHW -> HWC [0, 255]
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| image | torch.Tensor | Normalized image | None | 
| mean | List[float] | RGB averages used in normalization, by default [0.485, 0.456, 0.406] from imagenet1k | None | 
| std | List[float] | RGB standard deviations used in normalization, by default [0.229, 0.224, 0.225] from imagenet1k | None | 
Returns:
| Type | Description | 
|---|---|
| np.ndarray | Unnormalized image as numpy array | 
View Source
def unnormalize_to_hwc(
    image: torch.Tensor, mean: List[float] = [0.485, 0.456, 0.406], std: List[float] = [0.229, 0.224, 0.225]
) -> np.ndarray:
    """
    Unnormalizes and a normlized image tensor [0, 1] CHW -> HWC [0, 255]
    Parameters
    ----------
    image : torch.Tensor
        Normalized image
    mean : List[float], optional
        RGB averages used in normalization, by default [0.485, 0.456, 0.406] from imagenet1k
    std : List[float], optional
        RGB standard deviations used in normalization, by default [0.229, 0.224, 0.225] from imagenet1k
    Returns
    -------
    np.ndarray
        Unnormalized image as numpy array
    """
    image = image.numpy().transpose(1, 2, 0)  # HWC
    image = (image * std + mean).clip(0, 1)
    image = (image * 255).astype(np.uint8)
    return image
visualize
def visualize(
    image: numpy.ndarray,
    bboxes: Iterable[Union[torch.Tensor, numpy.ndarray]] = [],
    category_ids: Iterable[Union[torch.Tensor, numpy.ndarray]] = [],
    colors: Iterable[Tuple[int, int, int]] = [],
    titles: Iterable[str] = [],
    category_id_to_name={1: 'crazing', 2: 'inclusion', 3: 'pitted_surface', 4: 'patches', 5: 'rolled-in_scale', 6: 'scratches'},
    dpi=150
) -> None
Applies the bounding boxes and category ids to an image
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| image | np.ndarray | Image as numpy array | None | 
| bboxes | Iterable[Union[torch.Tensor, np.ndarray]] | Bouding boxes, by default [] | None | 
| category_ids | Iterable[Union[torch.Tensor, np.ndarray]] | Category ids, by default [] | None | 
| colors | Iterable[Tuple[int, int, int]] | Colors for each bounding box, by default [()] | None | 
| titles | Iterable[str] | Titles for each image, by default [] | None | 
| category_id_to_name | Dict[str, str] | Dictionary of category ids to names, by default CATEGORY_ID_TO_NAME | CATEGORY_ID_TO_NAME | 
| dpi | int | DPI for clarity, by default 150 | 150 | 
View Source
def visualize(
    image: np.ndarray,
    bboxes: Iterable[Union[torch.Tensor, np.ndarray]] = [],
    category_ids: Iterable[Union[torch.Tensor, np.ndarray]] = [],
    colors: Iterable[Tuple[int, int, int]] = [],
    titles: Iterable[str] = [],
    category_id_to_name=CATEGORY_ID_TO_NAME,
    dpi=150,
) -> None:
    """
    Applies the bounding boxes and category ids to an image
    Parameters
    ----------
    image : np.ndarray
        Image as numpy array
    bboxes : Iterable[Union[torch.Tensor, np.ndarray]], optional
        Bouding boxes, by default []
    category_ids : Iterable[Union[torch.Tensor, np.ndarray]], optional
        Category ids, by default []
    colors : Iterable[Tuple[int, int, int]], optional
        Colors for each bounding box, by default [()]
    titles : Iterable[str], optional
        Titles for each image, by default []
    category_id_to_name : Dict[str, str], optional
        Dictionary of category ids to names, by default CATEGORY_ID_TO_NAME
    dpi : int, optional
        DPI for clarity, by default 150
    """
    bboxes, category_ids, colors, titles = list(map(list, [bboxes, category_ids, colors, titles]))  # type: ignore
    n = len(bboxes)
    assert (
        n == len(category_ids) == len(colors) == len(titles) - 1
    ), f"number of bboxes, category ids, colors and titles (minus one) do not match"
    plt.figure(dpi=dpi)
    ncols = n + 1
    plt.subplot(1, ncols, 1)
    img = image.copy()
    plt.axis("off")
    plt.title(titles[0])
    plt.imshow(image)
    if not len(bboxes):
        return
    titles = titles[1:]
    for i in range(2, ncols + 1):
        img = image.copy()
        plt.subplot(1, ncols, i)
        plt.axis("off")
        j = i - 2
        plt.title(titles[j])
        for bbox, category_id in zip(bboxes[j], category_ids[j]):  # type: ignore
            if isinstance(bbox, torch.Tensor):
                bbox = bbox.numpy()
            if isinstance(category_id, torch.Tensor):
                category_id = category_id.numpy()
            if isinstance(category_id, np.ndarray):
                category_id = category_id.item()
            class_name = category_id_to_name[category_id]
            img = visualize_bbox(img, bbox, class_name, color=colors[j])
        plt.imshow(img)
    return
visualize_bbox
def visualize_bbox(
    img: numpy.ndarray,
    bbox: numpy.ndarray,
    class_name: str,
    color,
    thickness: int = 2
) -> numpy.ndarray
Uses cv2 to draw colored bounding boxes and class names in an image
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| img | np.ndarray | [description] | None | 
| bbox | np.ndarray | [description] | None | 
| class_name | str | Class name | None | 
| color | tuple | BGR tuple | None | 
| thickness | int | Bouding box thickness, by default 2 | 2 | 
View Source
def visualize_bbox(img: np.ndarray, bbox: np.ndarray, class_name: str, color, thickness: int = 2) -> np.ndarray:
    """
    Uses cv2 to draw colored bounding boxes and class names in an image
    Parameters
    ----------
    img : np.ndarray
        [description]
    bbox : np.ndarray
        [description]
    class_name : str
        Class name
    color : tuple
        BGR tuple
    thickness : int, optional
        Bouding box thickness, by default 2
    """
    x_min, y_min, x_max, y_max = tuple(map(int, bbox))
    cv2.rectangle(img, (x_min, y_min), (x_max, y_max), color=color, thickness=thickness)
    ((text_width, text_height), _) = cv2.getTextSize(class_name, cv2.FONT_HERSHEY_SIMPLEX, 0.35, 1)
    cv2.rectangle(img, (x_min, y_min - int(1.3 * text_height)), (x_min + text_width, y_min), color, -1)
    cv2.putText(
        img,
        text=class_name,
        org=(x_min, y_min - int(0.3 * text_height)),
        fontFace=cv2.FONT_HERSHEY_SIMPLEX,
        fontScale=0.35,
        color=TEXT_COLOR,
        lineType=cv2.LINE_AA,
    )
    return img