Spaces:
Running
Running
Download eval/render.py from yashwrites/blindspot: direct link, hf CLI and curl.
- Browser
- Download file 5.74 kB
-
https://huggingface.co/spaces/yashwrites/blindspot/resolve/main/eval/render.py
- Command line
-
hf download hf://spaces/yashwrites/blindspot/eval/render.py
-
curl -L -o render.py https://huggingface.co/spaces/yashwrites/blindspot/resolve/main/eval/render.py
5.74 kB
| """Image rendering for eval prompts and galleries (grid overlay, learner marks, expert outlines, crops). | |
| Rendered images go to the API (base64) or to data/eval_galleries/ — never to eval/reports/ (no patient images in | |
| committed reports). | |
| """ | |
| from __future__ import annotations | |
| import base64 | |
| from pathlib import Path | |
| from typing import Any | |
| import cv2 | |
| import numpy as np | |
| from eval.common import sha256_bytes | |
| from shared.contracts import Case | |
| CYAN = (221, 201, 53) # BGR of #35C9DD (expert truth) | |
| AMBER = (46, 169, 240) # BGR of #F0A92E (learner) | |
| GRID = (0, 230, 255) # BGR bright yellow | |
| BLACK = (0, 0, 0) | |
| COLS = "ABCDEFGH" | |
| def load_gray(root: Path, case: Case) -> np.ndarray: | |
| img = cv2.imread(str(Path(root) / case.image_path), cv2.IMREAD_GRAYSCALE) | |
| if img is None: | |
| raise FileNotFoundError(Path(root) / case.image_path) | |
| return img | |
| def to_rgb(gray: np.ndarray) -> np.ndarray: | |
| return cv2.cvtColor(gray, cv2.COLOR_GRAY2BGR) if gray.ndim == 2 else gray.copy() | |
| def png_bytes(img_bgr: np.ndarray) -> bytes: | |
| ok, buf = cv2.imencode(".png", img_bgr) | |
| if not ok: | |
| raise RuntimeError("png encode failed") | |
| return buf.tobytes() | |
| def image_block(img_bgr: np.ndarray) -> tuple[dict[str, Any], str]: | |
| """Anthropic image content block + sha256 of the PNG bytes.""" | |
| b = png_bytes(img_bgr) | |
| return ( | |
| { | |
| "type": "image", | |
| "source": { | |
| "type": "base64", | |
| "media_type": "image/png", | |
| "data": base64.standard_b64encode(b).decode("ascii"), | |
| }, | |
| }, | |
| sha256_bytes(b), | |
| ) | |
| def _text(img: np.ndarray, s: str, org: tuple[int, int], scale: float, color: tuple[int, int, int], th: int) -> None: | |
| cv2.putText(img, s, org, cv2.FONT_HERSHEY_SIMPLEX, scale, BLACK, th + 2, cv2.LINE_AA) | |
| cv2.putText(img, s, org, cv2.FONT_HERSHEY_SIMPLEX, scale, color, th, cv2.LINE_AA) | |
| # ------------------------------------------------------------------------------------------------ grid (§12.1) | |
| def cell_of(x: float, y: float, w: int, h: int, n: int = 8) -> str: | |
| c = int(np.clip(np.floor(x / (w / n)), 0, n - 1)) | |
| r = int(np.clip(np.floor(y / (h / n)), 0, n - 1)) | |
| return f"{COLS[c]}{r + 1}" | |
| def cell_bbox(cell: str, w: int, h: int, n: int = 8) -> tuple[int, int, int, int]: | |
| c = COLS.index(cell[0].upper()) | |
| r = int(cell[1:]) - 1 | |
| if not (0 <= c < n and 0 <= r < n): | |
| raise ValueError(cell) | |
| return (int(round(c * w / n)), int(round(r * h / n)), int(round((c + 1) * w / n)), int(round((r + 1) * h / n))) | |
| def all_cells(n: int = 8) -> list[str]: | |
| return [f"{COLS[c]}{r + 1}" for r in range(n) for c in range(n)] | |
| def grid_overlay(gray: np.ndarray, n: int = 8) -> np.ndarray: | |
| """Columns A–H left→right (image x), rows 1–8 top→bottom (image y); thin lines, edge labels.""" | |
| img = to_rgb(gray) | |
| h, w = img.shape[:2] | |
| th = max(1, int(round(w / 700))) | |
| for i in range(1, n): | |
| x = int(round(i * w / n)) | |
| y = int(round(i * h / n)) | |
| cv2.line(img, (x, 0), (x, h - 1), GRID, th, cv2.LINE_AA) | |
| cv2.line(img, (0, y), (w - 1, y), GRID, th, cv2.LINE_AA) | |
| scale = w / 1100 | |
| tth = max(1, int(round(w / 500))) | |
| for c in range(n): | |
| cx = int(round((c + 0.5) * w / n)) | |
| _text(img, COLS[c], (cx - int(10 * scale), int(28 * scale) + 4), scale, GRID, tth) | |
| for r in range(n): | |
| cy = int(round((r + 0.5) * h / n)) | |
| _text(img, str(r + 1), (4, cy + int(10 * scale)), scale, GRID, tth) | |
| return img | |
| # ------------------------------------------------------------------------------------------------ overlays | |
| def draw_marks(img: np.ndarray, marks: list[Any]) -> np.ndarray: | |
| h, w = img.shape[:2] | |
| r = max(4, int(round(w / 80))) | |
| th = max(1, int(round(w / 400))) | |
| for m in marks: | |
| x, y = int(round(m.x)), int(round(m.y)) | |
| cv2.circle(img, (x, y), r, AMBER, th, cv2.LINE_AA) | |
| cv2.line(img, (x - r // 2, y), (x + r // 2, y), AMBER, th) | |
| cv2.line(img, (x, y - r // 2), (x, y + r // 2), AMBER, th) | |
| _text(img, m.mark_id, (x + r + 2, y - r), w / 1300, AMBER, th) | |
| return img | |
| def draw_outlines(img: np.ndarray, case: Case, repo: Any, findings: list[Any] | None = None) -> np.ndarray: | |
| h, w = img.shape[:2] | |
| th = max(1, int(round(w / 450))) | |
| for f in findings if findings is not None else case.findings: | |
| m = repo.mask(case.case_id, f.finding_id) | |
| if m is not None: | |
| cnts, _ = cv2.findContours(m.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) | |
| cv2.drawContours(img, cnts, -1, CYAN, th, cv2.LINE_AA) | |
| else: | |
| x0, y0, x1, y1 = (int(round(v)) for v in f.geometry.bbox) | |
| cv2.rectangle(img, (x0, y0), (x1, y1), CYAN, th) | |
| x0, y0 = int(f.geometry.bbox[0]), int(f.geometry.bbox[1]) | |
| _text(img, f.short_id, (max(0, x0), max(12, y0 - 4)), w / 1300, CYAN, th) | |
| return img | |
| def crop(img: np.ndarray, cx: float, cy: float, size: int) -> np.ndarray: | |
| h, w = img.shape[:2] | |
| size = min(size, w, h) | |
| x0 = int(np.clip(round(cx - size / 2), 0, w - size)) | |
| y0 = int(np.clip(round(cy - size / 2), 0, h - size)) | |
| out = img[y0 : y0 + size, x0 : x0 + size].copy() | |
| if size < 512: | |
| out = cv2.resize(out, (size * 2, size * 2), interpolation=cv2.INTER_CUBIC) | |
| return out | |
| def point(img: np.ndarray, x: float, y: float, color: tuple[int, int, int], label: str = "") -> None: | |
| h, w = img.shape[:2] | |
| r = max(3, int(round(w / 100))) | |
| cv2.drawMarker(img, (int(round(x)), int(round(y))), color, cv2.MARKER_TILTED_CROSS, 2 * r, max(1, w // 400)) | |
| if label: | |
| _text(img, label, (int(x) + r, int(y) + r), w / 1400, color, max(1, w // 500)) | |