{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"#### Version history\n* V1 - First 60 eps\n* V2 - 90 - 120 eps with lower start learning rate\n* V3 - add conv_head per layer","metadata":{}},{"cell_type":"markdown","source":"# Discuss\nhttps://www.kaggle.com/c/sartorius-cell-instance-segmentation/discussion/292560\n\n# Watch Out \n\nthere are maybe some Bugs in backbone out_channel\nand FPN RPN in channel configuration\n\n# TODO \n1. split annotation to train and valid 【ok】\n2. change learning rate scheduler 【ok】\n3. Enable Amp 【ok】\n\n# Refferences\n1. https://detectron2.readthedocs.io/en/latest/modules/config.html\n2. https://www.kaggle.com/slawekbiel/positive-score-with-detectron-2-3-training","metadata":{}},{"cell_type":"markdown","source":"## Training","metadata":{}},{"cell_type":"code","source":"!pip install 'git+https://github.com/facebookresearch/detectron2.git'","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":true,"_kg_hide-output":true,"execution":{"iopub.status.busy":"2021-12-13T08:21:27.131989Z","iopub.execute_input":"2021-12-13T08:21:27.132323Z","iopub.status.idle":"2021-12-13T08:24:29.602642Z","shell.execute_reply.started":"2021-12-13T08:21:27.132245Z","shell.execute_reply":"2021-12-13T08:24:29.601595Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import detectron2\nfrom pathlib import Path\nimport random, cv2, os\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pycocotools.mask as mask_util\n# import some common detectron2 utilities\nfrom detectron2 import model_zoo\nfrom detectron2.engine import DefaultPredictor, DefaultTrainer\nfrom detectron2.config import get_cfg\nfrom detectron2.utils.visualizer import Visualizer, ColorMode\nfrom detectron2.data import MetadataCatalog, DatasetCatalog\nfrom detectron2.data.datasets import register_coco_instances\nfrom detectron2.utils.logger import setup_logger\nfrom detectron2.evaluation.evaluator import DatasetEvaluator\nsetup_logger()","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:29.604761Z","iopub.execute_input":"2021-12-13T08:24:29.605035Z","iopub.status.idle":"2021-12-13T08:24:30.892103Z","shell.execute_reply.started":"2021-12-13T08:24:29.605001Z","shell.execute_reply":"2021-12-13T08:24:30.891291Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Load the competition data\nThis is very simple once we have our data in the COCO format. See the [part one notebook](https://www.kaggle.com/slawekbiel/positive-score-with-detectron-1-3-input-data/) for details.","metadata":{}},{"cell_type":"code","source":"dataDir=Path('../input/sartorius-cell-instance-segmentation/')\ncfg = get_cfg()\ncfg.INPUT.MASK_FORMAT='bitmask'\nregister_coco_instances('sartorius_train',{}, '../input/sartorius-kfold-coco/fold_json/fold_1_train.json', dataDir)\nregister_coco_instances('sartorius_val',{},'../input/sartorius-kfold-coco/fold_json/fold_1_val.json', dataDir)","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:30.893373Z","iopub.execute_input":"2021-12-13T08:24:30.893632Z","iopub.status.idle":"2021-12-13T08:24:30.899930Z","shell.execute_reply.started":"2021-12-13T08:24:30.893598Z","shell.execute_reply":"2021-12-13T08:24:30.899281Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"metadata = MetadataCatalog.get('sartorius_train')\ntrain_ds = DatasetCatalog.get('sartorius_train')","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:30.902617Z","iopub.execute_input":"2021-12-13T08:24:30.903110Z","iopub.status.idle":"2021-12-13T08:24:32.322110Z","shell.execute_reply.started":"2021-12-13T08:24:30.903053Z","shell.execute_reply":"2021-12-13T08:24:32.321280Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Display a sample file to check the data is loaded correctly","metadata":{"execution":{"iopub.status.busy":"2021-10-20T14:54:34.693045Z","iopub.execute_input":"2021-10-20T14:54:34.693326Z","iopub.status.idle":"2021-10-20T14:54:34.814645Z","shell.execute_reply.started":"2021-10-20T14:54:34.69329Z","shell.execute_reply":"2021-10-20T14:54:34.813452Z"}}},{"cell_type":"code","source":"d = train_ds[42]\nimg = cv2.imread(d[\"file_name\"])\nvisualizer = Visualizer(img[:, :, ::-1], metadata=metadata)\nout = visualizer.draw_dataset_dict(d)\nplt.figure(figsize = (20,15))\nplt.imshow(out.get_image()[:, :, ::-1])","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:32.323362Z","iopub.execute_input":"2021-12-13T08:24:32.323637Z","iopub.status.idle":"2021-12-13T08:24:33.375865Z","shell.execute_reply.started":"2021-12-13T08:24:32.323603Z","shell.execute_reply":"2021-12-13T08:24:33.373522Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Define evaluator \nGenerates lines like this in the training output:\n`[10/27 18:31:26 d2.evaluation.testing]: copypaste: MaP IoU=0.2192638391201311` \n\nSee here for definition: https://www.kaggle.com/c/sartorius-cell-instance-segmentation/overview/evaluation","metadata":{}},{"cell_type":"code","source":"# Taken from https://www.kaggle.com/theoviel/competition-metric-map-iou\ndef precision_at(threshold, iou):\n    matches = iou > threshold\n    true_positives = np.sum(matches, axis=1) == 1  # Correct objects\n    false_positives = np.sum(matches, axis=0) == 0  # Missed objects\n    false_negatives = np.sum(matches, axis=1) == 0  # Extra objects\n    return np.sum(true_positives), np.sum(false_positives), np.sum(false_negatives)\n\ndef score(pred, targ):\n    pred_masks = pred['instances'].pred_masks.cpu().numpy()\n    enc_preds = [mask_util.encode(np.asarray(p, order='F')) for p in pred_masks]\n    enc_targs = list(map(lambda x:x['segmentation'], targ))\n    ious = mask_util.iou(enc_preds, enc_targs, [0]*len(enc_targs))\n    prec = []\n    for t in np.arange(0.5, 1.0, 0.05):\n        tp, fp, fn = precision_at(t, ious)\n        p = tp / (tp + fp + fn)\n        prec.append(p)\n    return np.mean(prec)\n\nclass MAPIOUEvaluator(DatasetEvaluator):\n    def __init__(self, dataset_name):\n        dataset_dicts = DatasetCatalog.get(dataset_name)\n        self.annotations_cache = {item['image_id']:item['annotations'] for item in dataset_dicts}\n            \n    def reset(self):\n        self.scores = []\n\n    def process(self, inputs, outputs):\n        for inp, out in zip(inputs, outputs):\n            if len(out['instances']) == 0:\n                self.scores.append(0)    \n            else:\n                targ = self.annotations_cache[inp['image_id']]\n                self.scores.append(score(out, targ))\n\n    def evaluate(self):\n        return {\"MaP IoU\": np.mean(self.scores)}    ","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:33.376982Z","iopub.execute_input":"2021-12-13T08:24:33.377201Z","iopub.status.idle":"2021-12-13T08:24:33.394179Z","shell.execute_reply.started":"2021-12-13T08:24:33.377172Z","shell.execute_reply":"2021-12-13T08:24:33.393499Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Train\nI haven't done any hyperparameter optimization yet, this is mostly taken as is from the Detectron tutorial. \n\nTraining for 1000 iterations here for demonstration. For a high scoring model you will need to train it longer, closer to 10000 with these settings","metadata":{}},{"cell_type":"code","source":"import math\nimport fvcore.nn.weight_init as weight_init\nimport torch\nimport torch.nn.functional as F\nfrom torch import nn\n\nfrom detectron2.modeling.backbone.build import BACKBONE_REGISTRY","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:33.396165Z","iopub.execute_input":"2021-12-13T08:24:33.396881Z","iopub.status.idle":"2021-12-13T08:24:33.405638Z","shell.execute_reply.started":"2021-12-13T08:24:33.396845Z","shell.execute_reply":"2021-12-13T08:24:33.404812Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from detectron2.modeling.backbone import Backbone\n\nfrom detectron2.layers import (\n    Conv2d,\n    ShapeSpec,\n    get_norm,\n)","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:33.407060Z","iopub.execute_input":"2021-12-13T08:24:33.407889Z","iopub.status.idle":"2021-12-13T08:24:33.413342Z","shell.execute_reply.started":"2021-12-13T08:24:33.407860Z","shell.execute_reply":"2021-12-13T08:24:33.412570Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install timm\nimport timm\nfrom timm.models.efficientnet import *","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:33.414723Z","iopub.execute_input":"2021-12-13T08:24:33.415153Z","iopub.status.idle":"2021-12-13T08:24:46.823123Z","shell.execute_reply.started":"2021-12-13T08:24:33.415117Z","shell.execute_reply":"2021-12-13T08:24:46.822288Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cfg.merge_from_file(model_zoo.get_config_file(\"COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml\"))\ncfg.DATASETS.TRAIN = (\"sartorius_train\",)\ncfg.DATASETS.TEST = (\"sartorius_val\",)\ncfg.DATALOADER.NUM_WORKERS = 2\ncfg.MODEL.WEIGHTS = model_zoo.get_checkpoint_url(\"COCO-InstanceSegmentation/mask_rcnn_R_50_FPN_3x.yaml\")  # Let training initialize from model zoo\n\n# map 0.255 90 eps\n#cfg.MODEL.WEIGHTS = \"../input/retrain-of-efficientnetv2-hacked-detectron2/output/model_final.pth\"\n\n# Options: WarmupMultiStepLR, WarmupCosineLR.\n# See detectron2/solver/build.py for definition.\ncfg.SOLVER.LR_SCHEDULER_NAME = \"WarmupCosineLR\"\n\ncfg.SOLVER.WARMUP_ITERS = 100\ncfg.SOLVER.IMS_PER_BATCH = 2\n\nITERATE_PER_EPOCH = len(DatasetCatalog.get('sartorius_train')) // cfg.SOLVER.IMS_PER_BATCH \n\nprint(\"ITERATE_PER_EPOCH\", ITERATE_PER_EPOCH)\n\ncfg.SOLVER.BASE_LR = 5e-3\ncfg.SOLVER.MAX_ITER = ITERATE_PER_EPOCH * 40\n#cfg.SOLVER.STEPS = (ITERATE_PER_EPOCH * 10, ITERATE_PER_EPOCH * 15)      \n\ncfg.MODEL.ROI_HEADS.BATCH_SIZE_PER_IMAGE = 128   \ncfg.MODEL.ROI_HEADS.NUM_CLASSES = 3  \ncfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = .5\n\ncfg.TEST.EVAL_PERIOD =  ITERATE_PER_EPOCH   # Once per epoch\n\n# Enable automatic mixed precision for training\ncfg.SOLVER.AMP.ENABLED = True\n\n# V4 modify\n\"\"\"\ncfg.MODEL.RPN.BBOX_REG_LOSS_TYPE = \"ciou\"\ncfg.MODEL.RPN.BBOX_REG_LOSS_WEIGHT: 2.0\n        \ncfg.MODEL.ROI_BOX_HEAD.BBOX_REG_LOSS_TYPE = \"ciou\"\ncfg.MODEL.ROI_BOX_HEAD.BBOX_REG_LOSS_WEIGHT: 10.0\n\"\"\"\n        \n        \n#cfg.INPUT.CROP.ENABLED = True\n#cfg.INPUT.CROP.SIZE = [0.9, 0.9]\n\n#cfg.TEST.AUG.ENABLED = True\n\n\"\"\"\n\ncfg.MODEL.ANCHOR_GENERATOR.SIZES = [[16], [32], [64], [128], [256], [512]]  # One size for each in feature map\ncfg.MODEL.FPN.IN_FEATURES = [\"b0\", \"b1\", \"b2\", \"b4\", \"b5\"]\n\ncfg.MODEL.RPN.IN_FEATURES = [\"p1\", \"p2\", \"p3\", \"p4\", \"p5\", \"p6\"]\ncfg.MODEL.ROI_HEADS.IN_FEATURES = [\"p1\", \"p2\", \"p3\", \"p4\", \"p5\"]\n\n\"\"\"\n    \n    \ncfg.MODEL.ANCHOR_GENERATOR.SIZES = [[32], [64], [128], [256], [512]]  # One size for each in feature map\ncfg.MODEL.FPN.IN_FEATURES = [\"b1\", \"b2\", \"b4\", \"b5\"]\n\ncfg.MODEL.RPN.IN_FEATURES = [\"p2\", \"p3\", \"p4\", \"p5\", \"p6\"]\ncfg.MODEL.ROI_HEADS.IN_FEATURES = [\"p2\", \"p3\", \"p4\", \"p5\"]\n\n\nos.makedirs(cfg.OUTPUT_DIR, exist_ok=True)","metadata":{"_kg_hide-output":false,"execution":{"iopub.status.busy":"2021-12-13T08:24:46.824387Z","iopub.execute_input":"2021-12-13T08:24:46.824655Z","iopub.status.idle":"2021-12-13T08:24:47.971717Z","shell.execute_reply.started":"2021-12-13T08:24:46.824623Z","shell.execute_reply":"2021-12-13T08:24:47.970748Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#e = efficientnetv2_rw_s(pretrained=True, drop_path_rate=0.2)\n#print(e)","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:47.975229Z","iopub.execute_input":"2021-12-13T08:24:47.975546Z","iopub.status.idle":"2021-12-13T08:24:47.978881Z","shell.execute_reply.started":"2021-12-13T08:24:47.975506Z","shell.execute_reply":"2021-12-13T08:24:47.977765Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class EfficientnetV2S(Backbone):\n    def __init__(self):\n        super().__init__()\n\n        e = efficientnetv2_rw_s(pretrained=True, drop_path_rate=0.2)\n        \n        all_features = [\"stem\", \"b0\", \"b1\", \"b2\", \"b3\", \"b4\", \"b5\"] \n        out_features = [\"b0\", \"b1\", \"b2\", \"b4\", \"b5\"]\n        # _stride = [2, 1, 2, 2, 2, 1, 2]\n        self._out_feature_strides = {\"stem\": 2, \"b0\": 2, \"b1\": 4, \"b2\": 8, \"b3\": 16, \"b4\": 16, \"b5\": 32}\n        #self._out_feature_channels = {\"stem\": 24, \"b0\": 24, \"b1\": 48, \"b2\": 64, \"b3\": 128, \"b4\": 160, \"b5\": 272}\n        self._out_feature_channels = {\"stem\": 24, \"b0\": 64, \"b1\": 128, \"b2\": 256, \"b3\": 512, \"b4\": 1024, \"b5\": 1792}\n        \n        self.stem = nn.Sequential(\n            e.conv_stem,\n            e.bn1,\n            e.act1,\n        )\n        \n        self.b0 = e.blocks[0]\n        self.b1 = e.blocks[1]\n        self.b2 = e.blocks[2]\n        self.b3 = e.blocks[3]\n        self.b4 = e.blocks[4]\n        self.b5 = e.blocks[5]\n        \n        # 12/07 add some conv head to feature mapping\n        self.b0_conv = nn.Sequential(\n            nn.Conv2d(24, 64, kernel_size=1, padding=0),\n            nn.BatchNorm2d(64),\n            nn.SiLU(inplace=True),\n        )\n        self.b1_conv = nn.Sequential(\n            nn.Conv2d(48, 128, kernel_size=1, padding=0),\n            nn.BatchNorm2d(128),\n            nn.SiLU(inplace=True),\n        )\n        self.b2_conv = nn.Sequential(\n            nn.Conv2d(64, 256, kernel_size=1, padding=0),\n            nn.BatchNorm2d(256),\n            nn.SiLU(inplace=True),\n        )\n        self.b3_conv = nn.Sequential(\n            nn.Conv2d(128, 512, kernel_size=1, padding=0),\n            nn.BatchNorm2d(512),\n            nn.SiLU(inplace=True),\n        )\n        self.b4_conv = nn.Sequential(\n            nn.Conv2d(160, 1024, kernel_size=1, padding=0),\n            nn.BatchNorm2d(1024),\n            nn.SiLU(inplace=True),\n        )\n        self.b5_conv = nn.Sequential(\n            e.conv_head,\n            e.bn2,\n            e.act2,\n        )\n        \n        # shall B7 conv_head be used ? \n\n        self.stages = [e.blocks[n] for n in range(0, 6)]\n        self.out_convs = [self.b0_conv, self.b1_conv, self.b2_conv, self.b3_conv, self.b4_conv, self.b5_conv]\n        \n        self.stage_names = all_features[1:]\n        self._out_features = out_features\n\n\n    def forward(self, x):\n        \"\"\"\n        Args:\n            x: Tensor of shape (N,C,H,W). H, W must be a multiple of ``self.size_divisibility``.\n\n        Returns:\n            dict[str->Tensor]: names and the corresponding features\n        \"\"\"\n        assert x.dim() == 4, f\"ResNet takes an input of shape (N, C, H, W). Got {x.shape} instead!\"\n        outputs = {}\n        x = self.stem(x)\n        if \"stem\" in self._out_features:\n            outputs[\"stem\"] = x\n        for name, stage, out_conv in zip(self.stage_names, self.stages, self.out_convs):\n            x = stage(x)\n            if name in self._out_features:\n                outputs[name] = out_conv(x)\n        return outputs\n\n    def output_shape(self):\n        return {\n            name: ShapeSpec(\n                channels=self._out_feature_channels[name], stride=self._out_feature_strides[name]\n            )\n            for name in self._out_features\n        }","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:47.980767Z","iopub.execute_input":"2021-12-13T08:24:47.981344Z","iopub.status.idle":"2021-12-13T08:24:48.004170Z","shell.execute_reply.started":"2021-12-13T08:24:47.981302Z","shell.execute_reply":"2021-12-13T08:24:48.003385Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del BACKBONE_REGISTRY._obj_map[\"build_resnet_backbone\"] \n@BACKBONE_REGISTRY.register()\ndef build_resnet_backbone(cfg, input_shape):    \n    return EfficientnetV2S()","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:48.005200Z","iopub.execute_input":"2021-12-13T08:24:48.005396Z","iopub.status.idle":"2021-12-13T08:24:48.016859Z","shell.execute_reply.started":"2021-12-13T08:24:48.005371Z","shell.execute_reply":"2021-12-13T08:24:48.016025Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#input_shape = ShapeSpec(channels=3, height=None, width=None, stride=None)\n#bottom_up = build_resnet_backbone(cfg, input_shape)\n#print(\"_out_feature_channels\", bottom_up._out_feature_channels)\n#print(\"_out_feature_strides\", bottom_up._out_feature_strides)\n#print(\"output_shape\", bottom_up.output_shape())","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:48.018107Z","iopub.execute_input":"2021-12-13T08:24:48.018572Z","iopub.status.idle":"2021-12-13T08:24:48.026276Z","shell.execute_reply.started":"2021-12-13T08:24:48.018531Z","shell.execute_reply":"2021-12-13T08:24:48.025379Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from detectron2.modeling.backbone.fpn import _assert_strides_are_log2_contiguous, LastLevelMaxPool\n\nclass FPN(Backbone):\n    \"\"\"\n    This module implements :paper:`FPN`.\n    It creates pyramid features built on top of some input feature maps.\n    \"\"\"\n\n    _fuse_type: torch.jit.Final[str]\n\n    def __init__(\n        self, bottom_up, in_features, out_channels, norm=\"\", top_block=None, fuse_type=\"sum\"\n    ):\n        \"\"\"\n        Args:\n            bottom_up (Backbone): module representing the bottom up subnetwork.\n                Must be a subclass of :class:`Backbone`. The multi-scale feature\n                maps generated by the bottom up network, and listed in `in_features`,\n                are used to generate FPN levels.\n            in_features (list[str]): names of the input feature maps coming\n                from the backbone to which FPN is attached. For example, if the\n                backbone produces [\"res2\", \"res3\", \"res4\"], any *contiguous* sublist\n                of these may be used; order must be from high to low resolution.\n            out_channels (int): number of channels in the output feature maps.\n            norm (str): the normalization to use.\n            top_block (nn.Module or None): if provided, an extra operation will\n                be performed on the output of the last (smallest resolution)\n                FPN output, and the result will extend the result list. The top_block\n                further downsamples the feature map. It must have an attribute\n                \"num_levels\", meaning the number of extra FPN levels added by\n                this block, and \"in_feature\", which is a string representing\n                its input feature (e.g., p5).\n            fuse_type (str): types for fusing the top down features and the lateral\n                ones. It can be \"sum\" (default), which sums up element-wise; or \"avg\",\n                which takes the element-wise mean of the two.\n        \"\"\"\n        super(FPN, self).__init__()\n        assert isinstance(bottom_up, Backbone)\n        assert in_features, in_features\n\n        # Feature map strides and channels from the bottom up network (e.g. ResNet)\n        input_shapes = bottom_up.output_shape()\n        strides = [input_shapes[f].stride for f in in_features]\n        in_channels_per_feature = [input_shapes[f].channels for f in in_features]\n        \n        _assert_strides_are_log2_contiguous(strides)\n        lateral_convs = []\n        output_convs = []\n\n        use_bias = norm == \"\"\n        for idx, in_channels in enumerate(in_channels_per_feature):\n            lateral_norm = get_norm(norm, out_channels)\n            output_norm = get_norm(norm, out_channels)\n\n            lateral_conv = Conv2d(\n                in_channels, out_channels, kernel_size=1, bias=use_bias, norm=lateral_norm\n            )\n            output_conv = Conv2d(\n                out_channels,\n                out_channels,\n                kernel_size=3,\n                stride=1,\n                padding=1,\n                bias=use_bias,\n                norm=output_norm,\n            )\n            weight_init.c2_xavier_fill(lateral_conv)\n            weight_init.c2_xavier_fill(output_conv)\n            stage = int(math.log2(strides[idx]))\n            self.add_module(\"fpn_lateral{}\".format(stage), lateral_conv)\n            self.add_module(\"fpn_output{}\".format(stage), output_conv)\n\n            lateral_convs.append(lateral_conv)\n            output_convs.append(output_conv)\n        # Place convs into top-down order (from low to high resolution)\n        # to make the top-down computation in forward clearer.\n        self.lateral_convs = lateral_convs[::-1]\n        self.output_convs = output_convs[::-1]\n        self.top_block = top_block\n        self.in_features = tuple(in_features)\n        self.bottom_up = bottom_up\n        # Return feature names are \"p<stage>\", like [\"p2\", \"p3\", ..., \"p6\"]\n        self._out_feature_strides = {\"p{}\".format(int(math.log2(s))): s for s in strides}\n        # top block output feature maps.\n        \n        if self.top_block is not None:\n            for s in range(stage, stage + self.top_block.num_levels):\n                self._out_feature_strides[\"p{}\".format(s + 1)] = 2 ** (s + 1)\n\n        self._out_features = list(self._out_feature_strides.keys())\n        self._out_feature_channels = {k: out_channels for k in self._out_features}\n        self._size_divisibility = strides[-1]\n        assert fuse_type in {\"avg\", \"sum\"}\n        self._fuse_type = fuse_type\n\n    @property\n    def size_divisibility(self):\n        return self._size_divisibility\n\n    def forward(self, x):\n        \"\"\"\n        Args:\n            input (dict[str->Tensor]): mapping feature map name (e.g., \"res5\") to\n                feature map tensor for each feature level in high to low resolution order.\n\n        Returns:\n            dict[str->Tensor]:\n                mapping from feature map name to FPN feature map tensor\n                in high to low resolution order. Returned feature names follow the FPN\n                paper convention: \"p<stage>\", where stage has stride = 2 ** stage e.g.,\n                [\"p2\", \"p3\", ..., \"p6\"].\n        \"\"\"\n        bottom_up_features = self.bottom_up(x)\n        results = []\n        prev_features = self.lateral_convs[0](bottom_up_features[self.in_features[-1]])\n        results.append(self.output_convs[0](prev_features))\n\n        # Reverse feature maps into top-down order (from low to high resolution)\n        for idx, (lateral_conv, output_conv) in enumerate(\n            zip(self.lateral_convs, self.output_convs)\n        ):\n            # Slicing of ModuleList is not supported https://github.com/pytorch/pytorch/issues/47336\n            # Therefore we loop over all modules but skip the first one\n            if idx > 0:\n                features = self.in_features[-idx - 1]\n                features = bottom_up_features[features]\n                top_down_features = F.interpolate(prev_features, scale_factor=2.0, mode=\"nearest\")\n                lateral_features = lateral_conv(features)\n                prev_features = lateral_features + top_down_features\n                if self._fuse_type == \"avg\":\n                    prev_features /= 2\n                results.insert(0, output_conv(prev_features))\n\n        if self.top_block is not None:\n            if self.top_block.in_feature in bottom_up_features:\n                top_block_in_feature = bottom_up_features[self.top_block.in_feature]\n            else:\n                top_block_in_feature = results[self._out_features.index(self.top_block.in_feature)]\n            results.extend(self.top_block(top_block_in_feature))\n        assert len(self._out_features) == len(results)\n        return {f: res for f, res in zip(self._out_features, results)}\n\n    def output_shape(self):\n        return {\n            name: ShapeSpec(\n                channels=self._out_feature_channels[name], stride=self._out_feature_strides[name]\n            )\n            for name in self._out_features\n        }\n\ndel BACKBONE_REGISTRY._obj_map[\"build_resnet_fpn_backbone\"] \n@BACKBONE_REGISTRY.register()\ndef build_resnet_fpn_backbone(cfg, input_shape: ShapeSpec):\n\n    \"\"\"\n    Args:\n        cfg: a detectron2 CfgNode\n\n    Returns:\n        backbone (Backbone): backbone module, must be a subclass of :class:`Backbone`.\n    \"\"\"\n    bottom_up = build_resnet_backbone(cfg, input_shape)\n    in_features = cfg.MODEL.FPN.IN_FEATURES\n    out_channels = cfg.MODEL.FPN.OUT_CHANNELS\n    backbone = FPN(\n        bottom_up=bottom_up,\n        in_features=in_features,\n        out_channels=out_channels,\n        norm=cfg.MODEL.FPN.NORM,\n        top_block=LastLevelMaxPool(),\n        fuse_type=cfg.MODEL.FPN.FUSE_TYPE,\n    )\n    return backbone","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:48.027979Z","iopub.execute_input":"2021-12-13T08:24:48.028551Z","iopub.status.idle":"2021-12-13T08:24:48.058566Z","shell.execute_reply.started":"2021-12-13T08:24:48.028512Z","shell.execute_reply":"2021-12-13T08:24:48.057793Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#bb = build_resnet_fpn_backbone(cfg, ShapeSpec(channels=3))\n#print(bb.output_shape())","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:48.059716Z","iopub.execute_input":"2021-12-13T08:24:48.061647Z","iopub.status.idle":"2021-12-13T08:24:48.071314Z","shell.execute_reply.started":"2021-12-13T08:24:48.061604Z","shell.execute_reply":"2021-12-13T08:24:48.070504Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from prettytable import PrettyTable\nimport re\ndef count_parameters(model):\n    table = PrettyTable([\"Modules\", \"Parameters\"])\n    total_params = 0\n    resnet_params = 0\n    for name, parameter in model.named_parameters():\n        if not parameter.requires_grad: continue\n        param = parameter.numel()\n        table.add_row([name, param])\n        if re.search(\"backbone.bottom_up\", name):\n          resnet_params += param\n        total_params+=param\n    print(table)\n    print(f\"Total Trainable Params: {total_params}\")\n    print(f\"Resnet Params: {resnet_params}\")\n    return total_params","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:48.072355Z","iopub.execute_input":"2021-12-13T08:24:48.074702Z","iopub.status.idle":"2021-12-13T08:24:48.082038Z","shell.execute_reply.started":"2021-12-13T08:24:48.074660Z","shell.execute_reply":"2021-12-13T08:24:48.081202Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from detectron2.engine.defaults import create_ddp_model, default_writers\nfrom detectron2.engine import hooks\n\nfrom detectron2.data import transforms as T\nfrom detectron2.data import detection_utils as utils\n\nfrom detectron2.data import (\n    DatasetMapper,\n    build_detection_test_loader,\n    build_detection_train_loader,\n)\n\nfrom detectron2.engine.train_loop import AMPTrainer, SimpleTrainer, TrainerBase\nfrom detectron2.checkpoint import DetectionCheckpointer\nfrom detectron2.modeling import build_model\nfrom detectron2.solver import build_lr_scheduler, build_optimizer\n\n\nfrom detectron2.evaluation import (\n    DatasetEvaluator,\n    inference_on_dataset,\n    print_csv_format,\n    verify_results,\n)\n\nimport weakref\nimport logging\nfrom detectron2.utils import comm\nfrom collections import OrderedDict\n\n\ndef build_customize_aug(cfg):\n    augs = utils.build_augmentation(cfg, True)\n    #T.RandomBrightness(0.9, 1.1),\n    #augs.append(T.RandomFlip(horizontal=False, vertical=True))\n    return augs\n\nclass CustomizeTrainer(TrainerBase):\n    \"\"\"\n    A trainer with default training logic. It does the following:\n\n    1. Create a :class:`SimpleTrainer` using model, optimizer, dataloader\n       defined by the given config. Create a LR scheduler defined by the config.\n    2. Load the last checkpoint or `cfg.MODEL.WEIGHTS`, if exists, when\n       `resume_or_load` is called.\n    3. Register a few common hooks defined by the config.\n\n    It is created to simplify the **standard model training workflow** and reduce code boilerplate\n    for users who only need the standard training workflow, with standard features.\n    It means this class makes *many assumptions* about your training logic that\n    may easily become invalid in a new research. In fact, any assumptions beyond those made in the\n    :class:`SimpleTrainer` are too much for research.\n\n    The code of this class has been annotated about restrictive assumptions it makes.\n    When they do not work for you, you're encouraged to:\n\n    1. Overwrite methods of this class, OR:\n    2. Use :class:`SimpleTrainer`, which only does minimal SGD training and\n       nothing else. You can then add your own hooks if needed. OR:\n    3. Write your own training loop similar to `tools/plain_train_net.py`.\n\n    See the :doc:`/tutorials/training` tutorials for more details.\n\n    Note that the behavior of this class, like other functions/classes in\n    this file, is not stable, since it is meant to represent the \"common default behavior\".\n    It is only guaranteed to work well with the standard models and training workflow in detectron2.\n    To obtain more stable behavior, write your own training logic with other public APIs.\n\n    Examples:\n    ::\n        trainer = DefaultTrainer(cfg)\n        trainer.resume_or_load()  # load last checkpoint or MODEL.WEIGHTS\n        trainer.train()\n\n    Attributes:\n        scheduler:\n        checkpointer (DetectionCheckpointer):\n        cfg (CfgNode):\n    \"\"\"\n\n    def __init__(self, cfg):\n        \"\"\"\n        Args:\n            cfg (CfgNode):\n        \"\"\"\n        super().__init__()\n        logger = logging.getLogger(\"detectron2\")\n        if not logger.isEnabledFor(logging.INFO):  # setup_logger is not called for d2\n            setup_logger()\n        cfg = self.auto_scale_workers(cfg, comm.get_world_size())\n\n        # Assume these objects must be constructed in this order.\n        model = self.build_model(cfg)\n        optimizer = self.build_optimizer(cfg, model)\n        data_loader = self.build_train_loader(cfg)\n\n        model = create_ddp_model(model, broadcast_buffers=False)\n\n        #pytorch_total_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n\n        #print(\"parameters\", sum(dict((p.data_ptr(), p.numel()) for p in model.parameters()).values()))\n        \n        #print(\"pytorch_total_params\", pytorch_total_params)\n        count_parameters(model)\n            \n        self._trainer = (AMPTrainer if cfg.SOLVER.AMP.ENABLED else SimpleTrainer)(\n            model, data_loader, optimizer\n        )\n\n        self.scheduler = self.build_lr_scheduler(cfg, optimizer)\n        self.checkpointer = DetectionCheckpointer(\n            # Assume you want to save checkpoints together with logs/statistics\n            model,\n            cfg.OUTPUT_DIR,\n            trainer=weakref.proxy(self),\n        )\n        self.start_iter = 0\n        self.max_iter = cfg.SOLVER.MAX_ITER\n        self.cfg = cfg\n\n        self.register_hooks(self.build_hooks())\n\n    def resume_or_load(self, resume=True):\n        \"\"\"\n        If `resume==True` and `cfg.OUTPUT_DIR` contains the last checkpoint (defined by\n        a `last_checkpoint` file), resume from the file. Resuming means loading all\n        available states (eg. optimizer and scheduler) and update iteration counter\n        from the checkpoint. ``cfg.MODEL.WEIGHTS`` will not be used.\n\n        Otherwise, this is considered as an independent training. The method will load model\n        weights from the file `cfg.MODEL.WEIGHTS` (but will not load other states) and start\n        from iteration 0.\n\n        Args:\n            resume (bool): whether to do resume or not\n        \"\"\"\n        self.checkpointer.resume_or_load(self.cfg.MODEL.WEIGHTS, resume=resume)\n        if resume and self.checkpointer.has_checkpoint():\n            # The checkpoint stores the training iteration that just finished, thus we start\n            # at the next iteration\n            self.start_iter = self.iter + 1\n\n    def build_hooks(self):\n        \"\"\"\n        Build a list of default hooks, including timing, evaluation,\n        checkpointing, lr scheduling, precise BN, writing events.\n\n        Returns:\n            list[HookBase]:\n        \"\"\"\n        cfg = self.cfg.clone()\n        cfg.defrost()\n        cfg.DATALOADER.NUM_WORKERS = 0  # save some memory and time for PreciseBN\n\n        ret = [\n            hooks.IterationTimer(),\n            hooks.LRScheduler(),\n            hooks.PreciseBN(\n                # Run at the same freq as (but before) evaluation.\n                cfg.TEST.EVAL_PERIOD,\n                self.model,\n                # Build a new data loader to not affect training\n                self.build_train_loader(cfg),\n                cfg.TEST.PRECISE_BN.NUM_ITER,\n            )\n            if cfg.TEST.PRECISE_BN.ENABLED and get_bn_modules(self.model)\n            else None,\n        ]\n\n        # Do PreciseBN before checkpointer, because it updates the model and need to\n        # be saved by checkpointer.\n        # This is not always the best: if checkpointing has a different frequency,\n        # some checkpoints may have more precise statistics than others.\n        if comm.is_main_process():\n            ret.append(hooks.PeriodicCheckpointer(self.checkpointer, cfg.SOLVER.CHECKPOINT_PERIOD))\n\n        def test_and_save_results():\n            self._last_eval_results = self.test(self.cfg, self.model)\n            return self._last_eval_results\n\n        # Do evaluation after checkpointer, because then if it fails,\n        # we can use the saved checkpoint to debug.\n        ret.append(hooks.EvalHook(cfg.TEST.EVAL_PERIOD, test_and_save_results))\n\n        if comm.is_main_process():\n            # Here the default print/log frequency of each writer is used.\n            # run writers in the end, so that evaluation metrics are written\n            ret.append(hooks.PeriodicWriter(self.build_writers(), period=20))\n        return ret\n\n    def build_writers(self):\n        \"\"\"\n        Build a list of writers to be used using :func:`default_writers()`.\n        If you'd like a different list of writers, you can overwrite it in\n        your trainer.\n\n        Returns:\n            list[EventWriter]: a list of :class:`EventWriter` objects.\n        \"\"\"\n        return default_writers(self.cfg.OUTPUT_DIR, self.max_iter)\n\n    def train(self):\n        \"\"\"\n        Run training.\n\n        Returns:\n            OrderedDict of results, if evaluation is enabled. Otherwise None.\n        \"\"\"\n        super().train(self.start_iter, self.max_iter)\n        if len(self.cfg.TEST.EXPECTED_RESULTS) and comm.is_main_process():\n            assert hasattr(\n                self, \"_last_eval_results\"\n            ), \"No evaluation results obtained during training!\"\n            verify_results(self.cfg, self._last_eval_results)\n            return self._last_eval_results\n\n    def run_step(self):\n        self._trainer.iter = self.iter\n        self._trainer.run_step()\n\n    def state_dict(self):\n        ret = super().state_dict()\n        ret[\"_trainer\"] = self._trainer.state_dict()\n        return ret\n\n    def load_state_dict(self, state_dict):\n        super().load_state_dict(state_dict)\n        self._trainer.load_state_dict(state_dict[\"_trainer\"])\n\n    @classmethod\n    def build_model(cls, cfg):\n        \"\"\"\n        Returns:\n            torch.nn.Module:\n\n        It now calls :func:`detectron2.modeling.build_model`.\n        Overwrite it if you'd like a different model.\n        \"\"\"\n        model = build_model(cfg)\n        print(\"Model\", model)\n        logger = logging.getLogger(__name__)\n        logger.info(\"Model:\\n{}\".format(model))\n        return model\n\n    @classmethod\n    def build_optimizer(cls, cfg, model):\n        \"\"\"\n        Returns:\n            torch.optim.Optimizer:\n\n        It now calls :func:`detectron2.solver.build_optimizer`.\n        Overwrite it if you'd like a different optimizer.\n        \"\"\"\n        return build_optimizer(cfg, model)\n\n    @classmethod\n    def build_lr_scheduler(cls, cfg, optimizer):\n        \"\"\"\n        It now calls :func:`detectron2.solver.build_lr_scheduler`.\n        Overwrite it if you'd like a different scheduler.\n        \"\"\"\n        return build_lr_scheduler(cfg, optimizer)\n\n    @classmethod\n    def build_train_loader(cls, cfg):\n        mapper = DatasetMapper(cfg, is_train=True, augmentations=build_customize_aug(cfg))\n        return build_detection_train_loader(cfg, mapper=mapper)\n\n    @classmethod\n    def build_test_loader(cls, cfg, dataset_name):\n        \"\"\"\n        Returns:\n            iterable\n\n        It now calls :func:`detectron2.data.build_detection_test_loader`.\n        Overwrite it if you'd like a different data loader.\n        \"\"\"\n        return build_detection_test_loader(cfg, dataset_name)\n\n    @classmethod\n    def build_evaluator(cls, cfg, dataset_name, output_folder=None):\n        return MAPIOUEvaluator(dataset_name)\n\n    @classmethod\n    def test(cls, cfg, model, evaluators=None):\n        \"\"\"\n        Evaluate the given model. The given model is expected to already contain\n        weights to evaluate.\n\n        Args:\n            cfg (CfgNode):\n            model (nn.Module):\n            evaluators (list[DatasetEvaluator] or None): if None, will call\n                :meth:`build_evaluator`. Otherwise, must have the same length as\n                ``cfg.DATASETS.TEST``.\n\n        Returns:\n            dict: a dict of result metrics\n        \"\"\"\n        logger = logging.getLogger(__name__)\n        if isinstance(evaluators, DatasetEvaluator):\n            evaluators = [evaluators]\n        if evaluators is not None:\n            assert len(cfg.DATASETS.TEST) == len(evaluators), \"{} != {}\".format(\n                len(cfg.DATASETS.TEST), len(evaluators)\n            )\n\n        results = OrderedDict()\n        for idx, dataset_name in enumerate(cfg.DATASETS.TEST):\n            data_loader = cls.build_test_loader(cfg, dataset_name)\n            # When evaluators are passed in as arguments,\n            # implicitly assume that evaluators can be created before data_loader.\n            if evaluators is not None:\n                evaluator = evaluators[idx]\n            else:\n                try:\n                    evaluator = cls.build_evaluator(cfg, dataset_name)\n                except NotImplementedError:\n                    logger.warn(\n                        \"No evaluator found. Use `DefaultTrainer.test(evaluators=)`, \"\n                        \"or implement its `build_evaluator` method.\"\n                    )\n                    results[dataset_name] = {}\n                    continue\n            results_i = inference_on_dataset(model, data_loader, evaluator)\n            results[dataset_name] = results_i\n            if comm.is_main_process():\n                assert isinstance(\n                    results_i, dict\n                ), \"Evaluator must return a dict on the main process. Got {} instead.\".format(\n                    results_i\n                )\n                logger.info(\"Evaluation results for {} in csv format:\".format(dataset_name))\n                print_csv_format(results_i)\n\n        if len(results) == 1:\n            results = list(results.values())[0]\n        return results\n\n    @staticmethod\n    def auto_scale_workers(cfg, num_workers: int):\n        \"\"\"\n        When the config is defined for certain number of workers (according to\n        ``cfg.SOLVER.REFERENCE_WORLD_SIZE``) that's different from the number of\n        workers currently in use, returns a new cfg where the total batch size\n        is scaled so that the per-GPU batch size stays the same as the\n        original ``IMS_PER_BATCH // REFERENCE_WORLD_SIZE``.\n\n        Other config options are also scaled accordingly:\n        * training steps and warmup steps are scaled inverse proportionally.\n        * learning rate are scaled proportionally, following :paper:`ImageNet in 1h`.\n\n        For example, with the original config like the following:\n\n        .. code-block:: yaml\n\n            IMS_PER_BATCH: 16\n            BASE_LR: 0.1\n            REFERENCE_WORLD_SIZE: 8\n            MAX_ITER: 5000\n            STEPS: (4000,)\n            CHECKPOINT_PERIOD: 1000\n\n        When this config is used on 16 GPUs instead of the reference number 8,\n        calling this method will return a new config with:\n\n        .. code-block:: yaml\n\n            IMS_PER_BATCH: 32\n            BASE_LR: 0.2\n            REFERENCE_WORLD_SIZE: 16\n            MAX_ITER: 2500\n            STEPS: (2000,)\n            CHECKPOINT_PERIOD: 500\n\n        Note that both the original config and this new config can be trained on 16 GPUs.\n        It's up to user whether to enable this feature (by setting ``REFERENCE_WORLD_SIZE``).\n\n        Returns:\n            CfgNode: a new config. Same as original if ``cfg.SOLVER.REFERENCE_WORLD_SIZE==0``.\n        \"\"\"\n        old_world_size = cfg.SOLVER.REFERENCE_WORLD_SIZE\n        if old_world_size == 0 or old_world_size == num_workers:\n            return cfg\n        cfg = cfg.clone()\n        frozen = cfg.is_frozen()\n        cfg.defrost()\n\n        assert (\n            cfg.SOLVER.IMS_PER_BATCH % old_world_size == 0\n        ), \"Invalid REFERENCE_WORLD_SIZE in config!\"\n        scale = num_workers / old_world_size\n        bs = cfg.SOLVER.IMS_PER_BATCH = int(round(cfg.SOLVER.IMS_PER_BATCH * scale))\n        lr = cfg.SOLVER.BASE_LR = cfg.SOLVER.BASE_LR * scale\n        max_iter = cfg.SOLVER.MAX_ITER = int(round(cfg.SOLVER.MAX_ITER / scale))\n        warmup_iter = cfg.SOLVER.WARMUP_ITERS = int(round(cfg.SOLVER.WARMUP_ITERS / scale))\n        cfg.SOLVER.STEPS = tuple(int(round(s / scale)) for s in cfg.SOLVER.STEPS)\n        cfg.TEST.EVAL_PERIOD = int(round(cfg.TEST.EVAL_PERIOD / scale))\n        cfg.SOLVER.CHECKPOINT_PERIOD = int(round(cfg.SOLVER.CHECKPOINT_PERIOD / scale))\n        cfg.SOLVER.REFERENCE_WORLD_SIZE = num_workers  # maintain invariant\n        logger = logging.getLogger(__name__)\n        logger.info(\n            f\"Auto-scaling the config to batch_size={bs}, learning_rate={lr}, \"\n            f\"max_iter={max_iter}, warmup={warmup_iter}.\"\n        )\n\n        if frozen:\n            cfg.freeze()\n        return cfg\n\n# Access basic attributes from the underlying trainer\nfor _attr in [\"model\", \"data_loader\", \"optimizer\"]:\n    setattr(\n        CustomizeTrainer,\n        _attr,\n        property(\n            # getter\n            lambda self, x=_attr: getattr(self._trainer, x),\n            # setter\n            lambda self, value, x=_attr: setattr(self._trainer, x, value),\n        ),\n    )","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:48.083735Z","iopub.execute_input":"2021-12-13T08:24:48.084347Z","iopub.status.idle":"2021-12-13T08:24:48.130168Z","shell.execute_reply.started":"2021-12-13T08:24:48.084311Z","shell.execute_reply":"2021-12-13T08:24:48.129484Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"trainer = CustomizeTrainer(cfg) \n#trainer = Trainer(cfg) \ntrainer.resume_or_load(resume=False)\ntrainer.train()","metadata":{"execution":{"iopub.status.busy":"2021-12-13T08:24:48.131310Z","iopub.execute_input":"2021-12-13T08:24:48.131929Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Lets look at some of the validation files to check if things look reasonable\nWe show predictions on the left and ground truth on the right","metadata":{}},{"cell_type":"code","source":"cfg.MODEL.WEIGHTS = os.path.join(cfg.OUTPUT_DIR, \"model_final.pth\")  # path to the model we just trained\ncfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = 0.5   # set a custom testing threshold\npredictor = DefaultPredictor(cfg)\ndataset_dicts = DatasetCatalog.get('sartorius_val')\nouts = []\nfor d in random.sample(dataset_dicts, 3):    \n    im = cv2.imread(d[\"file_name\"])\n    outputs = predictor(im)  # format is documented at https://detectron2.readthedocs.io/tutorials/models.html#model-output-format\n    v = Visualizer(im[:, :, ::-1],\n                   metadata = MetadataCatalog.get('sartorius_train'), \n                    \n                   instance_mode=ColorMode.IMAGE_BW   # remove the colors of unsegmented pixels. This option is only available for segmentation models\n    )\n    out_pred = v.draw_instance_predictions(outputs[\"instances\"].to(\"cpu\"))\n    visualizer = Visualizer(im[:, :, ::-1], metadata=MetadataCatalog.get('sartorius_train'))\n    out_target = visualizer.draw_dataset_dict(d)\n    outs.append(out_pred)\n    outs.append(out_target)\n_,axs = plt.subplots(len(outs)//2,2,figsize=(40,45))\nfor ax, out in zip(axs.reshape(-1), outs):\n    ax.imshow(out.get_image()[:, :, ::-1])","metadata":{"trusted":true},"execution_count":null,"outputs":[]}]}