vagrillo commited on
Commit
dc31532
Β·
verified Β·
1 Parent(s): d18fd51

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +281 -78
app.py CHANGED
@@ -1,92 +1,312 @@
1
- from flask import Flask, request, render_template, redirect, url_for, make_response
2
  import datetime
3
  import os
 
 
 
 
 
 
 
 
4
 
5
  app = Flask(__name__)
 
6
  SECRET_PASSWORD = "VeronaTrento25!"
7
- COOKIE_NAME = "authenticated"
8
 
 
9
  def is_authenticated():
10
- """Verifica se l'utente Γ¨ autenticato tramite cookie"""
11
- auth_cookie = request.cookies.get(COOKIE_NAME)
12
- if auth_cookie == "true":
13
- return True
14
- return False
15
-
16
- def set_auth_cookie(response):
17
- """Imposta il cookie di autenticazione per 24 ore"""
18
- expires = datetime.datetime.now() + datetime.timedelta(hours=24)
19
- response.set_cookie(
20
- COOKIE_NAME,
21
- "true",
22
- expires=expires,
23
- httponly=True,
24
- secure=False,
25
- path='/', # Aggiungi questo
26
- samesite='Lax' # Aggiungi questo
27
- )
28
- return response
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
 
 
 
 
31
  @app.route('/')
 
32
  def index():
33
- # Se l'utente Γ¨ giΓ  autenticato, mostra il contenuto normale
34
- if is_authenticated():
35
- return """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  <!DOCTYPE html>
37
  <html>
38
  <head>
39
- <title>Sito Protetto</title>
40
  <style>
41
- body { font-family: Arial, sans-serif; max-width: 800px; margin: 50px auto; padding: 20px; }
42
- .welcome { background: #e8f5e8; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
43
- .content { background: #f0f0f0; padding: 20px; border-radius: 5px; }
44
- a { color: #007bff; text-decoration: none; margin-right: 15px; }
 
 
 
 
45
  </style>
46
  </head>
47
  <body>
48
- <div class="welcome">
49
- <h2>βœ… Accesso Autorizzato!</h2>
50
- <p>Sei autenticato correttamente. La navigazione Γ¨ libera per 24 ore.</p>
51
- <p><a href="/logout">πŸ”“ Logout</a></p>
52
  </div>
53
 
 
 
54
  <div class="content">
55
- <h3>Benvenuto nel sito protetto</h3>
56
- <p>Questo Γ¨ il contenuto principale del tuo sito web.</p>
57
- <p>Qui puoi inserire tutto il contenuto che vuoi proteggere con password.</p>
 
 
 
 
 
 
 
 
 
 
58
  </div>
59
  </body>
60
  </html>
61
- """
62
-
63
- # Altrimenti reindirizza alla pagina di login
64
- return redirect(url_for('login'))
65
 
66
  @app.route('/login', methods=['GET', 'POST'])
67
  def login():
68
- # Se l'utente Γ¨ giΓ  autenticato, reindirizza alla home
69
  if is_authenticated():
70
  return redirect(url_for('index'))
71
 
72
  error = None
73
  if request.method == 'POST':
74
- password = request.form.get('password')
75
-
76
- if password == SECRET_PASSWORD:
77
- # Password corretta, crea risposta e imposta cookie
78
- response = make_response(redirect(url_for('index')))
79
- response = set_auth_cookie(response)
80
- return response
81
  else:
82
  error = "❌ Password errata. Riprova."
83
 
84
- # Costruisci l'HTML dinamicamente
85
- html_content = """
86
  <!DOCTYPE html>
87
  <html>
88
  <head>
89
- <title>Login Richiesto</title>
90
  <style>
91
  body {
92
  font-family: Arial, sans-serif;
@@ -140,42 +360,25 @@ def login():
140
  </head>
141
  <body>
142
  <div class="login-form">
143
- <h2>πŸ”’ Accesso Protetto</h2>
144
- <p style="text-align: center; color: #666;">Inserisci la password per accedere al sito</p>
145
- """
146
-
147
- # Aggiungi la sezione error se presente
148
- if error:
149
- html_content += f'<div class="error">{error}</div>'
150
-
151
- # Aggiungi il form
152
- html_content += """
153
  <form method="POST">
154
- <input type="password" name="password" placeholder="Inserisci la password" required>
155
  <button type="submit">πŸ”‘ Accedi</button>
156
  </form>
157
  </div>
158
  </body>
159
  </html>
160
- """
161
-
162
- return html_content
163
 
164
  @app.route('/logout')
165
  def logout():
166
- """Route per il logout (elimina il cookie)"""
167
- response = make_response(redirect(url_for('login')))
168
- response.set_cookie(COOKIE_NAME, '', expires=0, path='/') # Aggiungi path='/'
169
- return response
170
-
171
- # Proteggi tutte le route aggiuntive che potresti avere
172
- @app.route('/protected')
173
- def protected():
174
- if not is_authenticated():
175
- return redirect(url_for('login'))
176
- return "Questa Γ¨ una pagina protetta!"
177
 
178
  if __name__ == '__main__':
179
- # Configurazione per Hugging Face Spaces
180
- port = int(os.environ.get('PORT', 7860))
181
  app.run(host='0.0.0.0', port=port, debug=False)
 
1
+ from flask import Flask, session, request, redirect, url_for, render_template_string, send_file
2
  import datetime
3
  import os
4
+ import secrets
5
+ import torch
6
+ from PIL import Image, ImageDraw
7
+ from transformers import GroundingDinoProcessor
8
+ from modeling_grounding_dino import GroundingDinoForObjectDetection
9
+ from itertools import cycle
10
+ import tempfile
11
+ import io
12
 
13
  app = Flask(__name__)
14
+ app.secret_key = os.environ.get('SECRET_KEY', secrets.token_hex(16))
15
  SECRET_PASSWORD = "VeronaTrento25!"
16
+ app.permanent_session_lifetime = datetime.timedelta(hours=24)
17
 
18
+ # ===== AUTHENTICATION FUNCTIONS =====
19
  def is_authenticated():
20
+ return session.get('authenticated', False)
21
+
22
+ def require_auth(f):
23
+ def decorated_function(*args, **kwargs):
24
+ if not is_authenticated():
25
+ return redirect(url_for('login'))
26
+ return f(*args, **kwargs)
27
+ decorated_function.__name__ = f.__name__
28
+ return decorated_function
29
+
30
+ # ===== ML MODEL SETUP =====
31
+ DEVICE = "cpu"
32
+ model_id = "fushh7/llmdet_swin_tiny_hf"
33
+
34
+ print(f"[INFO] Using device: {DEVICE}")
35
+ print(f"[INFO] Loading model from {model_id}...")
36
+
37
+ processor = GroundingDinoProcessor.from_pretrained(model_id)
38
+ model = GroundingDinoForObjectDetection.from_pretrained(model_id).to(DEVICE)
39
+ model.eval()
40
+
41
+ print("[INFO] Model loaded successfully.")
42
+
43
+ # Pre-defined palette
44
+ BOX_COLORS = [
45
+ "deepskyblue", "red", "lime", "dodgerblue",
46
+ "cyan", "magenta", "yellow", "orange", "chartreuse"
47
+ ]
48
+
49
+ # ===== ML FUNCTIONS =====
50
+ def save_cropped_images(original_image, boxes, labels, scores):
51
+ saved_paths = []
52
+ for i, (box, label, score) in enumerate(zip(boxes, labels, scores)):
53
+ with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp_file:
54
+ filepath = tmp_file.name
55
+ cropped_img = original_image.crop(box)
56
+ cropped_img.save(filepath)
57
+ saved_paths.append(filepath)
58
+ return saved_paths
59
+
60
+ def draw_boxes(image, boxes, labels, scores, colors=BOX_COLORS, font_size=16):
61
+ colour_cycle = cycle(colors)
62
+ draw = ImageDraw.Draw(image)
63
+
64
+ try:
65
+ font = ImageFont.truetype("arial.ttf", size=font_size)
66
+ except:
67
+ font = ImageFont.load_default()
68
+
69
+ label_to_colour = {}
70
+
71
+ for box, label, score in zip(boxes, labels, scores):
72
+ colour = label_to_colour.setdefault(label, next(colour_cycle))
73
+ x_min, y_min, x_max, y_max = map(int, box)
74
+
75
+ draw.rectangle([x_min, y_min, x_max, y_max], outline=colour, width=2)
76
+ text = f"{label} ({score:.3f})"
77
+ text_bbox = draw.textbbox((0, 0), text, font=font)
78
+ text_width = text_bbox[2] - text_bbox[0]
79
+ text_height = text_bbox[3] - text_bbox[1]
80
+
81
+ bg_coords = [x_min, y_min - text_height - 4, x_min + text_width + 4, y_min]
82
+ draw.rectangle(bg_coords, fill=colour)
83
+ draw.text((x_min + 2, y_min - text_height - 2), text, fill="black", font=font)
84
+
85
+ return image
86
 
87
+ def resize_image_max_dimension(image, max_size=1024):
88
+ width, height = image.size
89
+ if max(width, height) <= max_size:
90
+ return image
91
+ ratio = max_size / max(width, height)
92
+ new_width = int(width * ratio)
93
+ new_height = int(height * ratio)
94
+ return image.resize((new_width, new_height), Image.Resampling.LANCZOS)
95
+
96
+ def detect_and_draw(img, text_query, box_threshold=0.14, text_threshold=0.13):
97
+ text_query = text_query.lower()
98
+ img = resize_image_max_dimension(img, max_size=1024)
99
+
100
+ inputs = processor(images=img, text=text_query, return_tensors="pt").to(DEVICE)
101
+
102
+ with torch.no_grad():
103
+ outputs = model(**inputs)
104
+
105
+ results = processor.post_process_grounded_object_detection(
106
+ outputs,
107
+ inputs.input_ids,
108
+ text_threshold=text_threshold,
109
+ target_sizes=[img.size[::-1]]
110
+ )[0]
111
+
112
+ img_out = img.copy()
113
+ img_out = draw_boxes(
114
+ img_out,
115
+ boxes=results["boxes"].cpu().numpy(),
116
+ labels=results.get("text_labels", results.get("labels", [])),
117
+ scores=results["scores"]
118
+ )
119
+
120
+ crop_paths = save_cropped_images(
121
+ img,
122
+ boxes=results["boxes"].cpu().numpy(),
123
+ labels=results.get("text_labels", results.get("labels", [])),
124
+ scores=results["scores"]
125
+ )
126
 
127
+ return img_out, crop_paths
128
+
129
+ # ===== FLASK ROUTES =====
130
  @app.route('/')
131
+ @require_auth
132
  def index():
133
+ return render_template_string('''
134
+ <!DOCTYPE html>
135
+ <html>
136
+ <head>
137
+ <title>Student Finder - Protetto</title>
138
+ <style>
139
+ body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
140
+ .header { background: #e8f5e8; padding: 20px; border-radius: 10px; margin-bottom: 20px; }
141
+ .content { background: #f5f5f5; padding: 30px; border-radius: 10px; }
142
+ .form-group { margin-bottom: 15px; }
143
+ label { display: block; margin-bottom: 5px; font-weight: bold; }
144
+ input, textarea, select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
145
+ button { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
146
+ button:hover { background: #0056b3; }
147
+ .logout { float: right; }
148
+ .results { margin-top: 20px; }
149
+ .gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin-top: 20px; }
150
+ .gallery img { max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px; }
151
+ </style>
152
+ </head>
153
+ <body>
154
+ <div class="header">
155
+ <h1>πŸŽ“ Student Finder</h1>
156
+ <p>Carica una foto di classe e trova gli studenti</p>
157
+ <a href="/logout" class="logout">πŸ”“ Logout</a>
158
+ <div style="clear: both;"></div>
159
+ </div>
160
+
161
+ <div class="content">
162
+ <form method="post" enctype="multipart/form-data" action="/detect">
163
+ <div class="form-group">
164
+ <label for="image">Immagine:</label>
165
+ <input type="file" id="image" name="image" accept="image/*" required>
166
+ </div>
167
+
168
+ <div class="form-group">
169
+ <label for="text_query">Text Query:</label>
170
+ <textarea id="text_query" name="text_query" rows="2" required>heads.</textarea>
171
+ <small>Testo in lowercase, ogni concetto termina con '.' (es. 'heads. faces.')</small>
172
+ </div>
173
+
174
+ <div class="form-group">
175
+ <label for="box_threshold">Box Threshold ({{ box_threshold }}):</label>
176
+ <input type="range" id="box_threshold" name="box_threshold" min="0" max="1" step="0.05" value="0.14">
177
+ </div>
178
+
179
+ <div class="form-group">
180
+ <label for="text_threshold">Text Threshold ({{ text_threshold }}):</label>
181
+ <input type="range" id="text_threshold" name="text_threshold" min="0" max="1" step="0.05" value="0.13">
182
+ </div>
183
+
184
+ <button type="submit">πŸ” Rileva Studenti</button>
185
+ </form>
186
+
187
+ {% if result_image %}
188
+ <div class="results">
189
+ <h3>Risultati:</h3>
190
+ <img src="data:image/jpeg;base64,{{ result_image }}" alt="Risultato" style="max-width: 100%;">
191
+
192
+ {% if crops %}
193
+ <h4>Ritagli individuati ({{ crops|length }}):</h4>
194
+ <div class="gallery">
195
+ {% for crop in crops %}
196
+ <img src="data:image/jpeg;base64,{{ crop }}" alt="Ritaglio {{ loop.index }}">
197
+ {% endfor %}
198
+ </div>
199
+ {% endif %}
200
+ </div>
201
+ {% endif %}
202
+ </div>
203
+ </body>
204
+ </html>
205
+ ''', box_threshold=0.14, text_threshold=0.13)
206
+
207
+ @app.route('/detect', methods=['POST'])
208
+ @require_auth
209
+ def detect():
210
+ if 'image' not in request.files:
211
+ return redirect(url_for('index'))
212
+
213
+ image_file = request.files['image']
214
+ if image_file.filename == '':
215
+ return redirect(url_for('index'))
216
+
217
+ try:
218
+ # Process image
219
+ image = Image.open(image_file.stream).convert('RGB')
220
+ text_query = request.form.get('text_query', 'heads.')
221
+ box_threshold = float(request.form.get('box_threshold', 0.14))
222
+ text_threshold = float(request.form.get('text_threshold', 0.13))
223
+
224
+ # Run detection
225
+ result_image, crop_paths = detect_and_draw(image, text_query, box_threshold, text_threshold)
226
+
227
+ # Convert images to base64 for display
228
+ import base64
229
+
230
+ # Convert result image to base64
231
+ img_buffer = io.BytesIO()
232
+ result_image.save(img_buffer, format='JPEG')
233
+ result_b64 = base64.b64encode(img_buffer.getvalue()).decode()
234
+
235
+ # Convert crops to base64
236
+ crops_b64 = []
237
+ for crop_path in crop_paths:
238
+ with open(crop_path, 'rb') as f:
239
+ crop_b64 = base64.b64encode(f.read()).decode()
240
+ crops_b64.append(crop_b64)
241
+ # Cleanup temp file
242
+ os.unlink(crop_path)
243
+
244
+ return render_template_string('''
245
  <!DOCTYPE html>
246
  <html>
247
  <head>
248
+ <title>Risultati - Student Finder</title>
249
  <style>
250
+ body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; }
251
+ .header { background: #e8f5e8; padding: 20px; border-radius: 10px; margin-bottom: 20px; }
252
+ .content { background: #f5f5f5; padding: 30px; border-radius: 10px; }
253
+ .logout { float: right; }
254
+ .gallery { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 10px; margin-top: 20px; }
255
+ .gallery img { max-width: 100%; height: auto; border: 1px solid #ddd; border-radius: 4px; }
256
+ .back-btn { background: #6c757d; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; margin-bottom: 20px; }
257
+ .back-btn:hover { background: #545b62; }
258
  </style>
259
  </head>
260
  <body>
261
+ <div class="header">
262
+ <h1>πŸŽ“ Risultati Student Finder</h1>
263
+ <a href="/logout" class="logout">πŸ”“ Logout</a>
264
+ <div style="clear: both;"></div>
265
  </div>
266
 
267
+ <a href="/" class="back-btn">← Nuova Analisi</a>
268
+
269
  <div class="content">
270
+ <h3>Immagine con bounding box:</h3>
271
+ <img src="data:image/jpeg;base64,{{ result_image }}" alt="Risultato" style="max-width: 100%; border: 1px solid #ddd; border-radius: 4px;">
272
+
273
+ {% if crops %}
274
+ <h3>Ritagli individuati ({{ crops|length }}):</h3>
275
+ <div class="gallery">
276
+ {% for crop in crops %}
277
+ <img src="data:image/jpeg;base64,{{ crop }}" alt="Ritaglio {{ loop.index }}">
278
+ {% endfor %}
279
+ </div>
280
+ {% else %}
281
+ <p>Nessun ritaglio individuato.</p>
282
+ {% endif %}
283
  </div>
284
  </body>
285
  </html>
286
+ ''', result_image=result_b64, crops=crops_b64)
287
+
288
+ except Exception as e:
289
+ return f"Errore durante l'elaborazione: {str(e)}", 500
290
 
291
  @app.route('/login', methods=['GET', 'POST'])
292
  def login():
 
293
  if is_authenticated():
294
  return redirect(url_for('index'))
295
 
296
  error = None
297
  if request.method == 'POST':
298
+ if request.form.get('password') == SECRET_PASSWORD:
299
+ session.permanent = True
300
+ session['authenticated'] = True
301
+ return redirect(url_for('index'))
 
 
 
302
  else:
303
  error = "❌ Password errata. Riprova."
304
 
305
+ return render_template_string('''
 
306
  <!DOCTYPE html>
307
  <html>
308
  <head>
309
+ <title>Login - Student Finder</title>
310
  <style>
311
  body {
312
  font-family: Arial, sans-serif;
 
360
  </head>
361
  <body>
362
  <div class="login-form">
363
+ <h2>πŸ”’ Student Finder - Accesso Protetto</h2>
364
+ <p style="text-align: center; color: #666;">Inserisci la password per accedere</p>
365
+ {% if error %}
366
+ <div class="error">{{ error }}</div>
367
+ {% endif %}
 
 
 
 
 
368
  <form method="POST">
369
+ <input type="password" name="password" placeholder="Password" required>
370
  <button type="submit">πŸ”‘ Accedi</button>
371
  </form>
372
  </div>
373
  </body>
374
  </html>
375
+ ''', error=error)
 
 
376
 
377
  @app.route('/logout')
378
  def logout():
379
+ session.clear()
380
+ return redirect(url_for('login'))
 
 
 
 
 
 
 
 
 
381
 
382
  if __name__ == '__main__':
383
+ port = int(os.environ.get('PORT', 7680))
 
384
  app.run(host='0.0.0.0', port=port, debug=False)