File size: 5,585 Bytes
06bd674
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
"""Run CountGD++ on every prompt combination and save a results table + detection images.

Positive prompt:  text | exemplars | text + exemplars
Negative prompt:  none | text | exemplars | text + exemplars
Exemplars from:   the counted image itself ("internal") or a separate image ("external")
Targets:          strawberry (negative: blueberry) and blueberry (negative: strawberry)

    python test_all_prompts.py [--repo nikigoli/CountGDPlusPlus] [--device cuda] [--out prompt_tests]

Writes <out>/results.csv, <out>/summary.png (all cases on one sheet) and one
image per case. img/336.jpg has about 16 strawberries and well over 100 blueberries.
"""
import argparse
import csv
import itertools
import os

import torch
from PIL import Image, ImageDraw

from countgdpp import CountGDPlusPlus, count

HERE = os.path.dirname(os.path.abspath(__file__))
IMG = os.path.join(HERE, "img")
IMAGE = os.path.join(IMG, "336.jpg")

# Visual exemplars as [x1, y1, x2, y2] pixel boxes.
EXEMPLARS = {
    "strawberry": {
        "internal": (IMAGE, [[158, 62, 206, 134], [95, 133, 145, 184]]),
        "external": (os.path.join(IMG, "synthetic_strawberry.jpg"), [[205, 95, 822, 915]]),
    },
    "blueberry": {
        "internal": (IMAGE, [[235, 38, 259, 62], [330, 165, 356, 192]]),
        "external": (os.path.join(IMG, "synthetic_blueberry.jpg"), [[143, 140, 878, 850]]),
    },
}
OTHER = {"strawberry": "blueberry", "blueberry": "strawberry"}
POS_MODES = ["text", "exemplars", "text+exemplars"]
NEG_MODES = ["none", "text", "exemplars", "text+exemplars"]
SOURCES = ["internal", "external"]


def build_cases():
    cases, seen = [], set()
    for target, pos, neg, src in itertools.product(OTHER, POS_MODES, NEG_MODES, SOURCES):
        uses_exemplars = "exemplars" in pos or "exemplars" in neg
        key = (target, pos, neg, src if uses_exemplars else "-")
        if key in seen:
            continue
        seen.add(key)
        cases.append(key)
    # Two negatives at once (text + exemplars for the other fruit, text only for "plate").
    for target in OTHER:
        cases.append((target, "text+exemplars", "two negatives", "internal"))
    return cases


def run_case(model, target, pos, neg, src):
    kwargs = {"text": target if "text" in pos else ""}
    if "exemplars" in pos:
        img, boxes = EXEMPLARS[target][src]
        kwargs.update(exemplar_boxes=boxes, exemplar_image=img)

    other = OTHER[target]
    if neg == "two negatives":
        img, boxes = EXEMPLARS[other][src]
        kwargs.update(negative_texts=[other, "plate"],
                      negative_exemplar_boxes=[boxes, None],
                      negative_exemplar_images=[img, None])
    elif neg != "none":
        kwargs["negative_texts"] = [other if "text" in neg else ""]
        if "exemplars" in neg:
            img, boxes = EXEMPLARS[other][src]
            kwargs.update(negative_exemplar_boxes=[boxes], negative_exemplar_images=[img])
    return count(model, IMAGE, **kwargs)


def draw(result, title):
    im = Image.open(IMAGE).convert("RGB")
    w, h = im.size
    d = ImageDraw.Draw(im)
    for cx, cy, bw, bh in result["boxes"]:
        d.rectangle([(cx - bw / 2) * w, (cy - bh / 2) * h, (cx + bw / 2) * w, (cy + bh / 2) * h],
                    outline="cyan", width=2)
    canvas = Image.new("RGB", (w, h + 22), "white")
    canvas.paste(im, (0, 22))
    ImageDraw.Draw(canvas).text((4, 5), title, fill="black")
    return canvas


def main():
    p = argparse.ArgumentParser()
    p.add_argument("--repo", default="nikigoli/CountGDPlusPlus")
    p.add_argument("--device", default="cuda" if torch.cuda.is_available() else "cpu")
    p.add_argument("--out", default="prompt_tests")
    args = p.parse_args()
    os.makedirs(args.out, exist_ok=True)

    model = CountGDPlusPlus.from_pretrained(args.repo).to(args.device).eval()

    rows, tiles = [], []
    cases = build_cases()
    print(f"{'#':>3}  {'target':<10} {'positive':<15} {'negative':<15} {'exemplars':<9} {'count':>5}")
    for i, (target, pos, neg, src) in enumerate(cases, 1):
        try:
            res = run_case(model, target, pos, neg, src)
            n, err = res["count"], ""
        except Exception as e:  # keep going so one failure doesn't hide the rest
            res, n, err = None, "", f"{type(e).__name__}: {e}"
        print(f"{i:>3}  {target:<10} {pos:<15} {neg:<15} {src:<9} {n!s:>5}  {err}")
        rows.append({"case": i, "target": target, "positive": pos, "negative": neg,
                     "exemplar_source": src, "count": n, "error": err})
        if res is not None:
            tile = draw(res, f"#{i} {target} | pos: {pos} | neg: {neg} | {src} -> {n}")
            tile.save(os.path.join(args.out, f"{i:02d}_{target}_{pos}_{neg}_{src}.png".replace("+", "-").replace(" ", "-")))
            tiles.append(tile)

    with open(os.path.join(args.out, "results.csv"), "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=list(rows[0]))
        writer.writeheader()
        writer.writerows(rows)

    if tiles:
        cols = 4
        tw, th = tiles[0].size
        sheet = Image.new("RGB", (cols * tw, -(-len(tiles) // cols) * th), "white")
        for k, t in enumerate(tiles):
            sheet.paste(t, ((k % cols) * tw, (k // cols) * th))
        sheet.save(os.path.join(args.out, "summary.png"))

    failed = [r for r in rows if r["error"]]
    print(f"\n{len(rows) - len(failed)}/{len(rows)} cases ran. Results in {args.out}/")
    if failed:
        raise SystemExit(f"{len(failed)} case(s) failed, see the error column.")


if __name__ == "__main__":
    main()