diff --git a/README.md b/README.md index e386be9..df235dd 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,7 @@ OpenCV from JetPack repos (4.x) usually ships without CUDA dnn. To use CUDA, bui - `RETURN_BOXES` (default `true`) - `USE_CUDA_DNN` (default `false`, set `true` to attempt CUDA backend for EAST if OpenCV built with CUDA) - `DEBUG_VISUAL` (default `false`; if true, response includes `debug_image_base64`. Per-request: add `?debug=1` or form field `debug=1`.) -- `SHOW_GUI` (default `false`; if true and OpenCV GUI available, service opens a window with overlays. Requires X/Wayland/VDI that supports GUI.) +- `SHOW_GUI` (default `false`; if true and OpenCV GUI available, service opens a text-centric window: left = detected text regions, right = OCR block text/conf. Requires X/Wayland/VDI that supports GUI.) ## Run ```bash diff --git a/__pycache__/app.cpython-313.pyc b/__pycache__/app.cpython-313.pyc index c0b5dab..bf2035c 100644 Binary files a/__pycache__/app.cpython-313.pyc and b/__pycache__/app.cpython-313.pyc differ diff --git a/app.py b/app.py index 7d9fd1a..e160a8f 100644 --- a/app.py +++ b/app.py @@ -235,6 +235,90 @@ def build_debug_image(img_rgb, boxes, blocks, objects, target_width=1024, return except Exception: return None, None + +def build_text_gui_image(img_rgb, boxes, blocks, target_width=1600): + """Build a text-centric GUI frame: regions on left, OCR lines on right.""" + if cv2 is None or img_rgb is None: + return None + try: + src = cv2.cvtColor(img_rgb, cv2.COLOR_RGB2BGR) + h, w = src.shape[:2] + frame_w = target_width if target_width and target_width > 0 else 1600 + if frame_w < 900: + frame_w = 900 + + left_w = int(frame_w * 0.64) + scale = float(left_w) / float(max(1, w)) + left_h = max(360, int(h * scale)) + left = cv2.resize(src, (left_w, left_h), interpolation=cv2.INTER_AREA if scale < 1.0 else cv2.INTER_CUBIC) + + # text detector regions + for idx, b in enumerate(boxes or []): + try: + x1, y1, x2, y2 = [int(v * scale) for v in b] + except Exception: + continue + cv2.rectangle(left, (x1, y1), (x2, y2), (255, 90, 0), 2) + cv2.putText(left, "T{}".format(idx + 1), (x1, max(12, y1 - 4)), + cv2.FONT_HERSHEY_SIMPLEX, 0.45, (255, 90, 0), 1, cv2.LINE_AA) + + # OCR blocks (where text was actually extracted) + for idx, blk in enumerate(blocks or []): + try: + x1, y1, x2, y2 = [int(v * scale) for v in blk.get("bbox", [0, 0, 0, 0])] + except Exception: + continue + cv2.rectangle(left, (x1, y1), (x2, y2), (0, 160, 255), 2) + cv2.putText(left, "R{}".format(idx + 1), (x1, min(left_h - 8, y2 + 14)), + cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 160, 255), 1, cv2.LINE_AA) + + right_w = frame_w - left_w + if right_w < 320: + right_w = 320 + left = cv2.resize(left, (frame_w - right_w, left_h)) + left_w = left.shape[1] + right = np.ones((left_h, right_w, 3), dtype=np.uint8) * 245 + + y = 28 + line_h = 22 + cv2.putText(right, "OCR text panel", (14, y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (30, 30, 30), 2, cv2.LINE_AA) + y += line_h + 4 + cv2.putText(right, "detected regions: {}".format(len(boxes or [])), (14, y), + cv2.FONT_HERSHEY_SIMPLEX, 0.55, (70, 70, 70), 1, cv2.LINE_AA) + y += line_h + cv2.putText(right, "recognized blocks: {}".format(len(blocks or [])), (14, y), + cv2.FONT_HERSHEY_SIMPLEX, 0.55, (70, 70, 70), 1, cv2.LINE_AA) + y += line_h + 4 + cv2.line(right, (12, y), (right_w - 12, y), (200, 200, 200), 1) + y += 18 + + if not blocks: + cv2.putText(right, "No OCR text blocks", (14, y), + cv2.FONT_HERSHEY_SIMPLEX, 0.56, (100, 100, 100), 1, cv2.LINE_AA) + else: + visible = blocks[:10] + max_chars = max(18, int((right_w - 24) / 8)) + for idx, blk in enumerate(visible): + conf = blk.get("conf", -1) + conf_str = "{:.1f}".format(conf) if isinstance(conf, (float, int)) and conf >= 0 else "n/a" + header = "#{:02d} conf={}".format(idx + 1, conf_str) + cv2.putText(right, header, (14, y), cv2.FONT_HERSHEY_SIMPLEX, 0.50, (20, 20, 20), 1, cv2.LINE_AA) + y += line_h - 2 + raw = (blk.get("text", "") or "").replace("\n", " ").replace("\r", " ").strip() + txt = " ".join(raw.split()) + if len(txt) > max_chars: + txt = txt[:max_chars - 1] + "..." + cv2.putText(right, txt if txt else "", (14, y), + cv2.FONT_HERSHEY_SIMPLEX, 0.52, (30, 30, 30), 1, cv2.LINE_AA) + y += line_h + if y > left_h - 18: + break + + frame = np.concatenate([left, right], axis=1) + return frame + except Exception: + return None + # ---- Routes ---- @app.route('/health', methods=['GET']) @@ -382,9 +466,18 @@ def image2text(): target_width=target_w, return_image=SHOW_GUI, ) - if SHOW_GUI and cv2 is not None and debug_image_arr is not None: + if SHOW_GUI and cv2 is not None: try: - _gui_show(debug_image_arr) + gui_frame = build_text_gui_image( + img_rgb, + boxes, + blocks if RETURN_BOXES else [], + target_width=GUI_TARGET_WIDTH, + ) + if gui_frame is None: + gui_frame = debug_image_arr + if gui_frame is not None: + _gui_show(gui_frame) except Exception: pass