Spaces:
Running
Running
File size: 4,635 Bytes
222e211 | 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 141 142 143 | /**
* Canvas-based annotation renderer.
* Ported from hf_space/visualizer.py (PIL -> Canvas 2D).
*/
import { CLASS_COLORS, _RAW_PALETTE } from "./constants.js";
function hexToRgb(hex) {
hex = hex.replace("#", "");
return [parseInt(hex.slice(0,2),16), parseInt(hex.slice(2,4),16), parseInt(hex.slice(4,6),16)];
}
function getColor(label) {
let hex = CLASS_COLORS[label];
if (!hex) hex = _RAW_PALETTE[Math.abs(hashCode(label)) % _RAW_PALETTE.length];
return hexToRgb(hex);
}
function hashCode(s) {
let h = 0;
for (let i = 0; i < s.length; i++) h = ((h << 5) - h + s.charCodeAt(i)) | 0;
return h;
}
/**
* Draw annotations on a canvas.
* @param {HTMLCanvasElement} canvas - Target canvas (must be sized to image dimensions).
* @param {HTMLImageElement|ImageBitmap} image - Source image.
* @param {object} parsed - Rescaled ParsedOutput with pixel coordinates.
* @param {object} opts - { lineWidth, fontSize, maskAlpha }
*/
export function drawAnnotations(canvas, image, parsed, opts = {}) {
const lw = opts.lineWidth || 3;
const fs = opts.fontSize || 14;
const maskAlpha = opts.maskAlpha || 0.25;
canvas.width = image.width;
canvas.height = image.height;
const ctx = canvas.getContext("2d");
ctx.drawImage(image, 0, 0);
// Detections: solid bounding boxes
for (const det of parsed.detections) {
const [r,g,b] = getColor(det.label);
const [x1,y1,x2,y2] = det.bbox;
ctx.strokeStyle = `rgb(${r},${g},${b})`;
ctx.lineWidth = lw;
ctx.setLineDash([]);
ctx.strokeRect(x1, y1, x2-x1, y2-y1);
drawLabel(ctx, det.label, x1, y1, [r,g,b], fs);
}
// Segmentations: filled polygon + dashed bbox
for (const seg of parsed.segmentations) {
const [r,g,b] = getColor(seg.label);
const [x1,y1,x2,y2] = seg.bbox;
// Filled polygon
if (seg.polygon && seg.polygon.length >= 3) {
ctx.fillStyle = `rgba(${r},${g},${b},${maskAlpha})`;
ctx.strokeStyle = `rgb(${r},${g},${b})`;
ctx.lineWidth = 1;
ctx.setLineDash([]);
ctx.beginPath();
ctx.moveTo(seg.polygon[0][0], seg.polygon[0][1]);
for (let i = 1; i < seg.polygon.length; i++) ctx.lineTo(seg.polygon[i][0], seg.polygon[i][1]);
ctx.closePath();
ctx.fill();
ctx.stroke();
}
// Dashed bbox
ctx.strokeStyle = `rgb(${r},${g},${b})`;
ctx.lineWidth = lw;
ctx.setLineDash([10, 6]);
ctx.strokeRect(x1, y1, x2-x1, y2-y1);
ctx.setLineDash([]);
drawLabel(ctx, seg.label, x1, y1, [r,g,b], fs);
}
// Keypoints
for (const kp of parsed.keypoints) {
const [r,g,b] = getColor(kp.label);
const [x1,y1,x2,y2] = kp.bbox;
// Dotted bbox
ctx.strokeStyle = `rgb(${r},${g},${b})`;
ctx.lineWidth = lw;
ctx.setLineDash([6, 4]);
ctx.strokeRect(x1, y1, x2-x1, y2-y1);
ctx.setLineDash([]);
drawLabel(ctx, kp.label, x1, y1, [r,g,b], fs);
const rad = Math.max(3, Math.min(canvas.width, canvas.height) / 150);
const visPts = [];
kp.keypoints.forEach(([kx, ky, vis], idx) => {
if (vis === 0) return;
visPts.push([kx, ky]);
ctx.fillStyle = `rgb(${r},${g},${b})`;
ctx.strokeStyle = "white";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.arc(kx, ky, rad, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.fillStyle = `rgb(${r},${g},${b})`;
ctx.font = `${fs}px sans-serif`;
ctx.fillText(String(idx + 1), kx + rad + 2, ky - rad + fs);
});
// Connect consecutive visible keypoints
if (visPts.length >= 2) {
ctx.strokeStyle = `rgb(${r},${g},${b})`;
ctx.lineWidth = Math.max(1, lw - 1);
ctx.beginPath();
ctx.moveTo(visPts[0][0], visPts[0][1]);
for (let i = 1; i < visPts.length; i++) ctx.lineTo(visPts[i][0], visPts[i][1]);
ctx.stroke();
}
}
// Classification labels at top
let yOff = 10;
ctx.font = `bold ${fs}px sans-serif`;
for (const cls of parsed.classifications) {
const [r,g,b] = getColor(cls.label);
const text = `View: ${cls.label}`;
const tm = ctx.measureText(text);
const pad = 4;
ctx.fillStyle = "rgba(50,50,50,0.78)";
ctx.fillRect(10 - pad, yOff - pad, tm.width + 2*pad, fs + 2*pad);
ctx.fillStyle = "white";
ctx.fillText(text, 10, yOff + fs);
yOff += fs + 2*pad + 6;
}
}
function drawLabel(ctx, label, x, y, [r,g,b], fs) {
ctx.font = `bold ${fs}px sans-serif`;
const tm = ctx.measureText(label);
const pad = 3;
const ly = Math.max(0, y - fs - 2*pad);
ctx.fillStyle = `rgb(${r},${g},${b})`;
ctx.fillRect(x - pad, ly, tm.width + 2*pad, fs + 2*pad);
ctx.fillStyle = "white";
ctx.fillText(label, x, ly + fs + pad);
}
|