"""Helpers for detection tests.""" import os import xml.etree.cElementTree as ET # nosec from glob import glob from typing import List, Tuple import cv2 import numpy as np class BBFromMasks: """Creates temporary XML files from masks for testing. Intended to be used as a context so that the XML files are automatically deleted when the execution goes out of scope. Example: >>> with BBFromMasks(root="/tmp/datasets/MVTec", datast_name="MVTec"): >>> tests_case() Args: root (str, optional): Path to the dataset location. Defaults to "datasets/MVTec". dataset_name (str, optional): Name of the dataset to write to the XML file. Defaults to "MVTec". """ def __init__(self, root: str = "datasets/MVTec", dataset_name: str = "MVTec") -> None: self.root = root self.dataset_name = dataset_name self.generated_xml_files: List[str] = [] def __enter__(self): """Generate XML files.""" for mask_path in glob(os.path.join(self.root, "*/ground_truth/*/*_mask.png")): path_tree = mask_path.split("/") image = cv2.imread(mask_path, cv2.IMREAD_GRAYSCALE) image = np.array(image, dtype=np.uint8) im_size = image.shape contours, _ = cv2.findContours(image, 1, 1) boxes = [] for contour in contours: p1 = [np.min(contour[..., 0]), np.min(contour[..., 1])] p2 = [np.max(contour[..., 0]), np.max(contour[..., 1])] boxes.append([p1, p2]) contents = self._create_xml_contents(boxes, path_tree, im_size) tree = ET.ElementTree(contents) output_loc = "/".join(path_tree[:-1]) + f"/{path_tree[-1].rstrip('_mask.png')}.xml" tree.write(output_loc) # write the xml self.generated_xml_files.append(output_loc) def __exit__(self, _exc_type, _exc_value, _exc_traceback): """Cleans up generated XML files.""" for file in self.generated_xml_files: os.remove(file) def _create_xml_contents( self, boxes: List[List[List[np.int]]], path_tree: List[str], image_size: Tuple[int, int] ) -> ET.Element: """Create the contents of the annotation file in Pascal VOC format. Args: boxes (List[List[List[np.int]]]): The calculated pox corners from the masks path_tree (List[str]): The entire filepath of the mask.png image split into a list image_size (Tuple[int, int]): Tuple of image size for writing into annotation Returns: ET.Element: annotation root element """ annotation = ET.Element("annotation") ET.SubElement(annotation, "folder").text = path_tree[-2] ET.SubElement(annotation, "filename").text = path_tree[-1] source = ET.SubElement(annotation, "source") ET.SubElement(source, "database").text = self.dataset_name ET.SubElement(source, "annotation").text = "PASCAL VOC" size = ET.SubElement(annotation, "size") ET.SubElement(size, "width").text = str(image_size[0]) ET.SubElement(size, "height").text = str(image_size[1]) ET.SubElement(size, "depth").text = "1" for box in boxes: object = ET.SubElement(annotation, "object") ET.SubElement(object, "name").text = "anomaly" ET.SubElement(object, "difficult").text = "1" bndbox = ET.SubElement(object, "bndbox") ET.SubElement(bndbox, "xmin").text = str(box[0][0]) ET.SubElement(bndbox, "ymin").text = str(box[0][1]) ET.SubElement(bndbox, "xmax").text = str(box[1][0]) ET.SubElement(bndbox, "ymax").text = str(box[1][1]) return annotation