Spaces:
Running
Running
Commit
·
30de1f3
1
Parent(s):
e5073bf
changes
Browse files- app.py +35 -42
- requirements.txt +4 -4
- static/style.css +57 -51
- templates/index.html +30 -46
app.py
CHANGED
@@ -1,55 +1,48 @@
|
|
|
|
|
|
1 |
import os
|
2 |
-
from flask import Flask, render_template, request, send_from_directory
|
3 |
-
from werkzeug.utils import secure_filename
|
4 |
from paddleocr import PaddleOCR
|
|
|
5 |
|
6 |
app = Flask(__name__)
|
7 |
-
|
8 |
-
|
9 |
-
UPLOAD_FOLDER = 'uploads'
|
10 |
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
11 |
-
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
12 |
-
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}
|
13 |
|
14 |
-
|
15 |
-
|
16 |
|
17 |
-
# Initialize PaddleOCR (CPU mode)
|
18 |
-
ocr = PaddleOCR(use_angle_cls=
|
19 |
|
20 |
@app.route('/', methods=['GET', 'POST'])
|
21 |
def index():
|
22 |
-
filename = None
|
23 |
extracted_text = None
|
24 |
-
|
25 |
|
26 |
if request.method == 'POST':
|
27 |
-
file
|
28 |
-
if
|
29 |
-
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
|
49 |
-
|
50 |
-
|
51 |
-
|
52 |
-
if __name__ == '__main__':
|
53 |
-
# Use the PORT environment variable if provided by Render, else 5000
|
54 |
-
port = int(os.environ.get('PORT', 5000))
|
55 |
-
app.run(host='0.0.0.0', port=port)
|
|
|
1 |
+
# app.py
|
2 |
+
from flask import Flask, render_template, request, redirect, flash, url_for
|
3 |
import os
|
|
|
|
|
4 |
from paddleocr import PaddleOCR
|
5 |
+
from werkzeug.utils import secure_filename
|
6 |
|
7 |
app = Flask(__name__)
|
8 |
+
app.secret_key = os.environ.get('SECRET_KEY', 'change-this') # Replace in production
|
9 |
+
UPLOAD_FOLDER = os.path.join('static', 'uploads')
|
|
|
10 |
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
|
|
|
11 |
|
12 |
+
# Ensure upload directory exists
|
13 |
+
os.makedirs(app.config['UPLOAD_FOLDER'], exist_ok=True)
|
14 |
|
15 |
+
# Initialize PaddleOCR once (CPU mode, English; angle_cls=False speeds up simple text)
|
16 |
+
ocr = PaddleOCR(use_angle_cls=False, use_gpu=False, lang='en')
|
17 |
|
18 |
@app.route('/', methods=['GET', 'POST'])
|
19 |
def index():
|
|
|
20 |
extracted_text = None
|
21 |
+
image_file = None
|
22 |
|
23 |
if request.method == 'POST':
|
24 |
+
# Check file in request
|
25 |
+
if 'image' not in request.files:
|
26 |
+
flash('No file part in the request.')
|
27 |
+
return redirect(request.url)
|
28 |
+
file = request.files['image']
|
29 |
+
if file.filename == '':
|
30 |
+
flash('No image selected.')
|
31 |
+
return redirect(request.url)
|
32 |
+
|
33 |
+
# Save uploaded file
|
34 |
+
filename = secure_filename(file.filename)
|
35 |
+
file_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
|
36 |
+
file.save(file_path)
|
37 |
+
|
38 |
+
# Run PaddleOCR on the saved image (CPU mode)
|
39 |
+
result = ocr.ocr(file_path, cls=False)
|
40 |
+
# Collect recognized text lines
|
41 |
+
lines = []
|
42 |
+
for res_line in result:
|
43 |
+
for box, (txt, prob) in res_line:
|
44 |
+
lines.append(txt)
|
45 |
+
extracted_text = "\n".join(lines)
|
46 |
+
image_file = filename
|
47 |
+
|
48 |
+
return render_template('index.html', extracted_text=extracted_text, image_file=image_file)
|
|
|
|
|
|
|
|
requirements.txt
CHANGED
@@ -1,5 +1,5 @@
|
|
1 |
-
Flask>=2.0
|
2 |
-
gunicorn
|
3 |
-
paddlepaddle
|
4 |
paddleocr>=2.0.1
|
5 |
-
|
|
|
|
1 |
+
Flask>=2.0.0
|
2 |
+
gunicorn>=20.1.0
|
|
|
3 |
paddleocr>=2.0.1
|
4 |
+
paddlepaddle>=2.0.0
|
5 |
+
opencv-python-headless>=4.8.0
|
static/style.css
CHANGED
@@ -103,70 +103,76 @@ button:hover {
|
|
103 |
transform: translate(-50%, -50%);
|
104 |
z-index: 1000;
|
105 |
} */
|
|
|
106 |
body {
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
display: flex;
|
111 |
-
justify-content: center;
|
112 |
-
align-items: center;
|
113 |
margin: 0;
|
|
|
114 |
}
|
115 |
-
|
116 |
.container {
|
117 |
-
background: #fff;
|
118 |
-
padding: 30px 40px;
|
119 |
-
border-radius: 20px;
|
120 |
-
box-shadow: 0 10px 30px rgba(0,0,0,0.2);
|
121 |
max-width: 600px;
|
122 |
-
|
123 |
-
|
|
|
|
|
|
|
124 |
}
|
125 |
-
|
126 |
-
|
127 |
-
margin-bottom:
|
128 |
-
|
129 |
-
|
|
|
130 |
}
|
131 |
-
|
132 |
-
|
133 |
display: flex;
|
134 |
-
|
135 |
-
gap: 15px;
|
136 |
-
margin-bottom: 30px;
|
137 |
}
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
|
143 |
-
z-index: 1000;
|
144 |
}
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
border
|
149 |
-
|
150 |
-
|
|
|
151 |
}
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
158 |
white-space: pre-wrap;
|
159 |
-
margin-top: 10px;
|
160 |
}
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
|
|
|
|
166 |
}
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
|
|
|
|
|
|
171 |
}
|
172 |
|
|
|
103 |
transform: translate(-50%, -50%);
|
104 |
z-index: 1000;
|
105 |
} */
|
106 |
+
/* static/style.css */
|
107 |
body {
|
108 |
+
background: #f0f2f5;
|
109 |
+
font-family: 'Segoe UI', Tahoma, sans-serif;
|
110 |
+
color: #333;
|
|
|
|
|
|
|
111 |
margin: 0;
|
112 |
+
padding: 0;
|
113 |
}
|
|
|
114 |
.container {
|
|
|
|
|
|
|
|
|
115 |
max-width: 600px;
|
116 |
+
margin: 40px auto;
|
117 |
+
background: #fff;
|
118 |
+
border-radius: 8px;
|
119 |
+
padding: 20px;
|
120 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
121 |
}
|
122 |
+
h1, h2 {
|
123 |
+
color: #444;
|
124 |
+
margin-bottom: 10px;
|
125 |
+
}
|
126 |
+
p {
|
127 |
+
color: #666;
|
128 |
}
|
129 |
+
form {
|
130 |
+
margin-top: 20px;
|
131 |
display: flex;
|
132 |
+
gap: 10px;
|
|
|
|
|
133 |
}
|
134 |
+
input[type="file"] {
|
135 |
+
flex: 1;
|
136 |
+
padding: 8px;
|
137 |
+
border: 1px solid #ccc;
|
138 |
+
border-radius: 4px;
|
|
|
139 |
}
|
140 |
+
button {
|
141 |
+
background-color: #007BFF;
|
142 |
+
color: white;
|
143 |
+
border: none;
|
144 |
+
padding: 8px 16px;
|
145 |
+
border-radius: 4px;
|
146 |
+
cursor: pointer;
|
147 |
}
|
148 |
+
button:hover {
|
149 |
+
background-color: #0056b3;
|
150 |
+
}
|
151 |
+
.result, .image-preview {
|
152 |
+
margin-top: 20px;
|
153 |
+
padding: 10px;
|
154 |
+
border-top: 1px solid #e1e1e1;
|
155 |
+
}
|
156 |
+
.result pre {
|
157 |
+
background: #f8f9fa;
|
158 |
+
padding: 10px;
|
159 |
+
border-radius: 4px;
|
160 |
white-space: pre-wrap;
|
|
|
161 |
}
|
162 |
+
.flashes {
|
163 |
+
list-style: none;
|
164 |
+
padding: 10px;
|
165 |
+
background: #ffe0e0;
|
166 |
+
border: 1px solid #ffb3b3;
|
167 |
+
border-radius: 4px;
|
168 |
+
color: #a94442;
|
169 |
}
|
170 |
+
.flashes li {
|
171 |
+
margin: 5px 0;
|
172 |
+
}
|
173 |
+
img {
|
174 |
+
max-width: 100%;
|
175 |
+
height: auto;
|
176 |
+
border-radius: 4px;
|
177 |
}
|
178 |
|
templates/index.html
CHANGED
@@ -1,59 +1,43 @@
|
|
|
|
1 |
<!DOCTYPE html>
|
2 |
-
<html
|
3 |
<head>
|
4 |
-
|
5 |
-
|
6 |
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
7 |
-
<!-- Bootstrap CSS -->
|
8 |
-
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
|
9 |
-
<!-- Google Font -->
|
10 |
-
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;600&display=swap" rel="stylesheet">
|
11 |
-
<!-- Custom Styles -->
|
12 |
-
<link href="{{ url_for('static', filename='style.css') }}" rel="stylesheet">
|
13 |
</head>
|
14 |
<body>
|
15 |
-
<div class="container
|
16 |
-
<h1
|
17 |
-
|
18 |
-
<form id="upload-form" method="post" enctype="multipart/form-data" class="upload-form mx-auto">
|
19 |
-
<input type="file" name="image" accept="image/*" class="form-control mb-3" required>
|
20 |
-
<button type="submit" class="btn btn-primary">Upload & Extract Text</button>
|
21 |
-
</form>
|
22 |
|
23 |
-
|
24 |
-
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
|
|
|
|
|
|
29 |
|
30 |
-
|
31 |
-
|
32 |
-
|
33 |
-
|
34 |
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
</div>
|
41 |
{% endif %}
|
42 |
|
43 |
-
|
44 |
-
|
45 |
-
|
46 |
-
|
47 |
-
|
48 |
-
</div>
|
49 |
{% endif %}
|
50 |
</div>
|
51 |
-
|
52 |
-
<!-- Show spinner on form submit -->
|
53 |
-
<script>
|
54 |
-
document.getElementById('upload-form').onsubmit = () => {
|
55 |
-
document.getElementById('spinner').classList.remove('d-none');
|
56 |
-
};
|
57 |
-
</script>
|
58 |
</body>
|
59 |
</html>
|
|
|
1 |
+
<!-- templates/index.html -->
|
2 |
<!DOCTYPE html>
|
3 |
+
<html>
|
4 |
<head>
|
5 |
+
<title>OCR App</title>
|
6 |
+
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
7 |
</head>
|
8 |
<body>
|
9 |
+
<div class="container">
|
10 |
+
<h1>Image Text Extraction</h1>
|
11 |
+
<p>Upload an image to extract text using PaddleOCR.</p>
|
|
|
|
|
|
|
|
|
12 |
|
13 |
+
{% with messages = get_flashed_messages() %}
|
14 |
+
{% if messages %}
|
15 |
+
<ul class="flashes">
|
16 |
+
{% for message in messages %}
|
17 |
+
<li>{{ message }}</li>
|
18 |
+
{% endfor %}
|
19 |
+
</ul>
|
20 |
+
{% endif %}
|
21 |
+
{% endwith %}
|
22 |
|
23 |
+
<form method="POST" enctype="multipart/form-data">
|
24 |
+
<input type="file" name="image" accept="image/*" required>
|
25 |
+
<button type="submit">Extract Text</button>
|
26 |
+
</form>
|
27 |
|
28 |
+
{% if extracted_text %}
|
29 |
+
<div class="result">
|
30 |
+
<h2>Extracted Text:</h2>
|
31 |
+
<pre>{{ extracted_text }}</pre>
|
32 |
+
</div>
|
|
|
33 |
{% endif %}
|
34 |
|
35 |
+
{% if image_file %}
|
36 |
+
<div class="image-preview">
|
37 |
+
<h2>Uploaded Image:</h2>
|
38 |
+
<img src="{{ url_for('static', filename='uploads/' + image_file) }}" alt="Uploaded Image">
|
39 |
+
</div>
|
|
|
40 |
{% endif %}
|
41 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
</body>
|
43 |
</html>
|