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
|