Download src/visualization/visualizer.py from InternsProgressUpdate/Venket: direct link, hf CLI and curl.
- Browser
- Download file 5.15 kB
-
https://huggingface.co/InternsProgressUpdate/Venket/resolve/main/src/visualization/visualizer.py
- Command line
-
hf download hf://InternsProgressUpdate/Venket/src/visualization/visualizer.py
-
curl -L -o visualizer.py https://huggingface.co/InternsProgressUpdate/Venket/resolve/main/src/visualization/visualizer.py
5.15 kB
| """Renders a multi-panel debug figure for one generated sample. | |
| Shows the full rendered document with the target box, detected bounding | |
| box, and detected polygon overlaid; a crop of the signature field; a mask | |
| reconstructed from the detected ink polygon; the resolved coverage metrics; | |
| and a difficulty-component breakdown bar chart. | |
| """ | |
| from __future__ import annotations | |
| from pathlib import Path | |
| from typing import Any | |
| import cv2 | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| from src.utils.logging_utils import get_logger | |
| logger = get_logger(__name__) | |
| def _draw_overlays(image: np.ndarray, metadata: dict[str, Any]) -> np.ndarray: | |
| out = image.copy() | |
| geometry = metadata["geometry"] | |
| def as_int_box(box): | |
| return tuple(int(round(v)) for v in box) | |
| tb = as_int_box(geometry["target_box"]) | |
| ab = as_int_box(geometry["actual_box"]) | |
| cv2.rectangle(out, (tb[0], tb[1]), (tb[2], tb[3]), (0, 200, 0), 2) | |
| cv2.rectangle(out, (ab[0], ab[1]), (ab[2], ab[3]), (30, 80, 230), 2) | |
| polygon = geometry.get("signature_polygon_px") or [] | |
| if len(polygon) >= 3: | |
| pts = np.array(polygon, dtype=np.int32).reshape(-1, 1, 2) | |
| cv2.polylines(out, [pts], isClosed=True, color=(220, 30, 30), thickness=2) | |
| return out | |
| def _crop_region(image: np.ndarray, box: list, pad: int = 25) -> np.ndarray: | |
| h, w = image.shape[:2] | |
| x1, y1, x2, y2 = (int(round(v)) for v in box) | |
| x1, y1 = max(x1 - pad, 0), max(y1 - pad, 0) | |
| x2, y2 = min(x2 + pad, w), min(y2 + pad, h) | |
| if x2 <= x1 or y2 <= y1: | |
| return image | |
| return image[y1:y2, x1:x2] | |
| def _reconstruct_mask(shape: tuple[int, int], box: list, polygon: list) -> np.ndarray: | |
| mask = np.zeros(shape, dtype=np.uint8) | |
| if len(polygon) >= 3: | |
| pts = np.array(polygon, dtype=np.int32).reshape(-1, 1, 2) | |
| cv2.fillPoly(mask, [pts], 255) | |
| x1, y1, x2, y2 = (int(round(v)) for v in box) | |
| pad = 25 | |
| x1c, y1c = max(x1 - pad, 0), max(y1 - pad, 0) | |
| x2c, y2c = min(x2 + pad, shape[1]), min(y2 + pad, shape[0]) | |
| if x2c <= x1c or y2c <= y1c: | |
| return mask | |
| return mask[y1c:y2c, x1c:x2c] | |
| def save_visualization(path: "str | Path", final_image: np.ndarray, metadata: dict[str, Any]) -> None: | |
| """Render and save a multi-panel debug figure for one generated sample. | |
| Args: | |
| path: Output PNG path. | |
| final_image: The fully rendered (and augmented) document. | |
| metadata: The sample's full metadata dict, as built by | |
| :class:`~src.metadata.metadata_exporter.MetadataExporter`. | |
| """ | |
| path = Path(path) | |
| path.parent.mkdir(parents=True, exist_ok=True) | |
| geometry = metadata["geometry"] | |
| difficulty = metadata["difficulty"] | |
| metrics = metadata["metrics"] | |
| overlaid = _draw_overlays(final_image, metadata) | |
| crop = _crop_region(final_image, geometry["actual_box"]) | |
| mask = _reconstruct_mask(final_image.shape[:2], geometry["actual_box"], geometry["signature_polygon_px"]) | |
| fig = plt.figure(figsize=(15, 9.5), dpi=110) | |
| fig.suptitle( | |
| f"{metadata['document_id']} | template={metadata['template']} | box={metadata['geometry']['target_box']}", | |
| x=0.02, ha="left", fontsize=11, family="monospace", | |
| ) | |
| grid = fig.add_gridspec(2, 3, width_ratios=[2.0, 1.0, 1.0], height_ratios=[1.0, 1.0]) | |
| ax_full = fig.add_subplot(grid[:, 0]) | |
| ax_full.imshow(overlaid) | |
| ax_full.axis("off") | |
| ax_crop = fig.add_subplot(grid[0, 1]) | |
| ax_crop.imshow(crop) | |
| ax_crop.set_title("Signature field (cropped)") | |
| ax_crop.axis("off") | |
| ax_mask = fig.add_subplot(grid[0, 2]) | |
| ax_mask.imshow(mask, cmap="gray") | |
| ax_mask.set_title("Detected ink mask") | |
| ax_mask.axis("off") | |
| ax_metrics = fig.add_subplot(grid[1, 1]) | |
| ax_metrics.axis("off") | |
| metric_lines = [ | |
| f"box_coverage_pct: {metrics['box_coverage_pct']:.2f}", | |
| f"containment_pct: {metrics['containment_pct']:.2f}", | |
| f"ink_fill_ratio: {metrics['ink_fill_ratio']:.3f}", | |
| f"polygon_overlap: {metrics['polygon_overlap_proxy']:.3f}", | |
| f"dist_from_center: {metrics['distance_from_box_center_px']:.1f}px", | |
| f"margin_util: {metrics['margin_utilization']:.3f}", | |
| f"mean_dark_int: {metrics['mean_dark_intensity']:.1f}", | |
| f"gray_stddev: {metrics['gray_stddev']:.1f}", | |
| f"signer: {metadata['signer']['name']} ({metadata['signer']['role']})", | |
| ] | |
| ax_metrics.text(0.0, 1.0, "\n".join(metric_lines), va="top", ha="left", family="monospace", fontsize=9) | |
| ax_metrics.set_title("Coverage metrics") | |
| ax_diff = fig.add_subplot(grid[1, 2]) | |
| components = ["misplacement", "whitespace", "faintness", "smallness", "low_texture"] | |
| values = [difficulty[c] for c in components] | |
| colors = ["#d62728" if v >= 0.66 else "#ff7f0e" if v >= 0.33 else "#2ca02c" for v in values] | |
| ax_diff.barh(components, values, color=colors) | |
| ax_diff.set_xlim(0, 1) | |
| ax_diff.set_title(f"Difficulty: {difficulty['score']:.3f} ({difficulty['tier']})") | |
| fig.tight_layout(rect=(0.0, 0.0, 1.0, 0.94)) | |
| fig.savefig(path) | |
| plt.close(fig) | |