Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- approach/ovod/d-cube/.assets/d-cube_logo.png +0 -0
- approach/ovod/d-cube/d_cube/__init__.py +1 -0
- approach/ovod/d-cube/d_cube/d3.py +775 -0
- approach/ovod/d-cube/d_cube/data_util.py +269 -0
- approach/ovod/d-cube/d_cube/vis_util.py +199 -0
- approach/ovod/d-cube/eval_sota/README.md +27 -0
- approach/ovod/d-cube/eval_sota/groundingdino.py +304 -0
- approach/ovod/d-cube/eval_sota/owl_vit.py +192 -0
- approach/ovod/d-cube/eval_sota/sphinx.py +184 -0
- approach/ovod/d-cube/scripts/eval_and_analysis_json.py +190 -0
- approach/ovod/d-cube/scripts/eval_json_example.py +16 -0
- approach/ovod/d-cube/scripts/get_d3_stat.py +98 -0
- approach/ovod/detectron2/.circleci/config.yml +270 -0
- approach/ovod/detectron2/.circleci/import-tests.sh +16 -0
- approach/ovod/detectron2/.github/CODE_OF_CONDUCT.md +5 -0
- approach/ovod/detectron2/.github/CONTRIBUTING.md +68 -0
- approach/ovod/detectron2/.github/Detectron2-Logo-Horz.svg +1 -0
- approach/ovod/detectron2/.github/ISSUE_TEMPLATE.md +5 -0
- approach/ovod/detectron2/.github/pull_request_template.md +10 -0
- approach/ovod/detectron2/configs/Base-RCNN-C4.yaml +18 -0
- approach/ovod/detectron2/configs/Base-RCNN-DilatedC5.yaml +31 -0
- approach/ovod/detectron2/configs/Base-RCNN-FPN.yaml +42 -0
- approach/ovod/detectron2/configs/Base-RetinaNet.yaml +25 -0
- approach/ovod/detectron2/demo/README.md +8 -0
- approach/ovod/detectron2/demo/demo.py +188 -0
- approach/ovod/detectron2/demo/predictor.py +220 -0
- approach/ovod/detectron2/detectron2/__init__.py +10 -0
- approach/ovod/detectron2/dev/README.md +7 -0
- approach/ovod/detectron2/dev/linter.sh +42 -0
- approach/ovod/detectron2/dev/parse_results.sh +45 -0
- approach/ovod/detectron2/dev/run_inference_tests.sh +44 -0
- approach/ovod/detectron2/dev/run_instant_tests.sh +27 -0
- approach/ovod/detectron2/docs/.gitignore +1 -0
- approach/ovod/detectron2/docs/Makefile +19 -0
- approach/ovod/detectron2/docs/README.md +15 -0
- approach/ovod/detectron2/docs/conf.py +391 -0
- approach/ovod/detectron2/docs/index.rst +14 -0
- approach/ovod/detectron2/docs/notes/benchmarks.md +196 -0
- approach/ovod/detectron2/docs/notes/changelog.md +48 -0
- approach/ovod/detectron2/docs/notes/compatibility.md +84 -0
- approach/ovod/detectron2/docs/notes/contributing.md +68 -0
- approach/ovod/detectron2/docs/notes/index.rst +10 -0
- approach/ovod/detectron2/docs/requirements.txt +24 -0
- approach/ovod/detectron2/docs/tutorials/README.md +4 -0
- approach/ovod/detectron2/docs/tutorials/builtin_datasets.md +140 -0
- approach/ovod/detectron2/docs/tutorials/configs.md +62 -0
- approach/ovod/detectron2/docs/tutorials/data_loading.md +95 -0
- approach/ovod/detectron2/docs/tutorials/datasets.md +290 -0
- approach/ovod/detectron2/docs/tutorials/deployment.md +137 -0
- approach/ovod/detectron2/docs/tutorials/extend.md +141 -0
approach/ovod/d-cube/.assets/d-cube_logo.png
ADDED
|
approach/ovod/d-cube/d_cube/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
from .d3 import D3
|
approach/ovod/d-cube/d_cube/d3.py
ADDED
|
@@ -0,0 +1,775 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
__author__ = "Chi Xie and Zhao Zhang"
|
| 3 |
+
__maintainer__ = "Chi Xie"
|
| 4 |
+
# this is the core of the d-cube toolkit
|
| 5 |
+
import os
|
| 6 |
+
import os.path as osp
|
| 7 |
+
import json
|
| 8 |
+
from collections import defaultdict
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
from pycocotools import mask
|
| 12 |
+
import cv2
|
| 13 |
+
import matplotlib.pyplot as plt
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
from .data_util import *
|
| 17 |
+
|
| 18 |
+
|
| 19 |
+
class D3:
|
| 20 |
+
def __init__(self, img_root, anno_root):
|
| 21 |
+
self.image_dir = img_root
|
| 22 |
+
self.anno_dir = anno_root
|
| 23 |
+
self.load_data()
|
| 24 |
+
|
| 25 |
+
def load_data(self):
|
| 26 |
+
file_names = ["sentences.pkl", "annotations.pkl", "images.pkl", "groups.pkl"]
|
| 27 |
+
self.data = {
|
| 28 |
+
name.split(".")[0]: load_pkl(osp.join(self.anno_dir, name))
|
| 29 |
+
for name in file_names
|
| 30 |
+
}
|
| 31 |
+
|
| 32 |
+
def get_sent_ids(self, anno_ids=[], img_ids=[], group_ids=[], sent_ids=[]):
|
| 33 |
+
"""get sentence ids for D-cube.
|
| 34 |
+
|
| 35 |
+
Args:
|
| 36 |
+
anno_ids (list, optional): annotation ids to get sentence ids. Defaults to [].
|
| 37 |
+
img_ids (list, optional): image ids to get sentence ids. Defaults to [].
|
| 38 |
+
group_ids (list, optional): group ids to get sentence ids. Defaults to [].
|
| 39 |
+
sent_ids (list, optional): additional sentence ids you want to include. Defaults to [].
|
| 40 |
+
|
| 41 |
+
Raises:
|
| 42 |
+
Exception: anno_ids, img_ids and group_ids cannot be used together.
|
| 43 |
+
|
| 44 |
+
Returns:
|
| 45 |
+
list: sentence ids.
|
| 46 |
+
"""
|
| 47 |
+
img_ids = img_ids if isinstance(img_ids, list) else [img_ids]
|
| 48 |
+
anno_ids = anno_ids if isinstance(anno_ids, list) else [anno_ids]
|
| 49 |
+
group_ids = group_ids if isinstance(group_ids, list) else [group_ids]
|
| 50 |
+
sent_ids = sent_ids if isinstance(sent_ids, list) else [sent_ids]
|
| 51 |
+
|
| 52 |
+
if not any([img_ids, anno_ids, group_ids, sent_ids]):
|
| 53 |
+
return list(self.data["sentences"].keys())
|
| 54 |
+
|
| 55 |
+
if (
|
| 56 |
+
(anno_ids and img_ids)
|
| 57 |
+
or (anno_ids and group_ids)
|
| 58 |
+
or (img_ids and group_ids)
|
| 59 |
+
):
|
| 60 |
+
raise Exception("anno_ids, img_ids, group_ids can only be used alone")
|
| 61 |
+
|
| 62 |
+
out_ids_set = set()
|
| 63 |
+
if img_ids:
|
| 64 |
+
for img_id in img_ids:
|
| 65 |
+
imganno_ids = self.data["images"][img_id]["anno_id"]
|
| 66 |
+
for ianno_id in imganno_ids:
|
| 67 |
+
out_ids_set |= set(self.data["annotations"][ianno_id]["sent_id"])
|
| 68 |
+
|
| 69 |
+
if group_ids:
|
| 70 |
+
for group_id in group_ids:
|
| 71 |
+
out_ids_set |= set(self.data["groups"][group_id]["inner_sent_id"])
|
| 72 |
+
|
| 73 |
+
if anno_ids:
|
| 74 |
+
for ianno_id in anno_ids:
|
| 75 |
+
out_ids_set |= set(self.data["annotations"][ianno_id]["sent_id"])
|
| 76 |
+
|
| 77 |
+
if sent_ids:
|
| 78 |
+
out_ids_set &= set(sent_ids)
|
| 79 |
+
|
| 80 |
+
return list(out_ids_set)
|
| 81 |
+
|
| 82 |
+
def get_anno_ids(self, anno_ids=[], img_ids=[], group_ids=[], sent_ids=[]):
|
| 83 |
+
"""get annotation ids for D-cube.
|
| 84 |
+
|
| 85 |
+
Args:
|
| 86 |
+
anno_ids (list, optional): additional annotation ids you want to include. Defaults to [].
|
| 87 |
+
img_ids (list, optional): image ids to get annotation ids. Defaults to [].
|
| 88 |
+
group_ids (list, optional): group ids to get annotation ids. Defaults to [].
|
| 89 |
+
sent_ids (list, optional): sentence ids to get annotation ids. Defaults to [].
|
| 90 |
+
|
| 91 |
+
Raises:
|
| 92 |
+
Exception: img_ids and group_ids cannot be used together.
|
| 93 |
+
|
| 94 |
+
Returns:
|
| 95 |
+
list: annotation ids.
|
| 96 |
+
"""
|
| 97 |
+
img_ids = img_ids if isinstance(img_ids, list) else [img_ids]
|
| 98 |
+
anno_ids = anno_ids if isinstance(anno_ids, list) else [anno_ids]
|
| 99 |
+
group_ids = group_ids if isinstance(group_ids, list) else [group_ids]
|
| 100 |
+
sent_ids = sent_ids if isinstance(sent_ids, list) else [sent_ids]
|
| 101 |
+
|
| 102 |
+
if not any([img_ids, anno_ids, group_ids, sent_ids]):
|
| 103 |
+
return list(self.data["annotations"].keys())
|
| 104 |
+
|
| 105 |
+
if img_ids and group_ids:
|
| 106 |
+
raise Exception("img_ids, group_ids can only be used alone")
|
| 107 |
+
|
| 108 |
+
out_ids_set = set()
|
| 109 |
+
if img_ids:
|
| 110 |
+
for img_id in img_ids:
|
| 111 |
+
out_ids_set |= set(self.data["images"][img_id]["anno_id"])
|
| 112 |
+
|
| 113 |
+
if group_ids:
|
| 114 |
+
for group_id in group_ids:
|
| 115 |
+
for groupimg_id in self.data["groups"][group_id]["img_id"]:
|
| 116 |
+
out_ids_set |= set(self.data["images"][groupimg_id]["anno_id"])
|
| 117 |
+
|
| 118 |
+
if sent_ids and img_ids:
|
| 119 |
+
for sent_id in sent_ids:
|
| 120 |
+
out_ids_set &= set(self.data["sentences"][sent_id]["anno_id"])
|
| 121 |
+
else:
|
| 122 |
+
for sent_id in sent_ids:
|
| 123 |
+
out_ids_set |= set(self.data["sentences"][sent_id]["anno_id"])
|
| 124 |
+
|
| 125 |
+
if anno_ids:
|
| 126 |
+
out_ids_set &= set(anno_ids)
|
| 127 |
+
|
| 128 |
+
return list(out_ids_set)
|
| 129 |
+
|
| 130 |
+
def get_img_ids(self, anno_ids=[], img_ids=[], group_ids=[], sent_ids=[]):
|
| 131 |
+
"""get image ids for D-cube.
|
| 132 |
+
|
| 133 |
+
Args:
|
| 134 |
+
anno_ids (list, optional): annotation ids to get image ids. Defaults to [].
|
| 135 |
+
img_ids (list, optional): additional image ids you want to include. Defaults to [].
|
| 136 |
+
group_ids (list, optional): group ids to get image ids. Defaults to [].
|
| 137 |
+
sent_ids (list, optional): sentence ids to get image ids. Defaults to [].
|
| 138 |
+
|
| 139 |
+
Raises:
|
| 140 |
+
Exception: anno_ids and img_ids cannot be used together.
|
| 141 |
+
Exception: anno_ids and group_ids cannot be used together.
|
| 142 |
+
|
| 143 |
+
Returns:
|
| 144 |
+
list: image ids.
|
| 145 |
+
"""
|
| 146 |
+
img_ids = img_ids if isinstance(img_ids, list) else [img_ids]
|
| 147 |
+
anno_ids = anno_ids if isinstance(anno_ids, list) else [anno_ids]
|
| 148 |
+
group_ids = group_ids if isinstance(group_ids, list) else [group_ids]
|
| 149 |
+
sent_ids = sent_ids if isinstance(sent_ids, list) else [sent_ids]
|
| 150 |
+
|
| 151 |
+
if not any([img_ids, anno_ids, group_ids, sent_ids]):
|
| 152 |
+
return list(self.data["images"].keys())
|
| 153 |
+
|
| 154 |
+
if anno_ids and img_ids:
|
| 155 |
+
raise Exception("anno_ids and img_ids can only be used alone")
|
| 156 |
+
if anno_ids and group_ids:
|
| 157 |
+
raise Exception("anno_ids and group_ids can only be used alone")
|
| 158 |
+
|
| 159 |
+
out_ids_set = set()
|
| 160 |
+
if anno_ids:
|
| 161 |
+
for ianno_id in anno_ids:
|
| 162 |
+
out_ids_set.add(self.data["annotations"][ianno_id]["img_id"])
|
| 163 |
+
|
| 164 |
+
if group_ids:
|
| 165 |
+
for group_id in group_ids:
|
| 166 |
+
out_ids_set |= set(self.data["groups"][group_id]["img_id"])
|
| 167 |
+
|
| 168 |
+
if sent_ids:
|
| 169 |
+
for sent_id in sent_ids:
|
| 170 |
+
for sentanno_id in self.data["sentences"][sent_id]["anno_id"]:
|
| 171 |
+
out_ids_set.add(self.data["annotations"][sentanno_id]["image_id"])
|
| 172 |
+
|
| 173 |
+
if img_ids:
|
| 174 |
+
out_ids_set &= set(img_ids)
|
| 175 |
+
|
| 176 |
+
return list(out_ids_set)
|
| 177 |
+
|
| 178 |
+
def get_group_ids(self, anno_ids=[], img_ids=[], group_ids=[], sent_ids=[]):
|
| 179 |
+
"""get group ids for D-cube.
|
| 180 |
+
|
| 181 |
+
Args:
|
| 182 |
+
anno_ids (list, optional): annotation ids to get group ids. Defaults to [].
|
| 183 |
+
img_ids (list, optional): image ids to get group ids. Defaults to [].
|
| 184 |
+
group_ids (list, optional): additional group_ids you want to include. Defaults to [].
|
| 185 |
+
sent_ids (list, optional): sentence ids to get group ids. Defaults to [].
|
| 186 |
+
|
| 187 |
+
Raises:
|
| 188 |
+
Exception: anno_ids, img_ids and sent_ids cannot be used together.
|
| 189 |
+
|
| 190 |
+
Returns:
|
| 191 |
+
list: group ids.
|
| 192 |
+
"""
|
| 193 |
+
img_ids = img_ids if isinstance(img_ids, list) else [img_ids]
|
| 194 |
+
anno_ids = anno_ids if isinstance(anno_ids, list) else [anno_ids]
|
| 195 |
+
group_ids = group_ids if isinstance(group_ids, list) else [group_ids]
|
| 196 |
+
sent_ids = sent_ids if isinstance(sent_ids, list) else [sent_ids]
|
| 197 |
+
|
| 198 |
+
if not any([img_ids, anno_ids, group_ids, sent_ids]):
|
| 199 |
+
return list(self.data["groups"].keys())
|
| 200 |
+
|
| 201 |
+
if anno_ids and img_ids:
|
| 202 |
+
raise Exception("anno_ids and img_ids can only be used alone")
|
| 203 |
+
if anno_ids and sent_ids:
|
| 204 |
+
raise Exception("anno_ids and sent_ids can only be used alone")
|
| 205 |
+
if img_ids and sent_ids:
|
| 206 |
+
raise Exception("img_ids and sent_ids can only be used alone")
|
| 207 |
+
|
| 208 |
+
out_ids_set = set()
|
| 209 |
+
if img_ids:
|
| 210 |
+
for img_id in img_ids:
|
| 211 |
+
out_ids_set.add(self.data["images"][img_id]["group_id"])
|
| 212 |
+
|
| 213 |
+
if anno_ids:
|
| 214 |
+
for anno_id in anno_ids:
|
| 215 |
+
out_ids_set.add(self.data["annotations"][anno_id]["group_id"])
|
| 216 |
+
|
| 217 |
+
if sent_ids:
|
| 218 |
+
for sent_id in sent_ids:
|
| 219 |
+
out_ids_set |= set(self.data["sentences"][sent_id]["group_id"])
|
| 220 |
+
|
| 221 |
+
if group_ids:
|
| 222 |
+
out_ids_set &= set(group_ids)
|
| 223 |
+
|
| 224 |
+
return list(out_ids_set)
|
| 225 |
+
|
| 226 |
+
def load_sents(self, sent_ids=None):
|
| 227 |
+
"""load sentence info.
|
| 228 |
+
|
| 229 |
+
Args:
|
| 230 |
+
sent_ids (list, int, optional): sentence ids. Defaults to None.
|
| 231 |
+
|
| 232 |
+
Returns:
|
| 233 |
+
list: a list of sentence info.
|
| 234 |
+
"""
|
| 235 |
+
if sent_ids is not None and not isinstance(sent_ids, list):
|
| 236 |
+
sent_ids = [sent_ids]
|
| 237 |
+
if isinstance(sent_ids, list):
|
| 238 |
+
return [self.data["sentences"][sent_id] for sent_id in sent_ids]
|
| 239 |
+
else:
|
| 240 |
+
return list(self.data["sentences"].values())
|
| 241 |
+
|
| 242 |
+
def load_annos(self, anno_ids=None):
|
| 243 |
+
"""load annotation info.
|
| 244 |
+
|
| 245 |
+
Args:
|
| 246 |
+
anno_ids (list, int, optional): annotation ids. Defaults to None.
|
| 247 |
+
|
| 248 |
+
Returns:
|
| 249 |
+
list: a list of annotation info.
|
| 250 |
+
"""
|
| 251 |
+
if anno_ids is not None and not isinstance(anno_ids, list):
|
| 252 |
+
anno_ids = [anno_ids]
|
| 253 |
+
if isinstance(anno_ids, list):
|
| 254 |
+
return [self.data["annotations"][anno_id] for anno_id in anno_ids]
|
| 255 |
+
else:
|
| 256 |
+
return list(self.data["annotations"].values())
|
| 257 |
+
|
| 258 |
+
def load_imgs(self, img_ids=None):
|
| 259 |
+
"""load image info.
|
| 260 |
+
|
| 261 |
+
Args:
|
| 262 |
+
img_ids (list, int, optional): image ids. Defaults to None.
|
| 263 |
+
|
| 264 |
+
Returns:
|
| 265 |
+
list: a list of image info.
|
| 266 |
+
"""
|
| 267 |
+
if img_ids is not None and not isinstance(img_ids, list):
|
| 268 |
+
img_ids = [img_ids]
|
| 269 |
+
if isinstance(img_ids, list):
|
| 270 |
+
return [self.data["images"][img_ids] for img_ids in img_ids]
|
| 271 |
+
else:
|
| 272 |
+
return list(self.data["images"].values())
|
| 273 |
+
|
| 274 |
+
def load_groups(self, group_ids=None):
|
| 275 |
+
"""load group info.
|
| 276 |
+
|
| 277 |
+
Args:
|
| 278 |
+
group_ids (list, int, optional): group ids. Defaults to None.
|
| 279 |
+
|
| 280 |
+
Returns:
|
| 281 |
+
list: a list of group info.
|
| 282 |
+
"""
|
| 283 |
+
if group_ids is not None and not isinstance(group_ids, list):
|
| 284 |
+
group_ids = [group_ids]
|
| 285 |
+
if isinstance(group_ids, list):
|
| 286 |
+
return [self.data["groups"][group_ids] for group_ids in group_ids]
|
| 287 |
+
else:
|
| 288 |
+
return list(self.data["groups"].values())
|
| 289 |
+
|
| 290 |
+
def get_mask(self, anno):
|
| 291 |
+
rle = anno[0]["segmentation"]
|
| 292 |
+
m = mask.decode(rle)
|
| 293 |
+
m = np.sum(
|
| 294 |
+
m, axis=2
|
| 295 |
+
) # sometimes there are multiple binary map (corresponding to multiple segs)
|
| 296 |
+
m = m.astype(np.uint8) # convert to np.uint8
|
| 297 |
+
# compute area
|
| 298 |
+
area = sum(mask.area(rle)) # should be close to ann['area']
|
| 299 |
+
return {"mask": m, "area": area}
|
| 300 |
+
|
| 301 |
+
def show_mask(self, anno):
|
| 302 |
+
M = self.get_mask(anno)
|
| 303 |
+
msk = M["mask"]
|
| 304 |
+
ax = plt.gca()
|
| 305 |
+
ax.imshow(msk)
|
| 306 |
+
|
| 307 |
+
def show_image_seg(
|
| 308 |
+
self,
|
| 309 |
+
img_ids=[],
|
| 310 |
+
save_dir=None,
|
| 311 |
+
show_sent=False,
|
| 312 |
+
on_image=False,
|
| 313 |
+
checkerboard_bg=False,
|
| 314 |
+
is_instance=True,
|
| 315 |
+
):
|
| 316 |
+
if is_instance and checkerboard_bg:
|
| 317 |
+
raise ValueError(
|
| 318 |
+
"Cannot apply both is_instance and checkboard_bg at the same time."
|
| 319 |
+
)
|
| 320 |
+
img_infos = self.load_imgs(img_ids=img_ids)
|
| 321 |
+
for img_idx, img_info in enumerate(img_infos):
|
| 322 |
+
img = cv2.imread(osp.join(self.image_dir, img_info["file_name"]))
|
| 323 |
+
anno_infos = self.load_annos(img_info["anno_id"])
|
| 324 |
+
|
| 325 |
+
bm_canvas = defaultdict(list)
|
| 326 |
+
merge_canvas = defaultdict(list)
|
| 327 |
+
for anno_info in anno_infos:
|
| 328 |
+
for sent_id in anno_info["sent_id"]:
|
| 329 |
+
bm_canvas[sent_id].append(anno_info["segmentation"])
|
| 330 |
+
|
| 331 |
+
for sent_id, bm_list in bm_canvas.items():
|
| 332 |
+
merge_canvas[sent_id] = merge_rle(
|
| 333 |
+
bm_list, is_instance=is_instance, on_image=on_image
|
| 334 |
+
)
|
| 335 |
+
|
| 336 |
+
cv2.imwrite(osp.join(save_dir, f"{img_info['id']}.png"), img)
|
| 337 |
+
for sent_id, merge_mask in merge_canvas.items():
|
| 338 |
+
if checkerboard_bg:
|
| 339 |
+
merge_mask = add_checkerboard_bg(img, merge_mask)
|
| 340 |
+
elif on_image:
|
| 341 |
+
merge_mask = visualize_mask_on_image(img, merge_mask, add_edge=True)
|
| 342 |
+
if show_sent:
|
| 343 |
+
sent_en = self.load_sents(sent_ids=sent_id)[0]["raw_sent"]
|
| 344 |
+
merge_mask = paste_text(merge_mask, sent_en)
|
| 345 |
+
cv2.imwrite(
|
| 346 |
+
osp.join(save_dir, f"{img_info['id']}_{sent_id}.png"), merge_mask
|
| 347 |
+
)
|
| 348 |
+
|
| 349 |
+
return merge_canvas
|
| 350 |
+
|
| 351 |
+
def show_group_seg(
|
| 352 |
+
self,
|
| 353 |
+
group_ids,
|
| 354 |
+
save_root,
|
| 355 |
+
show_sent=True,
|
| 356 |
+
is_instance=True,
|
| 357 |
+
on_image=False,
|
| 358 |
+
checkerboard_bg=False,
|
| 359 |
+
):
|
| 360 |
+
group_infos = self.load_groups(group_ids=group_ids)
|
| 361 |
+
for group_info in group_infos:
|
| 362 |
+
save_dir = osp.join(save_root, group_info["group_name"])
|
| 363 |
+
os.makedirs(save_dir, exist_ok=True)
|
| 364 |
+
self.show_image_seg(
|
| 365 |
+
img_ids=group_info["img_id"],
|
| 366 |
+
save_dir=save_dir,
|
| 367 |
+
show_sent=show_sent,
|
| 368 |
+
is_instance=is_instance,
|
| 369 |
+
on_image=on_image,
|
| 370 |
+
checkerboard_bg=checkerboard_bg,
|
| 371 |
+
)
|
| 372 |
+
|
| 373 |
+
def show_image_seg_bbox(
|
| 374 |
+
self,
|
| 375 |
+
img_ids=[],
|
| 376 |
+
save_dir=None,
|
| 377 |
+
show_sent=False,
|
| 378 |
+
on_image=False,
|
| 379 |
+
checkerboard_bg=False,
|
| 380 |
+
is_instance=True,
|
| 381 |
+
):
|
| 382 |
+
if is_instance and checkerboard_bg:
|
| 383 |
+
raise ValueError(
|
| 384 |
+
"Cannot apply both is_instance and checkboard_bg at the same time."
|
| 385 |
+
)
|
| 386 |
+
img_infos = self.load_imgs(img_ids=img_ids)
|
| 387 |
+
for img_idx, img_info in enumerate(img_infos):
|
| 388 |
+
img = cv2.imread(osp.join(self.image_dir, img_info["file_name"]))
|
| 389 |
+
anno_infos = self.load_annos(img_info["anno_id"])
|
| 390 |
+
|
| 391 |
+
bm_canvas = defaultdict(list)
|
| 392 |
+
merge_canvas = defaultdict(list)
|
| 393 |
+
sent_boxes = defaultdict(list)
|
| 394 |
+
for anno_info in anno_infos:
|
| 395 |
+
for sent_id in anno_info["sent_id"]:
|
| 396 |
+
bm_canvas[sent_id].append(anno_info["segmentation"])
|
| 397 |
+
sent_boxes[sent_id].append(anno_info["bbox"][0].tolist())
|
| 398 |
+
|
| 399 |
+
for sent_id, bm_list in bm_canvas.items():
|
| 400 |
+
merge_canvas[sent_id] = merge_rle(
|
| 401 |
+
bm_list, is_instance=is_instance, on_image=on_image
|
| 402 |
+
)
|
| 403 |
+
|
| 404 |
+
cv2.imwrite(osp.join(save_dir, f"{img_info['id']}.png"), img)
|
| 405 |
+
for sent_id, merge_mask in merge_canvas.items():
|
| 406 |
+
# vis mask
|
| 407 |
+
if checkerboard_bg:
|
| 408 |
+
merge_mask = add_checkerboard_bg(img, merge_mask)
|
| 409 |
+
elif on_image:
|
| 410 |
+
merge_mask = visualize_mask_on_image(img, merge_mask, add_edge=True)
|
| 411 |
+
# vis box
|
| 412 |
+
bboxes = sent_boxes[sent_id]
|
| 413 |
+
merge_mask = visualize_bbox_on_image(merge_mask, bboxes)
|
| 414 |
+
# vis sent
|
| 415 |
+
if show_sent:
|
| 416 |
+
sent_en = self.load_sents(sent_ids=sent_id)[0]["raw_sent"]
|
| 417 |
+
merge_mask = paste_text(merge_mask, sent_en)
|
| 418 |
+
cv2.imwrite(
|
| 419 |
+
osp.join(save_dir, f"{img_info['id']}_{sent_id}.png"), merge_mask
|
| 420 |
+
)
|
| 421 |
+
|
| 422 |
+
return merge_canvas
|
| 423 |
+
|
| 424 |
+
def show_group_seg_bbox(
|
| 425 |
+
self,
|
| 426 |
+
group_ids,
|
| 427 |
+
save_root,
|
| 428 |
+
show_sent=True,
|
| 429 |
+
is_instance=True,
|
| 430 |
+
on_image=False,
|
| 431 |
+
checkerboard_bg=False,
|
| 432 |
+
):
|
| 433 |
+
group_infos = self.load_groups(group_ids=group_ids)
|
| 434 |
+
for group_info in group_infos:
|
| 435 |
+
save_dir = osp.join(save_root, group_info["group_name"])
|
| 436 |
+
os.makedirs(save_dir, exist_ok=True)
|
| 437 |
+
self.show_image_seg_bbox(
|
| 438 |
+
img_ids=group_info["img_id"],
|
| 439 |
+
save_dir=save_dir,
|
| 440 |
+
show_sent=show_sent,
|
| 441 |
+
is_instance=is_instance,
|
| 442 |
+
on_image=on_image,
|
| 443 |
+
checkerboard_bg=checkerboard_bg,
|
| 444 |
+
)
|
| 445 |
+
|
| 446 |
+
def show_image_bbox(self, img_ids=[], save_dir=None, show_sent=False):
|
| 447 |
+
img_infos = self.load_imgs(img_ids=img_ids)
|
| 448 |
+
for img_idx, img_info in enumerate(img_infos):
|
| 449 |
+
img = cv2.imread(osp.join(self.image_dir, img_info["file_name"]))
|
| 450 |
+
anno_infos = self.load_annos(img_info["anno_id"])
|
| 451 |
+
|
| 452 |
+
sent_boxes = defaultdict(list)
|
| 453 |
+
for anno_info in anno_infos:
|
| 454 |
+
for sent_id in anno_info["sent_id"]:
|
| 455 |
+
sent_boxes[sent_id].append(anno_info["bbox"][0].tolist())
|
| 456 |
+
|
| 457 |
+
cv2.imwrite(osp.join(save_dir, f"{img_info['id']}.png"), img)
|
| 458 |
+
for sent_id, bboxes in sent_boxes.items():
|
| 459 |
+
merge_img = visualize_bbox_on_image(img, bboxes)
|
| 460 |
+
if show_sent:
|
| 461 |
+
sent_en = self.load_sents(sent_ids=sent_id)[0]["raw_sent"]
|
| 462 |
+
merge_img = paste_text(merge_img, sent_en)
|
| 463 |
+
cv2.imwrite(
|
| 464 |
+
osp.join(save_dir, f"{img_info['id']}_{sent_id}.png"), merge_img
|
| 465 |
+
)
|
| 466 |
+
|
| 467 |
+
def show_group_bbox(self, group_ids, save_root, show_sent=True):
|
| 468 |
+
group_infos = self.load_groups(group_ids=group_ids)
|
| 469 |
+
for group_info in group_infos:
|
| 470 |
+
save_dir = osp.join(save_root, group_info["group_name"])
|
| 471 |
+
os.makedirs(save_dir, exist_ok=True)
|
| 472 |
+
self.show_image_bbox(
|
| 473 |
+
img_ids=group_info["img_id"], save_dir=save_dir, show_sent=show_sent
|
| 474 |
+
)
|
| 475 |
+
|
| 476 |
+
def stat_description(self, with_rev=False, inter_group=False):
|
| 477 |
+
"""calculate and print dataset statistics.
|
| 478 |
+
|
| 479 |
+
Args:
|
| 480 |
+
with_rev (bool, optional): consider absence descriptions or not. Defaults to False.
|
| 481 |
+
inter_group (bool, optional): calculate under intra- or inter-group settings. Defaults to False.
|
| 482 |
+
"""
|
| 483 |
+
stat_dict = {}
|
| 484 |
+
# Number of sents
|
| 485 |
+
sent_ids = list(self.data["sentences"].keys())
|
| 486 |
+
if not with_rev:
|
| 487 |
+
sent_ids = [sent_id for sent_id in sent_ids if not self.is_revsent(sent_id)]
|
| 488 |
+
stat_dict["nsent"] = len(sent_ids)
|
| 489 |
+
# Number of annos / instance # TODO: rm rev
|
| 490 |
+
stat_dict["nanno"] = len(self.data["annotations"].keys())
|
| 491 |
+
# Number of images
|
| 492 |
+
stat_dict["nimg"] = len(self.data["images"].keys())
|
| 493 |
+
# Number of groups
|
| 494 |
+
stat_dict["ngroup"] = len(self.data["groups"].keys())
|
| 495 |
+
|
| 496 |
+
# Number of img-sent pair
|
| 497 |
+
num_img_sent = 0
|
| 498 |
+
for img_id in self.data["images"].keys():
|
| 499 |
+
anno_ids = self.get_anno_ids(img_ids=img_id)
|
| 500 |
+
anno_infos = self.load_annos(anno_ids=anno_ids)
|
| 501 |
+
cur_sent_set = set()
|
| 502 |
+
group_sent_ids = set(
|
| 503 |
+
self.load_groups(self.get_group_ids(img_ids=img_id))[0]["inner_sent_id"]
|
| 504 |
+
)
|
| 505 |
+
for anno_info in anno_infos:
|
| 506 |
+
cur_sent_set |= set(
|
| 507 |
+
[i for i in anno_info["sent_id"] if i in group_sent_ids]
|
| 508 |
+
)
|
| 509 |
+
if not with_rev:
|
| 510 |
+
cur_sent_set = [
|
| 511 |
+
sent_id for sent_id in cur_sent_set if not self.is_revsent(sent_id)
|
| 512 |
+
]
|
| 513 |
+
num_img_sent += len(cur_sent_set)
|
| 514 |
+
stat_dict["num_img_sent"] = num_img_sent
|
| 515 |
+
|
| 516 |
+
# Number of absence img-sent pair
|
| 517 |
+
num_anti_img_sent = 0
|
| 518 |
+
for img_id in self.data["images"].keys():
|
| 519 |
+
anno_ids = self.get_anno_ids(img_ids=img_id)
|
| 520 |
+
anno_infos = self.load_annos(anno_ids=anno_ids)
|
| 521 |
+
cur_sent_set = set()
|
| 522 |
+
group_sent_ids = set(
|
| 523 |
+
self.load_groups(self.get_group_ids(img_ids=img_id))[0]["inner_sent_id"]
|
| 524 |
+
)
|
| 525 |
+
for anno_info in anno_infos:
|
| 526 |
+
cur_sent_set |= set(
|
| 527 |
+
[i for i in anno_info["sent_id"] if i in group_sent_ids]
|
| 528 |
+
)
|
| 529 |
+
assert group_sent_ids.issuperset(
|
| 530 |
+
cur_sent_set
|
| 531 |
+
), f"{group_sent_ids}, {cur_sent_set}"
|
| 532 |
+
cur_anti_sent_set = group_sent_ids - cur_sent_set
|
| 533 |
+
if not with_rev:
|
| 534 |
+
cur_anti_sent_set = [
|
| 535 |
+
sent_id
|
| 536 |
+
for sent_id in cur_anti_sent_set
|
| 537 |
+
if not self.is_revsent(sent_id)
|
| 538 |
+
]
|
| 539 |
+
num_anti_img_sent += len(cur_anti_sent_set)
|
| 540 |
+
stat_dict["num_anti_img_sent"] = num_anti_img_sent
|
| 541 |
+
|
| 542 |
+
# Number of anno-sent pair
|
| 543 |
+
num_anno_sent = 0
|
| 544 |
+
anno_infos = self.load_annos()
|
| 545 |
+
for anno_info in anno_infos:
|
| 546 |
+
if inter_group:
|
| 547 |
+
anno_sent_ids = [i for i in anno_info["sent_id"]]
|
| 548 |
+
else:
|
| 549 |
+
group_sent_ids = set(
|
| 550 |
+
self.load_groups(anno_info["group_id"])[0]["inner_sent_id"]
|
| 551 |
+
)
|
| 552 |
+
anno_sent_ids = [i for i in anno_info["sent_id"] if i in group_sent_ids]
|
| 553 |
+
if not with_rev:
|
| 554 |
+
anno_sent_ids = [
|
| 555 |
+
sent_id for sent_id in anno_sent_ids if not self.is_revsent(sent_id)
|
| 556 |
+
]
|
| 557 |
+
num_anno_sent += len(anno_sent_ids)
|
| 558 |
+
|
| 559 |
+
stat_dict["num_anno_sent"] = num_anno_sent
|
| 560 |
+
|
| 561 |
+
# Number of anti anno-sent pair
|
| 562 |
+
num_anti_anno_sent = 0
|
| 563 |
+
anno_infos = self.load_annos()
|
| 564 |
+
for anno_info in anno_infos:
|
| 565 |
+
if inter_group:
|
| 566 |
+
all_sent_ids = set(self.get_sent_ids())
|
| 567 |
+
anno_sent_ids = anno_info["sent_id"]
|
| 568 |
+
|
| 569 |
+
anti_sent_ids = [
|
| 570 |
+
sent_id for sent_id in all_sent_ids if sent_id not in anno_sent_ids
|
| 571 |
+
]
|
| 572 |
+
else:
|
| 573 |
+
group_sent_ids = set(
|
| 574 |
+
self.load_groups(anno_info["group_id"])[0]["inner_sent_id"]
|
| 575 |
+
)
|
| 576 |
+
anno_sent_ids = [i for i in anno_info["sent_id"] if i in group_sent_ids]
|
| 577 |
+
|
| 578 |
+
anti_sent_ids = [
|
| 579 |
+
sent_id
|
| 580 |
+
for sent_id in group_sent_ids
|
| 581 |
+
if sent_id not in anno_sent_ids
|
| 582 |
+
]
|
| 583 |
+
|
| 584 |
+
if not with_rev:
|
| 585 |
+
anti_sent_ids = [
|
| 586 |
+
sent_id for sent_id in anti_sent_ids if not self.is_revsent(sent_id)
|
| 587 |
+
]
|
| 588 |
+
num_anti_anno_sent += len(anti_sent_ids)
|
| 589 |
+
|
| 590 |
+
stat_dict["num_anti_anno_sent"] = num_anti_anno_sent
|
| 591 |
+
|
| 592 |
+
# Len of sentence
|
| 593 |
+
totle_len = 0
|
| 594 |
+
for sent_info in self.load_sents(sent_ids):
|
| 595 |
+
totle_len += len(sent_info["raw_sent"].split())
|
| 596 |
+
|
| 597 |
+
stat_dict["avg_sent_len"] = totle_len / stat_dict["nsent"]
|
| 598 |
+
|
| 599 |
+
print(stat_dict)
|
| 600 |
+
|
| 601 |
+
def is_revsent(self, sent_id):
|
| 602 |
+
sent_info = self.load_sents(sent_ids=sent_id)
|
| 603 |
+
return sent_info[0]["is_negative"]
|
| 604 |
+
|
| 605 |
+
def data2coca(self, out_root, with_rev=False):
|
| 606 |
+
group_infos = self.load_groups()
|
| 607 |
+
for group_info in group_infos:
|
| 608 |
+
sent_ids = group_info["inner_sent_id"]
|
| 609 |
+
if not with_rev:
|
| 610 |
+
sent_ids = [
|
| 611 |
+
sent_id for sent_id in sent_ids if not self.is_revsent(sent_id)
|
| 612 |
+
]
|
| 613 |
+
sent_infos = self.load_sents(sent_ids)
|
| 614 |
+
for sent_info in sent_infos:
|
| 615 |
+
sent = sent_info["raw_sent"]
|
| 616 |
+
img_infos = self.load_imgs(group_info["img_id"])
|
| 617 |
+
for img_info in img_infos:
|
| 618 |
+
src_img_path = osp.join(self.image_dir, img_info["file_name"])
|
| 619 |
+
raw_name = img_info["file_name"].split("/")[-1]
|
| 620 |
+
out_img_dir = osp.join(out_root, "images", sent)
|
| 621 |
+
os.makedirs(out_img_dir, exist_ok=True)
|
| 622 |
+
out_img_path = osp.join(out_img_dir, raw_name)
|
| 623 |
+
copy_file(src_img_path, out_img_path)
|
| 624 |
+
|
| 625 |
+
out_mask_dir = osp.join(out_root, "masks", sent)
|
| 626 |
+
os.makedirs(out_mask_dir, exist_ok=True)
|
| 627 |
+
out_mask_path = osp.join(
|
| 628 |
+
out_mask_dir, raw_name.replace(".jpg", ".png")
|
| 629 |
+
)
|
| 630 |
+
|
| 631 |
+
cur_anno_ids = self.get_anno_ids(
|
| 632 |
+
img_ids=img_info["id"], sent_ids=sent_info["id"]
|
| 633 |
+
)
|
| 634 |
+
anno_infos = self.load_annos(cur_anno_ids)
|
| 635 |
+
rle_list = [anno_info["segmentation"] for anno_info in anno_infos]
|
| 636 |
+
bmask = merge2bin(rle_list, img_info["height"], img_info["width"])
|
| 637 |
+
cv2.imwrite(out_mask_path, bmask)
|
| 638 |
+
|
| 639 |
+
def convert2coco(self, out_root, anti_mode=False, is_group_separated=True):
|
| 640 |
+
"""
|
| 641 |
+
Convert the annotation format of D^3 dataset to COCO.
|
| 642 |
+
1. The sent_id can be viewed as category_id in COCO.
|
| 643 |
+
2. If `is_group_separated` is True, `outer_sent_id` does not need to be considered.
|
| 644 |
+
3. if `with_rev` is False, sents that meet `is_revsent` will be ignore.
|
| 645 |
+
"""
|
| 646 |
+
os.makedirs(out_root, exist_ok=True)
|
| 647 |
+
coco_dict = {
|
| 648 |
+
"images": [],
|
| 649 |
+
"categories": [],
|
| 650 |
+
"annotations": [],
|
| 651 |
+
}
|
| 652 |
+
|
| 653 |
+
sent_ids = self.get_sent_ids()
|
| 654 |
+
if anti_mode == 1:
|
| 655 |
+
sent_ids = [sent_id for sent_id in sent_ids if not self.is_revsent(sent_id)]
|
| 656 |
+
elif anti_mode == 2:
|
| 657 |
+
sent_ids = [sent_id for sent_id in sent_ids if self.is_revsent(sent_id)]
|
| 658 |
+
elif anti_mode == 0:
|
| 659 |
+
pass
|
| 660 |
+
else:
|
| 661 |
+
raise Exception("Unimplemented anti_mode.")
|
| 662 |
+
|
| 663 |
+
sent_infos = self.load_sents(sent_ids)
|
| 664 |
+
for isent_info in sent_infos:
|
| 665 |
+
coco_dict["categories"].append(
|
| 666 |
+
{
|
| 667 |
+
"id": isent_info["id"],
|
| 668 |
+
"name": isent_info["raw_sent"],
|
| 669 |
+
}
|
| 670 |
+
)
|
| 671 |
+
|
| 672 |
+
item_id = 0
|
| 673 |
+
img_infos = self.load_imgs()
|
| 674 |
+
for iimg_info in img_infos:
|
| 675 |
+
coco_dict["images"].append(
|
| 676 |
+
{
|
| 677 |
+
"id": iimg_info["id"],
|
| 678 |
+
"file_name": iimg_info["file_name"],
|
| 679 |
+
"height": iimg_info["height"],
|
| 680 |
+
"width": iimg_info["width"],
|
| 681 |
+
}
|
| 682 |
+
)
|
| 683 |
+
|
| 684 |
+
anno_ids = self.get_anno_ids(img_ids=iimg_info["id"])
|
| 685 |
+
anno_infos = self.load_annos(anno_ids)
|
| 686 |
+
|
| 687 |
+
for ianno_info in anno_infos:
|
| 688 |
+
if is_group_separated:
|
| 689 |
+
inner_group_sent_ids = [
|
| 690 |
+
isent_id
|
| 691 |
+
for isent_id in ianno_info["sent_id"]
|
| 692 |
+
if isent_id
|
| 693 |
+
in self.load_groups(ianno_info["group_id"])[0]["inner_sent_id"]
|
| 694 |
+
]
|
| 695 |
+
cur_sent_ids = inner_group_sent_ids
|
| 696 |
+
else:
|
| 697 |
+
cur_sent_ids = ianno_info["sent_id"]
|
| 698 |
+
|
| 699 |
+
for isent_id in cur_sent_ids:
|
| 700 |
+
if isent_id not in sent_ids:
|
| 701 |
+
continue
|
| 702 |
+
|
| 703 |
+
seg = ianno_info["segmentation"][0].copy()
|
| 704 |
+
if isinstance(seg, dict): # RLE
|
| 705 |
+
counts = seg["counts"]
|
| 706 |
+
if not isinstance(counts, str):
|
| 707 |
+
# make it json-serializable
|
| 708 |
+
seg["counts"] = counts.decode("ascii")
|
| 709 |
+
|
| 710 |
+
coco_dict["annotations"].append(
|
| 711 |
+
{
|
| 712 |
+
"id": item_id,
|
| 713 |
+
"image_id": iimg_info["id"],
|
| 714 |
+
"category_id": isent_id,
|
| 715 |
+
"segmentation": seg,
|
| 716 |
+
"area": int(ianno_info["area"][0]),
|
| 717 |
+
"bbox": [
|
| 718 |
+
int(cord) for cord in ianno_info["bbox"][0].tolist()
|
| 719 |
+
],
|
| 720 |
+
"iscrowd": 0, # TODO: ianno_info["iscrowd"]
|
| 721 |
+
}
|
| 722 |
+
)
|
| 723 |
+
item_id += 1
|
| 724 |
+
|
| 725 |
+
with open(osp.join(out_root, "coco_annotations.json"), "w") as f:
|
| 726 |
+
json.dump(coco_dict, f, indent=4)
|
| 727 |
+
|
| 728 |
+
def sent_analyse(self, save_dir, with_rev=False):
|
| 729 |
+
"""analyze word info in D-cube and generate word length histograms, word clouds, etc.
|
| 730 |
+
|
| 731 |
+
Args:
|
| 732 |
+
save_dir (str): path to save the visualized results.
|
| 733 |
+
with_rev (bool, optional): consider absence descriptions or not. Defaults to False.
|
| 734 |
+
"""
|
| 735 |
+
sent_ids = self.get_sent_ids()
|
| 736 |
+
if not with_rev:
|
| 737 |
+
sent_ids = [sent_id for sent_id in sent_ids if not self.is_revsent(sent_id)]
|
| 738 |
+
|
| 739 |
+
sent_lens, sent_raws = [], []
|
| 740 |
+
sent_infos = self.load_sents(sent_ids)
|
| 741 |
+
for isent_info in sent_infos:
|
| 742 |
+
sent_raws.append(isent_info["raw_sent"])
|
| 743 |
+
sent_lens.append(len(isent_info["raw_sent"].split()))
|
| 744 |
+
|
| 745 |
+
os.makedirs(save_dir, exist_ok=True)
|
| 746 |
+
# plot_hist(
|
| 747 |
+
# sent_lens,
|
| 748 |
+
# bins=max(sent_lens) - min(sent_lens) + 1,
|
| 749 |
+
# save_path=osp.join(save_dir, "words_hist.pdf"),
|
| 750 |
+
# x="Lengths of descriptions",
|
| 751 |
+
# )
|
| 752 |
+
# generate_wordclouds(sent_raws, osp.join(save_dir, "word_clouds"))
|
| 753 |
+
|
| 754 |
+
def group_analysis(self, save_dir, with_rev=False):
|
| 755 |
+
group_infos = self.load_groups()
|
| 756 |
+
scene_tree = defaultdict(dict)
|
| 757 |
+
|
| 758 |
+
for group_info in group_infos:
|
| 759 |
+
scene_tree[group_info["scene"]][group_info["group_name"]] = {"nimg": 0.1}
|
| 760 |
+
|
| 761 |
+
# vis_group_tree(scene_tree, osp.join(save_dir, 'scene_tree.png')) # the visualized result is ugly
|
| 762 |
+
|
| 763 |
+
def bbox_num_analyze(self):
|
| 764 |
+
n_cat = len(self.data["sentences"].keys())
|
| 765 |
+
all_img_ids = self.data["images"].keys()
|
| 766 |
+
n_img = len(all_img_ids)
|
| 767 |
+
cat_obj_count = np.zeros((n_cat, n_img), dtype=int)
|
| 768 |
+
for img_id in all_img_ids:
|
| 769 |
+
# img_cat_ids = self.get_sent_ids(img_ids=img_id)
|
| 770 |
+
anno_ids = self.get_anno_ids(img_ids=img_id)
|
| 771 |
+
anno_infos = self.load_annos(anno_ids=anno_ids)
|
| 772 |
+
for anno in anno_infos:
|
| 773 |
+
for sid in anno["sent_id"]:
|
| 774 |
+
cat_obj_count[sid - 1, img_id] += 1
|
| 775 |
+
return cat_obj_count
|
approach/ovod/d-cube/d_cube/data_util.py
ADDED
|
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
__author__ = "Chi Xie and Zhao Zhang"
|
| 3 |
+
__maintainer__ = "Chi Xie"
|
| 4 |
+
# data utility functions are defined in the script
|
| 5 |
+
import json
|
| 6 |
+
import pickle
|
| 7 |
+
import shutil
|
| 8 |
+
|
| 9 |
+
# from io import StringIO
|
| 10 |
+
# import string
|
| 11 |
+
|
| 12 |
+
import numpy as np
|
| 13 |
+
import cv2
|
| 14 |
+
from pycocotools import mask as cocomask
|
| 15 |
+
|
| 16 |
+
VOC_COLORMAP = [
|
| 17 |
+
[128, 0, 0],
|
| 18 |
+
[0, 128, 0],
|
| 19 |
+
[128, 128, 0],
|
| 20 |
+
[0, 0, 128],
|
| 21 |
+
[128, 0, 128],
|
| 22 |
+
[0, 128, 128],
|
| 23 |
+
[128, 128, 128],
|
| 24 |
+
[64, 0, 0],
|
| 25 |
+
[192, 0, 0],
|
| 26 |
+
[64, 128, 0],
|
| 27 |
+
[192, 128, 0],
|
| 28 |
+
[64, 0, 128],
|
| 29 |
+
[192, 0, 128],
|
| 30 |
+
[64, 128, 128],
|
| 31 |
+
[192, 128, 128],
|
| 32 |
+
[0, 64, 0],
|
| 33 |
+
[128, 64, 0],
|
| 34 |
+
[0, 192, 0],
|
| 35 |
+
[128, 192, 0],
|
| 36 |
+
[0, 64, 128],
|
| 37 |
+
]
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def visualize_bbox_on_image(img, bbox_list, save_path=None, thickness=3):
|
| 41 |
+
img_copy = img.copy()
|
| 42 |
+
for i, bbox in enumerate(bbox_list):
|
| 43 |
+
color = tuple(VOC_COLORMAP[i % len(VOC_COLORMAP)])
|
| 44 |
+
x, y, w, h = bbox
|
| 45 |
+
img_copy = cv2.rectangle(
|
| 46 |
+
img_copy, (int(x), int(y)), (int((x + w)), int(y + h)), color, thickness
|
| 47 |
+
)
|
| 48 |
+
if save_path:
|
| 49 |
+
cv2.imwrite(save_path, img_copy)
|
| 50 |
+
return img_copy
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
def rle2bmask(rle):
|
| 54 |
+
bm = cocomask.decode(rle)
|
| 55 |
+
if len(bm.shape) == 3:
|
| 56 |
+
bm = np.sum(
|
| 57 |
+
bm, axis=2
|
| 58 |
+
) # sometimes there are multiple binary map (corresponding to multiple segs)
|
| 59 |
+
bm = bm.astype(np.uint8) # convert to np.uint8
|
| 60 |
+
return bm
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def merge_rle(rle_list, is_instance=True, on_image=False):
|
| 64 |
+
if is_instance:
|
| 65 |
+
cm_list = []
|
| 66 |
+
for rle_idx, rle in enumerate(rle_list):
|
| 67 |
+
color = VOC_COLORMAP[rle_idx]
|
| 68 |
+
bm = rle2bmask(rle)
|
| 69 |
+
cm = cv2.cvtColor(bm, cv2.COLOR_GRAY2BGR)
|
| 70 |
+
cm_list.append(cm * color)
|
| 71 |
+
merge_map = np.sum(cm_list, axis=0, dtype=np.uint8)
|
| 72 |
+
else:
|
| 73 |
+
bm_list = [rle2bmask(rle) for rle in rle_list]
|
| 74 |
+
merge_map = np.sum(bm_list, axis=0, dtype=np.uint8)
|
| 75 |
+
merge_map[merge_map >= 1] = 1
|
| 76 |
+
if not on_image:
|
| 77 |
+
color = VOC_COLORMAP[0]
|
| 78 |
+
merge_map = cv2.cvtColor(merge_map, cv2.COLOR_GRAY2BGR)
|
| 79 |
+
merge_map *= np.array(color, dtype=np.uint8)
|
| 80 |
+
|
| 81 |
+
merge_map[merge_map > 255] = 255
|
| 82 |
+
|
| 83 |
+
if not on_image:
|
| 84 |
+
tmp_sum_map = np.sum(merge_map, axis=-1)
|
| 85 |
+
merge_map[tmp_sum_map == 0] = 220
|
| 86 |
+
return merge_map
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def merge2bin(rle_list, img_h, img_w):
|
| 90 |
+
if rle_list:
|
| 91 |
+
bm_list = [rle2bmask(rle) for rle in rle_list]
|
| 92 |
+
merge_map = np.sum(bm_list, axis=0, dtype=np.uint8)
|
| 93 |
+
merge_map[merge_map >= 1] = 255
|
| 94 |
+
merge_map = np.expand_dims(merge_map, axis=-1)
|
| 95 |
+
return merge_map
|
| 96 |
+
else:
|
| 97 |
+
return np.zeros([img_h, img_w, 1], dtype=np.uint8)
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
def paste_text(img, text):
|
| 101 |
+
fontFace = cv2.FONT_HERSHEY_COMPLEX_SMALL
|
| 102 |
+
overlay = img.copy()
|
| 103 |
+
# fontFace = cv2.FONT_HERSHEY_TRIPLEX
|
| 104 |
+
fontScale = 1
|
| 105 |
+
thickness = 1
|
| 106 |
+
backgroud_alpha = 0.8
|
| 107 |
+
|
| 108 |
+
retval, baseLine = cv2.getTextSize(
|
| 109 |
+
text, fontFace=fontFace, fontScale=fontScale, thickness=thickness
|
| 110 |
+
)
|
| 111 |
+
topleft = (0, 0)
|
| 112 |
+
# bottomright = (topleft[0] + retval[0], topleft[1] + retval[1]+10)
|
| 113 |
+
bottomright = (img.shape[1], topleft[1] + retval[1] + 10)
|
| 114 |
+
|
| 115 |
+
cv2.rectangle(overlay, topleft, bottomright, thickness=-1, color=(250, 250, 250))
|
| 116 |
+
img = cv2.addWeighted(overlay, backgroud_alpha, img, 1 - backgroud_alpha, 0)
|
| 117 |
+
|
| 118 |
+
cv2.putText(
|
| 119 |
+
img,
|
| 120 |
+
text,
|
| 121 |
+
(0, baseLine + 10),
|
| 122 |
+
fontScale=fontScale,
|
| 123 |
+
fontFace=fontFace,
|
| 124 |
+
thickness=thickness,
|
| 125 |
+
color=(10, 10, 10),
|
| 126 |
+
)
|
| 127 |
+
return img
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
def load_json(json_path, to_int=False):
|
| 131 |
+
clean_res_dic = {}
|
| 132 |
+
with open(json_path, "r", encoding="utf-8") as f_in:
|
| 133 |
+
res_dic = json.load(f_in)
|
| 134 |
+
|
| 135 |
+
for ikey, iv in res_dic.items():
|
| 136 |
+
ikey = int(ikey.strip()) if to_int else ikey.strip()
|
| 137 |
+
clean_res_dic[ikey] = iv
|
| 138 |
+
|
| 139 |
+
return clean_res_dic
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def path_map(src_path, obj_path):
|
| 143 |
+
def inner_map(full_path):
|
| 144 |
+
return full_path.replace(src_path, obj_path)
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def save_pkl(src, obj_path):
|
| 148 |
+
with open(obj_path, "wb") as f_out:
|
| 149 |
+
pickle.dump(src, f_out)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def load_pkl(src_path):
|
| 153 |
+
with open(src_path, "rb") as f_in:
|
| 154 |
+
in_pkl = pickle.load(f_in)
|
| 155 |
+
return in_pkl
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def copy_file(src_path, obj_path):
|
| 159 |
+
shutil.copy(src_path, obj_path)
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
def sentence_analysis():
|
| 163 |
+
return 0
|
| 164 |
+
|
| 165 |
+
|
| 166 |
+
def add_checkerboard_bg(image, mask, save_path=None):
|
| 167 |
+
# Create a new image with the same size as the original image
|
| 168 |
+
new_image = np.zeros_like(image)
|
| 169 |
+
|
| 170 |
+
# Define the size of the checkerboard pattern
|
| 171 |
+
checkerboard_size = 24
|
| 172 |
+
|
| 173 |
+
# Loop over each pixel in the mask
|
| 174 |
+
for x in range(mask.shape[1]):
|
| 175 |
+
for y in range(mask.shape[0]):
|
| 176 |
+
# If the pixel is transparent, draw a checkerboard pattern
|
| 177 |
+
if mask[y, x] == 0:
|
| 178 |
+
if (x // checkerboard_size) % 2 == (y // checkerboard_size) % 2:
|
| 179 |
+
new_image[y, x] = (255, 255, 255)
|
| 180 |
+
else:
|
| 181 |
+
new_image[y, x] = (128, 128, 128)
|
| 182 |
+
# Otherwise, copy the corresponding pixel from the original image
|
| 183 |
+
else:
|
| 184 |
+
new_image[y, x] = image[y, x]
|
| 185 |
+
|
| 186 |
+
# Save the new image with the checkerboard background
|
| 187 |
+
if save_path:
|
| 188 |
+
cv2.imwrite(save_path, new_image)
|
| 189 |
+
return new_image
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def visualize_mask_on_image(
|
| 193 |
+
img, mask, save_path=None, add_edge=False, dark_background=False
|
| 194 |
+
):
|
| 195 |
+
# Convert the mask to a binary mask if it's not already
|
| 196 |
+
if mask.max() > 1:
|
| 197 |
+
mask = mask.astype(np.uint8) // 255
|
| 198 |
+
|
| 199 |
+
# Convert the mask to a 3-channel mask if it's not already
|
| 200 |
+
if len(mask.shape) == 2:
|
| 201 |
+
mask = np.expand_dims(mask, axis=2)
|
| 202 |
+
mask = np.tile(mask, (1, 1, 3))
|
| 203 |
+
|
| 204 |
+
# Create a color map for the mask
|
| 205 |
+
cmap = np.array([255, 117, 44], dtype=np.uint8)
|
| 206 |
+
mask_colors = mask * cmap
|
| 207 |
+
|
| 208 |
+
# Add an opaque white edge to the mask if desired
|
| 209 |
+
if add_edge:
|
| 210 |
+
if len(mask.shape) == 2:
|
| 211 |
+
mask = np.expand_dims(mask, axis=2)
|
| 212 |
+
mask = np.tile(mask, (1, 1, 3))
|
| 213 |
+
|
| 214 |
+
kernel = np.ones((5, 5), dtype=np.uint8)
|
| 215 |
+
mask_edge = cv2.erode(mask, kernel, iterations=1)
|
| 216 |
+
mask_edge = mask - mask_edge
|
| 217 |
+
|
| 218 |
+
# mask_edge = np.tile(mask_edge[:, :, np.newaxis], [1, 1, 3])
|
| 219 |
+
mask_colors[mask_edge > 0] = 255
|
| 220 |
+
|
| 221 |
+
# Overlay the mask on the masked image
|
| 222 |
+
if dark_background:
|
| 223 |
+
masked_img = cv2.addWeighted(img, 0.4, mask_colors, 0.6, 0)
|
| 224 |
+
else:
|
| 225 |
+
masked_img = img.copy()
|
| 226 |
+
masked_img[mask > 0] = cv2.addWeighted(img, 0.4, mask_colors, 0.6, 0)[mask > 0]
|
| 227 |
+
|
| 228 |
+
# Save the result to the specified path if provided
|
| 229 |
+
if save_path is not None:
|
| 230 |
+
cv2.imwrite(save_path, masked_img)
|
| 231 |
+
|
| 232 |
+
return masked_img
|
| 233 |
+
|
| 234 |
+
|
| 235 |
+
# def visualize_mask_on_image(img, mask, save_path=None, add_edge=False):
|
| 236 |
+
# # Convert the mask to a binary mask if it's not already
|
| 237 |
+
# if mask.max() > 1:
|
| 238 |
+
# mask = mask.astype(np.uint8) // 255
|
| 239 |
+
|
| 240 |
+
# # Convert the mask to a 3-channel mask if it's not already
|
| 241 |
+
# if len(mask.shape) == 2:
|
| 242 |
+
# mask = np.expand_dims(mask, axis=2)
|
| 243 |
+
# mask = np.tile(mask, (1, 1, 3))
|
| 244 |
+
|
| 245 |
+
# # Create a color map for the mask
|
| 246 |
+
# cmap = np.array([255, 117, 44], dtype=np.uint8)
|
| 247 |
+
# mask_colors = mask * cmap
|
| 248 |
+
|
| 249 |
+
# # Add an opaque white edge to the mask if desired
|
| 250 |
+
# if add_edge:
|
| 251 |
+
# if len(mask.shape) == 2:
|
| 252 |
+
# mask = np.expand_dims(mask, axis=2)
|
| 253 |
+
# mask = np.tile(mask, (1, 1, 3))
|
| 254 |
+
|
| 255 |
+
# kernel = np.ones((5, 5), dtype=np.uint8)
|
| 256 |
+
# mask_edge = cv2.erode(mask, kernel, iterations=1)
|
| 257 |
+
# mask_edge = mask - mask_edge
|
| 258 |
+
|
| 259 |
+
# # mask_edge = np.tile(mask_edge[:, :, np.newaxis], [1, 1, 3])
|
| 260 |
+
# mask_colors[mask_edge > 0] = 255
|
| 261 |
+
|
| 262 |
+
# # Overlay the mask on the masked image
|
| 263 |
+
# masked_img = cv2.addWeighted(img, 0.5, mask_colors, 0.5, 0)
|
| 264 |
+
|
| 265 |
+
# # Save the result to the specified path if provided
|
| 266 |
+
# if save_path is not None:
|
| 267 |
+
# cv2.imwrite(save_path, masked_img)
|
| 268 |
+
|
| 269 |
+
# return masked_img
|
approach/ovod/d-cube/d_cube/vis_util.py
ADDED
|
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
__author__ = "Chi Xie and Zhao Zhang"
|
| 3 |
+
__maintainer__ = "Chi Xie"
|
| 4 |
+
import os
|
| 5 |
+
from collections import Counter
|
| 6 |
+
|
| 7 |
+
import spacy
|
| 8 |
+
import matplotlib.pyplot as plt
|
| 9 |
+
import seaborn as sns
|
| 10 |
+
from wordcloud import WordCloud
|
| 11 |
+
|
| 12 |
+
# from pycirclize import Circos
|
| 13 |
+
# from Bio.Phylo.BaseTree import Tree
|
| 14 |
+
# from Bio import Phylo
|
| 15 |
+
# from newick import Node
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def plot_hist(data, bins=10, is_norm=False, save_path=None, x=None):
|
| 19 |
+
sns.set_theme(style="whitegrid", font_scale=2.0)
|
| 20 |
+
ax = sns.histplot(data, bins=bins, common_norm=is_norm, kde=False)
|
| 21 |
+
ax.set_xlabel(x)
|
| 22 |
+
plt.tight_layout()
|
| 23 |
+
plt.savefig(save_path)
|
| 24 |
+
plt.close()
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def plot_bars(names, nums, is_sort, save_path=None):
|
| 28 |
+
sns.set(style="whitegrid")
|
| 29 |
+
|
| 30 |
+
if is_sort:
|
| 31 |
+
zipped = zip(nums, names)
|
| 32 |
+
sort_zipped = sorted(zipped, key=lambda x: (x[0], x[1]))
|
| 33 |
+
result = zip(*sort_zipped)
|
| 34 |
+
nums, names = [list(x) for x in result]
|
| 35 |
+
|
| 36 |
+
fontx = {"family": "Times New Roman", "size": 10}
|
| 37 |
+
fig, ax = plt.subplots()
|
| 38 |
+
fig = plt.figure(figsize=(16, 4))
|
| 39 |
+
# sns.set_palette("PuBuGn_d")
|
| 40 |
+
sns.barplot(names, nums, palette=sns.cubehelix_palette(80, start=0.5, rot=-0.75))
|
| 41 |
+
fig.autofmt_xdate(rotation=90)
|
| 42 |
+
plt.tick_params(axis="x", labelsize=10)
|
| 43 |
+
labels = ax.get_xticklabels() + ax.get_yticklabels()
|
| 44 |
+
[label.set_fontname("Times New Roman") for label in labels]
|
| 45 |
+
plt.tight_layout()
|
| 46 |
+
plt.savefig(save_path)
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def generate_wordclouds(sentences, save_dir):
|
| 50 |
+
"""Generates word clouds for different parts of speech in a list of sentences.
|
| 51 |
+
|
| 52 |
+
Args:
|
| 53 |
+
sentences: A list of sentences.
|
| 54 |
+
save_dir: The directory to save the word cloud images.
|
| 55 |
+
"""
|
| 56 |
+
|
| 57 |
+
os.makedirs(save_dir, exist_ok=True)
|
| 58 |
+
# Load the spacy model
|
| 59 |
+
nlp = spacy.load("en_core_web_sm")
|
| 60 |
+
|
| 61 |
+
# Define the parts of speech to include in the word clouds
|
| 62 |
+
pos_to_include = ["NOUN", "VERB", "ADJ", "ADV"]
|
| 63 |
+
|
| 64 |
+
# Process each sentence and collect the relevant words for each part of speech
|
| 65 |
+
words_by_pos = {pos: [] for pos in pos_to_include}
|
| 66 |
+
for sent in sentences:
|
| 67 |
+
doc = nlp(sent)
|
| 68 |
+
for token in doc:
|
| 69 |
+
if token.pos_ in pos_to_include:
|
| 70 |
+
words_by_pos[token.pos_].append(token.lemma_.lower())
|
| 71 |
+
|
| 72 |
+
# Generate a word cloud for each part of speech
|
| 73 |
+
for pos, words in words_by_pos.items():
|
| 74 |
+
if len(words) == 0:
|
| 75 |
+
continue # skip parts of speech with no words
|
| 76 |
+
|
| 77 |
+
# Count the frequency of each word
|
| 78 |
+
word_counts = Counter(words)
|
| 79 |
+
|
| 80 |
+
# Generate the word cloud
|
| 81 |
+
wordcloud = WordCloud(
|
| 82 |
+
width=800,
|
| 83 |
+
height=800,
|
| 84 |
+
background_color="white",
|
| 85 |
+
max_words=200,
|
| 86 |
+
colormap="Set2",
|
| 87 |
+
max_font_size=150,
|
| 88 |
+
).generate_from_frequencies(word_counts)
|
| 89 |
+
|
| 90 |
+
# Save the word cloud image
|
| 91 |
+
filename = f"{pos.lower()}_wordcloud.png"
|
| 92 |
+
filepath = os.path.join(save_dir, filename)
|
| 93 |
+
wordcloud.to_file(filepath)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
# def vis_group_tree(data_dict, save_path):
|
| 97 |
+
|
| 98 |
+
# # Create 3 randomized trees
|
| 99 |
+
# tree_size_list = [60, 40, 50]
|
| 100 |
+
# trees = [Tree.randomized(string.ascii_uppercase, branch_stdev=0.5) for size in tree_size_list]
|
| 101 |
+
|
| 102 |
+
# # Initialize circos sector with 3 randomized tree size
|
| 103 |
+
# sectors = {name: size for name, size in zip(list("ABC"), tree_size_list)}
|
| 104 |
+
# circos = Circos(sectors, space=5)
|
| 105 |
+
|
| 106 |
+
# colors = ["tomato", "skyblue", "limegreen"]
|
| 107 |
+
# cmaps = ["bwr", "viridis", "Spectral"]
|
| 108 |
+
# for idx, sector in enumerate(circos.sectors):
|
| 109 |
+
# sector.text(sector.name, r=120, size=12)
|
| 110 |
+
# # Plot randomized tree
|
| 111 |
+
# tree = trees[idx]
|
| 112 |
+
# tree_track = sector.add_track((30, 70))
|
| 113 |
+
# tree_track.axis(fc=colors[idx], alpha=0.2)
|
| 114 |
+
# tree_track.tree(tree, leaf_label_size=3, leaf_label_margin=21)
|
| 115 |
+
# # Plot randomized bar
|
| 116 |
+
# bar_track = sector.add_track((70, 90))
|
| 117 |
+
# x = np.arange(0, int(sector.size)) + 0.5
|
| 118 |
+
# height = np.random.randint(1, 10, int(sector.size))
|
| 119 |
+
# bar_track.bar(x, height, facecolor=colors[idx], ec="grey", lw=0.5, hatch="//")
|
| 120 |
+
|
| 121 |
+
# circos.savefig(save_path, dpi=600)
|
| 122 |
+
|
| 123 |
+
# def clean_newick_key(in_str):
|
| 124 |
+
# bad_chars = [':', ';', ',', '(', ')']
|
| 125 |
+
# for bad_char in bad_chars:
|
| 126 |
+
# in_str = in_str.replace(bad_char, ' ')
|
| 127 |
+
# return in_str
|
| 128 |
+
|
| 129 |
+
# def build_tree_from_dict(data):
|
| 130 |
+
# root = Node() # create the root node
|
| 131 |
+
# for key, value in data.items():
|
| 132 |
+
# node = Node(name=clean_newick_key(key)) # name doesn't need to be cleaned
|
| 133 |
+
# if value is not None:
|
| 134 |
+
# child_node = build_tree_from_dict(value)
|
| 135 |
+
# node.add_descendant(child_node)
|
| 136 |
+
# root.add_descendant(node)
|
| 137 |
+
|
| 138 |
+
# return root
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def replace_chars_in_dict_keys(d):
|
| 142 |
+
"""
|
| 143 |
+
Replaces the characters ':', ';', ',', '(', and ')' in the keys of a nested dictionary with '_'.
|
| 144 |
+
"""
|
| 145 |
+
new_dict = {}
|
| 146 |
+
for k, v in d.items():
|
| 147 |
+
if isinstance(v, dict):
|
| 148 |
+
v = replace_chars_in_dict_keys(v)
|
| 149 |
+
new_key = k.translate(str.maketrans(":;,()", "_____"))
|
| 150 |
+
new_dict[new_key] = v
|
| 151 |
+
return new_dict
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def build_newick_tree(tree_dict):
|
| 155 |
+
newick_tree = ""
|
| 156 |
+
if isinstance(tree_dict, dict):
|
| 157 |
+
for key, value in tree_dict.items():
|
| 158 |
+
if isinstance(value, dict):
|
| 159 |
+
subtree = build_newick_tree(value)
|
| 160 |
+
if subtree:
|
| 161 |
+
newick_tree += "(" + subtree + ")" + key + ","
|
| 162 |
+
else:
|
| 163 |
+
newick_tree += key + ","
|
| 164 |
+
else:
|
| 165 |
+
newick_tree += key + ":" + str(value) + ","
|
| 166 |
+
newick_tree = newick_tree.rstrip(",") + ")"
|
| 167 |
+
return newick_tree
|
| 168 |
+
else:
|
| 169 |
+
return None
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
# def vis_group_tree(data_dict, save_path):
|
| 173 |
+
# data_dic = replace_chars_in_dict_keys(data_dict)
|
| 174 |
+
# super_group_names = data_dict.keys()
|
| 175 |
+
|
| 176 |
+
# # Create 3 randomized trees
|
| 177 |
+
# tree_size_list = [60, 40, 50]
|
| 178 |
+
# trees = [Phylo.read(StringIO(build_newick_tree(data_dict[super_group_name])), "newick") for super_group_name in super_group_names]
|
| 179 |
+
|
| 180 |
+
# # Initialize circos sector with 3 randomized tree size
|
| 181 |
+
# sectors = {name: size for name, size in zip(list("ABC"), tree_size_list)}
|
| 182 |
+
# circos = Circos(sectors, space=5)
|
| 183 |
+
|
| 184 |
+
# colors = ["tomato", "skyblue", "limegreen"]
|
| 185 |
+
# cmaps = ["bwr", "viridis", "Spectral"]
|
| 186 |
+
# for idx, sector in enumerate(circos.sectors):
|
| 187 |
+
# sector.text(sector.name, r=120, size=12)
|
| 188 |
+
# # Plot randomized tree
|
| 189 |
+
# tree = trees[idx]
|
| 190 |
+
# tree_track = sector.add_track((30, 70))
|
| 191 |
+
# tree_track.axis(fc=colors[idx], alpha=0.2)
|
| 192 |
+
# tree_track.tree(tree, leaf_label_size=3, leaf_label_margin=21)
|
| 193 |
+
# # Plot randomized bar
|
| 194 |
+
# bar_track = sector.add_track((70, 90))
|
| 195 |
+
# x = np.arange(0, int(sector.size)) + 0.5
|
| 196 |
+
# height = np.random.randint(1, 10, int(sector.size))
|
| 197 |
+
# bar_track.bar(x, height, facecolor=colors[idx], ec="grey", lw=0.5, hatch="//")
|
| 198 |
+
|
| 199 |
+
# circos.savefig(save_path, dpi=600)
|
approach/ovod/d-cube/eval_sota/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Evaluting SOTA Methods on $D^3$
|
| 2 |
+
|
| 3 |
+
## Leaderboard
|
| 4 |
+
|
| 5 |
+
In this directory, we keep the scripts or github links (official or custom) to evaluate SOTA methods (REC/OVD/DOD/MLLM) on $D^3$:
|
| 6 |
+
|
| 7 |
+
| Name | Paper | Original Tasks | Training Data | Evaluation Code | Intra-FULL/PRES/ABS/Inter-FULL/PRES/ABS | Source | Note |
|
| 8 |
+
|:-----|:-----:|:----:|:-----:|:-----:|:-----:|:-----:|:-----:|
|
| 9 |
+
| OFA-large | [OFA: Unifying Architectures, Tasks, and Modalities Through a Simple Sequence-to-Sequence Learning Framework (ICML 2022)](https://arxiv.org/abs/2202.03052) | REC | - | - | 4.2/4.1/4.6/0.1/0.1/0.1 | [DOD paper](https://arxiv.org/abs/2307.12813) | - |
|
| 10 |
+
| CORA-R50 | [CORA: Adapting CLIP for Open-Vocabulary Detection with Region Prompting and Anchor Pre-Matching (CVPR 2023)](https://openaccess.thecvf.com/content/CVPR2023/papers/Wu_CORA_Adapting_CLIP_for_Open-Vocabulary_Detection_With_Region_Prompting_and_CVPR_2023_paper.pdf) | OVD | - | - | 6.2/6.7/5.0/2.0/2.2/1.3 | [DOD paper](https://arxiv.org/abs/2307.12813) | - |
|
| 11 |
+
| OWL-ViT-large | [Simple Open-Vocabulary Object Detection with Vision Transformers (ECCV 2022)](https://www.ecva.net/papers/eccv_2022/papers_ECCV/papers/136700714.pdf) | OVD | - | [DOD official](./owl_vit.py) | 9.6/10.7/6.4/2.5/2.9/2.1 | [DOD paper](https://arxiv.org/abs/2307.12813) | Post-processing hyper-parameters may affect the performance and the result may not exactly match the paper |
|
| 12 |
+
| SPHINX-7B | [SPHINX: The Joint Mixing of Weights, Tasks, and Visual Embeddings for Multi-modal Large Language Models (arxiv 2023)](https://arxiv.org/abs/2311.07575) | **MLLM** capable of REC | - | [DOD official](./sphinx.py) | 10.6/11.4/7.9/-/-/- | DOD authors | A lot of contribution from [Jie Li](https://github.com/theFool32) |
|
| 13 |
+
| GLIP-T | [Grounded Language-Image Pre-training (CVPR 2022)](https://arxiv.org/abs/2112.03857) | OVD & PG | - | - | 19.1/18.3/21.5/-/-/- | GEN paper | - |
|
| 14 |
+
| UNINEXT-huge | [Universal Instance Perception as Object Discovery and Retrieval (CVPR 2023)](https://arxiv.org/abs/2303.06674v2) | OVD & REC | - | [DOD official](https://github.com/Charles-Xie/UNINEXT_D3) | 20.0/20.6/18.1/3.3/3.9/1.6 | [DOD paper](https://arxiv.org/abs/2307.12813) | - |
|
| 15 |
+
| Grounding-DINO-base | [Grounding DINO: Marrying DINO with Grounded Pre-Training for Open-Set Object Detection (arxiv 2023)](https://arxiv.org/abs/2303.05499) | OVD & REC | - | [DOD official](./groundingdino.py) | 20.7/20.1/22.5/2.7/2.4/3.5 | [DOD paper](https://arxiv.org/abs/2307.12813) | Post-processing hyper-parameters may affect the performance and the result may not exactly match the paper |
|
| 16 |
+
| OFA-DOD-base | [Described Object Detection: Liberating Object Detection with Flexible Expressions (NeurIPS 2023)](https://arxiv.org/abs/2307.12813) | DOD | - | - | 21.6/23.7/15.4/5.7/6.9/2.3 | [DOD paper](https://arxiv.org/abs/2307.12813) | - |
|
| 17 |
+
| FIBER-B | [Coarse-to-Fine Vision-Language Pre-training with Fusion in the Backbone (NeurIPS 2022)](https://arxiv.org/abs/2206.07643) | OVD & REC | - | - | 22.7/21.5/26.0/-/-/- | GEN paper | - |
|
| 18 |
+
| MM-Grounding-DINO | [An Open and Comprehensive Pipeline for Unified Object Grounding and Detection (arxiv 2024)](https://arxiv.org/abs/2401.02361) | DOD & OVD & REC | O365, GoldG, GRIT, V3Det | [MM-GDINO official](https://github.com/open-mmlab/mmdetection/tree/main/configs/mm_grounding_dino#zero-shot-description-detection-datasetdod) | 22.9/21.9/26.0/-/-/- | MM-GDINO paper | - |
|
| 19 |
+
| GEN (FIBER-B) | [Generating Enhanced Negatives for Training Language-Based Object Detectors (arxiv 2024](https://arxiv.org/abs/2401.00094) | DOD | - | - | 26.0/25.2/28.1/-/-/- | GEN paper | Enhancement based on FIBER-B |
|
| 20 |
+
| APE-large (D) | [Aligning and Prompting Everything All at Once for Universal Visual Perception (arxiv 2023)](https://arxiv.org/abs/2312.02153) | DOD & OVD & REC | COCO, LVIS, O365, OpenImages, Visual Genome, RefCOCO/+/g, SA-1B, GQA, PhraseCut, Flickr30k | [APE official](https://github.com/shenyunhang/APE) | 37.5/38.8/33.9/21.0/22.0/17.9 | APE paper | Extra training data helps for this amazing performance |
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
Some extra notes:
|
| 24 |
+
- Each method is currently recorded by *the variant with the highest performance* in this table, if there are multiple variants available, so it's only a leaderboard, not meant for fair comparison.
|
| 25 |
+
- Methods like GLIP, FIBER, etc. are actually not evaluated on OVD benchmarks. For zero-shot eval on DOD, We currently do not distinguish between methods for OVD benchmarks and methods for ZS-OD, as long as it is verified with open-set detection capability.
|
| 26 |
+
|
| 27 |
+
For other variants (e.g. for a fair comparison regarding data, backbone, etc.), please refer to the papers.
|
approach/ovod/d-cube/eval_sota/groundingdino.py
ADDED
|
@@ -0,0 +1,304 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
__author__ = "Chi Xie"
|
| 3 |
+
__maintainer__ = "Chi Xie"
|
| 4 |
+
|
| 5 |
+
# An example for how to run this script:
|
| 6 |
+
# CUDA_VISIBLE_DEVICES=0
|
| 7 |
+
# python groundingdino.py \
|
| 8 |
+
# -c ./groundingdino/config/GroundingDINO_SwinB.cfg.py \
|
| 9 |
+
# -p ./ckpt/groundingdino_swinb_cogcoor.pth \
|
| 10 |
+
# -o "outputs/gdino_d3" \
|
| 11 |
+
# --box_threshold 0.05 \
|
| 12 |
+
# --text_threshold 0.05 \
|
| 13 |
+
# --img-top1
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import json
|
| 17 |
+
import os
|
| 18 |
+
|
| 19 |
+
import numpy as np
|
| 20 |
+
import torch
|
| 21 |
+
from PIL import Image, ImageDraw, ImageFont
|
| 22 |
+
from pycocotools.coco import COCO
|
| 23 |
+
from pycocotools.cocoeval import COCOeval
|
| 24 |
+
from tqdm import tqdm
|
| 25 |
+
|
| 26 |
+
import groundingdino.datasets.transforms as T
|
| 27 |
+
from groundingdino.models import build_model
|
| 28 |
+
from groundingdino.util.slconfig import SLConfig
|
| 29 |
+
from groundingdino.util.utils import clean_state_dict, get_phrases_from_posmap
|
| 30 |
+
from d_cube import D3
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def plot_boxes_to_image(image_pil, tgt):
|
| 34 |
+
H, W = tgt["size"]
|
| 35 |
+
boxes = tgt["boxes"]
|
| 36 |
+
labels = tgt["labels"]
|
| 37 |
+
assert len(boxes) == len(labels), "boxes and labels must have same length"
|
| 38 |
+
|
| 39 |
+
draw = ImageDraw.Draw(image_pil)
|
| 40 |
+
mask = Image.new("L", image_pil.size, 0)
|
| 41 |
+
mask_draw = ImageDraw.Draw(mask)
|
| 42 |
+
|
| 43 |
+
# draw boxes and masks
|
| 44 |
+
for box, label in zip(boxes, labels):
|
| 45 |
+
# from 0..1 to 0..W, 0..H
|
| 46 |
+
box = box * torch.Tensor([W, H, W, H])
|
| 47 |
+
# from xywh to xyxy
|
| 48 |
+
box[:2] -= box[2:] / 2
|
| 49 |
+
box[2:] += box[:2]
|
| 50 |
+
# random color
|
| 51 |
+
color = tuple(np.random.randint(0, 255, size=3).tolist())
|
| 52 |
+
# draw
|
| 53 |
+
x0, y0, x1, y1 = box
|
| 54 |
+
x0, y0, x1, y1 = int(x0), int(y0), int(x1), int(y1)
|
| 55 |
+
|
| 56 |
+
draw.rectangle([x0, y0, x1, y1], outline=color, width=6)
|
| 57 |
+
# draw.text((x0, y0), str(label), fill=color)
|
| 58 |
+
|
| 59 |
+
font = ImageFont.load_default()
|
| 60 |
+
if hasattr(font, "getbbox"):
|
| 61 |
+
bbox = draw.textbbox((x0, y0), str(label), font)
|
| 62 |
+
else:
|
| 63 |
+
w, h = draw.textsize(str(label), font)
|
| 64 |
+
bbox = (x0, y0, w + x0, y0 + h)
|
| 65 |
+
# bbox = draw.textbbox((x0, y0), str(label))
|
| 66 |
+
draw.rectangle(bbox, fill=color)
|
| 67 |
+
draw.text((x0, y0), str(label), fill="white")
|
| 68 |
+
|
| 69 |
+
mask_draw.rectangle([x0, y0, x1, y1], fill=255, width=6)
|
| 70 |
+
return image_pil, mask
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def load_image(image_path):
|
| 74 |
+
# load image
|
| 75 |
+
image_pil = Image.open(image_path).convert("RGB") # load image
|
| 76 |
+
|
| 77 |
+
transform = T.Compose(
|
| 78 |
+
[
|
| 79 |
+
T.RandomResize([800], max_size=1333),
|
| 80 |
+
T.ToTensor(),
|
| 81 |
+
T.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
|
| 82 |
+
]
|
| 83 |
+
)
|
| 84 |
+
image, _ = transform(image_pil, None) # 3, h, w
|
| 85 |
+
return image_pil, image
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def load_model(model_config_path, model_checkpoint_path, cpu_only=False):
|
| 89 |
+
args = SLConfig.fromfile(model_config_path)
|
| 90 |
+
args.device = "cuda" if not cpu_only else "cpu"
|
| 91 |
+
model = build_model(args)
|
| 92 |
+
checkpoint = torch.load(model_checkpoint_path, map_location="cpu")
|
| 93 |
+
load_res = model.load_state_dict(clean_state_dict(checkpoint["model"]), strict=False)
|
| 94 |
+
print(load_res)
|
| 95 |
+
_ = model.eval()
|
| 96 |
+
return model
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def get_grounding_output(model, image, caption, box_threshold, text_threshold, with_logits=True, cpu_only=False):
|
| 100 |
+
caption = caption.lower()
|
| 101 |
+
caption = caption.strip()
|
| 102 |
+
if not caption.endswith("."):
|
| 103 |
+
caption = caption + "."
|
| 104 |
+
device = "cuda" if not cpu_only else "cpu"
|
| 105 |
+
model = model.to(device)
|
| 106 |
+
image = image.to(device)
|
| 107 |
+
with torch.no_grad():
|
| 108 |
+
outputs = model(image[None], captions=[caption])
|
| 109 |
+
logits = outputs["pred_logits"].cpu().sigmoid()[0] # (nq, 256)
|
| 110 |
+
boxes = outputs["pred_boxes"].cpu()[0] # (nq, 4)
|
| 111 |
+
logits.shape[0]
|
| 112 |
+
|
| 113 |
+
# filter output
|
| 114 |
+
logits_filt = logits.clone()
|
| 115 |
+
boxes_filt = boxes.clone()
|
| 116 |
+
filt_mask = logits_filt.max(dim=1)[0] > box_threshold
|
| 117 |
+
logits_filt = logits_filt[filt_mask] # num_filt, 256
|
| 118 |
+
boxes_filt = boxes_filt[filt_mask] # num_filt, 4
|
| 119 |
+
logits_filt.shape[0]
|
| 120 |
+
|
| 121 |
+
# get phrase
|
| 122 |
+
tokenlizer = model.tokenizer
|
| 123 |
+
tokenized = tokenlizer(caption)
|
| 124 |
+
# build pred
|
| 125 |
+
pred_phrases = []
|
| 126 |
+
logits_list = []
|
| 127 |
+
for logit, box in zip(logits_filt, boxes_filt):
|
| 128 |
+
pred_phrase = get_phrases_from_posmap(logit > text_threshold, tokenized, tokenlizer)
|
| 129 |
+
logits_list.append(logit.max().item())
|
| 130 |
+
if with_logits:
|
| 131 |
+
pred_phrases.append(pred_phrase + f"({str(logit.max().item())[:4]})")
|
| 132 |
+
else:
|
| 133 |
+
pred_phrases.append(pred_phrase)
|
| 134 |
+
|
| 135 |
+
return boxes_filt, pred_phrases, logits_list
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
def get_dataset_iter(coco):
|
| 139 |
+
img_ids = coco.get_img_ids()
|
| 140 |
+
for img_id in img_ids:
|
| 141 |
+
img_info = coco.load_imgs(img_id)[0]
|
| 142 |
+
file_name = img_info["file_name"]
|
| 143 |
+
img_path = os.path.join(IMG_ROOT, file_name)
|
| 144 |
+
yield img_id, img_path
|
| 145 |
+
|
| 146 |
+
|
| 147 |
+
def eval_on_d3(pred_path, mode="pn"):
|
| 148 |
+
assert mode in ("pn", "p", "n")
|
| 149 |
+
if mode == "pn":
|
| 150 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_full_annotations.json")
|
| 151 |
+
elif mode == "p":
|
| 152 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_pres_annotations.json")
|
| 153 |
+
else:
|
| 154 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_abs_annotations.json")
|
| 155 |
+
coco = COCO(gt_path)
|
| 156 |
+
d3_res = coco.loadRes(pred_path)
|
| 157 |
+
cocoEval = COCOeval(coco, d3_res, "bbox")
|
| 158 |
+
cocoEval.evaluate()
|
| 159 |
+
cocoEval.accumulate()
|
| 160 |
+
cocoEval.summarize()
|
| 161 |
+
|
| 162 |
+
# comment the following if u only need intra/inter map for full/pres/abs
|
| 163 |
+
# ===================== uncomment this if u need detailed analysis =====================
|
| 164 |
+
# aps = cocoEval.eval["precision"][:, :, :, 0, -1]
|
| 165 |
+
# category_ids = coco.getCatIds()
|
| 166 |
+
# category_names = [cat["name"] for cat in coco.loadCats(category_ids)]
|
| 167 |
+
|
| 168 |
+
# aps_lens = defaultdict(list)
|
| 169 |
+
# counter_lens = defaultdict(int)
|
| 170 |
+
# for i in range(len(category_names)):
|
| 171 |
+
# ap = aps[:, :, i]
|
| 172 |
+
# ap_value = ap[ap > -1].mean()
|
| 173 |
+
# if not np.isnan(ap_value):
|
| 174 |
+
# len_ref = len(category_names[i].split(" "))
|
| 175 |
+
# aps_lens[len_ref].append(ap_value)
|
| 176 |
+
# counter_lens[len_ref] += 1
|
| 177 |
+
|
| 178 |
+
# ap_sum_short = sum([sum(aps_lens[i]) for i in range(0, 4)])
|
| 179 |
+
# ap_sum_mid = sum([sum(aps_lens[i]) for i in range(4, 7)])
|
| 180 |
+
# ap_sum_long = sum([sum(aps_lens[i]) for i in range(7, 10)])
|
| 181 |
+
# ap_sum_very_long = sum(
|
| 182 |
+
# [sum(aps_lens[i]) for i in range(10, max(counter_lens.keys()) + 1)]
|
| 183 |
+
# )
|
| 184 |
+
# c_sum_short = sum([counter_lens[i] for i in range(1, 4)])
|
| 185 |
+
# c_sum_mid = sum([counter_lens[i] for i in range(4, 7)])
|
| 186 |
+
# c_sum_long = sum([counter_lens[i] for i in range(7, 10)])
|
| 187 |
+
# c_sum_very_long = sum(
|
| 188 |
+
# [counter_lens[i] for i in range(10, max(counter_lens.keys()) + 1)]
|
| 189 |
+
# )
|
| 190 |
+
# map_short = ap_sum_short / c_sum_short
|
| 191 |
+
# map_mid = ap_sum_mid / c_sum_mid
|
| 192 |
+
# map_long = ap_sum_long / c_sum_long
|
| 193 |
+
# map_very_long = ap_sum_very_long / c_sum_very_long
|
| 194 |
+
# print(
|
| 195 |
+
# f"mAP over reference length: short - {map_short:.4f}, mid - {map_mid:.4f}, long - {map_long:.4f}, very long - {map_very_long:.4f}"
|
| 196 |
+
# )
|
| 197 |
+
# ===================== uncomment this if u need detailed analysis =====================
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def inference_on_d3(data_iter, model, args, box_threshold, text_threshold):
|
| 201 |
+
pred = []
|
| 202 |
+
for idx, (img_id, image_path) in enumerate(tqdm(data_iter)):
|
| 203 |
+
# load image
|
| 204 |
+
image_pil, image = load_image(image_path)
|
| 205 |
+
size = image_pil.size
|
| 206 |
+
W, H = size
|
| 207 |
+
|
| 208 |
+
group_ids = d3.get_group_ids(img_ids=[img_id])
|
| 209 |
+
sent_ids = d3.get_sent_ids(group_ids=group_ids)
|
| 210 |
+
sent_list = d3.load_sents(sent_ids=sent_ids)
|
| 211 |
+
text_list = [sent['raw_sent'] for sent in sent_list]
|
| 212 |
+
|
| 213 |
+
for sent_id, text_prompt in zip(sent_ids, text_list):
|
| 214 |
+
# run model
|
| 215 |
+
boxes_filt, pred_phrases, logit_list = get_grounding_output(
|
| 216 |
+
model, image, text_prompt, box_threshold, text_threshold, cpu_only=args.cpu_only, with_logits=False,
|
| 217 |
+
)
|
| 218 |
+
if args.vis:
|
| 219 |
+
pred_dict = {
|
| 220 |
+
"boxes": boxes_filt, # [x_center, y_center, w, h]
|
| 221 |
+
"size": [size[1], size[0]],
|
| 222 |
+
"labels": [f"{phrase}({str(logit)[:4]})" for phrase, logit in zip(pred_phrases, logit_list)],
|
| 223 |
+
}
|
| 224 |
+
image_with_box = plot_boxes_to_image(image_pil.copy(), pred_dict)[0]
|
| 225 |
+
image_with_box.save(os.path.join(output_dir, f"{img_id}_{text_prompt}.jpg"))
|
| 226 |
+
if not logit_list:
|
| 227 |
+
continue
|
| 228 |
+
if args.img_top1:
|
| 229 |
+
max_score_idx = logit_list.index(max(logit_list))
|
| 230 |
+
bboxes, phrases, logits = [boxes_filt[max_score_idx]], [pred_phrases[max_score_idx]], [logit_list[max_score_idx]]
|
| 231 |
+
else:
|
| 232 |
+
bboxes, phrases, logits = boxes_filt, pred_phrases, logit_list
|
| 233 |
+
for box, phrase, logit in zip(bboxes, phrases, logits):
|
| 234 |
+
if len(phrase) > args.overlap_percent * len(text_prompt) or phrase == text_prompt:
|
| 235 |
+
x1, y1, w, h = box.tolist()
|
| 236 |
+
x0, y0 = x1 - w / 2, y1 - h / 2
|
| 237 |
+
pred_item = {
|
| 238 |
+
"image_id": img_id,
|
| 239 |
+
"category_id": sent_id,
|
| 240 |
+
"bbox": [x0 * W, y0 * H, w * W, h * H],
|
| 241 |
+
"score": float(logit),
|
| 242 |
+
}
|
| 243 |
+
pred.append(pred_item)
|
| 244 |
+
|
| 245 |
+
return pred
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
if __name__ == "__main__":
|
| 249 |
+
IMG_ROOT = None # set here
|
| 250 |
+
JSON_ANNO_PATH = None # set here
|
| 251 |
+
PKL_ANNO_PATH = None # set here
|
| 252 |
+
assert IMG_ROOT is not None, "Please set IMG_ROOT in the script first"
|
| 253 |
+
assert JSON_ANNO_PATH is not None, "Please set JSON_ANNO_PATH in the script first"
|
| 254 |
+
assert PKL_ANNO_PATH is not None, "Please set PKL_ANNO_PATH in the script first"
|
| 255 |
+
|
| 256 |
+
d3 = D3(IMG_ROOT, PKL_ANNO_PATH)
|
| 257 |
+
|
| 258 |
+
parser = argparse.ArgumentParser("Grounding DINO evaluation on D-cube (https://arxiv.org/abs/2307.12813)", add_help=True)
|
| 259 |
+
parser.add_argument("--config_file", "-c", type=str, required=True, help="path to config file")
|
| 260 |
+
parser.add_argument(
|
| 261 |
+
"--checkpoint_path", "-p", type=str, required=True, help="path to checkpoint file"
|
| 262 |
+
)
|
| 263 |
+
# parser.add_argument("--image_path", "-i", type=str, required=True, help="path to image file")
|
| 264 |
+
# parser.add_argument("--text_prompt", "-t", type=str, required=True, help="text prompt")
|
| 265 |
+
parser.add_argument(
|
| 266 |
+
"--output_dir", "-o", type=str, default="outputs", required=True, help="output directory"
|
| 267 |
+
)
|
| 268 |
+
parser.add_argument("--vis", action="store_true", help="visualization on D3")
|
| 269 |
+
|
| 270 |
+
parser.add_argument("--box_threshold", type=float, default=0.3, help="box threshold")
|
| 271 |
+
parser.add_argument("--text_threshold", type=float, default=0.25, help="text threshold")
|
| 272 |
+
|
| 273 |
+
parser.add_argument("--cpu-only", action="store_true", help="running on cpu only!, default=False")
|
| 274 |
+
parser.add_argument("--img-top1", action="store_true", help="select only the box with top max score")
|
| 275 |
+
# parser.add_argument("--overlap-percent", type=float, default=1.0, help="overlapping percentage between input prompt and output label")
|
| 276 |
+
# this overlapping percentage denotes an additional post-processing technique we designed. if you turn this on, you may get higher performance by tuning this parameter.
|
| 277 |
+
args = parser.parse_args()
|
| 278 |
+
args.overlap_percent = 1 # by default, we do not use this technique.
|
| 279 |
+
print(args)
|
| 280 |
+
|
| 281 |
+
# cfg
|
| 282 |
+
config_file = args.config_file # change the path of the model config file
|
| 283 |
+
checkpoint_path = args.checkpoint_path # change the path of the model
|
| 284 |
+
# image_path = args.image_path
|
| 285 |
+
# text_prompt = args.text_prompt
|
| 286 |
+
output_dir = args.output_dir
|
| 287 |
+
box_threshold = args.box_threshold
|
| 288 |
+
text_threshold = args.text_threshold
|
| 289 |
+
|
| 290 |
+
# make dir
|
| 291 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 292 |
+
# load model
|
| 293 |
+
model = load_model(config_file, checkpoint_path, cpu_only=args.cpu_only)
|
| 294 |
+
|
| 295 |
+
data_iter = get_dataset_iter(d3)
|
| 296 |
+
|
| 297 |
+
pred = inference_on_d3(data_iter, model, args, box_threshold=box_threshold, text_threshold=text_threshold)
|
| 298 |
+
|
| 299 |
+
pred_path = os.path.join(output_dir, f"prediction.json")
|
| 300 |
+
with open(pred_path, "w") as f_:
|
| 301 |
+
json.dump(pred, f_)
|
| 302 |
+
eval_on_d3(pred_path, mode='pn')
|
| 303 |
+
eval_on_d3(pred_path, mode='p')
|
| 304 |
+
eval_on_d3(pred_path, mode='n')
|
approach/ovod/d-cube/eval_sota/owl_vit.py
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
from collections import defaultdict
|
| 4 |
+
|
| 5 |
+
from tqdm import tqdm
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import numpy as np
|
| 8 |
+
from pycocotools.coco import COCO
|
| 9 |
+
from pycocotools.cocoeval import COCOeval
|
| 10 |
+
import torch
|
| 11 |
+
from transformers import OwlViTProcessor, OwlViTForObjectDetection
|
| 12 |
+
|
| 13 |
+
from d_cube import D3
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def write_json(json_path, json_data):
|
| 17 |
+
with open(json_path, "w") as f_:
|
| 18 |
+
json.dump(json_data, f_)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def read_json(json_path):
|
| 22 |
+
with open(json_path, "r") as f_:
|
| 23 |
+
json_data = json.load(f_)
|
| 24 |
+
return json_data
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def load_image_general(image_path):
|
| 28 |
+
image_pil = Image.open(image_path)
|
| 29 |
+
return image_pil
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def get_prediction(model, image, captions, cpu_only=False):
|
| 33 |
+
for i in range(len(captions)):
|
| 34 |
+
captions[i] = captions[i].lower()
|
| 35 |
+
captions[i] = captions[i].strip()
|
| 36 |
+
if not captions[i].endswith("."):
|
| 37 |
+
captions[i] = captions[i] + "."
|
| 38 |
+
device = "cuda" if not cpu_only else "cpu"
|
| 39 |
+
model = model.to(device)
|
| 40 |
+
with torch.no_grad():
|
| 41 |
+
inputs = processor(text=[captions], images=image, return_tensors="pt").to(
|
| 42 |
+
device
|
| 43 |
+
)
|
| 44 |
+
outputs = model(**inputs)
|
| 45 |
+
target_size = torch.Tensor([image.size[::-1]]).to(device)
|
| 46 |
+
results = processor.post_process_object_detection(
|
| 47 |
+
outputs=outputs, target_sizes=target_size, threshold=0.1
|
| 48 |
+
# the post precessing threshold will affect the performance obviously
|
| 49 |
+
# you may tune it to get better performance, e.g., 0.05
|
| 50 |
+
)
|
| 51 |
+
boxes, scores, labels = (
|
| 52 |
+
results[0]["boxes"],
|
| 53 |
+
results[0]["scores"],
|
| 54 |
+
results[0]["labels"],
|
| 55 |
+
)
|
| 56 |
+
return boxes, scores, labels
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def get_dataset_iter(coco):
|
| 60 |
+
img_ids = coco.get_img_ids()
|
| 61 |
+
for img_id in img_ids:
|
| 62 |
+
img_info = coco.load_imgs(img_id)[0]
|
| 63 |
+
file_name = img_info["file_name"]
|
| 64 |
+
img_path = os.path.join(IMG_ROOT, file_name)
|
| 65 |
+
yield img_id, img_path
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def eval_on_d3(pred_path, mode="pn"):
|
| 69 |
+
assert mode in ("pn", "p", "n")
|
| 70 |
+
if mode == "pn":
|
| 71 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_full_annotations.json")
|
| 72 |
+
elif mode == "p":
|
| 73 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_pres_annotations.json")
|
| 74 |
+
else:
|
| 75 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_abs_annotations.json")
|
| 76 |
+
coco = COCO(gt_path)
|
| 77 |
+
d3_res = coco.loadRes(pred_path)
|
| 78 |
+
cocoEval = COCOeval(coco, d3_res, "bbox")
|
| 79 |
+
cocoEval.evaluate()
|
| 80 |
+
cocoEval.accumulate()
|
| 81 |
+
cocoEval.summarize()
|
| 82 |
+
|
| 83 |
+
# comment the following if u only need intra/inter map for full/pres/abs
|
| 84 |
+
# ===================== uncomment this if u need detailed analysis =====================
|
| 85 |
+
# aps = cocoEval.eval["precision"][:, :, :, 0, -1]
|
| 86 |
+
# category_ids = coco.getCatIds()
|
| 87 |
+
# category_names = [cat["name"] for cat in coco.loadCats(category_ids)]
|
| 88 |
+
|
| 89 |
+
# aps_lens = defaultdict(list)
|
| 90 |
+
# counter_lens = defaultdict(int)
|
| 91 |
+
# for i in range(len(category_names)):
|
| 92 |
+
# ap = aps[:, :, i]
|
| 93 |
+
# ap_value = ap[ap > -1].mean()
|
| 94 |
+
# if not np.isnan(ap_value):
|
| 95 |
+
# len_ref = len(category_names[i].split(" "))
|
| 96 |
+
# aps_lens[len_ref].append(ap_value)
|
| 97 |
+
# counter_lens[len_ref] += 1
|
| 98 |
+
|
| 99 |
+
# ap_sum_short = sum([sum(aps_lens[i]) for i in range(0, 4)])
|
| 100 |
+
# ap_sum_mid = sum([sum(aps_lens[i]) for i in range(4, 7)])
|
| 101 |
+
# ap_sum_long = sum([sum(aps_lens[i]) for i in range(7, 10)])
|
| 102 |
+
# ap_sum_very_long = sum(
|
| 103 |
+
# [sum(aps_lens[i]) for i in range(10, max(counter_lens.keys()) + 1)]
|
| 104 |
+
# )
|
| 105 |
+
# c_sum_short = sum([counter_lens[i] for i in range(1, 4)])
|
| 106 |
+
# c_sum_mid = sum([counter_lens[i] for i in range(4, 7)])
|
| 107 |
+
# c_sum_long = sum([counter_lens[i] for i in range(7, 10)])
|
| 108 |
+
# c_sum_very_long = sum(
|
| 109 |
+
# [counter_lens[i] for i in range(10, max(counter_lens.keys()) + 1)]
|
| 110 |
+
# )
|
| 111 |
+
# map_short = ap_sum_short / c_sum_short
|
| 112 |
+
# map_mid = ap_sum_mid / c_sum_mid
|
| 113 |
+
# map_long = ap_sum_long / c_sum_long
|
| 114 |
+
# map_very_long = ap_sum_very_long / c_sum_very_long
|
| 115 |
+
# print(
|
| 116 |
+
# f"mAP over reference length: short - {map_short:.4f}, mid - {map_mid:.4f}, long - {map_long:.4f}, very long - {map_very_long:.4f}"
|
| 117 |
+
# )
|
| 118 |
+
# ===================== uncomment this if u need detailed analysis =====================
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
def inference_on_d3(data_iter, model):
|
| 122 |
+
pred = []
|
| 123 |
+
error = []
|
| 124 |
+
for img_id, image_path in tqdm(data_iter):
|
| 125 |
+
image = load_image_general(image_path)
|
| 126 |
+
|
| 127 |
+
# ==================================== intra-group setting ====================================
|
| 128 |
+
# each image is evaluated with the categories in its group (usually 4)
|
| 129 |
+
group_ids = d3.get_group_ids(img_ids=[img_id])
|
| 130 |
+
sent_ids = d3.get_sent_ids(group_ids=group_ids)
|
| 131 |
+
# ==================================== intra-group setting ====================================
|
| 132 |
+
# ==================================== inter-group setting ====================================
|
| 133 |
+
# each image is evaluated with all categories in the dataset (422 for the first version of the dataset)
|
| 134 |
+
# sent_ids = d3.get_sent_ids()
|
| 135 |
+
# ==================================== inter-group setting ====================================
|
| 136 |
+
sent_list = d3.load_sents(sent_ids=sent_ids)
|
| 137 |
+
text_list = [sent["raw_sent"] for sent in sent_list]
|
| 138 |
+
|
| 139 |
+
try:
|
| 140 |
+
boxes, scores, labels = get_prediction(model, image, text_list, cpu_only=False)
|
| 141 |
+
for box, score, label in zip(boxes, scores, labels):
|
| 142 |
+
pred_item = {
|
| 143 |
+
"image_id": img_id,
|
| 144 |
+
"category_id": sent_ids[label],
|
| 145 |
+
"bbox": convert_to_xywh(box.tolist()), # use xywh
|
| 146 |
+
"score": float(score),
|
| 147 |
+
}
|
| 148 |
+
pred.append(pred_item) # the output to be saved to JSON.
|
| 149 |
+
except:
|
| 150 |
+
print("error!!!")
|
| 151 |
+
return pred, error
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def convert_to_xywh(bbox_xyxy):
|
| 155 |
+
"""
|
| 156 |
+
Convert top-left and bottom-right corner coordinates to [x, y, width, height] format.
|
| 157 |
+
"""
|
| 158 |
+
x1, y1, x2, y2 = bbox_xyxy
|
| 159 |
+
width = x2 - x1
|
| 160 |
+
height = y2 - y1
|
| 161 |
+
return [x1, y1, width, height]
|
| 162 |
+
|
| 163 |
+
|
| 164 |
+
if __name__ == "__main__":
|
| 165 |
+
IMG_ROOT = None # set here
|
| 166 |
+
JSON_ANNO_PATH = None # set here
|
| 167 |
+
PKL_ANNO_PATH = None # set here
|
| 168 |
+
assert IMG_ROOT is not None, "Please set IMG_ROOT in the script first"
|
| 169 |
+
assert JSON_ANNO_PATH is not None, "Please set JSON_ANNO_PATH in the script first"
|
| 170 |
+
assert PKL_ANNO_PATH is not None, "Please set PKL_ANNO_PATH in the script first"
|
| 171 |
+
|
| 172 |
+
d3 = D3(IMG_ROOT, PKL_ANNO_PATH)
|
| 173 |
+
|
| 174 |
+
output_dir = "ovd/owlvit/"
|
| 175 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 176 |
+
|
| 177 |
+
# model prediction
|
| 178 |
+
processor = OwlViTProcessor.from_pretrained("owl-vit")
|
| 179 |
+
model = OwlViTForObjectDetection.from_pretrained("owl-vit")
|
| 180 |
+
data_iter = get_dataset_iter(d3)
|
| 181 |
+
pred, error = inference_on_d3(data_iter, model)
|
| 182 |
+
|
| 183 |
+
pred_path = os.path.join(output_dir, f"prediction.json")
|
| 184 |
+
pred_path_error = os.path.join(output_dir, "error.json")
|
| 185 |
+
write_json(pred_path, pred)
|
| 186 |
+
write_json(pred_path_error, error)
|
| 187 |
+
# see https://github.com/shikras/d-cube/blob/main/doc.md#output-format for the output format
|
| 188 |
+
# the output format is identical to COCO.
|
| 189 |
+
|
| 190 |
+
eval_on_d3(pred_path, mode="pn") # the FULL setting
|
| 191 |
+
eval_on_d3(pred_path, mode="p") # the PRES setting
|
| 192 |
+
eval_on_d3(pred_path, mode="n") # the ABS setting
|
approach/ovod/d-cube/eval_sota/sphinx.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
__author__ = "Chi Xie and Jie Li"
|
| 3 |
+
__maintainer__ = "Chi Xie"
|
| 4 |
+
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
from collections import defaultdict
|
| 8 |
+
import re
|
| 9 |
+
|
| 10 |
+
from PIL import Image
|
| 11 |
+
from pycocotools.coco import COCO
|
| 12 |
+
from pycocotools.cocoeval import COCOeval
|
| 13 |
+
|
| 14 |
+
from d_cube import D3
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def write_json(json_path, json_data):
|
| 18 |
+
with open(json_path, "w") as f_:
|
| 19 |
+
json.dump(json_data, f_)
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
def read_json(json_path):
|
| 23 |
+
with open(json_path, "r") as f_:
|
| 24 |
+
json_data = json.load(f_)
|
| 25 |
+
return json_data
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
def load_image_general(image_path):
|
| 29 |
+
image_pil = Image.open(image_path)
|
| 30 |
+
return image_pil
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def extract_boxes(input_string):
|
| 34 |
+
# if input_string.startswith("None"):
|
| 35 |
+
# return []
|
| 36 |
+
# Define the pattern using regular expression
|
| 37 |
+
pattern = r'\[([\d.,; ]+)\]'
|
| 38 |
+
|
| 39 |
+
# Search for the pattern in the input string
|
| 40 |
+
match = re.search(pattern, input_string)
|
| 41 |
+
|
| 42 |
+
# If a match is found, extract and return the boxes as a list
|
| 43 |
+
if match:
|
| 44 |
+
boxes_str = match.group(1)
|
| 45 |
+
boxes_list = [list(map(float, box.split(','))) for box in boxes_str.split(';')]
|
| 46 |
+
return boxes_list
|
| 47 |
+
else:
|
| 48 |
+
return []
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def get_prediction(mllm_res, image, captions, cpu_only=False):
|
| 52 |
+
boxes, scores, labels = [], [], []
|
| 53 |
+
width, height = image.size
|
| 54 |
+
for idx, res_item in enumerate(mllm_res):
|
| 55 |
+
boxes_list = extract_boxes(res_item["answer"])
|
| 56 |
+
for bbox in boxes_list:
|
| 57 |
+
bbox_rescaled = get_true_bbox(image.size, bbox)
|
| 58 |
+
boxes.append(bbox_rescaled)
|
| 59 |
+
scores.append(1.0)
|
| 60 |
+
labels.append(idx)
|
| 61 |
+
return boxes, scores, labels
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def get_dataset_iter(coco):
|
| 65 |
+
img_ids = coco.get_img_ids()
|
| 66 |
+
for img_id in img_ids:
|
| 67 |
+
img_info = coco.load_imgs(img_id)[0]
|
| 68 |
+
file_name = img_info["file_name"]
|
| 69 |
+
img_path = os.path.join(IMG_ROOT, file_name)
|
| 70 |
+
yield img_id, file_name, img_path
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def eval_on_d3(pred_path, mode="pn"):
|
| 74 |
+
assert mode in ("pn", "p", "n")
|
| 75 |
+
if mode == "pn":
|
| 76 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_full_annotations.json")
|
| 77 |
+
elif mode == "p":
|
| 78 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_pres_annotations.json")
|
| 79 |
+
else:
|
| 80 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_abs_annotations.json")
|
| 81 |
+
coco = COCO(gt_path)
|
| 82 |
+
d3_res = coco.loadRes(pred_path)
|
| 83 |
+
cocoEval = COCOeval(coco, d3_res, "bbox")
|
| 84 |
+
cocoEval.evaluate()
|
| 85 |
+
cocoEval.accumulate()
|
| 86 |
+
cocoEval.summarize()
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def group_sphinx_res_by_img(inference_res):
|
| 90 |
+
inference_res_by_img = defaultdict(list)
|
| 91 |
+
for res_item in inference_res:
|
| 92 |
+
img_path = "/".join(res_item["image_path"].split("/")[-2:])
|
| 93 |
+
inference_res_by_img[img_path].append(res_item)
|
| 94 |
+
inference_res_by_img = dict(inference_res_by_img)
|
| 95 |
+
return inference_res_by_img
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def get_true_bbox(img_size, bbox):
|
| 99 |
+
width, height = img_size
|
| 100 |
+
max_edge = max(height, width)
|
| 101 |
+
bbox = [v * max_edge for v in bbox]
|
| 102 |
+
diff = abs(width - height) // 2
|
| 103 |
+
if height < width:
|
| 104 |
+
bbox[1] -= diff
|
| 105 |
+
bbox[3] -= diff
|
| 106 |
+
else:
|
| 107 |
+
bbox[0] -= diff
|
| 108 |
+
bbox[2] -= diff
|
| 109 |
+
return bbox
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
def inference_on_d3(data_iter, inference_res):
|
| 113 |
+
pred = []
|
| 114 |
+
inf_res_by_img = group_sphinx_res_by_img(inference_res)
|
| 115 |
+
for idx, (img_id, img_name, img_path) in enumerate(data_iter):
|
| 116 |
+
image = load_image_general(img_path)
|
| 117 |
+
|
| 118 |
+
# ==================================== intra-group setting ====================================
|
| 119 |
+
# each image is evaluated with the categories in its group (usually 4)
|
| 120 |
+
group_ids = d3.get_group_ids(img_ids=[img_id])
|
| 121 |
+
sent_ids = d3.get_sent_ids(group_ids=group_ids)
|
| 122 |
+
# ==================================== intra-group setting ====================================
|
| 123 |
+
# ==================================== inter-group setting ====================================
|
| 124 |
+
# each image is evaluated with all categories in the dataset (422 for the first version of the dataset)
|
| 125 |
+
# sent_ids = d3.get_sent_ids()
|
| 126 |
+
# ==================================== inter-group setting ====================================
|
| 127 |
+
sent_list = d3.load_sents(sent_ids=sent_ids)
|
| 128 |
+
text_list = [sent["raw_sent"] for sent in sent_list]
|
| 129 |
+
|
| 130 |
+
boxes, scores, labels = get_prediction(inf_res_by_img[img_name], image, text_list, cpu_only=False)
|
| 131 |
+
for box, score, label in zip(boxes, scores, labels):
|
| 132 |
+
pred_item = {
|
| 133 |
+
"image_id": img_id,
|
| 134 |
+
"category_id": sent_ids[label],
|
| 135 |
+
"bbox": convert_to_xywh(box), # use xywh
|
| 136 |
+
"score": float(score),
|
| 137 |
+
}
|
| 138 |
+
pred.append(pred_item) # the output to be saved to JSON.
|
| 139 |
+
return pred
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def convert_to_xywh(bbox_xyxy):
|
| 143 |
+
"""
|
| 144 |
+
Convert top-left and bottom-right corner coordinates to [x, y, width, height] format.
|
| 145 |
+
"""
|
| 146 |
+
x1, y1, x2, y2 = bbox_xyxy
|
| 147 |
+
width = x2 - x1
|
| 148 |
+
height = y2 - y1
|
| 149 |
+
return [x1, y1, width, height]
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
if __name__ == "__main__":
|
| 153 |
+
IMG_ROOT = None # set here
|
| 154 |
+
JSON_ANNO_PATH = None # set here
|
| 155 |
+
PKL_ANNO_PATH = None # set here
|
| 156 |
+
# ============================== SPHINX inference result file ===============
|
| 157 |
+
SPHINX_INFERENCE_RES_PATH = None
|
| 158 |
+
# You can download the SPHINX d3 inference result example from:
|
| 159 |
+
# https://github.com/shikras/d-cube/files/14276682/sphinx_d3_result.json
|
| 160 |
+
# For the inference process, please refer to SPHINX official repo (https://github.com/Alpha-VLLM/LLaMA2-Accessory)
|
| 161 |
+
# the prompts we used are available in this JSON file
|
| 162 |
+
# Thanks for the contribution from Jie Li (https://github.com/theFool32)
|
| 163 |
+
# ============================== SPHINX inference result file ===============
|
| 164 |
+
assert IMG_ROOT is not None, "Please set IMG_ROOT in the script first"
|
| 165 |
+
assert JSON_ANNO_PATH is not None, "Please set JSON_ANNO_PATH in the script first"
|
| 166 |
+
assert PKL_ANNO_PATH is not None, "Please set PKL_ANNO_PATH in the script first"
|
| 167 |
+
|
| 168 |
+
d3 = D3(IMG_ROOT, PKL_ANNO_PATH)
|
| 169 |
+
|
| 170 |
+
output_dir = "mllm/sphinx/" # or whatever you prefer
|
| 171 |
+
inference_res = read_json(SPHINX_INFERENCE_RES_PATH)
|
| 172 |
+
|
| 173 |
+
# model prediction
|
| 174 |
+
data_iter = get_dataset_iter(d3)
|
| 175 |
+
pred = inference_on_d3(data_iter, inference_res)
|
| 176 |
+
|
| 177 |
+
pred_path = os.path.join(output_dir, f"prediction.json")
|
| 178 |
+
write_json(pred_path, pred)
|
| 179 |
+
# see https://github.com/shikras/d-cube/blob/main/doc.md#output-format for the output format
|
| 180 |
+
# the output format is identical to COCO.
|
| 181 |
+
|
| 182 |
+
eval_on_d3(pred_path, mode="pn") # the FULL setting
|
| 183 |
+
eval_on_d3(pred_path, mode="p") # the PRES setting
|
| 184 |
+
eval_on_d3(pred_path, mode="n") # the ABS setting
|
approach/ovod/d-cube/scripts/eval_and_analysis_json.py
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
__author__ = "Chi Xie and Zhao Zhang"
|
| 3 |
+
__maintainer__ = "Chi Xie"
|
| 4 |
+
# this script takes the result json in, and print evaluation and analysis result on D-cube (FULL/PRES/ABS, etc.)
|
| 5 |
+
import os
|
| 6 |
+
import json
|
| 7 |
+
import argparse
|
| 8 |
+
from collections import defaultdict
|
| 9 |
+
|
| 10 |
+
import numpy as np
|
| 11 |
+
from pycocotools.coco import COCO
|
| 12 |
+
from pycocotools.cocoeval import COCOeval
|
| 13 |
+
|
| 14 |
+
from d_cube import D3
|
| 15 |
+
|
| 16 |
+
def eval_on_d3(pred_path, mode="pn", nbox_partition=None, lref_partition=False):
|
| 17 |
+
assert mode in ("pn", "p", "n")
|
| 18 |
+
if mode == "pn":
|
| 19 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_full_annotations.json")
|
| 20 |
+
elif mode == "p":
|
| 21 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_pres_annotations.json")
|
| 22 |
+
else:
|
| 23 |
+
gt_path = os.path.join(JSON_ANNO_PATH, "d3_abs_annotations.json")
|
| 24 |
+
|
| 25 |
+
if nbox_partition:
|
| 26 |
+
gt_path, pred_path = nbox_partition_json(gt_path, pred_path, nbox_partition)
|
| 27 |
+
|
| 28 |
+
# Eval results
|
| 29 |
+
coco = COCO(gt_path)
|
| 30 |
+
d3_res = coco.loadRes(pred_path)
|
| 31 |
+
cocoEval = COCOeval(coco, d3_res, "bbox")
|
| 32 |
+
cocoEval.evaluate()
|
| 33 |
+
cocoEval.accumulate()
|
| 34 |
+
cocoEval.summarize()
|
| 35 |
+
|
| 36 |
+
aps = cocoEval.eval["precision"][:, :, :, 0, -1]
|
| 37 |
+
category_ids = coco.getCatIds()
|
| 38 |
+
category_names = [cat["name"] for cat in coco.loadCats(category_ids)]
|
| 39 |
+
|
| 40 |
+
if lref_partition:
|
| 41 |
+
aps_lens = defaultdict(list)
|
| 42 |
+
counter_lens = defaultdict(int)
|
| 43 |
+
for i in range(len(category_names)):
|
| 44 |
+
ap = aps[:, :, i]
|
| 45 |
+
ap_value = ap[ap > -1].mean()
|
| 46 |
+
if not np.isnan(ap_value):
|
| 47 |
+
len_ref = len(category_names[i].split(" "))
|
| 48 |
+
aps_lens[len_ref].append(ap_value)
|
| 49 |
+
counter_lens[len_ref] += 1
|
| 50 |
+
|
| 51 |
+
ap_sum_short = sum([sum(aps_lens[i]) for i in range(0, 4)])
|
| 52 |
+
ap_sum_mid = sum([sum(aps_lens[i]) for i in range(4, 7)])
|
| 53 |
+
ap_sum_long = sum([sum(aps_lens[i]) for i in range(7, 10)])
|
| 54 |
+
ap_sum_very_long = sum(
|
| 55 |
+
[sum(aps_lens[i]) for i in range(10, max(counter_lens.keys()) + 1)]
|
| 56 |
+
)
|
| 57 |
+
c_sum_short = sum([counter_lens[i] for i in range(1, 4)])
|
| 58 |
+
c_sum_mid = sum([counter_lens[i] for i in range(4, 7)])
|
| 59 |
+
c_sum_long = sum([counter_lens[i] for i in range(7, 10)])
|
| 60 |
+
c_sum_very_long = sum(
|
| 61 |
+
[counter_lens[i] for i in range(10, max(counter_lens.keys()) + 1)]
|
| 62 |
+
)
|
| 63 |
+
map_short = ap_sum_short / c_sum_short
|
| 64 |
+
map_mid = ap_sum_mid / c_sum_mid
|
| 65 |
+
map_long = ap_sum_long / c_sum_long
|
| 66 |
+
map_very_long = ap_sum_very_long / c_sum_very_long
|
| 67 |
+
print(
|
| 68 |
+
f"mAP over reference length: short - {map_short:.4f}, mid - {map_mid:.4f}, long - {map_long:.4f}, very long - {map_very_long:.4f}"
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def nbox_partition_json(gt_path, pred_path, nbox_partition):
|
| 73 |
+
with open(gt_path, "r") as f_gt:
|
| 74 |
+
gts = json.load(f_gt)
|
| 75 |
+
with open(pred_path, "r") as f_pred:
|
| 76 |
+
preds = json.load(f_pred)
|
| 77 |
+
|
| 78 |
+
cat_obj_count = d3.bbox_num_analyze()
|
| 79 |
+
annos = gts["annotations"]
|
| 80 |
+
new_annos = []
|
| 81 |
+
for ann in annos:
|
| 82 |
+
img_id = ann["image_id"]
|
| 83 |
+
category_id = ann["category_id"]
|
| 84 |
+
if nbox_partition == "one" and cat_obj_count[category_id - 1, img_id] == 1:
|
| 85 |
+
new_annos.append(ann)
|
| 86 |
+
if nbox_partition == "multi" and cat_obj_count[category_id - 1, img_id] > 1:
|
| 87 |
+
new_annos.append(ann)
|
| 88 |
+
if nbox_partition == "two" and cat_obj_count[category_id - 1, img_id] == 2:
|
| 89 |
+
new_annos.append(ann)
|
| 90 |
+
if nbox_partition == "three" and cat_obj_count[category_id - 1, img_id] == 3:
|
| 91 |
+
new_annos.append(ann)
|
| 92 |
+
if nbox_partition == "four" and cat_obj_count[category_id - 1, img_id] == 4:
|
| 93 |
+
new_annos.append(ann)
|
| 94 |
+
if nbox_partition == "four_more" and cat_obj_count[category_id - 1, img_id] > 4:
|
| 95 |
+
new_annos.append(ann)
|
| 96 |
+
gts["annotations"] = new_annos
|
| 97 |
+
new_gts = gts
|
| 98 |
+
new_preds = []
|
| 99 |
+
for prd in preds:
|
| 100 |
+
img_id = prd["image_id"]
|
| 101 |
+
category_id = prd["category_id"]
|
| 102 |
+
if nbox_partition == "no" and cat_obj_count[category_id - 1, img_id] == 0:
|
| 103 |
+
new_preds.append(prd)
|
| 104 |
+
if nbox_partition == "one" and cat_obj_count[category_id - 1, img_id] == 1:
|
| 105 |
+
new_preds.append(prd)
|
| 106 |
+
if nbox_partition == "multi" and cat_obj_count[category_id - 1, img_id] > 1:
|
| 107 |
+
new_preds.append(prd)
|
| 108 |
+
if nbox_partition == "two" and cat_obj_count[category_id - 1, img_id] == 2:
|
| 109 |
+
new_preds.append(prd)
|
| 110 |
+
if nbox_partition == "three" and cat_obj_count[category_id - 1, img_id] == 3:
|
| 111 |
+
new_preds.append(prd)
|
| 112 |
+
if nbox_partition == "four" and cat_obj_count[category_id - 1, img_id] == 4:
|
| 113 |
+
new_preds.append(prd)
|
| 114 |
+
if nbox_partition == "four_more" and cat_obj_count[category_id - 1, img_id] > 4:
|
| 115 |
+
new_preds.append(prd)
|
| 116 |
+
|
| 117 |
+
new_gt_path = gt_path.replace(".json", f".{nbox_partition}-instance.json")
|
| 118 |
+
new_pred_path = pred_path.replace(".json", f".{nbox_partition}-instance.json")
|
| 119 |
+
with open(new_gt_path, "w") as fo_gt:
|
| 120 |
+
json.dump(new_gts, fo_gt)
|
| 121 |
+
with open(new_pred_path, "w") as fo_pred:
|
| 122 |
+
json.dump(new_preds, fo_pred)
|
| 123 |
+
return new_gt_path, new_pred_path
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def convert_to_xywh(x1, y1, x2, y2):
|
| 127 |
+
"""
|
| 128 |
+
Convert top-left and bottom-right corner coordinates to [x,y,width,height] format.
|
| 129 |
+
"""
|
| 130 |
+
width = x2 - x1
|
| 131 |
+
height = y2 - y1
|
| 132 |
+
return x1, y1, width, height
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def transform_json_boxes(pred_path):
|
| 136 |
+
with open(pred_path, "r") as f_:
|
| 137 |
+
res = json.load(f_)
|
| 138 |
+
for item in res:
|
| 139 |
+
item["bbox"] = convert_to_xywh(*item["bbox"])
|
| 140 |
+
res_path = pred_path.replace(".json", ".xywh.json")
|
| 141 |
+
with open(res_path, "w") as f_w:
|
| 142 |
+
json.dump(res, f_w)
|
| 143 |
+
return res_path
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
if __name__ == "__main__":
|
| 147 |
+
D3_DATASET_ROOT = os.environ.get("D3_DATASET_ROOT")
|
| 148 |
+
if not D3_DATASET_ROOT:
|
| 149 |
+
raise RuntimeError("Set D3_DATASET_ROOT to the extracted D3 dataset directory.")
|
| 150 |
+
IMG_ROOT = D3_DATASET_ROOT
|
| 151 |
+
JSON_ANNO_PATH = os.path.join(D3_DATASET_ROOT, "d3_json")
|
| 152 |
+
PKL_ANNO_PATH = os.path.join(D3_DATASET_ROOT, "d3_pkl")
|
| 153 |
+
d3 = D3(IMG_ROOT, PKL_ANNO_PATH)
|
| 154 |
+
|
| 155 |
+
parser = argparse.ArgumentParser(
|
| 156 |
+
"An example script for D-cube evaluation with prediction file (JSON)",
|
| 157 |
+
add_help=True,
|
| 158 |
+
)
|
| 159 |
+
parser.add_argument("pred_path", type=str, help="path to the prediction JSON file")
|
| 160 |
+
parser.add_argument(
|
| 161 |
+
"--partition-by-nbox",
|
| 162 |
+
action="store_true",
|
| 163 |
+
help="divide the images by num of boxes for each ref",
|
| 164 |
+
)
|
| 165 |
+
parser.add_argument(
|
| 166 |
+
"--partition-by-lens",
|
| 167 |
+
action="store_true",
|
| 168 |
+
help="divide the references by their lengths",
|
| 169 |
+
)
|
| 170 |
+
parser.add_argument(
|
| 171 |
+
"--xyxy2xywh",
|
| 172 |
+
action="store_true",
|
| 173 |
+
help="transform box coords from xyxy to xywh",
|
| 174 |
+
)
|
| 175 |
+
args = parser.parse_args()
|
| 176 |
+
if args.xyxy2xywh:
|
| 177 |
+
pred_path = transform_json_boxes(args.pred_path)
|
| 178 |
+
else:
|
| 179 |
+
pred_path = args.pred_path
|
| 180 |
+
pred_path = args.pred_path
|
| 181 |
+
if args.partition_by_nbox:
|
| 182 |
+
# partiton: no-instance, one-instance, multi-instance
|
| 183 |
+
for mode in ("pn", "p", "n"):
|
| 184 |
+
# for ptt in ('no', 'one', 'multi'):
|
| 185 |
+
for ptt in ("no", "one", "two", "three", "four", "four_more"):
|
| 186 |
+
eval_on_d3(pred_path, mode=mode, nbox_partition=ptt)
|
| 187 |
+
else:
|
| 188 |
+
eval_on_d3(pred_path, mode="pn", lref_partition=args.partition_by_lens)
|
| 189 |
+
eval_on_d3(pred_path, mode="p", lref_partition=args.partition_by_lens)
|
| 190 |
+
eval_on_d3(pred_path, mode="n", lref_partition=args.partition_by_lens)
|
approach/ovod/d-cube/scripts/eval_json_example.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
__author__ = "Chi Xie and Zhao Zhang"
|
| 3 |
+
__maintainer__ = "Chi Xie"
|
| 4 |
+
# this script takes the result json in, and print evaluation and analysis result on D-cube (FULL/PRES/ABS, etc.)
|
| 5 |
+
from pycocotools.coco import COCO
|
| 6 |
+
from pycocotools.cocoeval import COCOeval
|
| 7 |
+
|
| 8 |
+
# Eval results with COCOAPI
|
| 9 |
+
gt_path = "./d3_full_annotations.json" # FULL, PRES or ABS
|
| 10 |
+
pred_path = None # set your prediction JSON path
|
| 11 |
+
coco = COCO(gt_path)
|
| 12 |
+
d3_res = coco.loadRes(pred_path)
|
| 13 |
+
cocoEval = COCOeval(coco, d3_res, "bbox")
|
| 14 |
+
cocoEval.evaluate()
|
| 15 |
+
cocoEval.accumulate()
|
| 16 |
+
cocoEval.summarize()
|
approach/ovod/d-cube/scripts/get_d3_stat.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
|
| 3 |
+
from d_cube.vis_util import plot_hist
|
| 4 |
+
from d_cube import D3
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
def vis_num_instance(cat_obj_count):
|
| 8 |
+
# Assuming `cat_obj_count` is your numpy array of shape [n_cat, n_img]
|
| 9 |
+
|
| 10 |
+
# Calculate the total number of instances in each image
|
| 11 |
+
total_instances_per_image = np.sum(cat_obj_count, axis=0)
|
| 12 |
+
|
| 13 |
+
# # Plot the histogram
|
| 14 |
+
# plt.hist(total_instances_per_image, bins=20)
|
| 15 |
+
# plt.xlabel('Number of Instances')
|
| 16 |
+
# plt.ylabel('Frequency')
|
| 17 |
+
# plt.title('Distribution of Number of Instances on a Image')
|
| 18 |
+
|
| 19 |
+
# # Save the figure
|
| 20 |
+
# plt.savefig('vis_fig/instance_distribution.png', bbox_inches='tight')
|
| 21 |
+
# plt.close()
|
| 22 |
+
plot_hist(
|
| 23 |
+
total_instances_per_image,
|
| 24 |
+
bins=max(total_instances_per_image) - min(total_instances_per_image) + 1,
|
| 25 |
+
save_path="vis_fig/instance_dist_hist.pdf",
|
| 26 |
+
)
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def vis_num_category(cat_obj_count):
|
| 30 |
+
# Assuming `cat_obj_count` is your numpy array of shape [n_cat, n_img]
|
| 31 |
+
|
| 32 |
+
# Calculate the number of categories in each image
|
| 33 |
+
num_categories_per_image = np.sum(cat_obj_count > 0, axis=0)
|
| 34 |
+
|
| 35 |
+
# # Plot the histogram
|
| 36 |
+
# plt.hist(num_categories_per_image, bins=20)
|
| 37 |
+
# plt.xlabel('Number of Categories')
|
| 38 |
+
# plt.ylabel('Frequency')
|
| 39 |
+
# plt.title('Distribution of Number of Categories on a Image')
|
| 40 |
+
|
| 41 |
+
# # Save the figure
|
| 42 |
+
# plt.savefig('vis_fig/category_distribution.png', bbox_inches='tight')
|
| 43 |
+
# plt.close()
|
| 44 |
+
plot_hist(
|
| 45 |
+
num_categories_per_image,
|
| 46 |
+
bins=max(num_categories_per_image) - min(num_categories_per_image) + 1,
|
| 47 |
+
save_path="vis_fig/category_dist_hist.pdf",
|
| 48 |
+
)
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def vis_num_img_per_cat(cat_obj_count):
|
| 52 |
+
num_img_per_cat = np.sum(cat_obj_count > 0, axis=1)
|
| 53 |
+
plot_hist(
|
| 54 |
+
num_img_per_cat,
|
| 55 |
+
bins=20,
|
| 56 |
+
save_path="vis_fig/nimg_pcat_hist.pdf",
|
| 57 |
+
x="Num. of images",
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
def vis_num_box_per_cat(cat_obj_count):
|
| 62 |
+
num_box_per_cat = np.sum(cat_obj_count, axis=1)
|
| 63 |
+
plot_hist(
|
| 64 |
+
num_box_per_cat,
|
| 65 |
+
bins=20,
|
| 66 |
+
save_path="vis_fig/nbox_pcat_hist.pdf",
|
| 67 |
+
x="Num. of instances",
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def vis_num_box_per_cat_per_img(cat_obj_count):
|
| 72 |
+
img_obj_count = cat_obj_count.reshape(-1)
|
| 73 |
+
plot_hist(
|
| 74 |
+
img_obj_count[img_obj_count > 0],
|
| 75 |
+
bins=max(img_obj_count) - min(img_obj_count) + 1,
|
| 76 |
+
save_path="vis_fig/nbox_pcat_pimg_hist.pdf",
|
| 77 |
+
x="Num. of instances on a image",
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
if __name__ == "__main__":
|
| 82 |
+
IMG_ROOT = None # set here
|
| 83 |
+
PKL_ANNO_PATH = None # set here
|
| 84 |
+
assert IMG_ROOT is not None, "Please set IMG_ROOT in the script first"
|
| 85 |
+
assert PKL_ANNO_PATH is not None, "Please set PKL_ANNO_PATH in the script first"
|
| 86 |
+
d3 = D3(IMG_ROOT, PKL_ANNO_PATH)
|
| 87 |
+
|
| 88 |
+
cat_obj_count = d3.bbox_num_analyze()
|
| 89 |
+
vis_num_instance(cat_obj_count)
|
| 90 |
+
vis_num_category(cat_obj_count)
|
| 91 |
+
vis_num_img_per_cat(cat_obj_count)
|
| 92 |
+
vis_num_box_per_cat(cat_obj_count)
|
| 93 |
+
vis_num_box_per_cat_per_img(cat_obj_count)
|
| 94 |
+
|
| 95 |
+
d3.stat_description(with_rev=False)
|
| 96 |
+
d3.stat_description(with_rev=True)
|
| 97 |
+
d3.stat_description(with_rev=False, inter_group=True)
|
| 98 |
+
d3.stat_description(with_rev=True, inter_group=True)
|
approach/ovod/detectron2/.circleci/config.yml
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version: 2.1
|
| 2 |
+
|
| 3 |
+
# -------------------------------------------------------------------------------------
|
| 4 |
+
# Environments to run the jobs in
|
| 5 |
+
# -------------------------------------------------------------------------------------
|
| 6 |
+
cpu: &cpu
|
| 7 |
+
machine:
|
| 8 |
+
image: ubuntu-2004:202107-02
|
| 9 |
+
resource_class: medium
|
| 10 |
+
|
| 11 |
+
gpu: &gpu
|
| 12 |
+
machine:
|
| 13 |
+
# NOTE: use a cuda version that's supported by all our pytorch versions
|
| 14 |
+
image: ubuntu-1604-cuda-11.1:202012-01
|
| 15 |
+
resource_class: gpu.nvidia.small
|
| 16 |
+
|
| 17 |
+
windows-cpu: &windows_cpu
|
| 18 |
+
machine:
|
| 19 |
+
resource_class: windows.medium
|
| 20 |
+
image: windows-server-2019-vs2019:stable
|
| 21 |
+
shell: powershell.exe
|
| 22 |
+
|
| 23 |
+
# windows-gpu: &windows_gpu
|
| 24 |
+
# machine:
|
| 25 |
+
# resource_class: windows.gpu.nvidia.medium
|
| 26 |
+
# image: windows-server-2019-nvidia:stable
|
| 27 |
+
|
| 28 |
+
version_parameters: &version_parameters
|
| 29 |
+
parameters:
|
| 30 |
+
pytorch_version:
|
| 31 |
+
type: string
|
| 32 |
+
torchvision_version:
|
| 33 |
+
type: string
|
| 34 |
+
pytorch_index:
|
| 35 |
+
type: string
|
| 36 |
+
# use test wheels index to have access to RC wheels
|
| 37 |
+
# https://download.pytorch.org/whl/test/torch_test.html
|
| 38 |
+
default: "https://download.pytorch.org/whl/torch_stable.html"
|
| 39 |
+
python_version: # NOTE: only affect linux
|
| 40 |
+
type: string
|
| 41 |
+
default: '3.7.9'
|
| 42 |
+
|
| 43 |
+
environment:
|
| 44 |
+
PYTORCH_VERSION: << parameters.pytorch_version >>
|
| 45 |
+
TORCHVISION_VERSION: << parameters.torchvision_version >>
|
| 46 |
+
PYTORCH_INDEX: << parameters.pytorch_index >>
|
| 47 |
+
PYTHON_VERSION: << parameters.python_version>>
|
| 48 |
+
# point datasets to ~/.torch so it's cached in CI
|
| 49 |
+
DETECTRON2_DATASETS: ~/.torch/datasets
|
| 50 |
+
|
| 51 |
+
# -------------------------------------------------------------------------------------
|
| 52 |
+
# Re-usable commands
|
| 53 |
+
# -------------------------------------------------------------------------------------
|
| 54 |
+
# install_nvidia_driver: &install_nvidia_driver
|
| 55 |
+
# - run:
|
| 56 |
+
# name: Install nvidia driver
|
| 57 |
+
# working_directory: ~/
|
| 58 |
+
# command: |
|
| 59 |
+
# wget -q 'https://s3.amazonaws.com/ossci-linux/nvidia_driver/NVIDIA-Linux-x86_64-430.40.run'
|
| 60 |
+
# sudo /bin/bash ./NVIDIA-Linux-x86_64-430.40.run -s --no-drm
|
| 61 |
+
# nvidia-smi
|
| 62 |
+
|
| 63 |
+
add_ssh_keys: &add_ssh_keys
|
| 64 |
+
# https://circleci.com/docs/2.0/add-ssh-key/
|
| 65 |
+
- add_ssh_keys:
|
| 66 |
+
fingerprints:
|
| 67 |
+
- "e4:13:f2:22:d4:49:e8:e4:57:5a:ac:20:2f:3f:1f:ca"
|
| 68 |
+
|
| 69 |
+
install_python: &install_python
|
| 70 |
+
- run:
|
| 71 |
+
name: Install Python
|
| 72 |
+
working_directory: ~/
|
| 73 |
+
command: |
|
| 74 |
+
# upgrade pyenv
|
| 75 |
+
cd /opt/circleci/.pyenv/plugins/python-build/../.. && git pull && cd -
|
| 76 |
+
pyenv install -s $PYTHON_VERSION
|
| 77 |
+
pyenv global $PYTHON_VERSION
|
| 78 |
+
python --version
|
| 79 |
+
which python
|
| 80 |
+
pip install --upgrade pip
|
| 81 |
+
|
| 82 |
+
setup_venv: &setup_venv
|
| 83 |
+
- run:
|
| 84 |
+
name: Setup Virtual Env
|
| 85 |
+
working_directory: ~/
|
| 86 |
+
command: |
|
| 87 |
+
python -m venv ~/venv
|
| 88 |
+
echo ". ~/venv/bin/activate" >> $BASH_ENV
|
| 89 |
+
. ~/venv/bin/activate
|
| 90 |
+
python --version
|
| 91 |
+
which python
|
| 92 |
+
which pip
|
| 93 |
+
pip install --upgrade pip
|
| 94 |
+
|
| 95 |
+
setup_venv_win: &setup_venv_win
|
| 96 |
+
- run:
|
| 97 |
+
name: Setup Virtual Env for Windows
|
| 98 |
+
command: |
|
| 99 |
+
pip install virtualenv
|
| 100 |
+
python -m virtualenv env
|
| 101 |
+
.\env\Scripts\activate
|
| 102 |
+
python --version
|
| 103 |
+
which python
|
| 104 |
+
which pip
|
| 105 |
+
|
| 106 |
+
install_linux_dep: &install_linux_dep
|
| 107 |
+
- run:
|
| 108 |
+
name: Install Dependencies
|
| 109 |
+
command: |
|
| 110 |
+
# disable crash coredump, so unittests fail fast
|
| 111 |
+
sudo systemctl stop apport.service
|
| 112 |
+
# install from github to get latest; install iopath first since fvcore depends on it
|
| 113 |
+
pip install --progress-bar off -U 'git+https://github.com/facebookresearch/iopath'
|
| 114 |
+
pip install --progress-bar off -U 'git+https://github.com/facebookresearch/fvcore'
|
| 115 |
+
# Don't use pytest-xdist: cuda tests are unstable under multi-process workers.
|
| 116 |
+
pip install --progress-bar off ninja opencv-python-headless pytest tensorboard pycocotools onnx
|
| 117 |
+
pip install --progress-bar off torch==$PYTORCH_VERSION -f $PYTORCH_INDEX
|
| 118 |
+
if [[ "$TORCHVISION_VERSION" == "master" ]]; then
|
| 119 |
+
pip install git+https://github.com/pytorch/vision.git
|
| 120 |
+
else
|
| 121 |
+
pip install --progress-bar off torchvision==$TORCHVISION_VERSION -f $PYTORCH_INDEX
|
| 122 |
+
fi
|
| 123 |
+
|
| 124 |
+
python -c 'import torch; print("CUDA:", torch.cuda.is_available())'
|
| 125 |
+
gcc --version
|
| 126 |
+
|
| 127 |
+
install_detectron2: &install_detectron2
|
| 128 |
+
- run:
|
| 129 |
+
name: Install Detectron2
|
| 130 |
+
command: |
|
| 131 |
+
# Remove first, in case it's in the CI cache
|
| 132 |
+
pip uninstall -y detectron2
|
| 133 |
+
|
| 134 |
+
pip install --progress-bar off -e .[all]
|
| 135 |
+
python -m detectron2.utils.collect_env
|
| 136 |
+
./datasets/prepare_for_tests.sh
|
| 137 |
+
|
| 138 |
+
run_unittests: &run_unittests
|
| 139 |
+
- run:
|
| 140 |
+
name: Run Unit Tests
|
| 141 |
+
command: |
|
| 142 |
+
pytest -sv --durations=15 tests # parallel causes some random failures
|
| 143 |
+
|
| 144 |
+
uninstall_tests: &uninstall_tests
|
| 145 |
+
- run:
|
| 146 |
+
name: Run Tests After Uninstalling
|
| 147 |
+
command: |
|
| 148 |
+
pip uninstall -y detectron2
|
| 149 |
+
# Remove built binaries
|
| 150 |
+
rm -rf build/ detectron2/*.so
|
| 151 |
+
# Tests that code is importable without installation
|
| 152 |
+
PYTHONPATH=. ./.circleci/import-tests.sh
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
# -------------------------------------------------------------------------------------
|
| 156 |
+
# Jobs to run
|
| 157 |
+
# -------------------------------------------------------------------------------------
|
| 158 |
+
jobs:
|
| 159 |
+
linux_cpu_tests:
|
| 160 |
+
<<: *cpu
|
| 161 |
+
<<: *version_parameters
|
| 162 |
+
|
| 163 |
+
working_directory: ~/detectron2
|
| 164 |
+
|
| 165 |
+
steps:
|
| 166 |
+
- checkout
|
| 167 |
+
|
| 168 |
+
# Cache the venv directory that contains python, dependencies, and checkpoints
|
| 169 |
+
# Refresh the key when dependencies should be updated (e.g. when pytorch releases)
|
| 170 |
+
- restore_cache:
|
| 171 |
+
keys:
|
| 172 |
+
- cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210827
|
| 173 |
+
|
| 174 |
+
- <<: *install_python
|
| 175 |
+
- <<: *install_linux_dep
|
| 176 |
+
- <<: *install_detectron2
|
| 177 |
+
- <<: *run_unittests
|
| 178 |
+
- <<: *uninstall_tests
|
| 179 |
+
|
| 180 |
+
- save_cache:
|
| 181 |
+
paths:
|
| 182 |
+
- /opt/circleci/.pyenv
|
| 183 |
+
- ~/.torch
|
| 184 |
+
key: cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210827
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
linux_gpu_tests:
|
| 188 |
+
<<: *gpu
|
| 189 |
+
<<: *version_parameters
|
| 190 |
+
|
| 191 |
+
working_directory: ~/detectron2
|
| 192 |
+
|
| 193 |
+
steps:
|
| 194 |
+
- checkout
|
| 195 |
+
|
| 196 |
+
- restore_cache:
|
| 197 |
+
keys:
|
| 198 |
+
- cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210827
|
| 199 |
+
|
| 200 |
+
- <<: *install_python
|
| 201 |
+
- <<: *install_linux_dep
|
| 202 |
+
- <<: *install_detectron2
|
| 203 |
+
- <<: *run_unittests
|
| 204 |
+
- <<: *uninstall_tests
|
| 205 |
+
|
| 206 |
+
- save_cache:
|
| 207 |
+
paths:
|
| 208 |
+
- /opt/circleci/.pyenv
|
| 209 |
+
- ~/.torch
|
| 210 |
+
key: cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210827
|
| 211 |
+
|
| 212 |
+
windows_cpu_build:
|
| 213 |
+
<<: *windows_cpu
|
| 214 |
+
<<: *version_parameters
|
| 215 |
+
steps:
|
| 216 |
+
- <<: *add_ssh_keys
|
| 217 |
+
- checkout
|
| 218 |
+
- <<: *setup_venv_win
|
| 219 |
+
|
| 220 |
+
# Cache the env directory that contains dependencies
|
| 221 |
+
- restore_cache:
|
| 222 |
+
keys:
|
| 223 |
+
- cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210404
|
| 224 |
+
|
| 225 |
+
- run:
|
| 226 |
+
name: Install Dependencies
|
| 227 |
+
command: |
|
| 228 |
+
pip install certifi --ignore-installed # required on windows to workaround some cert issue
|
| 229 |
+
pip install numpy cython # required on windows before pycocotools
|
| 230 |
+
pip install opencv-python-headless pytest-xdist pycocotools tensorboard onnx
|
| 231 |
+
pip install -U git+https://github.com/facebookresearch/iopath
|
| 232 |
+
pip install -U git+https://github.com/facebookresearch/fvcore
|
| 233 |
+
pip install torch==$env:PYTORCH_VERSION torchvision==$env:TORCHVISION_VERSION -f $env:PYTORCH_INDEX
|
| 234 |
+
|
| 235 |
+
- save_cache:
|
| 236 |
+
paths:
|
| 237 |
+
- env
|
| 238 |
+
key: cache-{{ arch }}-<< parameters.pytorch_version >>-{{ .Branch }}-20210404
|
| 239 |
+
|
| 240 |
+
- <<: *install_detectron2
|
| 241 |
+
# TODO: unittest fails for now
|
| 242 |
+
|
| 243 |
+
workflows:
|
| 244 |
+
version: 2
|
| 245 |
+
regular_test:
|
| 246 |
+
jobs:
|
| 247 |
+
- linux_cpu_tests:
|
| 248 |
+
name: linux_cpu_tests_pytorch1.10
|
| 249 |
+
pytorch_version: '1.10.0+cpu'
|
| 250 |
+
torchvision_version: '0.11.1+cpu'
|
| 251 |
+
- linux_gpu_tests:
|
| 252 |
+
name: linux_gpu_tests_pytorch1.8
|
| 253 |
+
pytorch_version: '1.8.1+cu111'
|
| 254 |
+
torchvision_version: '0.9.1+cu111'
|
| 255 |
+
- linux_gpu_tests:
|
| 256 |
+
name: linux_gpu_tests_pytorch1.9
|
| 257 |
+
pytorch_version: '1.9+cu111'
|
| 258 |
+
torchvision_version: '0.10+cu111'
|
| 259 |
+
- linux_gpu_tests:
|
| 260 |
+
name: linux_gpu_tests_pytorch1.10
|
| 261 |
+
pytorch_version: '1.10+cu111'
|
| 262 |
+
torchvision_version: '0.11.1+cu111'
|
| 263 |
+
- linux_gpu_tests:
|
| 264 |
+
name: linux_gpu_tests_pytorch1.10_python39
|
| 265 |
+
pytorch_version: '1.10+cu111'
|
| 266 |
+
torchvision_version: '0.11.1+cu111'
|
| 267 |
+
python_version: '3.9.6'
|
| 268 |
+
- windows_cpu_build:
|
| 269 |
+
pytorch_version: '1.10+cpu'
|
| 270 |
+
torchvision_version: '0.11.1+cpu'
|
approach/ovod/detectron2/.circleci/import-tests.sh
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash -e
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
|
| 4 |
+
# Test that import works without building detectron2.
|
| 5 |
+
|
| 6 |
+
# Check that _C is not importable
|
| 7 |
+
python -c "from detectron2 import _C" > /dev/null 2>&1 && {
|
| 8 |
+
echo "This test should be run without building detectron2."
|
| 9 |
+
exit 1
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
# Check that other modules are still importable, even when _C is not importable
|
| 13 |
+
python -c "from detectron2 import modeling"
|
| 14 |
+
python -c "from detectron2 import modeling, data"
|
| 15 |
+
python -c "from detectron2 import evaluation, export, checkpoint"
|
| 16 |
+
python -c "from detectron2 import utils, engine"
|
approach/ovod/detectron2/.github/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Code of Conduct
|
| 2 |
+
|
| 3 |
+
Facebook has adopted a Code of Conduct that we expect project participants to adhere to.
|
| 4 |
+
Please read the [full text](https://code.fb.com/codeofconduct/)
|
| 5 |
+
so that you can understand what actions will and will not be tolerated.
|
approach/ovod/detectron2/.github/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to detectron2
|
| 2 |
+
|
| 3 |
+
## Issues
|
| 4 |
+
We use GitHub issues to track public bugs and questions.
|
| 5 |
+
Please make sure to follow one of the
|
| 6 |
+
[issue templates](https://github.com/facebookresearch/detectron2/issues/new/choose)
|
| 7 |
+
when reporting any issues.
|
| 8 |
+
|
| 9 |
+
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
| 10 |
+
disclosure of security bugs. In those cases, please go through the process
|
| 11 |
+
outlined on that page and do not file a public issue.
|
| 12 |
+
|
| 13 |
+
## Pull Requests
|
| 14 |
+
We actively welcome pull requests.
|
| 15 |
+
|
| 16 |
+
However, if you're adding any significant features (e.g. > 50 lines), please
|
| 17 |
+
make sure to discuss with maintainers about your motivation and proposals in an issue
|
| 18 |
+
before sending a PR. This is to save your time so you don't spend time on a PR that we'll not accept.
|
| 19 |
+
|
| 20 |
+
We do not always accept new features, and we take the following
|
| 21 |
+
factors into consideration:
|
| 22 |
+
|
| 23 |
+
1. Whether the same feature can be achieved without modifying detectron2.
|
| 24 |
+
Detectron2 is designed so that you can implement many extensions from the outside, e.g.
|
| 25 |
+
those in [projects](https://github.com/facebookresearch/detectron2/tree/master/projects).
|
| 26 |
+
* If some part of detectron2 is not extensible enough, you can also bring up a more general issue to
|
| 27 |
+
improve it. Such feature request may be useful to more users.
|
| 28 |
+
2. Whether the feature is potentially useful to a large audience (e.g. an impactful detection paper, a popular dataset,
|
| 29 |
+
a significant speedup, a widely useful utility),
|
| 30 |
+
or only to a small portion of users (e.g., a less-known paper, an improvement not in the object
|
| 31 |
+
detection field, a trick that's not very popular in the community, code to handle a non-standard type of data)
|
| 32 |
+
* Adoption of additional models, datasets, new task are by default not added to detectron2 before they
|
| 33 |
+
receive significant popularity in the community.
|
| 34 |
+
We sometimes accept such features in `projects/`, or as a link in `projects/README.md`.
|
| 35 |
+
3. Whether the proposed solution has a good design / interface. This can be discussed in the issue prior to PRs, or
|
| 36 |
+
in the form of a draft PR.
|
| 37 |
+
4. Whether the proposed solution adds extra mental/practical overhead to users who don't
|
| 38 |
+
need such feature.
|
| 39 |
+
5. Whether the proposed solution breaks existing APIs.
|
| 40 |
+
|
| 41 |
+
To add a feature to an existing function/class `Func`, there are always two approaches:
|
| 42 |
+
(1) add new arguments to `Func`; (2) write a new `Func_with_new_feature`.
|
| 43 |
+
To meet the above criteria, we often prefer approach (2), because:
|
| 44 |
+
|
| 45 |
+
1. It does not involve modifying or potentially breaking existing code.
|
| 46 |
+
2. It does not add overhead to users who do not need the new feature.
|
| 47 |
+
3. Adding new arguments to a function/class is not scalable w.r.t. all the possible new research ideas in the future.
|
| 48 |
+
|
| 49 |
+
When sending a PR, please do:
|
| 50 |
+
|
| 51 |
+
1. If a PR contains multiple orthogonal changes, split it to several PRs.
|
| 52 |
+
2. If you've added code that should be tested, add tests.
|
| 53 |
+
3. For PRs that need experiments (e.g. adding a new model or new methods),
|
| 54 |
+
you don't need to update model zoo, but do provide experiment results in the description of the PR.
|
| 55 |
+
4. If APIs are changed, update the documentation.
|
| 56 |
+
5. We use the [Google style docstrings](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html) in python.
|
| 57 |
+
6. Make sure your code lints with `./dev/linter.sh`.
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
## Contributor License Agreement ("CLA")
|
| 61 |
+
In order to accept your pull request, we need you to submit a CLA. You only need
|
| 62 |
+
to do this once to work on any of Facebook's open source projects.
|
| 63 |
+
|
| 64 |
+
Complete your CLA here: <https://code.facebook.com/cla>
|
| 65 |
+
|
| 66 |
+
## License
|
| 67 |
+
By contributing to detectron2, you agree that your contributions will be licensed
|
| 68 |
+
under the LICENSE file in the root directory of this source tree.
|
approach/ovod/detectron2/.github/Detectron2-Logo-Horz.svg
ADDED
|
|
approach/ovod/detectron2/.github/ISSUE_TEMPLATE.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
Please select an issue template from
|
| 3 |
+
https://github.com/facebookresearch/detectron2/issues/new/choose .
|
| 4 |
+
|
| 5 |
+
Otherwise your issue will be closed.
|
approach/ovod/detectron2/.github/pull_request_template.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Thanks for your contribution!
|
| 2 |
+
|
| 3 |
+
If you're sending a large PR (e.g., >100 lines),
|
| 4 |
+
please open an issue first about the feature / bug, and indicate how you want to contribute.
|
| 5 |
+
|
| 6 |
+
We do not always accept features.
|
| 7 |
+
See https://detectron2.readthedocs.io/notes/contributing.html#pull-requests about how we handle PRs.
|
| 8 |
+
|
| 9 |
+
Before submitting a PR, please run `dev/linter.sh` to lint the code.
|
| 10 |
+
|
approach/ovod/detectron2/configs/Base-RCNN-C4.yaml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: "GeneralizedRCNN"
|
| 3 |
+
RPN:
|
| 4 |
+
PRE_NMS_TOPK_TEST: 6000
|
| 5 |
+
POST_NMS_TOPK_TEST: 1000
|
| 6 |
+
ROI_HEADS:
|
| 7 |
+
NAME: "Res5ROIHeads"
|
| 8 |
+
DATASETS:
|
| 9 |
+
TRAIN: ("coco_2017_train",)
|
| 10 |
+
TEST: ("coco_2017_val",)
|
| 11 |
+
SOLVER:
|
| 12 |
+
IMS_PER_BATCH: 16
|
| 13 |
+
BASE_LR: 0.02
|
| 14 |
+
STEPS: (60000, 80000)
|
| 15 |
+
MAX_ITER: 90000
|
| 16 |
+
INPUT:
|
| 17 |
+
MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
|
| 18 |
+
VERSION: 2
|
approach/ovod/detectron2/configs/Base-RCNN-DilatedC5.yaml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: "GeneralizedRCNN"
|
| 3 |
+
RESNETS:
|
| 4 |
+
OUT_FEATURES: ["res5"]
|
| 5 |
+
RES5_DILATION: 2
|
| 6 |
+
RPN:
|
| 7 |
+
IN_FEATURES: ["res5"]
|
| 8 |
+
PRE_NMS_TOPK_TEST: 6000
|
| 9 |
+
POST_NMS_TOPK_TEST: 1000
|
| 10 |
+
ROI_HEADS:
|
| 11 |
+
NAME: "StandardROIHeads"
|
| 12 |
+
IN_FEATURES: ["res5"]
|
| 13 |
+
ROI_BOX_HEAD:
|
| 14 |
+
NAME: "FastRCNNConvFCHead"
|
| 15 |
+
NUM_FC: 2
|
| 16 |
+
POOLER_RESOLUTION: 7
|
| 17 |
+
ROI_MASK_HEAD:
|
| 18 |
+
NAME: "MaskRCNNConvUpsampleHead"
|
| 19 |
+
NUM_CONV: 4
|
| 20 |
+
POOLER_RESOLUTION: 14
|
| 21 |
+
DATASETS:
|
| 22 |
+
TRAIN: ("coco_2017_train",)
|
| 23 |
+
TEST: ("coco_2017_val",)
|
| 24 |
+
SOLVER:
|
| 25 |
+
IMS_PER_BATCH: 16
|
| 26 |
+
BASE_LR: 0.02
|
| 27 |
+
STEPS: (60000, 80000)
|
| 28 |
+
MAX_ITER: 90000
|
| 29 |
+
INPUT:
|
| 30 |
+
MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
|
| 31 |
+
VERSION: 2
|
approach/ovod/detectron2/configs/Base-RCNN-FPN.yaml
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: "GeneralizedRCNN"
|
| 3 |
+
BACKBONE:
|
| 4 |
+
NAME: "build_resnet_fpn_backbone"
|
| 5 |
+
RESNETS:
|
| 6 |
+
OUT_FEATURES: ["res2", "res3", "res4", "res5"]
|
| 7 |
+
FPN:
|
| 8 |
+
IN_FEATURES: ["res2", "res3", "res4", "res5"]
|
| 9 |
+
ANCHOR_GENERATOR:
|
| 10 |
+
SIZES: [[32], [64], [128], [256], [512]] # One size for each in feature map
|
| 11 |
+
ASPECT_RATIOS: [[0.5, 1.0, 2.0]] # Three aspect ratios (same for all in feature maps)
|
| 12 |
+
RPN:
|
| 13 |
+
IN_FEATURES: ["p2", "p3", "p4", "p5", "p6"]
|
| 14 |
+
PRE_NMS_TOPK_TRAIN: 2000 # Per FPN level
|
| 15 |
+
PRE_NMS_TOPK_TEST: 1000 # Per FPN level
|
| 16 |
+
# Detectron1 uses 2000 proposals per-batch,
|
| 17 |
+
# (See "modeling/rpn/rpn_outputs.py" for details of this legacy issue)
|
| 18 |
+
# which is approximately 1000 proposals per-image since the default batch size for FPN is 2.
|
| 19 |
+
POST_NMS_TOPK_TRAIN: 1000
|
| 20 |
+
POST_NMS_TOPK_TEST: 1000
|
| 21 |
+
ROI_HEADS:
|
| 22 |
+
NAME: "StandardROIHeads"
|
| 23 |
+
IN_FEATURES: ["p2", "p3", "p4", "p5"]
|
| 24 |
+
ROI_BOX_HEAD:
|
| 25 |
+
NAME: "FastRCNNConvFCHead"
|
| 26 |
+
NUM_FC: 2
|
| 27 |
+
POOLER_RESOLUTION: 7
|
| 28 |
+
ROI_MASK_HEAD:
|
| 29 |
+
NAME: "MaskRCNNConvUpsampleHead"
|
| 30 |
+
NUM_CONV: 4
|
| 31 |
+
POOLER_RESOLUTION: 14
|
| 32 |
+
DATASETS:
|
| 33 |
+
TRAIN: ("coco_2017_train",)
|
| 34 |
+
TEST: ("coco_2017_val",)
|
| 35 |
+
SOLVER:
|
| 36 |
+
IMS_PER_BATCH: 16
|
| 37 |
+
BASE_LR: 0.02
|
| 38 |
+
STEPS: (60000, 80000)
|
| 39 |
+
MAX_ITER: 90000
|
| 40 |
+
INPUT:
|
| 41 |
+
MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
|
| 42 |
+
VERSION: 2
|
approach/ovod/detectron2/configs/Base-RetinaNet.yaml
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MODEL:
|
| 2 |
+
META_ARCHITECTURE: "RetinaNet"
|
| 3 |
+
BACKBONE:
|
| 4 |
+
NAME: "build_retinanet_resnet_fpn_backbone"
|
| 5 |
+
RESNETS:
|
| 6 |
+
OUT_FEATURES: ["res3", "res4", "res5"]
|
| 7 |
+
ANCHOR_GENERATOR:
|
| 8 |
+
SIZES: !!python/object/apply:eval ["[[x, x * 2**(1.0/3), x * 2**(2.0/3) ] for x in [32, 64, 128, 256, 512 ]]"]
|
| 9 |
+
FPN:
|
| 10 |
+
IN_FEATURES: ["res3", "res4", "res5"]
|
| 11 |
+
RETINANET:
|
| 12 |
+
IOU_THRESHOLDS: [0.4, 0.5]
|
| 13 |
+
IOU_LABELS: [0, -1, 1]
|
| 14 |
+
SMOOTH_L1_LOSS_BETA: 0.0
|
| 15 |
+
DATASETS:
|
| 16 |
+
TRAIN: ("coco_2017_train",)
|
| 17 |
+
TEST: ("coco_2017_val",)
|
| 18 |
+
SOLVER:
|
| 19 |
+
IMS_PER_BATCH: 16
|
| 20 |
+
BASE_LR: 0.01 # Note that RetinaNet uses a different default learning rate
|
| 21 |
+
STEPS: (60000, 80000)
|
| 22 |
+
MAX_ITER: 90000
|
| 23 |
+
INPUT:
|
| 24 |
+
MIN_SIZE_TRAIN: (640, 672, 704, 736, 768, 800)
|
| 25 |
+
VERSION: 2
|
approach/ovod/detectron2/demo/README.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
## Detectron2 Demo
|
| 3 |
+
|
| 4 |
+
We provide a command line tool to run a simple demo of builtin configs.
|
| 5 |
+
The usage is explained in [GETTING_STARTED.md](../GETTING_STARTED.md).
|
| 6 |
+
|
| 7 |
+
See our [blog post](https://ai.facebook.com/blog/-detectron2-a-pytorch-based-modular-object-detection-library-)
|
| 8 |
+
for a high-quality demo generated with this tool.
|
approach/ovod/detectron2/demo/demo.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
import argparse
|
| 3 |
+
import glob
|
| 4 |
+
import multiprocessing as mp
|
| 5 |
+
import numpy as np
|
| 6 |
+
import os
|
| 7 |
+
import tempfile
|
| 8 |
+
import time
|
| 9 |
+
import warnings
|
| 10 |
+
import cv2
|
| 11 |
+
import tqdm
|
| 12 |
+
|
| 13 |
+
from detectron2.config import get_cfg
|
| 14 |
+
from detectron2.data.detection_utils import read_image
|
| 15 |
+
from detectron2.utils.logger import setup_logger
|
| 16 |
+
|
| 17 |
+
from predictor import VisualizationDemo
|
| 18 |
+
|
| 19 |
+
# constants
|
| 20 |
+
WINDOW_NAME = "COCO detections"
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def setup_cfg(args):
|
| 24 |
+
# load config from file and command-line arguments
|
| 25 |
+
cfg = get_cfg()
|
| 26 |
+
# To use demo for Panoptic-DeepLab, please uncomment the following two lines.
|
| 27 |
+
# from detectron2.projects.panoptic_deeplab import add_panoptic_deeplab_config # noqa
|
| 28 |
+
# add_panoptic_deeplab_config(cfg)
|
| 29 |
+
cfg.merge_from_file(args.config_file)
|
| 30 |
+
cfg.merge_from_list(args.opts)
|
| 31 |
+
# Set score_threshold for builtin models
|
| 32 |
+
cfg.MODEL.RETINANET.SCORE_THRESH_TEST = args.confidence_threshold
|
| 33 |
+
cfg.MODEL.ROI_HEADS.SCORE_THRESH_TEST = args.confidence_threshold
|
| 34 |
+
cfg.MODEL.PANOPTIC_FPN.COMBINE.INSTANCES_CONFIDENCE_THRESH = args.confidence_threshold
|
| 35 |
+
cfg.freeze()
|
| 36 |
+
return cfg
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def get_parser():
|
| 40 |
+
parser = argparse.ArgumentParser(description="Detectron2 demo for builtin configs")
|
| 41 |
+
parser.add_argument(
|
| 42 |
+
"--config-file",
|
| 43 |
+
default="configs/quick_schedules/mask_rcnn_R_50_FPN_inference_acc_test.yaml",
|
| 44 |
+
metavar="FILE",
|
| 45 |
+
help="path to config file",
|
| 46 |
+
)
|
| 47 |
+
parser.add_argument("--webcam", action="store_true", help="Take inputs from webcam.")
|
| 48 |
+
parser.add_argument("--video-input", help="Path to video file.")
|
| 49 |
+
parser.add_argument(
|
| 50 |
+
"--input",
|
| 51 |
+
nargs="+",
|
| 52 |
+
help="A list of space separated input images; "
|
| 53 |
+
"or a single glob pattern such as 'directory/*.jpg'",
|
| 54 |
+
)
|
| 55 |
+
parser.add_argument(
|
| 56 |
+
"--output",
|
| 57 |
+
help="A file or directory to save output visualizations. "
|
| 58 |
+
"If not given, will show output in an OpenCV window.",
|
| 59 |
+
)
|
| 60 |
+
|
| 61 |
+
parser.add_argument(
|
| 62 |
+
"--confidence-threshold",
|
| 63 |
+
type=float,
|
| 64 |
+
default=0.5,
|
| 65 |
+
help="Minimum score for instance predictions to be shown",
|
| 66 |
+
)
|
| 67 |
+
parser.add_argument(
|
| 68 |
+
"--opts",
|
| 69 |
+
help="Modify config options using the command-line 'KEY VALUE' pairs",
|
| 70 |
+
default=[],
|
| 71 |
+
nargs=argparse.REMAINDER,
|
| 72 |
+
)
|
| 73 |
+
return parser
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def test_opencv_video_format(codec, file_ext):
|
| 77 |
+
with tempfile.TemporaryDirectory(prefix="video_format_test") as dir:
|
| 78 |
+
filename = os.path.join(dir, "test_file" + file_ext)
|
| 79 |
+
writer = cv2.VideoWriter(
|
| 80 |
+
filename=filename,
|
| 81 |
+
fourcc=cv2.VideoWriter_fourcc(*codec),
|
| 82 |
+
fps=float(30),
|
| 83 |
+
frameSize=(10, 10),
|
| 84 |
+
isColor=True,
|
| 85 |
+
)
|
| 86 |
+
[writer.write(np.zeros((10, 10, 3), np.uint8)) for _ in range(30)]
|
| 87 |
+
writer.release()
|
| 88 |
+
if os.path.isfile(filename):
|
| 89 |
+
return True
|
| 90 |
+
return False
|
| 91 |
+
|
| 92 |
+
|
| 93 |
+
if __name__ == "__main__":
|
| 94 |
+
mp.set_start_method("spawn", force=True)
|
| 95 |
+
args = get_parser().parse_args()
|
| 96 |
+
setup_logger(name="fvcore")
|
| 97 |
+
logger = setup_logger()
|
| 98 |
+
logger.info("Arguments: " + str(args))
|
| 99 |
+
|
| 100 |
+
cfg = setup_cfg(args)
|
| 101 |
+
|
| 102 |
+
demo = VisualizationDemo(cfg)
|
| 103 |
+
|
| 104 |
+
if args.input:
|
| 105 |
+
if len(args.input) == 1:
|
| 106 |
+
args.input = glob.glob(os.path.expanduser(args.input[0]))
|
| 107 |
+
assert args.input, "The input path(s) was not found"
|
| 108 |
+
for path in tqdm.tqdm(args.input, disable=not args.output):
|
| 109 |
+
# use PIL, to be consistent with evaluation
|
| 110 |
+
img = read_image(path, format="BGR")
|
| 111 |
+
start_time = time.time()
|
| 112 |
+
predictions, visualized_output = demo.run_on_image(img)
|
| 113 |
+
logger.info(
|
| 114 |
+
"{}: {} in {:.2f}s".format(
|
| 115 |
+
path,
|
| 116 |
+
"detected {} instances".format(len(predictions["instances"]))
|
| 117 |
+
if "instances" in predictions
|
| 118 |
+
else "finished",
|
| 119 |
+
time.time() - start_time,
|
| 120 |
+
)
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
if args.output:
|
| 124 |
+
if os.path.isdir(args.output):
|
| 125 |
+
assert os.path.isdir(args.output), args.output
|
| 126 |
+
out_filename = os.path.join(args.output, os.path.basename(path))
|
| 127 |
+
else:
|
| 128 |
+
assert len(args.input) == 1, "Please specify a directory with args.output"
|
| 129 |
+
out_filename = args.output
|
| 130 |
+
visualized_output.save(out_filename)
|
| 131 |
+
else:
|
| 132 |
+
cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_NORMAL)
|
| 133 |
+
cv2.imshow(WINDOW_NAME, visualized_output.get_image()[:, :, ::-1])
|
| 134 |
+
if cv2.waitKey(0) == 27:
|
| 135 |
+
break # esc to quit
|
| 136 |
+
elif args.webcam:
|
| 137 |
+
assert args.input is None, "Cannot have both --input and --webcam!"
|
| 138 |
+
assert args.output is None, "output not yet supported with --webcam!"
|
| 139 |
+
cam = cv2.VideoCapture(0)
|
| 140 |
+
for vis in tqdm.tqdm(demo.run_on_video(cam)):
|
| 141 |
+
cv2.namedWindow(WINDOW_NAME, cv2.WINDOW_NORMAL)
|
| 142 |
+
cv2.imshow(WINDOW_NAME, vis)
|
| 143 |
+
if cv2.waitKey(1) == 27:
|
| 144 |
+
break # esc to quit
|
| 145 |
+
cam.release()
|
| 146 |
+
cv2.destroyAllWindows()
|
| 147 |
+
elif args.video_input:
|
| 148 |
+
video = cv2.VideoCapture(args.video_input)
|
| 149 |
+
width = int(video.get(cv2.CAP_PROP_FRAME_WIDTH))
|
| 150 |
+
height = int(video.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
| 151 |
+
frames_per_second = video.get(cv2.CAP_PROP_FPS)
|
| 152 |
+
num_frames = int(video.get(cv2.CAP_PROP_FRAME_COUNT))
|
| 153 |
+
basename = os.path.basename(args.video_input)
|
| 154 |
+
codec, file_ext = (
|
| 155 |
+
("x264", ".mkv") if test_opencv_video_format("x264", ".mkv") else ("mp4v", ".mp4")
|
| 156 |
+
)
|
| 157 |
+
if codec == ".mp4v":
|
| 158 |
+
warnings.warn("x264 codec not available, switching to mp4v")
|
| 159 |
+
if args.output:
|
| 160 |
+
if os.path.isdir(args.output):
|
| 161 |
+
output_fname = os.path.join(args.output, basename)
|
| 162 |
+
output_fname = os.path.splitext(output_fname)[0] + file_ext
|
| 163 |
+
else:
|
| 164 |
+
output_fname = args.output
|
| 165 |
+
assert not os.path.isfile(output_fname), output_fname
|
| 166 |
+
output_file = cv2.VideoWriter(
|
| 167 |
+
filename=output_fname,
|
| 168 |
+
# some installation of opencv may not support x264 (due to its license),
|
| 169 |
+
# you can try other format (e.g. MPEG)
|
| 170 |
+
fourcc=cv2.VideoWriter_fourcc(*codec),
|
| 171 |
+
fps=float(frames_per_second),
|
| 172 |
+
frameSize=(width, height),
|
| 173 |
+
isColor=True,
|
| 174 |
+
)
|
| 175 |
+
assert os.path.isfile(args.video_input)
|
| 176 |
+
for vis_frame in tqdm.tqdm(demo.run_on_video(video), total=num_frames):
|
| 177 |
+
if args.output:
|
| 178 |
+
output_file.write(vis_frame)
|
| 179 |
+
else:
|
| 180 |
+
cv2.namedWindow(basename, cv2.WINDOW_NORMAL)
|
| 181 |
+
cv2.imshow(basename, vis_frame)
|
| 182 |
+
if cv2.waitKey(1) == 27:
|
| 183 |
+
break # esc to quit
|
| 184 |
+
video.release()
|
| 185 |
+
if args.output:
|
| 186 |
+
output_file.release()
|
| 187 |
+
else:
|
| 188 |
+
cv2.destroyAllWindows()
|
approach/ovod/detectron2/demo/predictor.py
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
import atexit
|
| 3 |
+
import bisect
|
| 4 |
+
import multiprocessing as mp
|
| 5 |
+
from collections import deque
|
| 6 |
+
import cv2
|
| 7 |
+
import torch
|
| 8 |
+
|
| 9 |
+
from detectron2.data import MetadataCatalog
|
| 10 |
+
from detectron2.engine.defaults import DefaultPredictor
|
| 11 |
+
from detectron2.utils.video_visualizer import VideoVisualizer
|
| 12 |
+
from detectron2.utils.visualizer import ColorMode, Visualizer
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
class VisualizationDemo(object):
|
| 16 |
+
def __init__(self, cfg, instance_mode=ColorMode.IMAGE, parallel=False):
|
| 17 |
+
"""
|
| 18 |
+
Args:
|
| 19 |
+
cfg (CfgNode):
|
| 20 |
+
instance_mode (ColorMode):
|
| 21 |
+
parallel (bool): whether to run the model in different processes from visualization.
|
| 22 |
+
Useful since the visualization logic can be slow.
|
| 23 |
+
"""
|
| 24 |
+
self.metadata = MetadataCatalog.get(
|
| 25 |
+
cfg.DATASETS.TEST[0] if len(cfg.DATASETS.TEST) else "__unused"
|
| 26 |
+
)
|
| 27 |
+
self.cpu_device = torch.device("cpu")
|
| 28 |
+
self.instance_mode = instance_mode
|
| 29 |
+
|
| 30 |
+
self.parallel = parallel
|
| 31 |
+
if parallel:
|
| 32 |
+
num_gpu = torch.cuda.device_count()
|
| 33 |
+
self.predictor = AsyncPredictor(cfg, num_gpus=num_gpu)
|
| 34 |
+
else:
|
| 35 |
+
self.predictor = DefaultPredictor(cfg)
|
| 36 |
+
|
| 37 |
+
def run_on_image(self, image):
|
| 38 |
+
"""
|
| 39 |
+
Args:
|
| 40 |
+
image (np.ndarray): an image of shape (H, W, C) (in BGR order).
|
| 41 |
+
This is the format used by OpenCV.
|
| 42 |
+
|
| 43 |
+
Returns:
|
| 44 |
+
predictions (dict): the output of the model.
|
| 45 |
+
vis_output (VisImage): the visualized image output.
|
| 46 |
+
"""
|
| 47 |
+
vis_output = None
|
| 48 |
+
predictions = self.predictor(image)
|
| 49 |
+
# Convert image from OpenCV BGR format to Matplotlib RGB format.
|
| 50 |
+
image = image[:, :, ::-1]
|
| 51 |
+
visualizer = Visualizer(image, self.metadata, instance_mode=self.instance_mode)
|
| 52 |
+
if "panoptic_seg" in predictions:
|
| 53 |
+
panoptic_seg, segments_info = predictions["panoptic_seg"]
|
| 54 |
+
vis_output = visualizer.draw_panoptic_seg_predictions(
|
| 55 |
+
panoptic_seg.to(self.cpu_device), segments_info
|
| 56 |
+
)
|
| 57 |
+
else:
|
| 58 |
+
if "sem_seg" in predictions:
|
| 59 |
+
vis_output = visualizer.draw_sem_seg(
|
| 60 |
+
predictions["sem_seg"].argmax(dim=0).to(self.cpu_device)
|
| 61 |
+
)
|
| 62 |
+
if "instances" in predictions:
|
| 63 |
+
instances = predictions["instances"].to(self.cpu_device)
|
| 64 |
+
vis_output = visualizer.draw_instance_predictions(predictions=instances)
|
| 65 |
+
|
| 66 |
+
return predictions, vis_output
|
| 67 |
+
|
| 68 |
+
def _frame_from_video(self, video):
|
| 69 |
+
while video.isOpened():
|
| 70 |
+
success, frame = video.read()
|
| 71 |
+
if success:
|
| 72 |
+
yield frame
|
| 73 |
+
else:
|
| 74 |
+
break
|
| 75 |
+
|
| 76 |
+
def run_on_video(self, video):
|
| 77 |
+
"""
|
| 78 |
+
Visualizes predictions on frames of the input video.
|
| 79 |
+
|
| 80 |
+
Args:
|
| 81 |
+
video (cv2.VideoCapture): a :class:`VideoCapture` object, whose source can be
|
| 82 |
+
either a webcam or a video file.
|
| 83 |
+
|
| 84 |
+
Yields:
|
| 85 |
+
ndarray: BGR visualizations of each video frame.
|
| 86 |
+
"""
|
| 87 |
+
video_visualizer = VideoVisualizer(self.metadata, self.instance_mode)
|
| 88 |
+
|
| 89 |
+
def process_predictions(frame, predictions):
|
| 90 |
+
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
|
| 91 |
+
if "panoptic_seg" in predictions:
|
| 92 |
+
panoptic_seg, segments_info = predictions["panoptic_seg"]
|
| 93 |
+
vis_frame = video_visualizer.draw_panoptic_seg_predictions(
|
| 94 |
+
frame, panoptic_seg.to(self.cpu_device), segments_info
|
| 95 |
+
)
|
| 96 |
+
elif "instances" in predictions:
|
| 97 |
+
predictions = predictions["instances"].to(self.cpu_device)
|
| 98 |
+
vis_frame = video_visualizer.draw_instance_predictions(frame, predictions)
|
| 99 |
+
elif "sem_seg" in predictions:
|
| 100 |
+
vis_frame = video_visualizer.draw_sem_seg(
|
| 101 |
+
frame, predictions["sem_seg"].argmax(dim=0).to(self.cpu_device)
|
| 102 |
+
)
|
| 103 |
+
|
| 104 |
+
# Converts Matplotlib RGB format to OpenCV BGR format
|
| 105 |
+
vis_frame = cv2.cvtColor(vis_frame.get_image(), cv2.COLOR_RGB2BGR)
|
| 106 |
+
return vis_frame
|
| 107 |
+
|
| 108 |
+
frame_gen = self._frame_from_video(video)
|
| 109 |
+
if self.parallel:
|
| 110 |
+
buffer_size = self.predictor.default_buffer_size
|
| 111 |
+
|
| 112 |
+
frame_data = deque()
|
| 113 |
+
|
| 114 |
+
for cnt, frame in enumerate(frame_gen):
|
| 115 |
+
frame_data.append(frame)
|
| 116 |
+
self.predictor.put(frame)
|
| 117 |
+
|
| 118 |
+
if cnt >= buffer_size:
|
| 119 |
+
frame = frame_data.popleft()
|
| 120 |
+
predictions = self.predictor.get()
|
| 121 |
+
yield process_predictions(frame, predictions)
|
| 122 |
+
|
| 123 |
+
while len(frame_data):
|
| 124 |
+
frame = frame_data.popleft()
|
| 125 |
+
predictions = self.predictor.get()
|
| 126 |
+
yield process_predictions(frame, predictions)
|
| 127 |
+
else:
|
| 128 |
+
for frame in frame_gen:
|
| 129 |
+
yield process_predictions(frame, self.predictor(frame))
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
class AsyncPredictor:
|
| 133 |
+
"""
|
| 134 |
+
A predictor that runs the model asynchronously, possibly on >1 GPUs.
|
| 135 |
+
Because rendering the visualization takes considerably amount of time,
|
| 136 |
+
this helps improve throughput a little bit when rendering videos.
|
| 137 |
+
"""
|
| 138 |
+
|
| 139 |
+
class _StopToken:
|
| 140 |
+
pass
|
| 141 |
+
|
| 142 |
+
class _PredictWorker(mp.Process):
|
| 143 |
+
def __init__(self, cfg, task_queue, result_queue):
|
| 144 |
+
self.cfg = cfg
|
| 145 |
+
self.task_queue = task_queue
|
| 146 |
+
self.result_queue = result_queue
|
| 147 |
+
super().__init__()
|
| 148 |
+
|
| 149 |
+
def run(self):
|
| 150 |
+
predictor = DefaultPredictor(self.cfg)
|
| 151 |
+
|
| 152 |
+
while True:
|
| 153 |
+
task = self.task_queue.get()
|
| 154 |
+
if isinstance(task, AsyncPredictor._StopToken):
|
| 155 |
+
break
|
| 156 |
+
idx, data = task
|
| 157 |
+
result = predictor(data)
|
| 158 |
+
self.result_queue.put((idx, result))
|
| 159 |
+
|
| 160 |
+
def __init__(self, cfg, num_gpus: int = 1):
|
| 161 |
+
"""
|
| 162 |
+
Args:
|
| 163 |
+
cfg (CfgNode):
|
| 164 |
+
num_gpus (int): if 0, will run on CPU
|
| 165 |
+
"""
|
| 166 |
+
num_workers = max(num_gpus, 1)
|
| 167 |
+
self.task_queue = mp.Queue(maxsize=num_workers * 3)
|
| 168 |
+
self.result_queue = mp.Queue(maxsize=num_workers * 3)
|
| 169 |
+
self.procs = []
|
| 170 |
+
for gpuid in range(max(num_gpus, 1)):
|
| 171 |
+
cfg = cfg.clone()
|
| 172 |
+
cfg.defrost()
|
| 173 |
+
cfg.MODEL.DEVICE = "cuda:{}".format(gpuid) if num_gpus > 0 else "cpu"
|
| 174 |
+
self.procs.append(
|
| 175 |
+
AsyncPredictor._PredictWorker(cfg, self.task_queue, self.result_queue)
|
| 176 |
+
)
|
| 177 |
+
|
| 178 |
+
self.put_idx = 0
|
| 179 |
+
self.get_idx = 0
|
| 180 |
+
self.result_rank = []
|
| 181 |
+
self.result_data = []
|
| 182 |
+
|
| 183 |
+
for p in self.procs:
|
| 184 |
+
p.start()
|
| 185 |
+
atexit.register(self.shutdown)
|
| 186 |
+
|
| 187 |
+
def put(self, image):
|
| 188 |
+
self.put_idx += 1
|
| 189 |
+
self.task_queue.put((self.put_idx, image))
|
| 190 |
+
|
| 191 |
+
def get(self):
|
| 192 |
+
self.get_idx += 1 # the index needed for this request
|
| 193 |
+
if len(self.result_rank) and self.result_rank[0] == self.get_idx:
|
| 194 |
+
res = self.result_data[0]
|
| 195 |
+
del self.result_data[0], self.result_rank[0]
|
| 196 |
+
return res
|
| 197 |
+
|
| 198 |
+
while True:
|
| 199 |
+
# make sure the results are returned in the correct order
|
| 200 |
+
idx, res = self.result_queue.get()
|
| 201 |
+
if idx == self.get_idx:
|
| 202 |
+
return res
|
| 203 |
+
insert = bisect.bisect(self.result_rank, idx)
|
| 204 |
+
self.result_rank.insert(insert, idx)
|
| 205 |
+
self.result_data.insert(insert, res)
|
| 206 |
+
|
| 207 |
+
def __len__(self):
|
| 208 |
+
return self.put_idx - self.get_idx
|
| 209 |
+
|
| 210 |
+
def __call__(self, image):
|
| 211 |
+
self.put(image)
|
| 212 |
+
return self.get()
|
| 213 |
+
|
| 214 |
+
def shutdown(self):
|
| 215 |
+
for _ in self.procs:
|
| 216 |
+
self.task_queue.put(AsyncPredictor._StopToken())
|
| 217 |
+
|
| 218 |
+
@property
|
| 219 |
+
def default_buffer_size(self):
|
| 220 |
+
return len(self.procs) * 5
|
approach/ovod/detectron2/detectron2/__init__.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 2 |
+
|
| 3 |
+
from .utils.env import setup_environment
|
| 4 |
+
|
| 5 |
+
setup_environment()
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
# This line will be programatically read/write by setup.py.
|
| 9 |
+
# Leave them at the bottom of this file and don't touch them.
|
| 10 |
+
__version__ = "0.6"
|
approach/ovod/detectron2/dev/README.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
## Some scripts for developers to use, include:
|
| 3 |
+
|
| 4 |
+
- `linter.sh`: lint the codebase before commit.
|
| 5 |
+
- `run_{inference,instant}_tests.sh`: run inference/training for a few iterations.
|
| 6 |
+
Note that these tests require 2 GPUs.
|
| 7 |
+
- `parse_results.sh`: parse results from a log file.
|
approach/ovod/detectron2/dev/linter.sh
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash -e
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
|
| 4 |
+
# cd to detectron2 project root
|
| 5 |
+
cd "$(dirname "${BASH_SOURCE[0]}")/.."
|
| 6 |
+
|
| 7 |
+
{
|
| 8 |
+
black --version | grep -E "22\." > /dev/null
|
| 9 |
+
} || {
|
| 10 |
+
echo "Linter requires 'black==22.*' !"
|
| 11 |
+
exit 1
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
ISORT_VERSION=$(isort --version-number)
|
| 15 |
+
if [[ "$ISORT_VERSION" != 4.3* ]]; then
|
| 16 |
+
echo "Linter requires isort==4.3.21 !"
|
| 17 |
+
exit 1
|
| 18 |
+
fi
|
| 19 |
+
|
| 20 |
+
set -v
|
| 21 |
+
|
| 22 |
+
echo "Running isort ..."
|
| 23 |
+
isort -y -sp . --atomic
|
| 24 |
+
|
| 25 |
+
echo "Running black ..."
|
| 26 |
+
black -l 100 .
|
| 27 |
+
|
| 28 |
+
echo "Running flake8 ..."
|
| 29 |
+
if [ -x "$(command -v flake8)" ]; then
|
| 30 |
+
flake8 .
|
| 31 |
+
else
|
| 32 |
+
python3 -m flake8 .
|
| 33 |
+
fi
|
| 34 |
+
|
| 35 |
+
# echo "Running mypy ..."
|
| 36 |
+
# Pytorch does not have enough type annotations
|
| 37 |
+
# mypy detectron2/solver detectron2/structures detectron2/config
|
| 38 |
+
|
| 39 |
+
echo "Running clang-format ..."
|
| 40 |
+
find . -regex ".*\.\(cpp\|c\|cc\|cu\|cxx\|h\|hh\|hpp\|hxx\|tcc\|mm\|m\)" -print0 | xargs -0 clang-format -i
|
| 41 |
+
|
| 42 |
+
command -v arc > /dev/null && arc lint
|
approach/ovod/detectron2/dev/parse_results.sh
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
|
| 4 |
+
# A shell script that parses metrics from the log file.
|
| 5 |
+
# Make it easier for developers to track performance of models.
|
| 6 |
+
|
| 7 |
+
LOG="$1"
|
| 8 |
+
|
| 9 |
+
if [[ -z "$LOG" ]]; then
|
| 10 |
+
echo "Usage: $0 /path/to/log/file"
|
| 11 |
+
exit 1
|
| 12 |
+
fi
|
| 13 |
+
|
| 14 |
+
# [12/15 11:47:32] trainer INFO: Total training time: 12:15:04.446477 (0.4900 s / it)
|
| 15 |
+
# [12/15 11:49:03] inference INFO: Total inference time: 0:01:25.326167 (0.13652186737060548 s / img per device, on 8 devices)
|
| 16 |
+
# [12/15 11:49:03] inference INFO: Total inference pure compute time: .....
|
| 17 |
+
|
| 18 |
+
# training time
|
| 19 |
+
trainspeed=$(grep -o 'Overall training.*' "$LOG" | grep -Eo '\(.*\)' | grep -o '[0-9\.]*')
|
| 20 |
+
echo "Training speed: $trainspeed s/it"
|
| 21 |
+
|
| 22 |
+
# inference time: there could be multiple inference during training
|
| 23 |
+
inferencespeed=$(grep -o 'Total inference pure.*' "$LOG" | tail -n1 | grep -Eo '\(.*\)' | grep -o '[0-9\.]*' | head -n1)
|
| 24 |
+
echo "Inference speed: $inferencespeed s/it"
|
| 25 |
+
|
| 26 |
+
# [12/15 11:47:18] trainer INFO: eta: 0:00:00 iter: 90000 loss: 0.5407 (0.7256) loss_classifier: 0.1744 (0.2446) loss_box_reg: 0.0838 (0.1160) loss_mask: 0.2159 (0.2722) loss_objectness: 0.0244 (0.0429) loss_rpn_box_reg: 0.0279 (0.0500) time: 0.4487 (0.4899) data: 0.0076 (0.0975) lr: 0.000200 max mem: 4161
|
| 27 |
+
memory=$(grep -o 'max[_ ]mem: [0-9]*' "$LOG" | tail -n1 | grep -o '[0-9]*')
|
| 28 |
+
echo "Training memory: $memory MB"
|
| 29 |
+
|
| 30 |
+
echo "Easy to copypaste:"
|
| 31 |
+
echo "$trainspeed","$inferencespeed","$memory"
|
| 32 |
+
|
| 33 |
+
echo "------------------------------"
|
| 34 |
+
|
| 35 |
+
# [12/26 17:26:32] engine.coco_evaluation: copypaste: Task: bbox
|
| 36 |
+
# [12/26 17:26:32] engine.coco_evaluation: copypaste: AP,AP50,AP75,APs,APm,APl
|
| 37 |
+
# [12/26 17:26:32] engine.coco_evaluation: copypaste: 0.0017,0.0024,0.0017,0.0005,0.0019,0.0011
|
| 38 |
+
# [12/26 17:26:32] engine.coco_evaluation: copypaste: Task: segm
|
| 39 |
+
# [12/26 17:26:32] engine.coco_evaluation: copypaste: AP,AP50,AP75,APs,APm,APl
|
| 40 |
+
# [12/26 17:26:32] engine.coco_evaluation: copypaste: 0.0014,0.0021,0.0016,0.0005,0.0016,0.0011
|
| 41 |
+
|
| 42 |
+
echo "COCO Results:"
|
| 43 |
+
num_tasks=$(grep -o 'copypaste:.*Task.*' "$LOG" | sort -u | wc -l)
|
| 44 |
+
# each task has 3 lines
|
| 45 |
+
grep -o 'copypaste:.*' "$LOG" | cut -d ' ' -f 2- | tail -n $((num_tasks * 3))
|
approach/ovod/detectron2/dev/run_inference_tests.sh
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash -e
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
|
| 4 |
+
BIN="python tools/train_net.py"
|
| 5 |
+
OUTPUT="inference_test_output"
|
| 6 |
+
NUM_GPUS=2
|
| 7 |
+
|
| 8 |
+
CFG_LIST=( "${@:1}" )
|
| 9 |
+
|
| 10 |
+
if [ ${#CFG_LIST[@]} -eq 0 ]; then
|
| 11 |
+
CFG_LIST=( ./configs/quick_schedules/*inference_acc_test.yaml )
|
| 12 |
+
fi
|
| 13 |
+
|
| 14 |
+
echo "========================================================================"
|
| 15 |
+
echo "Configs to run:"
|
| 16 |
+
echo "${CFG_LIST[@]}"
|
| 17 |
+
echo "========================================================================"
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
for cfg in "${CFG_LIST[@]}"; do
|
| 21 |
+
echo "========================================================================"
|
| 22 |
+
echo "Running $cfg ..."
|
| 23 |
+
echo "========================================================================"
|
| 24 |
+
$BIN \
|
| 25 |
+
--eval-only \
|
| 26 |
+
--num-gpus $NUM_GPUS \
|
| 27 |
+
--config-file "$cfg" \
|
| 28 |
+
OUTPUT_DIR $OUTPUT
|
| 29 |
+
rm -rf $OUTPUT
|
| 30 |
+
done
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
echo "========================================================================"
|
| 34 |
+
echo "Running demo.py ..."
|
| 35 |
+
echo "========================================================================"
|
| 36 |
+
DEMO_BIN="python demo/demo.py"
|
| 37 |
+
COCO_DIR=datasets/coco/val2014
|
| 38 |
+
mkdir -pv $OUTPUT
|
| 39 |
+
|
| 40 |
+
set -v
|
| 41 |
+
|
| 42 |
+
$DEMO_BIN --config-file ./configs/quick_schedules/panoptic_fpn_R_50_inference_acc_test.yaml \
|
| 43 |
+
--input $COCO_DIR/COCO_val2014_0000001933* --output $OUTPUT
|
| 44 |
+
rm -rf $OUTPUT
|
approach/ovod/detectron2/dev/run_instant_tests.sh
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash -e
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
|
| 4 |
+
BIN="python tools/train_net.py"
|
| 5 |
+
OUTPUT="instant_test_output"
|
| 6 |
+
NUM_GPUS=2
|
| 7 |
+
|
| 8 |
+
CFG_LIST=( "${@:1}" )
|
| 9 |
+
if [ ${#CFG_LIST[@]} -eq 0 ]; then
|
| 10 |
+
CFG_LIST=( ./configs/quick_schedules/*instant_test.yaml )
|
| 11 |
+
fi
|
| 12 |
+
|
| 13 |
+
echo "========================================================================"
|
| 14 |
+
echo "Configs to run:"
|
| 15 |
+
echo "${CFG_LIST[@]}"
|
| 16 |
+
echo "========================================================================"
|
| 17 |
+
|
| 18 |
+
for cfg in "${CFG_LIST[@]}"; do
|
| 19 |
+
echo "========================================================================"
|
| 20 |
+
echo "Running $cfg ..."
|
| 21 |
+
echo "========================================================================"
|
| 22 |
+
$BIN --num-gpus $NUM_GPUS --config-file "$cfg" \
|
| 23 |
+
SOLVER.IMS_PER_BATCH $(($NUM_GPUS * 2)) \
|
| 24 |
+
OUTPUT_DIR "$OUTPUT"
|
| 25 |
+
rm -rf "$OUTPUT"
|
| 26 |
+
done
|
| 27 |
+
|
approach/ovod/detectron2/docs/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
_build
|
approach/ovod/detectron2/docs/Makefile
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Minimal makefile for Sphinx documentation
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
|
| 4 |
+
# You can set these variables from the command line.
|
| 5 |
+
SPHINXOPTS =
|
| 6 |
+
SPHINXBUILD = sphinx-build
|
| 7 |
+
SOURCEDIR = .
|
| 8 |
+
BUILDDIR = _build
|
| 9 |
+
|
| 10 |
+
# Put it first so that "make" without argument is like "make help".
|
| 11 |
+
help:
|
| 12 |
+
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
| 13 |
+
|
| 14 |
+
.PHONY: help Makefile
|
| 15 |
+
|
| 16 |
+
# Catch-all target: route all unknown targets to Sphinx using the new
|
| 17 |
+
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS).
|
| 18 |
+
%: Makefile
|
| 19 |
+
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
|
approach/ovod/detectron2/docs/README.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Read the docs:
|
| 2 |
+
|
| 3 |
+
The latest documentation built from this directory is available at [detectron2.readthedocs.io](https://detectron2.readthedocs.io/).
|
| 4 |
+
Documents in this directory are not meant to be read on github.
|
| 5 |
+
|
| 6 |
+
# Build the docs:
|
| 7 |
+
|
| 8 |
+
1. Install detectron2 according to [INSTALL.md](../INSTALL.md).
|
| 9 |
+
2. Install additional libraries required to build docs:
|
| 10 |
+
- docutils==0.16
|
| 11 |
+
- Sphinx==3.2.0
|
| 12 |
+
- recommonmark==0.6.0
|
| 13 |
+
- sphinx_rtd_theme
|
| 14 |
+
|
| 15 |
+
3. Run `make html` from this directory.
|
approach/ovod/detectron2/docs/conf.py
ADDED
|
@@ -0,0 +1,391 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# -*- coding: utf-8 -*-
|
| 2 |
+
# Copyright (c) Facebook, Inc. and its affiliates.
|
| 3 |
+
|
| 4 |
+
# flake8: noqa
|
| 5 |
+
|
| 6 |
+
# Configuration file for the Sphinx documentation builder.
|
| 7 |
+
#
|
| 8 |
+
# This file does only contain a selection of the most common options. For a
|
| 9 |
+
# full list see the documentation:
|
| 10 |
+
# http://www.sphinx-doc.org/en/master/config
|
| 11 |
+
|
| 12 |
+
# -- Path setup --------------------------------------------------------------
|
| 13 |
+
|
| 14 |
+
# If extensions (or modules to document with autodoc) are in another directory,
|
| 15 |
+
# add these directories to sys.path here. If the directory is relative to the
|
| 16 |
+
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
| 17 |
+
#
|
| 18 |
+
import os
|
| 19 |
+
import sys
|
| 20 |
+
from unittest import mock
|
| 21 |
+
from sphinx.domains import Domain
|
| 22 |
+
from typing import Dict, List, Tuple
|
| 23 |
+
|
| 24 |
+
# The theme to use for HTML and HTML Help pages. See the documentation for
|
| 25 |
+
# a list of builtin themes.
|
| 26 |
+
#
|
| 27 |
+
import sphinx_rtd_theme
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class GithubURLDomain(Domain):
|
| 31 |
+
"""
|
| 32 |
+
Resolve certain links in markdown files to github source.
|
| 33 |
+
"""
|
| 34 |
+
|
| 35 |
+
name = "githuburl"
|
| 36 |
+
ROOT = "https://github.com/facebookresearch/detectron2/blob/main/"
|
| 37 |
+
LINKED_DOC = ["tutorials/install", "tutorials/getting_started"]
|
| 38 |
+
|
| 39 |
+
def resolve_any_xref(self, env, fromdocname, builder, target, node, contnode):
|
| 40 |
+
github_url = None
|
| 41 |
+
if not target.endswith("html") and target.startswith("../../"):
|
| 42 |
+
url = target.replace("../", "")
|
| 43 |
+
github_url = url
|
| 44 |
+
if fromdocname in self.LINKED_DOC:
|
| 45 |
+
# unresolved links in these docs are all github links
|
| 46 |
+
github_url = target
|
| 47 |
+
|
| 48 |
+
if github_url is not None:
|
| 49 |
+
if github_url.endswith("MODEL_ZOO") or github_url.endswith("README"):
|
| 50 |
+
# bug of recommonmark.
|
| 51 |
+
# https://github.com/readthedocs/recommonmark/blob/ddd56e7717e9745f11300059e4268e204138a6b1/recommonmark/parser.py#L152-L155
|
| 52 |
+
github_url += ".md"
|
| 53 |
+
print("Ref {} resolved to github:{}".format(target, github_url))
|
| 54 |
+
contnode["refuri"] = self.ROOT + github_url
|
| 55 |
+
return [("githuburl:any", contnode)]
|
| 56 |
+
else:
|
| 57 |
+
return []
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
# to support markdown
|
| 61 |
+
from recommonmark.parser import CommonMarkParser
|
| 62 |
+
|
| 63 |
+
sys.path.insert(0, os.path.abspath("../"))
|
| 64 |
+
os.environ["_DOC_BUILDING"] = "True"
|
| 65 |
+
DEPLOY = os.environ.get("READTHEDOCS") == "True"
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
# -- Project information -----------------------------------------------------
|
| 69 |
+
|
| 70 |
+
# fmt: off
|
| 71 |
+
try:
|
| 72 |
+
import torch # noqa
|
| 73 |
+
except ImportError:
|
| 74 |
+
for m in [
|
| 75 |
+
"torch", "torchvision", "torch.nn", "torch.nn.parallel", "torch.distributed", "torch.multiprocessing", "torch.autograd",
|
| 76 |
+
"torch.autograd.function", "torch.nn.modules", "torch.nn.modules.utils", "torch.utils", "torch.utils.data", "torch.onnx",
|
| 77 |
+
"torchvision", "torchvision.ops",
|
| 78 |
+
]:
|
| 79 |
+
sys.modules[m] = mock.Mock(name=m)
|
| 80 |
+
sys.modules['torch'].__version__ = "1.7" # fake version
|
| 81 |
+
HAS_TORCH = False
|
| 82 |
+
else:
|
| 83 |
+
try:
|
| 84 |
+
torch.ops.detectron2 = mock.Mock(name="torch.ops.detectron2")
|
| 85 |
+
except:
|
| 86 |
+
pass
|
| 87 |
+
HAS_TORCH = True
|
| 88 |
+
|
| 89 |
+
for m in [
|
| 90 |
+
"cv2", "scipy", "portalocker", "detectron2._C",
|
| 91 |
+
"pycocotools", "pycocotools.mask", "pycocotools.coco", "pycocotools.cocoeval",
|
| 92 |
+
"google", "google.protobuf", "google.protobuf.internal", "onnx",
|
| 93 |
+
"caffe2", "caffe2.proto", "caffe2.python", "caffe2.python.utils", "caffe2.python.onnx", "caffe2.python.onnx.backend",
|
| 94 |
+
]:
|
| 95 |
+
sys.modules[m] = mock.Mock(name=m)
|
| 96 |
+
# fmt: on
|
| 97 |
+
sys.modules["cv2"].__version__ = "3.4"
|
| 98 |
+
|
| 99 |
+
import detectron2 # isort: skip
|
| 100 |
+
|
| 101 |
+
if HAS_TORCH:
|
| 102 |
+
from detectron2.utils.env import fixup_module_metadata
|
| 103 |
+
|
| 104 |
+
fixup_module_metadata("torch.nn", torch.nn.__dict__)
|
| 105 |
+
fixup_module_metadata("torch.utils.data", torch.utils.data.__dict__)
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
project = "detectron2"
|
| 109 |
+
copyright = "2019-2020, detectron2 contributors"
|
| 110 |
+
author = "detectron2 contributors"
|
| 111 |
+
|
| 112 |
+
# The short X.Y version
|
| 113 |
+
version = detectron2.__version__
|
| 114 |
+
# The full version, including alpha/beta/rc tags
|
| 115 |
+
release = version
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# -- General configuration ---------------------------------------------------
|
| 119 |
+
|
| 120 |
+
# If your documentation needs a minimal Sphinx version, state it here.
|
| 121 |
+
#
|
| 122 |
+
needs_sphinx = "3.0"
|
| 123 |
+
|
| 124 |
+
# Add any Sphinx extension module names here, as strings. They can be
|
| 125 |
+
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
| 126 |
+
# ones.
|
| 127 |
+
extensions = [
|
| 128 |
+
"recommonmark",
|
| 129 |
+
"sphinx.ext.autodoc",
|
| 130 |
+
"sphinx.ext.napoleon",
|
| 131 |
+
"sphinx.ext.intersphinx",
|
| 132 |
+
"sphinx.ext.todo",
|
| 133 |
+
"sphinx.ext.coverage",
|
| 134 |
+
"sphinx.ext.mathjax",
|
| 135 |
+
"sphinx.ext.viewcode",
|
| 136 |
+
"sphinx.ext.githubpages",
|
| 137 |
+
]
|
| 138 |
+
|
| 139 |
+
# -- Configurations for plugins ------------
|
| 140 |
+
napoleon_google_docstring = True
|
| 141 |
+
napoleon_include_init_with_doc = True
|
| 142 |
+
napoleon_include_special_with_doc = True
|
| 143 |
+
napoleon_numpy_docstring = False
|
| 144 |
+
napoleon_use_rtype = False
|
| 145 |
+
autodoc_inherit_docstrings = False
|
| 146 |
+
autodoc_member_order = "bysource"
|
| 147 |
+
|
| 148 |
+
if DEPLOY:
|
| 149 |
+
intersphinx_timeout = 10
|
| 150 |
+
else:
|
| 151 |
+
# skip this when building locally
|
| 152 |
+
intersphinx_timeout = 0.5
|
| 153 |
+
intersphinx_mapping = {
|
| 154 |
+
"python": ("https://docs.python.org/3.7", None),
|
| 155 |
+
"numpy": ("https://docs.scipy.org/doc/numpy/", None),
|
| 156 |
+
"torch": ("https://pytorch.org/docs/master/", None),
|
| 157 |
+
}
|
| 158 |
+
# -------------------------
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
# Add any paths that contain templates here, relative to this directory.
|
| 162 |
+
templates_path = ["_templates"]
|
| 163 |
+
|
| 164 |
+
source_suffix = [".rst", ".md"]
|
| 165 |
+
|
| 166 |
+
# The master toctree document.
|
| 167 |
+
master_doc = "index"
|
| 168 |
+
|
| 169 |
+
# The language for content autogenerated by Sphinx. Refer to documentation
|
| 170 |
+
# for a list of supported languages.
|
| 171 |
+
#
|
| 172 |
+
# This is also used if you do content translation via gettext catalogs.
|
| 173 |
+
# Usually you set "language" from the command line for these cases.
|
| 174 |
+
language = None
|
| 175 |
+
|
| 176 |
+
# List of patterns, relative to source directory, that match files and
|
| 177 |
+
# directories to ignore when looking for source files.
|
| 178 |
+
# This pattern also affects html_static_path and html_extra_path.
|
| 179 |
+
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", "build", "README.md", "tutorials/README.md"]
|
| 180 |
+
|
| 181 |
+
# The name of the Pygments (syntax highlighting) style to use.
|
| 182 |
+
pygments_style = "sphinx"
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
# -- Options for HTML output -------------------------------------------------
|
| 186 |
+
|
| 187 |
+
html_theme = "sphinx_rtd_theme"
|
| 188 |
+
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
|
| 189 |
+
|
| 190 |
+
# Theme options are theme-specific and customize the look and feel of a theme
|
| 191 |
+
# further. For a list of options available for each theme, see the
|
| 192 |
+
# documentation.
|
| 193 |
+
#
|
| 194 |
+
# html_theme_options = {}
|
| 195 |
+
|
| 196 |
+
# Add any paths that contain custom static files (such as style sheets) here,
|
| 197 |
+
# relative to this directory. They are copied after the builtin static files,
|
| 198 |
+
# so a file named "default.css" will overwrite the builtin "default.css".
|
| 199 |
+
html_static_path = ["_static"]
|
| 200 |
+
html_css_files = ["css/custom.css"]
|
| 201 |
+
|
| 202 |
+
# Custom sidebar templates, must be a dictionary that maps document names
|
| 203 |
+
# to template names.
|
| 204 |
+
#
|
| 205 |
+
# The default sidebars (for documents that don't match any pattern) are
|
| 206 |
+
# defined by theme itself. Builtin themes are using these templates by
|
| 207 |
+
# default: ``['localtoc.html', 'relations.html', 'sourcelink.html',
|
| 208 |
+
# 'searchbox.html']``.
|
| 209 |
+
#
|
| 210 |
+
# html_sidebars = {}
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
# -- Options for HTMLHelp output ---------------------------------------------
|
| 214 |
+
|
| 215 |
+
# Output file base name for HTML help builder.
|
| 216 |
+
htmlhelp_basename = "detectron2doc"
|
| 217 |
+
|
| 218 |
+
|
| 219 |
+
# -- Options for LaTeX output ------------------------------------------------
|
| 220 |
+
|
| 221 |
+
latex_elements = {
|
| 222 |
+
# The paper size ('letterpaper' or 'a4paper').
|
| 223 |
+
#
|
| 224 |
+
# 'papersize': 'letterpaper',
|
| 225 |
+
# The font size ('10pt', '11pt' or '12pt').
|
| 226 |
+
#
|
| 227 |
+
# 'pointsize': '10pt',
|
| 228 |
+
# Additional stuff for the LaTeX preamble.
|
| 229 |
+
#
|
| 230 |
+
# 'preamble': '',
|
| 231 |
+
# Latex figure (float) alignment
|
| 232 |
+
#
|
| 233 |
+
# 'figure_align': 'htbp',
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
# Grouping the document tree into LaTeX files. List of tuples
|
| 237 |
+
# (source start file, target name, title,
|
| 238 |
+
# author, documentclass [howto, manual, or own class]).
|
| 239 |
+
latex_documents = [
|
| 240 |
+
(master_doc, "detectron2.tex", "detectron2 Documentation", "detectron2 contributors", "manual")
|
| 241 |
+
]
|
| 242 |
+
|
| 243 |
+
|
| 244 |
+
# -- Options for manual page output ------------------------------------------
|
| 245 |
+
|
| 246 |
+
# One entry per manual page. List of tuples
|
| 247 |
+
# (source start file, name, description, authors, manual section).
|
| 248 |
+
man_pages = [(master_doc, "detectron2", "detectron2 Documentation", [author], 1)]
|
| 249 |
+
|
| 250 |
+
|
| 251 |
+
# -- Options for Texinfo output ----------------------------------------------
|
| 252 |
+
|
| 253 |
+
# Grouping the document tree into Texinfo files. List of tuples
|
| 254 |
+
# (source start file, target name, title, author,
|
| 255 |
+
# dir menu entry, description, category)
|
| 256 |
+
texinfo_documents = [
|
| 257 |
+
(
|
| 258 |
+
master_doc,
|
| 259 |
+
"detectron2",
|
| 260 |
+
"detectron2 Documentation",
|
| 261 |
+
author,
|
| 262 |
+
"detectron2",
|
| 263 |
+
"One line description of project.",
|
| 264 |
+
"Miscellaneous",
|
| 265 |
+
)
|
| 266 |
+
]
|
| 267 |
+
|
| 268 |
+
|
| 269 |
+
# -- Options for todo extension ----------------------------------------------
|
| 270 |
+
|
| 271 |
+
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
| 272 |
+
todo_include_todos = True
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def autodoc_skip_member(app, what, name, obj, skip, options):
|
| 276 |
+
# we hide something deliberately
|
| 277 |
+
if getattr(obj, "__HIDE_SPHINX_DOC__", False):
|
| 278 |
+
return True
|
| 279 |
+
|
| 280 |
+
# Hide some that are deprecated or not intended to be used
|
| 281 |
+
HIDDEN = {
|
| 282 |
+
"ResNetBlockBase",
|
| 283 |
+
"GroupedBatchSampler",
|
| 284 |
+
"build_transform_gen",
|
| 285 |
+
"apply_transform_gens",
|
| 286 |
+
"TransformGen",
|
| 287 |
+
"apply_augmentations",
|
| 288 |
+
"StandardAugInput",
|
| 289 |
+
"build_batch_data_loader",
|
| 290 |
+
"draw_panoptic_seg_predictions",
|
| 291 |
+
"WarmupCosineLR",
|
| 292 |
+
"WarmupMultiStepLR",
|
| 293 |
+
"downgrade_config",
|
| 294 |
+
"upgrade_config",
|
| 295 |
+
"add_export_config",
|
| 296 |
+
}
|
| 297 |
+
try:
|
| 298 |
+
if name in HIDDEN or (
|
| 299 |
+
hasattr(obj, "__doc__") and obj.__doc__.lower().strip().startswith("deprecated")
|
| 300 |
+
):
|
| 301 |
+
print("Skipping deprecated object: {}".format(name))
|
| 302 |
+
return True
|
| 303 |
+
except:
|
| 304 |
+
pass
|
| 305 |
+
return skip
|
| 306 |
+
|
| 307 |
+
|
| 308 |
+
_PAPER_DATA = {
|
| 309 |
+
"resnet": ("1512.03385", "Deep Residual Learning for Image Recognition"),
|
| 310 |
+
"fpn": ("1612.03144", "Feature Pyramid Networks for Object Detection"),
|
| 311 |
+
"mask r-cnn": ("1703.06870", "Mask R-CNN"),
|
| 312 |
+
"faster r-cnn": (
|
| 313 |
+
"1506.01497",
|
| 314 |
+
"Faster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks",
|
| 315 |
+
),
|
| 316 |
+
"deformconv": ("1703.06211", "Deformable Convolutional Networks"),
|
| 317 |
+
"deformconv2": ("1811.11168", "Deformable ConvNets v2: More Deformable, Better Results"),
|
| 318 |
+
"panopticfpn": ("1901.02446", "Panoptic Feature Pyramid Networks"),
|
| 319 |
+
"retinanet": ("1708.02002", "Focal Loss for Dense Object Detection"),
|
| 320 |
+
"cascade r-cnn": ("1712.00726", "Cascade R-CNN: Delving into High Quality Object Detection"),
|
| 321 |
+
"lvis": ("1908.03195", "LVIS: A Dataset for Large Vocabulary Instance Segmentation"),
|
| 322 |
+
"rrpn": ("1703.01086", "Arbitrary-Oriented Scene Text Detection via Rotation Proposals"),
|
| 323 |
+
"imagenet in 1h": ("1706.02677", "Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour"),
|
| 324 |
+
"xception": ("1610.02357", "Xception: Deep Learning with Depthwise Separable Convolutions"),
|
| 325 |
+
"mobilenet": (
|
| 326 |
+
"1704.04861",
|
| 327 |
+
"MobileNets: Efficient Convolutional Neural Networks for Mobile Vision Applications",
|
| 328 |
+
),
|
| 329 |
+
"deeplabv3+": (
|
| 330 |
+
"1802.02611",
|
| 331 |
+
"Encoder-Decoder with Atrous Separable Convolution for Semantic Image Segmentation",
|
| 332 |
+
),
|
| 333 |
+
"dds": ("2003.13678", "Designing Network Design Spaces"),
|
| 334 |
+
"scaling": ("2103.06877", "Fast and Accurate Model Scaling"),
|
| 335 |
+
"fcos": ("2006.09214", "FCOS: A Simple and Strong Anchor-free Object Detector"),
|
| 336 |
+
"rethinking-batchnorm": ("2105.07576", 'Rethinking "Batch" in BatchNorm'),
|
| 337 |
+
"vitdet": ("2203.16527", "Exploring Plain Vision Transformer Backbones for Object Detection"),
|
| 338 |
+
"mvitv2": (
|
| 339 |
+
"2112.01526",
|
| 340 |
+
"MViTv2: Improved Multiscale Vision Transformers for Classification and Detection",
|
| 341 |
+
),
|
| 342 |
+
"swin": (
|
| 343 |
+
"2103.14030",
|
| 344 |
+
"Swin Transformer: Hierarchical Vision Transformer using Shifted Windows",
|
| 345 |
+
),
|
| 346 |
+
}
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def paper_ref_role(
|
| 350 |
+
typ: str,
|
| 351 |
+
rawtext: str,
|
| 352 |
+
text: str,
|
| 353 |
+
lineno: int,
|
| 354 |
+
inliner,
|
| 355 |
+
options: Dict = {},
|
| 356 |
+
content: List[str] = [],
|
| 357 |
+
):
|
| 358 |
+
"""
|
| 359 |
+
Parse :paper:`xxx`. Similar to the "extlinks" sphinx extension.
|
| 360 |
+
"""
|
| 361 |
+
from docutils import nodes, utils
|
| 362 |
+
from sphinx.util.nodes import split_explicit_title
|
| 363 |
+
|
| 364 |
+
text = utils.unescape(text)
|
| 365 |
+
has_explicit_title, title, link = split_explicit_title(text)
|
| 366 |
+
link = link.lower()
|
| 367 |
+
if link not in _PAPER_DATA:
|
| 368 |
+
inliner.reporter.warning("Cannot find paper " + link)
|
| 369 |
+
paper_url, paper_title = "#", link
|
| 370 |
+
else:
|
| 371 |
+
paper_url, paper_title = _PAPER_DATA[link]
|
| 372 |
+
if "/" not in paper_url:
|
| 373 |
+
paper_url = "https://arxiv.org/abs/" + paper_url
|
| 374 |
+
if not has_explicit_title:
|
| 375 |
+
title = paper_title
|
| 376 |
+
pnode = nodes.reference(title, title, internal=False, refuri=paper_url)
|
| 377 |
+
return [pnode], []
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
def setup(app):
|
| 381 |
+
from recommonmark.transform import AutoStructify
|
| 382 |
+
|
| 383 |
+
app.add_domain(GithubURLDomain)
|
| 384 |
+
app.connect("autodoc-skip-member", autodoc_skip_member)
|
| 385 |
+
app.add_role("paper", paper_ref_role)
|
| 386 |
+
app.add_config_value(
|
| 387 |
+
"recommonmark_config",
|
| 388 |
+
{"enable_math": True, "enable_inline_math": True, "enable_eval_rst": True},
|
| 389 |
+
True,
|
| 390 |
+
)
|
| 391 |
+
app.add_transform(AutoStructify)
|
approach/ovod/detectron2/docs/index.rst
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.. detectron2 documentation master file, created by
|
| 2 |
+
sphinx-quickstart on Sat Sep 21 13:46:45 2019.
|
| 3 |
+
You can adapt this file completely to your liking, but it should at least
|
| 4 |
+
contain the root `toctree` directive.
|
| 5 |
+
|
| 6 |
+
Welcome to detectron2's documentation!
|
| 7 |
+
======================================
|
| 8 |
+
|
| 9 |
+
.. toctree::
|
| 10 |
+
:maxdepth: 2
|
| 11 |
+
|
| 12 |
+
tutorials/index
|
| 13 |
+
notes/index
|
| 14 |
+
modules/index
|
approach/ovod/detectron2/docs/notes/benchmarks.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Benchmarks
|
| 3 |
+
|
| 4 |
+
Here we benchmark the training speed of a Mask R-CNN in detectron2,
|
| 5 |
+
with some other popular open source Mask R-CNN implementations.
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
### Settings
|
| 9 |
+
|
| 10 |
+
* Hardware: 8 NVIDIA V100s with NVLink.
|
| 11 |
+
* Software: Python 3.7, CUDA 10.1, cuDNN 7.6.5, PyTorch 1.5,
|
| 12 |
+
TensorFlow 1.15.0rc2, Keras 2.2.5, MxNet 1.6.0b20190820.
|
| 13 |
+
* Model: an end-to-end R-50-FPN Mask-RCNN model, using the same hyperparameter as the
|
| 14 |
+
[Detectron baseline config](https://github.com/facebookresearch/Detectron/blob/master/configs/12_2017_baselines/e2e_mask_rcnn_R-50-FPN_1x.yaml)
|
| 15 |
+
(it does not have scale augmentation).
|
| 16 |
+
* Metrics: We use the average throughput in iterations 100-500 to skip GPU warmup time.
|
| 17 |
+
Note that for R-CNN-style models, the throughput of a model typically changes during training, because
|
| 18 |
+
it depends on the predictions of the model. Therefore this metric is not directly comparable with
|
| 19 |
+
"train speed" in model zoo, which is the average speed of the entire training run.
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
### Main Results
|
| 23 |
+
|
| 24 |
+
```eval_rst
|
| 25 |
+
+-------------------------------+--------------------+
|
| 26 |
+
| Implementation | Throughput (img/s) |
|
| 27 |
+
+===============================+====================+
|
| 28 |
+
| |D2| |PT| | 62 |
|
| 29 |
+
+-------------------------------+--------------------+
|
| 30 |
+
| mmdetection_ |PT| | 53 |
|
| 31 |
+
+-------------------------------+--------------------+
|
| 32 |
+
| maskrcnn-benchmark_ |PT| | 53 |
|
| 33 |
+
+-------------------------------+--------------------+
|
| 34 |
+
| tensorpack_ |TF| | 50 |
|
| 35 |
+
+-------------------------------+--------------------+
|
| 36 |
+
| simpledet_ |mxnet| | 39 |
|
| 37 |
+
+-------------------------------+--------------------+
|
| 38 |
+
| Detectron_ |C2| | 19 |
|
| 39 |
+
+-------------------------------+--------------------+
|
| 40 |
+
| `matterport/Mask_RCNN`__ |TF| | 14 |
|
| 41 |
+
+-------------------------------+--------------------+
|
| 42 |
+
|
| 43 |
+
.. _maskrcnn-benchmark: https://github.com/facebookresearch/maskrcnn-benchmark/
|
| 44 |
+
.. _tensorpack: https://github.com/tensorpack/tensorpack/tree/master/examples/FasterRCNN
|
| 45 |
+
.. _mmdetection: https://github.com/open-mmlab/mmdetection/
|
| 46 |
+
.. _simpledet: https://github.com/TuSimple/simpledet/
|
| 47 |
+
.. _Detectron: https://github.com/facebookresearch/Detectron
|
| 48 |
+
__ https://github.com/matterport/Mask_RCNN/
|
| 49 |
+
|
| 50 |
+
.. |D2| image:: https://github.com/facebookresearch/detectron2/raw/main/.github/Detectron2-Logo-Horz.svg?sanitize=true
|
| 51 |
+
:height: 15pt
|
| 52 |
+
:target: https://github.com/facebookresearch/detectron2/
|
| 53 |
+
.. |PT| image:: https://pytorch.org/assets/images/logo-icon.svg
|
| 54 |
+
:width: 15pt
|
| 55 |
+
:height: 15pt
|
| 56 |
+
:target: https://pytorch.org
|
| 57 |
+
.. |TF| image:: https://static.nvidiagrid.net/ngc/containers/tensorflow.png
|
| 58 |
+
:width: 15pt
|
| 59 |
+
:height: 15pt
|
| 60 |
+
:target: https://tensorflow.org
|
| 61 |
+
.. |mxnet| image:: https://github.com/dmlc/web-data/raw/master/mxnet/image/mxnet_favicon.png
|
| 62 |
+
:width: 15pt
|
| 63 |
+
:height: 15pt
|
| 64 |
+
:target: https://mxnet.apache.org/
|
| 65 |
+
.. |C2| image:: https://caffe2.ai/static/logo.svg
|
| 66 |
+
:width: 15pt
|
| 67 |
+
:height: 15pt
|
| 68 |
+
:target: https://caffe2.ai
|
| 69 |
+
```
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
Details for each implementation:
|
| 73 |
+
|
| 74 |
+
* __Detectron2__: with release v0.1.2, run:
|
| 75 |
+
```
|
| 76 |
+
python tools/train_net.py --config-file configs/Detectron1-Comparisons/mask_rcnn_R_50_FPN_noaug_1x.yaml --num-gpus 8
|
| 77 |
+
```
|
| 78 |
+
|
| 79 |
+
* __mmdetection__: at commit `b0d845f`, run
|
| 80 |
+
```
|
| 81 |
+
./tools/dist_train.sh configs/mask_rcnn/mask_rcnn_r50_caffe_fpn_1x_coco.py 8
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
* __maskrcnn-benchmark__: use commit `0ce8f6f` with `sed -i 's/torch.uint8/torch.bool/g' **/*.py; sed -i 's/AT_CHECK/TORCH_CHECK/g' **/*.cu`
|
| 85 |
+
to make it compatible with PyTorch 1.5. Then, run training with
|
| 86 |
+
```
|
| 87 |
+
python -m torch.distributed.launch --nproc_per_node=8 tools/train_net.py --config-file configs/e2e_mask_rcnn_R_50_FPN_1x.yaml
|
| 88 |
+
```
|
| 89 |
+
The speed we observed is faster than its model zoo, likely due to different software versions.
|
| 90 |
+
|
| 91 |
+
* __tensorpack__: at commit `caafda`, `export TF_CUDNN_USE_AUTOTUNE=0`, then run
|
| 92 |
+
```
|
| 93 |
+
mpirun -np 8 ./train.py --config DATA.BASEDIR=/data/coco TRAINER=horovod BACKBONE.STRIDE_1X1=True TRAIN.STEPS_PER_EPOCH=50 --load ImageNet-R50-AlignPadding.npz
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
* __SimpleDet__: at commit `9187a1`, run
|
| 97 |
+
```
|
| 98 |
+
python detection_train.py --config config/mask_r50v1_fpn_1x.py
|
| 99 |
+
```
|
| 100 |
+
|
| 101 |
+
* __Detectron__: run
|
| 102 |
+
```
|
| 103 |
+
python tools/train_net.py --cfg configs/12_2017_baselines/e2e_mask_rcnn_R-50-FPN_1x.yaml
|
| 104 |
+
```
|
| 105 |
+
Note that many of its ops run on CPUs, therefore the performance is limited.
|
| 106 |
+
|
| 107 |
+
* __matterport/Mask_RCNN__: at commit `3deaec`, apply the following diff, `export TF_CUDNN_USE_AUTOTUNE=0`, then run
|
| 108 |
+
```
|
| 109 |
+
python coco.py train --dataset=/data/coco/ --model=imagenet
|
| 110 |
+
```
|
| 111 |
+
Note that many small details in this implementation might be different
|
| 112 |
+
from Detectron's standards.
|
| 113 |
+
|
| 114 |
+
<details>
|
| 115 |
+
<summary>
|
| 116 |
+
(diff to make it use the same hyperparameters - click to expand)
|
| 117 |
+
</summary>
|
| 118 |
+
|
| 119 |
+
```diff
|
| 120 |
+
diff --git i/mrcnn/model.py w/mrcnn/model.py
|
| 121 |
+
index 62cb2b0..61d7779 100644
|
| 122 |
+
--- i/mrcnn/model.py
|
| 123 |
+
+++ w/mrcnn/model.py
|
| 124 |
+
@@ -2367,8 +2367,8 @@ class MaskRCNN():
|
| 125 |
+
epochs=epochs,
|
| 126 |
+
steps_per_epoch=self.config.STEPS_PER_EPOCH,
|
| 127 |
+
callbacks=callbacks,
|
| 128 |
+
- validation_data=val_generator,
|
| 129 |
+
- validation_steps=self.config.VALIDATION_STEPS,
|
| 130 |
+
+ #validation_data=val_generator,
|
| 131 |
+
+ #validation_steps=self.config.VALIDATION_STEPS,
|
| 132 |
+
max_queue_size=100,
|
| 133 |
+
workers=workers,
|
| 134 |
+
use_multiprocessing=True,
|
| 135 |
+
diff --git i/mrcnn/parallel_model.py w/mrcnn/parallel_model.py
|
| 136 |
+
index d2bf53b..060172a 100644
|
| 137 |
+
--- i/mrcnn/parallel_model.py
|
| 138 |
+
+++ w/mrcnn/parallel_model.py
|
| 139 |
+
@@ -32,6 +32,7 @@ class ParallelModel(KM.Model):
|
| 140 |
+
keras_model: The Keras model to parallelize
|
| 141 |
+
gpu_count: Number of GPUs. Must be > 1
|
| 142 |
+
"""
|
| 143 |
+
+ super().__init__()
|
| 144 |
+
self.inner_model = keras_model
|
| 145 |
+
self.gpu_count = gpu_count
|
| 146 |
+
merged_outputs = self.make_parallel()
|
| 147 |
+
diff --git i/samples/coco/coco.py w/samples/coco/coco.py
|
| 148 |
+
index 5d172b5..239ed75 100644
|
| 149 |
+
--- i/samples/coco/coco.py
|
| 150 |
+
+++ w/samples/coco/coco.py
|
| 151 |
+
@@ -81,7 +81,10 @@ class CocoConfig(Config):
|
| 152 |
+
IMAGES_PER_GPU = 2
|
| 153 |
+
|
| 154 |
+
# Uncomment to train on 8 GPUs (default is 1)
|
| 155 |
+
- # GPU_COUNT = 8
|
| 156 |
+
+ GPU_COUNT = 8
|
| 157 |
+
+ BACKBONE = "resnet50"
|
| 158 |
+
+ STEPS_PER_EPOCH = 50
|
| 159 |
+
+ TRAIN_ROIS_PER_IMAGE = 512
|
| 160 |
+
|
| 161 |
+
# Number of classes (including background)
|
| 162 |
+
NUM_CLASSES = 1 + 80 # COCO has 80 classes
|
| 163 |
+
@@ -496,29 +499,10 @@ if __name__ == '__main__':
|
| 164 |
+
# *** This training schedule is an example. Update to your needs ***
|
| 165 |
+
|
| 166 |
+
# Training - Stage 1
|
| 167 |
+
- print("Training network heads")
|
| 168 |
+
model.train(dataset_train, dataset_val,
|
| 169 |
+
learning_rate=config.LEARNING_RATE,
|
| 170 |
+
epochs=40,
|
| 171 |
+
- layers='heads',
|
| 172 |
+
- augmentation=augmentation)
|
| 173 |
+
-
|
| 174 |
+
- # Training - Stage 2
|
| 175 |
+
- # Finetune layers from ResNet stage 4 and up
|
| 176 |
+
- print("Fine tune Resnet stage 4 and up")
|
| 177 |
+
- model.train(dataset_train, dataset_val,
|
| 178 |
+
- learning_rate=config.LEARNING_RATE,
|
| 179 |
+
- epochs=120,
|
| 180 |
+
- layers='4+',
|
| 181 |
+
- augmentation=augmentation)
|
| 182 |
+
-
|
| 183 |
+
- # Training - Stage 3
|
| 184 |
+
- # Fine tune all layers
|
| 185 |
+
- print("Fine tune all layers")
|
| 186 |
+
- model.train(dataset_train, dataset_val,
|
| 187 |
+
- learning_rate=config.LEARNING_RATE / 10,
|
| 188 |
+
- epochs=160,
|
| 189 |
+
- layers='all',
|
| 190 |
+
+ layers='3+',
|
| 191 |
+
augmentation=augmentation)
|
| 192 |
+
|
| 193 |
+
elif args.command == "evaluate":
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
</details>
|
approach/ovod/detectron2/docs/notes/changelog.md
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Change Log and Backward Compatibility
|
| 2 |
+
|
| 3 |
+
### Releases
|
| 4 |
+
See release logs at
|
| 5 |
+
[https://github.com/facebookresearch/detectron2/releases](https://github.com/facebookresearch/detectron2/releases)
|
| 6 |
+
for new updates.
|
| 7 |
+
|
| 8 |
+
### Backward Compatibility
|
| 9 |
+
|
| 10 |
+
Due to the research nature of what the library does, there might be backward incompatible changes.
|
| 11 |
+
But we try to reduce users' disruption by the following ways:
|
| 12 |
+
* APIs listed in [API documentation](https://detectron2.readthedocs.io/modules/index.html), including
|
| 13 |
+
function/class names, their arguments, and documented class attributes, are considered *stable* unless
|
| 14 |
+
otherwise noted in the documentation.
|
| 15 |
+
They are less likely to be broken, but if needed, will trigger a deprecation warning for a reasonable period
|
| 16 |
+
before getting broken, and will be documented in release logs.
|
| 17 |
+
* Others functions/classses/attributes are considered internal, and are more likely to change.
|
| 18 |
+
However, we're aware that some of them may be already used by other projects, and in particular we may
|
| 19 |
+
use them for convenience among projects under `detectron2/projects`.
|
| 20 |
+
For such APIs, we may treat them as stable APIs and also apply the above strategies.
|
| 21 |
+
They may be promoted to stable when we're ready.
|
| 22 |
+
* Projects under "detectron2/projects" or imported with "detectron2.projects" are research projects
|
| 23 |
+
and are all considered experimental.
|
| 24 |
+
* Classes/functions that contain the word "default" or are explicitly documented to produce
|
| 25 |
+
"default behavior" may change their behaviors when new features are added.
|
| 26 |
+
|
| 27 |
+
Despite of the possible breakage, if a third-party project would like to keep up with the latest updates
|
| 28 |
+
in detectron2, using it as a library will still be less disruptive than forking, because
|
| 29 |
+
the frequency and scope of API changes will be much smaller than code changes.
|
| 30 |
+
|
| 31 |
+
To see such changes, search for "incompatible changes" in [release logs](https://github.com/facebookresearch/detectron2/releases).
|
| 32 |
+
|
| 33 |
+
### Config Version Change Log
|
| 34 |
+
|
| 35 |
+
Detectron2's config version has not been changed since open source.
|
| 36 |
+
There is no need for an open source user to worry about this.
|
| 37 |
+
|
| 38 |
+
* v1: Rename `RPN_HEAD.NAME` to `RPN.HEAD_NAME`.
|
| 39 |
+
* v2: A batch of rename of many configurations before release.
|
| 40 |
+
|
| 41 |
+
### Silent Regressions in Historical Versions:
|
| 42 |
+
|
| 43 |
+
We list a few silent regressions, since they may silently produce incorrect results and will be hard to debug.
|
| 44 |
+
|
| 45 |
+
* 04/01/2020 - 05/11/2020: Bad accuracy if `TRAIN_ON_PRED_BOXES` is set to True.
|
| 46 |
+
* 03/30/2020 - 04/01/2020: ResNets are not correctly built.
|
| 47 |
+
* 12/19/2019 - 12/26/2019: Using aspect ratio grouping causes a drop in accuracy.
|
| 48 |
+
* - 11/9/2019: Test time augmentation does not predict the last category.
|
approach/ovod/detectron2/docs/notes/compatibility.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Compatibility with Other Libraries
|
| 2 |
+
|
| 3 |
+
## Compatibility with Detectron (and maskrcnn-benchmark)
|
| 4 |
+
|
| 5 |
+
Detectron2 addresses some legacy issues left in Detectron. As a result, their models
|
| 6 |
+
are not compatible:
|
| 7 |
+
running inference with the same model weights will produce different results in the two code bases.
|
| 8 |
+
|
| 9 |
+
The major differences regarding inference are:
|
| 10 |
+
|
| 11 |
+
- The height and width of a box with corners (x1, y1) and (x2, y2) is now computed more naturally as
|
| 12 |
+
width = x2 - x1 and height = y2 - y1;
|
| 13 |
+
In Detectron, a "+ 1" was added both height and width.
|
| 14 |
+
|
| 15 |
+
Note that the relevant ops in Caffe2 have [adopted this change of convention](https://github.com/pytorch/pytorch/pull/20550)
|
| 16 |
+
with an extra option.
|
| 17 |
+
So it is still possible to run inference with a Detectron2-trained model in Caffe2.
|
| 18 |
+
|
| 19 |
+
The change in height/width calculations most notably changes:
|
| 20 |
+
- encoding/decoding in bounding box regression.
|
| 21 |
+
- non-maximum suppression. The effect here is very negligible, though.
|
| 22 |
+
|
| 23 |
+
- RPN now uses simpler anchors with fewer quantization artifacts.
|
| 24 |
+
|
| 25 |
+
In Detectron, the anchors were quantized and
|
| 26 |
+
[do not have accurate areas](https://github.com/facebookresearch/Detectron/issues/227).
|
| 27 |
+
In Detectron2, the anchors are center-aligned to feature grid points and not quantized.
|
| 28 |
+
|
| 29 |
+
- Classification layers have a different ordering of class labels.
|
| 30 |
+
|
| 31 |
+
This involves any trainable parameter with shape (..., num_categories + 1, ...).
|
| 32 |
+
In Detectron2, integer labels [0, K-1] correspond to the K = num_categories object categories
|
| 33 |
+
and the label "K" corresponds to the special "background" category.
|
| 34 |
+
In Detectron, label "0" means background, and labels [1, K] correspond to the K categories.
|
| 35 |
+
|
| 36 |
+
- ROIAlign is implemented differently. The new implementation is [available in Caffe2](https://github.com/pytorch/pytorch/pull/23706).
|
| 37 |
+
|
| 38 |
+
1. All the ROIs are shifted by half a pixel compared to Detectron in order to create better image-feature-map alignment.
|
| 39 |
+
See `layers/roi_align.py` for details.
|
| 40 |
+
To enable the old behavior, use `ROIAlign(aligned=False)`, or `POOLER_TYPE=ROIAlign` instead of
|
| 41 |
+
`ROIAlignV2` (the default).
|
| 42 |
+
|
| 43 |
+
1. The ROIs are not required to have a minimum size of 1.
|
| 44 |
+
This will lead to tiny differences in the output, but should be negligible.
|
| 45 |
+
|
| 46 |
+
- Mask inference function is different.
|
| 47 |
+
|
| 48 |
+
In Detectron2, the "paste_mask" function is different and should be more accurate than in Detectron. This change
|
| 49 |
+
can improve mask AP on COCO by ~0.5% absolute.
|
| 50 |
+
|
| 51 |
+
There are some other differences in training as well, but they won't affect
|
| 52 |
+
model-level compatibility. The major ones are:
|
| 53 |
+
|
| 54 |
+
- We fixed a [bug](https://github.com/facebookresearch/Detectron/issues/459) in
|
| 55 |
+
Detectron, by making `RPN.POST_NMS_TOPK_TRAIN` per-image, rather than per-batch.
|
| 56 |
+
The fix may lead to a small accuracy drop for a few models (e.g. keypoint
|
| 57 |
+
detection) and will require some parameter tuning to match the Detectron results.
|
| 58 |
+
- For simplicity, we change the default loss in bounding box regression to L1 loss, instead of smooth L1 loss.
|
| 59 |
+
We have observed that this tends to slightly decrease box AP50 while improving box AP for higher
|
| 60 |
+
overlap thresholds (and leading to a slight overall improvement in box AP).
|
| 61 |
+
- We interpret the coordinates in COCO bounding box and segmentation annotations
|
| 62 |
+
as coordinates in range `[0, width]` or `[0, height]`. The coordinates in
|
| 63 |
+
COCO keypoint annotations are interpreted as pixel indices in range `[0, width - 1]` or `[0, height - 1]`.
|
| 64 |
+
Note that this affects how flip augmentation is implemented.
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
[This article](https://ppwwyyxx.com/blog/2021/Where-are-Pixels/)
|
| 68 |
+
explains more details on the above mentioned issues
|
| 69 |
+
about pixels, coordinates, and "+1"s.
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
## Compatibility with Caffe2
|
| 73 |
+
|
| 74 |
+
As mentioned above, despite the incompatibilities with Detectron, the relevant
|
| 75 |
+
ops have been implemented in Caffe2.
|
| 76 |
+
Therefore, models trained with detectron2 can be converted in Caffe2.
|
| 77 |
+
See [Deployment](../tutorials/deployment.md) for the tutorial.
|
| 78 |
+
|
| 79 |
+
## Compatibility with TensorFlow
|
| 80 |
+
|
| 81 |
+
Most ops are available in TensorFlow, although some tiny differences in
|
| 82 |
+
the implementation of resize / ROIAlign / padding need to be addressed.
|
| 83 |
+
A working conversion script is provided by [tensorpack Faster R-CNN](https://github.com/tensorpack/tensorpack/tree/master/examples/FasterRCNN/convert_d2)
|
| 84 |
+
to run a standard detectron2 model in TensorFlow.
|
approach/ovod/detectron2/docs/notes/contributing.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributing to detectron2
|
| 2 |
+
|
| 3 |
+
## Issues
|
| 4 |
+
We use GitHub issues to track public bugs and questions.
|
| 5 |
+
Please make sure to follow one of the
|
| 6 |
+
[issue templates](https://github.com/facebookresearch/detectron2/issues/new/choose)
|
| 7 |
+
when reporting any issues.
|
| 8 |
+
|
| 9 |
+
Facebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe
|
| 10 |
+
disclosure of security bugs. In those cases, please go through the process
|
| 11 |
+
outlined on that page and do not file a public issue.
|
| 12 |
+
|
| 13 |
+
## Pull Requests
|
| 14 |
+
We actively welcome pull requests.
|
| 15 |
+
|
| 16 |
+
However, if you're adding any significant features (e.g. > 50 lines), please
|
| 17 |
+
make sure to discuss with maintainers about your motivation and proposals in an issue
|
| 18 |
+
before sending a PR. This is to save your time so you don't spend time on a PR that we'll not accept.
|
| 19 |
+
|
| 20 |
+
We do not always accept new features, and we take the following
|
| 21 |
+
factors into consideration:
|
| 22 |
+
|
| 23 |
+
1. Whether the same feature can be achieved without modifying detectron2.
|
| 24 |
+
Detectron2 is designed so that you can implement many extensions from the outside, e.g.
|
| 25 |
+
those in [projects](https://github.com/facebookresearch/detectron2/tree/master/projects).
|
| 26 |
+
* If some part of detectron2 is not extensible enough, you can also bring up a more general issue to
|
| 27 |
+
improve it. Such feature request may be useful to more users.
|
| 28 |
+
2. Whether the feature is potentially useful to a large audience (e.g. an impactful detection paper, a popular dataset,
|
| 29 |
+
a significant speedup, a widely useful utility),
|
| 30 |
+
or only to a small portion of users (e.g., a less-known paper, an improvement not in the object
|
| 31 |
+
detection field, a trick that's not very popular in the community, code to handle a non-standard type of data)
|
| 32 |
+
* Adoption of additional models, datasets, new task are by default not added to detectron2 before they
|
| 33 |
+
receive significant popularity in the community.
|
| 34 |
+
We sometimes accept such features in `projects/`, or as a link in `projects/README.md`.
|
| 35 |
+
3. Whether the proposed solution has a good design / interface. This can be discussed in the issue prior to PRs, or
|
| 36 |
+
in the form of a draft PR.
|
| 37 |
+
4. Whether the proposed solution adds extra mental/practical overhead to users who don't
|
| 38 |
+
need such feature.
|
| 39 |
+
5. Whether the proposed solution breaks existing APIs.
|
| 40 |
+
|
| 41 |
+
To add a feature to an existing function/class `Func`, there are always two approaches:
|
| 42 |
+
(1) add new arguments to `Func`; (2) write a new `Func_with_new_feature`.
|
| 43 |
+
To meet the above criteria, we often prefer approach (2), because:
|
| 44 |
+
|
| 45 |
+
1. It does not involve modifying or potentially breaking existing code.
|
| 46 |
+
2. It does not add overhead to users who do not need the new feature.
|
| 47 |
+
3. Adding new arguments to a function/class is not scalable w.r.t. all the possible new research ideas in the future.
|
| 48 |
+
|
| 49 |
+
When sending a PR, please do:
|
| 50 |
+
|
| 51 |
+
1. If a PR contains multiple orthogonal changes, split it to several PRs.
|
| 52 |
+
2. If you've added code that should be tested, add tests.
|
| 53 |
+
3. For PRs that need experiments (e.g. adding a new model or new methods),
|
| 54 |
+
you don't need to update model zoo, but do provide experiment results in the description of the PR.
|
| 55 |
+
4. If APIs are changed, update the documentation.
|
| 56 |
+
5. We use the [Google style docstrings](https://www.sphinx-doc.org/en/master/usage/extensions/napoleon.html) in python.
|
| 57 |
+
6. Make sure your code lints with `./dev/linter.sh`.
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
## Contributor License Agreement ("CLA")
|
| 61 |
+
In order to accept your pull request, we need you to submit a CLA. You only need
|
| 62 |
+
to do this once to work on any of Facebook's open source projects.
|
| 63 |
+
|
| 64 |
+
Complete your CLA here: <https://code.facebook.com/cla>
|
| 65 |
+
|
| 66 |
+
## License
|
| 67 |
+
By contributing to detectron2, you agree that your contributions will be licensed
|
| 68 |
+
under the LICENSE file in the root directory of this source tree.
|
approach/ovod/detectron2/docs/notes/index.rst
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Notes
|
| 2 |
+
======================================
|
| 3 |
+
|
| 4 |
+
.. toctree::
|
| 5 |
+
:maxdepth: 2
|
| 6 |
+
|
| 7 |
+
benchmarks
|
| 8 |
+
compatibility
|
| 9 |
+
contributing
|
| 10 |
+
changelog
|
approach/ovod/detectron2/docs/requirements.txt
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
docutils==0.16
|
| 2 |
+
# https://github.com/sphinx-doc/sphinx/commit/7acd3ada3f38076af7b2b5c9f3b60bb9c2587a3d
|
| 3 |
+
sphinx==3.2.0
|
| 4 |
+
recommonmark==0.6.0
|
| 5 |
+
sphinx_rtd_theme
|
| 6 |
+
# Dependencies here are only those required by import
|
| 7 |
+
termcolor
|
| 8 |
+
numpy
|
| 9 |
+
tqdm
|
| 10 |
+
matplotlib
|
| 11 |
+
termcolor
|
| 12 |
+
yacs
|
| 13 |
+
tabulate
|
| 14 |
+
cloudpickle
|
| 15 |
+
Pillow
|
| 16 |
+
future
|
| 17 |
+
git+https://github.com/facebookresearch/fvcore.git
|
| 18 |
+
https://download.pytorch.org/whl/cpu/torch-1.8.1%2Bcpu-cp37-cp37m-linux_x86_64.whl
|
| 19 |
+
https://download.pytorch.org/whl/cpu/torchvision-0.9.1%2Bcpu-cp37-cp37m-linux_x86_64.whl
|
| 20 |
+
omegaconf>=2.1.0.dev24
|
| 21 |
+
hydra-core>=1.1.0.dev5
|
| 22 |
+
scipy
|
| 23 |
+
timm
|
| 24 |
+
fairscale
|
approach/ovod/detectron2/docs/tutorials/README.md
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Read the docs:
|
| 2 |
+
|
| 3 |
+
The latest documentation built from this directory is available at [detectron2.readthedocs.io](https://detectron2.readthedocs.io/).
|
| 4 |
+
Documents in this directory are not meant to be read on github.
|
approach/ovod/detectron2/docs/tutorials/builtin_datasets.md
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use Builtin Datasets
|
| 2 |
+
|
| 3 |
+
A dataset can be used by accessing [DatasetCatalog](https://detectron2.readthedocs.io/modules/data.html#detectron2.data.DatasetCatalog)
|
| 4 |
+
for its data, or [MetadataCatalog](https://detectron2.readthedocs.io/modules/data.html#detectron2.data.MetadataCatalog) for its metadata (class names, etc).
|
| 5 |
+
This document explains how to setup the builtin datasets so they can be used by the above APIs.
|
| 6 |
+
[Use Custom Datasets](https://detectron2.readthedocs.io/tutorials/datasets.html) gives a deeper dive on how to use `DatasetCatalog` and `MetadataCatalog`,
|
| 7 |
+
and how to add new datasets to them.
|
| 8 |
+
|
| 9 |
+
Detectron2 has builtin support for a few datasets.
|
| 10 |
+
The datasets are assumed to exist in a directory specified by the environment variable
|
| 11 |
+
`DETECTRON2_DATASETS`.
|
| 12 |
+
Under this directory, detectron2 will look for datasets in the structure described below, if needed.
|
| 13 |
+
```
|
| 14 |
+
$DETECTRON2_DATASETS/
|
| 15 |
+
coco/
|
| 16 |
+
lvis/
|
| 17 |
+
cityscapes/
|
| 18 |
+
VOC20{07,12}/
|
| 19 |
+
```
|
| 20 |
+
|
| 21 |
+
You can set the location for builtin datasets by `export DETECTRON2_DATASETS=/path/to/datasets`.
|
| 22 |
+
If left unset, the default is `./datasets` relative to your current working directory.
|
| 23 |
+
|
| 24 |
+
The [model zoo](https://github.com/facebookresearch/detectron2/blob/master/MODEL_ZOO.md)
|
| 25 |
+
contains configs and models that use these builtin datasets.
|
| 26 |
+
|
| 27 |
+
## Expected dataset structure for [COCO instance/keypoint detection](https://cocodataset.org/#download):
|
| 28 |
+
|
| 29 |
+
```
|
| 30 |
+
coco/
|
| 31 |
+
annotations/
|
| 32 |
+
instances_{train,val}2017.json
|
| 33 |
+
person_keypoints_{train,val}2017.json
|
| 34 |
+
{train,val}2017/
|
| 35 |
+
# image files that are mentioned in the corresponding json
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
You can use the 2014 version of the dataset as well.
|
| 39 |
+
|
| 40 |
+
Some of the builtin tests (`dev/run_*_tests.sh`) uses a tiny version of the COCO dataset,
|
| 41 |
+
which you can download with `./datasets/prepare_for_tests.sh`.
|
| 42 |
+
|
| 43 |
+
## Expected dataset structure for PanopticFPN:
|
| 44 |
+
|
| 45 |
+
Extract panoptic annotations from [COCO website](https://cocodataset.org/#download)
|
| 46 |
+
into the following structure:
|
| 47 |
+
```
|
| 48 |
+
coco/
|
| 49 |
+
annotations/
|
| 50 |
+
panoptic_{train,val}2017.json
|
| 51 |
+
panoptic_{train,val}2017/ # png annotations
|
| 52 |
+
panoptic_stuff_{train,val}2017/ # generated by the script mentioned below
|
| 53 |
+
```
|
| 54 |
+
|
| 55 |
+
Install panopticapi by:
|
| 56 |
+
```
|
| 57 |
+
pip install git+https://github.com/cocodataset/panopticapi.git
|
| 58 |
+
```
|
| 59 |
+
Then, run `python datasets/prepare_panoptic_fpn.py`, to extract semantic annotations from panoptic annotations.
|
| 60 |
+
|
| 61 |
+
## Expected dataset structure for [LVIS instance segmentation](https://www.lvisdataset.org/dataset):
|
| 62 |
+
```
|
| 63 |
+
coco/
|
| 64 |
+
{train,val,test}2017/
|
| 65 |
+
lvis/
|
| 66 |
+
lvis_v0.5_{train,val}.json
|
| 67 |
+
lvis_v0.5_image_info_test.json
|
| 68 |
+
lvis_v1_{train,val}.json
|
| 69 |
+
lvis_v1_image_info_test{,_challenge}.json
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
Install lvis-api by:
|
| 73 |
+
```
|
| 74 |
+
pip install git+https://github.com/lvis-dataset/lvis-api.git
|
| 75 |
+
```
|
| 76 |
+
|
| 77 |
+
To evaluate models trained on the COCO dataset using LVIS annotations,
|
| 78 |
+
run `python datasets/prepare_cocofied_lvis.py` to prepare "cocofied" LVIS annotations.
|
| 79 |
+
|
| 80 |
+
## Expected dataset structure for [cityscapes](https://www.cityscapes-dataset.com/downloads/):
|
| 81 |
+
```
|
| 82 |
+
cityscapes/
|
| 83 |
+
gtFine/
|
| 84 |
+
train/
|
| 85 |
+
aachen/
|
| 86 |
+
color.png, instanceIds.png, labelIds.png, polygons.json,
|
| 87 |
+
labelTrainIds.png
|
| 88 |
+
...
|
| 89 |
+
val/
|
| 90 |
+
test/
|
| 91 |
+
# below are generated Cityscapes panoptic annotation
|
| 92 |
+
cityscapes_panoptic_train.json
|
| 93 |
+
cityscapes_panoptic_train/
|
| 94 |
+
cityscapes_panoptic_val.json
|
| 95 |
+
cityscapes_panoptic_val/
|
| 96 |
+
cityscapes_panoptic_test.json
|
| 97 |
+
cityscapes_panoptic_test/
|
| 98 |
+
leftImg8bit/
|
| 99 |
+
train/
|
| 100 |
+
val/
|
| 101 |
+
test/
|
| 102 |
+
```
|
| 103 |
+
Install cityscapes scripts by:
|
| 104 |
+
```
|
| 105 |
+
pip install git+https://github.com/mcordts/cityscapesScripts.git
|
| 106 |
+
```
|
| 107 |
+
|
| 108 |
+
Note: to create labelTrainIds.png, first prepare the above structure, then run cityscapesescript with:
|
| 109 |
+
```
|
| 110 |
+
CITYSCAPES_DATASET=/path/to/abovementioned/cityscapes python cityscapesscripts/preparation/createTrainIdLabelImgs.py
|
| 111 |
+
```
|
| 112 |
+
These files are not needed for instance segmentation.
|
| 113 |
+
|
| 114 |
+
Note: to generate Cityscapes panoptic dataset, run cityscapesescript with:
|
| 115 |
+
```
|
| 116 |
+
CITYSCAPES_DATASET=/path/to/abovementioned/cityscapes python cityscapesscripts/preparation/createPanopticImgs.py
|
| 117 |
+
```
|
| 118 |
+
These files are not needed for semantic and instance segmentation.
|
| 119 |
+
|
| 120 |
+
## Expected dataset structure for [Pascal VOC](http://host.robots.ox.ac.uk/pascal/VOC/index.html):
|
| 121 |
+
```
|
| 122 |
+
VOC20{07,12}/
|
| 123 |
+
Annotations/
|
| 124 |
+
ImageSets/
|
| 125 |
+
Main/
|
| 126 |
+
trainval.txt
|
| 127 |
+
test.txt
|
| 128 |
+
# train.txt or val.txt, if you use these splits
|
| 129 |
+
JPEGImages/
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
## Expected dataset structure for [ADE20k Scene Parsing](http://sceneparsing.csail.mit.edu/):
|
| 133 |
+
```
|
| 134 |
+
ADEChallengeData2016/
|
| 135 |
+
annotations/
|
| 136 |
+
annotations_detectron2/
|
| 137 |
+
images/
|
| 138 |
+
objectInfo150.txt
|
| 139 |
+
```
|
| 140 |
+
The directory `annotations_detectron2` is generated by running `python datasets/prepare_ade20k_sem_seg.py`.
|
approach/ovod/detectron2/docs/tutorials/configs.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Yacs Configs
|
| 2 |
+
|
| 3 |
+
Detectron2 provides a key-value based config system that can be
|
| 4 |
+
used to obtain standard, common behaviors.
|
| 5 |
+
|
| 6 |
+
This system uses YAML and [yacs](https://github.com/rbgirshick/yacs).
|
| 7 |
+
Yaml is a very limited language,
|
| 8 |
+
so we do not expect all features in detectron2 to be available through configs.
|
| 9 |
+
If you need something that's not available in the config space,
|
| 10 |
+
please write code using detectron2's API.
|
| 11 |
+
|
| 12 |
+
With the introduction of a more powerful [LazyConfig system](lazyconfigs.md),
|
| 13 |
+
we no longer add functionality / new keys to the Yacs/Yaml-based config system.
|
| 14 |
+
|
| 15 |
+
### Basic Usage
|
| 16 |
+
|
| 17 |
+
Some basic usage of the `CfgNode` object is shown here. See more in [documentation](../modules/config.html#detectron2.config.CfgNode).
|
| 18 |
+
```python
|
| 19 |
+
from detectron2.config import get_cfg
|
| 20 |
+
cfg = get_cfg() # obtain detectron2's default config
|
| 21 |
+
cfg.xxx = yyy # add new configs for your own custom components
|
| 22 |
+
cfg.merge_from_file("my_cfg.yaml") # load values from a file
|
| 23 |
+
|
| 24 |
+
cfg.merge_from_list(["MODEL.WEIGHTS", "weights.pth"]) # can also load values from a list of str
|
| 25 |
+
print(cfg.dump()) # print formatted configs
|
| 26 |
+
with open("output.yaml", "w") as f:
|
| 27 |
+
f.write(cfg.dump()) # save config to file
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
In addition to the basic Yaml syntax, the config file can
|
| 31 |
+
define a `_BASE_: base.yaml` field, which will load a base config file first.
|
| 32 |
+
Values in the base config will be overwritten in sub-configs, if there are any conflicts.
|
| 33 |
+
We provided several base configs for standard model architectures.
|
| 34 |
+
|
| 35 |
+
Many builtin tools in detectron2 accept command line config overwrite:
|
| 36 |
+
Key-value pairs provided in the command line will overwrite the existing values in the config file.
|
| 37 |
+
For example, [demo.py](../../demo/demo.py) can be used with
|
| 38 |
+
```sh
|
| 39 |
+
./demo.py --config-file config.yaml [--other-options] \
|
| 40 |
+
--opts MODEL.WEIGHTS /path/to/weights INPUT.MIN_SIZE_TEST 1000
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
To see a list of available configs in detectron2 and what they mean,
|
| 44 |
+
check [Config References](../modules/config.html#config-references)
|
| 45 |
+
|
| 46 |
+
### Configs in Projects
|
| 47 |
+
|
| 48 |
+
A project that lives outside the detectron2 library may define its own configs, which will need to be added
|
| 49 |
+
for the project to be functional, e.g.:
|
| 50 |
+
```python
|
| 51 |
+
from detectron2.projects.point_rend import add_pointrend_config
|
| 52 |
+
cfg = get_cfg() # obtain detectron2's default config
|
| 53 |
+
add_pointrend_config(cfg) # add pointrend's default config
|
| 54 |
+
# ... ...
|
| 55 |
+
```
|
| 56 |
+
|
| 57 |
+
### Best Practice with Configs
|
| 58 |
+
|
| 59 |
+
1. Treat the configs you write as "code": avoid copying them or duplicating them; use `_BASE_`
|
| 60 |
+
to share common parts between configs.
|
| 61 |
+
|
| 62 |
+
2. Keep the configs you write simple: don't include keys that do not affect the experimental setting.
|
approach/ovod/detectron2/docs/tutorials/data_loading.md
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
# Dataloader
|
| 3 |
+
|
| 4 |
+
Dataloader is the component that provides data to models.
|
| 5 |
+
A dataloader usually (but not necessarily) takes raw information from [datasets](./datasets.md),
|
| 6 |
+
and process them into a format needed by the model.
|
| 7 |
+
|
| 8 |
+
## How the Existing Dataloader Works
|
| 9 |
+
|
| 10 |
+
Detectron2 contains a builtin data loading pipeline.
|
| 11 |
+
It's good to understand how it works, in case you need to write a custom one.
|
| 12 |
+
|
| 13 |
+
Detectron2 provides two functions
|
| 14 |
+
[build_detection_{train,test}_loader](../modules/data.html#detectron2.data.build_detection_train_loader)
|
| 15 |
+
that create a default data loader from a given config.
|
| 16 |
+
Here is how `build_detection_{train,test}_loader` work:
|
| 17 |
+
|
| 18 |
+
1. It takes the name of a registered dataset (e.g., "coco_2017_train") and loads a `list[dict]` representing the dataset items
|
| 19 |
+
in a lightweight format. These dataset items are not yet ready to be used by the model (e.g., images are
|
| 20 |
+
not loaded into memory, random augmentations have not been applied, etc.).
|
| 21 |
+
Details about the dataset format and dataset registration can be found in
|
| 22 |
+
[datasets](./datasets.md).
|
| 23 |
+
2. Each dict in this list is mapped by a function ("mapper"):
|
| 24 |
+
* Users can customize this mapping function by specifying the "mapper" argument in
|
| 25 |
+
`build_detection_{train,test}_loader`. The default mapper is [DatasetMapper](../modules/data.html#detectron2.data.DatasetMapper).
|
| 26 |
+
* The output format of the mapper can be arbitrary, as long as it is accepted by the consumer of this data loader (usually the model).
|
| 27 |
+
The outputs of the default mapper, after batching, follow the default model input format documented in
|
| 28 |
+
[Use Models](./models.html#model-input-format).
|
| 29 |
+
* The role of the mapper is to transform the lightweight representation of a dataset item into a format
|
| 30 |
+
that is ready for the model to consume (including, e.g., read images, perform random data augmentation and convert to torch Tensors).
|
| 31 |
+
If you would like to perform custom transformations to data, you often want a custom mapper.
|
| 32 |
+
3. The outputs of the mapper are batched (simply into a list).
|
| 33 |
+
4. This batched data is the output of the data loader. Typically, it's also the input of
|
| 34 |
+
`model.forward()`.
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
## Write a Custom Dataloader
|
| 38 |
+
|
| 39 |
+
Using a different "mapper" with `build_detection_{train,test}_loader(mapper=)` works for most use cases
|
| 40 |
+
of custom data loading.
|
| 41 |
+
For example, if you want to resize all images to a fixed size for training, use:
|
| 42 |
+
|
| 43 |
+
```python
|
| 44 |
+
import detectron2.data.transforms as T
|
| 45 |
+
from detectron2.data import DatasetMapper # the default mapper
|
| 46 |
+
dataloader = build_detection_train_loader(cfg,
|
| 47 |
+
mapper=DatasetMapper(cfg, is_train=True, augmentations=[
|
| 48 |
+
T.Resize((800, 800))
|
| 49 |
+
]))
|
| 50 |
+
# use this dataloader instead of the default
|
| 51 |
+
```
|
| 52 |
+
If the arguments of the default [DatasetMapper](../modules/data.html#detectron2.data.DatasetMapper)
|
| 53 |
+
does not provide what you need, you may write a custom mapper function and use it instead, e.g.:
|
| 54 |
+
|
| 55 |
+
```python
|
| 56 |
+
from detectron2.data import detection_utils as utils
|
| 57 |
+
# Show how to implement a minimal mapper, similar to the default DatasetMapper
|
| 58 |
+
def mapper(dataset_dict):
|
| 59 |
+
dataset_dict = copy.deepcopy(dataset_dict) # it will be modified by code below
|
| 60 |
+
# can use other ways to read image
|
| 61 |
+
image = utils.read_image(dataset_dict["file_name"], format="BGR")
|
| 62 |
+
# See "Data Augmentation" tutorial for details usage
|
| 63 |
+
auginput = T.AugInput(image)
|
| 64 |
+
transform = T.Resize((800, 800))(auginput)
|
| 65 |
+
image = torch.from_numpy(auginput.image.transpose(2, 0, 1))
|
| 66 |
+
annos = [
|
| 67 |
+
utils.transform_instance_annotations(annotation, [transform], image.shape[1:])
|
| 68 |
+
for annotation in dataset_dict.pop("annotations")
|
| 69 |
+
]
|
| 70 |
+
return {
|
| 71 |
+
# create the format that the model expects
|
| 72 |
+
"image": image,
|
| 73 |
+
"instances": utils.annotations_to_instances(annos, image.shape[1:])
|
| 74 |
+
}
|
| 75 |
+
dataloader = build_detection_train_loader(cfg, mapper=mapper)
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
If you want to change not only the mapper (e.g., in order to implement different sampling or batching logic),
|
| 79 |
+
`build_detection_train_loader` won't work and you will need to write a different data loader.
|
| 80 |
+
The data loader is simply a
|
| 81 |
+
python iterator that produces [the format](./models.md) that the model accepts.
|
| 82 |
+
You can implement it using any tools you like.
|
| 83 |
+
|
| 84 |
+
No matter what to implement, it's recommended to
|
| 85 |
+
check out [API documentation of detectron2.data](../modules/data) to learn more about the APIs of
|
| 86 |
+
these functions.
|
| 87 |
+
|
| 88 |
+
## Use a Custom Dataloader
|
| 89 |
+
|
| 90 |
+
If you use [DefaultTrainer](../modules/engine.html#detectron2.engine.defaults.DefaultTrainer),
|
| 91 |
+
you can overwrite its `build_{train,test}_loader` method to use your own dataloader.
|
| 92 |
+
See the [deeplab dataloader](../../projects/DeepLab/train_net.py)
|
| 93 |
+
for an example.
|
| 94 |
+
|
| 95 |
+
If you write your own training loop, you can plug in your data loader easily.
|
approach/ovod/detectron2/docs/tutorials/datasets.md
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use Custom Datasets
|
| 2 |
+
|
| 3 |
+
This document explains how the dataset APIs
|
| 4 |
+
([DatasetCatalog](../modules/data.html#detectron2.data.DatasetCatalog), [MetadataCatalog](../modules/data.html#detectron2.data.MetadataCatalog))
|
| 5 |
+
work, and how to use them to add custom datasets.
|
| 6 |
+
|
| 7 |
+
Datasets that have builtin support in detectron2 are listed in [builtin datasets](builtin_datasets.md).
|
| 8 |
+
If you want to use a custom dataset while also reusing detectron2's data loaders,
|
| 9 |
+
you will need to:
|
| 10 |
+
|
| 11 |
+
1. __Register__ your dataset (i.e., tell detectron2 how to obtain your dataset).
|
| 12 |
+
2. Optionally, __register metadata__ for your dataset.
|
| 13 |
+
|
| 14 |
+
Next, we explain the above two concepts in detail.
|
| 15 |
+
|
| 16 |
+
The [Colab tutorial](https://colab.research.google.com/drive/16jcaJoc6bCFAQ96jDe2HwtXj7BMD_-m5)
|
| 17 |
+
has a live example of how to register and train on a dataset of custom formats.
|
| 18 |
+
|
| 19 |
+
### Register a Dataset
|
| 20 |
+
|
| 21 |
+
To let detectron2 know how to obtain a dataset named "my_dataset", users need to implement
|
| 22 |
+
a function that returns the items in your dataset and then tell detectron2 about this
|
| 23 |
+
function:
|
| 24 |
+
```python
|
| 25 |
+
def my_dataset_function():
|
| 26 |
+
...
|
| 27 |
+
return list[dict] in the following format
|
| 28 |
+
|
| 29 |
+
from detectron2.data import DatasetCatalog
|
| 30 |
+
DatasetCatalog.register("my_dataset", my_dataset_function)
|
| 31 |
+
# later, to access the data:
|
| 32 |
+
data: List[Dict] = DatasetCatalog.get("my_dataset")
|
| 33 |
+
```
|
| 34 |
+
|
| 35 |
+
Here, the snippet associates a dataset named "my_dataset" with a function that returns the data.
|
| 36 |
+
The function must return the same data (with same order) if called multiple times.
|
| 37 |
+
The registration stays effective until the process exits.
|
| 38 |
+
|
| 39 |
+
The function can do arbitrary things and should return the data in `list[dict]`, each dict in either
|
| 40 |
+
of the following formats:
|
| 41 |
+
1. Detectron2's standard dataset dict, described below. This will make it work with many other builtin
|
| 42 |
+
features in detectron2, so it's recommended to use it when it's sufficient.
|
| 43 |
+
2. Any custom format. You can also return arbitrary dicts in your own format,
|
| 44 |
+
such as adding extra keys for new tasks.
|
| 45 |
+
Then you will need to handle them properly downstream as well.
|
| 46 |
+
See below for more details.
|
| 47 |
+
|
| 48 |
+
#### Standard Dataset Dicts
|
| 49 |
+
|
| 50 |
+
For standard tasks
|
| 51 |
+
(instance detection, instance/semantic/panoptic segmentation, keypoint detection),
|
| 52 |
+
we load the original dataset into `list[dict]` with a specification similar to COCO's annotations.
|
| 53 |
+
This is our standard representation for a dataset.
|
| 54 |
+
|
| 55 |
+
Each dict contains information about one image.
|
| 56 |
+
The dict may have the following fields,
|
| 57 |
+
and the required fields vary based on what the dataloader or the task needs (see more below).
|
| 58 |
+
|
| 59 |
+
```eval_rst
|
| 60 |
+
.. list-table::
|
| 61 |
+
:header-rows: 1
|
| 62 |
+
|
| 63 |
+
* - Task
|
| 64 |
+
- Fields
|
| 65 |
+
* - Common
|
| 66 |
+
- file_name, height, width, image_id
|
| 67 |
+
|
| 68 |
+
* - Instance detection/segmentation
|
| 69 |
+
- annotations
|
| 70 |
+
|
| 71 |
+
* - Semantic segmentation
|
| 72 |
+
- sem_seg_file_name
|
| 73 |
+
|
| 74 |
+
* - Panoptic segmentation
|
| 75 |
+
- pan_seg_file_name, segments_info
|
| 76 |
+
```
|
| 77 |
+
|
| 78 |
+
+ `file_name`: the full path to the image file.
|
| 79 |
+
+ `height`, `width`: integer. The shape of the image.
|
| 80 |
+
+ `image_id` (str or int): a unique id that identifies this image. Required by many
|
| 81 |
+
evaluators to identify the images, but a dataset may use it for different purposes.
|
| 82 |
+
+ `annotations` (list[dict]): Required by __instance detection/segmentation or keypoint detection__ tasks.
|
| 83 |
+
Each dict corresponds to annotations of one instance in this image, and
|
| 84 |
+
may contain the following keys:
|
| 85 |
+
+ `bbox` (list[float], required): list of 4 numbers representing the bounding box of the instance.
|
| 86 |
+
+ `bbox_mode` (int, required): the format of bbox. It must be a member of
|
| 87 |
+
[structures.BoxMode](../modules/structures.html#detectron2.structures.BoxMode).
|
| 88 |
+
Currently supports: `BoxMode.XYXY_ABS`, `BoxMode.XYWH_ABS`.
|
| 89 |
+
+ `category_id` (int, required): an integer in the range [0, num_categories-1] representing the category label.
|
| 90 |
+
The value num_categories is reserved to represent the "background" category, if applicable.
|
| 91 |
+
+ `segmentation` (list[list[float]] or dict): the segmentation mask of the instance.
|
| 92 |
+
+ If `list[list[float]]`, it represents a list of polygons, one for each connected component
|
| 93 |
+
of the object. Each `list[float]` is one simple polygon in the format of `[x1, y1, ..., xn, yn]` (n≥3).
|
| 94 |
+
The Xs and Ys are absolute coordinates in unit of pixels.
|
| 95 |
+
+ If `dict`, it represents the per-pixel segmentation mask in COCO's compressed RLE format.
|
| 96 |
+
The dict should have keys "size" and "counts". You can convert a uint8 segmentation mask of 0s and
|
| 97 |
+
1s into such dict by `pycocotools.mask.encode(np.asarray(mask, order="F"))`.
|
| 98 |
+
`cfg.INPUT.MASK_FORMAT` must be set to `bitmask` if using the default data loader with such format.
|
| 99 |
+
+ `keypoints` (list[float]): in the format of [x1, y1, v1,..., xn, yn, vn].
|
| 100 |
+
v[i] means the [visibility](http://cocodataset.org/#format-data) of this keypoint.
|
| 101 |
+
`n` must be equal to the number of keypoint categories.
|
| 102 |
+
The Xs and Ys are absolute real-value coordinates in range [0, W or H].
|
| 103 |
+
|
| 104 |
+
(Note that the keypoint coordinates in COCO format are integers in range [0, W-1 or H-1], which is different
|
| 105 |
+
from our standard format. Detectron2 adds 0.5 to COCO keypoint coordinates to convert them from discrete
|
| 106 |
+
pixel indices to floating point coordinates.)
|
| 107 |
+
+ `iscrowd`: 0 (default) or 1. Whether this instance is labeled as COCO's "crowd
|
| 108 |
+
region". Don't include this field if you don't know what it means.
|
| 109 |
+
|
| 110 |
+
If `annotations` is an empty list, it means the image is labeled to have no objects.
|
| 111 |
+
Such images will by default be removed from training,
|
| 112 |
+
but can be included using `DATALOADER.FILTER_EMPTY_ANNOTATIONS`.
|
| 113 |
+
|
| 114 |
+
+ `sem_seg_file_name` (str):
|
| 115 |
+
The full path to the semantic segmentation ground truth file.
|
| 116 |
+
It should be a grayscale image whose pixel values are integer labels.
|
| 117 |
+
+ `pan_seg_file_name` (str):
|
| 118 |
+
The full path to panoptic segmentation ground truth file.
|
| 119 |
+
It should be an RGB image whose pixel values are integer ids encoded using the
|
| 120 |
+
[panopticapi.utils.id2rgb](https://github.com/cocodataset/panopticapi/) function.
|
| 121 |
+
The ids are defined by `segments_info`.
|
| 122 |
+
If an id does not appear in `segments_info`, the pixel is considered unlabeled
|
| 123 |
+
and is usually ignored in training & evaluation.
|
| 124 |
+
+ `segments_info` (list[dict]): defines the meaning of each id in panoptic segmentation ground truth.
|
| 125 |
+
Each dict has the following keys:
|
| 126 |
+
+ `id` (int): integer that appears in the ground truth image.
|
| 127 |
+
+ `category_id` (int): an integer in the range [0, num_categories-1] representing the category label.
|
| 128 |
+
+ `iscrowd`: 0 (default) or 1. Whether this instance is labeled as COCO's "crowd region".
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
```eval_rst
|
| 132 |
+
|
| 133 |
+
.. note::
|
| 134 |
+
|
| 135 |
+
The PanopticFPN model does not use the panoptic segmentation
|
| 136 |
+
format defined here, but a combination of both instance segmentation and semantic segmentation data
|
| 137 |
+
format. See :doc:`builtin_datasets` for instructions on COCO.
|
| 138 |
+
|
| 139 |
+
```
|
| 140 |
+
|
| 141 |
+
Fast R-CNN (with pre-computed proposals) models are rarely used today.
|
| 142 |
+
To train a Fast R-CNN, the following extra keys are needed:
|
| 143 |
+
|
| 144 |
+
+ `proposal_boxes` (array): 2D numpy array with shape (K, 4) representing K precomputed proposal boxes for this image.
|
| 145 |
+
+ `proposal_objectness_logits` (array): numpy array with shape (K, ), which corresponds to the objectness
|
| 146 |
+
logits of proposals in 'proposal_boxes'.
|
| 147 |
+
+ `proposal_bbox_mode` (int): the format of the precomputed proposal bbox.
|
| 148 |
+
It must be a member of
|
| 149 |
+
[structures.BoxMode](../modules/structures.html#detectron2.structures.BoxMode).
|
| 150 |
+
Default is `BoxMode.XYXY_ABS`.
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
#### Custom Dataset Dicts for New Tasks
|
| 155 |
+
|
| 156 |
+
In the `list[dict]` that your dataset function returns, the dictionary can also have __arbitrary custom data__.
|
| 157 |
+
This will be useful for a new task that needs extra information not covered
|
| 158 |
+
by the standard dataset dicts. In this case, you need to make sure the downstream code can handle your data
|
| 159 |
+
correctly. Usually this requires writing a new `mapper` for the dataloader (see [Use Custom Dataloaders](./data_loading.md)).
|
| 160 |
+
|
| 161 |
+
When designing a custom format, note that all dicts are stored in memory
|
| 162 |
+
(sometimes serialized and with multiple copies).
|
| 163 |
+
To save memory, each dict is meant to contain __small__ but sufficient information
|
| 164 |
+
about each sample, such as file names and annotations.
|
| 165 |
+
Loading full samples typically happens in the data loader.
|
| 166 |
+
|
| 167 |
+
For attributes shared among the entire dataset, use `Metadata` (see below).
|
| 168 |
+
To avoid extra memory, do not save such information inside each sample.
|
| 169 |
+
|
| 170 |
+
### "Metadata" for Datasets
|
| 171 |
+
|
| 172 |
+
Each dataset is associated with some metadata, accessible through
|
| 173 |
+
`MetadataCatalog.get(dataset_name).some_metadata`.
|
| 174 |
+
Metadata is a key-value mapping that contains information that's shared among
|
| 175 |
+
the entire dataset, and usually is used to interpret what's in the dataset, e.g.,
|
| 176 |
+
names of classes, colors of classes, root of files, etc.
|
| 177 |
+
This information will be useful for augmentation, evaluation, visualization, logging, etc.
|
| 178 |
+
The structure of metadata depends on what is needed from the corresponding downstream code.
|
| 179 |
+
|
| 180 |
+
If you register a new dataset through `DatasetCatalog.register`,
|
| 181 |
+
you may also want to add its corresponding metadata through
|
| 182 |
+
`MetadataCatalog.get(dataset_name).some_key = some_value`, to enable any features that need the metadata.
|
| 183 |
+
You can do it like this (using the metadata key "thing_classes" as an example):
|
| 184 |
+
|
| 185 |
+
```python
|
| 186 |
+
from detectron2.data import MetadataCatalog
|
| 187 |
+
MetadataCatalog.get("my_dataset").thing_classes = ["person", "dog"]
|
| 188 |
+
```
|
| 189 |
+
|
| 190 |
+
Here is a list of metadata keys that are used by builtin features in detectron2.
|
| 191 |
+
If you add your own dataset without these metadata, some features may be
|
| 192 |
+
unavailable to you:
|
| 193 |
+
|
| 194 |
+
* `thing_classes` (list[str]): Used by all instance detection/segmentation tasks.
|
| 195 |
+
A list of names for each instance/thing category.
|
| 196 |
+
If you load a COCO format dataset, it will be automatically set by the function `load_coco_json`.
|
| 197 |
+
|
| 198 |
+
* `thing_colors` (list[tuple(r, g, b)]): Pre-defined color (in [0, 255]) for each thing category.
|
| 199 |
+
Used for visualization. If not given, random colors will be used.
|
| 200 |
+
|
| 201 |
+
* `stuff_classes` (list[str]): Used by semantic and panoptic segmentation tasks.
|
| 202 |
+
A list of names for each stuff category.
|
| 203 |
+
|
| 204 |
+
* `stuff_colors` (list[tuple(r, g, b)]): Pre-defined color (in [0, 255]) for each stuff category.
|
| 205 |
+
Used for visualization. If not given, random colors are used.
|
| 206 |
+
|
| 207 |
+
* `ignore_label` (int): Used by semantic and panoptic segmentation tasks. Pixels in ground-truth
|
| 208 |
+
annotations with this category label should be ignored in evaluation. Typically these are "unlabeled"
|
| 209 |
+
pixels.
|
| 210 |
+
|
| 211 |
+
* `keypoint_names` (list[str]): Used by keypoint detection. A list of names for each keypoint.
|
| 212 |
+
|
| 213 |
+
* `keypoint_flip_map` (list[tuple[str]]): Used by keypoint detection. A list of pairs of names,
|
| 214 |
+
where each pair are the two keypoints that should be flipped if the image is
|
| 215 |
+
flipped horizontally during augmentation.
|
| 216 |
+
* `keypoint_connection_rules`: list[tuple(str, str, (r, g, b))]. Each tuple specifies a pair of keypoints
|
| 217 |
+
that are connected and the color (in [0, 255]) to use for the line between them when visualized.
|
| 218 |
+
|
| 219 |
+
Some additional metadata that are specific to the evaluation of certain datasets (e.g. COCO):
|
| 220 |
+
|
| 221 |
+
* `thing_dataset_id_to_contiguous_id` (dict[int->int]): Used by all instance detection/segmentation tasks in the COCO format.
|
| 222 |
+
A mapping from instance class ids in the dataset to contiguous ids in range [0, #class).
|
| 223 |
+
Will be automatically set by the function `load_coco_json`.
|
| 224 |
+
|
| 225 |
+
* `stuff_dataset_id_to_contiguous_id` (dict[int->int]): Used when generating prediction json files for
|
| 226 |
+
semantic/panoptic segmentation.
|
| 227 |
+
A mapping from semantic segmentation class ids in the dataset
|
| 228 |
+
to contiguous ids in [0, num_categories). It is useful for evaluation only.
|
| 229 |
+
|
| 230 |
+
* `json_file`: The COCO annotation json file. Used by COCO evaluation for COCO-format datasets.
|
| 231 |
+
* `panoptic_root`, `panoptic_json`: Used by COCO-format panoptic evaluation.
|
| 232 |
+
* `evaluator_type`: Used by the builtin main training script to select
|
| 233 |
+
evaluator. Don't use it in a new training script.
|
| 234 |
+
You can just provide the [DatasetEvaluator](../modules/evaluation.html#detectron2.evaluation.DatasetEvaluator)
|
| 235 |
+
for your dataset directly in your main script.
|
| 236 |
+
|
| 237 |
+
```eval_rst
|
| 238 |
+
.. note::
|
| 239 |
+
|
| 240 |
+
In recognition, sometimes we use the term "thing" for instance-level tasks,
|
| 241 |
+
and "stuff" for semantic segmentation tasks.
|
| 242 |
+
Both are used in panoptic segmentation tasks.
|
| 243 |
+
For background on the concept of "thing" and "stuff", see
|
| 244 |
+
`On Seeing Stuff: The Perception of Materials by Humans and Machines
|
| 245 |
+
<http://persci.mit.edu/pub_pdfs/adelson_spie_01.pdf>`_.
|
| 246 |
+
```
|
| 247 |
+
|
| 248 |
+
### Register a COCO Format Dataset
|
| 249 |
+
|
| 250 |
+
If your instance-level (detection, segmentation, keypoint) dataset is already a json file in the COCO format,
|
| 251 |
+
the dataset and its associated metadata can be registered easily with:
|
| 252 |
+
```python
|
| 253 |
+
from detectron2.data.datasets import register_coco_instances
|
| 254 |
+
register_coco_instances("my_dataset", {}, "json_annotation.json", "path/to/image/dir")
|
| 255 |
+
```
|
| 256 |
+
|
| 257 |
+
If your dataset is in COCO format but need to be further processed, or has extra custom per-instance annotations,
|
| 258 |
+
the [load_coco_json](../modules/data.html#detectron2.data.datasets.load_coco_json)
|
| 259 |
+
function might be useful.
|
| 260 |
+
|
| 261 |
+
### Update the Config for New Datasets
|
| 262 |
+
|
| 263 |
+
Once you've registered the dataset, you can use the name of the dataset (e.g., "my_dataset" in
|
| 264 |
+
example above) in `cfg.DATASETS.{TRAIN,TEST}`.
|
| 265 |
+
There are other configs you might want to change to train or evaluate on new datasets:
|
| 266 |
+
|
| 267 |
+
* `MODEL.ROI_HEADS.NUM_CLASSES` and `MODEL.RETINANET.NUM_CLASSES` are the number of thing classes
|
| 268 |
+
for R-CNN and RetinaNet models, respectively.
|
| 269 |
+
* `MODEL.ROI_KEYPOINT_HEAD.NUM_KEYPOINTS` sets the number of keypoints for Keypoint R-CNN.
|
| 270 |
+
You'll also need to set [Keypoint OKS](http://cocodataset.org/#keypoints-eval)
|
| 271 |
+
with `TEST.KEYPOINT_OKS_SIGMAS` for evaluation.
|
| 272 |
+
* `MODEL.SEM_SEG_HEAD.NUM_CLASSES` sets the number of stuff classes for Semantic FPN & Panoptic FPN.
|
| 273 |
+
* `TEST.DETECTIONS_PER_IMAGE` controls the maximum number of objects to be detected.
|
| 274 |
+
Set it to a larger number if test images may contain >100 objects.
|
| 275 |
+
* If you're training Fast R-CNN (with precomputed proposals), `DATASETS.PROPOSAL_FILES_{TRAIN,TEST}`
|
| 276 |
+
need to match the datasets. The format of proposal files are documented
|
| 277 |
+
[here](../modules/data.html#detectron2.data.load_proposals_into_dataset).
|
| 278 |
+
|
| 279 |
+
New models
|
| 280 |
+
(e.g. [TensorMask](../../projects/TensorMask),
|
| 281 |
+
[PointRend](../../projects/PointRend))
|
| 282 |
+
often have similar configs of their own that need to be changed as well.
|
| 283 |
+
|
| 284 |
+
```eval_rst
|
| 285 |
+
.. tip::
|
| 286 |
+
|
| 287 |
+
After changing the number of classes, certain layers in a pre-trained model will become incompatible
|
| 288 |
+
and therefore cannot be loaded to the new model.
|
| 289 |
+
This is expected, and loading such pre-trained models will produce warnings about such layers.
|
| 290 |
+
```
|
approach/ovod/detectron2/docs/tutorials/deployment.md
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Deployment
|
| 2 |
+
|
| 3 |
+
Models written in Python need to go through an export process to become a deployable artifact.
|
| 4 |
+
A few basic concepts about this process:
|
| 5 |
+
|
| 6 |
+
__"Export method"__ is how a Python model is fully serialized to a deployable format.
|
| 7 |
+
We support the following export methods:
|
| 8 |
+
|
| 9 |
+
* `tracing`: see [pytorch documentation](https://pytorch.org/tutorials/beginner/Intro_to_TorchScript_tutorial.html) to learn about it
|
| 10 |
+
* `scripting`: see [pytorch documentation](https://pytorch.org/tutorials/beginner/Intro_to_TorchScript_tutorial.html) to learn about it
|
| 11 |
+
* `caffe2_tracing`: replace parts of the model by caffe2 operators, then use tracing.
|
| 12 |
+
|
| 13 |
+
__"Format"__ is how a serialized model is described in a file, e.g.
|
| 14 |
+
TorchScript, Caffe2 protobuf, ONNX format.
|
| 15 |
+
__"Runtime"__ is an engine that loads a serialized model and executes it,
|
| 16 |
+
e.g., PyTorch, Caffe2, TensorFlow, onnxruntime, TensorRT, etc.
|
| 17 |
+
A runtime is often tied to a specific format
|
| 18 |
+
(e.g. PyTorch needs TorchScript format, Caffe2 needs protobuf format).
|
| 19 |
+
We currently support the following combination and each has some limitations:
|
| 20 |
+
|
| 21 |
+
```eval_rst
|
| 22 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 23 |
+
| Export Method | tracing | scripting | caffe2_tracing |
|
| 24 |
+
+============================+=============+=============+=============================+
|
| 25 |
+
| **Formats** | TorchScript | TorchScript | Caffe2, TorchScript, ONNX |
|
| 26 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 27 |
+
| **Runtime** | PyTorch | PyTorch | Caffe2, PyTorch |
|
| 28 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 29 |
+
| C++/Python inference | ✅ | ✅ | ✅ |
|
| 30 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 31 |
+
| Dynamic resolution | ✅ | ✅ | ✅ |
|
| 32 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 33 |
+
| Batch size requirement | Constant | Dynamic | Batch inference unsupported |
|
| 34 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 35 |
+
| Extra runtime deps | torchvision | torchvision | Caffe2 ops (usually already |
|
| 36 |
+
| | | | |
|
| 37 |
+
| | | | included in PyTorch) |
|
| 38 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 39 |
+
| Faster/Mask/Keypoint R-CNN | ✅ | ✅ | ✅ |
|
| 40 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 41 |
+
| RetinaNet | ✅ | ✅ | ✅ |
|
| 42 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 43 |
+
| PointRend R-CNN | ✅ | ❌ | ❌ |
|
| 44 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 45 |
+
| Cascade R-CNN | ✅ | ❌ | ❌ |
|
| 46 |
+
+----------------------------+-------------+-------------+-----------------------------+
|
| 47 |
+
|
| 48 |
+
```
|
| 49 |
+
|
| 50 |
+
`caffe2_tracing` is going to be deprecated.
|
| 51 |
+
We don't plan to work on additional support for other formats/runtime, but contributions are welcome.
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
## Deployment with Tracing or Scripting
|
| 55 |
+
|
| 56 |
+
Models can be exported to TorchScript format, by either
|
| 57 |
+
[tracing or scripting](https://pytorch.org/tutorials/beginner/Intro_to_TorchScript_tutorial.html).
|
| 58 |
+
The output model file can be loaded without detectron2 dependency in either Python or C++.
|
| 59 |
+
The exported model often requires torchvision (or its C++ library) dependency for some custom ops.
|
| 60 |
+
|
| 61 |
+
This feature requires PyTorch ≥ 1.8.
|
| 62 |
+
|
| 63 |
+
### Coverage
|
| 64 |
+
Most official models under the meta architectures `GeneralizedRCNN` and `RetinaNet`
|
| 65 |
+
are supported in both tracing and scripting mode.
|
| 66 |
+
Cascade R-CNN and PointRend are currently supported in tracing.
|
| 67 |
+
Users' custom extensions are supported if they are also scriptable or traceable.
|
| 68 |
+
|
| 69 |
+
For models exported with tracing, dynamic input resolution is allowed, but batch size
|
| 70 |
+
(number of input images) must be fixed.
|
| 71 |
+
Scripting can support dynamic batch size.
|
| 72 |
+
|
| 73 |
+
### Usage
|
| 74 |
+
|
| 75 |
+
The main export APIs for tracing and scripting are [TracingAdapter](../modules/export.html#detectron2.export.TracingAdapter)
|
| 76 |
+
and [scripting_with_instances](../modules/export.html#detectron2.export.scripting_with_instances).
|
| 77 |
+
Their usage is currently demonstrated in [test_export_torchscript.py](../../tests/test_export_torchscript.py)
|
| 78 |
+
(see `TestScripting` and `TestTracing`)
|
| 79 |
+
as well as the [deployment example](../../tools/deploy).
|
| 80 |
+
Please check that these examples can run, and then modify for your use cases.
|
| 81 |
+
The usage now requires some user effort and necessary knowledge for each model to workaround the limitation of scripting and tracing.
|
| 82 |
+
In the future we plan to wrap these under simpler APIs to lower the bar to use them.
|
| 83 |
+
|
| 84 |
+
## Deployment with Caffe2-tracing
|
| 85 |
+
We provide [Caffe2Tracer](../modules/export.html#detectron2.export.Caffe2Tracer)
|
| 86 |
+
that performs the export logic.
|
| 87 |
+
It replaces parts of the model with Caffe2 operators,
|
| 88 |
+
and then export the model into Caffe2, TorchScript or ONNX format.
|
| 89 |
+
|
| 90 |
+
The converted model is able to run in either Python or C++ without detectron2/torchvision dependency, on CPU or GPUs.
|
| 91 |
+
It has a runtime optimized for CPU & mobile inference, but not optimized for GPU inference.
|
| 92 |
+
|
| 93 |
+
This feature requires ONNX ≥ 1.6.
|
| 94 |
+
|
| 95 |
+
### Coverage
|
| 96 |
+
|
| 97 |
+
Most official models under these 3 common meta architectures: `GeneralizedRCNN`, `RetinaNet`, `PanopticFPN`
|
| 98 |
+
are supported. Cascade R-CNN is not supported. Batch inference is not supported.
|
| 99 |
+
|
| 100 |
+
Users' custom extensions under these architectures (added through registration) are supported
|
| 101 |
+
as long as they do not contain control flow or operators not available in Caffe2 (e.g. deformable convolution).
|
| 102 |
+
For example, custom backbones and heads are often supported out of the box.
|
| 103 |
+
|
| 104 |
+
### Usage
|
| 105 |
+
|
| 106 |
+
The APIs are listed at [the API documentation](../modules/export).
|
| 107 |
+
We provide [export_model.py](../../tools/deploy/) as an example that uses
|
| 108 |
+
these APIs to convert a standard model. For custom models/datasets, you can add them to this script.
|
| 109 |
+
|
| 110 |
+
### Use the model in C++/Python
|
| 111 |
+
|
| 112 |
+
The model can be loaded in C++ and deployed with
|
| 113 |
+
either Caffe2 or Pytorch runtime.. [C++ examples](../../tools/deploy/) for Mask R-CNN
|
| 114 |
+
are given as a reference. Note that:
|
| 115 |
+
|
| 116 |
+
* Models exported with `caffe2_tracing` method take a special input format
|
| 117 |
+
described in [documentation](../modules/export.html#detectron2.export.Caffe2Tracer).
|
| 118 |
+
This was taken care of in the C++ example.
|
| 119 |
+
|
| 120 |
+
* The converted models do not contain post-processing operations that
|
| 121 |
+
transform raw layer outputs into formatted predictions.
|
| 122 |
+
For example, the C++ examples only produce raw outputs (28x28 masks) from the final
|
| 123 |
+
layers that are not post-processed, because in actual deployment, an application often needs
|
| 124 |
+
its custom lightweight post-processing, so this step is left for users.
|
| 125 |
+
|
| 126 |
+
To help use the Caffe2-format model in python,
|
| 127 |
+
we provide a python wrapper around the converted model, in the
|
| 128 |
+
[Caffe2Model.\_\_call\_\_](../modules/export.html#detectron2.export.Caffe2Model.__call__) method.
|
| 129 |
+
This method has an interface that's identical to the [pytorch versions of models](./models.md),
|
| 130 |
+
and it internally applies pre/post-processing code to match the formats.
|
| 131 |
+
This wrapper can serve as a reference for how to use Caffe2's python API,
|
| 132 |
+
or for how to implement pre/post-processing in actual deployment.
|
| 133 |
+
|
| 134 |
+
## Conversion to TensorFlow
|
| 135 |
+
[tensorpack Faster R-CNN](https://github.com/tensorpack/tensorpack/tree/master/examples/FasterRCNN/convert_d2)
|
| 136 |
+
provides scripts to convert a few standard detectron2 R-CNN models to TensorFlow's pb format.
|
| 137 |
+
It works by translating configs and weights, therefore only support a few models.
|
approach/ovod/detectron2/docs/tutorials/extend.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Extend Detectron2's Defaults
|
| 2 |
+
|
| 3 |
+
__Research is about doing things in new ways__.
|
| 4 |
+
This brings a tension in how to create abstractions in code,
|
| 5 |
+
which is a challenge for any research engineering project of a significant size:
|
| 6 |
+
|
| 7 |
+
1. On one hand, it needs to have very thin abstractions to allow for the possibility of doing
|
| 8 |
+
everything in new ways. It should be reasonably easy to break existing
|
| 9 |
+
abstractions and replace them with new ones.
|
| 10 |
+
|
| 11 |
+
2. On the other hand, such a project also needs reasonably high-level
|
| 12 |
+
abstractions, so that users can easily do things in standard ways,
|
| 13 |
+
without worrying too much about the details that only certain researchers care about.
|
| 14 |
+
|
| 15 |
+
In detectron2, there are two types of interfaces that address this tension together:
|
| 16 |
+
|
| 17 |
+
1. Functions and classes that take a config (`cfg`) argument
|
| 18 |
+
created from a yaml file
|
| 19 |
+
(sometimes with few extra arguments).
|
| 20 |
+
|
| 21 |
+
Such functions and classes implement
|
| 22 |
+
the "standard default" behavior: it will read what it needs from a given
|
| 23 |
+
config and do the "standard" thing.
|
| 24 |
+
Users only need to load an expert-made config and pass it around, without having to worry about
|
| 25 |
+
which arguments are used and what they all mean.
|
| 26 |
+
|
| 27 |
+
See [Yacs Configs](configs.md) for a detailed tutorial.
|
| 28 |
+
|
| 29 |
+
2. Functions and classes that have well-defined explicit arguments.
|
| 30 |
+
|
| 31 |
+
Each of these is a small building block of the entire system.
|
| 32 |
+
They require users' expertise to understand what each argument should be,
|
| 33 |
+
and require more effort to stitch together to a larger system.
|
| 34 |
+
But they can be stitched together in more flexible ways.
|
| 35 |
+
|
| 36 |
+
When you need to implement something not supported by the "standard defaults"
|
| 37 |
+
included in detectron2, these well-defined components can be reused.
|
| 38 |
+
|
| 39 |
+
The [LazyConfig system](lazyconfigs.md) relies on such functions and classes.
|
| 40 |
+
|
| 41 |
+
3. A few functions and classes are implemented with the
|
| 42 |
+
[@configurable](../modules/config.html#detectron2.config.configurable)
|
| 43 |
+
decorator - they can be called with either a config, or with explicit arguments, or a mixture of both.
|
| 44 |
+
Their explicit argument interfaces are currently experimental.
|
| 45 |
+
|
| 46 |
+
As an example, a Mask R-CNN model can be built in the following ways:
|
| 47 |
+
|
| 48 |
+
1. Config-only:
|
| 49 |
+
```python
|
| 50 |
+
# load proper yaml config file, then
|
| 51 |
+
model = build_model(cfg)
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
2. Mixture of config and additional argument overrides:
|
| 55 |
+
```python
|
| 56 |
+
model = GeneralizedRCNN(
|
| 57 |
+
cfg,
|
| 58 |
+
roi_heads=StandardROIHeads(cfg, batch_size_per_image=666),
|
| 59 |
+
pixel_std=[57.0, 57.0, 57.0])
|
| 60 |
+
```
|
| 61 |
+
|
| 62 |
+
3. Full explicit arguments:
|
| 63 |
+
<details>
|
| 64 |
+
<summary>
|
| 65 |
+
(click to expand)
|
| 66 |
+
</summary>
|
| 67 |
+
|
| 68 |
+
```python
|
| 69 |
+
model = GeneralizedRCNN(
|
| 70 |
+
backbone=FPN(
|
| 71 |
+
ResNet(
|
| 72 |
+
BasicStem(3, 64, norm="FrozenBN"),
|
| 73 |
+
ResNet.make_default_stages(50, stride_in_1x1=True, norm="FrozenBN"),
|
| 74 |
+
out_features=["res2", "res3", "res4", "res5"],
|
| 75 |
+
).freeze(2),
|
| 76 |
+
["res2", "res3", "res4", "res5"],
|
| 77 |
+
256,
|
| 78 |
+
top_block=LastLevelMaxPool(),
|
| 79 |
+
),
|
| 80 |
+
proposal_generator=RPN(
|
| 81 |
+
in_features=["p2", "p3", "p4", "p5", "p6"],
|
| 82 |
+
head=StandardRPNHead(in_channels=256, num_anchors=3),
|
| 83 |
+
anchor_generator=DefaultAnchorGenerator(
|
| 84 |
+
sizes=[[32], [64], [128], [256], [512]],
|
| 85 |
+
aspect_ratios=[0.5, 1.0, 2.0],
|
| 86 |
+
strides=[4, 8, 16, 32, 64],
|
| 87 |
+
offset=0.0,
|
| 88 |
+
),
|
| 89 |
+
anchor_matcher=Matcher([0.3, 0.7], [0, -1, 1], allow_low_quality_matches=True),
|
| 90 |
+
box2box_transform=Box2BoxTransform([1.0, 1.0, 1.0, 1.0]),
|
| 91 |
+
batch_size_per_image=256,
|
| 92 |
+
positive_fraction=0.5,
|
| 93 |
+
pre_nms_topk=(2000, 1000),
|
| 94 |
+
post_nms_topk=(1000, 1000),
|
| 95 |
+
nms_thresh=0.7,
|
| 96 |
+
),
|
| 97 |
+
roi_heads=StandardROIHeads(
|
| 98 |
+
num_classes=80,
|
| 99 |
+
batch_size_per_image=512,
|
| 100 |
+
positive_fraction=0.25,
|
| 101 |
+
proposal_matcher=Matcher([0.5], [0, 1], allow_low_quality_matches=False),
|
| 102 |
+
box_in_features=["p2", "p3", "p4", "p5"],
|
| 103 |
+
box_pooler=ROIPooler(7, (1.0 / 4, 1.0 / 8, 1.0 / 16, 1.0 / 32), 0, "ROIAlignV2"),
|
| 104 |
+
box_head=FastRCNNConvFCHead(
|
| 105 |
+
ShapeSpec(channels=256, height=7, width=7), conv_dims=[], fc_dims=[1024, 1024]
|
| 106 |
+
),
|
| 107 |
+
box_predictor=FastRCNNOutputLayers(
|
| 108 |
+
ShapeSpec(channels=1024),
|
| 109 |
+
test_score_thresh=0.05,
|
| 110 |
+
box2box_transform=Box2BoxTransform((10, 10, 5, 5)),
|
| 111 |
+
num_classes=80,
|
| 112 |
+
),
|
| 113 |
+
mask_in_features=["p2", "p3", "p4", "p5"],
|
| 114 |
+
mask_pooler=ROIPooler(14, (1.0 / 4, 1.0 / 8, 1.0 / 16, 1.0 / 32), 0, "ROIAlignV2"),
|
| 115 |
+
mask_head=MaskRCNNConvUpsampleHead(
|
| 116 |
+
ShapeSpec(channels=256, width=14, height=14),
|
| 117 |
+
num_classes=80,
|
| 118 |
+
conv_dims=[256, 256, 256, 256, 256],
|
| 119 |
+
),
|
| 120 |
+
),
|
| 121 |
+
pixel_mean=[103.530, 116.280, 123.675],
|
| 122 |
+
pixel_std=[1.0, 1.0, 1.0],
|
| 123 |
+
input_format="BGR",
|
| 124 |
+
)
|
| 125 |
+
```
|
| 126 |
+
|
| 127 |
+
</details>
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
If you only need the standard behavior, the [Beginner's Tutorial](./getting_started.md)
|
| 131 |
+
should suffice. If you need to extend detectron2 to your own needs,
|
| 132 |
+
see the following tutorials for more details:
|
| 133 |
+
|
| 134 |
+
* Detectron2 includes a few standard datasets. To use custom ones, see
|
| 135 |
+
[Use Custom Datasets](./datasets.md).
|
| 136 |
+
* Detectron2 contains the standard logic that creates a data loader for training/testing from a
|
| 137 |
+
dataset, but you can write your own as well. See [Use Custom Data Loaders](./data_loading.md).
|
| 138 |
+
* Detectron2 implements many standard detection models, and provide ways for you
|
| 139 |
+
to overwrite their behaviors. See [Use Models](./models.md) and [Write Models](./write-models.md).
|
| 140 |
+
* Detectron2 provides a default training loop that is good for common training tasks.
|
| 141 |
+
You can customize it with hooks, or write your own loop instead. See [training](./training.md).
|