WofoAI commited on
Commit
2aec074
ยท
verified ยท
1 Parent(s): beff787

Upload 2 files

Browse files
Files changed (2) hide show
  1. README.md +102 -0
  2. app.py +149 -0
README.md ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: KYC OCR (India)
3
+ emoji: ๐Ÿ“„
4
+ colorFrom: blue
5
+ colorTo: green
6
+ sdk: gradio
7
+ sdk_version: "4.44.0"
8
+ python_version: "3.12"
9
+ app_file: app.py
10
+ pinned: false
11
+ license: mit
12
+ short_description: Extract fields from Indian PAN cards and cheques on CPU
13
+ ---
14
+
15
+ # ๐Ÿ‡ฎ๐Ÿ‡ณ KYC Document OCR
16
+
17
+ A lightweight, CPU-friendly pipeline for extracting structured fields from
18
+ Indian KYC documents (PAN card, cheque) from images or PDFs.
19
+
20
+ Built on [RapidOCR](https://github.com/RapidAI/RapidOCR) โ€” ONNX-runtime
21
+ deployment of PaddleOCR PP-OCRv4 models. No GPU needed.
22
+
23
+ ## What it does
24
+
25
+ - **PDF or image input** (PyMuPDF rasterises PDFs at 300 DPI)
26
+ - **Document type auto-detection** via keyword + regex scoring
27
+ - **Cheque rotation correction** โ€” detects sideways-scanned cheques (text
28
+ spans more vertically than horizontally) and rotates before MICR extraction
29
+ - **Format-spec validation** โ€” PAN regex with 4th-character entity-type
30
+ lookup, IFSC regex, date normalisation, MICR positional parse
31
+
32
+ ## Supported documents
33
+
34
+ | Document | Fields extracted |
35
+ |---|---|
36
+ | **PAN card** | PAN number, name, father's name (individual) / company name, DOB / date of incorporation, entity type (Individual / Company / HUF / Firm / Trust / etc.) |
37
+ | **Cheque** | Bank name, branch address, IFSC code, account number, 9-digit MICR code, cheque number, transaction code |
38
+ | **Aadhaar** | _Coming soon โ€” recommended path: read the secure QR code via `pyzbar` rather than OCR_ |
39
+ | **Passport** | _Coming soon โ€” recommended path: parse the MRZ via `passporteye`_ |
40
+
41
+ ## How to use the Space
42
+
43
+ 1. Drop a PDF or image into the upload box
44
+ 2. Click **Extract fields**
45
+ 3. View the structured output and raw OCR (for debugging)
46
+
47
+ The included example is a synthetic PAN with fake data so you can try it
48
+ without uploading real documents.
49
+
50
+ ## Running locally
51
+
52
+ ```bash
53
+ git clone <this-space-url>
54
+ cd <repo>
55
+ pip install -r requirements.txt
56
+ python app.py
57
+ ```
58
+
59
+ Then open `http://127.0.0.1:7860`.
60
+
61
+ ## Using `kyc_extract.py` directly as a library
62
+
63
+ ```python
64
+ from kyc_extract import process, RapidOCR
65
+
66
+ engine = RapidOCR()
67
+ results = process("path/to/document.pdf", engine)
68
+
69
+ for page in results:
70
+ print(page["detected_type"]) # 'pan' or 'cheque'
71
+ print(page["fields"]) # structured dict
72
+ ```
73
+
74
+ ## Privacy
75
+
76
+ This is a public Space. **Do not upload real personal documents.** For
77
+ production KYC pipelines, fork this repo and deploy on your own private
78
+ infrastructure (DPDP Act and UIDAI guidelines require it for Aadhaar in
79
+ particular).
80
+
81
+ ## Architecture notes
82
+
83
+ - All extraction is rule-based โ€” no model is hardcoded to specific names,
84
+ banks, PANs, or addresses. The same script handles any PAN or any bank's
85
+ cheque.
86
+ - Document classification uses keyword + regex scoring; if confidence is
87
+ low, type is `unknown` and no extractor runs.
88
+ - For cheques in portrait-orientation PDFs (common when phones are held
89
+ vertically), the pipeline detects the layout anomaly and re-OCRs the
90
+ rotated image, merging text from both passes so address and IFSC don't
91
+ get lost in the rotation.
92
+
93
+ ## Limitations
94
+
95
+ - English-only OCR โ€” Hindi label text (`เคจเคพเคฎ/Name`) comes out garbled but
96
+ the pipeline has shape-based fallbacks so this doesn't break field
97
+ extraction. For full Hindi extraction, add `lang='hi'` to RapidOCR.
98
+ - MICR fonts are E-13B and not perfectly read by general OCR. The pipeline
99
+ reconstructs the 9-digit MICR from OCR fragments via positional parsing,
100
+ which works on most cheques but degrades on poor scans.
101
+ - Aadhaar and passport extractors are stubs. The proper approach uses QR
102
+ and MRZ respectively, not pure OCR.
app.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ KYC OCR โ€” Gradio app for Hugging Face Spaces.
3
+
4
+ Upload a PAN card, cheque, Aadhaar, or passport (PDF or image) and get
5
+ structured field extraction. Runs on CPU using RapidOCR (ONNX).
6
+ """
7
+ import gradio as gr
8
+ import time
9
+ import traceback
10
+ from pathlib import Path
11
+
12
+ from kyc_extract import process, RapidOCR
13
+
14
+ # โ”€โ”€ Initialise OCR engine once at startup โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
15
+ print("Loading RapidOCR engine (ONNX models)โ€ฆ")
16
+ _t0 = time.time()
17
+ ENGINE = RapidOCR()
18
+ print(f"Ready. Engine init took {time.time() - _t0:.1f}s")
19
+
20
+
21
+ # โ”€โ”€ Pretty-format the extracted fields as Markdown โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
22
+ def _fmt_fields(fields: dict) -> str:
23
+ if not fields:
24
+ return "_No fields extracted._"
25
+ lines = []
26
+ for k, v in fields.items():
27
+ label = k.replace("_", " ").title()
28
+ if isinstance(v, bool):
29
+ v = "โœ… Yes" if v else "โŒ No"
30
+ elif v is None:
31
+ v = "_(not found)_"
32
+ lines.append(f"**{label}:** {v}")
33
+ return "\n\n".join(lines)
34
+
35
+
36
+ def _fmt_info(result: dict) -> str:
37
+ bits = [
38
+ f"**Detected type:** `{result.get('detected_type', 'unknown').upper()}`",
39
+ f"**OCR time:** {result.get('ocr_time_s')}s",
40
+ f"**Rotation applied:** {result.get('rotation_applied', 0)}ยฐ",
41
+ f"**OCR quality score:** {result.get('ocr_score')}",
42
+ ]
43
+ return " โ€ข ".join(bits)
44
+
45
+
46
+ # โ”€โ”€ Main handler โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
47
+ def extract(file):
48
+ if file is None:
49
+ return "_Please upload a file._", "", ""
50
+
51
+ file_path = file if isinstance(file, str) else file.name
52
+
53
+ try:
54
+ pages = process(file_path, ENGINE)
55
+ except Exception as exc:
56
+ return (
57
+ f"โŒ **Error:** {exc}\n\n```\n{traceback.format_exc()}\n```",
58
+ "",
59
+ "",
60
+ )
61
+
62
+ if not pages:
63
+ return "_No pages processed._", "", ""
64
+
65
+ # Render all pages (cheques and PANs are normally single-page; multi-page
66
+ # PDFs of compound KYC packets get each page rendered separately).
67
+ info_blocks, field_blocks, raw_blocks = [], [], []
68
+ for p in pages:
69
+ if "error" in p:
70
+ info_blocks.append(f"### Page {p['page']}\n_{p['error']}_")
71
+ continue
72
+ info_blocks.append(f"### Page {p['page']}\n{_fmt_info(p)}")
73
+ field_blocks.append(f"### Page {p['page']}\n{_fmt_fields(p['fields'])}")
74
+ raw_blocks.append(
75
+ f"โ”โ”โ” Page {p['page']} โ”โ”โ”\n"
76
+ + "\n".join(f"[{c:.2f}] {t}" for c, t in p["_raw_lines"])
77
+ )
78
+
79
+ return (
80
+ "\n\n".join(info_blocks),
81
+ "\n\n---\n\n".join(field_blocks) if field_blocks else "_No fields extracted._",
82
+ "\n\n".join(raw_blocks),
83
+ )
84
+
85
+
86
+ # โ”€โ”€ Gradio UI โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
87
+ DESCRIPTION = """
88
+ # ๐Ÿ‡ฎ๐Ÿ‡ณ KYC Document OCR
89
+
90
+ Extract structured fields from Indian KYC documents โ€” **PAN card**, **cheque**,
91
+ **Aadhaar**, and **passport** โ€” from images or PDFs. Runs entirely on CPU using
92
+ [RapidOCR](https://github.com/RapidAI/RapidOCR) (PaddleOCR PP-OCRv4 models in ONNX).
93
+
94
+ **Supported documents and fields:**
95
+ - **PAN:** PAN number, name, father's name, DOB / date of incorporation, entity type
96
+ - **Cheque:** bank name, address, IFSC, account number, MICR code, cheque number, transaction code
97
+ - **Aadhaar / Passport:** _coming soon โ€” Aadhaar reads best from QR code (`pyzbar`), passport from MRZ (`passporteye`)_
98
+
99
+ **Privacy note:** this is a public Space. Don't upload real personal documents.
100
+ For production use, fork it and run on private infrastructure.
101
+ """
102
+
103
+ with gr.Blocks(title="KYC OCR") as demo:
104
+ gr.Markdown(DESCRIPTION)
105
+
106
+ with gr.Row():
107
+ with gr.Column(scale=1):
108
+ file_input = gr.File(
109
+ label="Upload PAN / Cheque (PDF or image)",
110
+ file_types=[".pdf", ".png", ".jpg", ".jpeg", ".webp"],
111
+ type="filepath",
112
+ )
113
+ submit_btn = gr.Button("Extract fields", variant="primary", size="lg")
114
+ gr.Examples(
115
+ examples=[["examples/synthetic_pan.png"]],
116
+ inputs=file_input,
117
+ label="Example (synthetic PAN with fake data)",
118
+ )
119
+
120
+ with gr.Column(scale=2):
121
+ info_out = gr.Markdown()
122
+ fields_out = gr.Markdown()
123
+
124
+ with gr.Accordion("Raw OCR output (for debugging)", open=False):
125
+ raw_out = gr.Textbox(label="Raw OCR lines with confidence scores",
126
+ lines=20)
127
+
128
+ gr.Markdown(
129
+ "---\n"
130
+ "Pipeline source: `kyc_extract.py`. "
131
+ "All extraction is rule-based โ€” no hardcoded names/banks. "
132
+ "Doc-type detection โ†’ optional rotation correction (cheques) โ†’ "
133
+ "format-spec regex + heuristics + checksum validation."
134
+ )
135
+
136
+ submit_btn.click(
137
+ extract,
138
+ inputs=file_input,
139
+ outputs=[info_out, fields_out, raw_out],
140
+ )
141
+ file_input.change(
142
+ extract,
143
+ inputs=file_input,
144
+ outputs=[info_out, fields_out, raw_out],
145
+ )
146
+
147
+
148
+ if __name__ == "__main__":
149
+ demo.launch(theme=gr.themes.Soft())