Spaces:
Running
Running
| import cv2 | |
| import easyocr | |
| import numpy as np | |
| import json | |
| import sys | |
| import os | |
| import re | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # CONFIGURATION | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| OCR_LANGUAGES = ['en'] | |
| MIN_OCR_CONF = 0.4 # raised to filter junk text | |
| IC_LABELS = ['ic', 'transistor', 'clock', 'display'] | |
| PADDING = 10 | |
| # Junk patterns to filter out from OCR results | |
| JUNK_PATTERNS = [ | |
| r'^[^a-zA-Z0-9]+$', # only symbols | |
| r'^\d{1,2}$', # single/double digit only (too short to be useful) | |
| r'^[a-zA-Z]{1}$', # single letter | |
| ] | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # INITIALIZE READER | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| print("[->] Loading EasyOCR model...") | |
| reader = easyocr.Reader(OCR_LANGUAGES, gpu=True) | |
| print("[OK] EasyOCR ready") | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # FILTER JUNK OCR TEXT | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def is_junk(text: str) -> bool: | |
| for pattern in JUNK_PATTERNS: | |
| if re.match(pattern, text): | |
| return True | |
| return False | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # PREPROCESS CHIP PATCH | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def preprocess_patch(patch: np.ndarray) -> np.ndarray: | |
| h, w = patch.shape[:2] | |
| # Only upscale if patch is small | |
| scale = 3 if max(h, w) < 100 else 2 | |
| upscaled = cv2.resize(patch, (w * scale, h * scale), | |
| interpolation=cv2.INTER_CUBIC) | |
| kernel = np.array([[0, -1, 0], [-1, 5, -1], [0, -1, 0]]) | |
| sharpened = cv2.filter2D(upscaled, -1, kernel) | |
| denoised = cv2.fastNlMeansDenoisingColored(sharpened, h=10) | |
| return denoised | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # RUN OCR ON A SINGLE PATCH | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def read_text_from_patch(patch: np.ndarray) -> list: | |
| processed = preprocess_patch(patch) | |
| results = reader.readtext(processed) | |
| texts = [] | |
| for (_, text, conf) in results: | |
| text = text.strip() | |
| if conf >= MIN_OCR_CONF and len(text) >= 2 and not is_junk(text): | |
| texts.append((text, round(conf, 3))) | |
| return texts | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # RUN OCR ON ALL IC DETECTIONS | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def run_ocr_on_detections(image_path: str, detections: list) -> list: | |
| img = cv2.imread(image_path) | |
| if img is None: | |
| print(f"[X] Could not load image: {image_path}") | |
| return detections | |
| ih, iw = img.shape[:2] | |
| updated = [] | |
| ic_count = sum(1 for d in detections if d['label'] in IC_LABELS) | |
| print(f"\n[->] Running OCR on {ic_count} IC/chip regions...") | |
| for det in detections: | |
| label = det['label'] | |
| if label not in IC_LABELS: | |
| det['ocr_text'] = [] | |
| det['part_number']= "N/A" | |
| updated.append(det) | |
| continue | |
| x1, y1, x2, y2 = det['bbox'] | |
| x1p = max(0, x1 - PADDING) | |
| y1p = max(0, y1 - PADDING) | |
| x2p = min(iw, x2 + PADDING) | |
| y2p = min(ih, y2 + PADDING) | |
| patch = img[y1p:y2p, x1p:x2p] | |
| if patch.size == 0: | |
| det['ocr_text'] = [] | |
| det['part_number'] = "unknown" | |
| updated.append(det) | |
| continue | |
| texts = read_text_from_patch(patch) | |
| combined = " ".join(t for t, c in texts).strip() | |
| det['ocr_text'] = texts | |
| det['part_number'] = combined if combined else "unknown" | |
| if texts: | |
| print(f" [{label}] @ ({x1},{y1}) β '{combined}'") | |
| else: | |
| print(f" [{label}] @ ({x1},{y1}) β (no text detected)") | |
| updated.append(det) | |
| return updated | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # PRINT OCR SUMMARY | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| def print_ocr_summary(detections: list): | |
| ic_dets = [d for d in detections if d['label'] in IC_LABELS] | |
| identified = [d for d in ic_dets if d.get('part_number', 'unknown') not in ('unknown', 'N/A', '')] | |
| print(f"\n-- OCR Summary ---------------------------") | |
| for det in ic_dets: | |
| label = det['label'] | |
| part = det.get('part_number', 'unknown') | |
| conf = det['confidence'] | |
| hits = len(det.get('ocr_text', [])) | |
| print(f" {label:<15} | part: {part:<30} | conf: {conf:.0%} | ocr hits: {hits}") | |
| print(f"\n Total ICs/chips : {len(ic_dets)}") | |
| print(f" Text identified : {len(identified)}") | |
| print(f"------------------------------------------\n") | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| # ENTRY POINT | |
| # βββββββββββββββββββββββββββββββββββββββββββββ | |
| if __name__ == "__main__": | |
| if len(sys.argv) < 3: | |
| print("Usage: python ocr.py <image_path> <results_json>") | |
| print("Example: python ocr.py sample5.jpg sample5_results.json") | |
| sys.exit(1) | |
| image_path = sys.argv[1] | |
| results_json = sys.argv[2] | |
| with open(results_json) as f: | |
| data = json.load(f) | |
| detections = data.get("components", []) | |
| for d in detections: | |
| d['bbox'] = tuple(d['bbox']) | |
| print(f"[OK] Loaded {len(detections)} detections from {results_json}") | |
| updated = run_ocr_on_detections(image_path, detections) | |
| print_ocr_summary(updated) | |
| # Save updated JSON | |
| base = os.path.splitext(results_json)[0] | |
| out_path = base + "_ocr.json" | |
| out_data = { | |
| "total_components": len(updated), | |
| "components": [ | |
| {**d, | |
| "bbox": list(d["bbox"]), | |
| "ocr_text": [[t, c] for t, c in d.get("ocr_text", [])]} | |
| for d in updated | |
| ] | |
| } | |
| with open(out_path, "w") as f: | |
| json.dump(out_data, f, indent=2) | |
| print(f"[OK] Updated results saved: {out_path}") |