Spaces:
Build error
Build error
Commit ·
3d82aef
1
Parent(s): de3df47
inital commit
Browse files- app.py +165 -4
- requerements.txt +4 -0
app.py
CHANGED
|
@@ -1,7 +1,168 @@
|
|
| 1 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
|
| 3 |
-
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import gradio as gr
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
import pytesseract
|
| 5 |
+
import base64, json, io
|
| 6 |
+
from PIL import Image
|
| 7 |
|
| 8 |
+
# HTML template that loads Fabric.js and creates an interactive canvas.
|
| 9 |
+
html_template = """
|
| 10 |
+
<!DOCTYPE html>
|
| 11 |
+
<html>
|
| 12 |
+
<head>
|
| 13 |
+
<meta charset="utf-8">
|
| 14 |
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.6.0/fabric.min.js"></script>
|
| 15 |
+
<style>
|
| 16 |
+
canvas { border: 1px solid #ccc; }
|
| 17 |
+
</style>
|
| 18 |
+
</head>
|
| 19 |
+
<body>
|
| 20 |
+
<canvas id="c" width="600" height="400"></canvas>
|
| 21 |
+
<script>
|
| 22 |
+
// Parse JSON data from Python.
|
| 23 |
+
var data = {data_json};
|
| 24 |
|
| 25 |
+
// Initialize Fabric.js canvas.
|
| 26 |
+
var canvas = new fabric.Canvas('c');
|
| 27 |
+
|
| 28 |
+
// Load the image as canvas background.
|
| 29 |
+
var imgObj = new Image();
|
| 30 |
+
imgObj.src = "data:image/png;base64," + data.image_data;
|
| 31 |
+
imgObj.onload = function() {
|
| 32 |
+
var bg = new fabric.Image(imgObj);
|
| 33 |
+
bg.selectable = false;
|
| 34 |
+
// Scale background to canvas dimensions.
|
| 35 |
+
bg.scaleToWidth(canvas.width);
|
| 36 |
+
bg.scaleToHeight(canvas.height);
|
| 37 |
+
canvas.setBackgroundImage(bg, canvas.renderAll.bind(canvas));
|
| 38 |
+
};
|
| 39 |
+
|
| 40 |
+
// Add detected objects to the canvas.
|
| 41 |
+
data.objects.forEach(function(obj) {
|
| 42 |
+
if(obj.type === "text") {
|
| 43 |
+
var textObj = new fabric.IText(obj.text, {
|
| 44 |
+
left: obj.x,
|
| 45 |
+
top: obj.y,
|
| 46 |
+
fontSize: 20,
|
| 47 |
+
fill: 'black'
|
| 48 |
+
});
|
| 49 |
+
canvas.add(textObj);
|
| 50 |
+
} else if(obj.type === "image") {
|
| 51 |
+
var rect = new fabric.Rect({
|
| 52 |
+
left: obj.x,
|
| 53 |
+
top: obj.y,
|
| 54 |
+
width: obj.width,
|
| 55 |
+
height: obj.height,
|
| 56 |
+
fill: 'rgba(0, 0, 255, 0.3)'
|
| 57 |
+
});
|
| 58 |
+
canvas.add(rect);
|
| 59 |
+
}
|
| 60 |
+
});
|
| 61 |
+
</script>
|
| 62 |
+
</body>
|
| 63 |
+
</html>
|
| 64 |
+
"""
|
| 65 |
+
|
| 66 |
+
def generate_html(image):
|
| 67 |
+
# If the PNG has transparency, composite it onto a white background.
|
| 68 |
+
if image.shape[2] == 4:
|
| 69 |
+
alpha = image[:, :, 3] / 255.0
|
| 70 |
+
image_rgb = image[:, :, :3]
|
| 71 |
+
white_bg = np.ones_like(image_rgb, dtype=np.uint8) * 255
|
| 72 |
+
image = np.uint8(image_rgb * alpha[..., None] + white_bg * (1 - alpha[..., None]))
|
| 73 |
+
|
| 74 |
+
# Convert the image (numpy array) to a base64-encoded PNG.
|
| 75 |
+
pil_image = Image.fromarray(image)
|
| 76 |
+
buffer = io.BytesIO()
|
| 77 |
+
pil_image.save(buffer, format="PNG")
|
| 78 |
+
base64_image = base64.b64encode(buffer.getvalue()).decode('utf-8')
|
| 79 |
+
|
| 80 |
+
# ------------------- TEXT DETECTION -------------------
|
| 81 |
+
text_data = pytesseract.image_to_data(image, output_type=pytesseract.Output.DICT)
|
| 82 |
+
detected_texts = []
|
| 83 |
+
n_boxes = len(text_data['level'])
|
| 84 |
+
for i in range(n_boxes):
|
| 85 |
+
try:
|
| 86 |
+
conf = int(text_data['conf'][i])
|
| 87 |
+
except:
|
| 88 |
+
conf = 0
|
| 89 |
+
text_content = text_data['text'][i].strip()
|
| 90 |
+
if conf > 60 and text_content:
|
| 91 |
+
x = int(text_data['left'][i])
|
| 92 |
+
y = int(text_data['top'][i])
|
| 93 |
+
w = int(text_data['width'][i])
|
| 94 |
+
h = int(text_data['height'][i])
|
| 95 |
+
detected_texts.append({
|
| 96 |
+
'type': 'text',
|
| 97 |
+
'text': text_content,
|
| 98 |
+
'x': x,
|
| 99 |
+
'y': y,
|
| 100 |
+
'width': w,
|
| 101 |
+
'height': h,
|
| 102 |
+
'confidence': conf
|
| 103 |
+
})
|
| 104 |
+
|
| 105 |
+
# ---------------- NON-TEXT OBJECT DETECTION ----------------
|
| 106 |
+
# Convert image to grayscale and threshold to detect non-white regions.
|
| 107 |
+
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 108 |
+
_, thresh = cv2.threshold(gray, 240, 255, cv2.THRESH_BINARY_INV)
|
| 109 |
+
contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
|
| 110 |
+
detected_images = []
|
| 111 |
+
|
| 112 |
+
# Helper function to compute Intersection over Union (IoU) for overlap testing.
|
| 113 |
+
def iou(box1, box2):
|
| 114 |
+
x1, y1, w1, h1 = box1
|
| 115 |
+
x2, y2, w2, h2 = box2
|
| 116 |
+
inter_x = max(0, min(x1+w1, x2+w2) - max(x1, x2))
|
| 117 |
+
inter_y = max(0, min(y1+h1, y2+h2) - max(y1, y2))
|
| 118 |
+
inter_area = inter_x * inter_y
|
| 119 |
+
area1 = w1 * h1
|
| 120 |
+
area2 = w2 * h2
|
| 121 |
+
union = area1 + area2 - inter_area
|
| 122 |
+
return inter_area / union if union != 0 else 0
|
| 123 |
+
|
| 124 |
+
# Prepare text bounding boxes for filtering.
|
| 125 |
+
text_boxes = [(obj['x'], obj['y'], obj['width'], obj['height']) for obj in detected_texts]
|
| 126 |
+
image_id = 0
|
| 127 |
+
for cnt in contours:
|
| 128 |
+
x, y, w, h = cv2.boundingRect(cnt)
|
| 129 |
+
if w < 10 or h < 10:
|
| 130 |
+
continue
|
| 131 |
+
# Skip if the contour significantly overlaps with a detected text box.
|
| 132 |
+
overlap = any(iou((x, y, w, h), tb) > 0.5 for tb in text_boxes)
|
| 133 |
+
if not overlap:
|
| 134 |
+
detected_images.append({
|
| 135 |
+
'type': 'image',
|
| 136 |
+
'id': image_id,
|
| 137 |
+
'x': x,
|
| 138 |
+
'y': y,
|
| 139 |
+
'width': w,
|
| 140 |
+
'height': h
|
| 141 |
+
})
|
| 142 |
+
image_id += 1
|
| 143 |
+
|
| 144 |
+
# Combine text and non-text objects.
|
| 145 |
+
objects = detected_texts + detected_images
|
| 146 |
+
result = {
|
| 147 |
+
"image_data": base64_image,
|
| 148 |
+
"objects": objects
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
# Insert the JSON data into the HTML template.
|
| 152 |
+
json_data = json.dumps(result)
|
| 153 |
+
html_code = html_template.replace("{data_json}", json_data)
|
| 154 |
+
return html_code
|
| 155 |
+
|
| 156 |
+
# Create the Gradio interface.
|
| 157 |
+
with gr.Blocks() as demo:
|
| 158 |
+
gr.Markdown("## Interactive Image Editor")
|
| 159 |
+
with gr.Row():
|
| 160 |
+
with gr.Column():
|
| 161 |
+
input_image = gr.Image(label="Upload PNG Image", source="upload", type="numpy")
|
| 162 |
+
process_button = gr.Button("Process Image")
|
| 163 |
+
with gr.Column():
|
| 164 |
+
html_output = gr.HTML(label="Interactive Editor")
|
| 165 |
+
|
| 166 |
+
process_button.click(fn=generate_html, inputs=input_image, outputs=html_output)
|
| 167 |
+
|
| 168 |
+
demo.launch()
|
requerements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
pytesseract
|
| 3 |
+
Pillow
|
| 4 |
+
opencv-python
|