Download app.py from ankandrew/ppocrv6: direct link, hf CLI and curl.
- Browser
- Download file 4.66 kB
-
https://huggingface.co/spaces/ankandrew/ppocrv6/resolve/main/app.py
- Command line
-
hf download hf://spaces/ankandrew/ppocrv6/app.py
-
curl -L -o app.py https://huggingface.co/spaces/ankandrew/ppocrv6/resolve/main/app.py
4.66 kB
| from functools import lru_cache | |
| import numpy as np | |
| import gradio as gr | |
| from PIL import Image, ImageDraw | |
| from paddleocr import PaddleOCR | |
| import bbox_align | |
| # Official PP-OCRv6 tiers. A single selector chooses the matching | |
| # detection + recognition pair. | |
| V6_MODELS = { | |
| "Tiny": ("PP-OCRv6_tiny_det", "PP-OCRv6_tiny_rec"), | |
| "Small": ("PP-OCRv6_small_det", "PP-OCRv6_small_rec"), | |
| "Medium": ("PP-OCRv6_medium_det", "PP-OCRv6_medium_rec"), | |
| } | |
| def get_ocr(tier): | |
| det_model, rec_model = V6_MODELS[tier] | |
| return PaddleOCR( | |
| text_detection_model_name=det_model, | |
| text_recognition_model_name=rec_model, | |
| use_doc_orientation_classify=False, | |
| use_doc_unwarping=False, | |
| use_textline_orientation=False, | |
| device="cpu", | |
| enable_mkldnn=True, | |
| cpu_threads=2, | |
| ) | |
| def _result_dict(result_obj): | |
| """Normalize PaddleOCR's result object to its inner `res` dictionary.""" | |
| if isinstance(result_obj, dict): | |
| data = result_obj | |
| elif hasattr(result_obj, "json"): | |
| data = result_obj.json | |
| if callable(data): | |
| data = data() | |
| elif hasattr(result_obj, "res"): | |
| return result_obj.res | |
| else: | |
| raise TypeError(f"Unsupported PaddleOCR result type: {type(result_obj)!r}") | |
| return data.get("res", data) | |
| def _group_lines(polys, width, height): | |
| vertices = [ | |
| [(float(x), float(y)) for x, y in np.asarray(poly).reshape(-1, 2)] | |
| for poly in polys | |
| ] | |
| page_boundary = [ | |
| (0.0, 0.0), | |
| (float(width), 0.0), | |
| (float(width), float(height)), | |
| (0.0, float(height)), | |
| ] | |
| groups = bbox_align.process(vertices, page_boundary) | |
| groups = [ | |
| sorted(group, key=lambda i: min(p[0] for p in vertices[i])) | |
| for group in groups | |
| ] | |
| groups.sort( | |
| key=lambda group: min(min(p[1] for p in vertices[i]) for i in group) | |
| ) | |
| return groups, vertices | |
| def _draw_groups(image, groups, vertices): | |
| out = image.copy().convert("RGB") | |
| draw = ImageDraw.Draw(out) | |
| # Thin boxes = OCR detections. | |
| for poly in vertices: | |
| draw.line(poly + [poly[0]], fill="red", width=2) | |
| # Thick boxes = reconstructed logical lines. | |
| for line_no, group in enumerate(groups, start=1): | |
| pts = [p for i in group for p in vertices[i]] | |
| xs = [p[0] for p in pts] | |
| ys = [p[1] for p in pts] | |
| x1, y1, x2, y2 = min(xs), min(ys), max(xs), max(ys) | |
| draw.rectangle((x1, y1, x2, y2), outline="blue", width=4) | |
| draw.text((x1 + 3, max(0, y1 - 14)), str(line_no), fill="blue") | |
| return out | |
| def run_receipt(image, tier): | |
| if image is None: | |
| return None, "Upload a receipt image." | |
| image = image.convert("RGB") | |
| ocr = get_ocr(tier) | |
| results = list(ocr.predict(np.asarray(image))) | |
| if not results: | |
| return image, "No text found." | |
| res = _result_dict(results[0]) | |
| texts = list(res.get("rec_texts", [])) | |
| scores = list(res.get("rec_scores", [])) | |
| polys = list(res.get("rec_polys", [])) | |
| items = [] | |
| for i, (text, poly) in enumerate(zip(texts, polys)): | |
| text = str(text).strip() | |
| if not text: | |
| continue | |
| score = float(scores[i]) if i < len(scores) else 0.0 | |
| items.append((text, score, poly)) | |
| if not items: | |
| return image, "No recognized text found." | |
| texts = [x[0] for x in items] | |
| polys = [x[2] for x in items] | |
| groups, vertices = _group_lines(polys, image.width, image.height) | |
| annotated = _draw_groups(image, groups, vertices) | |
| grouped_text = "\n".join( | |
| f"{line_no:02d}: " + " | ".join(texts[i] for i in group) | |
| for line_no, group in enumerate(groups, start=1) | |
| ) | |
| return annotated, grouped_text | |
| with gr.Blocks(title="PP-OCRv6 Receipt Line Grouping") as demo: | |
| gr.Markdown( | |
| "# Receipt OCR · PP-OCRv6 + bbox-align\n" | |
| "Upload a receipt, choose a PP-OCRv6 tier, and inspect the reconstructed logical lines." | |
| ) | |
| model_tier = gr.Dropdown( | |
| choices=list(V6_MODELS.keys()), | |
| value="Small", | |
| label="PP-OCRv6 model", | |
| ) | |
| with gr.Row(): | |
| input_image = gr.Image(type="pil", label="Receipt") | |
| output_image = gr.Image(type="pil", label="Grouped lines") | |
| output_text = gr.Textbox( | |
| label="Grouped text", | |
| lines=16, | |
| buttons=["copy"], | |
| ) | |
| run_button = gr.Button("Run OCR", variant="primary") | |
| run_button.click( | |
| run_receipt, | |
| inputs=[input_image, model_tier], | |
| outputs=[output_image, output_text], | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch(ssr_mode=False) | |