Récents articles

owasp zap openid connect kong oauth dast Cybersécurité

Tests dynamiques de sécurité (DAST) sous OWASP Zap avec…

L’ensemble du code source est disponible sur mon Github  😁

 

Un peu de théorie… 📚

On utilise de nos jours beaucoup de données, au seins de diverses applications, qui elles-mêmes peuvent s’échanger des données sur de multiples plateformes, accessible depuis une multitude d’appareils.

Quid de la sécurité pour éviter toute utilisation abusive ou frauduleuse ?

 

Protocole

OAuth

C’est un protocole permettant une autorisation à ‘consommer’ une API sécurisée. On peut alors interagir avec une application via une autorisation accordé par ce protocole sans avoir à partager son username ou mot de passe en clair.

 

OpenID Connect

C’est une surcouche à OAuth. Autant ce dernier et un protocole  d’autorisation de ressources, OIDC est un protocole d’identification, permettant de vérifier l’identité d’un utilisateur en se basant sur l’authentification fourni par un serveur d’autorisation.

 

Kong

Celui-ci est le serveur d’autorisation précédemment évoqué.

 

Logiciel

OWASP ZAP

Logiciel gratuit et open source, c’est l’outils de pentest le plus connue. Il n’as pas pour but de vous faire remonter la dernière faille zéro day du jour, mais plutôt de tester des mécanismes d’intrusions standard dont les plus connus.

Mais pourquoi un tuto? Car pour permettre de tester votre application dans sa globalité, et afin d’accéder à l’ensemble des pages et sous pages, vous allez devoir manipuler ZAP pour lui indiquer comment s’authentifier comme un vrai utilisateur. Vous ne parcourrez que partiellement votre appli web dans le cas contraire.

 

Authentification: form-based script

Vous allez avoir des scripts déjà fait au sein même de ZAP pour le cas d’application simple, tel que des sites wordpress. En effet, vous avez une URL statique, des credentials à envoyé dans une requête et ça fonctionne. Ce sont des form-based script.

 

Authentification: authentication-based script

Un autre type, appelé authentification-based script, peuvent ajouter des éléments pour des connexion à des sites plus complexe. Par exemple, si vous devez injecter en requête HTTP POST un token anti-CRSF. Il ajoute une sécurité en plus à votre site web, il est généré en général via des éléments renvoyé par le serveur (state, nonce, etc.). Cela permet d’éviter des attaques de type CRSF ( cross-site request forgery) qui consiste à transmettre à un user connecté une requête HTTP falsifié, résultant en une exécution d’une action sans être au courant ou l’initiateur, mais via ses propres droits. L’user est donc complice de l’attaque sans même sans rendre compte.

 

Authentification: HTTP-sender script

C’est celui-ci dont on va utiliser pour ce tuto. Ce script est appelé automatiquement lors de chaque requête que va envoyer ou recevoir ZAP vers votre application web. Mais pourquoi le script précédent ne peut-il pas correspondre ?

Voici un diagramme de séquence entre vos divers autorités :

diag de sequence zap open id connect kong oauth owasp2

 

Vous allez donc avoir une multitude d’échange (rendant impossible le login avec un script menant vers une page statique, au vu de nos multitude redirections on va manger un timeout), permettant de générer des variables aléatoires (state, nonce, crsf token, etc.). Avec celles-ci vous allez pouvoir vous loger dans votre application web, et enfin recevoir un cookie de session ou un JWT. C’est l’un des deux selon votre appli web, qui sera consommé par celle-ci afin de vérifier votre authenticité. Nous ne pouvons récupérer l’ensemble de ces champs avec l’un des type de script dont nous avons parlé précédent, et qui sont essentiel.

Le script HTTPSender interceptant chaque requête, on va pouvoir lui injecter en header HTTP GET ce cookie de session/JWT que l’on va stocker en variable globale, permettant de maintenir notre utilisateur connecté et permettant de parser l’appli web dans son intégralité.

 

Nous savons maintenant comment injecter ces tokens. Cependant, je n’ai parlé de comment le générer, avant de pouvoir l’envoyer des nos requêtes. Nous allons devoir utiliser une approche hybride selon la complexité de vos application web, avec d’une part du selenium/webdriver afin de remplir les champs comme bon nous semble, ne nous loger, et de récupérer les cookies et autre token que nous souhaitons. Vous pourrez toujours faire des appels supplémentaires GET ou POST si votre application nécessite d’avantage de complexité.

 

Si on doit résumer la procédure :

  1. Approche hybride selenium/webdriver pour bypass les xxx redirections vers la bonne page de login, avec l’ensemble des state/nonce généré aléatoirement qui sont nécéssaire
  2. Remplir la page de login avec nos credentials et valider
  3. Extraire le cookie de session ou JWT (bearer token) en variable globale
  4. Injecter ce cookie/JWT en header de requête pour tout HTTP GET via notre HTTPSender script afin d’être authentifié

 

Beaucoup de pratique 😁

Avant de vous lancer directement dans l’automatisation de vos tâches pour une CI/CD par exemple, dans un environnement sans UI, veillez à connaître dans les moindres détails l’ensemble des échanges qui s’effectue entre votre divers protocoles, car vous allez devoir scripter chaque requête à la main afin de reproduire une connexion à vos ressources comme si celle-ci était faîte depuis un navigateur. Cela va aller de la connexion sur le site avec vos credentials, avec la récupération de divers éléments (state, nonce, crsf token, etc.) afin de pouvoir en générer d’autres permettant de prouver votre identité ( divers cookie, token, etc.)

Afin de faciliter la tâche, je vous conseil de débuter de scripter la partie authentification à votre site web via l’application desktop dans un premier temps. Vous pourrez ainsi voir les divers échanges entre vos protocole, si vous ne connaissez pas la procédure dans sa globalité.

 

OWASP ZAP en mode desktop

Je vais détailler d’avantage le contenu des scripts dans la partie suivante, et me consacrer dans celle-ci principalement à vous présenter l’application desktop Zap.

 

Moteur de scripting

Zap est écrit en Java. Mais nous pouvons écrire nos scripts dans d’autres langages via des moteurs de scripts :

  • Javascript via Graal.js ou Oracle Nashorn
  • Zest via Mozilla
  • Python via Jython

J’aime bien le python, donc la suite du tuto sera utilisé avec Jython.

Python et javascript vous permettent d’écrire tout à la main vos scripts. Cependant, Zest vous permet d’enregistrer automatiquement toute action que vous faîtes sur votre appli web, lorsque vous la scanné en mode manuel. Vous pouvez ainsi vous décharger de la tâche pénible de scripter chaque action nécessaire pour être authentifier au sein de votre application, et juste naviguer en mode manuel via Zap sur votre application. Zest va s’occuper d’enregistrer chaque étape dans un format ressemblant à du JSON. Gain de temps assuré ! 😎

zest script
Enregistrement de la séquence d’authentification via le moteur de script Zest (Mozilla)
Scripting – Standalone script
create standalone script
Création d’un script autonome (standalone)
Scripting – HTTPSender script
create http sender script
Création d’un HTTPSender script
Test de l’authentification

Une fois que vos deux scripts sont crée on va pouvoir tester notre solution. Par exemple pour un scan actif.

Commencez par clique-droit sur votre script standalone, et exécutez le afin de reset le contenu de notre cookie. A faire entre chaque parcours manuel de votre application, sinon vous risquez de vous prendre un time out si votre cookie se refresh à chaque session.

Ensuite clique-droit sur votre script http-sender afin de l’activer.

Vous pouvez maintenant lancer votre scan actif et vérifier que le spider récupère bien des pages avec un code de retour OK (200). Choisissez votre point de départ et lancez le balayage :

 

On peut voir que le scan se passe comme souhaité. Vérifiez qu’il passe bien dans vos sous-domaines réservés aux utilisateurs authentifié.

OWASP ZAP en mode automatique/headless

Scripting – Standalone script

On commence par écrire notre premier script qui nous permet d’initialiser une seule et unique fois notre variable globale du cookie à null :

Scripting – HTTPSender script

La partie la plus importante du projet consiste à écrire notre script qui va à la fois nous permettre de nous connecter, de récupérer notre objet nous permettant de nous authentifier, pour enfin l’envoyer dans chaque requête qui passe par Zap.

On commence donc par divers imports de bibliothèque dont on va avoir besoin. Ne vous affolez pas à voir des imports de lib Java dans le script Python !

On y défini quelques variables globales, dont la page de connexion initiale à notre app web :

Lorsque vous créez un script HTTPSender avec le squelette de base, vous allez avoir ces deux fonctions de base :

  • sendingRequest : appelé à chaque envoie de requête à travers Zap vers notre app
  • ResponseReceived : appelé à chaque réponse de notre app vers Zap.

Nous n’utiliserons que la première, car nous souhaitons simplement modifier notre en-tête de requête afin d’ajouter un élément qui nous permette de prouver que l’on est un utilisateur authentifié.

La fonction est simple. On vérifie que notre cookie existe. Si c’est le cas, on l’injecte simplement à notre requête via une fonction défini plus tard. Dans le cas contraire, on va récupérer ce cookie d’abords.

Cette fonction est très généraliste, vous devrez l’adapter en fonction de l’ensemble des étapes nécessaires concernant votre type d’authentification. Chaque site étant différent, je ne peux vous la faire dans sa globalité. Mais le schéma global est le suivant :

  • Créer une fenêtre Firefox. Vous pouvez lui spécifier des arguments, par exemple –headless qui doit être obligatoire si vous tournez sous une CI/CD par exemple. Si vous êtes sur un environnement windows, vous devrez rajouter le –disable-gpu ( qui est une issue connue sur le github de Zap), ou encore ajouter –disable-extensions pour gagner en performance. Si vous avez un proxy d’entreprise par exemple, vous pourrez aussi lui passer.
  • Ouvrir Firefox sur votre page de login. Selon votre provider d’autorisation, vous allez avoir plusieurs redirections. J’ai rajouté une condition afin d’être certains d’être sur la bonne page de login à l’arrivée.
  • Remplir les champs pour s’authentifier via Selenium. Je vous ait mis quelques exemple pour trouver vos champs, et les remplir.
  • Une fois connecté et arrivé sur notre page final pour un utilisateur authentifié, je récupère mon cookie pour l’injecter dans ma variable global. Dans mon cas je récupère un cookie dont le nom est « session », ainsi que sa valeur.

On peut enfin dans une dernière fonction injecter notre cookie de session dans l’entête de l’ensemble de nos requêtes qui transitent par Zap, nous permettant de maintenir notre authentification durant tout le processus de scan :

 

Dans le cas ou vous souhaitez envoyer un Json Web Token (JWT ou appelé Bearer Token) c’est quasiment la même chose :

Dans ce cas là, n’oubliez pas de sauvegarder en variable globale dans la fonction login(), votre token d’accès ainsi que votre token d’expiration. Dans le cas que votre token ne dure pas une session complète mais pour un temps limité, vous devrez ajouter dans la méthode login() une étape permettant de vérifier que votre jeton est encore valide. Dans le cas contraire, il faudra repasser par l’étape login() afin de demander de renouveler votre jeton.

C’est fait de tête rapidos, mais le principe est là si vous souhaitez implémenter une vérification de token concernant sa date de validité et d’expiration :

 

Lancement en CI/CD

On lance notre image depuis le répo officiel :

Afin d’utiliser nos scripts, on doit les déplacer au sein même de notre image dans les dossiers correspondant : /scripts pour les scripts, et /plugin pour y mettre n’extension du moteur de script Jython. En effet il n’est pas disponible de base, il faut soit le rajouter à la main comme ceci, soit l’ajouter (indiqué dans la commande suivante) au lancement de Zap :

Nous n’avons plus qu’a lancer le script zap-x.sh (très important le -x afin de tourner dans un mode headless). J’autorise tout les IP à accéder à Zap en désactivant la clée API et en whitelistant via le name et regex. Vous pouvez ajouter des extensions ici à la volée, ou même mettre à jour ceux déjà présent :

 

Script global

Je fais référence à un script main.py dans ma dernière commande docker. En effet pas besoin dans ZAPDesktop, mais en version full-script, vous allez devoir avoir besoin d’un script que l’on peu qualifier de global, qui va discuter avec notre image de Zap lancé en mode daemon. Ce mode daemon va permettre à ZAP d’être à l’écoute de tout appel. Nous pourrons communiquer entre celui-ci et notre script main.py via l’api REST offerte.

Faîte vous un environnement virtuel Python, que ça soit Venv sur MAC OS ou Anaconda pour windows. On aura besoin de descendre une dépendance :

pip install python-owasp-zap-v2.4

 

Voici la manœuvre à suivre pour communiquer avec ZAP, et surtout comment charger, activer et lancer vos scripts afin de gérer toute l’étape d’authentification dans un environnement headless avant de lancer les spider pour crawl et de lancer les attaques via scanner :

 

Requête générique

Si votre flux d’authentification est plus complexe, et que vous devez récupérer des champs comme le State, None, ou encore un CRSF Token, je vous met ci dessous de quoi réaliser des requêtes génériques GET et POST dans le scritpt HTTPSender :

 

loss function perte cout fonction dice jaccard iou binary categorical cross weighted entropy Cours théoriques - Deep learning

Fonctions de perte/coût (loss function)

Distribution-based Loss

Binary Cross-Entropy

Fonction la plus connue, définissant comme mesure de la différence entre deux probabilité de distribution pour une variable prise aléatoirement.

Quand l’utiliser ? 

Utilisé pour de la classification binaire, ou classification multi-classes ou l’on souhaite plusieurs label en sortie. Utilisable aussi pour de la segmentation sémantique. Fonction basé sur la distribution de Bernoulli.

Référence de base à utiliser comme première fonction pour tout nouveau réseau. Non adapté à des datasets biaisé/déséquilibré.

 

Categorical Cross-Entropy

Utilisé pour de la classification multi-classes, mais en souhaitant un seul label en sortie.

Quand l’utiliser ? 

Pour de la classification multi-class à simple label de sortie.

 

Weighted Cross-Entropy

C’est une variante de la Binary Cross-Entropy. Elle permet de rectifier la faiblesse de la BCE pour les datasets déséquilibrés, en ajoutant des poids selon des coefficients, pour les exemples positifs.

Quand l’utiliser ? 

Pour des datasets biaisé/déséquilibré.

 

Balenced Cross-Entropy

Elle est similaire à la Weighted Cross-Entropy.  A la différence prêt, qu’elle pénalise en plus des exemples positives, les exemples négatifs

Quand l’utiliser ? 

Pour des datasets biaisé/déséquilibré.

 

Focal 

Elle est similaire à la Binary Cross-Entropy. Elle excelle dans les datasets déséquilibrés pour la raison qu’elle diminue le poids lors de la prédiction d’exemples facile, pour permettre au réseau de se focaliser pour l’apprentissage des exemples complexes.

Quand l’utiliser ? 

Pour des datasets TRES biaisé/déséquilibré.

 

Region-based Loss

Pixel-Wise Cross-Entropy

Fonction la plus connue pour des tâches d’image segmentation. Celle-ci examine chaque pixel de façon individuelle. On compare la classe prédite du pixel par notre réseau à notre One Hot encoding de notre masque original.

Etant donnée que le calcul se fait selon la moyenne des pixels, on suppose d’avoir des classes en répartition égales sur nos images. Peut donc poser des problèmes si on a des classes non équilibré. Par exemple pour un algo ou l’on souhaite détecter piétons, ciel et route dans des photos, on sait déjà d’avance que les classes ciel et route seront bien plus présente que les piétons. Résultant ainsi dans un réseau avec des prédisposition à détecter d’avantage les classes les plus présentes.

Quand l’utiliser ? 

Référence de base à utiliser comme première fonction pour tout nouveau réseau de segmentation d’image. Non adapté à des datasets biaisé/déséquilibré.

 

Dice 

Inspiré des coefficients Dice, métriques utilisé pour évaluer la performance d’un réseau de segmentation sémantique d’image en mesurant le chevauchement entre deux objets.

Quand l’utiliser ? 

Très apprécié pour des problématiques de segmentation d’image.

 

Soft Dice

Calcul la probabilité de chaque classe de façon indépendante, pour ensuite donner un score final résultant d’une moyenne.

 

Focal

Utile pour des dataset extrêmement déséquilibré ou les cas positifs restent très rare.

Quand l’utiliser ? 

Apprécié pour des problématiques de segmentation d’image.

 

Tversky 

Similaire à la Dice. Elle ajoute un poids aux éléments FP (Faux Positif) et FN (Faux Négatif). Conçu à la base pour palier contre les datasets déséquilibré d’origine médical. Des constantes y sont ajoutés (alpha et beta) afin de pénaliser différents types d’erreurs (FP et FN) à plus haut dégrés à mesure que leurs valeurs augmentent.

Quand l’utiliser ? 

Très apprécié pour des problématiques de segmentation d’image.

 

Focal Tversky

Mélange de la Focal, qui focus l’entrainement sur les exemples complexe en diminuant le poids des exemples simples, comme par exemple pour les petits ROI (Region of Interest) avec la Tversky.

Quand l’utiliser ? 

Très apprécié pour des problématiques de segmentation d’image.

 

BCE-Dice 

Mélange entre la Dice et la Binary Cross-Entropy. Permet d’avoir quelque peu de diversité, tout en bénéficiant de la stabilité de la BCE.

Quand l’utiliser ? 

Très apprécié pour des problématiques de segmentation d’image.

 

Jaccard / Intersection Over Union (IoU)

Inspiré des coefficients IoU ou communément appelé index de Jaccard, métriques utilisé pour évaluer la performance d’un réseau de segmentation sémantique d’image. C’est le ratio entre le chevauchage de l’instance prédite avec la réelle.

Quand l’utiliser ? 

Très apprécié pour des problématiques de segmentation d’image.

 

Lovasz Hinge

Conçu comme étant une IoU optimisé à des fin de segmentations d’images à multi-classes.

 

Combo

Combination de la Dice loss avec une version modifié de la Cross-Entropy, avec des constantes qui pénalise les FP et FN plus spécifiquement.

 

image semantic segmentation keras tensorflow Cours pratiques - Deep learning

Segmentation sémantique d’images

Introduction & code source Github

Je vous met ici l’ensemble du code source sur mon dépôt Github, avec un exemple concret de segmentation sémantique d’images sur la reconnaissance de carries sur des radiographies dentaire.

 

Que-ce que la segmentation sémantique ?

Ce type de segmentation consiste à classifier chaque pixel d’une image en un label. Plus d’informations ici.

 

Types de vos données

En entrée, nous allons envoyer à un réseau de neurones en entrée des images RGB. Plus d’informations sur la composition d’une image ici. Mais de façon concise, notre image RGB à la forme d’un tenseur d’ordre 3 et de dimensions ( hauteur x largeur x 3 ). Le 3 est le nombre de canal. 1 pour une image noir/blanc, 3 pour RGB. Un canal pour le rouge, un pour le vert, et un dernier pour le bleu. Chaque valeur de la matrice représente donc un pixel. Ce pixel qui à une valeur entre 0 et 255, selon l’intensité de la couleur d’un canal spécifique.

En sortie, on souhaite avoir une matrice de dimensions (hauteur x largeur  x 1), avec chaque pixel ayant n’ont pas une intensité entre 0 et 255, mais un nombre correspondant à nos classes. La valeur du pixel est donc compris entre 0 (indice du background) à n (nombre de classe dans nos images).

Une image sera plus parlante :

semantic segmentation image keras tensorflow

 

Dataset X et Y

Pour réaliser ce genre de classification on utilise de l’entrainement supervisé. Cela consiste à entrainer notre réseau de neurones sur des couples (X, Y). X est l’image à inspecter, Y son étiquette et ce que l’on cherche à avoir. Cela correspond donc à nos masques contenant nos classes à détecter.

 

Labeliser les données

Pour chacune de vos images, vous aller devoir, afin de générer les masques par la suite, les labéliser, les annoter. Via des logiciels spécialisés (VoTT, SuperAnnotate, LabelMe, etc), vous allez pouvoir segmenter vos classes au sein de vos images. Voici un Example de segmentation de Carrie sur des radiographies :

image segmentation tools vott super annotate

Vous aurez la possibilités de faire des carrés, des cubes, des polygones en tout genre. Cela a pour but de générer un fichier JSON. Celui-ci contiendra pour chaque photos un ensemble de coordonnées, de points (X,Y) correspondant aux formes géométriques que vous aurez dessiné sur le logiciel.

segmentation image labeling

 

Génération des masques

Vous devez accordez la configuration de vos masques selon ce que vous souhaitez avoir en sortie du réseau. Vous allez utiliser une fonction d’activation spécifique afin de faire de la prédiction d’une valeur, ou utiliser une autre fonction d’activation pour de la prédiction multi classes. Mais vous pouvez très bien utiliser l’une ou l’autre selon comment vous agencez vos données. On va voir cela dans les prochaines lignes.

Ces différences de configuration de masque concerne la fonction d’activation de la couche final de votre réseau. Et selon elle, vous devez accorder vos métriques et fonction de perte.

 

Génération du masque

Pour générer vos masques, vous allez devoir parcourir l’ensemble de vos radiographies une à une avec le fichier JSON de coordonnées qui lui est associé. Vous allez pouvoir tracer des masques (polygones) via plusieurs procédés. Le but est de remplir des matrices Numpy, avec la classe souhaités contenu dans chaque pixel.

Pour vous donnez des idées de librairies pouvant le faire facilement :

  • Skimage via Polygon2Mask
  • Pillow (PIL) via ImageDraw
  • Scipy
  • OpenCV
  • Matplotlib via points_inside_poly

A FAIRE /// Image toshop photo original input -> masque généré binaire

Exemple d’un masque crée via Skimage :

image segmentation mask polygon 2d numpy

 

Integer ou One hot encoding ?

Comment définir nos classes pour que notre réseau apprennent à les reconnaîtrais ? En effet, on ne peut pas envoyer des mots sous forme de chaine de caractères. Comment donc définir nos classes dans nos matrices ?

 

Integer encoding consiste à donner un entier, un nombre unique qui sert d’identifiant unique pour chaque classe. Pour un exemple avec 3 classes différentes, on peut définir comme ci :

  • Chat, 0
  • Chien, 1
  • Lapin, 2

Dans notre exemple, chaque pixel aura donc une valeur entre 0 et n, correspondant au nombre de classe total présent au sein de notre dataset.

 

One hot encoding consiste à définir un ensemble de colonne selon le nombre de classe possible. Chaque colonne représente une seule classe. Seul la colonne représentant la classe aura comme valeur 1. Les autres colonnes auront comme valeur 0. Si on prends pour trois classe (chat, chien, lapin) on aura un vecteur de la forme suivante :

[probabilité chat, probabilité chien, probabilité lapin], soit [0, 0, 1] par exemple, si on a un lapin à prédire.

Dans notre exemple, chaque pixel aura donc comme valeur un tenseur d’ordre 1, un vecteur. Celui ci sera de taille n, correspondant au nombre de classe total présent au sein de notre dataset.

 

integer labeling vs one hot encoding

 

Pour résumer, si vos données ont des relations entre elles, privilégiez l’integer encoding. Dans le cas contraire ou vos données n’ont pas de relation, privilégiez le one hot encoding.

A FAIRE

 

Préparation des données pour Softmax

Softmax permet de définir une probabilité dans un ensemble de valeurs.

La métrique la plus commune de suivi sera accuracy.

La fonction la plus commune de perte  sera categorical_cross_entropy.

softmax activation function

 

Vous aurez donc des masques de dimensions (Hauteur, Largeur, 1).

Pour les sauvegarder, privilégiez les formats PNG ou TIFF. Car JPG est un format comprimé et vous allez perdre de l’information voir pire avoir des changements de classe. TIFF est un format sans perte, un peu lourd. PNG est un format compressé mais sans perte, donc vous pouvez le choisir sans soucis.

 

De façon concrète vous aurez ceci en sortie de prédictions :

semantic segmentation image keras tensorflow

Préparation des données pour Sigmoid

Sigmoid donne une probabilité entre 0 et 1 pour une seule et unique valeur.

La métrique la plus commune de suivi sera binary_accuracy.

La fonction la plus commune de perte  sera binary_cross_entropy.

sigmoid activation function

 

Vous aurez donc des masques de dimensions ( Hauteur, Largeur, Canal), avec Canal= nombre de classe à identifier. Pensez à ajouter une classe supplémentaire étant le background. En effet elle servira de classe ‘poubelle’ si on ne rencontre aucun de nos classes.

Pour les sauvegarder étant donnée que l’on aura N canal, on ne peut utiliser le TIFF ou PNG car ils ont (je fais un raccourcie et une généralité car c’est plus complexe que cela) que 4 channels (rouge, vert, bleu alpha pour la transparence). Donc vous pourrez les sauvegarder directement sous leur format matricielle Numpy (.NPY).

 

De façon concrète vous aurez ceci en sortie de prédictions :

 

Réaliser une prédiction

Masque pour classe unique

Etant donnée que l’on souhaite prédire une seule classe, notre masque sera binaire et ne contiendra que des 0 (classe background ou poubelle) ou 1 (notre classe à détecter).

prediction classe binary sigmoid

Masque pour multi-classes

Si l’on souhaite prédire plusieurs classes, nous

prediction multi classes numpy argmax softmax

 

Fonction de perte plus adaptés

Les fonctions de perte de type CrossEntropy sont les fonctions basique pour tout problème de classification.

Alors pourquoi ne pas garder nos fonctions de pertes cités plus haut si elles font le boulot ? Car celles-ci évaluent pour chaque pixel de façon individuelle, pour en faire une moyenne sur l’ensemble des pixels. Etant donnée que l’on va travailler avec des dataset déséquilibrés, nous pouvoir avoir des fonctions qui sont d’avantages étudiés pour ces problématique ci. En effet en gardant ces fonctions de base, on pourrait alors avoirs des chances d’avoir des prédictions penchant vers les classes les plus présente au sein de nos images, car basé sur leurs distributions.

Si vous souhaitez néanmoins rester sur une fonction de type CrossEntropy, sachez qu’il en existe des variantes. En effet, vous pouvez attribuer des poids différents pour chacune de vos classes, selon leur plus ou moins grande présence aux seins de vos images.

Mais nous allons passer en revu certaines autres fonctions de pertes qui peuvent s’avérer bien plus efficaces pour de la segmentation.

 

Dice loss

IoU loss

Pixel-wise cross entropy

 

 

 

dataset loader custom keras tensorflow batch Cours pratiques - Deep learning

Réaliser son propre générateur de données

Pourquoi faire son custom loader de données ?

Nous avons vu dans l’article Charger & entrainer le réseau sur des images : fit() vs fit_generator(), qu’il est préférable d’utiliser un générateur pour charger l’ensemble de ses données à son réseau de neurones via des mini-lots de données, par rapport à envoyer le dataset dans sa globalité via un fichier (numpy par exemple).

Vous pouvez gagner du temps en utilisant les générateurs intégré à Keras :

  • flow : charge des données via une variable
  • flow_from_dataframe : charge des données via un dataframe Pandas
  • flow_from_directory : charge des données via un dossier spécifique sur l’ordinateur

 

Vous répondre à une problématique simple, ils feront largement l’affaire. Mais ils vous imposent certaines choses :

  • Train/Validation dataset : ça peut devenir un peu tricky pour définir ces deux jeux de données via Keras. Par exemple construire ces deux jeux avec des données dans un dossier unique. Vous pouvez à la rigueur vous en sortir comme cela :
  • Réaliser des opérations spécifiques, comme de la data augmentation à la volée, avant qu’elles ne soient envoyé au réseau. Keras en propose quelque une via sa classe ImageDataGenerator, mais reste limiter.

 

Cette solution nous permet donc de nous adapter face à de large dataset plus facilement, et évite tout saturation de RAM ou celle du GPU. On profitera en plus du calcul parallèles via les threads de votre CPU.

Etat actuel des choses

Vous devriez avoir pour le moment quelques choses comme ceci comme cheminement pour charger vos données. Je minimalise l’exemple pour la compréhension :

  1. On charge le dataset dans sa globalité via Numpy
  2. On créer son modèle et le compile selon son optimiser et diverses métriques souhaités
  3. On entraine notre modèle sur nos données précédemment chargé

 

Réalisation du générateur

Squelette de base

Voici le squelette de base que je préconise. Votre classe doit hériter de la classe Keras.utils.Sequence :

Cette classe parente vous assure dans le cas ou vous souhaitez utiliser du calcul parallèle avec vos threads, de garantir de parcourir une seule et unique fois vos données au cours d’une époch, contrairement à pures custom générateurs maison que j’ai déjà vu sur des forums qui ne se synchronisaient pas entre eux. On verra plus tard dans le tutoriel comment utiliser ce multiprocess.

 

Implémentation du constructeur

Obligatoire à écrire, et défini dans l’objet de base de Keras.utils.Sequence. Permet d’instancier un nouvel objet.

Vous aurez besoin au minima besoin de vos données X et Y. Je vous montre ici un exemple :

  • data : nos données dans un dataframe Pandas
  • xLabel : nom de colonne du df contenant nos données X
  • yLabel : nom de colonne du df contenant nos données Y
  • batchSize : taille d’un mini lot de données
  • shuffle : booléen si on souhaite envoyer des données de façon aléatoire, ou dans l’ordre de l’index du dataframe
  • targetSize : afin de resize nos images

 

Implémentation __len__

Obligatoire à écrire, et défini dans l’objet de base de Keras.utils.Sequence. Définie le nombre de batch durant une époch.

 

Implémentation on_epoch_end

Défini dans l’objet de base de Keras.utils.Sequence. Appelé à chaque fin d’epoch. On va s’en servir ici afin de rendre aléatoire l’ordre de la liste des ID des items à envoyer pour constituer le batch de données courant.

 

Implémentation __getitem__

Obligatoire à écrire, et défini dans l’objet de base de Keras.utils.Sequence. Génère un batch de données.

Concrètement je récupère une liste d’ID qui correspond à des items spécifiques (X et Y) contenu dans le dataframe de données, de taille batchSize.  Ici pour l’exemple je ne fais que charger des images dans un tableau Numpy

A vous d’effectuer vos transformation souhaités sur vos données, selon votre problématique. A savoir normaliser les données (diviser par 255 les valeurs des images par exemple pour passer de 0<x<255 à 0<x<1), réaliser de la data augmentation, etc.

 

Appel à notre générateur

On a plus qu’a créer un générateur pour le jeu d’entrainement, et un second pour le jeu de validation à partir de notre classe custom. On lui fourni un dataframe avec un nombre d’élément spécifique à chaque type de jeu de données spécifié avec un ratio de 0,8/0,2.

On fourni ainsi nos deux générateurs au modèle via la méthode fit_generator(). On peut spécifier si on souhaite utiliser nos threads afin de paralléliser le chargement des données du disque dur vers le GPU.

 

Data augmentation à la volée

Vous pouvez très bien réaliser une data augmentation sur votre fichier de données en même temps que vos masques, en local, au préalable. L’utilisation du générateur prendra en compte l’ensemble des fichiers de vos deux dossiers.

Vous pouvez néanmoins réaliser de la data augmentation au sein même du générateur, à la volée. Cela permet de stocker votre dataset de base, sans avoir des dizaines et dizaines de giga-octets supplémentaire et ainsi d’économiser du stockage sur votre disque dur. Cependant vous aurez alors un points négatif dans cette affaire. Vous perdrez légèrement en temps de chargement des données vers le GPU. En effet, en plus de lire et charger les images en local, on ajoute une petit étape ici, de créer de nouveaux échantillons à la volée avant de tout envoyer au GPU. A vous de voir quel moyen au final est le plus adapté selon votre type de dataset, votre hardware et votre approche pour répondre à votre problématique.

Ici je vous montre les quelques changements à réaliser pour adapter notre précédent générateur, afin d’y ajouter de la data augmentation à la volée.

 

Modification du constructeur

On va initialiser ici un nouvel attribut, batchSizeAugmented :

  • batchSizeAugmented : nombre d’image total du batch
  • batchSize : nombre d’image lu en local de notre dataset d’origine

Ici comme exemple, je prends batchSize =2 et batchSizeAugmented =32. Dans mon raisonnement, je souhaite que pour une image lu en local, je crée 15 nouvelles images augmentées avec divers effets. Donc si je lis 2 images, j’aurais 30 images augmentés. Ce qui fait 2 + 30 = 32 images au total.

 

Modification de __len__

Rappelez vous cette méthode défini le nombre de batch par époch. On remplace alors batchSize par batchSizeAugmented :

 

Modification de __getitem__

On souhaite générer des images augmentés (30) à partir d’image local (2), aucun changement donc pour l’objet currentBatchIdsRow. 

On change cependant la première dimension de xTrain et yTrain de batchSize à batchSizeAugmented, qui correspond au nombre total d’image par batch ( donc les images locales en plus des augmentés).

On ajoute une nouvelle boucle dans la première , et c’est ici que vous allez opérez vos transformations pour générer de nouvelles images.

classification localisation segmentation detection Cours théoriques - Deep learning

Différences entre classification d’image, localisation/détection & segmentation d’objets

Image classification

image segmentation object detection localization deep learning cnn keras tensorflow

Dans la tâche la plus courante lorsque l’on parle de traitement d’image, on parle de classification d’image. Cela résulte à identifier une classe au sein de l’image. 

 

 

Détection d’objet

image segmentation object detection localization deep learning cnn keras tensorflow

La détection d’objet se superpose à la simple classification d’image, en ajoutant de la localisation d’objet. Cela fonctionne encore avec une classe au sein de l’image, en plus d’avoir sa position. Celle-ci est souvent définie par des rectangles, appelés bounding box.

 

Segmentation d’objets

image segmentation object detection localization deep learning cnn keras tensorflow

Segmentation sémantique

C’est le processus permettant de classifier chaque pixel d’une image en un label particulier. On ne peut faire de différence entre deux instances d’un même objet. Par exemple, si on a deux voitures sur une image, ce type de segmentation donnera le même label sur l’ensemble des pixels des deux voitures.

Segmentation d’instance

La segmentation d’instance va attribuer un unique label sur chaque instance d’un même objet au sein de l’image. En reprenant le précédent exemple, si nous avons à nouveau deux voitures sur une image, chacune d’entre elles se verront attribuer une couleur différente. À l’inverse de la segmentation sémantique, qui aurait défini la même couleur pour les deux voitures.

 

fit vs fit_generator keras tensorflow numpy generator Cours pratiques - Deep learning

Charger & entrainer le réseau sur des images :…

Petit comparatif sur deux méthodes permettant de chargeur un dataset pour entrainer un réseau de neurones via Tensorflow et Keras.

 

Fit()

Cela permet d’envoyer en un seul coup l’ensemble du dataset au réseau de neurones. En conséquence, on doit être sur que le dataset puisse rentrer en RAM.

C’est donc plutôt adapté pour les petits dataset.

 

Générer les fichiers numpy en local

On commence par définir nos tableau qui vont contenir nos images. Pour cela je vous donne deux méthodes différentes :

 

Maintenant on va parcourir un dossier supposé contenant nos images que l’on souhaite ajouter à nos deux précédents tableau. Je vous montre selon les deux types d’initialisation faîte précédemment. Pour chaque image que l’on aura, on va devoir les ouvrir et les transformer en tenseur de taille 3 (largeur x hauteur x canal, ou canal=3 si RGB ou canal=1 si Noir/Blanc). Une couleur peut avoir un gradient allant de 0 à 255, selon son intensité. Pour chaque pixel, on aura alors des triplets (0<Intensité rouge<255, 0<intensité vert<255, 0<intensité bleu<255). Pour des raisons d’optimisation, les réseaux apprennent plus facilement sur des valeurs normalisés. Pour cela, je vais divisé par 255 les valeurs, pour avoir des valeurs comprises entre 0 et 1.

 

Maintenant on les enregistre en local :

 

Charger les fichiers numpy

On a plus qu’a charger les fichiers précédemment sauvegardé en local, et de les fournir directement au réseau via la méthode fit():

 

Fit_Generator()

On se sert de générateurs afin d’envoyer des mini-lots (batch) de notre dataset au réseau.

C’est donc plutôt adapté pour les grands dataset et convient donc mieux aux problématiques rencontré dans la vie réel. Cela permet en plus d’ajouter de la data augmentation à la volée, donc pratique !

 

Besoin de data augmentation ?

C’est ici que l’on défini notre data augmentation. Vous pouvez laisser vide si vous n’en souhaitez pas. On laisse juste la normalisation des données, comme vu précédemment :

 

Chargement des données

On peut charger les données directement via un dossier spécifique ou via un dataframe de pandas. Gardez un seed identique permettant de synchro les deux générateurs sur les mêmes images en entrée entre sa donnée et son label.

 

Via un dossier spécifique

 

Via un datafame pandas

 

Train & Validation set depuis un même dossier commun

Simple exemple pour de la segmentation d’image. Mais selon votre problématique vous devrez changez dans la méthode flow_from_directory/dataframe le class_mode (type de vos données en Y, catégorie, binaire, etc.) et le classes (liste de vos classes [chiens, chats, etc.])

react native expo android ios stack navigator Cours web Mobile – React Native

Chap 0 : Présentation de ReactNative & Expo

Vous commencez le développement mobile et vous hésitez à choisir ReactNative ou la surcouche Expo ? Je vous donne quelques indices afin d’y voir plus clair sur quel framework vous devriez choisir en fonction de vos compétences, du temps, et de vos besoins.

Code source du projet est disponible ici, sur Github.

Présentation de React

Framework pour le développement web frontend, développé par Facebook et concurrent direct de Angular et de Vue.js.

 

Dom Réel et virtuel

React est à la mode. Il est performant, et ce grave à un point important, est qu’il utilise un DOM (Document Object Model) virtuel.

Le DOM (réel) est l’arbre de nœuds, la représentation textuelle de toute votre application web. Quand vous effectuez un changement d’état au sein de votre application, l’intégralité du DOM est recréer. Cela comprend alors l’ensembles des éléments fils de l’application. Et ceci peut être couteux en terme de performance pour des applications grandissantes, surtout si vous y effectuez des modifications fréquentes de votre UI.

Le DOM virtuel quand à lui est un second arbre, correspondant à l’état réel de la globalité de votre application. Vous pouvez alors vous demandez, n’est-ce pas doublon d’avoir deux arbres à l’identique ? Et bien non.

On imagine que vous effectuiez une modification de couleur, sur un texte par exemple. Dans le cas d’une application avec Angular, celui-ci va redessiner la globalité de l’application. Avec React, seul le DOM virtuel va être modifié. React va alors réaliser une comparaison avec des algorithmes spécifiques entre le DOM virtuel fraichement actualisé, et le DOM réel. En fonction des différences entre les deux arbres, seul les nœuds qui ont un nouvel état, vont être redessiner dans le DOM réel. Cela permet de redessiner seulement un seul nœud, dans notre exemple.

Voici une image plus parlante :

virutal dom vs real dom

 

Présentation de React Native (Vanilla)

C’est un framework développé par Facebook pour le développement d’applications cross-platform (iOS et Android).

 

Développement natif vs développement hybride

Le développement natif est l’utilisation de SDK spécifique. Xcode pour iOS, et Android studio pour Android.

 

Pourquoi favoriser le dév. natif
  • Le développement natif utilise ses modules et bibliothèques natives, avec son langage natif. Il sera toujours plus performant que les solutions hybrides, même si au cours du temps nous observons de moins en moins de différences
  • L’accès aux modules spécifique facile (notification push par exemple)
  • Poids des apk/ipa très léger

 

Pourquoi favoriser le dév. hybride
  • Un seul langage de développement pour deux plateformes
  • Un code unique. Donc 2x moins de bug, en 2x moins de temps
  • Une interface UI/UX identique, évitant des différences

Présentation de Expo

Expo embarque ReactNative, c’est une sorte de wrapper, de surcouche. Vous retrouvez donc l’ensemble des items graphiques de ReactNative.

En voici les principales caractéristiques :

Projet déjà configuré ! En une minute vous avez votre Helloworld de fonctionnel ! Cela évite des paramétrages en tout genre et fastidieux sous ReactNative Vanilla pour paramétrer Xcode et AndroidStudio.

Pas besoin de mac pour tester votre app sur les deux OS mobile ! Et ça, ça fait plaisir. Ayant un vielle Iphone et mon Android de tout les jours, je peux tester via l’application Expo (disponible sur les stores iOS et Play), avec hot-reload, et sans avoir de Mac, l’application que je développe sur mes deux smartphones. Même si le dév. hybride promet d’avoir un UI/UX 100% identique entre les deux plateformes, il m’est arrivé que mes checkbox ne s’affichait pas correctement sur iOS. J’ai donc forcé pour mes deux plateforme, l’utilisation de la checkbox Android. Soucis auquel je me serais rendu compte une fois seulement uploadé sur les stores. Sous ReactNative Vanilla, vous pouvez seulement tester sur votre iOS si vous tournez sous macOS,  et seulement sur votre Android si vous êtes sur Windows pour développer.

Pas besoin de mac pour builder votre app ! Vous pouvez utiliser les serveurs expo pour cela. Attention, il vous faudra obligatoirement un compte développeur chez les deux sociétés, et acheter la licence qui va avec. Autre piège, votre build se fera à distance et non en local, attention donc si vous développer une app qui doit rester à l’abris des regards.. Mais dans tout les cas si vous souhaitez build en local et sans mac, vous pouvez utiliser une VM sous macOS Catalina avec QEMU/KVM sans soucis. Sous ReactNative Vanilla il vous faudra forcement les deux OS pour builder sur les deux plateformes, mais qui se résous aussi bien par la VM.

Accès à l’ensemble des modules natifs compliqué voir impossible… A un moment donnée, votre application grandissante va prendre en fonctionnalités. Etant donnée que Expo est une surcouche, l’accès aux modules natifs n’est pas possible pour réaliser certaines fonctionnalités. Je prends comme exemple connu comme les notifications push, ou achats-in app. Vous n’y aurez simplement pas accès.

… mais vous pouvez Ejecter Expo ! Vous pouvez cependant ‘éjecter’ Expo pour palier au problème précédent. Mais à en lire certaines expériences sur le net, selon la taille et le versionning de votre projet, que ça puisse très bien se passer comme mal se passer.

Poids supérieur des apk/ipa ! J’ai publié sur les stores ma première application avec des fonctionnalités très simple. Elle fait 50mo à télécharger sur les stores.

 

Quel framework choisir au final ?

  • Vous débutez sur le développement mobile ?
    • Oui ? Foncez sur ReactNative + Expo

 

  • Votre application requiert des accès natifs à des modules particuliers (achats in-apps ou push-notifications) ?
    • Oui ? : Foncez sur ReactNative vanilla
    • Non ? : Foncez ReactNative + Expo

 

Si votre choix se confirme à développer sous Expo, je vous propose de commencer par le premier chapitre, afin d’installer l’environnement de développement. Hello World sur mon smartphone en moins d’une minute garantie ! 😉

mac os apple installation catalina vm virtual machine qemu kvm Système d'exploitation

macOS Catalina sans macbook, gratuit & fluide

J’ai eu récemment besoin de Catalina afin de builder une application ReactNative pour iOS. Les solutions traditionnels Virtualbox ou encore Vmware m’ont donnée des OS inutilisable, résultant en d’important lag même sur ma config à 4000€. Au bout de deux jours de recherche et d’installations en tout genre pour activer l’accélération graphique via GPU, je suis tombé ENFIN sur une option viable, gratuite, et donnant d’excellent résultat sur mon laptop à 500€ qui datent des années 2015 avec un vieux dual-core !

Vous aurez accès à l’ensemble des outils de Apple, XCode, les app stores, etc.

 

Prérequis

Désolé les amis, il vous faudra lâché Windows au profit d’une distrib Linux. Donc ayez une machine en dual-boot sous la main, c’est obligatoire.

 

Dépôt

J’ai trouvé deux répo intéressant:

 

Le premier de Foxlet est le plus simple. Mais celui de Kholia est un poil plus performant, et nous partirons sur celui-ci dans la suite du tuto.

 

Pourquoi ?

Foxlet utilise le bootmanager Clover, alors que Kholia Open-core. Open-core est plus jeune, moins robuste, mais semble être plus prometteur et performant. Clover semble être délaissé au profit du jeune OpenCore, la plupart des gros développeur sont partie pour ce dernier.

Vous pouvez y ajouter votre GPU, votre carte son, périphériques USB pour développer sous XCode, etc.

 

Installation

On va suivre les infos du dépôts :

  • On paramètre KVM
    $ echo 1 | sudo tee /sys/module/kvm/parameters/ignore_msrs
    

    On rend la modification permanente

    $ sudo cp kvm.conf /etc/modprobe.d/kvm.conf
    
  • Installation des packages nécessaires
    sudo apt-get install qemu uml-utilities virt-manager git wget libguestfs-tools -y
    
  • Clone le répo de Kholia
    cd ~
    
    git clone --depth 1 https://github.com/kholia/OSX-KVM.git
    
    cd OSX-KVM
    
  • Lancez le scripts suivant :
    ./fetch-macOS.py
    

    Selectionnez la version de macOS que vous souhaitez installer.

    $ ./fetch-macOS.py
     #    ProductID    Version   Post Date  Title
     1    061-26578    10.14.5  2019-10-14  macOS Mojave
     2    061-26589    10.14.6  2019-10-14  macOS Mojave
     3    041-91758    10.13.6  2019-10-19  macOS High Sierra
     4    041-88800    10.14.4  2019-10-23  macOS Mojave
     5    041-90855    10.13.5  2019-10-23  Install macOS High Sierra Beta
     6    061-86291    10.15.3  2020-03-23  macOS Catalina
     7    001-04366    10.15.4  2020-05-04  macOS Catalina
     8    001-15219    10.15.5  2020-06-15  macOS Catalina
     9    001-36735    10.15.6  2020-08-06  macOS Catalina
    10    001-36801    10.15.6  2020-08-12  macOS Catalina
    11    001-51042    10.15.7  2020-09-24  macOS Catalina
    12    001-57224    10.15.7  2020-10-27  macOS Catalina
    13    001-68446    10.15.7  2020-11-11  macOS Catalina
    14    001-79699     11.0.1  2020-11-12  macOS Big Sur
    
    Choose a product to download (1-14): 14
    

    On a récupérer le BaseSystem.dmg, on va le convertir

    qemu-img convert BaseSystem.dmg -O raw BaseSystem.img
    
  • On créer un disque dur virtuel, ou notre distri de macOS sera installé. Attribuez la valeur de stockage comme vous le souhaitez. Sachez que Xcode prends pas mal de place, donc au minimum une partition de 64go me semble correct…
    qemu-img create -f qcow2 mac_hdd_ng.img 128G
    

     

Démarrer Catalina

Pour démarrer notre futur macbook pour la première ainsi que les autres sessions, lancez le script suivant :

./OpenCore-Boot.sh

 

On commence par booter sur le disque de base :

installer apple mac os catalina gratuit qemu kvm

 

Sélectionnez Disk Utiliy :

installer apple mac os catalina gratuit qemu kvm

 

Sélectionnez le disque que vous venez de créer dans les étapes précédentes :

installer apple mac os catalina gratuit qemu kvm

 

On va cliquer sur Erase. Donnez lui un nom à notre disque, macOS Catalina par exemple. Validez en cliquant une nouvelle fois sur Erase :

installer apple mac os catalina gratuit qemu kvm

 

Tout est fini. Fermez l’utilitaire de disque afin de revenir sur le menu de départ.

installer apple mac os catalina gratuit qemu kvm

 

Sélectionnez Reinstall macOS :

installer apple mac os catalina gratuit qemu kvm

 

Un tas de chose vont vous être demander, laissez vous guidez à travers l’installation. Acceptez les divers conditions, remplissez votre profil, enregistrer votre compte Apple afin d’avoir accès au store, etc. Je vous explique pas chaque détails, c’est plutôt intuitif :

installer apple mac os catalina gratuit qemu kvm

 

Après une vingtaine de minute, votre installation est fini

installer apple mac os catalina gratuit qemu kvm

 

Pour utiliser le plein écran : Shift + Alt + F

Pour quitter le focus et revenir sur votre linux : Shit + Alt + G

react native expo android ios sqlite Cours web Mobile – React Native

Chap 6 : Stockage de données complexes (SQLite)

Nous avons vu dans le chapitre précédent comment stocker en local des données simples.

On va s’attarder cette fois-ci à comment stocker des informations plus nombreuses, plus complexes et ce de façon plus ordonnée, en utilisant des bases de données SQL.

On va réaliser une simple to-do app, avec possibilité d’ajouter une note, ou d’en supprimer, avec une liste permettant de toute les afficher.

 

Code source du chapitre disponible sur Github.

 

On ne peut utiliser n’importe quel outils pour persister des données car nous sommes sur un projet Expo, nous empêchant d’accéder aux modules natifs de iOS et Android. SQLite est un moyen fonctionnant avec des projets Expo.

 

Objectifs

  • Stocker localement des informations via une base de donnée SQL

 

react native sqlite

 

Prérequis

Installez le package pour utiliser SQLite :

expo install expo-sqlite

 

Nous l’importerons dans nos fichiers de la manière suivante :

import * as SQLite from 'expo-sqlite';

Base de données & CRUD

CRUD : diminutif correspondant aux requêtes basique, à savoir Create, Read, Update et Delete.

Création de la base de données

Rien de plus simple, une seule ligne suffit :

On donne un nom à notre base, en argument. Celle-ci n’est crée qu’une seule et unique fois. Si on rappelle cette même méthode, on récupère la base de donnée crée auparavant, aucun risque de doublon.

 

Création de la table NOTE

On va maintenant créer notre table. Je vous montre la façon la plus simple de réaliser des transaction SQL vers notre base :

Requête SQL des plus basique avec le mot clé CREATE TABLE. On lui donne un nom de table, ainsi que la définitions de nos colonnes. Un ID auto-incrémenté pour garantir l’unicité de nos données, ainsi qu’un attribut TEXT qui contiendra le contenu de nos notes.

Nos deux attributs ne peuvent être null, et on leur défini un type, soit Text ou Integer.

 

Optimiser les requêtes

Je préfère une autre annotation que la précédente pour questionner la base de données, récupérer nos objets et leurs affecter des transformations. Je trouve plus clair et simple à l’utilisation. On définit notre modèle de requête avec une promesse :

On pourra utiliser ce modèle dans des fonctions async, via un appel par un await. On pourra utiliser des then() et catch() à l’appel du service dans nos vues, permettant par exemple d’afficher à l’utilisateur dans une popup si une note à bien été ajouté ou si dans le cas inverse afficher un message d’erreur avec son origine.

 

Récupérer toute les notes

On définit une interface pour les objets que l’on va récupérer en base. Cela nous facilitera leurs manipulations au sein de notre application :

 

On fait appel à notre modèle pour questionner la base :

On récupère nos objets via la requête. On va itérer sur notre résultat de requête pour re-typer correctement nos objets.

 

Ajouter une note

Seule différence, on va ici passer en argument le contenu de ma note que l’on souhaite persister en base. Pas besoin de lui passer un ID pour la note, car celle-ci est généré automatiquement en base. Nous avons défini cette option tout à l’heure, lors de la création de la table.

Supprimer une note

Quasiment identique au point précédent, sauf qu’ici on lui passe un ID de note à notre fonction, étant donné que c’est l’attribut qui défini l’unicité de mes items dans ma table NOTE :

 

Récupération des items dans la vue

Nous venons de créer notre base ainsi que des opérations CRUD permettant d’interagir avec elle. On va désormais créer une nouvelle vue, avec un champ de texte permettant de donner du contenu à une note, un bouton pour ajouter cette note, une liste scrollable permettant de naviguer sur l’ensemble de nos notes en base, et leur associé à chacun un bouton pour les supprimer.

 

Définition du state de notre vue

On commence par créer le squelette de base de notre vue :

On utilise ici un composant de classe et non un composant fonctionnel. On a besoin d’utiliser un state pour l’affichage dynamique du contenu de notre liste scrollable. On défini un attribut myNoteList correspondant à une liste de l’ensemble de nos notes en base, ainsi qu’un second attribut note correspondant au champ de texte remplissable par l’utilisateur pour créer une nouvelle note. On initialise les deux attributs dans le constructeur.

 

Définition des méthodes de notre vue

On défini les méthodes pour mettre à jour nos éléments, pour ajouter une note, ainsi que la supprimer :

La méthode componentDidMount() est une méthode standardisé de React, permettant d’être appelé une seule et unique fois et ce à la fin de la création du composant. On lui demande à l’ouverture de notre page, d’initialiser notre liste avec le contenu de notre base de données.

 

Définition des éléments visuels de notre vue

Ici rien de bien complexe. J’ai créer un titre de page, un champ de texte avec un bouton pour ajouter une note. Une liste scrollable présentant l’ensemble des notes en base, ainsi qu’une option permettant de les supprimer :

J’ai utilisé des icones Ionicons inclus dans Expo, qui s’adapte en fonction de si vous êtes sur Android ou iOS via la méthode Platform.OS, encapsulé dans des TouchableOpacity, pour rendre un peu plus esthétique mes boutons d’interactions, ainsi que des flexbox pour avoir une touche de responsive design.

 

Conclusion

Vous venez de voir comment créer très simplement une base de donnée pour votre application React Native tournant sous Expo, comment l’interroger et surtout comment récupérer et afficher le résultats dans une belle vue responsive.

react native expo android ios async storage Cours web Mobile – React Native

Chap 5 : Stockage de données simples (AsyncStorage)

Nous venons de voir au chapitre précédent comment organiser une navigation complexe et imbriquée pour une application avec une architecture grandissante.

Nous allons voir aujourd’hui comment stocker des données plutôt simple, par exemple des paramètres de l’application comme l’activation ou non d’un thème clair ou sombre.

 

On ne peut utiliser n’importe quel outils pour persister des données car nous sommes sur un projet Expo, nous empêchant d’accéder aux modules natifs de iOS et Android. AsyncStorage est un moyen fonctionnant avec des projets Expo.

 

Code source du chapitre disponible sur Github.
 

 

Objectifs

  • Stocker localement des informations via JSON

async storage chap 5

 

Prérequis

Installez le packet requis via une console :

expo install @react-native-async-storage/async-storage

Utilisez l’import qui correspond dans vos fichiers :

import AsyncStorage from '@react-native-async-storage/async-storage';

Que-ce que AsyncStorage

AsyncStorage nous permet de persister des données en local sur le smartphone, de façon asynchrone et non crypté. Les informations sont sauvegardé en clair, donc éviter de stocker des données sensibles tel que des mots de passes.

Le stockage s’effectue sous forme de couple tel quel : <Clé, Valeur>.

On ne peut stocker que des string, donc pour des éléments plus complexe tel que des objets, on devra utiliser JSON.stringify() pour la conversion en JSON lors de la sauvegarde d’une donnée, et utiliser JSON.parse() pour lire un objet.

Sauvegarder des données

Pour un string

Pour un objet

 

Lire des données

Pour un string

Pour un objet

 

En pratique

On va faire un exemple tout bête, à savoir charger le nom d’un utilisateur à l’appui d’un bouton.

 

Méthode d’écriture & lecture de donnée

On commence par définir nos deux méthodes pour charger et sauvegarder une donnée :

On appellera la méthode initProfileName() dans l’entrée de notre l’application (App.tsx) pour initialiser notre donnée.

 

Mise à jour de notre vue

On crée un état initialisé à ‘Invité’. On aura une méthode appelant notre fonction pour lire notre donnée via AsyncStorage, définit précédemment. Et enfin une méthode render() pour afficher un champ de texte, et un bouton bindé avec la fonction de chargement de notre donnée locale :

 

Conclusion

On vient de voir comment stocker des informations simple en local. Nous allons voir au prochain chapitre comment stocker des informations plus complexe et surtout en quantités plus importantes.