PROJET AUTOBLOG ~ Streisand Effect


sciunto

Site original : sciunto

⇐ retour index

Campagne de dons libre association de l'April : derniers jours

Friday 15 August 2014 à 00:00

L'April a lancé une campagne de dons en faveur de la promotion du logiciel libre en milieu associatif.

Le logiciel libre, porteur d’une idéologie de partage, rejoint les valeurs véhiculées par le monde associatif en général (partage, volontariat, bénévolat, passion, etc.) et par l’éducation populaire en particulier. Paradoxalement, nous constatons régulièrement que les associations sont captives de logiciels et services privateurs de liberté.

Dans le cadre du groupe de travail « Libre Association », l'April a entrepris de « jeter des ponts entre les logiciels libres et le monde associatif » en réalisant un guide répertoriant des méthodes et des outils de travail adaptés aux besoins des associations. Ce guide est prévu pour donner à chaque association les clés de ses libertés informatiques. Il est disponible en version Web et en Version papier (PDF).

L'April a réalisé et diffusé gratuitement à 10 000 exemplaires une première version de ce guide, en 2012, grâce à une subvention de la Fondation Crédit Coopératif. Dans la continuité de ces actions, l'April souhaite financer une nouvelle édition du guide et le distribuer gracieusement auprès des associations.

Il ne reste plus que quelques jours !

Utiliser pyserial pour dialoguer avec une balance et un port série

Wednesday 11 June 2014 à 00:00

Dans mon billet précédent, je présentais une méthode permettant d'obtenir la valeur d'un afficheur à partir d'une prise de photos et d'un traitement d'images suivis d'un machine-learning.

Je passe maintenant au cas où l'appareil est doté d'un port série et de la documentation qui va avec. Je connecte donc ma machine à un adapatateur usb/série et le device apparait dans /dev/ttyUSB0.

On sort python et sa bibliothèque pyserial. Je souhaite ici récupérer la masse en fonction du temps d'une balance Mettler Toledo XS105. Dans la doc, je sais que je dois envoyer la commande 'SI' et que la balance me répondra (même si elle n'est pas stable, cf doc).

Je commence par écrire une petite fonction qui va me récupérer tout ça.

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
# License: GPLv3

# Parameters for the Mettler Toledo XS105 scale:
# 9600
# 8/No
# 1 stopbit
# Xon/Xoff
# <CR><LF>
# Ansi/win
# Off

import serial
import time
import re


def read_weight(socket, timelapse=1):
    """
    Returns the weight in gram and the stability.

    :param socket: serial socket
    :param timelapse: timelapse between each measurement
    :returns: tuple (weight, stability)
    """
    ser.write('\nSI\n')
    time.sleep(1)
    value = ser.read(ser.inWaiting())
    value = value.split('\n')[1][:-1]
    if value[3] == 'S':
        stability = True
    else:
        stability = False
    weight = value[4:-1].strip(' ')
    return (weight, stability)

Ensuite, je procède à l'ouverture d'une socket avec les options que j'ai configuré dans la balance, puis je boucle sur ma fonction pour récupérer régulièrement la masse. Le programme se termine avec un Ctrl-C.

if __name__ == '__main__':

    ser = serial.Serial(port='/dev/ttyUSB0',
                        baudrate=9600,
                        parity=serial.PARITY_NONE,
                        stopbits=serial.STOPBITS_ONE,
                        bytesize=serial.EIGHTBITS
    )

    if not ser.isOpen():
        ser.open()
    with open('data.dat', 'w') as fh:
        fh.write('# Time (s) | Weight (g) | Stability')
        zerotimer = time.time()  # perf_counter might be better py3
        try:
            while True:
                weight, stability = read_weight(ser)
                timer = time.time() - zerotimer
                print('t = ' + str(timer) + 's | M = ' + str(weight) + ' g')
                fh.write(str(timer) + ' ' + weight + ' ' + str(stability) +'\n')
        except KeyboardInterrupt:
            fh.close()

Et voilà !

Détection de nombres d'un afficheur avec scikit-image et scikit-learn

Sunday 8 June 2014 à 00:00

Dans ce billet, je propose de traiter le problème suivant. Prenons le cas d'un appareil possédant un afficheur digital tel qu'une balance, un voltmètre, un réveil, un hygromètre... mais ne possédant pas de moyen simple de récupérer les données (absence de port série etc). Si on souhaite collecter les données, deux méthodes s'offrent à nous. La première est celle de l'électronicien qui consiste à mettre un moyen de communication là où il n'y en a pas. L'inconvénient est qu'il faut avoir une compétence (que je n'ai pas) et qu'il faut faire du cas par cas. La seconde technique est de prendre en photo l'afficheur et d'analyser les images. C'est ce qu'on va faire ici.

Pour la démonstration, je vais utiliser python, une balance dont la masse affichée fluctue au cours du temps et les photos de l'afficheur.

Une fois le lot de photos sur le disque (ex: Img0001.png), on va chercher à détecter les chiffres (ou digits). Scikit-image va nous être précieux. Il nous faut :

Ceci est fait par ce code (qui manque encore de soin)

import os.path
import glob

import numpy as np
from scipy import ndimage
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

import skimage.io
from skimage.filter import threshold_otsu
from skimage.segmentation import clear_border
from skimage.morphology import closing, square
from skimage.measure import regionprops, label
from skimage.color import label2rgb


def segment_digit(image, filename, output_dir, digit_height=100, digit_width=52,
                  border=7, black_on_white=True, closingpx=4):
    """
    Segement each digit of a picture

    :param image: grey scale picture
    :param filename: filename of the picture source
    :param output_dir: path for the output
    :param digit_height: height of a digit
    :param digit_width: width of a digit
    :param border: pixels to shift the border
    :param black_on_white: black digit on clear background
    :param closingpx: number of pixels to close
    """
    # apply threshold
    thresh = threshold_otsu(image)
    if black_on_white:
        bw = closing(image > thresh, square(closingpx))
    else:
        bw = closing(image < thresh, square(closingpx))

    filled = ndimage.binary_fill_holes(bw)
    #plt.imshow(filled)
    #plt.show()

    # remove artifacts connected to image border
    cleared = filled.copy()
    clear_border(cleared)

    # label image regions
    label_image = label(cleared)
    borders = np.logical_xor(filled, cleared)
    label_image[borders] = -1
    image_label_overlay = label2rgb(label_image, image=image)

    fig, ax = plt.subplots(ncols=1, nrows=1, figsize=(6, 6))
    ax.imshow(image_label_overlay)

    regions = regionprops(label_image)

    for item, region in enumerate(regions):
        # skip small elements
        if region['Area'] < 300:
            continue

        # draw rectangle around segmented digits
        minr, minc, maxr, maxc = region['BoundingBox']
        rect = mpatches.Rectangle((minc, minr), maxc - minc, maxr - minr,
                                  fill=False, edgecolor='red', linewidth=2)

        # uniq size
        img = np.zeros((100, 75), 'uint8')
        img[bw[minr:maxr, minc:maxc]!=0] = 255
        newname = os.path.splitext(os.path.basename(filename))[0] + '-' + str(item) + '.png'
        skimage.io.imsave(os.path.join(output_dir, newname), img)
        ax.add_patch(rect)

    plt.show()
Segmentation de chaque digit

Segmentation de chaque digit

On s'est donc fabriqué un lot d'images (Img0001-0.png, Img0001-1.png...) correspondant à chaque digit.

Il nous reste maintenant à les identifier. Une première méthode serait de faire du template matching, mais ça peut vite s'avérer peu robuste. On va donc préférer passer par du machine-learning. Scikit-learn est là pour ça.

Le principe :

Pour la partie connue :

import glob
import os

import numpy as np
import pylab as pl

from sklearn import svm, metrics
import skimage.io

def load_knowndata(filenames):
    training = {'images': [], 'targets': [], 'data' : [], 'name' : []}

    for index, filename in enumerate(filenames):
        target = os.path.splitext(os.path.basename(filename))[0]
        target = int(target.split('-')[0])
        image = skimage.io.imread(filename)
        training['targets'].append(target)
        training['images'].append(image)
        training['name'].append(filename)
        training['data'].append(image.flatten().tolist())
        pl.subplot(6, 5, index + 1)
        pl.axis('off')
        pl.imshow(image, cmap=pl.cm.gray_r, interpolation='nearest')
        pl.title('Training: %i' % target)

    ## To apply an classifier on this data, we need to flatten the image, to
    ## turn the data in a (samples, feature) matrix:
    n_samples = len(training['images'])
    training['images'] = np.array(training['images'])
    training['targets'] = np.array(training['targets'])
    training['data'] = np.array(training['data'])
    return training

Pour la partie inconnue :

def load_unknowndata(filenames):
    training = {'images': [], 'targets': [], 'data' : [], 'name' : []}

    for index, filename in enumerate(filenames):
        image = skimage.io.imread(filename)
        training['targets'].append(-1) # Target = -1: unkown
        training['images'].append(image)
        training['name'].append(filename)
        training['data'].append(image.flatten().tolist())

    ## To apply an classifier on this data, we need to flatten the image, to
    ## turn the data in a (samples, feature) matrix:
    n_samples = len(training['images'])
    training['images'] = np.array(training['images'])
    training['targets'] = np.array(training['targets'])
    training['data'] = np.array(training['data'])
    return training

et on écrit un main rapidement pour enchainer tout ça


if __name__ == '__main__':
    filenames = sorted(glob.glob('learn/*-*.png'))
    training = load_knowndata(filenames)
    pl.show()
    pl.close()

    # Create a classifier: a support vector classifier
    classifier = svm.SVC(gamma=1e-8)

    # We learn the digits on the first half of the digits
    classifier.fit(training['data'], training['targets'])

    filenames = sorted(glob.glob('data/*.png'))
    unknown = load_unknowndata(filenames)

    filenames = glob.glob('data/*.png')
    filenames = set([os.path.splitext(os.path.basename(fn))[0].split('-')[0] for fn in filenames])

    for filename in filenames:
        print('----')
        print(filename)
        print('----')
        fn = sorted(glob.glob('data/' + filename  + '*.png'))
        unknown = load_unknowndata(fn)
        # Now predict the value of the digit on the second half:
        #expected = digits.target[n_samples / 2:]
        predicted = classifier.predict(unknown['data'])

        result = ''
        for pred, image, name in zip(predicted, unknown['images'], unknown['name']):
            print(pred)
            print(name)
            result += str(pred)

        # Check
        fn = 'pictures/' + filename  + '.png'
        image = skimage.io.imread(fn)
        pl.imshow(image[:150, 56:], cmap=pl.cm.gray_r, interpolation='nearest')
        pl.title('Predicted: %s' % result)
        pl.show()

Le résultat est que sur des images de 150 par 300 pixels, contenant 5 digits chacune, j'ai eu 100% de succès sur un test de 60 images. L'algo est même robuste à un changement de valeur du dernier chiffre significatif de la balance lors de la prise de la photo qui se traduit par une superposition de deux digits sur la photos. L'algo détecte l'une ou l'autre des valeurs qu'un humain verrait.

detection de nombres avec une fluctuation : 24774

detection de nombres avec une fluctuation : 24774

En conclusion, on se retrouve avec un moyen simple de récupérer les valeurs de n'importe quel afficheur et sans effort ni bricolage.

Les lanceurs d'alerte sont des parrhêsiastes

Friday 16 May 2014 à 00:00

Les événements récents qu'on nomme les révélations, ont remis au goût du jour la notion de lanceur d'alerte (whistleblower) qui, selon wikipedia, est une personne ayant pour but de signaler un danger ou un risque, en interpelant les pouvoirs en place et en suscitant la prise de conscience de ses contemporains.

Je souhaite ici établir un lien entre ces lanceurs d'alerte et le parrhêsiaste décrit par Michel Foucault, notamment dans ses cours au Collège de France, juste avant la fin de sa vie. J'ai assez peu vu ce lien être établi alors qu'il me semble tout à fait interessant. Pour Foucault, la parrhêsia consiste à "dire vrai", c'est-à-dire à énoncer une vérité, mais pas seulement.

Foucault distingue plusieurs types de discours.

Le parrhêsiaste bouscule l'ordre établit, l'ordre de la société. Il est alors vu comme celui causant le trouble, fragilisant la démocratie.

Les lanceurs d'alerte sont donc sujets à un dualisme. D'une part, ils sont garants d'une transparence, d'établir la vérité sur ce qu'il est amené à rencontrer comme le souligne Véronique Bonnet :

La question qui se pose est : « Peut-on pour autant renoncer à toute démarche de surveillance ? ». Parce que la démocratie doit pouvoir protéger elle-même des atteintes qui lui seraient faites. Et donc pouvoir avoir les moyens d'interroger le contexte sur ce qu'il en est.
Ici il s'agit d'interroger le seuil. C'est-à-dire le niveau de surveillance. Pourquoi, ici, une référence à Edward Snowden ? Parce que toute cette affaire se réfère à la notion de moyens disproportionnés. Qu'est-ce que qu'un moyen disproportionné ? S'il faut pouvoir avoir idée de ce que fait quiconque, où qu'il soit et à tout moment, la question est la suivante : « Est-ce que les lanceurs d'alerte, qui protègent la démocratie, peuvent éventuellement parler aux journalistes ? » La notion de lanceur d'alerte est une notion qui est investie d'une valeur démocratique très forte, puisque la notion de lanceur d'alerte définit la fonction de celui qui, se référant à une certaine tâche qu'on lui demande, doit pouvoir, dans le registre qui n'est pas celui de son travail immédiat alerter et faire part de contradictions.

Source

D'autre part, les lanceurs d'alerte sont des parrhêsiaste selon Foucault car ils prennent librement la parole pour délivrer une vérité, celle à laquelle il est confronté et au péril de sa vie, contre les dérives du pouvoir des "maitres".

Il serait intéressant de creuser d'avantage la perception du parrhêsiaste et de sa parole par la société ainsi que l'effet réelle de l'énonciation de la vérité.

Ressources :

Fail2ban : bloquer des IP sur certaines URL

Saturday 19 April 2014 à 00:00

Régulièrement, sur mon serveur apache, je recupère des requêtes sur des urls qui n'existent pas et qui sont visiblement destinées à chercher à brute-forcer une authentification.

Puisque fail2ban est installé sur la machine, on peut l'utiliser afin de bannir les IP de ces bots.

On commence par définir un nouveau filtre dans le fichier /etc/fail2ban/filter.d/apache-customurl.conf :

[Definition]
url = myadmin\/scripts\/setup.php|phpMyAdmin\/scripts\/setup.php|pma\/scripts\/setup.php|wordpress\/wp-admin\/|wp-admin\/|wp-login.php|blog\/wp-login.php|administrator\/index.php

failregex = ^<HOST>.*"(GET|POST).\/(%(url)s).*$
ignoreregex =

Ce filtre est activé dans le fichier de configuration de fail2ban /etc/fail2ban/jail.conf :

[apache-customurl]

enabled  = true
port     = http,https
filter   = apache-customurl
logpath  = /var/log/apache*/*access.log
maxretry = 1 
bantime  = 36000

On peut tester que la regexp est correcte avec la commande :

fail2ban-regex /var/log/apache2/www-access.log /etc/fail2ban/filter.d/apache-customurl.conf

Enfin, on relance le service

# debian
service fail2ban reload