Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Off by 1 error between polygon and masks #659

Open
matthost opened this issue Sep 1, 2023 · 0 comments
Open

Off by 1 error between polygon and masks #659

matthost opened this issue Sep 1, 2023 · 0 comments

Comments

@matthost
Copy link

matthost commented Sep 1, 2023

I would think the mask created by a polygon includes the vertices of the polygon. However, the code below shows an off-by-one error in the recovered mask when decoding a frPyObjects encoded mask. But only the frPyObjects encoded mask results in perfect evaluation scores as expected.

I do not understand where this off-by-one error comes from or how it works.

from pycocotools import mask as mask
from pycocotools.coco import COCO
from pycocotools.cocoeval import COCOeval
import cv2
import os
import numpy as np
import tempfile
import json

segmentation = [[0, 0, 0, 9, 9, 9, 9, 0]]
coco = {
    "images": [
        {"id": 1, "width": 100, "height": 100, "file_name": "1.jpg"}
    ], 
    "annotations": [
        {"id": 1, "image_id": 1, "category_id": 0, "segmentation": segmentation, "area": 0, "bbox": [1, 1, 9, 9], "iscrowd": 0}
    ],
    "categories": [{"id": 0, "name": "category_0", "supercategory": "supercategory_0"}, {"id": 1, "name": "category_1", "supercategory": "supercategory_1"}]
}

expected_mask = np.zeros((100, 100), dtype=np.uint8)
expected_mask[0:10, 0:10] = 1
print(f"Expected mask sum: {expected_mask.sum()}")

cv2_filled = np.zeros((100, 100), dtype=np.uint8)
cv2_polygon_format = np.array([[[segmentation[0][i], segmentation[0][i+1]] for i in range(0, len(segmentation[0]), 2)]])
cv2.fillPoly(cv2_filled, cv2_polygon_format, 1)
print(f"cv2_filled mask sum: {cv2_filled.sum()}")

mask_encoded_rle = mask.encode(np.asarray(cv2_filled, order='F'))
decoded_mask = mask.decode(mask_encoded_rle)
print(f"En/decoded expected mask sum: {decoded_mask.sum()}")

frPyObjectsRLE = mask.frPyObjects(segmentation, 100, 100)[0]
print(f"frPyObjectsRLE: {frPyObjectsRLE}")

decoded_frPyObjects_mask = mask.decode(frPyObjectsRLE)
print(f"Decoded_frPyObjects_mask sum: {decoded_frPyObjects_mask.sum()}")

def eval(rle):
    with tempfile.TemporaryDirectory() as temp_dir:
        coco_gt_path = os.path.join(temp_dir, "coco_gt.json")
        with open(coco_gt_path, "w") as file:
            json.dump(coco, file)
        rle["counts"] = rle["counts"].decode("utf-8")
        coco_gt = COCO(coco_gt_path)
        coco_dt = coco_gt.loadRes([
            {
                "image_id": 1,
                "category_id": 0,
                "segmentation": rle,
                "score": 1.0,
            }
        ])
        coco_eval = COCOeval(coco_gt, coco_dt, "segm")
        coco_eval.evaluate()
        coco_eval.accumulate()
        coco_eval.summarize()

eval(frPyObjectsRLE)
eval(mask_encoded_rle)

prints:

Expected mask sum: 100
cv2_filled mask sum: 100
En/decoded expected mask sum: 100
frPyObjectsRLE: {'size': [100, 100], 'counts': b'09k2000000000000000\\l8'}
Decoded_frPyObjects_mask sum: 81
loading annotations into memory...
Done (t=0.00s)
creating index...
index created!
Loading and preparing results...
DONE (t=0.00s)
creating index...
index created!
Running per image evaluation...
Evaluate annotation type *segm*
DONE (t=0.00s).
Accumulating evaluation results...
DONE (t=0.01s).
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.000
loading annotations into memory...
Done (t=0.00s)
creating index...
index created!
Loading and preparing results...
DONE (t=0.00s)
creating index...
index created!
Running per image evaluation...
Evaluate annotation type *segm*
DONE (t=0.00s).
Accumulating evaluation results...
DONE (t=0.01s).
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.700
 Average Precision  (AP) @[ IoU=0.50      | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.75      | area=   all | maxDets=100 ] = 1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.700
 Average Precision  (AP) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Precision  (AP) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=  1 ] = 0.700
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets= 10 ] = 0.700
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=   all | maxDets=100 ] = 0.700
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= small | maxDets=100 ] = 0.700
 Average Recall     (AR) @[ IoU=0.50:0.95 | area=medium | maxDets=100 ] = -1.000
 Average Recall     (AR) @[ IoU=0.50:0.95 | area= large | maxDets=100 ] = -1.000
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant