Dans ce TP nous allons implémenter un prototype simplifié de technique d'interaction d'entrée de texte gestuel.
L'image ci-dessous montre le résultat attendu. Ce prototype sera implémenté en PyQt en dessinant sur le canvas.
Créez un repo sur le GIT du FIL et donnez l'accès à votre chargé de TP immédiatement. Effectuez des commits au moins à chaque étape, avec un message expliquant la fonctionnalité ajoutée par le commit. Assurez-vous que le code mis sur le dépôt soit fonctionnel.
Créez une fenêtre principale Editeur qui hérite de QWidget. Cette fenêtre utilisera un layout QVBoxLayout. Ce layout contiendra un QLineEdit et un nouveau composant de type KeyboardWidget. Ce nouveau composant héritera de QWidget.
Nous allons commencer par charger le layout du clavier sous forme de fichier JSON. Ce fichier contient des paraamètres pour les dimensions et espacements des touches, ainsi que les coordonnées, taille, et symbole de chaque touche du clavier. La Ligne 0 est réservée aux recommandations de mots.
Pour charger le fichier JSON, utilisez le package json et enregistrez les paramètres dans votre KeyboardWidget pour pouvoir les réutiliser plus tard.
Les touches doivent être enregistrées dans une nouvelle classe Key, qui contiendra la position, taille et symbole de chaque touche de clavier. Vous allez aussi calculer les coordonnées et dimensions de chaque touche et la stocker sous forme de QRect.
Implémentez la méthode paintEvent pour afficher les touches du clavier.
Implémentez la méthode sizeHint pour que le widget se redimensionne automatiquement. Pour cela vous devez connaître le nombre de touches en x et en y, et utilisez les paramètres de taille et d'espacement des touches du fichier JSON.
Nous allons commencer par implémenter la saisie de texte ordinaire qui consiste à effectuer un press puis un release sur la même touche. Dans ce cas, émettez un signal newletter que vous allez définir. Ce signal sera connecté par l'Editeur à un slot qui ajoutera le symbole correspondant dans son QLineEdit.
Dans la classe Key, implémentez une méthode isOver qui facilitera la recherche du symbole correspondant à la position du curseur souris. Modifiez la méthode paintEvent pour mettre en surbrillance la touche survolée par le curseur souris.
Surchargez la méthode mouseMoveEvent pour enregistrer les coordonnées des points correspondant à un tracé sur le clavier sous forme de liste de tuples.
Affichez le tracé courant grâce à la méthode paintEvent. Ajustez la couleur, épaisseur et opacité du tracé à votre goût et de façon à ne pas masquer le clavier.
Les tracés contiendront de nombreux points, ce qui empêchera un fonctionnement en temps réel du clavier. Il faudra donc réduire le nombre de points tout en conservant une forme de tracé la plus proche possible de l'original.
Créez un nouveau module Reconnaisseur dans lequel vous allez définir une fonction d'interpolation def interpolate(a : tuple, b : tuple, d : int). Cette fonction doit calculer un point entre a et b tel que la distance euclidienne entre a et ce point soit d. Dans cette fonction vous pouvez considérer que d < ab.
Utilisez cette méthode pour écrire la méthode def resample(stroke : list, d : int) qui calcule une liste de point espacés de d pixels suivant le même tracé que stroke.
Pour debugguer votre fonction, ajoutez à votre paintEvent un affichage des points du tracé courant ré-échantilloné comme ci-dessous. Notez que la distance entre les points est toujours la même (sauf pour le dernier point), et ne dépend pas de la vitesse du tracé.
Créez un nouveau module Dictionnaire qui contiendra deux classes : Mot et Dictionnaire. Vous devez charger la liste de mots comme un simple fichier texte, qui contient un mot par ligne.
Les objets Mot contiennent la chaine de caractères correspondant au mot, ainsi qu'un template de tracé correspondant à ce mot sur le clavier. Pour cela, implémentez dans la classe KeyboardWidget une méthode def wordToStroke(self, word : str, d : int) qui calcule le tracé d'un mot passant par les milieux des touches de clavier correspondant à ses caractères. Le tracé est ré-échantillonné pour que la distance entre chaque point soit d. Enregistrez le tracé de chaque mot dans les objets Mot.
Implémentez l'algorithme DTW qui calcule des alignements de séquences. Avec cet algorithme vous allez comparer le geste réalisé par l'utilisateur, ré-échantilloné, avec les template des mots de votre dictionnaire. Voici le pseudo-code de la page Wikipedia.
L'algorithme vous retourne la distance minimale entre les deux séries de points. Vous devez conserver les meilleurs recommandations, c'est-à-dire celles dont cette distance est minimale. Le nombre de recommandations est défini par le paramètre nbReco du fichier JSON. Affichez ces mots sur la première ligne du clavier, et définissez un signal newword que vous émettrez quand l'utilisateur cliquera sur une recommandation. Ce signal sera connecté par l'Editeur à un slot qui ajoutera le mot correspondant suivi d'un espace dans son QLineEdit.
La recherche peut être longue. Vous pouvez effectuer plusieurs optimisations. La première est l'augmentation de la distance de ré-échantillonage. Vous devez choisir une distance suffisamment petite pour que les gestes ne soient pas trop déformés, mais suffisamment grande pour réduire le nombre de points et donc accélérer la reconnaissance. L'autre optimisation que vous pouvez faire est de ne chercher que parmi les mots dont la première lettre est celle correspondant au premier point du geste.