m2zm commited on
Commit
6a0da27
·
verified ·
1 Parent(s): 73cc0b5

Upload 3 files

Browse files
Files changed (3) hide show
  1. README.md +12 -12
  2. app.py +152 -0
  3. requirements.txt +6 -0
README.md CHANGED
@@ -1,12 +1,12 @@
1
- ---
2
- title: Ppf Test
3
- emoji: 💻
4
- colorFrom: purple
5
- colorTo: red
6
- sdk: gradio
7
- sdk_version: 6.3.0
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ ---
2
+ title: ppf-captcha
3
+ emoji: 🔥
4
+ colorFrom: gray
5
+ colorTo: indigo
6
+ sdk: gradio
7
+ sdk_version: 5.6.0
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
app.py ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import time
3
+ import os
4
+ import re
5
+ import numpy as np
6
+ from PIL import Image, ImageFilter
7
+ from cairosvg import svg2png
8
+ from transformers import VisionEncoderDecoderModel, TrOCRProcessor
9
+ import gradio as gr
10
+
11
+ processor = TrOCRProcessor.from_pretrained("anuashok/ocr-captcha-v3")
12
+ model = VisionEncoderDecoderModel.from_pretrained("anuashok/ocr-captcha-v3")
13
+ os.makedirs("outputs", exist_ok=True)
14
+
15
+
16
+ def _single_ocr_from_image(image: Image.Image) -> str:
17
+ pixel_values = processor(image, return_tensors="pt").pixel_values
18
+ generated_ids = model.generate(pixel_values)
19
+ generated_text = processor.batch_decode(generated_ids, skip_special_tokens=True)[0]
20
+ sanitized = re.sub(r'[^A-Z0-9]', '', generated_text).upper()
21
+ return sanitized[:4]
22
+
23
+
24
+ def solve_svg_captcha(svg_data: str) -> str:
25
+ svg = svg_data or ""
26
+ svg = re.sub(r'<style>.*?</style>', '', svg, flags=re.DOTALL)
27
+ svg = svg.replace('file:///', '')
28
+ svg = svg.replace('/app/', '')
29
+ svg = re.sub(r'url\(["\']?\/?app\/[^)"\']*["\']?\)', 'url()', svg)
30
+
31
+ svg_static = re.sub(r'<animateTransform\b[^>]*>(?:.*?</animateTransform>)?', '', svg, flags=re.DOTALL)
32
+
33
+ rotate_re = re.compile(r'rotate\(\s*([+-]?\d+)\s*,\s*([0-9.]+)\s*,\s*([0-9.]+)\s*\)')
34
+ matches = rotate_re.findall(svg_static)
35
+ centers = []
36
+ seen = set()
37
+ for _, cx, cy in matches:
38
+ key = f"{cx},{cy}"
39
+ if key not in seen:
40
+ seen.add(key)
41
+ centers.append((cx, cy))
42
+
43
+ if not centers:
44
+ try:
45
+ png_bytes = svg2png(bytestring=svg_static.encode('utf-8'))
46
+ image = Image.open(io.BytesIO(png_bytes)).convert("RGBA")
47
+ image = image.resize((600, 400))
48
+ background = Image.new("RGBA", image.size, (255, 255, 255))
49
+ combined = Image.alpha_composite(background, image).convert("RGB")
50
+ return _single_ocr_from_image(combined)
51
+ except Exception as e:
52
+ print("OCR fallback error:", e)
53
+ return ""
54
+
55
+ centers = centers[:2]
56
+ angle_step = 15
57
+ top_k = 2
58
+ best_angles = {}
59
+
60
+ for cx, cy in centers:
61
+ metrics = []
62
+ for angle in range(0, 360, angle_step):
63
+ try:
64
+ tmp = re.sub(rf'rotate\(\s*1\s*,\s*{re.escape(cx)}\s*,\s*{re.escape(cy)}\s*\)', f'rotate({angle}, {cx}, {cy})', svg_static)
65
+ tmp = re.sub(rf'rotate\(\s*-1\s*,\s*{re.escape(cx)}\s*,\s*{re.escape(cy)}\s*\)', f'rotate(-{angle}, {cx}, {cy})', tmp)
66
+ png_bytes = svg2png(bytestring=tmp.encode('utf-8'))
67
+ img = Image.open(io.BytesIO(png_bytes)).convert('L')
68
+ img = img.resize((600, 400))
69
+ img = img.filter(ImageFilter.GaussianBlur(radius=1))
70
+ edges = img.filter(ImageFilter.FIND_EDGES)
71
+ arr = np.array(edges)
72
+ edge_count = int((arr > 10).sum())
73
+ metrics.append((edge_count, angle))
74
+ except Exception:
75
+ continue
76
+ metrics.sort(key=lambda x: x[0])
77
+ picked = [m[1] for m in metrics[:top_k]] if metrics else [0] * top_k
78
+ if len(picked) < top_k:
79
+ picked += [picked[0]] * (top_k - len(picked))
80
+ best_angles[f"{cx},{cy}"] = picked
81
+
82
+ combos = []
83
+ if len(centers) == 1:
84
+ k = f"{centers[0][0]},{centers[0][1]}"
85
+ a1, a2 = best_angles[k][:2]
86
+ combos = [{k: a1}, {k: a2}, {k: a1}, {k: a2}]
87
+ else:
88
+ k0 = f"{centers[0][0]},{centers[0][1]}"
89
+ k1 = f"{centers[1][0]},{centers[1][1]}"
90
+ a1, a2 = best_angles[k0][:2]
91
+ b1, b2 = best_angles[k1][:2]
92
+ combos = [
93
+ {k0: a1, k1: b1},
94
+ {k0: a2, k1: b1},
95
+ {k0: a1, k1: b2},
96
+ {k0: a2, k1: b2},
97
+ ]
98
+
99
+ images = []
100
+ for combo in combos:
101
+ tmp = svg_static
102
+ for key, angle in combo.items():
103
+ cx, cy = key.split(',')
104
+ tmp = re.sub(rf'rotate\(\s*1\s*,\s*{re.escape(cx)}\s*,\s*{re.escape(cy)}\s*\)', f'rotate({angle}, {cx}, {cy})', tmp)
105
+ tmp = re.sub(rf'rotate\(\s*-1\s*,\s*{re.escape(cx)}\s*,\s*{re.escape(cy)}\s*\)', f'rotate(-{angle}, {cx}, {cy})', tmp)
106
+ try:
107
+ png_bytes = svg2png(bytestring=tmp.encode('utf-8'))
108
+ img = Image.open(io.BytesIO(png_bytes)).convert("RGBA")
109
+ img = img.resize((600, 400))
110
+ background = Image.new("RGBA", img.size, (255, 255, 255))
111
+ combined = Image.alpha_composite(background, img).convert("RGB")
112
+ images.append(combined)
113
+ except Exception:
114
+ continue
115
+
116
+ ocr_results = []
117
+ for img in images:
118
+ try:
119
+ txt = _single_ocr_from_image(img)
120
+ ocr_results.append(txt)
121
+ except Exception:
122
+ ocr_results.append("")
123
+
124
+ for r in ocr_results:
125
+ if len(r) == 4:
126
+ return r
127
+ if ocr_results:
128
+ best = max(ocr_results, key=lambda x: len(x or ""))
129
+ return best or ""
130
+ return ""
131
+
132
+ def predict(svgdata):
133
+ if not svgdata:
134
+ return "No SVG provided"
135
+ if len(svgdata) > 50000:
136
+ return "SVG too large"
137
+ try:
138
+ model_answer = solve_svg_captcha(svgdata)
139
+ except Exception as e:
140
+ print(f"Error in predict: {e}")
141
+ return "Model could not predict"
142
+ return model_answer or "Model could not predict"
143
+
144
+ with gr.Blocks() as demo:
145
+ gr.Markdown("Enter SVG data and receive model answer")
146
+ svg_input = gr.Textbox(label="SVG Data", lines=10)
147
+ predict_btn = gr.Button("Get Model Answer")
148
+ model_answer = gr.Textbox(label="Model Answer", interactive=False)
149
+ predict_btn.click(predict, inputs=[svg_input], outputs=[model_answer])
150
+
151
+ if __name__ == "__main__":
152
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ cairosvg==2.7.1
2
+ transformers>=4.30.0
3
+ torch>=2.0.0
4
+ Pillow>=9.0.0
5
+ gradio>=3.0.0
6
+ hf_xet