Yash goyal commited on
Commit
82107dd
·
verified ·
1 Parent(s): a702a08

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +192 -490
app.py CHANGED
@@ -1,493 +1,195 @@
1
- import os
2
- os.environ['MPLCONFIGDIR'] = '/tmp/matplotlib'
3
- from flask import Flask, render_template, request, redirect, url_for, session, send_file
4
- from flask_sqlalchemy import SQLAlchemy
5
- from flask_migrate import Migrate
6
- import tensorflow as tf
7
- import numpy as np
8
- from PIL import Image
9
- import pickle
10
- import io
11
- import os
12
- import matplotlib.pyplot as plt
13
- from reportlab.lib.pagesizes import A4
14
- from reportlab.lib import colors
15
- from reportlab.pdfgen import canvas
16
- from reportlab.lib.units import inch
17
- from datetime import datetime
18
- import logging
19
- from flask_mail import Mail, Message
20
- from flask import jsonify, url_for
21
-
22
- app = Flask(__name__)
23
- app.secret_key = "e3f6f40bb8b2471b9f07c4025d845be9"
24
-
25
- # Database configuration
26
- app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/snapsin.db'
27
- app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
28
- db = SQLAlchemy(app)
29
- migrate = Migrate(app, db)
30
-
31
- # Mail configuration
32
- app.config['MAIL_SERVER'] = 'smtp.gmail.com'
33
- app.config['MAIL_PORT'] = 465
34
- app.config['MAIL_USERNAME'] = os.environ.get('MAIL_USERNAME')
35
- app.config['MAIL_PASSWORD'] = os.environ.get('MAIL_PASSWORD')
36
- app.config['MAIL_USE_TLS'] = False
37
- app.config['MAIL_USE_SSL'] = True
38
- mail = Mail(app)
39
-
40
- MODEL_PATH = "skin_lesion_model.h5"
41
- HISTORY_PATH = "training_history.pkl"
42
- PLOT_PATH = "/tmp/static/training_plot.png"
43
- LOGO_PATH = "static/logo.jpg"
44
- IMG_SIZE = (224, 224)
45
- CONFIDENCE_THRESHOLD = 0.30
46
-
47
- label_map = {
48
- 0: "Melanoma",
49
- 1: "Melanocytic nevus",
50
- 2: "Basal cell carcinoma",
51
- 3: "Actinic keratosis",
52
- 4: "Benign keratosis",
53
- 5: "Dermatofibroma",
54
- 6: "Vascular lesion",
55
- 7: "Squamous cell carcinoma"
56
- }
57
-
58
- recommendations = {
59
- "Melanoma": {
60
- "solutions": [
61
- "Consult a dermatologist immediately.",
62
- "Surgical removal is typically required.",
63
- "Regular follow-up and screening for metastasis."
64
- ],
65
- "medications": ["Interferon alfa-2b", "Vemurafenib", "Dacarbazine"]
66
- },
67
- "Melanocytic nevus": {
68
- "solutions": [
69
- "Usually benign and requires no treatment.",
70
- "Monitor for any change in shape or color."
71
- ],
72
- "medications": ["No medication necessary unless changes occur."]
73
- },
74
- "Basal cell carcinoma": {
75
- "solutions": [
76
- "Surgical excision or Mohs surgery.",
77
- "Topical treatments if superficial.",
78
- "Radiation in select cases."
79
- ],
80
- "medications": ["Imiquimod cream", "Fluorouracil cream", "Vismodegib"]
81
- },
82
- "Actinic keratosis": {
83
- "solutions": [
84
- "Cryotherapy or topical treatments.",
85
- "Avoid prolonged sun exposure.",
86
- "Use of sunscreen regularly."
87
- ],
88
- "medications": ["Fluorouracil", "Imiquimod", "Diclofenac gel"]
89
- },
90
- "Benign keratosis": {
91
- "solutions": [
92
- "Generally harmless and often left untreated.",
93
- "Can be removed for cosmetic reasons."
94
- ],
95
- "medications": ["No medication required unless infected."]
96
- },
97
- "Dermatofibroma": {
98
- "solutions": [
99
- "Benign skin growth, no treatment needed.",
100
- "Surgical removal if painful or for cosmetic reasons."
101
- ],
102
- "medications": ["No medication needed."]
103
- },
104
- "Vascular lesion": {
105
- "solutions": [
106
- "Treatment depends on type (e.g., hemangioma).",
107
- "Laser therapy is commonly used.",
108
- "Observation if no complications."
109
- ],
110
- "medications": ["Beta-blockers (e.g., propranolol for hemangioma)"]
111
- },
112
- "Squamous cell carcinoma": {
113
- "solutions": [
114
- "Surgical removal is standard.",
115
- "Follow-up for recurrence or metastasis.",
116
- "Avoid sun exposure and use sunscreen."
117
- ],
118
- "medications": ["Fluorouracil", "Cisplatin", "Imiquimod"]
119
- },
120
- "Low confidence": {
121
- "solutions": [
122
- "The image is not confidently classified.",
123
- "Please upload a clearer image or consult a doctor."
124
- ],
125
- "medications": ["Not available due to low confidence."]
126
- },
127
- "Unknown": {
128
- "solutions": ["No specific guidance available."],
129
- "medications": ["N/A"]
130
- }
131
- }
132
-
133
- # Logger
134
- logging.basicConfig(level=logging.INFO)
135
- logger = logging.getLogger(__name__)
136
-
137
- # Database Models
138
- class User(db.Model):
139
- id = db.Column(db.Integer, primary_key=True)
140
- name = db.Column(db.String(100), nullable=False)
141
- email = db.Column(db.String(120), unique=True, nullable=False)
142
- scans = db.relationship('Scan', backref='user', lazy=True)
143
-
144
- class Scan(db.Model):
145
- id = db.Column(db.Integer, primary_key=True)
146
- user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
147
- patient_name = db.Column(db.String(100), nullable=False)
148
- patient_gender = db.Column(db.String(20), nullable=False)
149
- patient_age = db.Column(db.Integer, nullable=False)
150
- prediction = db.Column(db.String(100), nullable=False)
151
- confidence = db.Column(db.String(20), nullable=False)
152
- timestamp = db.Column(db.DateTime, default=datetime.utcnow)
153
- image_filename = db.Column(db.String(100), nullable=False)
154
-
155
- # Load Model
156
- try:
157
- logger.info("Loading model from %s", MODEL_PATH)
158
- model = tf.keras.models.load_model(MODEL_PATH)
159
- except Exception as e:
160
- logger.error("Failed to load model: %s", str(e))
161
- raise
162
-
163
- # Plot training history
164
- if os.path.exists(HISTORY_PATH):
165
- try:
166
- with open(HISTORY_PATH, "rb") as f:
167
- history_dict = pickle.load(f)
168
- if "accuracy" in history_dict and "val_accuracy" in history_dict:
169
- os.makedirs("/tmp/static", exist_ok=True)
170
- plt.plot(history_dict['accuracy'], label='Train Accuracy')
171
- plt.plot(history_dict['val_accuracy'], label='Val Accuracy')
172
- plt.xlabel('Epochs')
173
- plt.ylabel('Accuracy')
174
- plt.title('Training History')
175
- plt.legend()
176
- plt.grid(True)
177
- plt.savefig(PLOT_PATH)
178
- plt.close()
179
- logger.info("Training plot saved at %s", PLOT_PATH)
180
- except Exception as e:
181
- logger.error("Training history load error: %s", str(e))
182
-
183
- def preprocess_image(image_bytes):
184
- image = Image.open(io.BytesIO(image_bytes)).convert("RGB")
185
- image = image.resize(IMG_SIZE)
186
- image_array = tf.keras.utils.img_to_array(image)
187
- return np.expand_dims(image_array, axis=0) / 255.0
188
-
189
- def generate_pdf(report, filepath):
190
- c = canvas.Canvas(filepath, pagesize=A4)
191
- width, height = A4
192
- y = height - 60
193
-
194
- # Background
195
- c.setFillColor(colors.Color(0.98, 0.98, 0.99, alpha=1))
196
- c.rect(0, 0, width, height, fill=1, stroke=0)
197
-
198
- # Header background
199
- c.setFillColor(colors.Color(0.94, 0.96, 0.98, alpha=1))
200
- c.rect(0, height-120, width, 120, fill=1, stroke=0)
201
-
202
- # Logo from root directory - square JPG format
203
- try:
204
- logo_path = "./logo.jpg"
205
- if os.path.exists(logo_path):
206
- c.setFillColor(colors.white)
207
- c.rect(65, y-25, 50, 50, fill=1, stroke=1)
208
- c.setStrokeColor(colors.Color(0.7, 0.7, 0.7, alpha=1))
209
- c.setLineWidth(1)
210
- c.rect(65, y-25, 50, 50, fill=0, stroke=1)
211
- c.drawImage(logo_path, 67, y-23, width=46, height=46, preserveAspectRatio=True, mask='auto')
212
- except Exception as e:
213
- logger.warning("Logo error: %s", str(e))
214
-
215
- # Professional title
216
- c.setFont("Helvetica-Bold", 22)
217
- c.setFillColor(colors.Color(0.2, 0.2, 0.2, alpha=1))
218
- c.drawCentredString(width / 2, y + 5, "Medical Diagnosis Report")
219
-
220
- # Subtitle
221
- c.setFont("Helvetica", 11)
222
- c.setFillColor(colors.Color(0.5, 0.5, 0.5, alpha=1))
223
- c.drawCentredString(width / 2, y - 15, "Dermatological Analysis")
224
-
225
- # Professional line
226
- c.setStrokeColor(colors.Color(0.8, 0.8, 0.8, alpha=1))
227
- c.setLineWidth(1)
228
- c.line(80, y - 35, width - 80, y - 35)
229
-
230
- y -= 80
231
-
232
- def professional_section_box(title, fields, extra_gap=20):
233
- nonlocal y
234
-
235
- box_height = len(fields) * 20 + 40
236
- c.setFillColor(colors.Color(0.96, 0.96, 0.96, alpha=0.3))
237
- c.rect(42, y - box_height - 2, width - 84, box_height, fill=1, stroke=0)
238
-
239
- c.setFillColor(colors.white)
240
- c.rect(40, y - box_height, width - 80, box_height, fill=1, stroke=1)
241
- c.setStrokeColor(colors.Color(0.9, 0.9, 0.9, alpha=1))
242
-
243
- c.setFillColor(colors.Color(0.95, 0.95, 0.95, alpha=1))
244
- c.rect(40, y - 30, width - 80, 30, fill=1, stroke=0)
245
-
246
- c.setFont("Helvetica-Bold", 12)
247
- c.setFillColor(colors.Color(0.3, 0.3, 0.3, alpha=1))
248
- c.drawString(55, y - 20, title)
249
-
250
- y -= 45
251
- c.setFont("Helvetica", 10)
252
- c.setFillColor(colors.Color(0.2, 0.2, 0.2, alpha=1))
253
-
254
- for label, val in fields.items():
255
- c.setFont("Helvetica-Bold", 9)
256
- c.setFillColor(colors.Color(0.4, 0.4, 0.4, alpha=1))
257
- c.drawString(55, y, f"{label}:")
258
- c.setFont("Helvetica", 9)
259
- c.setFillColor(colors.Color(0.2, 0.2, 0.2, alpha=1))
260
- c.drawString(150, y, str(val))
261
- y -= 20
262
-
263
- y -= extra_gap
264
-
265
- professional_section_box("Patient Information", {
266
- "Name": report["name"],
267
- "Email": report["email"],
268
- "Gender": report["gender"],
269
- "Age": f"{report['age']} years"
270
- })
271
-
272
- confidence_val = float(report["confidence"].replace('%', ''))
273
- confidence_text = f"{report['confidence']} ({'High' if confidence_val > 85 else 'Moderate' if confidence_val > 70 else 'Low'} Confidence)"
274
-
275
- professional_section_box("Diagnostic Results", {
276
- "Condition": report["prediction"],
277
- "Confidence": confidence_text,
278
- "Notes": report["message"] if report["message"] else "No additional notes"
279
- })
280
-
281
- disease = report["prediction"]
282
- treatment = recommendations.get(disease, recommendations["Unknown"])
283
-
284
- professional_section_box("Treatment Recommendations", {
285
- f"{i+1}. {line}": "" for i, line in enumerate(treatment["solutions"])
286
- })
287
-
288
- professional_section_box("Medication Guidelines", {
289
- f"{i+1}. {line}": "" for i, line in enumerate(treatment["medications"])
290
- })
291
-
292
- c.setFillColor(colors.Color(0.98, 0.98, 0.98, alpha=1))
293
- c.rect(40, 40, width - 80, 70, fill=1, stroke=1)
294
- c.setStrokeColor(colors.Color(0.9, 0.9, 0.9, alpha=1))
295
-
296
- c.setFont("Helvetica-Bold", 10)
297
- c.setFillColor(colors.Color(0.4, 0.4, 0.4, alpha=1))
298
- c.drawString(50, 95, "Medical Disclaimer")
299
-
300
- c.setFont("Helvetica", 8)
301
- c.setFillColor(colors.Color(0.3, 0.3, 0.3, alpha=1))
302
- disclaimer_lines = [
303
- "This report is generated using AI technology for preliminary assessment purposes only.",
304
- "Results should not replace professional medical consultation and diagnosis.",
305
- "Please consult a qualified healthcare provider for comprehensive medical evaluation."
306
- ]
307
-
308
- for i, line in enumerate(disclaimer_lines):
309
- c.drawString(50, 80 - (i * 10), line)
310
-
311
- c.save()
312
-
313
- @app.route("/")
314
- def home():
315
- return redirect(url_for("form"))
316
-
317
- @app.route("/form")
318
- def form():
319
- return render_template("form.html", history_plot="/training_plot.png")
320
-
321
- @app.route("/training_plot.png")
322
- def training_plot():
323
- return send_file(PLOT_PATH, mimetype="image/png")
324
-
325
- @app.route("/api/history")
326
- def api_history():
327
- user_email = request.args.get('email')
328
- if not user_email:
329
- return jsonify({"error": "Email parameter is required"}), 400
330
-
331
- user = User.query.filter_by(email=user_email).first()
332
- if not user:
333
- return jsonify([])
334
-
335
- scans = Scan.query.filter_by(user_id=user.id).order_by(Scan.timestamp.desc()).all()
336
-
337
- history_data = []
338
- for scan in scans:
339
- image_url = url_for('uploaded_file', filename=scan.image_filename, _external=True)
340
- history_data.append({
341
- "id": scan.id,
342
- "prediction": scan.prediction,
343
- "confidence": scan.confidence,
344
- "timestamp": scan.timestamp.strftime("%B %d, %Y at %I:%M %p"),
345
- "patient_name": scan.patient_name,
346
- "image_url": image_url
347
- })
348
-
349
- return jsonify(history_data)
350
-
351
- @app.route("/api/email-report/<int:scan_id>")
352
- def email_report(scan_id):
353
- scan = Scan.query.get(scan_id)
354
- if not scan:
355
- return jsonify({"error": "Report not found"}), 404
356
-
357
- try:
358
- report_data = {
359
- "name": scan.user.name,
360
- "email": scan.user.email,
361
- "gender": scan.patient_gender,
362
- "age": scan.patient_age,
363
- "prediction": scan.prediction,
364
- "confidence": scan.confidence,
365
- "message": ""
366
  }
367
- pdf_path = f"/tmp/report_{scan_id}.pdf"
368
- generate_pdf(report_data, pdf_path)
369
-
370
- msg = Message(
371
- 'Your SnapSkin Diagnostic Report',
372
- sender=app.config['MAIL_USERNAME'],
373
- recipients=[scan.user.email]
374
- )
375
- msg.body = f"Dear {scan.user.name},\n\nPlease find your requested diagnostic report attached.\n\nThank you for using SnapSkin."
376
- with app.open_resource(pdf_path) as fp:
377
- msg.attach(f"SnapSkin_Report_{scan_id}.pdf", "application/pdf", fp.read())
378
-
379
- mail.send(msg)
380
- os.remove(pdf_path)
381
-
382
- return jsonify({"success": True, "message": f"Report sent to {scan.user.email}"})
383
 
384
- except Exception as e:
385
- logger.error(f"Failed to send email for scan {scan_id}: {e}")
386
- return jsonify({"success": False, "message": "Failed to send email."}), 500
387
-
388
- @app.route("/predict", methods=["POST"])
389
- def predict():
390
- try:
391
- if "image" not in request.files:
392
- raise ValueError("No image uploaded.")
393
- image = request.files["image"]
394
- image_bytes = image.read()
395
- img_array = preprocess_image(image_bytes)
396
- prediction = model.predict(img_array)[0]
397
- predicted_index = int(np.argmax(prediction))
398
- confidence = float(prediction[predicted_index])
399
- label = label_map.get(predicted_index, "Unknown") if confidence >= CONFIDENCE_THRESHOLD else "Low confidence"
400
- msg = "⚠ This image is not confidently recognized. Please upload a clearer image." if confidence < CONFIDENCE_THRESHOLD else ""
401
-
402
- # Save user and scan data
403
- email = request.form.get("email")
404
- user = User.query.filter_by(email=email).first()
405
- if not user:
406
- user = User(name=request.form.get("name"), email=email)
407
- db.session.add(user)
408
- db.session.commit()
409
-
410
- # Save image
411
- timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
412
- image_filename = f"scan_{timestamp}.jpg"
413
- image_path = os.path.join("static/uploads", image_filename)
414
- os.makedirs("static/uploads", exist_ok=True)
415
- image.seek(0)
416
- image.save(image_path)
417
-
418
- scan = Scan(
419
- user_id=user.id,
420
- patient_name=request.form.get("name"),
421
- patient_gender=request.form.get("gender"),
422
- patient_age=int(request.form.get("age")),
423
- prediction=label,
424
- confidence=f"{confidence * 100:.2f}%",
425
- image_filename=image_filename
426
- )
427
- db.session.add(scan)
428
- db.session.commit()
429
-
430
- report = {
431
- "name": request.form.get("name"),
432
- "email": email,
433
- "gender": request.form.get("gender"),
434
- "age": request.form.get("age"),
435
- "prediction": label,
436
- "confidence": f"{confidence * 100:.2f}%",
437
- "message": msg,
438
- "scan_id": scan.id
439
  }
440
- session["report"] = report
441
-
442
- # Send email automatically
443
- try:
444
- pdf_path = f"/tmp/report_{scan.id}.pdf"
445
- generate_pdf(report, pdf_path)
446
- msg = Message(
447
- 'Your SnapSkin Diagnostic Report',
448
- sender=app.config['MAIL_USERNAME'],
449
- recipients=[email]
450
- )
451
- msg.body = f"Dear {report['name']},\n\nPlease find your diagnostic report attached.\n\nThank you for using SnapSkin."
452
- with app.open_resource(pdf_path) as fp:
453
- msg.attach(f"SnapSkin_Report_{scan.id}.pdf", "application/pdf", fp.read())
454
- mail.send(msg)
455
- os.remove(pdf_path)
456
- report["email_status"] = "Report sent to your email."
457
- except Exception as e:
458
- logger.error(f"Failed to send email: {e}")
459
- report["email_status"] = "Failed to send report to email."
460
-
461
- return redirect(url_for("result"))
462
- except Exception as e:
463
- return render_template("form.html", history_plot="/training_plot.png", result={
464
- "prediction": "Error",
465
- "confidence": "N/A",
466
- "message": str(e),
467
- "email_status": "Error occurred, no email sent."
468
- })
469
-
470
- @app.route("/result")
471
- def result():
472
- report = session.get("report", {})
473
- return render_template("result.html", **report)
474
-
475
- @app.route("/download-report")
476
- def download_report():
477
- report = session.get("report", {})
478
- if not report:
479
- return redirect(url_for("form"))
480
- os.makedirs("/tmp/reports", exist_ok=True)
481
- timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
482
- filepath = f"/tmp/reports/report_{timestamp}.pdf"
483
- generate_pdf(report, filepath)
484
- return send_file(filepath, as_attachment=True)
485
-
486
- @app.route("/uploads/<filename>")
487
- def uploaded_file(filename):
488
- return send_file(os.path.join("static/uploads", filename))
489
-
490
- if __name__ == "__main__":
491
- with app.app_context():
492
- db.create_all()
493
- app.run(host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
+ <title>SnapSkin</title>
7
+ <link rel="stylesheet" href="/static/form-styles.css" />
8
+ <link rel="shortcut icon" href="/static/logo.png" />
9
+ <link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap" rel="stylesheet"/>
10
+ <script src="/static/preloader.js"></script>
11
+ <script src="/static/cursor-effect.js"></script>
12
+ <script defer>
13
+ document.addEventListener("DOMContentLoaded", () => {
14
+ const fileInput = document.getElementById("image");
15
+ const fileMessage = document.getElementById("upload-message");
16
+ const submitBtn = document.getElementById("submit-btn");
17
+ const emailBtn = document.getElementById("email-report-btn");
18
+
19
+ fileInput.addEventListener("change", () => {
20
+ const file = fileInput.files[0];
21
+ fileMessage.style.display = "block";
22
+
23
+ if (!file) {
24
+ fileMessage.textContent = "No file selected.";
25
+ fileMessage.style.color = "orange";
26
+ submitBtn.disabled = true;
27
+ return;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
+ const allowedTypes = ["image/jpeg", "image/png"];
31
+ const maxSize = 20 * 1024 * 1024; // 20MB
32
+
33
+ if (!allowedTypes.includes(file.type)) {
34
+ fileMessage.textContent = "Invalid file format. Use JPG or PNG.";
35
+ fileMessage.style.color = "red";
36
+ submitBtn.disabled = true;
37
+ } else if (file.size > maxSize) {
38
+ fileMessage.textContent = "File too large. Max 20MB allowed.";
39
+ fileMessage.style.color = "red";
40
+ submitBtn.disabled = true;
41
+ } else {
42
+ fileMessage.textContent = "✅ File ready for upload.";
43
+ fileMessage.style.color = "limegreen";
44
+ submitBtn.disabled = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  }
46
+ });
47
+
48
+ if (emailBtn) {
49
+ emailBtn.addEventListener("click", async () => {
50
+ const scanId = emailBtn.dataset.scanId;
51
+ const emailStatus = document.getElementById("email-status");
52
+ try {
53
+ const response = await fetch(`/api/email-report/${scanId}`);
54
+ const data = await response.json();
55
+ emailStatus.textContent = data.message;
56
+ emailStatus.style.color = data.success ? "limegreen" : "red";
57
+ } catch (error) {
58
+ emailStatus.textContent = "Error sending email.";
59
+ emailStatus.style.color = "red";
60
+ }
61
+ });
62
+ }
63
+ });
64
+ </script>
65
+ </head>
66
+ <body>
67
+ <div class="preloader">
68
+ <div class="preloader-particles"></div>
69
+ <div class="dna-loader">
70
+ <div class="dna-loader-strand"></div>
71
+ <div class="dna-loader-strand"></div>
72
+ <div class="dna-loader-rung"></div>
73
+ <div class="dna-loader-rung"></div>
74
+ <div class="dna-loader-rung"></div>
75
+ <div class="dna-loader-rung"></div>
76
+ <div class="dna-loader-rung"></div>
77
+ <div class="dna-loader-rung"></div>
78
+ <div class="dna-loader-rung"></div>
79
+ <div class="dna-loader-rung"></div>
80
+ <div class="loader-text">LOADING...</div>
81
+ </div>
82
+ </div>
83
+
84
+ <header>
85
+ <nav>
86
+ <div class="logo" style="color: beige;">SNAP<span style="color: #00ffff;">SKIN</span></div>
87
+ <ul>
88
+ <li><a href="https://snapskin.vercel.app/">Home</a></li>
89
+ </ul>
90
+ </nav>
91
+ </header>
92
+
93
+ <section class="form-section">
94
+ <div class="container">
95
+ <div class="form-container">
96
+ <h1 style="color: white;">ENTER PATIENT DETAILS</h1>
97
+ <p class="intro-text">
98
+ Fill out the form below for the diagnosis of your skin disease.
99
+ Our AI will process your image and generate a predicted report for you.
100
+ </p>
101
+
102
+ <div class="form-wrapper">
103
+ <form id="patient-form" action="/predict" method="POST" enctype="multipart/form-data">
104
+ <div class="form-group">
105
+ <label for="name">Full Name</label>
106
+ <input type="text" id="name" name="name" placeholder="Enter your name" required />
107
+ </div>
108
+
109
+ <div class="form-group">
110
+ <label for="email">Email Address</label>
111
+ <input type="email" id="email" name="email" placeholder="Enter your email address" required />
112
+ </div>
113
+
114
+ <div class="form-row">
115
+ <div class="form-group half">
116
+ <label for="gender">Gender</label>
117
+ <select id="gender" name="gender" required>
118
+ <option value="" disabled selected>Select gender</option>
119
+ <option value="male">Male</option>
120
+ <option value="female">Female</option>
121
+ <option value="other">Other</option>
122
+ </select>
123
+ </div>
124
+
125
+ <div class="form-group half">
126
+ <label for="age">Age</label>
127
+ <input type="number" id="age" name="age" placeholder="Enter your age" min="1" max="120" required />
128
+ </div>
129
+ </div>
130
+
131
+ <div class="form-group file-upload-group">
132
+ <label for="image">Upload Image</label>
133
+ <div class="file-upload-wrapper">
134
+ <input type="file" id="image" name="image" accept=".jpg,.jpeg,.png" required />
135
+ <span class="file-upload-text">Choose Image</span>
136
+ </div>
137
+ <small class="warning">Allowed: JPG, PNG | Max size: 20MB</small>
138
+ <div class="upload-message" id="upload-message" style="display: none;"></div>
139
+ </div>
140
+
141
+ <div class="form-group consent-group">
142
+ <input type="checkbox" id="consent" name="consent" required />
143
+ <label for="consent">I agree to the <a href="#">Terms</a> and <a href="#">Privacy Policy</a></label>
144
+ </div>
145
+
146
+ <div class="form-actions">
147
+ <button type="submit" class="submit-button" id="submit-btn">Submit Details</button>
148
+ </div>
149
+ </form>
150
+
151
+ <!-- Result Section -->
152
+ <div class="result-section" id="result-section" style="margin-top: 30px; {% if result %}display: block;{% else %}display: none;{% endif %}">
153
+ <h2>Diagnosis Result</h2>
154
+ <div id="result-content">
155
+ {% if result %}
156
+ <p><strong>Prediction:</strong> {{ result.prediction }}</p>
157
+ <p><strong>Confidence:</strong> {{ result.confidence }}</p>
158
+ {% if result.message %}
159
+ <p class="warning-message">{{ result.message }}</p>
160
+ {% endif %}
161
+ <p id="email-status" style="color: {% if 'Failed' in result.email_status %}red{% else %}limegreen{% endif %}">{{ result.email_status }}</p>
162
+ <button id="email-report-btn" class="submit-button" data-scan-id="{{ result.scan_id }}">Resend Report to Email</button>
163
+ {% endif %}
164
+ </div>
165
+ </div>
166
+ </div>
167
+ </div>
168
+ </div>
169
+ </section>
170
+
171
+ <footer>
172
+ <div class="container">
173
+ <div class="footer-content">
174
+ <div class="footer-logo">SNAP<span>SKIN</span></div>
175
+ <div class="footer-links">
176
+ <h3>Quick Links</h3>
177
+ <ul>
178
+ <li><a href="/form">Home</a></li>
179
+ <li><a href="#">About</a></li>
180
+ <li><a href="#">Features</a></li>
181
+ </ul>
182
+ </div>
183
+ <div class="footer-contact">
184
+ <h3>Support</h3>
185
+ <p>Email: info.snapskin@gmail.com</p>
186
+ <p>Phone: 7817833974 / 7983595318</p>
187
+ </div>
188
+ </div>
189
+ <div class="copyright">
190
+ <p>© 2025 SnapSkin. All rights reserved.</p>
191
+ </div>
192
+ </div>
193
+ </footer>
194
+ </body>
195
+ </html>