# Prétraitement (`app/preprocessing.py`) ## `detect_screen_region(img)` Cherche automatiquement la zone rectangulaire de l'écran LCD dans la photo, avant tout traitement. **Méthode** : niveaux de gris → flou gaussien → détection de contours (Canny) → fermeture morphologique pour relier les segments → on garde le plus grand rectangle candidat dont le ratio largeur/hauteur est plausible pour un écran (entre 1.0 et 8.0) et dont l'aire dépasse 2% de l'image. **Limite connue et importante** : cette heuristique cherche "le plus grand rectangle contrasté", pas spécifiquement un écran LCD. Sur certaines photos, elle capture par erreur le bandeau de marque du carburant (ex. "Shell FuelSave Super") au lieu de l'écran numérique, parce que ce bandeau forme un rectangle net et contrasté lui aussi. Repéré concrètement pendant cette session sur `20260622_112333_023.jpg`/`024.jpg` : la détection automatique renvoyait le bandeau jaune, pas l'écran noir avec les chiffres. Si le pipeline ne trouve aucune donnée exploitable sur une photo qui en contient pourtant, **c'est le premier endroit à vérifier** — visualiser le crop retourné par cette fonction avant d'aller chercher plus loin. Si aucun rectangle plausible n'est trouvé, la fonction retourne l'image complète inchangée (charge alors au découpage en lignes de s'en sortir). ## `split_lcd_lines(screen_crop, n_lines=3)` Découpe le crop d'écran en `n_lines` bandes horizontales, une par valeur affichée. **Méthode principale** : projection horizontale des pixels de texte (binarisation Otsu + CLAHE, puis comptage de pixels "actifs" par ligne) pour repérer automatiquement où sont les bandes de texte et où sont les espaces vides entre elles. **Repli** : si la projection ne trouve pas exactement `n_lines` bandes (image bruitée, reflet, contraste insuffisant), découpage en bandes de hauteur égale. **Ce repli est peu précis** — voir `docs/LIMITATIONS.md`, c'est la cause principale du plafond de précision actuel du modèle (86% du jeu d'entraînement est passé par ce repli plutôt que par la détection précise). **Pourquoi `n_lines=3` par défaut et pas 2** : la majorité des écrans du jeu d'annotation affichent trois valeurs (prix total, volume, prix du litre). Un défaut à 2 aurait systématiquement tronqué la troisième ligne — piège découvert en inspectant `src/fine_tuned_inference.py` (l'ancienne version, dans le prototype racine, avait ce défaut à 2). ## `FIELD_NAMES = ["prix", "volume", "prix_litre"]` Associe chaque ligne découpée (dans l'ordre où elle apparaît, de haut en bas) à un nom de champ métier. Cet ordre correspond à la convention observée sur les pompes photographiées : **"Prix" en haut = montant total payé** (pas le prix unitaire, malgré le nom), **"Volume" au milieu = litres**, **"Prix Unitaire"/"Prix du litre" en bas = prix par litre**. Voir `docs/POSTPROCESS_RULES.md` pour pourquoi cette distinction a causé un bug réel (confusion montant/prix unitaire).