| from fastapi import FastAPI, File, UploadFile, Form |
| from fastapi.responses import HTMLResponse |
| from model import predict, get_advice |
| from io import BytesIO |
| from PIL import Image |
| import base64 |
|
|
| app = FastAPI() |
|
|
| @app.get("/", response_class=HTMLResponse) |
| async def index(): |
| html_content = """ |
| <!DOCTYPE html> |
| <html> |
| <head> |
| <title>Détection de Race</title> |
| <meta charset="UTF-8"> |
| <style> |
| body { |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| background: linear-gradient(135deg, #e8eafa 0%, #dfecf2 100%); |
| display: flex; |
| flex-direction: column; |
| align-items: center; |
| justify-content: start; |
| min-height: 100vh; |
| padding-top: 60px; |
| margin: 0; |
| } |
| h1 { |
| color: #333; |
| font-size: 2rem; |
| margin-bottom: 30px; |
| } |
| form { |
| background-color: #ffffff; |
| width: 400px; |
| padding: 30px; |
| border-radius: 15px; |
| box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); |
| } |
| label { |
| font-size: 1rem; |
| font-weight: bold; |
| color: #444; |
| } |
| input[type="file"] { |
| margin-top: 10px; |
| margin-bottom: 20px; |
| padding: 8px; |
| width: 100%; |
| border: 1px solid #ccc; |
| border-radius: 6px; |
| background-color: #f9f9f9; |
| } |
| button { |
| background: linear-gradient(135deg, #4d6bb0, #5a5e99); |
| color: white; |
| padding: 10px 20px; |
| border: none; |
| border-radius: 8px; |
| font-size: 1rem; |
| cursor: pointer; |
| } |
| img#preview { |
| margin-top: 20px; |
| max-width: 300px; |
| border-radius: 12px; |
| display: none; |
| } |
| </style> |
| <script> |
| document.addEventListener("DOMContentLoaded", function () { |
| const fileInput = document.getElementById("fileInput"); |
| const preview = document.getElementById("preview"); |
| |
| fileInput.addEventListener("change", function () { |
| if (this.files.length > 0) { |
| const reader = new FileReader(); |
| reader.onload = function (e) { |
| preview.src = e.target.result; |
| preview.style.display = "block"; |
| }; |
| reader.readAsDataURL(this.files[0]); |
| } |
| }); |
| }); |
| </script> |
| </head> |
| <body> |
| <h1>Détection de Race à partir de l'Image</h1> |
| <form id="uploadForm" action="/predict/" method="post" enctype="multipart/form-data"> |
| <label for="fileInput">Choisir une image :</label><br><br> |
| <input type="file" name="file" id="fileInput" accept="image/*" required><br><br> |
| <center><img id="preview" src="#" alt="Aperçu de l'image"><br></center> |
| <center><button type="submit">Analyse</button></center> |
| </form> |
| </body> |
| </html> |
| """ |
| return HTMLResponse(content=html_content) |
|
|
|
|
| @app.post("/predict/") |
| async def predict_race(file: UploadFile = File(...)): |
| image_data = await file.read() |
| image = Image.open(BytesIO(image_data)).convert("RGB") |
|
|
| |
| buffered = BytesIO() |
| image.save(buffered, format="JPEG") |
| img_str = base64.b64encode(buffered.getvalue()).decode("utf-8") |
|
|
| label, confidence = predict(image) |
| advice = get_advice(label) |
|
|
| temperament = taille = activite = esperance = "" |
|
|
| if isinstance(advice, str): |
| lines = advice.split("\n") |
| for line in lines: |
| line_lower = line.lower() |
| if line_lower.startswith("tempérament") or line_lower.startswith("temperament"): |
| temperament = line |
| elif line_lower.startswith("taille"): |
| taille = line |
| elif line_lower.startswith("activité") or line_lower.startswith("activity"): |
| activite = line |
| elif line_lower.startswith("espérance") or line_lower.startswith("life"): |
| esperance = line |
| advice = "" |
| else: |
| advice = "Aucune information disponible." |
|
|
| return HTMLResponse(content=f""" |
| <html> |
| <head> |
| <title>Résultats</title> |
| <style> |
| body {{ |
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; |
| background: linear-gradient(135deg, #e8eafa, #dfecf2); |
| text-align: center; |
| padding: 60px 20px; |
| margin: 0; |
| }} |
| .result {{ |
| background-color: #ffffff; |
| padding: 40px; |
| border-radius: 16px; |
| box-shadow: 0 12px 24px rgba(0, 0, 0, 0.1); |
| display: inline-block; |
| max-width: 600px; |
| width: 90%; |
| }} |
| h1, h2, h3 {{ |
| color: #333; |
| margin: 10px 0; |
| }} |
| p {{ |
| font-size: 1rem; |
| color: #555; |
| margin: 15px 0; |
| }} |
| img {{ |
| margin-top: 20px; |
| max-width: 100%; |
| border-radius: 12px; |
| box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); |
| }} |
| a {{ |
| display: inline-block; |
| margin-top: 30px; |
| background: linear-gradient(135deg, #66bb6a, #43a047); |
| color: white; |
| padding: 12px 24px; |
| border-radius: 8px; |
| text-decoration: none; |
| font-size: 1rem; |
| }} |
| </style> |
| </head> |
| <body> |
| <div class="result"> |
| <h1>Résultat de la Détection</h1> |
| <h2>Race détectée : {label}</h2> |
| <h3>Confiance : {confidence * 100:.2f}%</h3> |
| <h3>Fiche descriptive :</h3> |
| <p>{advice}</p> |
| <p>{temperament}</p> |
| <p>{taille}</p> |
| <p>{activite}</p> |
| <p>{esperance}</p> |
| <img src="data:image/jpeg;base64,{img_str}" alt="Image Uploadée"> |
| <br> |
| <a href="/">🔙 Retour</a> |
| </div> |
| </body> |
| </html> |
| """) |
|
|
|
|