CoolFace
Apppublic

EQUES/suspicious-behavior-detector

sourceHugging Faceupdated 2y agoView on Hugging Face
0likes
transforms.py597 linesDownload Raw Back to utils
1from typing import Dict, List, Optional, Tuple, Union2 3import torch4import torchvision5from torch import nn, Tensor6from torchvision import ops7from torchvision.transforms import functional as F, InterpolationMode, transforms as T8 9 10def _flip_coco_person_keypoints(kps, width):11    flip_inds = [0, 2, 1, 4, 3, 6, 5, 8, 7, 10, 9, 12, 11, 14, 13, 16, 15]12    flipped_data = kps[:, flip_inds]13    flipped_data[..., 0] = width - flipped_data[..., 0]14    # Maintain COCO convention that if visibility == 0, then x, y = 015    inds = flipped_data[..., 2] == 016    flipped_data[inds] = 017    return flipped_data18 19 20class Compose:21    def __init__(self, transforms):22        self.transforms = transforms23 24    def __call__(self, image, target):25        for t in self.transforms:26            image, target = t(image, target)27        return image, target28 29class ToTensor(object):30    def __call__(self, image, target):31        image = F.to_tensor(image)32        return image, target33 34class RandomHorizontalFlip(T.RandomHorizontalFlip):35    def forward(36        self, image: Tensor, target: Optional[Dict[str, Tensor]] = None37    ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]:38        if torch.rand(1) < self.p:39            image = F.hflip(image)40            if target is not None:41                _, _, width = F.get_dimensions(image)42                target["boxes"][:, [0, 2]] = width - target["boxes"][:, [2, 0]]43                if "masks" in target:44                    target["masks"] = target["masks"].flip(-1)45                if "keypoints" in target:46                    keypoints = target["keypoints"]47                    keypoints = _flip_coco_person_keypoints(keypoints, width)48                    target["keypoints"] = keypoints49        return image, target50 51 52class PILToTensor(nn.Module):53    def forward(54        self, image: Tensor, target: Optional[Dict[str, Tensor]] = None55    ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]:56        image = F.pil_to_tensor(image)57        return image, target58 59 60class ConvertImageDtype(nn.Module):61    def __init__(self, dtype: torch.dtype) -> None:62        super().__init__()63        self.dtype = dtype64 65    def forward(66        self, image: Tensor, target: Optional[Dict[str, Tensor]] = None67    ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]:68        image = F.convert_image_dtype(image, self.dtype)69        return image, target70 71 72class RandomIoUCrop(nn.Module):73    def __init__(74        self,75        min_scale: float = 0.3,76        max_scale: float = 1.0,77        min_aspect_ratio: float = 0.5,78        max_aspect_ratio: float = 2.0,79        sampler_options: Optional[List[float]] = None,80        trials: int = 40,81    ):82        super().__init__()83        # Configuration similar to https://github.com/weiliu89/caffe/blob/ssd/examples/ssd/ssd_coco.py#L89-L17484        self.min_scale = min_scale85        self.max_scale = max_scale86        self.min_aspect_ratio = min_aspect_ratio87        self.max_aspect_ratio = max_aspect_ratio88        if sampler_options is None:89            sampler_options = [0.0, 0.1, 0.3, 0.5, 0.7, 0.9, 1.0]90        self.options = sampler_options91        self.trials = trials92 93    def forward(94        self, image: Tensor, target: Optional[Dict[str, Tensor]] = None95    ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]:96        if target is None:97            raise ValueError("The targets can't be None for this transform.")98 99        if isinstance(image, torch.Tensor):100            if image.ndimension() not in {2, 3}:101                raise ValueError(f"image should be 2/3 dimensional. Got {image.ndimension()} dimensions.")102            elif image.ndimension() == 2:103                image = image.unsqueeze(0)104 105        _, orig_h, orig_w = F.get_dimensions(image)106 107        while True:108            # sample an option109            idx = int(torch.randint(low=0, high=len(self.options), size=(1,)))110            min_jaccard_overlap = self.options[idx]111            if min_jaccard_overlap >= 1.0:  # a value larger than 1 encodes the leave as-is option112                return image, target113 114            for _ in range(self.trials):115                # check the aspect ratio limitations116                r = self.min_scale + (self.max_scale - self.min_scale) * torch.rand(2)117                new_w = int(orig_w * r[0])118                new_h = int(orig_h * r[1])119                aspect_ratio = new_w / new_h120                if not (self.min_aspect_ratio <= aspect_ratio <= self.max_aspect_ratio):121                    continue122 123                # check for 0 area crops124                r = torch.rand(2)125                left = int((orig_w - new_w) * r[0])126                top = int((orig_h - new_h) * r[1])127                right = left + new_w128                bottom = top + new_h129                if left == right or top == bottom:130                    continue131 132                # check for any valid boxes with centers within the crop area133                cx = 0.5 * (target["boxes"][:, 0] + target["boxes"][:, 2])134                cy = 0.5 * (target["boxes"][:, 1] + target["boxes"][:, 3])135                is_within_crop_area = (left < cx) & (cx < right) & (top < cy) & (cy < bottom)136                if not is_within_crop_area.any():137                    continue138 139                # check at least 1 box with jaccard limitations140                boxes = target["boxes"][is_within_crop_area]141                ious = torchvision.ops.boxes.box_iou(142                    boxes, torch.tensor([[left, top, right, bottom]], dtype=boxes.dtype, device=boxes.device)143                )144                if ious.max() < min_jaccard_overlap:145                    continue146 147                # keep only valid boxes and perform cropping148                target["boxes"] = boxes149                target["labels"] = target["labels"][is_within_crop_area]150                target["boxes"][:, 0::2] -= left151                target["boxes"][:, 1::2] -= top152                target["boxes"][:, 0::2].clamp_(min=0, max=new_w)153                target["boxes"][:, 1::2].clamp_(min=0, max=new_h)154                image = F.crop(image, top, left, new_h, new_w)155 156                return image, target157 158 159class RandomZoomOut(nn.Module):160    def __init__(161        self, fill: Optional[List[float]] = None, side_range: Tuple[float, float] = (1.0, 4.0), p: float = 0.5162    ):163        super().__init__()164        if fill is None:165            fill = [0.0, 0.0, 0.0]166        self.fill = fill167        self.side_range = side_range168        if side_range[0] < 1.0 or side_range[0] > side_range[1]:169            raise ValueError(f"Invalid canvas side range provided {side_range}.")170        self.p = p171 172    @torch.jit.unused173    def _get_fill_value(self, is_pil):174        # type: (bool) -> int175        # We fake the type to make it work on JIT176        return tuple(int(x) for x in self.fill) if is_pil else 0177 178    def forward(179        self, image: Tensor, target: Optional[Dict[str, Tensor]] = None180    ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]:181        if isinstance(image, torch.Tensor):182            if image.ndimension() not in {2, 3}:183                raise ValueError(f"image should be 2/3 dimensional. Got {image.ndimension()} dimensions.")184            elif image.ndimension() == 2:185                image = image.unsqueeze(0)186 187        if torch.rand(1) >= self.p:188            return image, target189 190        _, orig_h, orig_w = F.get_dimensions(image)191 192        r = self.side_range[0] + torch.rand(1) * (self.side_range[1] - self.side_range[0])193        canvas_width = int(orig_w * r)194        canvas_height = int(orig_h * r)195 196        r = torch.rand(2)197        left = int((canvas_width - orig_w) * r[0])198        top = int((canvas_height - orig_h) * r[1])199        right = canvas_width - (left + orig_w)200        bottom = canvas_height - (top + orig_h)201 202        if torch.jit.is_scripting():203            fill = 0204        else:205            fill = self._get_fill_value(F._is_pil_image(image))206 207        image = F.pad(image, [left, top, right, bottom], fill=fill)208        if isinstance(image, torch.Tensor):209            # PyTorch's pad supports only integers on fill. So we need to overwrite the colour210            v = torch.tensor(self.fill, device=image.device, dtype=image.dtype).view(-1, 1, 1)211            image[..., :top, :] = image[..., :, :left] = image[..., (top + orig_h) :, :] = image[212                ..., :, (left + orig_w) :213            ] = v214 215        if target is not None:216            target["boxes"][:, 0::2] += left217            target["boxes"][:, 1::2] += top218 219        return image, target220 221 222class RandomPhotometricDistort(nn.Module):223    def __init__(224        self,225        contrast: Tuple[float, float] = (0.5, 1.5),226        saturation: Tuple[float, float] = (0.5, 1.5),227        hue: Tuple[float, float] = (-0.05, 0.05),228        brightness: Tuple[float, float] = (0.875, 1.125),229        p: float = 0.5,230    ):231        super().__init__()232        self._brightness = T.ColorJitter(brightness=brightness)233        self._contrast = T.ColorJitter(contrast=contrast)234        self._hue = T.ColorJitter(hue=hue)235        self._saturation = T.ColorJitter(saturation=saturation)236        self.p = p237 238    def forward(239        self, image: Tensor, target: Optional[Dict[str, Tensor]] = None240    ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]:241        if isinstance(image, torch.Tensor):242            if image.ndimension() not in {2, 3}:243                raise ValueError(f"image should be 2/3 dimensional. Got {image.ndimension()} dimensions.")244            elif image.ndimension() == 2:245                image = image.unsqueeze(0)246 247        r = torch.rand(7)248 249        if r[0] < self.p:250            image = self._brightness(image)251 252        contrast_before = r[1] < 0.5253        if contrast_before:254            if r[2] < self.p:255                image = self._contrast(image)256 257        if r[3] < self.p:258            image = self._saturation(image)259 260        if r[4] < self.p:261            image = self._hue(image)262 263        if not contrast_before:264            if r[5] < self.p:265                image = self._contrast(image)266 267        if r[6] < self.p:268            channels, _, _ = F.get_dimensions(image)269            permutation = torch.randperm(channels)270 271            is_pil = F._is_pil_image(image)272            if is_pil:273                image = F.pil_to_tensor(image)274                image = F.convert_image_dtype(image)275            image = image[..., permutation, :, :]276            if is_pil:277                image = F.to_pil_image(image)278 279        return image, target280 281 282class ScaleJitter(nn.Module):283    """Randomly resizes the image and its bounding boxes  within the specified scale range.284    The class implements the Scale Jitter augmentation as described in the paper285    `"Simple Copy-Paste is a Strong Data Augmentation Method for Instance Segmentation" <https://arxiv.org/abs/2012.07177>`_.286 287    Args:288        target_size (tuple of ints): The target size for the transform provided in (height, weight) format.289        scale_range (tuple of ints): scaling factor interval, e.g (a, b), then scale is randomly sampled from the290            range a <= scale <= b.291        interpolation (InterpolationMode): Desired interpolation enum defined by292            :class:`torchvision.transforms.InterpolationMode`. Default is ``InterpolationMode.BILINEAR``.293    """294 295    def __init__(296        self,297        target_size: Tuple[int, int],298        scale_range: Tuple[float, float] = (0.1, 2.0),299        interpolation: InterpolationMode = InterpolationMode.BILINEAR,300    ):301        super().__init__()302        self.target_size = target_size303        self.scale_range = scale_range304        self.interpolation = interpolation305 306    def forward(307        self, image: Tensor, target: Optional[Dict[str, Tensor]] = None308    ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]:309        if isinstance(image, torch.Tensor):310            if image.ndimension() not in {2, 3}:311                raise ValueError(f"image should be 2/3 dimensional. Got {image.ndimension()} dimensions.")312            elif image.ndimension() == 2:313                image = image.unsqueeze(0)314 315        _, orig_height, orig_width = F.get_dimensions(image)316 317        scale = self.scale_range[0] + torch.rand(1) * (self.scale_range[1] - self.scale_range[0])318        r = min(self.target_size[1] / orig_height, self.target_size[0] / orig_width) * scale319        new_width = int(orig_width * r)320        new_height = int(orig_height * r)321 322        image = F.resize(image, [new_height, new_width], interpolation=self.interpolation)323 324        if target is not None:325            target["boxes"][:, 0::2] *= new_width / orig_width326            target["boxes"][:, 1::2] *= new_height / orig_height327            if "masks" in target:328                target["masks"] = F.resize(329                    target["masks"], [new_height, new_width], interpolation=InterpolationMode.NEAREST330                )331 332        return image, target333 334 335class FixedSizeCrop(nn.Module):336    def __init__(self, size, fill=0, padding_mode="constant"):337        super().__init__()338        size = tuple(T._setup_size(size, error_msg="Please provide only two dimensions (h, w) for size."))339        self.crop_height = size[0]340        self.crop_width = size[1]341        self.fill = fill  # TODO: Fill is currently respected only on PIL. Apply tensor patch.342        self.padding_mode = padding_mode343 344    def _pad(self, img, target, padding):345        # Taken from the functional_tensor.py pad346        if isinstance(padding, int):347            pad_left = pad_right = pad_top = pad_bottom = padding348        elif len(padding) == 1:349            pad_left = pad_right = pad_top = pad_bottom = padding[0]350        elif len(padding) == 2:351            pad_left = pad_right = padding[0]352            pad_top = pad_bottom = padding[1]353        else:354            pad_left = padding[0]355            pad_top = padding[1]356            pad_right = padding[2]357            pad_bottom = padding[3]358 359        padding = [pad_left, pad_top, pad_right, pad_bottom]360        img = F.pad(img, padding, self.fill, self.padding_mode)361        if target is not None:362            target["boxes"][:, 0::2] += pad_left363            target["boxes"][:, 1::2] += pad_top364            if "masks" in target:365                target["masks"] = F.pad(target["masks"], padding, 0, "constant")366 367        return img, target368 369    def _crop(self, img, target, top, left, height, width):370        img = F.crop(img, top, left, height, width)371        if target is not None:372            boxes = target["boxes"]373            boxes[:, 0::2] -= left374            boxes[:, 1::2] -= top375            boxes[:, 0::2].clamp_(min=0, max=width)376            boxes[:, 1::2].clamp_(min=0, max=height)377 378            is_valid = (boxes[:, 0] < boxes[:, 2]) & (boxes[:, 1] < boxes[:, 3])379 380            target["boxes"] = boxes[is_valid]381            target["labels"] = target["labels"][is_valid]382            if "masks" in target:383                target["masks"] = F.crop(target["masks"][is_valid], top, left, height, width)384 385        return img, target386 387    def forward(self, img, target=None):388        _, height, width = F.get_dimensions(img)389        new_height = min(height, self.crop_height)390        new_width = min(width, self.crop_width)391 392        if new_height != height or new_width != width:393            offset_height = max(height - self.crop_height, 0)394            offset_width = max(width - self.crop_width, 0)395 396            r = torch.rand(1)397            top = int(offset_height * r)398            left = int(offset_width * r)399 400            img, target = self._crop(img, target, top, left, new_height, new_width)401 402        pad_bottom = max(self.crop_height - new_height, 0)403        pad_right = max(self.crop_width - new_width, 0)404        if pad_bottom != 0 or pad_right != 0:405            img, target = self._pad(img, target, [0, 0, pad_right, pad_bottom])406 407        return img, target408 409 410class RandomShortestSize(nn.Module):411    def __init__(412        self,413        min_size: Union[List[int], Tuple[int], int],414        max_size: int,415        interpolation: InterpolationMode = InterpolationMode.BILINEAR,416    ):417        super().__init__()418        self.min_size = [min_size] if isinstance(min_size, int) else list(min_size)419        self.max_size = max_size420        self.interpolation = interpolation421 422    def forward(423        self, image: Tensor, target: Optional[Dict[str, Tensor]] = None424    ) -> Tuple[Tensor, Optional[Dict[str, Tensor]]]:425        _, orig_height, orig_width = F.get_dimensions(image)426 427        min_size = self.min_size[torch.randint(len(self.min_size), (1,)).item()]428        r = min(min_size / min(orig_height, orig_width), self.max_size / max(orig_height, orig_width))429 430        new_width = int(orig_width * r)431        new_height = int(orig_height * r)432 433        image = F.resize(image, [new_height, new_width], interpolation=self.interpolation)434 435        if target is not None:436            target["boxes"][:, 0::2] *= new_width / orig_width437            target["boxes"][:, 1::2] *= new_height / orig_height438            if "masks" in target:439                target["masks"] = F.resize(440                    target["masks"], [new_height, new_width], interpolation=InterpolationMode.NEAREST441                )442 443        return image, target444 445 446def _copy_paste(447    image: torch.Tensor,448    target: Dict[str, Tensor],449    paste_image: torch.Tensor,450    paste_target: Dict[str, Tensor],451    blending: bool = True,452    resize_interpolation: F.InterpolationMode = F.InterpolationMode.BILINEAR,453) -> Tuple[torch.Tensor, Dict[str, Tensor]]:454 455    # Random paste targets selection:456    num_masks = len(paste_target["masks"])457 458    if num_masks < 1:459        # Such degerante case with num_masks=0 can happen with LSJ460        # Let's just return (image, target)461        return image, target462 463    # We have to please torch script by explicitly specifying dtype as torch.long464    random_selection = torch.randint(0, num_masks, (num_masks,), device=paste_image.device)465    random_selection = torch.unique(random_selection).to(torch.long)466 467    paste_masks = paste_target["masks"][random_selection]468    paste_boxes = paste_target["boxes"][random_selection]469    paste_labels = paste_target["labels"][random_selection]470 471    masks = target["masks"]472 473    # We resize source and paste data if they have different sizes474    # This is something we introduced here as originally the algorithm works475    # on equal-sized data (for example, coming from LSJ data augmentations)476    size1 = image.shape[-2:]477    size2 = paste_image.shape[-2:]478    if size1 != size2:479        paste_image = F.resize(paste_image, size1, interpolation=resize_interpolation)480        paste_masks = F.resize(paste_masks, size1, interpolation=F.InterpolationMode.NEAREST)481        # resize bboxes:482        ratios = torch.tensor((size1[1] / size2[1], size1[0] / size2[0]), device=paste_boxes.device)483        paste_boxes = paste_boxes.view(-1, 2, 2).mul(ratios).view(paste_boxes.shape)484 485    paste_alpha_mask = paste_masks.sum(dim=0) > 0486 487    if blending:488        paste_alpha_mask = F.gaussian_blur(489            paste_alpha_mask.unsqueeze(0),490            kernel_size=(5, 5),491            sigma=[492                2.0,493            ],494        )495 496    # Copy-paste images:497    image = (image * (~paste_alpha_mask)) + (paste_image * paste_alpha_mask)498 499    # Copy-paste masks:500    masks = masks * (~paste_alpha_mask)501    non_all_zero_masks = masks.sum((-1, -2)) > 0502    masks = masks[non_all_zero_masks]503 504    # Do a shallow copy of the target dict505    out_target = {k: v for k, v in target.items()}506 507    out_target["masks"] = torch.cat([masks, paste_masks])508 509    # Copy-paste boxes and labels510    boxes = ops.masks_to_boxes(masks)511    out_target["boxes"] = torch.cat([boxes, paste_boxes])512 513    labels = target["labels"][non_all_zero_masks]514    out_target["labels"] = torch.cat([labels, paste_labels])515 516    # Update additional optional keys: area and iscrowd if exist517    if "area" in target:518        out_target["area"] = out_target["masks"].sum((-1, -2)).to(torch.float32)519 520    if "iscrowd" in target and "iscrowd" in paste_target:521        # target['iscrowd'] size can be differ from mask size (non_all_zero_masks)522        # For example, if previous transforms geometrically modifies masks/boxes/labels but523        # does not update "iscrowd"524        if len(target["iscrowd"]) == len(non_all_zero_masks):525            iscrowd = target["iscrowd"][non_all_zero_masks]526            paste_iscrowd = paste_target["iscrowd"][random_selection]527            out_target["iscrowd"] = torch.cat([iscrowd, paste_iscrowd])528 529    # Check for degenerated boxes and remove them530    boxes = out_target["boxes"]531    degenerate_boxes = boxes[:, 2:] <= boxes[:, :2]532    if degenerate_boxes.any():533        valid_targets = ~degenerate_boxes.any(dim=1)534 535        out_target["boxes"] = boxes[valid_targets]536        out_target["masks"] = out_target["masks"][valid_targets]537        out_target["labels"] = out_target["labels"][valid_targets]538 539        if "area" in out_target:540            out_target["area"] = out_target["area"][valid_targets]541        if "iscrowd" in out_target and len(out_target["iscrowd"]) == len(valid_targets):542            out_target["iscrowd"] = out_target["iscrowd"][valid_targets]543 544    return image, out_target545 546 547class SimpleCopyPaste(torch.nn.Module):548    def __init__(self, blending=True, resize_interpolation=F.InterpolationMode.BILINEAR):549        super().__init__()550        self.resize_interpolation = resize_interpolation551        self.blending = blending552 553    def forward(554        self, images: List[torch.Tensor], targets: List[Dict[str, Tensor]]555    ) -> Tuple[List[torch.Tensor], List[Dict[str, Tensor]]]:556        torch._assert(557            isinstance(images, (list, tuple)) and all([isinstance(v, torch.Tensor) for v in images]),558            "images should be a list of tensors",559        )560        torch._assert(561            isinstance(targets, (list, tuple)) and len(images) == len(targets),562            "targets should be a list of the same size as images",563        )564        for target in targets:565            # Can not check for instance type dict with inside torch.jit.script566            # torch._assert(isinstance(target, dict), "targets item should be a dict")567            for k in ["masks", "boxes", "labels"]:568                torch._assert(k in target, f"Key {k} should be present in targets")569                torch._assert(isinstance(target[k], torch.Tensor), f"Value for the key {k} should be a tensor")570 571        # images = [t1, t2, ..., tN]572        # Let's define paste_images as shifted list of input images573        # paste_images = [t2, t3, ..., tN, t1]574        # FYI: in TF they mix data on the dataset level575        images_rolled = images[-1:] + images[:-1]576        targets_rolled = targets[-1:] + targets[:-1]577 578        output_images: List[torch.Tensor] = []579        output_targets: List[Dict[str, Tensor]] = []580 581        for image, target, paste_image, paste_target in zip(images, targets, images_rolled, targets_rolled):582            output_image, output_data = _copy_paste(583                image,584                target,585                paste_image,586                paste_target,587                blending=self.blending,588                resize_interpolation=self.resize_interpolation,589            )590            output_images.append(output_image)591            output_targets.append(output_data)592 593        return output_images, output_targets594 595    def __repr__(self) -> str:596        s = f"{self.__class__.__name__}(blending={self.blending}, resize_interpolation={self.resize_interpolation})"597        return s