Spaces:
Sleeping
Sleeping
Commit
Β·
770c2ad
1
Parent(s):
3d46820
scaffold
Browse files- app.py +138 -4
- requirements.txt +6 -0
app.py
CHANGED
@@ -1,7 +1,141 @@
|
|
1 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
2 |
|
3 |
-
|
4 |
-
|
|
|
|
|
|
|
|
|
5 |
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
+
import cv2
|
3 |
+
import pytesseract
|
4 |
+
import numpy as np
|
5 |
+
import re
|
6 |
+
import requests
|
7 |
|
8 |
+
# Align image using OpenCV
|
9 |
+
def align_form_from_image(image):
|
10 |
+
img = cv2.cvtColor(np.array(image), cv2.COLOR_RGB2BGR)
|
11 |
+
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
12 |
+
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
13 |
+
edged = cv2.Canny(blurred, 75, 200)
|
14 |
|
15 |
+
contours, _ = cv2.findContours(edged.copy(), cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
|
16 |
+
contours = sorted(contours, key=cv2.contourArea, reverse=True)[:5]
|
17 |
+
doc_cnts = None
|
18 |
+
for c in contours:
|
19 |
+
peri = cv2.arcLength(c, True)
|
20 |
+
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
|
21 |
+
if len(approx) == 4:
|
22 |
+
doc_cnts = approx
|
23 |
+
break
|
24 |
+
|
25 |
+
if doc_cnts is not None:
|
26 |
+
pts = doc_cnts.reshape(4, 2)
|
27 |
+
rect = order_points(pts)
|
28 |
+
dst = np.array([[0, 0], [800, 0], [800, 1000], [0, 1000]], dtype="float32")
|
29 |
+
M = cv2.getPerspectiveTransform(rect, dst)
|
30 |
+
aligned = cv2.warpPerspective(img, M, (800, 1000))
|
31 |
+
else:
|
32 |
+
aligned = img
|
33 |
+
|
34 |
+
rgb = cv2.cvtColor(aligned, cv2.COLOR_BGR2RGB)
|
35 |
+
text = pytesseract.image_to_string(rgb)
|
36 |
+
return rgb, text
|
37 |
+
|
38 |
+
def order_points(pts):
|
39 |
+
rect = np.zeros((4, 2), dtype="float32")
|
40 |
+
s = pts.sum(axis=1)
|
41 |
+
rect[0] = pts[np.argmin(s)]
|
42 |
+
rect[2] = pts[np.argmax(s)]
|
43 |
+
diff = np.diff(pts, axis=1)
|
44 |
+
rect[1] = pts[np.argmin(diff)]
|
45 |
+
rect[3] = pts[np.argmax(diff)]
|
46 |
+
return rect
|
47 |
+
|
48 |
+
# Extract fields from driver payout form
|
49 |
+
def parse_driver_payout_custom(text: str):
|
50 |
+
def find_checkbox(label_yes, label_no):
|
51 |
+
yes = re.search(label_yes + r"\s*[:]?[\s\S]{0,20}?β", text)
|
52 |
+
no = re.search(label_no + r"\s*[:]?[\s\S]{0,20}?β", text)
|
53 |
+
if yes and not no:
|
54 |
+
return "Yes"
|
55 |
+
elif no and not yes:
|
56 |
+
return "No"
|
57 |
+
elif yes and no:
|
58 |
+
return "Both Checked"
|
59 |
+
return "Unchecked"
|
60 |
+
|
61 |
+
data = {
|
62 |
+
"date": re.search(r"Date[:\s]*([\d/]+)", text),
|
63 |
+
"time": re.search(r"Time[:\s]*([\d:]+)", text),
|
64 |
+
"name": re.search(r"Name[:\s]*([A-Za-z ]+)", text),
|
65 |
+
"email": re.search(r"Email[:\s]*(\S+@\S+)?", text),
|
66 |
+
"phone": re.search(r"Phone Number[:\s]*(\d{3}[- ]\d{3}[- ]\d{4})", text),
|
67 |
+
"service_type": None,
|
68 |
+
"w9_filled_out": find_checkbox("Yes", "No"),
|
69 |
+
"payment_received": re.search(r"\$\s?([\d.]+)", text),
|
70 |
+
"payout": "Payout Now" if "Payout Now" in text and "β" in text.split("Payout Now")[1][:10] else (
|
71 |
+
"Payout Later" if "Payout Later" in text and "β" in text.split("Payout Later")[1][:10] else None),
|
72 |
+
"team_member": re.search(r"Team Member's Name[:\s]*([A-Za-z]+)", text),
|
73 |
+
"uploaded_to_drive": re.search(r"Uploaded to the Drive\?\s*(Yes|No)", text, re.IGNORECASE),
|
74 |
+
}
|
75 |
+
|
76 |
+
for service in ["Taxi", "Limo", "Uber", "Lyft", "Other"]:
|
77 |
+
if f"{service}" in text and "β" in text.split(service)[1][:10]:
|
78 |
+
data["service_type"] = service
|
79 |
+
|
80 |
+
return {k: v.group(1).strip() if v else v for k, v in data.items()}
|
81 |
+
|
82 |
+
# Send to webhook
|
83 |
+
def send_to_webhook(webhook, *field_values):
|
84 |
+
data = {f"field_{i}": val for i, val in enumerate(field_values)}
|
85 |
+
try:
|
86 |
+
resp = requests.post(webhook, json=data)
|
87 |
+
return f"β
Sent! Status: {resp.status_code}"
|
88 |
+
except Exception as e:
|
89 |
+
return f"β Failed: {str(e)}"
|
90 |
+
|
91 |
+
# Launch Gradio app
|
92 |
+
with gr.Blocks() as demo:
|
93 |
+
gr.Markdown("# π Driver Payout Form OCR β Webhook")
|
94 |
+
|
95 |
+
webhook_url = gr.State("https://example.com/webhook")
|
96 |
+
|
97 |
+
with gr.Row():
|
98 |
+
image_input = gr.Image(type="pil", label="Upload or Take Photo", source="upload")
|
99 |
+
aligned_image = gr.Image(type="numpy", label="Aligned Image")
|
100 |
+
|
101 |
+
form_type = gr.Radio(["Driver Payout"], label="Form Type", value="Driver Payout")
|
102 |
+
raw_text_output = gr.Textbox(label="OCR Text", lines=8)
|
103 |
+
parsed_json = gr.JSON(label="Parsed Fields")
|
104 |
+
|
105 |
+
editable_fields_group = gr.Group(visible=False)
|
106 |
+
editable_fields = []
|
107 |
+
for i in range(12): # max 12 fields
|
108 |
+
tb = gr.Textbox(label=f"Field {i+1}")
|
109 |
+
editable_fields.append(tb)
|
110 |
+
editable_fields_group.children = editable_fields
|
111 |
+
|
112 |
+
status_output = gr.Textbox(label="Webhook Response")
|
113 |
+
|
114 |
+
def process_image(image, form_type):
|
115 |
+
aligned, text = align_form_from_image(image)
|
116 |
+
parsed = parse_driver_payout_custom(text)
|
117 |
+
visible = gr.update(visible=True)
|
118 |
+
values = list(parsed.values()) + [""] * (12 - len(parsed))
|
119 |
+
return aligned, text, parsed, visible, values[:12]
|
120 |
+
|
121 |
+
process_btn = gr.Button("OCR + Parse")
|
122 |
+
process_btn.click(
|
123 |
+
fn=process_image,
|
124 |
+
inputs=[image_input, form_type],
|
125 |
+
outputs=[aligned_image, raw_text_output, parsed_json, editable_fields_group] + editable_fields
|
126 |
+
)
|
127 |
+
|
128 |
+
send_btn = gr.Button("Send to Webhook")
|
129 |
+
send_btn.click(
|
130 |
+
fn=send_to_webhook,
|
131 |
+
inputs=[webhook_url] + editable_fields,
|
132 |
+
outputs=status_output
|
133 |
+
)
|
134 |
+
|
135 |
+
with gr.Accordion("Admin Settings", open=False):
|
136 |
+
webhook_input = gr.Textbox(label="Set Webhook URL")
|
137 |
+
set_webhook_btn = gr.Button("Save Webhook")
|
138 |
+
set_webhook_btn.click(lambda url: url, inputs=webhook_input, outputs=webhook_url)
|
139 |
+
|
140 |
+
if __name__ == "__main__":
|
141 |
+
demo.launch()
|
requirements.txt
ADDED
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
gradio>=4.0.0
|
2 |
+
opencv-python
|
3 |
+
pytesseract
|
4 |
+
numpy
|
5 |
+
requests
|
6 |
+
Pillow
|