|
|
import gradio as gr |
|
|
import requests |
|
|
import os |
|
|
from PIL import Image |
|
|
import io |
|
|
import base64 |
|
|
import json |
|
|
import tempfile |
|
|
|
|
|
class FashionClassifier: |
|
|
def __init__(self): |
|
|
self.api_url = "https://api.marqo.ai/classify" |
|
|
self.api_key = os.getenv("MARQO_API_KEY") |
|
|
|
|
|
|
|
|
if not self.api_key or self.api_key == "your_marqo_api_key": |
|
|
print("⚠️ ATTENTION: Clé API Marqo non configurée!") |
|
|
print("👉 Ajoutez MARQO_API_KEY dans les secrets Hugging Face") |
|
|
|
|
|
def classify_image(self, image, max_categories=5, confidence_threshold=0.3): |
|
|
"""Classifie une image avec gestion d'erreurs complète""" |
|
|
try: |
|
|
|
|
|
if image is None: |
|
|
return {"error": "Aucune image fournie"} |
|
|
|
|
|
|
|
|
image = self.prepare_image(image) |
|
|
|
|
|
|
|
|
buffered = io.BytesIO() |
|
|
image.save(buffered, format="JPEG", optimize=True, quality=85) |
|
|
img_str = base64.b64encode(buffered.getvalue()).decode() |
|
|
|
|
|
headers = { |
|
|
"Content-Type": "application/json", |
|
|
"Authorization": f"Bearer {self.api_key}" |
|
|
} |
|
|
|
|
|
payload = { |
|
|
"model": "Marqo/Marqo-FashionSigLIP-Classification", |
|
|
"image_data": img_str, |
|
|
"parameters": { |
|
|
"max_categories": int(max_categories), |
|
|
"confidence_threshold": float(confidence_threshold) |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
print(f"📤 Envoi requête à Marqo API...") |
|
|
|
|
|
response = requests.post(self.api_url, headers=headers, json=payload, timeout=30) |
|
|
|
|
|
|
|
|
print(f"📥 Réponse reçue: {response.status_code}") |
|
|
|
|
|
if response.status_code != 200: |
|
|
return { |
|
|
"error": f"Erreur API {response.status_code}", |
|
|
"details": response.text |
|
|
} |
|
|
|
|
|
return response.json() |
|
|
|
|
|
except Exception as e: |
|
|
return {"error": f"Erreur: {str(e)}"} |
|
|
|
|
|
def prepare_image(self, image): |
|
|
"""Prépare l'image pour le modèle""" |
|
|
|
|
|
if image.mode != 'RGB': |
|
|
image = image.convert('RGB') |
|
|
|
|
|
|
|
|
max_size = (1024, 1024) |
|
|
image.thumbnail(max_size, Image.Resampling.LANCZOS) |
|
|
|
|
|
return image |
|
|
|
|
|
|
|
|
classifier = FashionClassifier() |
|
|
|
|
|
def process_image(image, max_categories, confidence): |
|
|
"""Fonction de traitement avec debug""" |
|
|
try: |
|
|
if image is None: |
|
|
return "❌ Veuillez uploader une image valide" |
|
|
|
|
|
print(f"🖼️ Image reçue: {image.size}, mode: {image.mode}") |
|
|
|
|
|
result = classifier.classify_image(image, max_categories, confidence) |
|
|
|
|
|
|
|
|
print(f"🔍 Résultat brut: {json.dumps(result, indent=2)}") |
|
|
|
|
|
if "error" in result: |
|
|
error_msg = f"❌ Erreur de classification:\n\n" |
|
|
error_msg += f"**Détails:** {result['error']}\n\n" |
|
|
|
|
|
|
|
|
if "401" in str(result['error']): |
|
|
error_msg += "🔑 **Solution:** Vérifiez votre clé API Marqo dans les secrets Hugging Face" |
|
|
elif "timeout" in str(result['error']).lower(): |
|
|
error_msg += "⏱️ **Solution:** L'image est trop lourde, essayez une image plus petite" |
|
|
else: |
|
|
error_msg += "💡 **Solution:** Essayez une image plus petite ou un format différent" |
|
|
|
|
|
return error_msg |
|
|
|
|
|
if "predictions" in result and result["predictions"]: |
|
|
output = "## 🎯 Résultats de classification:\n\n" |
|
|
for i, pred in enumerate(result["predictions"]): |
|
|
output += f"{i+1}. **{pred['label']}** - {pred['score']*100:.1f}%\n" |
|
|
|
|
|
if "processing_time" in result: |
|
|
output += f"\n⏱️ Temps de traitement: {result['processing_time']}s" |
|
|
|
|
|
return output |
|
|
else: |
|
|
return "❌ Aucune prédiction trouvé - Le modèle n'a pas reconnu l'image" |
|
|
|
|
|
except Exception as e: |
|
|
return f"❌ Erreur inattendue: {str(e)}" |
|
|
|
|
|
|
|
|
with gr.Blocks(title="Classificateur de Mode") as demo: |
|
|
gr.Markdown(""" |
|
|
# 🎨 Classificateur de Vêtements Marqo |
|
|
**Uploader une image de vêtement** pour la classifier automatiquement |
|
|
""") |
|
|
|
|
|
with gr.Row(): |
|
|
with gr.Column(scale=1): |
|
|
gr.Markdown("### 📤 Uploader votre image") |
|
|
image_input = gr.Image( |
|
|
type="pil", |
|
|
label="Image à classifier", |
|
|
height=300, |
|
|
sources=["upload", "webcam", "clipboard"] |
|
|
) |
|
|
|
|
|
gr.Markdown("### ⚙️ Paramètres") |
|
|
max_categories = gr.Slider(1, 10, value=5, label="Catégories max") |
|
|
confidence = gr.Slider(0.1, 1.0, value=0.3, label="Seuil de confiance") |
|
|
|
|
|
gr.Markdown("### 🚀 Actions") |
|
|
submit_btn = gr.Button("Classifier l'image", variant="primary") |
|
|
clear_btn = gr.Button("Effacer", variant="secondary") |
|
|
|
|
|
with gr.Column(scale=2): |
|
|
gr.Markdown("### 📊 Résultats") |
|
|
output_text = gr.Markdown( |
|
|
value="⬅️ Uploader une image de vêtement pour commencer" |
|
|
) |
|
|
|
|
|
|
|
|
gr.Examples( |
|
|
examples=[ |
|
|
["https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/beignets-task-guide.png"], |
|
|
["https://images.unsplash.com/photo-1558769132-cb1aea458c5e"], |
|
|
["https://images.unsplash.com/photo-1543163521-1bf539c55dd2"] |
|
|
], |
|
|
inputs=image_input, |
|
|
label="🖼️ Exemples testés et fonctionnels" |
|
|
) |
|
|
|
|
|
|
|
|
submit_btn.click( |
|
|
fn=process_image, |
|
|
inputs=[image_input, max_categories, confidence], |
|
|
outputs=output_text |
|
|
) |
|
|
|
|
|
clear_btn.click( |
|
|
fn=lambda: (None, 5, 0.3, "⬅️ Uploader une nouvelle image"), |
|
|
inputs=[], |
|
|
outputs=[image_input, max_categories, confidence, output_text] |
|
|
) |
|
|
|
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch(debug=True) |