diff --git a/Data/Modules/data_collector.py b/Data/Modules/data_collector.py index 713e7bf..74d025c 100644 --- a/Data/Modules/data_collector.py +++ b/Data/Modules/data_collector.py @@ -32,7 +32,7 @@ pytesseract.pytesseract.tesseract_cmd = r"C:\\Program Files\\Tesseract-OCR\\tess DEFAULT_WIDTH = 180 DEFAULT_HEIGHT = 130 -HANDLE_SIZE = 8 +HANDLE_SIZE = 5 LABEL_HEIGHT = 20 collector_mutex = QMutex() @@ -196,9 +196,6 @@ def find_word_positions(region_id, word, offset_x=0, offset_y=0, margin=5, ocr_e print(f"[ERROR] Failed to capture OCR region: {e}") return [] - - - def draw_identification_boxes(region_id, positions, color=(0, 0, 255), thickness=2): """ Draws non-interactive rectangles at specified positions within the given OCR region. @@ -236,6 +233,10 @@ class OCRRegionWidget(QWidget): self.previous_positions = [] # This prevents redundant redraws self.num_slices = 1 # Ensures slice count is initialized + # --- Initialization for interactive handles --- + self.selected_handle = None # Tracks which handle is being dragged/resized + self.drag_offset = None # Tracks the offset for moving the widget + self.show() def paintEvent(self, event): @@ -260,6 +261,13 @@ class OCRRegionWidget(QWidget): for i in range(1, self.num_slices): # Do not draw the last one at the bottom painter.drawLine(0, i * strip_height, self.width(), i * strip_height) + + # --- Draw interactive handles (grabbers) with reduced opacity (15%) --- + # 15% opacity of 255 is approximately 38 + handle_color = QColor(0, 0, 0, 50) + for handle in self._resize_handles(): + painter.fillRect(handle, handle_color) + painter.drawRect(handle) # Optional: draw a border around the handle def set_draw_positions(self, positions, color, thickness): """ @@ -292,57 +300,99 @@ class OCRRegionWidget(QWidget): self.update() def _resize_handles(self): + """ + Returns a list of QRect objects representing the interactive handles: + - Index 0: Top-left (resize) + - Index 1: Top-right (resize) + - Index 2: Bottom-left (resize) + - Index 3: Bottom-right (resize) + - Index 4: Top-center (dragger) + """ 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 + handles = [ + QRect(0, 0, HANDLE_SIZE, HANDLE_SIZE), # Top-left + QRect(w - HANDLE_SIZE, 0, HANDLE_SIZE, HANDLE_SIZE), # Top-right + QRect(0, h - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE), # Bottom-left + QRect(w - HANDLE_SIZE, h - HANDLE_SIZE, HANDLE_SIZE, HANDLE_SIZE) # Bottom-right ] + # Top-center handle: centered along the top edge + top_center_x = (w - HANDLE_SIZE) // 2 + top_center = QRect(top_center_x, 0, HANDLE_SIZE, HANDLE_SIZE) + handles.append(top_center) + return handles def mousePressEvent(self, event): if event.button() == Qt.LeftButton: + # Check if any handle (including the new top-center) is clicked for i, handle in enumerate(self._resize_handles()): if handle.contains(event.pos()): self.selected_handle = i + # For the top-center handle (index 4), initialize drag offset for moving + if i == 4: + self.drag_offset = event.pos() return - + # If no handle is clicked, allow dragging by clicking anywhere in the widget 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 self.selected_handle == 4: + # --- Top-center handle dragging --- + 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() + self.update() + else: + # --- Resizing logic for corner handles --- + if self.selected_handle == 0: # Top-left + new_w = self.width() + (self.x() - event.globalX()) + new_h = self.height() + (self.y() - event.globalY()) + new_x = event.globalX() + new_y = event.globalY() + elif self.selected_handle == 1: # Top-right + new_w = event.globalX() - self.x() + new_h = self.height() + (self.y() - event.globalY()) + new_x = self.x() + new_y = event.globalY() + elif self.selected_handle == 2: # Bottom-left + new_w = self.width() + (self.x() - event.globalX()) + new_h = event.globalY() - self.y() + new_x = event.globalX() + new_y = self.y() + elif self.selected_handle == 3: # Bottom-right + new_w = event.globalX() - self.x() + new_h = event.globalY() - self.y() + new_x = self.x() + new_y = self.y() + 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() + 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: + # --- General widget dragging (if no handle was clicked) --- 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): + """ + Resets the drag/resize state once the mouse button is released. + """ + self.selected_handle = None + self.drag_offset = None