Spaces:
Runtime error
Runtime error
Upload 3 files
Browse files- eval_bboxes.py +555 -0
- install.sh +17 -0
- run_inference.sh +16 -0
eval_bboxes.py
ADDED
|
@@ -0,0 +1,555 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved
|
| 2 |
+
import argparse
|
| 3 |
+
import contextlib
|
| 4 |
+
import copy
|
| 5 |
+
import io
|
| 6 |
+
import itertools
|
| 7 |
+
import json
|
| 8 |
+
import logging
|
| 9 |
+
import os
|
| 10 |
+
import os.path as osp
|
| 11 |
+
import pickle as pkl
|
| 12 |
+
from collections import OrderedDict
|
| 13 |
+
|
| 14 |
+
from utils.arg_parser import get_argparser
|
| 15 |
+
import numpy as np
|
| 16 |
+
import torch
|
| 17 |
+
from detectron2.evaluation.evaluator import DatasetEvaluator
|
| 18 |
+
from detectron2.evaluation.fast_eval_api import COCOeval_opt as COCOeval
|
| 19 |
+
from detectron2.structures import BoxMode
|
| 20 |
+
from detectron2.utils.logger import create_small_table
|
| 21 |
+
from fvcore.common.file_io import PathManager
|
| 22 |
+
from pycocotools.coco import COCO
|
| 23 |
+
from tabulate import tabulate
|
| 24 |
+
from torchvision.ops import box_iou
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class COCOEvaluator(DatasetEvaluator):
|
| 28 |
+
"""
|
| 29 |
+
Evaluate AR for object proposals, AP for instance detection/segmentation, AP
|
| 30 |
+
for keypoint detection outputs using COCO's metrics.
|
| 31 |
+
See http://cocodataset.org/#detection-eval and
|
| 32 |
+
http://cocodataset.org/#keypoints-eval to understand its metrics.
|
| 33 |
+
In addition to COCO, this evaluator is able to support any bounding box detection,
|
| 34 |
+
instance segmentation, or keypoint detection dataset.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
def __init__(
|
| 38 |
+
self,
|
| 39 |
+
gt_json_file,
|
| 40 |
+
pred_json_file,
|
| 41 |
+
counting_gt_json_path,
|
| 42 |
+
split="val",
|
| 43 |
+
image_set=None,
|
| 44 |
+
visualize_res=True,
|
| 45 |
+
output_dir=None,
|
| 46 |
+
):
|
| 47 |
+
"""
|
| 48 |
+
Args:
|
| 49 |
+
dataset_name (str): name of the dataset to be evaluated.
|
| 50 |
+
It must have either the following corresponding metadata:
|
| 51 |
+
"json_file": the path to the COCO format annotation
|
| 52 |
+
Or it must be in detectron2's standard dataset format
|
| 53 |
+
so it can be converted to COCO format automatically.
|
| 54 |
+
cfg (CfgNode): config instance
|
| 55 |
+
distributed (True): if True, will collect results from all ranks and run evaluation
|
| 56 |
+
in the main process.
|
| 57 |
+
Otherwise, will evaluate the results in the current process.
|
| 58 |
+
output_dir (str): optional, an output directory to dump all
|
| 59 |
+
results predicted on the dataset. The dump contains two files:
|
| 60 |
+
1. "instance_predictions.pth" a file in torch serialization
|
| 61 |
+
format that contains all the raw original predictions.
|
| 62 |
+
2. "coco_instances_results.json" a json file in COCO's result
|
| 63 |
+
format.
|
| 64 |
+
"""
|
| 65 |
+
self._tasks = [
|
| 66 |
+
"bbox",
|
| 67 |
+
]
|
| 68 |
+
self._output_dir = output_dir
|
| 69 |
+
self.counting_gt_json_path = counting_gt_json_path
|
| 70 |
+
|
| 71 |
+
self._cpu_device = torch.device("cpu")
|
| 72 |
+
|
| 73 |
+
# replace fewx with d2
|
| 74 |
+
self._logger = logging.getLogger(__name__)
|
| 75 |
+
|
| 76 |
+
gt_json_file = PathManager.get_local_path(gt_json_file)
|
| 77 |
+
with contextlib.redirect_stdout(io.StringIO()):
|
| 78 |
+
self._coco_api = COCO(gt_json_file)
|
| 79 |
+
|
| 80 |
+
pred_json_file = PathManager.get_local_path(pred_json_file)
|
| 81 |
+
with contextlib.redirect_stdout(io.StringIO()):
|
| 82 |
+
self.pred_coco_api = COCO(pred_json_file)
|
| 83 |
+
|
| 84 |
+
with open(gt_json_file) as f:
|
| 85 |
+
tmp_gt = json.load(f)
|
| 86 |
+
info_images = tmp_gt["images"]
|
| 87 |
+
self.map_id_2_name = dict()
|
| 88 |
+
self.map_name_2_id = dict()
|
| 89 |
+
for info_image in info_images:
|
| 90 |
+
img_id = info_image["id"]
|
| 91 |
+
img_name = info_image["file_name"]
|
| 92 |
+
self.map_id_2_name[img_id] = img_name
|
| 93 |
+
self.map_name_2_id[img_name] = img_id
|
| 94 |
+
|
| 95 |
+
with open(counting_gt_json_path) as f:
|
| 96 |
+
self.point_annos = json.load(f)
|
| 97 |
+
|
| 98 |
+
# Test set json files do not contain annotations (evaluation must be
|
| 99 |
+
# performed using the COCO evaluation server).
|
| 100 |
+
self._do_evaluation = "annotations" in self._coco_api.dataset
|
| 101 |
+
self.counting_dict = dict()
|
| 102 |
+
self._predictions = []
|
| 103 |
+
self._image_set = image_set
|
| 104 |
+
self.visualize_res = visualize_res
|
| 105 |
+
self._vis_dir = osp.join(self._output_dir, "vis_res")
|
| 106 |
+
os.makedirs(self._vis_dir, exist_ok=True)
|
| 107 |
+
self.aps = []
|
| 108 |
+
self.split = split
|
| 109 |
+
self.relative_error = []
|
| 110 |
+
|
| 111 |
+
def _tasks_from_config(self, cfg):
|
| 112 |
+
"""
|
| 113 |
+
Returns:
|
| 114 |
+
tuple[str]: tasks that can be evaluated under the given configuration.
|
| 115 |
+
"""
|
| 116 |
+
tasks = ("bbox",)
|
| 117 |
+
if cfg.MODEL.MASK_ON:
|
| 118 |
+
tasks = tasks + ("segm",)
|
| 119 |
+
return tasks
|
| 120 |
+
|
| 121 |
+
def process(self):
|
| 122 |
+
"""
|
| 123 |
+
Args:
|
| 124 |
+
inputs: the inputs to a COCO model (e.g., GeneralizedRCNN).
|
| 125 |
+
It is a list of dict. Each dict corresponds to an image and
|
| 126 |
+
contains keys like "height", "width", "file_name", "image_id".
|
| 127 |
+
outputs: the outputs of a COCO model. It is a list of dicts with key
|
| 128 |
+
"instances" that contains :class:`Instances`.
|
| 129 |
+
"""
|
| 130 |
+
if self._image_set is None:
|
| 131 |
+
img_ids = self.pred_coco_api.getImgIds()
|
| 132 |
+
else:
|
| 133 |
+
img_ids = self._image_set
|
| 134 |
+
print("number of images", len(img_ids))
|
| 135 |
+
for img_id in img_ids:
|
| 136 |
+
|
| 137 |
+
|
| 138 |
+
img_name = self.map_id_2_name[img_id]
|
| 139 |
+
anno_ids = self.pred_coco_api.getAnnIds([img_id])
|
| 140 |
+
point_anno = self.point_annos[img_name]["points"]
|
| 141 |
+
pred_annos = self.pred_coco_api.loadAnns(anno_ids)
|
| 142 |
+
img_info = self.pred_coco_api.loadImgs([img_id])
|
| 143 |
+
|
| 144 |
+
prediction = {"image_id": img_id}
|
| 145 |
+
results = []
|
| 146 |
+
num_pred = len(pred_annos)
|
| 147 |
+
for anno in pred_annos:
|
| 148 |
+
box = anno["bbox"]
|
| 149 |
+
x_cen, y_cen, w, h = box
|
| 150 |
+
new_box = [x_cen, y_cen, w, h]
|
| 151 |
+
result = {
|
| 152 |
+
"image_id": anno["image_id"],
|
| 153 |
+
"category_id": anno["category_id"],
|
| 154 |
+
"bbox": new_box,
|
| 155 |
+
"score": anno["score"],
|
| 156 |
+
}
|
| 157 |
+
|
| 158 |
+
results.append(result)
|
| 159 |
+
num_pred = len(results)
|
| 160 |
+
gt_anno_ids = self._coco_api.getAnnIds([img_id])
|
| 161 |
+
gt_annos = self._coco_api.loadAnns(gt_anno_ids)
|
| 162 |
+
|
| 163 |
+
ap = 0
|
| 164 |
+
|
| 165 |
+
if self.visualize_res:
|
| 166 |
+
import cv2
|
| 167 |
+
img = cv2.imread(osp.join(os.path.dirname(self.counting_gt_json_path), 'images_384_VarV2', img_name))
|
| 168 |
+
|
| 169 |
+
height, width, channels = img.shape
|
| 170 |
+
height = 25 * len(pred_annos) + 10
|
| 171 |
+
score_img = np.zeros((height, width, 3), np.uint8)
|
| 172 |
+
score_img[:] = 255
|
| 173 |
+
|
| 174 |
+
|
| 175 |
+
for idx, pred_anno in enumerate(pred_annos):
|
| 176 |
+
pred_box = pred_anno["bbox"]
|
| 177 |
+
|
| 178 |
+
x_cen, y_cen, w, h = pred_box
|
| 179 |
+
|
| 180 |
+
pred_box = [int(x_cen), int(y_cen), int(w), int(h)]
|
| 181 |
+
|
| 182 |
+
pred_x, pred_y, pred_w, pred_h = pred_box
|
| 183 |
+
|
| 184 |
+
pred_x, pred_y, pred_w, pred_h = int(pred_x), int(pred_y), int(pred_w), int(pred_h)
|
| 185 |
+
img = cv2.rectangle(img, (pred_x, pred_y), (pred_x + pred_w, pred_y + pred_h), (0, 165, 255), 2)
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
vis_img_path = os.path.join(self._vis_dir, str(len(pred_annos)-len(gt_annos))+"_"+ img_name[:-4] + "_"+str(len(pred_annos))+".jpg")
|
| 190 |
+
|
| 191 |
+
cv2.imwrite(vis_img_path, img)
|
| 192 |
+
|
| 193 |
+
info = {
|
| 194 |
+
"img_name": img_name,
|
| 195 |
+
"img_id": img_id,
|
| 196 |
+
"ap": ap,
|
| 197 |
+
"count_gt": len(point_anno),
|
| 198 |
+
"count_pred": num_pred,
|
| 199 |
+
}
|
| 200 |
+
self.aps.append(info)
|
| 201 |
+
prediction["instances"] = results
|
| 202 |
+
self._predictions.append(prediction)
|
| 203 |
+
self.counting_dict[img_id] = {"gt": len(point_anno), "pred": num_pred}
|
| 204 |
+
rel_err = abs(len(point_anno) - num_pred) / len(point_anno)
|
| 205 |
+
self.relative_error.append(rel_err)
|
| 206 |
+
|
| 207 |
+
def evaluate(self):
|
| 208 |
+
predictions = self._predictions
|
| 209 |
+
|
| 210 |
+
if len(predictions) == 0:
|
| 211 |
+
self._logger.warning("[COCOEvaluator] Did not receive valid predictions.")
|
| 212 |
+
return {}
|
| 213 |
+
|
| 214 |
+
self._results = OrderedDict()
|
| 215 |
+
self._eval_predictions(set(self._tasks), predictions)
|
| 216 |
+
|
| 217 |
+
# Copy so the caller can do whatever with results
|
| 218 |
+
cnt = 0
|
| 219 |
+
SAE = 0 # sum of absolute errors
|
| 220 |
+
SSE = 0 # sum of square errors
|
| 221 |
+
NAE = 0
|
| 222 |
+
SRE = 0
|
| 223 |
+
preds = []
|
| 224 |
+
gts = []
|
| 225 |
+
|
| 226 |
+
for ii, (img_id, anno) in enumerate(self.counting_dict.items()):
|
| 227 |
+
gt_cnt = anno["gt"]
|
| 228 |
+
pred_cnt = anno["pred"]
|
| 229 |
+
cnt = cnt + 1
|
| 230 |
+
err = abs(gt_cnt - pred_cnt)
|
| 231 |
+
|
| 232 |
+
preds.append(pred_cnt)
|
| 233 |
+
gts.append(gt_cnt)
|
| 234 |
+
SAE += err
|
| 235 |
+
SSE += err ** 2
|
| 236 |
+
NAE += err / gt_cnt
|
| 237 |
+
SRE += err ** 2 / gt_cnt
|
| 238 |
+
# print("Pred cnts ", preds)
|
| 239 |
+
# print("gts ", gts)
|
| 240 |
+
# print(max(gts))
|
| 241 |
+
print("number of images: {}".format(cnt))
|
| 242 |
+
print("MAE: {:.2f}".format(SAE / cnt))
|
| 243 |
+
print("RMSE: {:.2f}".format((SSE / cnt) ** 0.5))
|
| 244 |
+
print("NAE: {:.4f}".format(NAE / cnt))
|
| 245 |
+
print("SRE: {:.2f}".format((SRE / cnt) ** 0.5))
|
| 246 |
+
print("Detect results")
|
| 247 |
+
print(self._results)
|
| 248 |
+
output_path = osp.join(self._output_dir, "each_img_infor"+self.split+".pkl")
|
| 249 |
+
print("save to {}".format(output_path))
|
| 250 |
+
|
| 251 |
+
with open(output_path, "wb") as handle:
|
| 252 |
+
pkl.dump(self.aps, handle, protocol=pkl.HIGHEST_PROTOCOL)
|
| 253 |
+
print(10 * "**")
|
| 254 |
+
return copy.deepcopy(self._results)
|
| 255 |
+
|
| 256 |
+
def _eval_predictions(self, tasks, predictions):
|
| 257 |
+
"""
|
| 258 |
+
Evaluate predictions on the given tasks.
|
| 259 |
+
Fill self._results with the metrics of the tasks.
|
| 260 |
+
"""
|
| 261 |
+
self._logger.info("Preparing results for COCO format ...")
|
| 262 |
+
coco_results = list(itertools.chain(*[x["instances"] for x in predictions]))
|
| 263 |
+
|
| 264 |
+
if not self._do_evaluation:
|
| 265 |
+
self._logger.info("Annotations are not available for evaluation.")
|
| 266 |
+
return
|
| 267 |
+
|
| 268 |
+
self._logger.info("Evaluating predictions ...")
|
| 269 |
+
for task in sorted(tasks):
|
| 270 |
+
if self._image_set is not None:
|
| 271 |
+
coco_eval = (
|
| 272 |
+
_evaluate_predictions_on_coco(self._coco_api, coco_results, task, self._image_set)
|
| 273 |
+
if len(coco_results) > 0
|
| 274 |
+
else None # cocoapi does not handle empty results very well
|
| 275 |
+
)
|
| 276 |
+
else:
|
| 277 |
+
coco_eval = (
|
| 278 |
+
_evaluate_predictions_on_coco(self._coco_api, coco_results, task,)
|
| 279 |
+
if len(coco_results) > 0
|
| 280 |
+
else None # cocoapi does not handle empty results very well
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
res = self._derive_coco_results(
|
| 284 |
+
# coco_eval, task, class_names=self._metadata.get("thing_classes")
|
| 285 |
+
coco_eval,
|
| 286 |
+
task,
|
| 287 |
+
class_names=["fg",],
|
| 288 |
+
)
|
| 289 |
+
self._results[task] = res
|
| 290 |
+
|
| 291 |
+
def _derive_coco_results(self, coco_eval, iou_type, class_names=None):
|
| 292 |
+
"""
|
| 293 |
+
Derive the desired score numbers from summarized COCOeval.
|
| 294 |
+
Args:
|
| 295 |
+
coco_eval (None or COCOEval): None represents no predictions from model.
|
| 296 |
+
iou_type (str):
|
| 297 |
+
class_names (None or list[str]): if provided, will use it to predict
|
| 298 |
+
per-category AP.
|
| 299 |
+
Returns:
|
| 300 |
+
a dict of {metric name: score}
|
| 301 |
+
"""
|
| 302 |
+
|
| 303 |
+
metrics = {"bbox": ["AP", "AP50", "AP75", "APs", "APm", "APl"],}[iou_type]
|
| 304 |
+
|
| 305 |
+
if coco_eval is None:
|
| 306 |
+
self._logger.warn("No predictions from the model!")
|
| 307 |
+
return {metric: float("nan") for metric in metrics}
|
| 308 |
+
|
| 309 |
+
# the standard metrics
|
| 310 |
+
results = {
|
| 311 |
+
metric: float(coco_eval.stats[idx] * 100 if coco_eval.stats[idx] >= 0 else "nan")
|
| 312 |
+
for idx, metric in enumerate(metrics)
|
| 313 |
+
}
|
| 314 |
+
self._logger.info("Evaluation results for {}: \n".format(iou_type) + create_small_table(results))
|
| 315 |
+
if not np.isfinite(sum(results.values())):
|
| 316 |
+
self._logger.info("Some metrics cannot be computed and is shown as NaN.")
|
| 317 |
+
if class_names is None or len(class_names) <= 1:
|
| 318 |
+
return results
|
| 319 |
+
|
| 320 |
+
# Compute per-category AP
|
| 321 |
+
# from https://github.com/facebookresearch/Detectron/blob/a6a835f5b8208c45d0dce217ce9bbda915f44df7/detectron/datasets/json_dataset_evaluator.py#L222-L252 # noqa
|
| 322 |
+
|
| 323 |
+
precisions = coco_eval.eval["precision"]
|
| 324 |
+
# precision has dims (iou, recall, cls, area range, max dets)
|
| 325 |
+
assert len(class_names) == precisions.shape[2]
|
| 326 |
+
|
| 327 |
+
results_per_category = []
|
| 328 |
+
for idx, name in enumerate(class_names):
|
| 329 |
+
# area range index 0: all area ranges
|
| 330 |
+
# max dets index -1: typically 100 per image
|
| 331 |
+
precision = precisions[:, :, idx, 0, -1]
|
| 332 |
+
precision = precision[precision > -1]
|
| 333 |
+
ap = np.mean(precision) if precision.size else float("nan")
|
| 334 |
+
results_per_category.append(("{}".format(name), float(ap * 100)))
|
| 335 |
+
|
| 336 |
+
# tabulate it
|
| 337 |
+
N_COLS = min(6, len(results_per_category) * 2)
|
| 338 |
+
results_flatten = list(itertools.chain(*results_per_category))
|
| 339 |
+
results_2d = itertools.zip_longest(*[results_flatten[i::N_COLS] for i in range(N_COLS)])
|
| 340 |
+
table = tabulate(
|
| 341 |
+
results_2d, tablefmt="pipe", floatfmt=".3f", headers=["category", "AP"] * (N_COLS // 2), numalign="left",
|
| 342 |
+
)
|
| 343 |
+
self._logger.info("Per-category {} AP: \n".format(iou_type) + table)
|
| 344 |
+
|
| 345 |
+
results.update({"AP-" + name: ap for name, ap in results_per_category})
|
| 346 |
+
return results
|
| 347 |
+
|
| 348 |
+
|
| 349 |
+
def instances_to_coco_json(instances, img_id):
|
| 350 |
+
"""
|
| 351 |
+
Dump an "Instances" object to a COCO-format json that's used for evaluation.
|
| 352 |
+
Args:
|
| 353 |
+
instances (Instances):
|
| 354 |
+
img_id (int): the image id
|
| 355 |
+
Returns:
|
| 356 |
+
list[dict]: list of json annotations in COCO format.
|
| 357 |
+
"""
|
| 358 |
+
num_instance = len(instances)
|
| 359 |
+
if num_instance == 0:
|
| 360 |
+
return []
|
| 361 |
+
|
| 362 |
+
boxes = instances.pred_boxes.tensor.numpy()
|
| 363 |
+
boxes = BoxMode.convert(boxes, BoxMode.XYXY_ABS, BoxMode.XYWH_ABS)
|
| 364 |
+
boxes = boxes.tolist()
|
| 365 |
+
scores = instances.scores.tolist()
|
| 366 |
+
classes = instances.pred_classes.tolist()
|
| 367 |
+
|
| 368 |
+
results = []
|
| 369 |
+
for k in range(num_instance):
|
| 370 |
+
result = {
|
| 371 |
+
"image_id": img_id,
|
| 372 |
+
"category_id": classes[k],
|
| 373 |
+
"bbox": boxes[k],
|
| 374 |
+
"score": scores[k],
|
| 375 |
+
}
|
| 376 |
+
results.append(result)
|
| 377 |
+
return results
|
| 378 |
+
|
| 379 |
+
|
| 380 |
+
class COCOevalMaxDets(COCOeval):
|
| 381 |
+
"""
|
| 382 |
+
Modified version of COCOeval for evaluating AP with a custom
|
| 383 |
+
maxDets (by default for COCO, maxDets is 100)
|
| 384 |
+
"""
|
| 385 |
+
|
| 386 |
+
def summarize(self):
|
| 387 |
+
"""
|
| 388 |
+
Compute and display summary metrics for evaluation results given
|
| 389 |
+
a custom value for max_dets_per_image
|
| 390 |
+
"""
|
| 391 |
+
|
| 392 |
+
def _summarize(ap=1, iouThr=None, areaRng="all", maxDets=100000):
|
| 393 |
+
p = self.params
|
| 394 |
+
iStr = " {:<18} {} @[ IoU={:<9} | area={:>6s} | maxDets={:>3d} ] = {:0.3f}"
|
| 395 |
+
titleStr = "Average Precision" if ap == 1 else "Average Recall"
|
| 396 |
+
typeStr = "(AP)" if ap == 1 else "(AR)"
|
| 397 |
+
iouStr = (
|
| 398 |
+
"{:0.2f}:{:0.2f}".format(p.iouThrs[0], p.iouThrs[-1]) if iouThr is None else "{:0.2f}".format(iouThr)
|
| 399 |
+
)
|
| 400 |
+
aind = [i for i, aRng in enumerate(p.areaRngLbl) if aRng == areaRng]
|
| 401 |
+
mind = [i for i, mDet in enumerate(p.maxDets) if mDet == maxDets]
|
| 402 |
+
if ap == 1:
|
| 403 |
+
# dimension of precision: [TxRxKxAxM]
|
| 404 |
+
s = self.eval["precision"]
|
| 405 |
+
# IoU
|
| 406 |
+
if iouThr is not None:
|
| 407 |
+
t = np.where(iouThr == p.iouThrs)[0]
|
| 408 |
+
s = s[t]
|
| 409 |
+
s = s[:, :, :, aind, mind]
|
| 410 |
+
else:
|
| 411 |
+
# dimension of recall: [TxKxAxM]
|
| 412 |
+
s = self.eval["recall"]
|
| 413 |
+
if iouThr is not None:
|
| 414 |
+
t = np.where(iouThr == p.iouThrs)[0]
|
| 415 |
+
s = s[t]
|
| 416 |
+
s = s[:, :, aind, mind]
|
| 417 |
+
if len(s[s > -1]) == 0:
|
| 418 |
+
mean_s = -1
|
| 419 |
+
else:
|
| 420 |
+
mean_s = np.mean(s[s > -1])
|
| 421 |
+
print(iStr.format(titleStr, typeStr, iouStr, areaRng, maxDets, mean_s))
|
| 422 |
+
return mean_s
|
| 423 |
+
|
| 424 |
+
def _summarizeDets():
|
| 425 |
+
stats = np.zeros((12,))
|
| 426 |
+
# Evaluate AP using the custom limit on maximum detections per image
|
| 427 |
+
stats[0] = _summarize(1, maxDets=self.params.maxDets[2])
|
| 428 |
+
stats[1] = _summarize(1, iouThr=0.5, maxDets=self.params.maxDets[2])
|
| 429 |
+
stats[2] = _summarize(1, iouThr=0.75, maxDets=self.params.maxDets[2])
|
| 430 |
+
stats[3] = _summarize(1, areaRng="small", maxDets=self.params.maxDets[2])
|
| 431 |
+
stats[4] = _summarize(1, areaRng="medium", maxDets=self.params.maxDets[2])
|
| 432 |
+
stats[5] = _summarize(1, areaRng="large", maxDets=self.params.maxDets[2])
|
| 433 |
+
stats[6] = _summarize(0, maxDets=self.params.maxDets[0])
|
| 434 |
+
stats[7] = _summarize(0, maxDets=self.params.maxDets[1])
|
| 435 |
+
stats[8] = _summarize(0, maxDets=self.params.maxDets[2])
|
| 436 |
+
stats[9] = _summarize(0, areaRng="small", maxDets=self.params.maxDets[2])
|
| 437 |
+
stats[10] = _summarize(0, areaRng="medium", maxDets=self.params.maxDets[2])
|
| 438 |
+
stats[11] = _summarize(0, areaRng="large", maxDets=self.params.maxDets[2])
|
| 439 |
+
return stats
|
| 440 |
+
|
| 441 |
+
def _summarizeKps():
|
| 442 |
+
stats = np.zeros((10,))
|
| 443 |
+
stats[0] = _summarize(1, maxDets=3000)
|
| 444 |
+
stats[1] = _summarize(1, maxDets=3000, iouThr=0.5)
|
| 445 |
+
stats[2] = _summarize(1, maxDets=3000, iouThr=0.75)
|
| 446 |
+
stats[3] = _summarize(1, maxDets=3000, areaRng="medium")
|
| 447 |
+
stats[4] = _summarize(1, maxDets=3000, areaRng="large")
|
| 448 |
+
stats[5] = _summarize(0, maxDets=3000)
|
| 449 |
+
stats[6] = _summarize(0, maxDets=3000, iouThr=0.5)
|
| 450 |
+
stats[7] = _summarize(0, maxDets=3000, iouThr=0.75)
|
| 451 |
+
stats[8] = _summarize(0, maxDets=3000, areaRng="medium")
|
| 452 |
+
stats[9] = _summarize(0, maxDets=3000, areaRng="large")
|
| 453 |
+
return stats
|
| 454 |
+
|
| 455 |
+
if not self.eval:
|
| 456 |
+
raise Exception("Please run accumulate() first")
|
| 457 |
+
iouType = self.params.iouType
|
| 458 |
+
if iouType == "segm" or iouType == "bbox":
|
| 459 |
+
summarize = _summarizeDets
|
| 460 |
+
elif iouType == "keypoints":
|
| 461 |
+
summarize = _summarizeKps
|
| 462 |
+
self.stats = summarize()
|
| 463 |
+
|
| 464 |
+
def __str__(self):
|
| 465 |
+
self.summarize()
|
| 466 |
+
|
| 467 |
+
|
| 468 |
+
def _evaluate_predictions_on_coco(
|
| 469 |
+
coco_gt, coco_results, iou_type, img_ids=None, max_dets_per_image=None, kpt_oks_sigmas=None
|
| 470 |
+
):
|
| 471 |
+
"""
|
| 472 |
+
Evaluate the coco results using COCOEval API.
|
| 473 |
+
"""
|
| 474 |
+
assert len(coco_results) > 0
|
| 475 |
+
coco_dt = coco_gt.loadRes(coco_results)
|
| 476 |
+
# coco_eval = COCOeval(coco_gt, coco_dt, iou_type)
|
| 477 |
+
coco_eval = COCOevalMaxDets(coco_gt, coco_dt, iou_type)
|
| 478 |
+
if iou_type == "segm":
|
| 479 |
+
coco_results = copy.deepcopy(coco_results)
|
| 480 |
+
# When evaluating mask AP, if the results contain bbox, cocoapi will
|
| 481 |
+
# use the box area as the area of the instance, instead of the mask area.
|
| 482 |
+
# This leads to a different definition of small/medium/large.
|
| 483 |
+
# We remove the bbox field to let mask AP use mask area.
|
| 484 |
+
for c in coco_results:
|
| 485 |
+
c.pop("bbox", None)
|
| 486 |
+
|
| 487 |
+
# For COCO, the default max_dets_per_image is [1, 10, 100].
|
| 488 |
+
if max_dets_per_image is None:
|
| 489 |
+
max_dets_per_image = [10000, 10000, 10000]
|
| 490 |
+
else:
|
| 491 |
+
assert (
|
| 492 |
+
len(max_dets_per_image) >= 3
|
| 493 |
+
), "COCOeval requires maxDets (and max_dets_per_image) to have length at least 3"
|
| 494 |
+
# In the case that user supplies a custom input for max_dets_per_image,
|
| 495 |
+
# apply COCOevalMaxDets to evaluate AP with the custom input.
|
| 496 |
+
if max_dets_per_image[2] != 100:
|
| 497 |
+
coco_eval = COCOevalMaxDets(coco_gt, coco_dt, iou_type)
|
| 498 |
+
if iou_type != "keypoints":
|
| 499 |
+
coco_eval.params.maxDets = max_dets_per_image
|
| 500 |
+
|
| 501 |
+
if img_ids is not None:
|
| 502 |
+
coco_eval.params.imgIds = img_ids
|
| 503 |
+
|
| 504 |
+
coco_eval.evaluate()
|
| 505 |
+
coco_eval.accumulate()
|
| 506 |
+
coco_eval.summarize()
|
| 507 |
+
|
| 508 |
+
return coco_eval
|
| 509 |
+
|
| 510 |
+
|
| 511 |
+
def get_args_parser():
|
| 512 |
+
parser = argparse.ArgumentParser("GECO2", add_help=False)
|
| 513 |
+
parser.add_argument("--input_folder", required=True, type=str)
|
| 514 |
+
args = parser.parse_args()
|
| 515 |
+
return args
|
| 516 |
+
|
| 517 |
+
|
| 518 |
+
if __name__ == '__main__':
|
| 519 |
+
parser = argparse.ArgumentParser('GeCo', parents=[get_argparser()])
|
| 520 |
+
args = parser.parse_args()
|
| 521 |
+
|
| 522 |
+
input_folder = ''
|
| 523 |
+
dataset_folder = args.data_path
|
| 524 |
+
print("Evaluating on validation set")
|
| 525 |
+
gt_json_path = dataset_folder+"/annotations/instances_val.json"
|
| 526 |
+
|
| 527 |
+
pred_json_path = 'geco2_val.json'
|
| 528 |
+
counting_json_path = dataset_folder+"/annotation_FSC147_384.json"
|
| 529 |
+
output_dir = input_folder
|
| 530 |
+
coco_evaluator = COCOEvaluator(
|
| 531 |
+
gt_json_file=gt_json_path,
|
| 532 |
+
pred_json_file=pred_json_path,
|
| 533 |
+
counting_gt_json_path=counting_json_path,
|
| 534 |
+
output_dir=output_dir,
|
| 535 |
+
visualize_res=False,
|
| 536 |
+
split="val",
|
| 537 |
+
)
|
| 538 |
+
coco_evaluator.process()
|
| 539 |
+
coco_evaluator.evaluate()
|
| 540 |
+
|
| 541 |
+
print("Evaluating on test set")
|
| 542 |
+
gt_json_path = dataset_folder+"/annotations/instances_test.json"
|
| 543 |
+
pred_json_path = 'geco2_test.json'
|
| 544 |
+
counting_json_path = dataset_folder+"/annotation_FSC147_384.json"
|
| 545 |
+
output_dir = input_folder
|
| 546 |
+
coco_evaluator = COCOEvaluator(
|
| 547 |
+
gt_json_file=gt_json_path,
|
| 548 |
+
pred_json_file=pred_json_path,
|
| 549 |
+
counting_gt_json_path=counting_json_path,
|
| 550 |
+
output_dir=output_dir,
|
| 551 |
+
visualize_res=False,
|
| 552 |
+
split="test",
|
| 553 |
+
)
|
| 554 |
+
coco_evaluator.process()
|
| 555 |
+
coco_evaluator.evaluate()
|
install.sh
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
conda create -n test_geco2 python=3.10 -y
|
| 4 |
+
pip install torch==2.7.1 torchvision==0.22.1 torchaudio==2.7.1 --index-url https://download.pytorch.org/whl/cu126
|
| 5 |
+
cd ./Deformable-DETR/models/ops
|
| 6 |
+
CUDA_VISIBLE_DEVICES=0 python -m pip install .
|
| 7 |
+
cd ../../..
|
| 8 |
+
cp -r ./Deformable-DETR/models/ops ./models/ops
|
| 9 |
+
pip install hydra-core
|
| 10 |
+
pip install scikit-image
|
| 11 |
+
pip install pycocotools
|
| 12 |
+
pip install einops
|
| 13 |
+
pip install "numpy<2"
|
| 14 |
+
pip install gradio
|
| 15 |
+
pip install gradio_image_prompter
|
| 16 |
+
pip install huggingface-hub==0.34.3
|
| 17 |
+
pip install --force-reinstall "pydantic<2.11"
|
run_inference.sh
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
|
| 3 |
+
conda activate cnt2
|
| 4 |
+
|
| 5 |
+
python inference.py \
|
| 6 |
+
--model_name GECO2FSCD \
|
| 7 |
+
--data_path /d/hpc/projects/FRI/pelhanj/fsc147 \
|
| 8 |
+
--model_path /d/hpc/projects/FRI/pelhanj/CNT_SAM2/models/ \
|
| 9 |
+
--backbone resnet50 \
|
| 10 |
+
--reduction 16 \
|
| 11 |
+
--image_size 1024 \
|
| 12 |
+
--emb_dim 256 \
|
| 13 |
+
--num_heads 8 \
|
| 14 |
+
--kernel_dim 1 \
|
| 15 |
+
--num_objects 3 \
|
| 16 |
+
--batch_size 2 \
|