Blog de développement

Extraction Automatique de Signature

Dernière Modification le :
2024-02-19

Notre 'Listener' écoute en permanence les boites mails, détécte et analyse les bons de commande. Pour de petites commandes et de petites stuctures, des clients envoient un simple 'Bon pour accord' (aka 'Bon pour commande'). Le principe de base est simple: on imprime le devis, on le signe, on le scanne et on l'envoie.

Cette pratique est complétée par une version plus moderne: le client ouvre le PDF (l'offre) ajoute une signature (par exemple un .gif ou un .jpg), le sauvegarde et vous l'envoie.

Le listener va analyser une certain nombre de caractéristiques afin de filter puis décider s'il s'agit bien d'un mail contenant un 'Bon pour accord'. Les caractéristiques suivantes s'appliquent:

  • Hormis quelques petits parasites, le mail ne véhicule qu'un PDF unique.
  • Le PDF est d'une seule page (scan) ou du même nombre de pages que nos offres (typiquement 4 ou 5).
  • La première page contient notre QRcode, qui pointe vers l'archive de l'offre sur notre serveur, donc son numéro.
  • Dans le cas d'un scan, le document a pu être retourné (la tête en bas): on corrigera.
  • Le document est signé... On explorera l'utilisation d'un détecteur de signature.

Détection d'un 'Bon pour accord'

Le préfiltrage d'un 'bon de commande' est facilité par l'utilisation du mot commande et autres mots magiques dans le corps du mail et dans les documents joints. Le bon pour accord est plus simple, plus rapide et n'a pas cette richesse sémantique.

Le filtrage a lieu en 2 étapes: un préfiltre sensible mais moins spécifique que celui du bon de commande, puis, un second filtre, très spécifique qui attribue le mail à sa nature avec une efficacité de 100 %.

Commande sans Bon de Commande

Le Bon pour accord est une commande. Le préfiltre, qui audite le contenu texuel du mail d'accompagnement peut fort bien identifier Bon pour accord comme étant une commande. Ainsi un mail identifié comme commande devra-t-il être "differencié" entre Commande-Avec-Bon-de-Commande et Commande-avec-Bon-pour accord. Dans un premier temps le chemin est commun, puis on fait le distinguo, puis les chemins se rejoignent pour l'entrée dans le Carneet de Commandes

Le Bon pour accord a une seule page, mais...

Le PDF est d'une seule page (scan) ou du même nombre de pages que nos offres (typiquement 4 ou 5). Toutefois, dans la manipulation du scanner, une page vierge parasite peut venir polluer un filtre sur le nombre de page... Dans la cas où le nombre de pages est 2, on regarde si la deuxième page est vierge. Si elle ne l'est pas, cela invalide le bon pour accord.

On utilise PdfToPNG pour créer un PNG en noir et blanc, c'est à dire Binarisé, avec le flag -mono. Pourquoi Binariser ? Améliorer le Contraste, Simplifier l'Analyse, Réduire le Bruit...

Sinon, on peut binariser avec ImageMagick:

convert original.png -threshold 50% binarise.png

On envisage 3 stratégies:

  • - avec ImageMagick, seul, sans Python (j'utilise cette méthode...)
  • - avec Python, mais sans OpenCV (voir ZIP de ressources)
  • - avec Python et OpenCV (voir ZIP de ressources)

convert -size 2480x3508 canvas:white fond_blanc_A4.png
compare -metric AE votre_image.png fond_blanc_A4.png difference.png 2>difference.txt
L'option -metric AE (Absolute Error) compte le nombre de pixels qui diffèrent. Si la sortie est 0, cela signifie que les images sont identiques. Le résultat numérique (l'erreur absolue) sera affiché dans le terminal (ou dirigée puis lue avec 2>....txt il faut choisir 2> et non pas 1> car le nombre de pixel est dans Sdterr,). Si ce nombre est proche de zéro, votre image est probablement vierge.

Sans extraction de signature

Nos offres ont un espace bien visible, dédié à la signature et validation de l'offre. On va tout simplement isoler une partie centrale de ce cartouche, en prévoyant un padding. Cette zone (ROI: Region Of Interest) est définie par la position X,Y de son coint haut et gauche et de sa largeur, hauteur.
Pour s'assurer que la zone est bien choisie, on crée un PNG avec la première page de l'offre où l'on remplit le cartouche de noir. C'est juste pour debuggage éventuel ... On crée aussi un PNG blanc de même taille, puis on extrait le sous cartouche et l'on compare et enregistre la comparaison (avec 2>) On lit la comapraion pour statuer si oui ou non le cartouche comprend une modification (signature). Il ne s'agit pas d'une extraction de signature et c'est rendu possible par le fait que nous sommes l'auteur de l'offre et de son cartouche dédié à la signature.

REM creation d'un cache blanc avec Largeur L = 220-60 et Hauteur H = 640 - 585, a ajuster ...
magick convert -size 180x55 canvas:white "E:\...\ROI_cache_blanc.png"
REM  creation d'un test avec ROI en noir avec ici X = 60, Y = 585 X+L = 220, Y+H = 640, a ajuster ...
magick convert "E:\...\x_sans_QRCode.png" -fill black -draw "rectangle 60,585 220,640" "E:\...\x_avec_ROI_Noire.png" 
REM creation de la ROI
magick convert "E:\...\x_sans_QRCode.png" -crop 160x55+60+585 +repage "E:\...\x_ROI.png" 
REM comparaison de la ROI avec le cache blanc
magick compare -metric AE "E:\...\x_ROI.png" "E:\...\ROI_cache_blanc.png" "E:\...\x_ROI_difference.png" 2>"E:\...\x_ROI_difference.txt"

Extraction de Signature

Cet article 'Comment extraire des signatures de documents papier', décrit les différentes étapes Lire →. Son archive et sa traduction en français sont dans le ZIP de ressources. Il introduit aussi la solution API (payante?) par Dropbox.

L'extraction de Signature s'appuie sur le script d'Ahmet Özlü : Lire et Télécharger→ (le site de l'auteur)

Extraction de Signature Vs Détection d'altérations

En réalité, dans le cas particulier du 'Bon pour Accord', sur une offre que NOUS avons faite, il est possible de faire plus simple, même si c'est moins élégant: détecter que votre document a été altéré par le client, ce qui, en pratique, indique de façon certaine, le 'Bon pour accord'.

A la réception du mail, on n'échappe pas à l'extraction des pièces jointes. Avant d'extaire...

Le préfiltre puis l'extraction des pièces jointes

L'expéditeur: on filtre sur le fait que le nom de domaine de l'expéditeur doit être dans la base de données des offres que nous avons faites.

Il n'est généralement pas possible de détecter le nombre de pages d'un fichier PDF attaché, directement à partir d'un fichier .eml (courriel), sans extraire la pièce jointe. Un fichier .eml est essentiellement un fichier texte brut qui suit un format spécifique pour les messages électroniques. Il comprend les en-têtes, le corps du message (non encodé) et les pièces jointes sont codées dans un format tel que Base64 voire compressées !

Pour déterminer le nombre de pages d'une pièce jointes au format PDF, il faut généralement

  • Extraire la pièce jointe du fichier .eml.
  • Décoder la pièce jointe à partir de son format encodé Base64 (ou similaire).
  • Déterminer l'extension (PDF), compter le nombre de pages.
  • Analyser le fichier décodé à l'aide d'un outil de traitement PDF ou d'une bibliothèque.

Ce processus nécessite d'analyser le fichier .eml, identifier la pièce jointe, de la décoder, puis ouvrir le fichier PDF décodé et compter les pages. Il n'existe pas de raccourci pour ce processus.

Des langages de programmation comme Python, ainsi que des bibliothèques pour traiter les courriels (comme email pour analyser les fichiers .eml) et les PDF (comme PyPDF2 ou PDFMiner pour lire le contenu des PDF), peuvent être utilisés pour automatiser ce processus. Cependant, il ne s'agit pas d'une tâche triviale !

En fait, il existe des utilitaires en ligne de commandes, clé en main, faciles et rapides à programmer, qui peuvent excuter ces tâches sans programmation.

Mes outils

Logiciel Action Site Manuel Téléchargement
munpack Extraction Site →
UUDWin Extraction Site →
PdfInfo Nb Pages Site →
PDFImage Liste Images Site →
ASPPDF Crée QRCode Site → ($)
ZBAR Lit QRCode Site → (*)
ImageMagick Manipulation
Images
Site →
PdfDetach Mouchard Site →
Ahmet Özlü Lit Signature Site →

(*) zbar: suite à des difficultés avec le version github: je pointe vers la version 1.0 sur SourceForge

Décompte des PDF en pièces jointes: CDO (Microsoft) a cette fonction, mais il bute sur les (rares) mails au standard Linux/Apple. Le décompte est alors inexact.

Comme la nature des pièces jointes est dans les headers (non encodés), il suffit de lire le mail comme un texte et par une expression régulière sur le MIME, énumérer les PDF qui se reconnaissent à la description:

objRegExpr.Pattern = "(filename=)(.+)(\.pdf)
(Il y a quelques parasites à éliminer, c'est assez simple : le double "", les expressions comme "-iso-8859-1-Q-", "iso-8859-15-") C'est donc simple!

L'extraction des pièces jointes: Sur un serveur Linux, munpack devrait être satisfaisant. Sur Windows, je l'ai vu bloquer le système et je lui préfère un utilitaire peu connu mais robuste UUDWin. Lire mon billet: Tri et Distribution des Mails → (sa programmation est simple, mais un peu inhabituelle)

La détermination du nombre de pages se fait très facilement avec PdfInfo. Dans l'automate, je l'inclus dans un pipe, pour plus de rapidité, mais on peut faire autrement et aussi bien. Lire mon billet: Le nombre de pages →

Détermination du caractère scanné ou pas

J'ai posé la question à CHAT-GPT, qui se trompe souvent: il détermine si le PDF peut être lu (en texte). Or, les scanners les plus modernes (ex: CANON LIDE 400) intègrent une reconnaissance de caractères et fournissent, de fait, un PDF hybide, contenant l'image ET le texte. Donc, dire, ceci est (ou n'est pas) un scan sur le base de ce qu'il existe (ou pas) un texte, n'est plus une méthode efficace. (PDFtoText faisait cela très bien...)

Le plus simple est d'extraire le (ou les) JPG dans le PDF, sur la première page, avec PDFImage. Si un JPG est 'gros', c'est un scan ! Alternativement vous pouvez extraire de votre offre sa première page, noter la taille du PDF que vous avez envoyé. Si la taille du PDF en retour est sensiblement différente, c'est que le PDF a été altéré (scanné et/ou signé)
La comparaison de taille devrait suffire... On peut rafiner en extrayant les images de la première page du document envoyé, et du document reçu, et comparer. PDFImage a un mode liste qui convient très bien!

Ainsi, il est aisé de comparer les 2 PDF. Ceci tient au fait que le 'bon pour accord' est l'approbation d'un document que vous avez fournit. Donc, vous l'avez !. Si vous passez par un prestataire externe, ou un programmeur, cette solution peut ne pas marcher.

Au demeurant, vous pouvez préparer votre offre de sorte à simplifier la tâche. Je mets un QR code, avec ASPPDF (il est payant... Vous pouvez essayer segno) que je lis avec ZBAR, gratuit et robuste.

Cela permet de s'assurer que le PDF 'pointe" vers un devis (et lequel...). Cela permet aussi de vérifier l'orientation.

La version la plus récente de ZBAR exige une version particulière de GohstScript, une version particulière d'imageMagick, c'est un vrai casse tête! La version, un peu ancienne qu'on trouve sur SourceForge (version 0.10) est plus robuste et fonctionne parfaitement! ZBAR devrait pouvoir traiter les PDF directement, au travers d'un pipe GhostScript/ImageMagick. Ce qui implique qu'il vous faut ces outils et qu'il n'y ait pas de problème de compatibilité! Chez moi, cela ne marche pas! Qu'importe, je fais une transformation en PNG (avec PDFtoPNG) et je lis le PNG, sans soucis!

Une alternative: le mouchard

Si on passe par un prestataire externe, qui ne dispose pas de l'offre, ou pour simplifier/sécuriser le processus, il y a une astuce. Elle reprend l'idée de factur-X (voire Order-X): faire un PDF combinant le document PDF et des données XML structurées ou un simple fichier texte. Au scan, ce fichier 'caché' va disparaitre... Ou peut être encore plus simple... Détecter les polices (surtout si vous avez mis une police rare, sans l'embarquer...)

XXX
				
				

Détection de l'orientation

Mes offres ont maintenant un QR-Code, situé en haut à droite. En effectuant un recadrage (crop) sur le coin inférieur gauche et en confirmant l'abscance de QR-Code dans ce coin là, on détermine si oui ou non, l'image est retournée.

Le QR-code, lui, s'accomode et outes les positions... Le recadrage de l'image avec ImageMagick est un processus en 2 étapes. Déterminer la taille de l'image, les positions absolues des points de recadrage (crop) en y a appliquant nos pourcentages, enfin le rognage à proprement parler

On utilise un .bat pour rogner (crop) et ne conserver que le coin en bas à gauche :


@echo off
setlocal enabledelayedexpansion

REM Chemin de votre image
set "IMAGE_PATH=input.png"

REM Obtention des dimensions de l'image
for /f "tokens=1,2 delims=x" %%G in ('magick identify -format "%%wx%%h" "%IMAGE_PATH%"') do (
  set /a "WIDTH=%%G / 4"
  set /a "HEIGHT=%%H / 4"
  set /a "OFFSET_Y=%%H - !HEIGHT!"
)

REM Commande pour extraire le coin inférieur gauche à 25%
magick convert "%IMAGE_PATH%" -crop !WIDTH!x!HEIGHT!+0+!OFFSET_Y! output.png

On lui applique ZBAR... Si on y trouve le QRcode, c'est que l'image est inversée. On y applique alors un FILP

magick convert input.png -flip output.png

Redressement éventuel

Jusqu'ici on ne fait pas de detection automatique de la signature. C'est peut être inutile. Aumoins, pas souci esthétique on peut vouloir redresser un petit delta de redressement.