CoolFace
Apppublic

coreml-community/ControlNet-v1-1-Annotators-cpu

sourceHugging Facemitupdated 2y agoView on Hugging Face
15likes
mmdet_wrapper.py274 linesDownload Raw Back to modeling
1# Copyright (c) Facebook, Inc. and its affiliates.2import itertools3import logging4import numpy as np5from collections import OrderedDict6from collections.abc import Mapping7from typing import Dict, List, Optional, Tuple, Union8import torch9from omegaconf import DictConfig, OmegaConf10from torch import Tensor, nn11 12from annotator.oneformer.detectron2.layers import ShapeSpec13from annotator.oneformer.detectron2.structures import BitMasks, Boxes, ImageList, Instances14from annotator.oneformer.detectron2.utils.events import get_event_storage15 16from .backbone import Backbone17 18logger = logging.getLogger(__name__)19 20 21def _to_container(cfg):22    """23    mmdet will assert the type of dict/list.24    So convert omegaconf objects to dict/list.25    """26    if isinstance(cfg, DictConfig):27        cfg = OmegaConf.to_container(cfg, resolve=True)28    from mmcv.utils import ConfigDict29 30    return ConfigDict(cfg)31 32 33class MMDetBackbone(Backbone):34    """35    Wrapper of mmdetection backbones to use in detectron2.36 37    mmdet backbones produce list/tuple of tensors, while detectron2 backbones38    produce a dict of tensors. This class wraps the given backbone to produce39    output in detectron2's convention, so it can be used in place of detectron240    backbones.41    """42 43    def __init__(44        self,45        backbone: Union[nn.Module, Mapping],46        neck: Union[nn.Module, Mapping, None] = None,47        *,48        output_shapes: List[ShapeSpec],49        output_names: Optional[List[str]] = None,50    ):51        """52        Args:53            backbone: either a backbone module or a mmdet config dict that defines a54                backbone. The backbone takes a 4D image tensor and returns a55                sequence of tensors.56            neck: either a backbone module or a mmdet config dict that defines a57                neck. The neck takes outputs of backbone and returns a58                sequence of tensors. If None, no neck is used.59            output_shapes: shape for every output of the backbone (or neck, if given).60                stride and channels are often needed.61            output_names: names for every output of the backbone (or neck, if given).62                By default, will use "out0", "out1", ...63        """64        super().__init__()65        if isinstance(backbone, Mapping):66            from mmdet.models import build_backbone67 68            backbone = build_backbone(_to_container(backbone))69        self.backbone = backbone70 71        if isinstance(neck, Mapping):72            from mmdet.models import build_neck73 74            neck = build_neck(_to_container(neck))75        self.neck = neck76 77        # "Neck" weights, if any, are part of neck itself. This is the interface78        # of mmdet so we follow it. Reference:79        # https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/detectors/two_stage.py80        logger.info("Initializing mmdet backbone weights...")81        self.backbone.init_weights()82        # train() in mmdet modules is non-trivial, and has to be explicitly83        # called. Reference:84        # https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/backbones/resnet.py85        self.backbone.train()86        if self.neck is not None:87            logger.info("Initializing mmdet neck weights ...")88            if isinstance(self.neck, nn.Sequential):89                for m in self.neck:90                    m.init_weights()91            else:92                self.neck.init_weights()93            self.neck.train()94 95        self._output_shapes = output_shapes96        if not output_names:97            output_names = [f"out{i}" for i in range(len(output_shapes))]98        self._output_names = output_names99 100    def forward(self, x) -> Dict[str, Tensor]:101        outs = self.backbone(x)102        if self.neck is not None:103            outs = self.neck(outs)104        assert isinstance(105            outs, (list, tuple)106        ), "mmdet backbone should return a list/tuple of tensors!"107        if len(outs) != len(self._output_shapes):108            raise ValueError(109                "Length of output_shapes does not match outputs from the mmdet backbone: "110                f"{len(outs)} != {len(self._output_shapes)}"111            )112        return {k: v for k, v in zip(self._output_names, outs)}113 114    def output_shape(self) -> Dict[str, ShapeSpec]:115        return {k: v for k, v in zip(self._output_names, self._output_shapes)}116 117 118class MMDetDetector(nn.Module):119    """120    Wrapper of a mmdetection detector model, for detection and instance segmentation.121    Input/output formats of this class follow detectron2's convention, so a122    mmdetection model can be trained and evaluated in detectron2.123    """124 125    def __init__(126        self,127        detector: Union[nn.Module, Mapping],128        *,129        # Default is 32 regardless of model:130        # https://github.com/open-mmlab/mmdetection/tree/master/configs/_base_/datasets131        size_divisibility=32,132        pixel_mean: Tuple[float],133        pixel_std: Tuple[float],134    ):135        """136        Args:137            detector: a mmdet detector, or a mmdet config dict that defines a detector.138            size_divisibility: pad input images to multiple of this number139            pixel_mean: per-channel mean to normalize input image140            pixel_std: per-channel stddev to normalize input image141        """142        super().__init__()143        if isinstance(detector, Mapping):144            from mmdet.models import build_detector145 146            detector = build_detector(_to_container(detector))147        self.detector = detector148        self.detector.init_weights()149        self.size_divisibility = size_divisibility150 151        self.register_buffer("pixel_mean", torch.tensor(pixel_mean).view(-1, 1, 1), False)152        self.register_buffer("pixel_std", torch.tensor(pixel_std).view(-1, 1, 1), False)153        assert (154            self.pixel_mean.shape == self.pixel_std.shape155        ), f"{self.pixel_mean} and {self.pixel_std} have different shapes!"156 157    def forward(self, batched_inputs: List[Dict[str, torch.Tensor]]):158        images = [x["image"].to(self.device) for x in batched_inputs]159        images = [(x - self.pixel_mean) / self.pixel_std for x in images]160        images = ImageList.from_tensors(images, size_divisibility=self.size_divisibility).tensor161        metas = []162        rescale = {"height" in x for x in batched_inputs}163        if len(rescale) != 1:164            raise ValueError("Some inputs have original height/width, but some don't!")165        rescale = list(rescale)[0]166        output_shapes = []167        for input in batched_inputs:168            meta = {}169            c, h, w = input["image"].shape170            meta["img_shape"] = meta["ori_shape"] = (h, w, c)171            if rescale:172                scale_factor = np.array(173                    [w / input["width"], h / input["height"]] * 2, dtype="float32"174                )175                ori_shape = (input["height"], input["width"])176                output_shapes.append(ori_shape)177                meta["ori_shape"] = ori_shape + (c,)178            else:179                scale_factor = 1.0180                output_shapes.append((h, w))181            meta["scale_factor"] = scale_factor182            meta["flip"] = False183            padh, padw = images.shape[-2:]184            meta["pad_shape"] = (padh, padw, c)185            metas.append(meta)186 187        if self.training:188            gt_instances = [x["instances"].to(self.device) for x in batched_inputs]189            if gt_instances[0].has("gt_masks"):190                from mmdet.core import PolygonMasks as mm_PolygonMasks, BitmapMasks as mm_BitMasks191 192                def convert_mask(m, shape):193                    # mmdet mask format194                    if isinstance(m, BitMasks):195                        return mm_BitMasks(m.tensor.cpu().numpy(), shape[0], shape[1])196                    else:197                        return mm_PolygonMasks(m.polygons, shape[0], shape[1])198 199                gt_masks = [convert_mask(x.gt_masks, x.image_size) for x in gt_instances]200                losses_and_metrics = self.detector.forward_train(201                    images,202                    metas,203                    [x.gt_boxes.tensor for x in gt_instances],204                    [x.gt_classes for x in gt_instances],205                    gt_masks=gt_masks,206                )207            else:208                losses_and_metrics = self.detector.forward_train(209                    images,210                    metas,211                    [x.gt_boxes.tensor for x in gt_instances],212                    [x.gt_classes for x in gt_instances],213                )214            return _parse_losses(losses_and_metrics)215        else:216            results = self.detector.simple_test(images, metas, rescale=rescale)217            results = [218                {"instances": _convert_mmdet_result(r, shape)}219                for r, shape in zip(results, output_shapes)220            ]221            return results222 223    @property224    def device(self):225        return self.pixel_mean.device226 227 228# Reference: show_result() in229# https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/detectors/base.py230def _convert_mmdet_result(result, shape: Tuple[int, int]) -> Instances:231    if isinstance(result, tuple):232        bbox_result, segm_result = result233        if isinstance(segm_result, tuple):234            segm_result = segm_result[0]235    else:236        bbox_result, segm_result = result, None237 238    bboxes = torch.from_numpy(np.vstack(bbox_result))  # Nx5239    bboxes, scores = bboxes[:, :4], bboxes[:, -1]240    labels = [241        torch.full((bbox.shape[0],), i, dtype=torch.int32) for i, bbox in enumerate(bbox_result)242    ]243    labels = torch.cat(labels)244    inst = Instances(shape)245    inst.pred_boxes = Boxes(bboxes)246    inst.scores = scores247    inst.pred_classes = labels248 249    if segm_result is not None and len(labels) > 0:250        segm_result = list(itertools.chain(*segm_result))251        segm_result = [torch.from_numpy(x) if isinstance(x, np.ndarray) else x for x in segm_result]252        segm_result = torch.stack(segm_result, dim=0)253        inst.pred_masks = segm_result254    return inst255 256 257# reference: https://github.com/open-mmlab/mmdetection/blob/master/mmdet/models/detectors/base.py258def _parse_losses(losses: Dict[str, Tensor]) -> Dict[str, Tensor]:259    log_vars = OrderedDict()260    for loss_name, loss_value in losses.items():261        if isinstance(loss_value, torch.Tensor):262            log_vars[loss_name] = loss_value.mean()263        elif isinstance(loss_value, list):264            log_vars[loss_name] = sum(_loss.mean() for _loss in loss_value)265        else:266            raise TypeError(f"{loss_name} is not a tensor or list of tensors")267 268        if "loss" not in loss_name:269            # put metrics to storage; don't return them270            storage = get_event_storage()271            value = log_vars.pop(loss_name).cpu().item()272            storage.put_scalar(loss_name, value)273    return log_vars274