La segmentation pour le marketing – Retour d’expérience

Voici un article que j’ai rédigé pour ma société, dont le thème est « comment mieux connaître ses clients ».

Lien direct : http://tekcollab.imdeo.com/la-segmentation-pour-le-marketing-retour-dexperience/

Introduction

Vous désirez mieux connaitre vos clients, rechercher parmi eux des cibles pour vendre vos produits, proposer vos services ? Qui n’a pas été contacté au moins une fois pour laisser son adresse email, son code postal ou un numéro de téléphone en échange d’un petit cadeau ?

Cette approche de gagnant / gagnant est un bon moyen d’augmenter votre connaissance d’un échantillon de la population. Chaque partie est gagnante lors d’un échange ; chacun y trouve son compte.
Certaines personnes sont tellement friandes de ces opérations marketing qu’elles se sont spécialisées en recherche d’opérations promotionnelles dans tous les domaines (gel douche, pot de yaourt, objets déco). Elles traquent les petits cadeaux ou promotions en échange de quelques infos personnelles.

Voici un exemple de méthodologie, visant à mieux connaître une partie de vos clients, pour leur proposer un produit qu’ils envisagent d’acheter. Autant qu’ils fassent cet acte après de vous…

 

Pour s’appuyer sur un exemple concret, imaginons un centre de balnéothérapie, qui a des clients récurrents et des nouvelles têtes. Comment vendre plus, ou mieux, aux bonnes personnes ?

 

Segmentation stratégique

Au début de chaque histoire, on pose les personnages : qui sont les cibles de cette campagne ?
Dans la masse de la population, tous vos clients ne sont pas intéressés par la campagne du moment. La segmentation stratégique vise à séparer en groupes distincts la population globale, avec une stratégie marketing. Votre base de données de clients doit être découpée en plusieurs groupes, en définissant des clés de sélection.

Pour le centre de balnéo, ce sera tous les clients (anciens et récents) qu’il faudra “segmenter”.

 

 

Ce découpage peut être fait selon plusieurs critères : la valeur de l’individu (ce qu’il peut acheter), son comportement (la fréquence d’achat), l’objectif commercial (à fidéliser, à reconquérir, à approfondir).

 

Les clients de la balnéo seront découpés en :

  • clients “fun”, qui viennent au centre pour se détendre, passer un bon moment entre amis, qu’ils soient des clients actuels ou non.
  • clients “contraints”, qui font de la rééducation, et ne viennent pas trop par plaisir.

 

Gain espéré

Avec cette campagne, votre objectif est de proposer un produit qui correspond aux attentes d’un segment particulier. Inutile de courir plusieurs lièvres à la fois ; il faudra se concentrer sur un seul segment.
Gardons en tête que l’objectif premier de la campagne est la meilleure connaissance d’un groupe de la population. La vente d’un produit n’est que la conséquence.

Si le client ne vient pas par lui-même pour acheter un produit, c’est qu’il n’en a pas besoin, ou qu’il n’a pas vu le produit, ou qu’il ne sait pas qu’il en a besoin.

Pour le premier cas, il n’y a rien à faire. Si on identifie clairement cette population, on ne perd pas son temps à proposer une offre alléchante. Voici un bon exemple avec Amazon : quand je recherche des lunettes de piscine, cette information est conservée pour me proposer d’acheter d’autres modèles de lunettes, pendant que j’hésite sur mon choix, pendant plusieurs jours. Une fois mon achat terminé, et mes lunettes reçues, des publicités de lunettes sont encore proposées, pour rien… Je ne vais pas acheter une autre paire de lunettes.

Si le segment est trop vaste et trop diversifié, une autre phase de segmentation plus fine est lancée, pour un autre cycle.

 

 

Dans notre cas de la balnéo, le gain est de connaître ceux qui sont “fun”, prêts à venir au centre pour le plaisir, et à y revenir. Ce sont des “clients VIP”, qu’il faudra chouchouter. Non seulement ils ont un potentiel, mais leur réseau d’amis aussi.

Parmi cette population, il sera peut-être plus facile de considérer les femmes des hommes, ou les jeunes des anciens.

 

Offre

Que désirez-vous donner en échange d’informations de vos clients ? Vous perdez un peu avec ce don, mais vous gagnerez en contrepartie. Pour rappel, l’objectif n’est pas de vendre maintenant, mais de connaître ceux à qui on peut proposer d’acheter plus tard.

Réfléchissez à un petit cadeau, en contrepartie d’une petite information. Et un gros cadeau, au regard d’une information très importante ou plus personnelle.

 

Dans notre centre balnéo, Les clients ont tous une adresse email. En échange d’informations plus personnelles, comme la catégorie socio-professionnelle, on pourra déterminer si ce sont des clients qui entrent dans le segment ou non. De plus, en recoupant avec les plannings des activités, les cartes d’abonnement, etc, on défini le segment “clients VIP”.
L’offre est simple : réservez en ligne avec votre email, commandez une carte d’abonnement sur notre site Internet, et profitez de 5% de réduction. Sinon, il faut passer au centre et payer plein pot.
Sur le site, les processus de réservation et de commande sont accompagnés de messages invitants à compléter ses informations personnelles. Pour réserver, peu d’informations sont demandées. Pour acheter une carte d’abonnement, il en faut plus.

 

Conclusion

Ce travail de segmentation n’est possible qu’après avoir fait le recueil des informations pertinentes de votre cible. Cela peut prendre du temps, à coup de campagnes de récupération de ces précieuses données.

Plus tard, l’exploitation de votre travail sera facilité et vous gagnerez en pertinence sur vos clients.

 

Sources :

Données d’un capteur vers un serveur Web

Introduction

Lors de mon précédent article, j’évoque l’envoi des données via Wifi depuis un ensemble de composants autour d’une carte Arduino. Or, la programmation n’est pas très facile pour donner plus d’intelligence à ces capteurs. Je suis donc reparti du capteur de température et de pression DHT11, mais relié à un Raspberry PI.


DHT11 - Cảm biến nhiệt độ và độ ẩm https://images.duckduckgo.com/iu/?u=http%3A%2F%2Fwww.openmediacentre.com.au%2Ffileadmin%2Fuser_upload%2Ftx_onqcatalogue%2Fproduct%2Fraspberry-pi-model-b_80_thumbimg1.jpg&f=1
(les photos ne sont pas à l’échelle)
Prix des composants :

  • Raspberry Pi 2 starter kit (avec les câbles, l’alimentation, une carte SD, des câbles, des radiateurs, un boitier, etc) : 75 € sur Amazon
  • DHT11 (capteur nu) : 2.20 € sur Amazon
  • Dongle Wifi (sur port USB) : quelques Euros (sur eBay)

La programmation est faite en script SH et Python dans le Raspberry et je dispose d’un serveur PHP qui héberge les données (mon PC).

 

Vue du Raspberry avec les câbles pour clavier, souris, écran (inutiles en mode normal, mais nécessaire pour la programmation) + le composant DHT11, relié par 3 fils.

 

Vue des composants minimum pour que les données soient envoyées après avoir tout programmé. Il faudra juste prévoir une boite pour protéger le Raspberry et laisser le capteur « à l’air libre ».

 

Protocole de communication – 1. Souscription

Le Raspberry dispose d’un Dongle Wifi pour se connecter au serveur. J’utilise donc l’adresse MAC du Wifi pour souscrire au serveur selon ce principe :

Dans ces échanges, l’adresse MAC sert de clé. Chaque unité de communication devrait avoir une adresse MAC unique sur le réseau.

Une fois le capteur identifié sur le serveur, il n’est pas encore actif. Le serveur a un capteur en attente de paramétrage manuel (donner un nom compréhensible par un humain, un picto, un groupe, une catégorie, etc…). Tant que cette étape n’est pas faite, les données ne sont pas transmises. Le capteur passe à l’état « Activé » lorsque le paramétrage est terminé.

 

Protocole de communication – 2. Envoi des données

Le serveur est maintenant prêt à réceptionner les données du capteur.

Dans le cas du DHT11, il y a 2 données à chaque envoi : température (en °C) et humidité (en %). Le Raspberry ne fait qu’un appel au serveur pour ces 2 informations.

 

Montage électronique

Le montage sur les PINs du Raspberry sont faits directement car le DHT11 accepte un voltage de 3.3v, fourni par le PIN numéro 1. Si cette alimentation directe n’est pas assez stable, on pourra utiliser un régulateur de tension (AMS1117, 4.70 € les 10 pièces sur Amazon), prenant en entrée du 5v et donnant en sortie du 3.3v stabilisé (monté avec un condensateur de 470µF). Ce sera surtout le cas si on utilise une autre source d’énergie qu’une prise de courant (pile, accu, batterie de voiture, capteurs solaires, etc).

 

Le DHT11 comme capteur

Le DHT11 est un composant assez instable, qui ne garanti pas toujours la bonne lecture des données. Il semble que d’autres capteurs soient plus efficaces, peu onéreux, fonctionnant avec une plus grande plage de voltage : TMP36GT9, DS18B20, etc.

Le DHT11 est par contre bien documenté, pour les bidouilleurs comme moi, devant programmer en Python.

 

Copies d’écran du serveur

Liste des capteurs

 

 

Détail d’un capteur d’humidité

 

Pour aller plus loin

A partir de ce système, il est possible d’équiper toute la maison, d’avoir des capteurs partout et de suivre les courbes. Le budget à prévoir reste collé au prix du Raspberry (carte nue à 40 €), car les capteurs ne coûtent que quelques Euros. D’autres solutions voient le jour, avec des cartes électroniques programmables, comme TESSEL (avec Wifi intégré) : https://www.tessel.io/

D’autres capteurs pourront être ajoutés, sous réserve de pouvoir lire les données (sur le même principe), depuis les ports GPIO du Raspberry. Grâce au grand nombre de PINs disponibles, on pourrait ajouter d’autres capteurs à un même Raspberry, recueillant ainsi un lot de données. Pour une pièce de la maison : température, humidité, ensoleillement, concentration CO2, odeur (oui, pour faire remonter le niveau de puanteur du placard à chaussures), méthane, détecteur de mouvement, etc.

 

Raspberry : un feu de cheminée ou un aquarium

Introduction

Noël approche, qui n’a pas rêvé d’avoir un feu de cheminée. Je vous propose de transformer votre Raspberry en « boite à diffuser une vidéo de distraction », comme un feu de cheminée, un aquarium ou une jolie vue de ruisseau.

 

Configuration du Raspberry

Rien de plus qu’un Raspberry qui fonctionne et qui est connecté à un écran.

 

Choix de la vidéo

Youtube fourni un grand nombre de vidéos, en HD de feu de cheminée ou d’aquarium. Voici mon choix :

https://www.youtube.com/watch?v=0fYL_qiDYf0

La vidéo dure 2 heures, mais on n’a besoin de tout le fichier. Il faut passer par une outil de téléchargement (cherchez « youtube downloader firefox plugin ») pour récupérer le fichier MP4.

 

Extraction d’une portion de quelques minutes

Passez par ffmpeg pour extraire une partie de la vidéo. Notez la période : début et durée, puis lancer la ligne de commande suivante pour l’extraction :

ffmpeg -ss 00:30:00 -i feuDeCheminee-full.mp4 -t 00:02:00 -vcodec copy -acodec copy feu-portion.mp4

Sur la vidéo « feuDeCheminee-full.mp4 », je saute 30 minutes du début et je prends 2 minutes de vidéo finale. Le fichier « feu-portion.mp4 » contiendra le résultat : une vidéo de quelques dizaines de Mo (au lieu de plus d’un Go pour la vidéo complète).

 

Lire la vidéo avec le Raspberry

Tentez de lire la vidéo avec VLC : c’est très lent. Impossible de choisir cette solution. En allant voir sur le Net, le player conseillé est « omxplayer ». Cherchez à l’installer avec les bibliothèques pour être sûr du résultat.

Mon Raspberry est configuré en point d’accès Wifi, avec une application Web pour le piloter (depuis mon smartphone ou depuis un PC). J’ajoute cette fonctionnalité, en appelant un script pour lancer la vidéo :

#!/bin/bash

export DISPLAY=:0.0

SERVICE='omxplayer'
RES=0
trap "exit" INT
while [ $RES -eq 0 ]; do
  if ps ax | grep -v grep | grep $SERVICE > /dev/null
  then
    sleep 1
  else
    xterm -fullscreen -fg black -bg black -e omxplayer --win "0 0 1600 1200" -r $1
    RES=$?
    echo "RES: $RES"
  fi
done

xrefresh -display :0

 

Vous constaterez des lignes particulières (que j’utilise peu) :

trap "exit" INT

Cela signifie que si un sous-script est lancé et qu’il se fait killer, le script s’arrête aussi.

 

xrefresh -display :0

A la fin du script, je force le rafraîchissement de l’écran pour nettoyer ce qui peut rester.

 

xterm -fullscreen -fg black -bg black -e omxplayer --win "0 0 1600 1200" -r $1
RES=$?

La ligne de commande principale, qui lance la vidéo, dans un terminal (xterm), en plein écran, avec omxplayer en mode fenêtre, d’une taille de 1600×1200. La variable RES récupère le code retour du player. Si elle vaut 0, on boucle, sinon, on s’arrête (le CTRL-C fait un RES = 2).

Pour simuler la lecture sans fin, j’ai une boucle while autour de tout ça.

 

Arrêt de la lecture par un script

J’ai aussi un autre script qui arrête la lecture en boucle :

PID=$(ps aux | grep omxplayer | awk {'print $2'})
kill -9 $PID 2>/dev/null

xrefresh -display :0

Rien de très novateur. J’ai essayé la commande « killall -9 omxplayer », mais elle n’est pas satisfaisante.

Mon interface (depuis un PC) pour lancer quelques vidéos et un bouton en bas pour arrêter :

Arduino : premiers essais

Introduction

Après avoir complètement paramétré mon Raspberry Pi en point d’accès wifi, avec une webcam et une application web pour le piloter depuis mon smartphone (article complet), je voulais essayer jouer avec les PIN du GPIO. Mais je ne voulais pas refaire un autre Raspberry pour jouer avec les « sensors » (détecteur de température, détecteur de mouvement, etc.). Pour faire ça, il y a Arduino, en plus simple et plus économique…

 

 

Installation de l’environnement

Depuis longtemps, je suis habitué à l’IDE Eclipse pour mes développements. J’ai pris mes réflexes et je pense être moins désorienté dans ces environnement. C’est pour cette raison que j’installe un Eclipse dédié.

J’ai donc suivi quelques tutoriels, mais celui-ci me semble très complet pour tout savoir sur l’interface entre ma machine et la carte Adruino : http://www.codeproject.com/Articles/1003347/Creating-Arduino-programs-in-Eclipse

Avec Ubuntu, le port est à changer. Pour savoir quel port il faut configurer (« //./COM15″ dans le tutoriel), voici les étapes, depuis un terminal :

  1. Branchez votre carte Arduino sur le port USB et tapez « lsusb ». Si une ligne apparaît avec « Arduino », c’est bon, la carte est reconnue.
  2. Tapez « dmesg ». Vous devriez voir :
    [ 1013.247248] usb 8-2: Product: Arduino Uno
    [ 1013.247250] usb 8-2: Manufacturer: Arduino Srl
    [ 1013.247253] usb 8-2: SerialNumber: 85431303636351D0A101
    [ 1013.272419] cdc_acm 8-2:1.0: ttyACM0: USB ACM device
  3. Tapez « ls /dev/ttyACM* ». Vous devriez avoir le port de l’Arduino : /dev/ttyACM0
  4. Il reste à permettre à l’utilisateur d’écrire dans ce device : sudo chmod 777 /dev/ttyACM0

 

Premier script : « blink »

En suivant le tutoriel, je crée le programme « blink.cpp » en copiant / collant le code fourni, mais une erreur survient sur l’include :

#include "arduino.h"

 

Il faut la changer en :

#include "Arduino.h"

En consultant le fichier recherché dans mon code (version 1.6.6 d’Arduino), il y a un « A » (majuscule).

Le build se passe correctement et l’installation sur la carte aussi. La LED clignote bien.

 

Conclusion

Même si l’IDE fourni par Arduino fonctionne très bien, il m’a fallu un peu plus d’une heure pour installer quelques plugins et adapter le dernier. J’espère que cet article fera gagner du temps à ceux qui essaieront.

 

OpenCV pour mesurer la qualité des images – update

Introduction

Un premier article (OpenCV pour mesurer la qualité des images) a été rédigé il y a fort longtemps sur la mesure de qualité d’une image ou d’une photo, par rapport au flou ou au défaut de mise au point. A vrai dire, je n’étais pas complètement satisfait du résultat, en comparant 2 photos ayant de légères différences de netteté. Après d’autres recherches et tests, voici une version nettement améliorée.

 

Le script v2

import cv2, sys

filepath = sys.argv[1]
filename = sys.argv[2]

myCamera = cv2.VideoCapture(0)
if myCamera.isOpened():
    ret, frame = myCamera.read()
    gray = cv2.cvtColor( frame, cv2.COLOR_BGR2GRAY)
    variation = cv2.Laplacian(gray, cv2.CV_64F).var()

    finalFilename = "%s/%d-%s" % (filepath, variation, filename)
    cv2.imwrite(finalFilename, frame)

 

Notes et remarques

Ce script donne un autre résultat, en se basant sur une image en tons de gris et en calculant le laplacien. Ensuite, il calcule la variation sous forme d’un scalaire, donc facilement exploitable pour trier les photos nettes de celles qui le sont moins.

Ce script est utilisé pour un mode « rafale » de prises de vues sur une webcam (derrière un télescope), pour isoler les photos les plus nettes. L’indice de qualité est le préfixe du nom de fichier, donc c’est facile d’avoir les 50 meilleures photos et les 50 les plus floues.

 

Raspberry Pi + Camera : pilotage par un smartphone

Intérêt

Le raspberry est une petite boite contenant l’équivalent d’un PC assez puissant pour faire quelques petites choses intéressantes. L’objectif est de brancher le raspberry sur une source d’alimentation (dans la voiture, près de la télé, sous un télescope) et de piloter quelques actions depuis un smartphone.

Exemples d’application :

  • Près d’un point de surveillance de la maison : Caméra de surveillance, avec enregistrement des vidéos sur une clé USB
  • Dans la voiture : Enregistrement de la conduite de manière automatique. en cas de litiges, vous aurez un enregistrement vidéo
  • Sous un télescope : avec une webcam derrière l’objectif pour faire la mise au point et une série de prises de vue enregistrées sur la clé USB
  • Près d’un moniteur ou d’une télé : pour lancer une présentation PowerPoint / PDF, pour jouer des vidéos, avec la webcam pour prendre des photos

Tout ça, avec un smartphone en guise de télécommande Wifi.

 

Configuration matérielle pour le démarrage

Voici le matériel nécessaire pour configurer le Raspberry complètement :

  • Un clavier USB
  • Une souris USB
  • Un écran
  • Une clé USB
  • Un accès à Internet

 

Configuration logicielle pour le démarrage

  1. Installation de l’image Ubuntu Mate pour Raspberry
  2. Installation des services :
    1. LAMP (Apache + PHP + MySQL), avec changement du user apache
    2. openssh-server (pour accès shell par SSH)
    3. usbmount (auto-mount les clés USB)
  3. Configuration des points de montage : pour la clé USB :  « uid=1000,noauto,user »
  4. Désactivation du firewall Ubuntu
  5. Installation d’un serveur FTP et SSH pour déposer les fichiers depuis un autre PC et lancer des lignes de commandes

 

Configuration logicielle étape 2

Cette étape se fait depuis un PC pour plus de confort. On a donc mis le Raspberry sur un réseau interne :

  • Un PC connecté via RJ45 à un routeur
  • Le Raspberry connecté en RJ45 au routeur

Par la suite, le câble RJ45 sera supprimé.

 

Le clavier, la souris et l’écran peuvent être supprimés après avoir installé les serveurs SSH et FTP.

L’installation d »es logiciels continue :

  1. Installation d’une application web pour accéder au Raspberry depuis un smartphone (PHP avec CodeIgniter) – Réalisée par mes soins ; cf chapitre « Application web pour contrôler le Raspberry » ci-dessous
  2. Changement de la politique de sécurité pour autoriser l’arret et reboot par un utilisateur quelconque (/usr/share/polkit-1/actions/org.freedesktop.login1.policy)
  3. Connexion automatique d’un user (pour ne pas avoir de mire de connexion)
  4. Ajout d’un script à la connexion pour lancer « xhost + » (pour que d’autres machines puissent ouvrir des fenêtres dans la session ouverte)
  5. Activation du dongle Wifi en mode AccessPoint (point d’accès Wifi) et paramétrage du DHCP

 

Application web pour contrôler le Raspberry

Cette application web permettra de piloter le Rasbberry depuis un smartphone. Elle est développée en PHP avec le Framework CodeIgniter. Un template HTML permet d’avoir une charte graphique en Responsive Design : http://binarycart.com/bclivedemos/01-05-2014/v1/bs-binary-admin/index.html

 

Fonctionnalités basiques de l’application PHP :

  • Etat du raspberry : charge machine, type de processeur, version du noyau, version et nom du système (Ubuntu)
  • Lancement de scripts SH (exemple : lancer une présentation avec un fichier PDF précis)
  • Parcours dans les répertoires
  • Caméra : prise de photo, enregistrement d’une vidéo
  • Arrêt et relance du raspberry

 

Utilisation avec un smartphone

Après avoir mis sous tension le raspberry, le smartphone peut s’y connecter par wifi. Un simple navigateur permet d’accéder aux fonctionnalités. Le smartphone devient une télécommande.

 

Copies d’écran sur smartphone

Page de connexion

Page d’accueil : description de la machine

Menu

Page des scripts à lancer

Page des médias USB connectés et détectés

Parcours des fichiers du Raspberry

Page de la gestion de la caméra : prendre une photo, prendre des vidéos

Page d’arrêt / relance du Raspberry

Kivy : gestion de la fin d’une lecture audio

Introduction

Kivy ne propose pas d’interface élaborée pour surveiller le bon déroulement du fichier audio qui est en cours de lecture (durée, temps passé). Mais je vous propose de voir en détail ce qui est exploitable en utilisant le « player » associé à l’objet « son joué ».

 

Structure des données

Pour écouter un fichier MP3 (WAV ou OGG), il faut réaliser ceci :

filename = "youi.mp3"
self.currentSound = SoundLoader.load(filename)
self.currentSound.play()

L’objet « SoundLoader » ne propose pas beaucoup d’interaction. Il est possible de le lire (méthode play), de le forcer à s’arrêter (méthode stop) et de savoir s’il est arrêté ou s’il en lecture.

La méthode « on_stop » est appelée à la fin du son.

 

Description

Le fichier audio doit être joué par le scheduler et non directement :

filename = "youi.mp3"
self.currentSound = SoundLoader.load(filename)
Clock.schedule_once(self.play_current_sound, 0)

De plus, d’autres méthodes sont définies.

1. Méthode pour lancer la lecture du fichier audio :

    def play_current_sound(self, arg):
        self.currentSound.play()
        self.currentSound.bind(on_stop = self.stop_sound)

Attention à respecter cet ordre sinon à la fin du son, la méthode « on_stop » n’est pas appelée.

 

2. Méthode qui arrête la lecture du fichier audio (forcée par un bouton) ou qui est appelée en fin de lecture :

    def stop_sound(self,  arg = None):
        self.currentSound.unbind(on_stop = self.stop_sound)
        if self.currentSound is not None:
            self.currentSound.stop()

 

Conclusion

Avec ce principe, la méthode « stop_sound » est appelée lorsque le fichier est terminé ou lorsque l’utilisateur a volontairement arrêté la lecture. Il est possible de présenter un bouton [Stop] uniquement lorsqu’il y a un son qui est joué.

Cette fonctionnalité de lecture d’un son est implémentée de manière différente en le PC (sous Linux) et un SmartPhone Android. Il faudra faire les ajustements au dernier moment ou implémenter une interface…

 

Kivy : scrolling horizontal

Introduction

Dans une page web, un tel composant s’appelle un carrousel. Il permet de faire passer les éléments de la droite vers la gauche ou inversement. Dans Kivy, ce widget n’existe pas en tant que tel, mais un composant plus générique est disponible. Reste à le configurer correctement.

source de l’image : http://www.androidpatterns.com/uap_pattern/carousel

Tout est dans le kv

Le « main.py » de l’application de démo est très classique :

import kivy
kivy.require('1.8.0')

from kivy.config import Config
Config.set('graphics', 'width', '450')
Config.set('graphics', 'height', '800')

__version__ = '0.1'

from kivy.app import App
from kivy.uix.screenmanager import Screen

class HScroll(Screen):
    pass

class HScrollApp(App):
    def build(self):
        return HScroll()

if __name__ == '__main__':
    HScrollApp().run()

 

Le fichier « hscroll.kv » défini les éléments de manière statique. Si besoin, il faudra les dynamiser dans votre application. Dans cet exemple, il y a un widget Label avec le texte « Horizontal scrolling » et un widget ScrollView qui définira la zone de scrolling.

<HScroll>:
    BoxLayout:
        orientation: 'vertical'
        canvas.before:
            Color:
                rgba: 1,1,1, 1
            Rectangle:
                pos: self.pos
                size: self.size

        Label:
            text: "Horizontal scrolling"
            size_hint_y: None
            height: sp(25)
            font_size: '16sp'
            color: (0,0,0, 1)
        ScrollView:
            GridLayout:
                rows: 1
                spacing: 10
                size_hint_x: None
                width: sp( 7 * (100 + 10) )

                canvas.before:
                    Color:
                        rgba: 0.2,0.2,0.2, 1
                    Rectangle:
                        pos: self.pos
                        size: self.size
                Image:
                    source: "images/disc-large.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/disc-medium.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/disc-small.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/ic_action_accept.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/ic_action_back.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/ic_action_cancel.png"
                    size_hint_x: None
                    width: sp(100)
                Image:
                    source: "images/ic_action_cloud.png"
                    size_hint_x: None
                    width: sp(100)

 

Tout le travail du ScrollView est de tronquer un widget à l’intérieur, un GridLayout, plus grand (trop grand) et nécessitant un scrolling pour le voir entièrement.

Pour ce faire, il faut que le GridLayout ait une largeur définie. Dans cet exemple, c’est en dur : il y a 7 images de 100sp chacune. Sa largeur sera donc de 7 * (100 + 10 de spacing).

Il faut donc :

  1. Un widget ScrollView qui contient un GridLayout
  2. Une seule ligne dans le GridLayout (« rows: 1″)
  3. Définir la largeur du GridLayout (« size_hint_x: None » et width: sp(…) »)

Avec ces informations, vous pourrez faire un joli scrolling horizontal. Pour un scrolling vertical, il suffit de mettre une colonne (point #2) et de changer les largeurs en hauteurs (point #3).

 

Kivy : accès à l’appareil photo sous Android

Introduction

Depuis une application Kivy, il est possible d’appeler des objets Android et les faire interagir en utilisant Python. Pour ce faire, une interface est disponible sous le nom de « jnius ». En effet, c’est très pratique et génial…

Voici un exemple d’utilisation de l’appareil photo, le tout réutilisable dans vos applications Kivy, car dans un module à part (genre « Service »).

 

Appel de l’appareil photo dans l’application Kivy

Attention, avant de procéder à l’appel de caméra pour prendre une photo, il faut savoir une chose : l’application Kivy se mets en pause. Pour ce faire, il est nécessaire que l’instance de App définisse la méthode « pause » et renvoie « True ». C’est un peu comme si Kivy disant « je mets en pause l’appli, est-ce que j’en ait le droit ? ». Si « True », je passe à la suite. Si « False », je stoppe.

 

Le service d’appel de la photo est défini comme ceci (basé sur le tuto « takepicture ») :

from jnius import autoclass, cast
from android import activity
from functools import partial
from kivy.clock import Clock
import os

Intent = autoclass('android.content.Intent')
PythonActivity = autoclass('org.renpy.android.PythonActivity')
MediaStore = autoclass('android.provider.MediaStore')
Uri = autoclass('android.net.Uri')
Environment = autoclass('android.os.Environment') 

class CameraService(object):
    '''
    Service pour acceder a la camera du device.
    Attention, seules les images JPG sont autorisées.
    '''

    def __init__(self, savingPath = None, aFilename = None):
        '''
        Constructeur
        savingPath doit se terminer par le separateur de répertoire
        '''
        if savingPath is None:
            savingPath = Environment.getExternalStorageDirectory().getPath()
        self.path = savingPath
        self.filename = aFilename
        self.picture = None

    def __define_new_filename(self):
        index = 0
        while True:
            index += 1
            fn = os.path.join(self.path, 'takepicture{}.jpg'.format(index))
            if not os.path.exists(fn):
                return fn

    def take_picture(self, aCallbackFunction):
        self.callbackFunction = aCallbackFunction
        if self.filename is None:
            self.filename = self.__define_new_filename()
        uri = Uri.parse('file://' + os.path.join(self.path, self.filename) )
        uri = cast('android.os.Parcelable', uri)

        activity.bind(on_activity_result=self.__on_activity_result)

        intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri)
        PythonActivity.mActivity.startActivityForResult(intent, 0x123)

    def __on_activity_result(self, requestCode, resultCode, intent):
        if requestCode == 0x123:
            Clock.schedule_once(partial(self.__add_picture, self.filename), 0)

    def __add_picture(self, fn, *args):
        self.picture = fn
        self.callbackFunction(self.picture)
        activity.unbind(on_activity_result=self.__on_activity_result)

 

Son appel se fait dans une instance de Screen, par exemple :

class myScreen(Screen):
    #...

    def btn_take_photo(self):
        camera = CameraService()
        camera.take_picture(self.set_picture)

    def set_picture(self, newPicture_name):
        self.logLabel.text = "Photo : %s" % str(newPicture_name)

Lorsqu’un bouton est cliqué, la méthode « btn_take_photo() » est appelée. Celle-ci fait appel au service (sans paramètre dans ce cas) et lance la prise de photo avec un paramètre : la méthode à appeler lorsque l’utilisateur aura terminé : « self.set_picture ».

Cette méthode prend un paramètre : le nom du fichier sauvegardé par l’appareil photo du smartphone. Attention, seul le format JPEG est autorisé…

 

Conclusion

Pour reprendre cette photo, je vous conseille de la réduire avec PIL pour éviter d’avoir une image en 4128 x 2322 pixels.

im = Image.open(filename)
width, height = im.size
im.thumbnail( (width/4,height/4) , Image.ANTIALIAS)
im.save(filename,quality=95)

Pill Chart : graphe en forme de pillule

Introduction

Suite à la création du sélecteur de couleur sous forme d’une petite barre, j’ai voulu faire un graphique simple sur une dimension. J’ai repris les mêmes bases que le plugin précédent pour celui-ci.

Description

La syntaxe en JS est assez simple, un « PillChart » est défini par :

  • le DIV qui sera utilisé pour afficher le plugin
  • les données à afficher, avec les clés suivantes :
    • « label » : libellé au survol de la souris
    • « value » : valeur à faire apparaître dans la case du graphe
    • « width » : largeur en pixel ou en pourcentage
    • « cssAttr » : texte à coller dans l’attribut CSS « style »
new PillChart({
            containerId: 'pill_1',
            data: [ {
                label: "moins de 10 ans",
                value: 20,
                width: '20px',
                cssAttr: "background-color: #69C8FF;"
              }, ...
           ],
            updateCallback: function(obj){alert("clicked on : "+obj.label );}
        }).draw();

 

Démo

La page de démo est disponible sur cette page : http://jc.specs.free.fr/pillChart/demo.html

 

Démo avec une légende (générée par le plugin) :