{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":13707816,"sourceType":"datasetVersion","datasetId":8720220}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ==========================================================\n# 1. Librairies de Base et d'Analyse de Données\n# ==========================================================\nimport pandas as pd     # Manipulation de DataFrames (CSV)\nimport numpy as np      # Calculs numériques et manipulation d'arrays\nimport os               # Opérations sur le système de fichiers\nfrom pathlib import Path # Gestion des chemins de fichiers (Kaggle-friendly)\n\n# ==========================================================\n# 2. Traitement d'Images et Affichage\n# ==========================================================\nimport cv2              # OpenCV : Lecture d'images, conversion d'espace couleur (BGR/RGB/HSV)\nimport matplotlib.pyplot as plt # Affichage des images et des graphiques\n\n# ==========================================================\n# 3. Traitement Avancé des Images (Augmentation et Tenseurs)\n# ==========================================================\n# Importez uniquement si vous utilisez PyTorch pour l'entraînement\nimport torch            # La librairie de calcul et de réseaux de neurones\nfrom torch.utils.data import Dataset # Classe de base pour créer votre Dataset\nfrom torch.utils.data import DataLoader # Pour charger les données par lots\n\n# Librairie essentielle pour les augmentations rapides et efficaces\nimport albumentations as A \nfrom albumentations.pytorch import ToTensorV2 # Pour convertir l'image NumPy en Tenseur PyTorch\n\n# ==========================================================\n# 4. Autres Utilitaires (Optionnel)\n# ==========================================================\n# Vous pourriez en avoir besoin plus tard pour l'entraînement ou la gestion des modèles\n# import timm # Pour accéder facilement aux modèles pré-entraînés (EfficientNet, ConvNeXt...)\n# from sklearn.model_selection import KFold # Pour la validation croisée\n# from sklearn.preprocessing import StandardScaler # Pour la normalisation des features tabulaires","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:06:42.503483Z","iopub.execute_input":"2025-11-13T19:06:42.503863Z","iopub.status.idle":"2025-11-13T19:06:49.784623Z","shell.execute_reply.started":"2025-11-13T19:06:42.503833Z","shell.execute_reply":"2025-11-13T19:06:49.783589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cellule 2 : Définition des Chemins et Préparation du DataFrame\n\n# Définition des chemins de base\nKAGGLE_ROOT = Path(\"/kaggle/input/csiro-biomass\")\nTRAIN_CSV_PATH = KAGGLE_ROOT / \"train.csv\"\nTRAIN_IMAGES_DIR = KAGGLE_ROOT / \"train\" # Chemin absolu des images\n\n# 1. Chargement du fichier de métadonnées\ndf_train = pd.read_csv(TRAIN_CSV_PATH)\n\n# 2. Préparation : Créer un DataFrame par Image Unique (pour lier image et 5 cibles)\nmetadata_cols = ['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\n\n# Création de l'ID d'image (si la colonne 'sample_id' est utilisée)\nif '__' in df_train['sample_id'].iloc[0]:\n    df_train['image_id'] = df_train['sample_id'].str.split('__').str[0]\nelse:\n    df_train['image_id'] = df_train['sample_id'] \n\n# Pivotement pour obtenir une ligne par image\ndf_unique_images = df_train.pivot_table(\n    index=['image_id'] + metadata_cols,\n    columns='target_name',\n    values='target',\n    aggfunc='first'\n).reset_index()\n\n# Assurer que les cibles manquantes sont remplies par 0.0\ntargets = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\ndf_unique_images[targets] = df_unique_images[targets].fillna(0.0)\n\nprint(f\"✅ DataFrame 'df_unique_images' créé avec {len(df_unique_images)} images uniques.\")\nprint(df_unique_images[['image_id', 'image_path', 'Dry_Total_g']].head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:06:57.659258Z","iopub.execute_input":"2025-11-13T19:06:57.660183Z","iopub.status.idle":"2025-11-13T19:06:57.740127Z","shell.execute_reply.started":"2025-11-13T19:06:57.660118Z","shell.execute_reply":"2025-11-13T19:06:57.739206Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Shadow detection & shadow replacement","metadata":{}},{"cell_type":"code","source":"# Cellule 3 : Fonctions de Traitement d'Images et d'Augmentation\n\ndef create_shadow_mask(image_rgb, lightness_threshold=70):\n    \"\"\"\n    Crée un masque d'ombre binaire (1 canal) à partir de l'image RGB.\n    \"\"\"\n    image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV)\n    V = image_hsv[:, :, 2]\n    shadow_mask = (V < lightness_threshold).astype(np.float32)\n    return np.expand_dims(shadow_mask, axis=-1)\n\ndef get_train_transforms(img_size):\n    \"\"\"\n    Définit le pipeline de 'retouches' aléatoires (augmentation de données).\n    \"\"\"\n    # NOTE: Les moyennes et écarts-types incluent un 4e canal pour le masque d'ombre.\n    return A.Compose([\n        A.Resize(img_size, img_size), \n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.ShiftScaleRotate(\n            shift_limit=0.0625, scale_limit=0.1, rotate_limit=10, \n            p=0.75, border_mode=cv2.BORDER_CONSTANT\n        ),\n        A.ColorJitter(\n            brightness=0.2, contrast=0.2, saturation=0.2, hue=0.01, \n            p=0.75\n        ),\n        A.Normalize(\n            mean=[0.485, 0.456, 0.406, 0.5], \n            std=[0.229, 0.224, 0.225, 0.5], \n            max_pixel_value=255.0, \n        ),\n        ToTensorV2(),\n    ])\n\n# Définir les transformations pour la validation/test (seulement redimensionnement/normalisation)\ndef get_valid_transforms(img_size):\n    return A.Compose([\n        A.Resize(img_size, img_size),\n        A.Normalize(\n            mean=[0.485, 0.456, 0.406, 0.5], \n            std=[0.229, 0.224, 0.225, 0.5], \n            max_pixel_value=255.0, \n        ),\n        ToTensorV2(),\n    ])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:07:28.993432Z","iopub.execute_input":"2025-11-13T19:07:28.993767Z","iopub.status.idle":"2025-11-13T19:07:29.002286Z","shell.execute_reply.started":"2025-11-13T19:07:28.993741Z","shell.execute_reply":"2025-11-13T19:07:29.001362Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\nimport torch\nfrom torch.utils.data import Dataset\nimport numpy as np\nimport cv2\n\n# Gardons la fonction de détection d'ombre pour l'exemple\ndef create_shadow_mask(image_rgb, lightness_threshold=70):\n    image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV)\n    V = image_hsv[:, :, 2]\n    shadow_mask = (V < lightness_threshold).astype(np.float32)\n    return np.expand_dims(shadow_mask, axis=-1)\n\nclass BiomassImageOnlyDataset(Dataset):\n    \"\"\"\n    Dataset pour la compétition CSIRO Biomass, utilisant UNIQUEMENT\n    l'image (RGB + Masque d'Ombre) comme entrée.\n    \"\"\"\n    # NOTE : 'df' et 'img_dir' sont toujours nécessaires pour l'indexation et le chargement.\n    def __init__(self, df, img_dir, transforms=None): \n        # df doit être le DataFrame pivoté (une ligne = une image)\n        self.df = df.reset_index(drop=True) \n        self.img_dir = img_dir \n        self.transforms = transforms\n        # Définition des cibles une seule fois\n        self.targets_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        # 1. Chargement de la ligne de données (pour le chemin et les cibles)\n        row = self.df.iloc[idx]\n        \n        # --- A. Traitement des Images (Image 4 Canaux) ---\n        \n        # Chemin d'accès à l'image\n        img_path = self.img_dir / Path(row['image_path'])\n        image = cv2.imread(str(img_path))\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) # (H, W, 3)\n        \n        # Création et ajout du masque d'ombre (4e canal)\n        shadow_mask = create_shadow_mask(image, lightness_threshold=70) # (H, W, 1)\n        image_4chan = np.concatenate([image, shadow_mask * 255], axis=-1).astype(np.uint8)\n        \n        # Application des transformations (redimensionnement, augmentation, ToTensorV2)\n        if self.transforms:\n            augmented = self.transforms(image=image_4chan)\n            image_final = augmented['image'] # Tenseur de shape (4, H, W)\n        else:\n            # Code pour normaliser et convertir si pas de transforms...\n            # Pour l'exemple, nous allons laisser la transformation gérée par `transforms`\n            # car la normalisation est cruciale.\n            raise ValueError(\"Transforms (incluant ToTensorV2) ne doit pas être None.\")\n\n        # --- B. Extraction des Cibles (Biomasse) ---\n        \n        # Extraction des 5 valeurs de biomasse\n        targets = row[self.targets_cols].values.astype(np.float32)\n        targets_tensor = torch.tensor(targets, dtype=torch.float32)\n\n        # Retourne uniquement l'image (entrée) et les cibles (sortie)\n        return image_final, targets_tensor\n\n# N'oubliez pas d'exécuter à nouveau les cellules précédentes pour définir \n# KAGGLE_ROOT et df_unique_images avant de tester cette nouvelle classe.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:07:33.575825Z","iopub.execute_input":"2025-11-13T19:07:33.576234Z","iopub.status.idle":"2025-11-13T19:07:33.586408Z","shell.execute_reply.started":"2025-11-13T19:07:33.576206Z","shell.execute_reply":"2025-11-13T19:07:33.585533Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_shadow_mask(image_rgb, lightness_threshold=70):\n    \"\"\"\n    Crée un masque binaire des zones d'ombre en utilisant l'espace HSV.\n    \"\"\"\n    # Convertir l'image RGB à HSV\n    image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV)\n    V = image_hsv[:, :, 2] # Canal de Valeur/Luminosité\n    \n    # Masque : 1 pour l'ombre (pixel sombre), 0 sinon\n    shadow_mask = (V < lightness_threshold).astype(np.uint8)\n    \n    return shadow_mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:07:47.089837Z","iopub.execute_input":"2025-11-13T19:07:47.090141Z","iopub.status.idle":"2025-11-13T19:07:47.095615Z","shell.execute_reply.started":"2025-11-13T19:07:47.090120Z","shell.execute_reply":"2025-11-13T19:07:47.094376Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def correct_shadows_inpaint(image_rgb):\n    \"\"\"\n    Détecte les ombres et les corrige en utilisant l'algorithme Inpainting \n    pour remplacer les pixels d'ombre par ceux des zones voisines.\n    \n    Args:\n        image_rgb (np.array): Image RGB (H, W, 3).\n        \n    Returns:\n        np.array: Image RGB corrigée sans les zones d'ombre.\n    \"\"\"\n    # --- A. Détection du Masque ---\n    # Nous utilisons un seuil plus strict car l'inpainting est coûteux.\n    shadow_mask = create_shadow_mask(image_rgb, lightness_threshold=60) \n    \n    # L'algorithme d'Inpainting fonctionne mieux sur un masque binaire large.\n    # On applique un filtre pour connecter les petits trous (facultatif mais recommandé)\n    kernel = np.ones((3,3), np.uint8)\n    shadow_mask = cv2.dilate(shadow_mask, kernel, iterations=1)\n    \n    # --- B. Remplacement par Inpainting ---\n    # cv2.INPAINT_TELEA est l'algorithme qui mélange les informations des voisins.\n    # L'image est considérée comme l'image avec les \"trous\" (ombres).\n    # Le masque indique les \"trous\" à combler.\n    # Le rayon (3) définit la zone des voisins à utiliser.\n    image_corrected = cv2.inpaint(image_rgb, shadow_mask, inpaintRadius=3, flags=cv2.INPAINT_TELEA)\n    \n    return image_corrected, shadow_mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:07:51.140035Z","iopub.execute_input":"2025-11-13T19:07:51.140399Z","iopub.status.idle":"2025-11-13T19:07:51.146371Z","shell.execute_reply.started":"2025-11-13T19:07:51.140371Z","shell.execute_reply":"2025-11-13T19:07:51.145346Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- Ré-exécutez la Cellule 2 pour avoir df_unique_images en mémoire ! ---\n\ndef plot_shadow_correction(df, index):\n    \"\"\"Charge, détecte, corrige les ombres et affiche les résultats.\"\"\"\n    row = df.iloc[index]\n    img_relative_path = row['image_path']\n    full_path = KAGGLE_ROOT / img_relative_path\n    \n    # Charger l'image\n    image = cv2.imread(str(full_path))\n    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    \n    # 1. Détection du Masque d'Ombre\n    shadow_mask_original = create_shadow_mask(image_rgb, lightness_threshold=60)\n    \n    # 2. Correction par Inpainting\n    image_corrected, shadow_mask_used = correct_shadows_inpaint(image_rgb)\n    \n    # Affichage des trois étapes\n    fig, axes = plt.subplots(1, 3, figsize=(18, 6))\n    \n    # Image 1 : Image Originale\n    axes[0].imshow(image_rgb)\n    axes[0].set_title(\"1. Image Originale (avec Ombre)\")\n    axes[0].axis('off')\n\n    # Image 2 : Masque d'Ombre (Isolation)\n    axes[1].imshow(shadow_mask_original, cmap='gray')\n    axes[1].set_title(\"2. Masque d'Ombre Isolé\")\n    axes[1].axis('off')\n    \n    # Image 3 : Image Corrigée (Remplacement par les Voisins)\n    axes[2].imshow(image_corrected)\n    axes[2].set_title(\"3. Image Corrigée (Remplacement par Inpainting)\")\n    axes[2].axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\n# Testez sur une image (choisissez un index qui a clairement des ombres)\nplot_shadow_correction(df_unique_images, index=20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:07:59.210992Z","iopub.execute_input":"2025-11-13T19:07:59.211694Z","iopub.status.idle":"2025-11-13T19:08:01.733085Z","shell.execute_reply.started":"2025-11-13T19:07:59.211662Z","shell.execute_reply":"2025-11-13T19:08:01.732073Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Soil detection","metadata":{}},{"cell_type":"code","source":"def create_soil_mask(image_rgb):\n    \"\"\"\n    Détection du 5e canal (Sol), avec un filtrage morphologique plus agressif \n    pour tenter d'éliminer l'herbe morte fine.\n    \"\"\"\n    image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV)\n    H, S, V = image_hsv[:, :, 0], image_hsv[:, :, 1], image_hsv[:, :, 2]\n    \n    # Critères de couleur : Faible saturation, teinte non verte, luminosité modérée\n    low_saturation = S < 80\n    # On ajuste la teinte pour ne pas trop cibler les jaunes (H autour de 20-30)\n    non_green_hue = (H < 25) | (H > 85) \n    moderate_value = (V > 50) & (V < 200)\n    \n    soil_candidate_mask = (low_saturation & non_green_hue & moderate_value).astype(np.uint8)\n    \n    # NOUVEAU : Utiliser un kernel plus large (ex: 7x7) pour enlever les brins d'herbe fines\n    # L'opération d'OUVERTURE (OPEN) élimine les petits objets et les projections fines.\n    kernel = np.ones((7, 7), np.uint8) # Augmenté de 5x5 à 7x7\n    \n    # Appliquer l'ouverture\n    soil_mask_final = cv2.morphologyEx(\n        soil_candidate_mask, \n        cv2.MORPH_OPEN, \n        kernel\n    ).astype(np.float32)\n    \n    return soil_mask_final","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:37:13.629265Z","iopub.execute_input":"2025-11-13T19:37:13.631273Z","iopub.status.idle":"2025-11-13T19:37:13.651162Z","shell.execute_reply.started":"2025-11-13T19:37:13.631198Z","shell.execute_reply":"2025-11-13T19:37:13.649696Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def plot_shadow_and_soil_masks(df, index):\n    \"\"\"Charge, détecte les ombres et le sol, et affiche les masques.\"\"\"\n    row = df.iloc[index]\n    img_relative_path = row['image_path']\n    full_path = KAGGLE_ROOT / img_relative_path\n    \n    image = cv2.imread(str(full_path))\n    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    \n    # 1. Masque d'Ombre\n    shadow_mask = create_shadow_mask(image_rgb, lightness_threshold=60)\n    \n    # 2. Masque de Sol\n    soil_mask = create_soil_mask(image_rgb)\n    \n    # Affichage des trois étapes\n    fig, axes = plt.subplots(1, 3, figsize=(18, 6))\n    \n    # Image 1 : Image Originale\n    axes[0].imshow(image_rgb)\n    axes[0].set_title(\"1. Image Originale\")\n    axes[0].axis('off')\n\n    # Image 2 : Masque d'Ombre\n    axes[1].imshow(shadow_mask, cmap='gray')\n    axes[1].set_title(\"2. Masque d'Ombre (pixels à compenser)\")\n    axes[1].axis('off')\n    \n    # Image 3 : Masque de Sol\n    axes[2].imshow(soil_mask, cmap='hot') # Utiliser 'hot' pour bien voir les zones de sol\n    axes[2].set_title(\"3. Masque de Sol (pixels à ignorer)\")\n    axes[2].axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\n# Testez sur un index où le sol est visible (exemple 42)\nplot_shadow_and_soil_masks(df_unique_images, index=42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:37:17.406561Z","iopub.execute_input":"2025-11-13T19:37:17.406922Z","iopub.status.idle":"2025-11-13T19:37:18.443532Z","shell.execute_reply.started":"2025-11-13T19:37:17.406895Z","shell.execute_reply":"2025-11-13T19:37:18.442503Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Clover detection","metadata":{}},{"cell_type":"code","source":"def create_clover_mask(image_rgb):\n    \"\"\"\n    Crée un masque binaire des zones de trèfle (Clover) en utilisant une \n    combinaison de filtres de couleur et de morphologie (approche très simplifiée).\n    Les seuils devront être ajustés très finement.\n    \"\"\"\n    # 1. Convertir l'image RGB à HSV\n    image_hsv = cv2.cvtColor(image_rgb, cv2.COLOR_RGB2HSV)\n    H = image_hsv[:, :, 0] # Teinte (Hue)\n    S = image_hsv[:, :, 1] # Saturation\n    V = image_hsv[:, :, 2] # Valeur (Luminosité)\n    \n    # 2. Définir les critères pour le vert du trèfle (à ajuster !)\n    # Le trèfle est souvent un vert légèrement différent de l'herbe.\n    # Plage de vert (par exemple, H de 45 à 85 sur 180)\n    clover_hue_range = (H > 40) & (H < 90) \n    \n    # Saturation et Luminosité élevées (pour éviter le sol et l'ombre)\n    high_saturation = S > 80\n    high_value = V > 70\n    \n    # Masque basé sur la couleur\n    color_mask = (clover_hue_range & high_saturation & high_value).astype(np.uint8)\n    \n    # 3. Utiliser la morphologie pour cibler les formes rondes (Trèfle)\n    # L'herbe est fine, le trèfle est large.\n    # Nous utilisons un kernel rond pour isoler les blobs ronds.\n    kernel_round = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (7, 7))\n    \n    # Opération de fermeture (Close) : ferme les petits trous et connecte les parties proches\n    clover_mask = cv2.morphologyEx(color_mask, cv2.MORPH_CLOSE, kernel_round, iterations=1)\n    \n    # 4. Enlever les petites zones (qui sont souvent des brins d'herbe)\n    # Ceci est complexe sans contours, mais un masque est une bonne start.\n    \n    return clover_mask.astype(np.float32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:08:41.979558Z","iopub.execute_input":"2025-11-13T19:08:41.979898Z","iopub.status.idle":"2025-11-13T19:08:41.986941Z","shell.execute_reply.started":"2025-11-13T19:08:41.979873Z","shell.execute_reply":"2025-11-13T19:08:41.985739Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cellule de démonstration visuelle pour le Masque de Trèfle\n\n# Assurez-vous d'avoir exécuté les Cellules 2 et 3 pour df_unique_images et les fonctions de masquage !\n\ndef plot_clover_detection_and_zoom(df, index, zoom_slice_y=(200, 400), zoom_slice_x=(200, 400)):\n    \"\"\"\n    Charge, détecte le trèfle et affiche l'image originale, le masque de trèfle,\n    et un zoom pour évaluer la qualité de la détection.\n    \"\"\"\n    row = df.iloc[index]\n    img_relative_path = row['image_path']\n    full_path = KAGGLE_ROOT / img_relative_path\n    \n    # Chargement de l'image\n    image = cv2.imread(str(full_path))\n    if image is None:\n        print(f\"Erreur: Impossible de charger l'image à {full_path}\")\n        return\n        \n    image_rgb = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    \n    # Détection du Masque de Trèfle (Canal 6)\n    clover_mask = create_clover_mask(image_rgb)\n    \n    # Application du masque sur l'image pour un meilleur aperçu\n    # Les zones de trèfle sont surlignées en bleu\n    clover_overlay = image_rgb.copy()\n    clover_overlay[clover_mask == 1] = [0, 0, 255] # Mettre les pixels de trèfle en bleu\n    \n    \n    # Préparation des zooms\n    \n    # Pour le Zoom Original (montrer où le trèfle se trouve)\n    zoom_original = image_rgb[zoom_slice_y[0]:zoom_slice_y[1], zoom_slice_x[0]:zoom_slice_x[1], :]\n    \n    # Pour le Zoom Masque (montrer la précision du masque)\n    zoom_mask = clover_mask[zoom_slice_y[0]:zoom_slice_y[1], zoom_slice_x[0]:zoom_slice_x[1]]\n\n\n    # Affichage des résultats\n    fig, axes = plt.subplots(1, 4, figsize=(22, 6))\n    \n    # Image 1 : Image Originale\n    axes[0].imshow(image_rgb)\n    axes[0].set_title(f\"1. Image Originale (Index {index})\")\n    axes[0].axis('off')\n\n    # Image 2 : Masque de Trèfle Binaire\n    axes[1].imshow(clover_mask, cmap='viridis')\n    axes[1].set_title(\"2. Masque de Trèfle Isolé (Canal 6)\")\n    axes[1].axis('off')\n    \n    # Image 3 : Zoom sur l'Image Originale\n    axes[2].imshow(zoom_original)\n    axes[2].set_title(\"3. Zone Zoomée (Originale)\")\n    axes[2].axis('off')\n\n    # Image 4 : Zoom sur le Masque de Trèfle\n    axes[3].imshow(zoom_mask, cmap='viridis')\n    axes[3].set_title(\"4. Zone Zoomée (Masque)\")\n    axes[3].axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\n# --- UTILISATION : ---\n# 1. Choisissez un index où le trèfle est susceptible d'être présent.\n#    (Vous pouvez regarder la colonne 'Dry_Clover_g' dans df_unique_images pour choisir un index > 0)\n# 2. Ajustez les tranches de zoom si l'image est très grande ou si le trèfle n'est pas dans le coin par défaut.\n\n# Exemple : Testez avec l'index 50 (ou un autre index de votre choix)\nplot_clover_detection_and_zoom(df_unique_images, index=50, zoom_slice_y=(100, 300), zoom_slice_x=(300, 500))\n\n#","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-13T19:10:58.411710Z","iopub.execute_input":"2025-11-13T19:10:58.412053Z","iopub.status.idle":"2025-11-13T19:10:59.678854Z","shell.execute_reply.started":"2025-11-13T19:10:58.412032Z","shell.execute_reply":"2025-11-13T19:10:59.677924Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Manual clover detection is not a success (due to the visual similarity between clover and grass) -> We need to rely on the Deep Learning Model (CNN) to learn that complex distinction.","metadata":{}}]}