"""COCO loading, input normalization and per-category image-level labels. Images are resized to a square `resolution` with bilinear interpolation and normalized with ImageNet statistics. """ from pathlib import Path from typing import Iterable, List, Sequence, Tuple, Union import numpy as np import torch from PIL import Image from .paths import COCO_ROOT MEAN = (0.485, 0.456, 0.406) STD = (0.229, 0.224, 0.225) def _stats(device: str) -> Tuple[torch.Tensor, torch.Tensor]: mean = torch.tensor(MEAN).view(1, 3, 1, 1).to(device) std = torch.tensor(STD).view(1, 3, 1, 1).to(device) return mean, std def normalize(img: Image.Image, resolution: int, device: str) -> torch.Tensor: """PIL image -> (1, 3, R, R) normalized float tensor.""" img = img.convert('RGB').resize((resolution, resolution), Image.BILINEAR) arr = np.asarray(img, dtype=np.uint8).copy() x = torch.from_numpy(arr).permute(2, 0, 1).unsqueeze(0).to(device).float() / 255.0 mean, std = _stats(device) return (x - mean) / std def load_image(image: Union[str, Path, Image.Image, np.ndarray, torch.Tensor], resolution: int, device: str) -> torch.Tensor: """Accept a path, PIL image, HWC array, or CHW tensor; return a batch of 1.""" if isinstance(image, (str, Path)): img = Image.open(image) elif isinstance(image, Image.Image): img = image elif isinstance(image, np.ndarray): img = Image.fromarray(image) elif isinstance(image, torch.Tensor): arr = image.cpu().numpy() if image.ndim == 3 else image[0].cpu().numpy() if arr.shape[0] == 3: arr = arr.transpose(1, 2, 0) img = Image.fromarray((arr * 255).astype('uint8')) else: raise TypeError(f'unsupported image type: {type(image)}') return normalize(img, resolution, device) def coco_split(split: str = 'val2017'): """Return (COCO handle, image-file lookup) for a COCO split.""" from pycocotools.coco import COCO coco = COCO(str(COCO_ROOT / 'annotations' / f'instances_{split}.json')) id_to_file = {i['id']: i['file_name'] for i in coco.loadImgs(coco.getImgIds())} return coco, id_to_file def category_labels(coco, img_ids: Sequence[int], cat_id: int) -> torch.Tensor: """Image-level presence of one category for each id, as a bool tensor.""" have = set(coco.getImgIds(catIds=[cat_id])) return torch.tensor([i in have for i in img_ids], dtype=torch.bool) def image_paths(id_to_file: dict, img_ids: Iterable[int], split: str = 'val2017') -> List[Path]: """Absolute paths for a sequence of image ids within a split.""" root = COCO_ROOT / split return [root / id_to_file[i] for i in img_ids]