File size: 6,575 Bytes
f773ae3
 
 
 
 
 
 
a81d3f4
f773ae3
 
 
 
a81d3f4
 
 
f773ae3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a81d3f4
 
f773ae3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
import os
import io
import cv2
import gradio as gr
import pandas as pd
import easyocr
import numpy as np
from PIL import Image, ImageDraw, ImageFont
from lxml import etree
from ultralytics import YOLO
import tempfile

# Load Arial with bigger size (e.g., 24)
font = ImageFont.truetype("arial.ttf", size=24)

# === Load models ===
model = YOLO("model/NER_66_37_50_V2.pt")  # Update path if needed
reader = easyocr.Reader(['fr'])

# Global store
stored_detections = []
stored_image = None
image_name = "uploaded_image.jpg"

# === Helper Functions ===
def detect_yolo(image_path):
    global stored_detections, stored_image, image_name

    image_name = os.path.basename(image_path)  # <-- Extract just filename
    image_pil = Image.open(image_path).convert("RGB")
    # image_pil = image_pil.convert("RGB")
    stored_image = np.array(image_pil)
    results = model(stored_image)[0]
    draw = ImageDraw.Draw(image_pil)
    detections = []

    for box in results.boxes.data.tolist():
        coords = list(map(int, box[:4]))
        conf = float(box[4])
        cls = int(box[5])
        x1, y1, x2, y2 = coords
        class_name = model.names[cls]

        draw.rectangle([(x1, y1), (x2, y2)], outline="red", width=6)
        draw.text((x1, y1 - 30), class_name, fill="blue", font=font)

        detections.append({
            "image": image_name,
            "class": class_name,
            "x1": x1, "y1": y1,
            "x2": x2, "y2": y2,
            "yolo_conf": round(conf, 4),
            "text": "", "ocr_conf": 0.0
        })

    stored_detections = detections
    return image_pil

def recognize_text():
    global stored_detections, stored_image

    for det in stored_detections:
        x1, y1, x2, y2 = det['x1'], det['y1'], det['x2'], det['y2']
        crop = stored_image[y1:y2, x1:x2]
        ocr_result = reader.readtext(crop, detail=1)

        if ocr_result:
            det['text'] = " ".join([res[1] for res in ocr_result])
            det['ocr_conf'] = round(sum(res[2] for res in ocr_result) / len(ocr_result), 4)
        else:
            det['text'], det['ocr_conf'] = "", 0.0

    summary = "\n".join([f"[{d['class']}] {d['text']} (conf: {d['ocr_conf']})" for d in stored_detections])
    return summary

# def generate_csv():
#     df = pd.DataFrame(stored_detections)
#     csv_buffer = io.StringIO()
#     df.to_csv(csv_buffer, index=False)
#     csv_buffer.seek(0)
#     # return df, gr.File.update(value=csv_buffer, filename="results.csv")
#     return df, "results.csv"

def generate_csv():
    df = pd.DataFrame(stored_detections)
    temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".csv", mode='w', encoding='utf-8', newline='')
    df.to_csv(temp_file.name, index=False)
    temp_file.close()
    # return df, temp_file.name
    csv_filename = os.path.splitext(image_name)[0] + ".csv"
    temp_file_path = os.path.join(tempfile.gettempdir(), csv_filename)
    df.to_csv(temp_file_path, index=False)
    return df, temp_file_path

def generate_alto_xml():
    H, W, _ = stored_image.shape
    NSMAP = {"alto": "http://www.loc.gov/standards/alto/ns-v4#"}
    root = etree.Element("alto", nsmap=NSMAP)

    desc = etree.SubElement(root, "Description")
    etree.SubElement(desc, "MeasurementUnit").text = "pixel"
    etree.SubElement(etree.SubElement(desc, "sourceImageInformation"), "fileName").text = image_name

    layout = etree.SubElement(root, "Layout")
    page = etree.SubElement(layout, "Page", ID="page_1", PHYSICAL_IMG_NR="1", HEIGHT=str(H), WIDTH=str(W))
    ps = etree.SubElement(page, "PrintSpace", HEIGHT=str(H), WIDTH=str(W), HPOS="0", VPOS="0")

    for i, d in enumerate(stored_detections):
        w, h = d['x2'] - d['x1'], d['y2'] - d['y1']
        block = etree.SubElement(ps, "TextBlock", ID=f"b_{i}", HPOS=str(d['x1']), VPOS=str(d['y1']),
                                 WIDTH=str(w), HEIGHT=str(h))
        line = etree.SubElement(block, "TextLine", ID=f"l_{i}", HPOS=str(d['x1']), VPOS=str(d['y1']),
                                WIDTH=str(w), HEIGHT=str(h))
        etree.SubElement(line, "String", CONTENT=d['text'], HPOS=str(d['x1']), VPOS=str(d['y1']),
                         WIDTH=str(w), HEIGHT=str(h),
                         WC=str(d['ocr_conf']), CLASS=d['class'], YOLO_CONF=str(d['yolo_conf']))

    xml_bytes = etree.tostring(root, pretty_print=True, xml_declaration=True, encoding="UTF-8")

    xml_text = xml_bytes.decode("utf-8")

    # buffer = io.BytesIO(xml_bytes)
    # buffer.seek(0)
    # # return xml_text, gr.File.update(value=buffer, filename="result.xml")

    # with open("result.xml", "wb") as f:
    #     f.write(xml_bytes)

    # return xml_text, "result.xml"

    temp_file = tempfile.NamedTemporaryFile(delete=False, suffix=".xml", mode="wb")
    temp_file.write(xml_bytes)
    temp_file.close()

    # return xml_text, temp_file.name
    xml_filename = os.path.splitext(image_name)[0] + ".xml"
    temp_file_path = os.path.join(tempfile.gettempdir(), xml_filename)

    with open(temp_file_path, "wb") as f:
        f.write(xml_bytes)

    return xml_text, temp_file_path

# === Gradio UI Layout ===
with gr.Blocks(title="TextVision AI: From Detection to XML") as demo:
    gr.Markdown("# 🧾 TextVision AI: From Detection to XML")

    # input_img = gr.Image(label="πŸ“ Upload Image", type="pil")
    input_img = gr.Image(label="πŸ“ Upload Image", type="filepath")

    # === Load Sample Images ===

    example_images = [
        ["example/Image_1.jpg"],
        ["example/Image_2.jpg"],
        ["example/Image_3.jpg"]
    ]

    gr.Examples(
    examples=example_images,
    inputs=input_img,
    label="πŸ–ΌοΈ Example Images"
    )

    btn_detect = gr.Button("πŸ”² Step 1: Detect")
    detected_img = gr.Image(label="πŸ–ΌοΈ Detected Image")

    btn_ocr = gr.Button("πŸ”  Step 2: Recognize")
    ocr_output = gr.Textbox(label="πŸ“ Recognized Text with Tags", lines=6)

    btn_csv = gr.Button("πŸ“„ Step 3: Generate CSV")
    csv_table = gr.Dataframe(label="πŸ“Š Detection Table")
    csv_file = gr.File(label="⬇️ Download CSV")

    btn_xml = gr.Button("πŸ—‚οΈ Step 4: Generate ALTO XML")
    xml_text = gr.Textbox(label="πŸ“„ ALTO XML View", lines=12)
    xml_file = gr.File(label="⬇️ Download XML")

    # Bind buttons
    btn_detect.click(fn=detect_yolo, inputs=input_img, outputs=detected_img)
    btn_ocr.click(fn=recognize_text, outputs=ocr_output)
    btn_csv.click(fn=generate_csv, outputs=[csv_table, csv_file])
    btn_xml.click(fn=generate_alto_xml, outputs=[xml_text, xml_file])

if __name__ == "__main__":
    demo.launch()