# app.py - Labasni Cloth Detection # Compatible Gradio 5.9.1 avec API REST fonctionnelle import os os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2' import gradio as gr import tensorflow as tf from tensorflow.keras.models import Model from tensorflow.keras.layers import GlobalAveragePooling2D, Dense from tensorflow.keras.applications import MobileNetV2 from ultralytics import YOLO from PIL import Image import numpy as np from sklearn.cluster import KMeans import json print("=" * 60) print("🚀 DÉMARRAGE LABASNI CLOTH DETECTION") print("=" * 60) # ============================================ # CHARGEMENT DES MODÈLES # ============================================ print("\n🔄 Chargement du modĂšle YOLO...") try: yolo_model = YOLO("best.pt") print("✅ YOLO chargĂ© avec succĂšs") except Exception as e: print(f"❌ Erreur YOLO: {e}") raise print("\n🔄 Chargement du modĂšle Style/Season...") try: base_model = MobileNetV2( input_shape=(224, 224, 3), include_top=False, weights=None ) x = base_model.output x = GlobalAveragePooling2D()(x) style_output = Dense(4, activation='softmax', name='style_output')(x) season_output = Dense(4, activation='softmax', name='season_output')(x) style_model = Model( inputs=base_model.input, outputs=[style_output, season_output] ) style_model.load_weights( "style_season_model.h5", by_name=True, skip_mismatch=True ) print("✅ Style/Season model chargĂ© avec succĂšs") except Exception as e: print(f"❌ Erreur Style/Season: {e}") raise print("\n" + "=" * 60) print("✅ TOUS LES MODÈLES CHARGÉS") print("=" * 60 + "\n") # ============================================ # FONCTIONS UTILITAIRES # ============================================ def get_dominant_color(crop): """Extrait la couleur dominante via K-Means""" try: img_array = np.array(crop).reshape(-1, 3) img_array = img_array[np.any(img_array != [0, 0, 0], axis=1)] if len(img_array) == 0: return "#808080" kmeans = KMeans(n_clusters=3, random_state=0, n_init=10) kmeans.fit(img_array) dominant = kmeans.cluster_centers_[0] return '#{:02x}{:02x}{:02x}'.format( int(dominant[0]), int(dominant[1]), int(dominant[2]) ).upper() except: return "#808080" # ============================================ # FONCTION PRINCIPALE DE DÉTECTION # ============================================ def detect_cloth(image): """DĂ©tecte type, couleur, style et saison d'un vĂȘtement""" print("\n" + "="*60) print("🔍 NOUVELLE DÉTECTION") print("="*60) try: # Validation if image is None: error_result = {"error": "Aucune image fournie"} print(f"❌ {error_result['error']}") return error_result # Conversion PIL if not isinstance(image, Image.Image): image = Image.fromarray(image) img = image.convert('RGB') w, h = img.size print(f"📾 Image: {w}x{h} pixels") # YOLO Detection print("đŸ€– DĂ©tection YOLO...") results = yolo_model(img, verbose=False)[0] boxes = results.boxes if not boxes or len(boxes) == 0: error_result = {"error": "Aucun vĂȘtement dĂ©tectĂ©"} print(f"❌ {error_result['error']}") return error_result best_idx = boxes.conf.argmax() confidence = float(boxes.conf[best_idx]) print(f"✅ Confiance: {confidence:.2%}") # Extraction x1, y1, x2, y2 = map(int, boxes.xyxy[best_idx]) x1, y1 = max(0, x1), max(0, y1) x2, y2 = min(w, x2), min(h, y2) if x2 <= x1 or y2 <= y1: error_result = {"error": "BoĂźte de dĂ©tection invalide"} print(f"❌ {error_result['error']}") return error_result cropped = img.crop((x1, y1, x2, y2)) # Couleur print("🎹 Extraction couleur...") hex_color = get_dominant_color(cropped) print(f"✅ Couleur: {hex_color}") # Style & Saison print("👔 Classification...") resized = cropped.resize((224, 224)) arr = np.array(resized) / 255.0 arr = np.expand_dims(arr, axis=0) style_pred, season_pred = style_model.predict(arr, verbose=0) styles = ["casual", "formal", "sport"] seasons = ["summer", "winter", "fall", "spring"] style = styles[np.argmax(style_pred)] season = seasons[np.argmax(season_pred)] style_conf = float(np.max(style_pred)) season_conf = float(np.max(season_pred)) print(f"✅ Style: {style} ({style_conf:.2%})") print(f"✅ Saison: {season} ({season_conf:.2%})") # Type type_vetement = results.names[int(boxes.cls[best_idx])] print(f"✅ Type: {type_vetement}") # RĂ©sultat result = { "success": True, "detection": { "type": type_vetement, "color": hex_color, "style": style, "season": season }, "confidence": { "detection": f"{confidence:.2%}", "style": f"{style_conf:.2%}", "season": f"{season_conf:.2%}" } } print("="*60) print("✅ DÉTECTION RÉUSSIE") print("="*60) print(json.dumps(result, indent=2)) return result except Exception as e: error_result = {"error": f"Erreur: {str(e)}"} print(f"❌ {error_result['error']}") import traceback traceback.print_exc() return error_result # ============================================ # WRAPPER POUR FORMATER LE JSON EN STRING (UI uniquement) # ============================================ def detect_cloth_wrapper(image): """Wrapper qui convertit le rĂ©sultat en JSON string Évite le bug gr.JSON dans Gradio 5.x (TypeError avec additionalProperties) L'API REST retournera une string JSON que les clients devront parser """ result = detect_cloth(image) # Convertir le dictionnaire en JSON string formatĂ© return json.dumps(result, indent=2, ensure_ascii=False) # ============================================ # INTERFACE GRADIO 5.x - AVEC API # ============================================ # CSS personnalisĂ© custom_css = """ .gradio-container { font-family: 'IBM Plex Sans', sans-serif; } .output-json { font-family: 'Courier New', monospace; font-size: 14px; } """ # CrĂ©ation de l'interface avec Gradio Blocks (meilleur contrĂŽle UI et API) with gr.Blocks( title="đŸ§„ Labasni - DĂ©tection de VĂȘtements", theme=gr.themes.Soft(), css=custom_css ) as demo: gr.Markdown(""" # 👔 Labasni - DĂ©tection de VĂȘtements ## 👔 Labasni - DĂ©tection Automatique de VĂȘtements Cette API utilise l'intelligence artificielle pour analyser vos photos de vĂȘtements et dĂ©tecter : - 👕 **Type** : Haut, pantalon, robe, chaussures, etc. - 🎹 **Couleur dominante** : Code hexadĂ©cimal de la couleur principale - 👔 **Style** : Casual, Formel, Sport - đŸŒ€ïž **Saison** : ÉtĂ©, Hiver, Automne, Printemps ## 🚀 Comment utiliser 1. **Via l'interface** : Uploadez une image ci-dessous 2. **Via l'API** : Utilisez l'endpoint `/api/predict` ## 📡 Exemple d'appel API ```bash curl -X POST "https://syleto-labasni-detection.hf.space/api/predict" \\ -H "Content-Type: application/json" \\ -d '{"data": [""]}' ``` ## ⚙ ModĂšles utilisĂ©s - **YOLO** (best.pt) : DĂ©tection des vĂȘtements - **MobileNetV2** : Classification style/saison - **K-Means** : Extraction couleur dominante """) with gr.Row(): with gr.Column(): image_input = gr.Image( label="📾 Photo du vĂȘtement", type="pil", height=400 ) with gr.Row(): clear_btn = gr.Button("Clear", variant="secondary") submit_btn = gr.Button("Submit", variant="primary") with gr.Column(): # ✅ SOLUTION : Utiliser gr.Textbox au lieu de gr.JSON pour Ă©viter le bug Gradio 5.x # Le bug se produit dans la gĂ©nĂ©ration du schĂ©ma API pour gr.JSON result_output = gr.Textbox( label="📊 RĂ©sultat de la dĂ©tection", elem_classes=["output-json"], lines=15, max_lines=20, interactive=False ) # ✅ IMPORTANT : Configurer l'API explicitement pour Gradio 5.x # SOLUTION : Utiliser detect_cloth_wrapper qui retourne une string JSON # Cela Ă©vite le bug gr.JSON (TypeError avec additionalProperties) # L'API REST retournera une string JSON que les clients devront parser # C'est acceptable car cela rĂ©sout le bug "No API found" submit_btn.click( fn=detect_cloth_wrapper, # ✅ Wrapper qui retourne JSON string formatĂ© inputs=[image_input], outputs=[result_output], api_name="predict" # CrĂ©e l'endpoint /api/predict ) clear_btn.click( fn=lambda: (None, ""), # ✅ Retourner "" au lieu de None pour Textbox inputs=[], outputs=[image_input, result_output] ) gr.Markdown(""" --- **DĂ©veloppĂ© par** : Aziz Ben Ammar & Équipe Labasni **Version** : 1.0.0 **Contact** : [Hugging Face Space](https://huggingface.co/spaces/Syleto/labasni-detection) """) # ============================================ # LANCEMENT # ============================================ if __name__ == "__main__": print("\n" + "="*60) print("🌐 LANCEMENT DE L'INTERFACE GRADIO") print("="*60 + "\n") # ✅ IMPORTANT : Dans Gradio 5.x, l'API est automatiquement exposĂ©e avec api_name # L'interface API sĂ©parĂ©e garantit que l'API REST est accessible # Pas besoin de show_api (paramĂštre n'existe pas dans Gradio 5.x) demo.launch( server_name="0.0.0.0", server_port=7860, show_error=True, share=False )