LogSAD / anomalib /utils /post_processing.py
zhiqing0205
Add core libraries: anomalib, dinov2, open_clip_local
3de7bf6
raw
history blame
5.55 kB
"""Post Process This module contains utils function to apply post-processing to the output predictions."""
# Copyright (C) 2022-2024 Intel Corporation
# SPDX-License-Identifier: Apache-2.0
import math
import cv2
import numpy as np
from skimage import morphology
def add_label(
image: np.ndarray,
label_name: str,
color: tuple[int, int, int],
confidence: float | None = None,
font_scale: float = 5e-3,
thickness_scale: float = 1e-3,
) -> np.ndarray:
"""Add a label to an image.
Args:
image (np.ndarray): Input image.
label_name (str): Name of the label that will be displayed on the image.
color (tuple[int, int, int]): RGB values for background color of label.
confidence (float | None): confidence score of the label.
font_scale (float): scale of the font size relative to image size. Increase for bigger font.
thickness_scale (float): scale of the font thickness. Increase for thicker font.
Returns:
np.ndarray: Image with label.
"""
image = image.copy()
img_height, img_width, _ = image.shape
font = cv2.FONT_HERSHEY_PLAIN
text = label_name if confidence is None else f"{label_name} ({confidence*100:.0f}%)"
# get font sizing
font_scale = min(img_width, img_height) * font_scale
thickness = math.ceil(min(img_width, img_height) * thickness_scale)
(width, height), baseline = cv2.getTextSize(text, font, fontScale=font_scale, thickness=thickness)
# create label
label_patch = np.zeros((height + baseline, width + baseline, 3), dtype=np.uint8)
label_patch[:, :] = color
cv2.putText(
label_patch,
text,
(0, baseline // 2 + height),
font,
fontScale=font_scale,
thickness=thickness,
color=0,
lineType=cv2.LINE_AA,
)
# add label to image
image[: baseline + height, : baseline + width] = label_patch
return image
def add_normal_label(image: np.ndarray, confidence: float | None = None) -> np.ndarray:
"""Add the normal label to the image."""
return add_label(image, "normal", (225, 252, 134), confidence)
def add_anomalous_label(image: np.ndarray, confidence: float | None = None) -> np.ndarray:
"""Add the anomalous label to the image."""
return add_label(image, "anomalous", (255, 100, 100), confidence)
def anomaly_map_to_color_map(anomaly_map: np.ndarray, normalize: bool = True) -> np.ndarray:
"""Compute anomaly color heatmap.
Args:
anomaly_map (np.ndarray): Final anomaly map computed by the distance metric.
normalize (bool, optional): Bool to normalize the anomaly map prior to applying
the color map. Defaults to True.
Returns:
np.ndarray: [description]
"""
if normalize:
anomaly_map = (anomaly_map - anomaly_map.min()) / np.ptp(anomaly_map)
anomaly_map = anomaly_map * 255
anomaly_map = anomaly_map.astype(np.uint8)
anomaly_map = cv2.applyColorMap(anomaly_map, cv2.COLORMAP_JET)
return cv2.cvtColor(anomaly_map, cv2.COLOR_BGR2RGB)
def superimpose_anomaly_map(
anomaly_map: np.ndarray,
image: np.ndarray,
alpha: float = 0.4,
gamma: int = 0,
normalize: bool = False,
) -> np.ndarray:
"""Superimpose anomaly map on top of in the input image.
Args:
anomaly_map (np.ndarray): Anomaly map
image (np.ndarray): Input image
alpha (float, optional): Weight to overlay anomaly map
on the input image. Defaults to 0.4.
gamma (int, optional): Value to add to the blended image
to smooth the processing. Defaults to 0. Overall,
the formula to compute the blended image is
I' = (alpha*I1 + (1-alpha)*I2) + gamma
normalize: whether or not the anomaly maps should
be normalized to image min-max at image level
Returns:
np.ndarray: Image with anomaly map superimposed on top of it.
"""
anomaly_map = anomaly_map_to_color_map(anomaly_map.squeeze(), normalize=normalize)
return cv2.addWeighted(anomaly_map, alpha, image, (1 - alpha), gamma)
def compute_mask(anomaly_map: np.ndarray, threshold: float, kernel_size: int = 4) -> np.ndarray:
"""Compute anomaly mask via thresholding the predicted anomaly map.
Args:
anomaly_map (np.ndarray): Anomaly map predicted via the model
threshold (float): Value to threshold anomaly scores into 0-1 range.
kernel_size (int): Value to apply morphological operations to the predicted mask. Defaults to 4.
Returns:
Predicted anomaly mask
"""
anomaly_map = anomaly_map.squeeze()
mask: np.ndarray = np.zeros_like(anomaly_map).astype(np.uint8)
mask[anomaly_map > threshold] = 1
kernel = morphology.disk(kernel_size)
mask = morphology.opening(mask, kernel)
mask *= 255
return mask
def draw_boxes(image: np.ndarray, boxes: np.ndarray, color: tuple[int, int, int]) -> np.ndarray:
"""Draw bounding boxes on an image.
Args:
image (np.ndarray): Source image.
boxes (np.nparray): 2D array of shape (N, 4) where each row contains the xyxy coordinates of a bounding box.
color (tuple[int, int, int]): Color of the drawn boxes in RGB format.
Returns:
np.ndarray: Image showing the bounding boxes drawn on top of the source image.
"""
for box in boxes:
x_1, y_1, x_2, y_2 = box.astype(int)
image = cv2.rectangle(image, (x_1, y_1), (x_2, y_2), color=color, thickness=2)
return image