Skip to content

Commit 7de665b

Browse files
author
Oyeesh Mann Singh
committed
Initial files for deployment
1 parent 3c3d8fa commit 7de665b

10 files changed

+603
-13
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.venv
2+
flagged/

README.md

+37-13
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,37 @@
1-
---
2-
title: Checkbox Text Extraction
3-
emoji: 🔥
4-
colorFrom: blue
5-
colorTo: blue
6-
sdk: gradio
7-
sdk_version: 4.36.1
8-
app_file: app.py
9-
pinned: false
10-
license: apache-2.0
11-
---
12-
13-
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
1+
# Checkbox Detection and Text Extraction
2+
3+
## Introduction
4+
5+
- This tool helps you to extract the checked text (The text next to the checked box).
6+
- It is built on top of [Checkbox-Detection](https://github.com/LynnHaDo/Checkbox-Detection) by [LynnHaDo](https://github.com/LynnHaDo).
7+
- It uses the **Euclidean Distance** to extract the checked text following the result from checkbox detection model and the OCR
8+
![Checkbox Text Extraction](images/checked_text_extraction.png "Checkbox Text Extraction")
9+
10+
## Workflow
11+
![Checkbox Detection and corresponding text extraction](images/checkbox_detection_workflow.png "Checkbox Detection and Text Extraction Workflow")
12+
13+
## Installation
14+
15+
# Create virtual environment
16+
python3.11 -m venv .venv
17+
18+
# Activate the virtual environment
19+
source .venv/bin/activate
20+
21+
# Install the requirements from requirements.txt
22+
pip install -r requirements.txt
23+
24+
# Install poppler-utils
25+
sudo apt-get install poppler-utils
26+
27+
## Demo
28+
29+
This is currently deployed in [HuggingFace Spaces](https://huggingface.co/spaces/oyashi163/checkbox_text_extraction)
30+
31+
## Note
32+
33+
Please use the sample image provided in `images` folder for testing purpose.
34+
You might want to modify the OCR parameters for your image to get more accurate later.
35+
36+
## References
37+
- https://github.com/LynnHaDo/Checkbox-Detection

app.py

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import math
2+
import gradio as gr
3+
import easyocr
4+
import cv2
5+
from ultralytics import YOLO
6+
7+
# Load OCR model into memory
8+
reader = easyocr.Reader(['en']) # this needs to run only once to load the model into memory
9+
10+
# Define constants
11+
BOX_COLORS = {
12+
"unchecked": (242, 48, 48),
13+
"checked": (38, 115, 101),
14+
"block": (242, 159, 5)
15+
}
16+
BOX_PADDING = 2
17+
18+
# Load models
19+
DETECTION_MODEL = YOLO("models/detector-model.pt")
20+
21+
def detect_checkbox(image_path):
22+
"""
23+
Output inference image with bounding box
24+
Args:
25+
- image: to check for checkboxes
26+
Return: image with bounding boxes drawn and box coordinates
27+
"""
28+
image = cv2.imread(image_path)
29+
if image is None:
30+
return image
31+
32+
# Predict on image
33+
results = DETECTION_MODEL.predict(source=image, conf=0.1, iou=0.8) # Predict on image
34+
boxes = results[0].boxes # Get bounding boxes
35+
36+
if len(boxes) == 0:
37+
return image
38+
39+
box_coordinates = []
40+
41+
# Get bounding boxes
42+
for box in boxes:
43+
detection_class_conf = round(box.conf.item(), 2)
44+
detection_class = list(BOX_COLORS)[int(box.cls)]
45+
# Get start and end points of the current box
46+
start_box = (int(box.xyxy[0][0]), int(box.xyxy[0][1]))
47+
end_box = (int(box.xyxy[0][2]), int(box.xyxy[0][3]))
48+
box = image[start_box[1]:end_box[1], start_box[0]: end_box[0], :]
49+
50+
if detection_class == 'checked':
51+
box_coordinates.append((start_box, end_box))
52+
53+
# 01. DRAW BOUNDING BOX OF OBJECT
54+
line_thickness = round(0.002 * (image.shape[0] + image.shape[1]) / 2) + 1
55+
image = cv2.rectangle(img=image,
56+
pt1=start_box,
57+
pt2=end_box,
58+
color=BOX_COLORS['checked'],
59+
thickness = line_thickness) # Draw the box with predefined colors
60+
61+
image = cv2.putText(img=image, org=start_box, text=detection_class, fontFace=0, color=(0,0,0), fontScale=line_thickness/3)
62+
63+
# 02. DRAW LABEL
64+
text = str(detection_class_conf)
65+
# Get text dimensions to draw wrapping box
66+
font_thickness = max(line_thickness - 1, 1)
67+
(text_w, text_h), _ = cv2.getTextSize(text=text, fontFace=2, fontScale=line_thickness/3, thickness=font_thickness)
68+
# Draw wrapping box for text
69+
image = cv2.rectangle(img=image,
70+
pt1=(start_box[0], start_box[1] - text_h - BOX_PADDING*2),
71+
pt2=(start_box[0] + text_w + BOX_PADDING * 2, start_box[1]),
72+
color=BOX_COLORS['checked'],
73+
thickness=-1)
74+
# Put class name on image
75+
start_text = (start_box[0] + BOX_PADDING, start_box[1] - BOX_PADDING)
76+
image = cv2.putText(img=image, text=text, org=start_text, fontFace=0, color=(255,255,255), fontScale=line_thickness/3, thickness=font_thickness)
77+
78+
return image, box_coordinates
79+
80+
def euclidean_distance(coord1, coord2):
81+
return math.sqrt((coord1[0] - coord2[0])**2 + (coord1[1] - coord2[1])**2)
82+
83+
def nearest_coordinate(target_coord, coordinates):
84+
min_distance = float('inf')
85+
nearest_coord = None
86+
87+
for coord in coordinates:
88+
distance = euclidean_distance(target_coord, coord)
89+
if distance < min_distance:
90+
min_distance = distance
91+
nearest_coord = coord
92+
93+
94+
return nearest_coord, euclidean_distance(target_coord, nearest_coord)
95+
96+
def checkbox_text_extract(image_filename):
97+
checkbox_img, checkbox_coordinates = detect_checkbox(image_filename)
98+
99+
result = reader.readtext(image_filename, decoder = 'beamsearch',
100+
text_threshold = 0.8, low_text = 0.2, link_threshold = 0.4,
101+
canvas_size = 1500, mag_ratio = 1.5,
102+
slope_ths = 0.1, ycenter_ths = 0.8, height_ths = 0.8,
103+
width_ths = 1.0, y_ths = 0.8, x_ths = 1.0, add_margin = 0.1)
104+
105+
# Get the bottom right coordinates of the CHECKED checkbox
106+
checkbox_bottom_right_coord = []
107+
108+
for each in checkbox_coordinates:
109+
checkbox_bottom_right_coord.append((each[1][0], each[0][1]))
110+
111+
# Sort based on the coordinates
112+
checkbox_bottom_right_coord = sorted(checkbox_bottom_right_coord, key=lambda point: point[1])
113+
114+
detected_text = {}
115+
116+
for index, each in enumerate(result):
117+
x_coord = int(each[0][0][0])
118+
y_coord = int(each[0][0][1])
119+
detected_text[(x_coord, y_coord)] = each[1]
120+
121+
checked_text = ''
122+
for each_checkbox_coord in checkbox_bottom_right_coord:
123+
nearest, distance = nearest_coordinate(each_checkbox_coord, list(detected_text.keys()))
124+
if distance <= 15:
125+
checked_text += f"- {detected_text[nearest]}\n"
126+
127+
return checked_text
128+
129+
130+
iface = gr.Interface(fn=checkbox_text_extract,
131+
inputs=gr.Image(label="Upload image having checkboxes and text", type="filepath"),
132+
outputs=gr.Markdown())
133+
134+
iface.launch()

checkbox_detection.ipynb

+401
Large diffs are not rendered by default.
55.7 KB
Loading

images/checked_text_extraction.png

63.8 KB
Loading
151 KB
Loading

images/sample.png

134 KB
Loading

models/detector-model.pt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:9c7c3b6f1858e045cec68db2fd3b2c28c6d67fa937ed34cf2a20231dc3ea2e8e
3+
size 87617854

requirements.txt

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
certifi==2022.9.24
2+
chardet==5.1.0
3+
charset-normalizer==3.0.1
4+
cryptography==38.0.4
5+
distlib==0.3.6
6+
filelock==3.9.0
7+
GDAL==3.6.2
8+
httplib2==0.20.4
9+
idna==3.3
10+
ntpsec==1.2.2
11+
numpy==1.24.2
12+
pipenv==2022.12.19
13+
platformdirs==2.6.0
14+
pycurl==7.45.2
15+
pyOpenSSL==23.0.0
16+
pyparsing==3.0.9
17+
PySimpleSOAP==1.16.2
18+
python-apt==2.6.0
19+
python-debian==0.1.49
20+
python-debianbts==4.0.1
21+
reportbug==12.0.0
22+
requests==2.28.1
23+
six==1.16.0
24+
urllib3==1.26.12
25+
virtualenv==20.17.1+ds
26+
virtualenv-clone==0.3.0

0 commit comments

Comments
 (0)