# Modules/data_collector.py import threading import time import re import sys import numpy as np import cv2 import pytesseract from PIL import Image, ImageGrab, ImageFilter from PyQt5.QtWidgets import QApplication, QWidget from PyQt5.QtCore import QRect, QPoint, Qt, QMutex, QTimer from PyQt5.QtGui import QPainter, QPen, QColor, QFont pytesseract.pytesseract.tesseract_cmd = r"C:\Program Files\Tesseract-OCR\tesseract.exe" DEFAULT_WIDTH = 180 DEFAULT_HEIGHT = 130 HANDLE_SIZE = 8 LABEL_HEIGHT = 20 collector_mutex = QMutex() regions = {} app_instance = None def _ensure_qapplication(): """ Ensures that QApplication is initialized before creating widgets. """ global app_instance if QApplication.instance() is None: app_instance = QApplication(sys.argv) threading.Thread(target=app_instance.exec_, daemon=True).start() def create_ocr_region(region_id, x=250, y=50, w=DEFAULT_WIDTH, h=DEFAULT_HEIGHT): """ Creates an OCR region with a visible, resizable box on the screen. """ _ensure_qapplication() # Ensure QApplication is running first collector_mutex.lock() if region_id in regions: collector_mutex.unlock() return regions[region_id] = { 'bbox': [x, y, w, h], 'raw_text': "", 'widget': OCRRegionWidget(x, y, w, h, region_id) } collector_mutex.unlock() def get_raw_text(region_id): collector_mutex.lock() if region_id not in regions: collector_mutex.unlock() return "" text = regions[region_id]['raw_text'] collector_mutex.unlock() return text def start_collector(): t = threading.Thread(target=_update_ocr_loop, daemon=True) t.start() def _update_ocr_loop(): while True: collector_mutex.lock() region_ids = list(regions.keys()) collector_mutex.unlock() for rid in region_ids: collector_mutex.lock() bbox = regions[rid]['bbox'][:] collector_mutex.unlock() x, y, w, h = bbox screenshot = ImageGrab.grab(bbox=(x, y, x + w, y + h)) processed = _preprocess_image(screenshot) raw_text = pytesseract.image_to_string(processed, config='--psm 4 --oem 1') collector_mutex.lock() if rid in regions: regions[rid]['raw_text'] = raw_text collector_mutex.unlock() # print(f"OCR Text for {rid}: {raw_text}") # SHOW RAW OCR OUTPUT IN TERMINAL FOR DEBUGGING time.sleep(0.7) def _preprocess_image(image): gray = image.convert("L") scaled = gray.resize((gray.width * 3, gray.height * 3)) thresh = scaled.point(lambda p: 255 if p > 200 else 0) return thresh.filter(ImageFilter.MedianFilter(3)) class OCRRegionWidget(QWidget): def __init__(self, x, y, w, h, region_id): super().__init__() self.setGeometry(x, y, w, h) self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint | Qt.Tool) self.setAttribute(Qt.WA_TranslucentBackground, True) self.setAttribute(Qt.WA_TransparentForMouseEvents, False) self.drag_offset = None self.selected_handle = None self.region_id = region_id self.show() def paintEvent(self, event): painter = QPainter(self) pen = QPen(QColor(0, 0, 255)) pen.setWidth(3) painter.setPen(pen) # Draw main rectangle painter.drawRect(0, 0, self.width(), self.height()) # Draw resize handles painter.setBrush(QColor(0, 0, 255)) for handle in self._resize_handles(): painter.drawRect(handle) def _resize_handles(self): w, h = self.width(), self.height() return [ QRect(0, 0, HANDLE_SIZE, HANDLE_SIZE), # Top-left QRect(w - HANDLE_SIZE, h - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE) # Bottom-right ] def mousePressEvent(self, event): if event.button() == Qt.LeftButton: for i, handle in enumerate(self._resize_handles()): if handle.contains(event.pos()): self.selected_handle = i return self.drag_offset = event.pos() def mouseMoveEvent(self, event): if self.selected_handle is not None: w, h = self.width(), self.height() if self.selected_handle == 0: # Top-left new_w = w + (self.x() - event.globalX()) new_h = h + (self.y() - event.globalY()) new_x = event.globalX() new_y = event.globalY() if new_w < 20: new_w = 20 if new_h < 20: new_h = 20 self.setGeometry(new_x, new_y, new_w, new_h) elif self.selected_handle == 1: # Bottom-right new_w = event.globalX() - self.x() new_h = event.globalY() - self.y() if new_w < 20: new_w = 20 if new_h < 20: new_h = 20 self.setGeometry(self.x(), self.y(), new_w, new_h) collector_mutex.lock() if self.region_id in regions: regions[self.region_id]['bbox'] = [self.x(), self.y(), self.width(), self.height()] collector_mutex.unlock() self.update() elif self.drag_offset: new_x = event.globalX() - self.drag_offset.x() new_y = event.globalY() - self.drag_offset.y() self.move(new_x, new_y) collector_mutex.lock() if self.region_id in regions: regions[self.region_id]['bbox'] = [new_x, new_y, self.width(), self.height()] collector_mutex.unlock() def mouseReleaseEvent(self, event): self.selected_handle = None self.drag_offset = None