alexvoss's picture
Publish ECSeg optimization study and experimental FP16 artifacts
9496f98 verified
Raw
History Blame Contribute Delete
3.79 kB
#!/usr/bin/env python3
"""Side-by-side FP32-vs-candidate mask visualization for the report.
For one image, renders three panels: FP32 baseline instances, candidate instances, and a per-pixel
mask disagreement map (baseline-only / candidate-only / agreement), plus the mean mask IoU. Uses the
same app-faithful preprocessing + edgecrafter-seg decode as the rest of the pipeline.
Usage:
python visualize.py --baseline FP32.onnx --candidate CAND.onnx --image IMG.jpg --out OUT.png
"""
from __future__ import annotations
import argparse
import os
import sys
import cv2
import numpy as np
import onnxruntime as ort
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
import ecseg_common as ec # noqa: E402
# Distinct BGR colors for instance overlays.
PALETTE = [
(0, 0, 255), (0, 255, 0), (255, 0, 0), (0, 255, 255), (255, 0, 255), (255, 255, 0),
(0, 128, 255), (128, 0, 255), (0, 255, 128), (255, 128, 0), (128, 255, 0), (255, 0, 128),
]
def run(sess, x):
names = [o.name for o in sess.get_outputs()]
return dict(zip(names, sess.run(names, {"images": x})))
def instances_with_masks(out, w, h):
inst = ec.parse_instances(out["labels"], out["boxes"], out["scores"], num_classes=80)
masks = out["masks"].astype(np.float32)
for it in inst:
it["mask"] = ec.decode_mask(masks[0, it["q"]], w, h)
return inst
def overlay(base_img, instances):
canvas = base_img.copy()
for i, it in enumerate(instances):
color = PALETTE[i % len(PALETTE)]
m = it["mask"]
canvas[m] = (0.5 * canvas[m] + 0.5 * np.array(color)).astype(np.uint8)
ys, xs = np.where(m)
if len(xs):
cv2.rectangle(canvas, (xs.min(), ys.min()), (xs.max(), ys.max()), color, 2)
return canvas
def disagreement(base_inst, cand_inst, w, h):
"""Union-of-masks disagreement: red=baseline-only, blue=candidate-only, gray=agree."""
b = np.zeros((h, w), bool)
c = np.zeros((h, w), bool)
for it in base_inst:
b |= it["mask"]
for it in cand_inst:
c |= it["mask"]
img = np.zeros((h, w, 3), np.uint8)
img[np.logical_and(b, c)] = (90, 90, 90)
img[np.logical_and(b, ~c)] = (0, 0, 255) # baseline-only (lost)
img[np.logical_and(~b, c)] = (255, 0, 0) # candidate-only (spurious)
iou = ec.mask_iou(b, c)
return img, iou
def label(img, text):
out = img.copy()
cv2.rectangle(out, (0, 0), (img.shape[1], 26), (0, 0, 0), -1)
cv2.putText(out, text, (6, 18), cv2.FONT_HERSHEY_SIMPLEX, 0.55, (255, 255, 255), 1, cv2.LINE_AA)
return out
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--baseline", required=True)
ap.add_argument("--candidate", required=True)
ap.add_argument("--image", required=True)
ap.add_argument("--out", required=True)
args = ap.parse_args()
bgr = cv2.imread(args.image, cv2.IMREAD_COLOR)
h, w = bgr.shape[:2]
x = ec.preprocess_bgr(bgr)
base = ort.InferenceSession(args.baseline, providers=["CPUExecutionProvider"])
cand = ort.InferenceSession(args.candidate, providers=["CPUExecutionProvider"])
base_inst = instances_with_masks(run(base, x), w, h)
cand_inst = instances_with_masks(run(cand, x), w, h)
p1 = label(overlay(bgr, base_inst), f"FP32 baseline ({len(base_inst)} inst)")
p2 = label(overlay(bgr, cand_inst), f"{os.path.basename(args.candidate)} ({len(cand_inst)} inst)")
dis, iou = disagreement(base_inst, cand_inst, w, h)
p3 = label(dis, f"disagreement union maskIoU={iou:.3f} (red=lost blue=spurious)")
strip = np.concatenate([p1, p2, p3], axis=1)
cv2.imwrite(args.out, strip)
print(f"wrote {args.out} base={len(base_inst)} cand={len(cand_inst)} unionMaskIoU={iou:.3f}")
if __name__ == "__main__":
main()