Monike123's picture
Deploy DocVerify FastAPI backend (EasyOCR + Gemini 3 Flash)
0e39d80
Raw
History Blame Contribute Delete
2.95 kB
"""PII masking using OCR bounding boxes — no YOLO dependency."""
from pathlib import Path
import re
import cv2
import numpy as np
from PIL import Image, ImageDraw
from ml_utils.ocr import OcrResult
# Regex patterns for PII fields to mask
PII_PATTERNS: dict[str, list[re.Pattern]] = {
"aadhaar": [
re.compile(r"\d{4}\s?\d{4}\s?\d{4}"), # Full Aadhaar number
],
"pan": [
re.compile(r"[A-Z]{5}\d{4}[A-Z]"), # PAN number
],
}
# Additional keyword-based masking: if an OCR block near these labels,
# mask the VALUE block (the one to the right/below)
PII_LABELS: dict[str, list[str]] = {
"aadhaar": ["address", "पता"],
"pan": [],
}
def mask_pii_on_image(
image_bgr: np.ndarray,
ocr_results: list[OcrResult],
doc_type: str,
) -> np.ndarray:
"""Mask PII fields on the image using OCR bounding boxes."""
img_rgb = cv2.cvtColor(image_bgr, cv2.COLOR_BGR2RGB)
img_pil = Image.fromarray(img_rgb)
draw = ImageDraw.Draw(img_pil)
patterns = PII_PATTERNS.get(doc_type, [])
for result in ocr_results:
for pat in patterns:
if pat.search(result.text):
_draw_mask(draw, result.bbox, img_pil.size)
break
# Mask address region for Aadhaar (bottom-right blocks after "Address" label)
if doc_type == "aadhaar":
_mask_address_region(draw, ocr_results, img_pil.size)
return cv2.cvtColor(np.array(img_pil), cv2.COLOR_RGB2BGR)
def _draw_mask(draw: ImageDraw.ImageDraw, bbox: list[list[int]], img_size: tuple[int, int]):
"""Draw a black rectangle over a bounding box."""
w, h = img_size
xs = [max(0, min(p[0], w)) for p in bbox]
ys = [max(0, min(p[1], h)) for p in bbox]
x1, x2 = min(xs), max(xs)
y1, y2 = min(ys), max(ys)
# Add small padding
pad = 3
draw.rectangle([x1 - pad, y1 - pad, x2 + pad, y2 + pad], fill=(0, 0, 0))
def _mask_address_region(draw: ImageDraw.ImageDraw, results: list[OcrResult], img_size: tuple[int, int]):
"""Mask text blocks in the address region of an Aadhaar card."""
# Find "Address" or "पता" label
address_label = None
for r in results:
if any(kw in r.text.lower() for kw in ["address", "पता"]):
address_label = r
break
if address_label is None:
return
# Mask all blocks below the address label (within reasonable distance)
_, label_y1, _, label_y2 = address_label.rect
img_h = img_size[1]
max_y = label_y2 + (img_h - label_y2) # everything below
for r in results:
_, ry1, _, _ = r.rect
if ry1 >= label_y1 and r is not address_label:
_draw_mask(draw, r.bbox, img_size)
def save_masked_image(image_bgr: np.ndarray, output_path: Path) -> str:
"""Save masked image to disk."""
output_path.parent.mkdir(parents=True, exist_ok=True)
cv2.imwrite(str(output_path), image_bgr)
return str(output_path)