Quantcast
Channel: AFPy's Planet
Viewing all 3409 articles
Browse latest View live

[logilab] Compte rendu présentation Salt à Solution Linux

$
0
0

Logilab était à l'édition 2014 de Solutions Linux qui se déroulait au CNIT à Paris. David Douard participait à la table ronde sur les outils libres pour la supervision lors de la session Administration Système, Devops, au cours de laquelle un certain nombre de projets libres ont été mentionnés : nagios, shinken, graphite, ElasticSearch, logstash, munin, saltstack, kibana, centreon, rsyslog.

http://www.logilab.org/file/248048/raw/solutionlinux.png

Suite à des présentations sur OpenLDAP, LXC, btrfs et ElasticSearch David Douard a présenté notre approche agile de l'administration système articulée autour de Salt et en particulier le principe de l'administration système pilotée par les tests (diapos) (Test-Driven Infrastructure).

https://www.logilab.org/file/248098/raw/Screenshot%20from%202014-05-21%2017%3A55%3A35.png

Merci aux organisateurs de Solutions Linux pour cette édition 2014.


[tarek] French Services Hackaton #1

$
0
0

The Mozilla Cloud Services team just got a little bit reorganized and I am now managing a new subteam.

I am very happy about the new subteam I am part of: Alexis, Rémy and myself are the French Cloud Services team. We're still working on projects with other teams of course - but being a team in the same timezone and speaking the same native language helps a lot.

One topic that is owned by our team is building and maintaining tools that help build, deploy & run web services.

Currently, the three most active projects are:

  • Loads -- a distributed load testing tool we use to verify that a given service scales.
  • Circus -- an extensible process supervisor we're using in all our deployments
  • Cornice -- a Python framework on the top of Pyramid that helps you write web services - as opposed to "web pages"

Hackaton #1

Rémy & Alexis are in Rennes, and I am located not far from Dijon - so we're very close to each other. Closer than from our colleagues from the US or Canada. When the team was set, we've decided that we would gather for a couple of days once every month at the Mozilla Paris office to work on tools.

That's what we did this week. And that event is open to the community - we had the pleasure to have Boris coming and working with us during those 2 days.

We worked on the following topics:

  • We released the long overdue Circus 0.11
  • We made the Circus web dashboard compatible with several Circus instances. In other words, you can now supervise processes that are running on several servers from the same dashboard
  • We looked at some improvements in how Cornice publishes its APIs specifications.

But the most important task: we socialized, had fun around food/drinks.

The next hackaton will be sometimes in June. I'll try to announce it so more people can join us.

[logilab] SaltStack Meetup with Thomas Hatch in Paris France

$
0
0

This monday (19th of may 2014), Thomas Hatch was in Paris for dotScale 2014. After presenting SaltStack there (videos will be published at some point), he spent the evening with members of the French SaltStack community during a meetup set up by Logilab at IRILL.

http://www.logilab.org/file/248338/raw/thomas-hatch.png

Here is a list of what we talked about :

  • Since Salt seems to have pushed ZMQ to its limits, SaltStack has been working on RAET (Reliable Asynchronous Event Transport Protocol ), a transport layer based on UDP and elliptic curve cryptography (Dan Berstein's CURVE-255-19) that works more like a stack than a socket and has reliability built in. RAET will be released as an optionnal beta feature in the next Salt release.
  • Folks from Dailymotion bumped into a bug that seems related to high latency networks and the auth_timeout. Updating to the very latest release should fix the issue.
  • Thomas told us about how a dedicated team at SaltStack handles pull requests and another team works on triaging github issues to input them into their internal SCRUM process. There are a lot of duplicate issues and old inactive issues that need attention and clutter the issue tracker. Help will be welcome.
http://www.logilab.org/file/248336/raw/Salt-Logo.png
  • Continuous integration is based on Jenkins and spins up VMs to test pull request. There is work in progress to test multiple clouds, various latencies and loads.
  • For the Docker integration, salt now keeps track of forwarded ports and relevant information about the containers.
  • salt-virt bumped into problems with chroots and timeouts due to ZMQ.
  • Multi-master: the problem lies with syncronisation of data which is sent to minions but also the data that is sent to the masters. Possible solutions to be explored are : the use of gitfs, there is no built-in solution for keys (salt-key has to be run on all masters), mine.send should send the data at both masters, for the jobs cache: one could use an external returner.
  • Thomas talked briefly about ioflo which should bring queuing, data hierarchy and data pub-sub to Salt.
http://www.logilab.org/file/248335/raw/ioflo.png
  • About the rolling release question: versions in Salt are definitely not git snapshots, things get backported into previous versions. No clear definition yet of length of LTS versions.
  • salt-cloud and libcloud : in the next release, libcloud will not be a hard dependency. Some clouds didn't work in libcloud (for example AWS), so these providers got implemented directly in salt-cloud or by using third-party libraries (eg. python-boto).
  • Documentation: a sprint is planned next week. Reference documentation will not be completly revamped, but tutorial content will be added.

Boris Feld showed a demo of vagrant images orchestrated by salt and a web UI to monitor a salt install.

http://www.vagrantup.com/images/logo_vagrant-81478652.png

Thanks again to Thomas Hatch for coming and meeting up with (part of) the community here in France.

[cubicweb] Logilab's roadmap for CubicWeb on May 15th, 2014

$
0
0

The Logilab team holds a roadmap meeting every two months to plan its CubicWeb development effort. Here is the report about the May 15th, 2014 meeting. The previous report posted to the blog was the march 2014 roadmap.

Versions

Version 3.17

This version is stable but old and maintainance will continue only as long as some customers will be willing to pay for it (current is 3.17.15).

Version 3.18

This version is stable and maintained (current is 3.18.4).

Version 3.19

This version was published at the end of April. It includes support for Cross Origin Resource Sharing (CORS) and a heavy refactoring that modifies sessions and sources to lay the path for CubicWeb 4.

For details read the release notes or the list of tickets for CubicWeb 3.19.0.

Version 3.20

This version is under development. It will try to reduce as much as possible the stock of patches in the state "reviewed", "awaiting review" and "in progress". If you have had something in the works that has not been accepted yet, please ready it for 3.20 and get it merged.

It should also include the work done for CWEP-002 (computed attributes and relations) and the merging of Connection and ClientConnection if it happens to be simple enough to get done quickly (in case the removal of dbapi would really help, this merging will wait for 3.21).

For details read list of tickets for CubicWeb 3.20.0.

Version 3.21 (or maybe 4.0?)

Removal of the dbapi and merging of CWEP-003 (adding a FROM clause to RQL).

Cubes

Here is a list of cubes that had versions published over the past two months: accidents, awstats, book, bootstrap, brainomics, cmt, collaboration, condor, container, dataio, expense, faq, file, forge, forum, genomics, geocoding, inlineedit, inventory, keyword, link, mailinglist, mediaplayer, medicalexp, nazcaui, ner, neuroimaging, newsaggregator, processing, questionnaire, rqlcontroller, semnews, signedrequest, squareui, task, testcard, timesheet, tracker, treeview, vcsfile, workorder.

Here are a the new cubes we are pleased to announce:

rqlcontroller receives via a POST a list of RQL queries and executes them. This is a way to build web services.

wsme is helping build a web service API on top of a CubicWeb database.

signedrequest is a simple token based authentication system. This is a way for scripts or callback urls to access an instance without login/pwd information.

relationwidget is a widget usable in forms to edit relationships between objects. It depends on CubicWeb 3.19.

searchui is an experiment on adding blocks to the list of facets that allow building complex RQL queries step by step by clicking with the mouse instead of directly writing the RQL with the keyboard.

ckan is using the REST API of a CKAN data portal to mirror its content.

CWEPs

Here is the status of open CubicWeb Evolution Proposals:

CWEP-0002 is now in good shape and the goal is to have it merged into 3.20. It lacks some documentation and a migration script.

CWEP-0003 has made good progress during the latest sprint, but will need a thorough review before being merged. It will probably not be ready for 3.20 and have to wait for 3.21.

New CWEPs are expected to be written for clarifying the API of the _cw object, supporting persistent sessions and improving the performance of massive imports.

Visual identity

CubicWeb has a new logo that will appear before the end of may on its revamped homepage at http://www.cubicweb.org

Last but not least

As already said on the mailing list, other developers and contributors are more than welcome to share their own goals in order to define a roadmap that best fits everyone's needs.

Logilab's next roadmap meeting will be held at the beginning of july 2014.

[tarek] Data decentralization & Mozilla

$
0
0

The fine folks at the Mozilla Paris office took the opportunity of our presence (Alexis/Remy/myself) to organize a "Meet the Cloud Services French Team" event yesterday night. Among all the discussions we had, one topic came back several times during the evening.

How do we let people using our services, host their data anywhere they want

I built the first Python version of the Firefox Sync server, so I had an answer already - you can tweak your browser configuration to point to your own server.

But self-hosting your Sync server requires quite some knowledge. I provided a Makefile to build the server back in the days, but the amount of work to set everything up was quite important.

And it got bigger with the new Sync version, because we've added dependencies to other services for authentication purposes. Our overall architecture is getting better but self-hosting Firefox Sync is getting harder.

Alexis is quite excited about trying to improve this situation, and suggested building debian packages to make the process easy as in "apt-get install firefox-sync".

There were also discussion around remoteStorage and the more I look at it, the more I feel like a product like Firefox Sync could rely on a remoteStorage server. That would make self-hosting straightforward.

The only thing that's unclear to me yet is if remoteStorage is heavily tied to OAuth or if we can plug our own authentication process. (e.g. Firefox Account tokens)

Another problem I see: it's easy to build client-side applications that directly interacts with a remoteStorage, but sometimes you do have to provide server-side APIs. In that case, I am wondering how convenient it would be for a web service to interact with a 3rd party remoteStorage server on behalf of a user. If both parts are different entities, it's a recipe for technical nightmares.

It feels in any case that those topics are going to be very important for the web in the upcoming months, and that Mozilla needs to play an important role there.

Looking forward to see what we'll do in this area.

Tristan Nitot, who came by during the meeting, has sparkled this discussion and is planning to organize recurrent meetings on the topic at the Paris community space - helped by Claire and Axel. They are also zillions of other cool stuff happening at the Paris space this summer. Like, several meetings per week. I'll try to update this blog post whenever I find a good link to the events list.

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

$
0
0

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 :

  • appliquer un seuil à l'image pour enlever les parasites et passer en binaire. Otsu est intéressant car local, ce qui permet de se prémunir de variation de contraste à grande échelle.
  • connecter les morceaux dans le cas d'un afficheur 7 segments ou d'un seuillage trop agressif.
  • remplir les trous pour qu'il ne soit pas détecté comme objet
  • étiqueter les régions et ne considérer que celles supérieures à une taille critique
  • les extraire

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 :

  • On identifie un sous lot d'images. Dans mon cas, j'ai identifié trois images pour chaque digit (donc 30) et elles sont nommées sous la forme digit-id.png).
  • On entraine l'algorithme sur ces images identifiées.
  • On demande de deviner les images inconnues.

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.

[logilab] Open Legislative Data Conference 2014

$
0
0

I was at the Open Legislative Data Conference on may 28 2014 in Paris, to present a simple demo I worked on since the same event that happened two years ago.

The demo was called "Law is Code Rebooted with CubicWeb". It featured the use of the cubicweb-vcreview component to display the amendments of the hospital law ("loi hospitalière") gathered into a version control system (namely Mercurial).

The basic idea is to compare writing code and writing law, for both are collaborative and distributed writing processes. Could we reuse for the second one the tools developed for the first?

Here are the slides and a few screenshots.

http://www.logilab.org/file/253394/raw/lawiscode1.png

Statistics with queries embedded in report page.

http://www.logilab.org/file/253400/raw/lawiscode2.png

List of amendments.

http://www.logilab.org/file/253396/raw/lawiscode3.png

User comment on an amendment.

While attending the conference, I enjoyed several interesting talks and chats with other participants, including:

  1. the study of co-sponsorship of proposals in the french parliament
  2. data.senat.fr announcing their use of PostgreSQL and JSON.
  3. and last but not least, the great work done by RegardsCitoyens and SciencesPo MediaLab on visualizing the law making process.

Thanks to the organisation team and the other speakers. Hope to see you again!

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

$
0
0

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à !


[afpyro] AFPyro à Lyon - mercredi 25 juin

$
0
0

Un Afpyro aura lieu le mercredi 25 juin à partir de 20h à l’Antre Autre - 11 rue Terme - 69001 Lyon.

Cette soirée aura pour thème Docker avec deux présentations :

  • Docker 101 par Geoffrey Bachelet
  • Docker et Python par Haïkel Guémar

L’Antre Autre est un lieu où nous pouvons discuter autour d’un verre, et, pour ceux qui le souhaitent, prendre un repas.

Pour se rendre à l’Antre Autre :

  • en métro : arrêt Hôtel de Ville
  • en bus : lignes C13 et C18 arrêt Mairie du 1er ou lignes 19, C14 et C3 à l’arrêt Terreaux
  • en vélo’v : stations Place Sathonay, Carmélites Burdeau, Place de la paix

[Biologeek] Écriture et bonheur

$
0
0

Difficile d’écrire sur le bonheur. Sans faire dans le mièvre. Sans tomber dans les clichés. Sans craindre d’attiser les convoitises et jalousies. Sans avoir peur surtout de briser cet instant en tentant de le décrire. Sans mentir.

6 mois de vie. De survie. De co-vie. Je ne sais pas trop comment appeler cela, ni de quel point de vue. Toujours est-il que ça semble fonctionner. Des doutes, des essais, des désespoirs, des soulagements. Des moments spéciaux. Inattendus. Intimes.

Apprendre à flâner avec une poussette. Se défendre des fumées et des bruits de la ville. Réduire son exposition aux écrans. Se nourrir plus sainement. Sourire très souvent. Apprécier ce rythme plus lent qui contribue au bien-être. Et au bonheur.

Songer à cette question de l’héritage. Vouloir léguer des valeurs et une culture plus que des biens. Amasser du temps de vivre ensemble. Donner son attention avant tout. Et sentir qu’il s’agit d’un échange. Réciproque et gratuit.

Rire et s’émerveiller des nouveautés. Se demander qui éduque qui. S’endormir épuisé mais heureux. Se réveiller sur un simple sourire. Apprendre à se connaître, à apprécier des rituels. À cohabiter.

Et au milieu de tout cela des questionnements. Pourquoi est-ce que j’ai choisi de passer autant de temps avec mon fils ? Comment lui transmettre des valeurs sans ressentir la pression de l’exemplarité ? Quel enseignement lui proposer avant qu’il ne puisse choisir par lui-même ? Quels « effets papillons » lui permettront à terme de battre de ses propres ailes ? Quelle image va-t-il me renvoyer de moi-même ? Que ressent-il vraiment ? Et tant d’autres.

Fermer les yeux. Respirer. Faire le vide. Sourire. Se sentir bien.

[logilab] Tester MPI avec CMake et un framework de test comme Boost

$
0
0

Objectif

Compiler et exécuter un fichier de test unitaire avec MPI.

Je suppose que :

  • une implémentation de MPI est installée (nous prendrons OpenMPI)
  • les bibliothèques Boost MPI et Boost Unit Test Framework sont présentes
  • vous connaissez quelques rudiments de CMake

CMake

On utilise le bien connu find_package pour Boost et MPI afin de récupérer tout ce qu'il nous faut pour les headers et les futurs links.

find_package (MPI REQUIRED)
find_package (Boost COMPONENTS mpi REQUIRED)

# Boost dirs for headers and libs.
include_directories (SYSTEM ${Boost_INCLUDE_DIR})
link_directories (${Boost_LIBRARY_DIRS})

Par la suite, on a essentiellement besoin des variables CMake :

  • Boost_MPI_LIBRARY pour le link avec Boost::MPI
  • MPI_CXX_LIBRARIES pour le link avec la bibliothèque OpenMPI
  • MPIEXEC qui nous donne la commande pour lancer un exécutable via MPI

On prend un fichier example_mpi.cpp (des exemples simples sont faciles à trouver sur la Toile). Pour le compiler, on fait :

set(CMAKE_CXX_COMPILER mpicxx)

# MPI example.
add_executable(example_mpi example_mpi.cpp)
target_link_libraries(example_mpi ${MPI_CXX_LIBRARIES})

voire juste

target_link_libraries(example_mpi ${Boost_MPI_LIBRARY})

si on décide d'utiliser la bibliothèque Boost MPI.

Note

mpicxx est une commande qui enrobe le compilateur (g++ par exemple). On dit à CMake d'utiliser mpicxx au lieu du compilo par défaut.

Note

Boost::MPI n'est pas une implémentation de MPI. C'est une bibliothèque plus haut niveau qui s'abstrait de l'implémentation de MPI. Il faut nécessairement en installer une (OpenMPI, LAM/MPI, MPICH2, ...).

L'exemple peut très simplement ressembler à :

#include <boost/mpi/environment.hpp>
#include <boost/mpi/communicator.hpp>
#include <iostream>

namespace mpi = boost::mpi;

int main()
{
  mpi::environment env;
  mpi::communicator world;
  std::cout << "I am process " << world.rank() << " of " << world.size()
            << "." << std::endl;
  return 0;
}

Exécuter

Une fois la compilation effectuée, faire simplement :

> mpiexec -np 4 ./example_mpi

pour lancer l'exécutable sur 4 cœurs.

Problème : mais pourquoi tu testes ?

On veut pouvoir faire des exécutables qui soient de vrais tests unitaires et non pas un exemple avec juste une fonction main. De plus, comme j'utilise CMake, je veux pouvoir automatiser le lancement de tous mes exécutables via CTest.

Problème : il faut bien initialiser et bien dire à MPI que j'en ai fini avec toutes mes MPI-series.

En "vrai" MPI, on a :

int main(int argc, char* argv[])
{
  MPI_Init(&argc, &argv);

  int rank;
  MPI_Comm_rank(MPI_COMM_WORLD, &rank);
  // Code here..
  // ... and here
  MPI_Finalize();

  return 0;
}

Note

C'est ce que fait le constructeur/destructeur de boost::mpi::environment.

En d'autres termes, je veux me faire ma propre fonction main pour l'initialisation de tous mes cas tests Boost (ou avec n'importe quel autre framework de test unitaire C/C++).

La documentation de Boost Unit Test, que je trouve parfois très peu claire avec un manque cruel d'exemple, m'a fait galérer quelques heures avant de trouver quelque chose de simple qui fonctionne.

Conseil : aller regarder les exemples des sources en faisant quelques grep est parfois plus efficace que de trouver la bonne info dans la doc en ligne. On peut aussi en lire sur https://github.com/boostorg/test/tree/master/example

Deux solutions :

  1. la première que j'ai trouvée dans les tests de Boost::MPI lui-même. Ils utilisent le minimal testing facility. Mais seule la macro BOOST_CHECK est utilisable. Et oubliez les BOOST_CHECK_EQUAL ainsi que l'enregistrement automatique de vos tests dans la suite de tests.
  2. la deuxième redéfinit la fonction main et appelle boost::unit_test::unit_test_main sans définir ni la macro BOOST_TEST_MODULE ni BOOST_TEST_MAIN qui impliquent la génération automatique de la fonction main par le framework de test (que l'on compile en statique ou dynamique). Pour plus de détails, lire http://www.boost.org/doc/libs/release/libs/test/doc/html/utf/user-guide/test-runners.html

J'utiliserai la deuxième solution.

Note

Ne pensez même pas faire une fixture Boost pour y mettre votre boost::mpi::environment puisque cette dernière sera construite/détruite pour chaque cas test (équivalent du setUp/tearDown). Et il est fort probable que vous ayez ce genre d'erreur :

*** The MPI_Errhandler_set() function was called after MPI_FINALIZE was invoked.
*** This is disallowed by the MPI standard.
*** Your MPI job will now abort.
[hostname:11843] Abort after MPI_FINALIZE completed successfully; not able to guarantee that all other processes were killed!

Un exemple qui marche

On souhaite ici tester que le nombre de procs passés en argument de mpiexec est au moins 2.

Le BOOST_TEST_DYN_LINK dit juste que je vais me lier dynamiquement à Boost::Test.

#define BOOST_TEST_DYN_LINK
#include <boost/test/unit_test.hpp>

#include <boost/mpi/environment.hpp>
#include <boost/mpi/communicator.hpp>

namespace mpi = boost::mpi;

BOOST_AUTO_TEST_CASE(test_check_world_size)
{
    mpi::communicator world;
    BOOST_CHECK(world.size() > 1);
}


// (empty) Initialization function. Can't use testing tools here.
bool init_function()
{
    return true;
}

int main(int argc, char* argv[])
{
    mpi::environment env(argc, argv);
    return ::boost::unit_test::unit_test_main( &init_function, argc, argv );
}

On lance tout ça avec un joli mpiexec -np 2 ./test_mpi et on est (presque) content.

Et un peu de CTest pour finir

Une dernière chose : on aimerait dire à CMake via CTest de lancer cet exécutable, mais avec mpiexec et les arguments qui vont bien.

Un cmake --help-command add_test nous indique qu'il est possible de lancer n'importe quel exécutable avec un nombre variable d'arguments. On va donc lui passer : /usr/bin/mpiexec -np NB_PROC ./test_mpi.

Un œil au module FindMPI.cmake nous dit aussi qu'on peut utiliser d'autres variables CMake MPI pour ce genre de chose.

Reprenons donc notre fichier CMake et ajoutons :

add_executable(test_mpi test_mpi.cpp)
target_link_libraries(test_mpi ${Boost_MPI_LIBRARY})
# Number of procs for MPI.
set (PROCS 2)
# Command to launch by CTest. Need the MPI executable and the number of procs.
add_test (test_mpi ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${PROCS}
   ${MPIEXEC_PREFLAGS}
   test_mpi
   ${MPIEXEC_POSTFLAGS})

où mon exécutable et mon test porte le même nom : test_mpi. On lance le tout avec la commande ctest dans notre dossier de build et hop, le tour est joué !

[Biologeek] Éducation et informatique

$
0
0

Moins les enfants regarderont les écrans, plus ils développeront leur imagination et leur créativité. Car les écrans contiennent en eux des histoires visuelles et sonores qui empêcheraient les enfants d’imaginer d’autres images et d’autres mondes possibles.

Écran global, reportage de Anne-Sophie Lévy-Chambon

Protéger son enfant des écrans s’avère être une tâche plus difficile que ce que j’imaginais. Le compteur en cumulé doit être aux alentours des 10-15 minutes après 7 mois, ce qui est plutôt dans la limite haute de ce que je m’étais fixé. Et lorsque je vois l’attrait qu’il a pour un écran dès qu’il en croise un, cela me conforte dans l’idée qu’il va falloir attendre qu’il soit en mesure de comprendre un peu ce qu’il y a derrière : une fenêtre déshumanisée sur l’Humanité.

On parle souvent de la limite des 3 ans pour qu’un enfant puisse regarder/interagir avec un écran sans être perturbé par l’absence de retour de sa part. On verra à ce moment là si mon digital native souhaite faire usage de ses doigts.

L’élève sait que les équipements informatiques utilisent une information codée et il est initié au fonctionnement, au processus et aux règles des langages informatiques ; il est capable de réaliser de petites applications utilisant des algorithmes simples.

Socle commun de connaissances, de compétences et de culture

Damien B et Éric D. discutaient ce soir de l’apprentissage du code en primaire sur Twitter. J’ai beaucoup de mal avec cette question car le code constitue une part non négligeable de mon quotidien et de mon métier. Et si je trouve que la connaissance du Web devrait faire partie de l’éducation citoyenne, je suis plus circonspect sur le développement en lui-même. En fait, il s’agit d’une problématique de cohérence. Soit on considère que le codage (sic) est une activité artisanale et dans ce cas il faudrait également enseigner d’autres activités comme le travail du bois en primaire (ce que certaines écoles alternatives font — voir le reportage sus-cité). Soit on considère que l’industrialisation de l’informatique est inévitable et dans ce cas là toute la question algorithmique simple devient inutile puisqu’il s’agira d’empiler les boîtes noires à un autre niveau. Il faut bien définir les objectifs que l’on se fixe avec cet apprentissage du code dès la primaire. Dans quelle mesure permet-il la poursuite d’études, la construction d’un avenir personnel et professionnel et préparer à l’exercice de la citoyenneté ? Tous bidouilleurs, soit, mais que nous laisse-t-on bidouiller par la suite ?

Ce que je vois en filigrane de cette mutation est plus grave, c’est la mise sur un piédestal de ceux qui savent coder par pure incompréhension du changement de paradigme qui est en train de s’effectuer avec le numérique. Ou serait-ce pour pouvoir à terme alimenter plus facilement les canons publicitaires ? La question reste ouverte… jusqu’à la prochaine réforme.

[tshirtman] Using tex_coords in kivy for fun and profit

$
0
0

Your gpu is an awesome thing, it can do all kind of calculations, very fast. For example, instead of telling it exactly how you want each pixels to be, you can throw textures and vertices at it, and have it correctly deduce how this things will have to look.

To do this, though, you must tell it what part of the texture will be stuck to each vertice. This is usually denoted using texture coordinates.

Texture coordinates, like usual coordinates, indicate the place of something. Instead of noting them x and y, they are often called u and v, which allows to clearly note what parts of an equation relate to each.

While kivy offers you high level canvas instructions, it gives you a pretty good access to lower level features, and you can actually manipulate the texture coordinates of your Rectangle instructions. This allow for cheap zooming, repeat-scrolling, and other image manipulations, since your gpu is doing all the actual work.

tex_coords = u, v, u + w, v, u + w, v + h, u, v + h

which is better understood as:

tex_coords = [
    u,     v,
    u + w, v,
    u + w, v + h,
    u,     v + h
]

considering a default tex_coords value, where u and v = 0, and w and h = 1 (all values are relatives, so usually between 0 and 1).

u, v + h-------u + w, v + h
|              |
u, v-----------u + w, v

which means the 4 angles of your texture, will be on the 4 angles of your rectangle, how dull!

One way to play with different values, is to create a little app showing you the effect of deformations.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.widget import Widget
from kivy.properties import ObjectProperty, ListProperty
from kivy.core.image import Image as CoreImage

kv = '''
#:import chain itertools.chain
RootWidget:
    canvas:
        Color:
            rgba: 1, 1, 1, 1
        Rectangle:
            pos: root.pos
            size: root.size
            texture: app.texture
            # here is our usage of the calculated texture coordinates
            # we devide by 100 because we took the input with a 100x100
            # rectangle as default value
            tex_coords: [x / 100. for x in chain(*root.points)]

        PushMatrix
        # translate the rectangle to make it easier to play with
        Translate:
            xy: root.width / 2, root.height / 2

        Color:
            rgba: 1, 0, 0, .5
        Line:
            points: chain(*root.points + root.points[:1])
            width: 2
        PopMatrix
'''


def dist(point, pos):
    return ((point[0] - pos[0]) ** 2 + (point[1] - pos[1]) ** 2)
    # ** .5 # who cares about square root here? it doesn't change the order


class RootWidget(Widget):
    # the default values, a 100x100 square, displayed around the middle of the screen
    points = ListProperty([[0, 0], [100, 0], [100, 100], [0, 100]])

    def on_touch_down(self, touch, *args):
        # compensate the translation done in canvas
        pos = touch.pos[0] - self.width / 2, touch.pos[1] - self.height / 2

        touch.ud['point'] = min(
            range(4), key=lambda x: dist(self.points[x], pos))

    def on_touch_move(self, touch, *args):
        # compensate the translation done in canvas
        pos = touch.pos[0] - self.width / 2, touch.pos[1] - self.height / 2
        # update the point
        self.points[touch.ud['point']] = pos


class TexCoordsApp(App):
    texture = ObjectProperty(None)

    def build(self):
        self.root = Builder.load_string(kv)
        self.texture = CoreImage.load(
            'GrassGreenTexture0002.jpg').texture
        self.texture.wrap = 'repeat'
        return self.root


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

The texture have been scrapped on the web and is not very interesting, but you can find it here.

Of course, this is much more a learning device (helful because these transformation are not quite straighforward for our brains) than a practical application, but a lot more can be done.

Here is, for example, a little infinite scroll app uting this concept.

edit: Ben Rousch kindly created apks out of the two examples, if you want to try them on android: TexCoordsExample ScrollExample

[tarek] Decentralization Mailing List

$
0
0

This is long time due !

Following up with my previous blog post I have started a Decentralization Mailing list.

The goal of this mailing list is to discuss technologies related to data decentralization in Mozilla projects - and in particular in the ones we work at in the Cloud Services team (but it can go beyond of course)

A few topic examples that people brought up:

  1. Could remoteStorage be used with Firefox Account to build Firefox OS apps where people can store their own data ?
  2. Can Firefox Sync use a remoteStorage backend ?
  3. Why can't I run a simple "apt-get install firefox-sync" and host my own server ?
  4. What about peer-to-peer ? How does that fit with the services we provide ?

The list is at : https://lists.mozilla.org/listinfo/dev-decentralization

I will wait a week or so to launch topics there, but if you are interested in this topic, if you want to lurk or to launch an idea - please join.

On my side, the first topic I will probably talk about in this ML is #1.

[tarek] NGinxTest

$
0
0

I've been playing with Lua and Nginx lately, using the OpenResty bundle.

This bundle is an Nginx distribution on steroids, that includes some extensions and in particular the HTTPLuaModule which let you script Nginx using the Lua programming language.

Coming from a Python background, I was quite pleased with the Lua syntax, which feels like a cleaner Javascript inspired from Pascal and Python - if that even makes any sense :)

Here's how a Lua function look like:

-- rate limiting
function rate_limit(remote_ip, stats)
    local hits = stats:get(remote_ip)
    if hits == nil then
        stats:set(remote_ip, 1, throttle_time)
        return false
    end

    hits = hits + 1
    stats:set(remote_ip, hits, throttle_time)
    return hits >= max_hits
end

Peformance-wise, interacting with incoming web requests in Lua co-routines in Nginx is blazing fast. And there are a lot of work that can be done there to spare your proxied Python/Node.js/Go/Whatever application some cycles and complexity.

It can also help you standardize and reuse good practices across all your web apps no matter what language/framework they use.

Some things that can be done there:

  • web application firewalling
  • caching
  • dynamic routing
  • logging, load balancing
  • a ton of other pre-work...

To put it simply:

Nginx become very easy to extend with Lua scripting without having to re-compile it all the time, and Lua lowers the barrier for ops and developers to implement new server behaviors.

Testing with Test::Nginx

When you start to add some Lua scripting in your Nginx environment, testing soon become mandatory. Pure unit testing Lua scripts in that context is quite hard because you are interacting with Nginx variables and functions.

The other approach is doing only pure functional tests by launching Nginx with the Lua script loaded, and interacting with the server using an HTTP client.

OpenResty offers a Perl tool to do this, called Test::Nginx where you can describe in a light DSL an interaction with the NGinx server.

Example from the documentation:

# foo.t
use Test::Nginx::Socket;
repeat_each(1);
plan tests => 2 * repeat_each() * blocks();
$ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211;  # make this env take a default value
run_tests();

__DATA__

=== TEST 1: sanity
--- config
location /foo {
    set $memc_cmd set;
    set $memc_key foo;
    set $memc_value bar;
    memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT;
}
--- request
    GET /foo
--- response_body_like: STORED
--- error_code: 201

The data section of the Perl script describes the Nginx configuration, the request made and the expected response body and status code.

For simple tests it's quite handy, but as soon as you want to do more complex tests it becomes hard to use the DSL. In my case I needed to run a series of requests in a precise timing to test my rate limiting script.

I missed my usual tools like WebTest, where you can write plain Python to interact with a web server.

Testing with NginxTest

Starting and stopping an Nginx server with a specific configuration loaded is not hard, so I started a small project in Python in order to be able to write my tests using WebTest.

It's called NGinxTest and has no ambitions to provide all the features the Perl tool provides, but is good enough to write complex scenarios in WebTest or whatever Python HTTP client you want in a Python unit test class.

The project provides a NginxServer class that takes care of driving an Nginx server.

Here's a full example of a test using it:

import os
import unittest
import time

from webtest import TestApp
from nginxtest.server import NginxServer

LIBDIR = os.path.normpath(os.path.join(os.path.dirname(__file__),
                        '..', 'lib'))
LUA_SCRIPT = os.path.join(LIBDIR, 'rate_limit.lua')

_HTTP_OPTIONS = """\
lua_package_path "%s/?.lua;;";
lua_shared_dict stats 100k;
""" % LIBDIR


_SERVER_OPTIONS = """\
set $max_hits 4;
set $throttle_time 0.3;
access_by_lua_file '%s/rate_limit.lua';
""" % LIBDIR


class TestMyNginx(unittest.TestCase):

    def setUp(self):
        hello = {'path': '/hello', 'definition': 'echo "hello";'}
        self.nginx = NginxServer(locations=[hello],
                                http_options=_HTTP_OPTIONS,
                                server_options=_SERVER_OPTIONS)
        self.nginx.start()
        self.app = TestApp(self.nginx.root_url, lint=True)

    def tearDown(self):
        self.nginx.stop()

    def test_rate(self):
        # the 3rd call should be returning a 429
        self.app.get('/hello', status=200)
        self.app.get('/hello', status=200)
        self.app.get('/hello', status=404)

    def test_rate2(self):
        # the 3rd call should be returning a 200
        # because the blacklist is ttled
        self.app.get('/hello', status=200)
        self.app.get('/hello', status=200)
        time.sleep(.4)
        self.app.get('/hello', status=200)

Like the Perl script, you provide bits of configuration for your Nginx server -- in this case pointing the Lua script to test and some general configuration.

Then I test my rate limiting feature using Nose:

$ bin/nosetests -sv tests/test_rate_limit.py
test_rate (test_rate_limit.TestMyNginx) ... ok
test_rate2 (test_rate_limit.TestMyNginx) ... ok

----------------------------------------------------------------------
Ran 2 tests in 1.196s

OK

Out of the 1.2 seconds, the test sleeps half a second, and the class starts and stops a full Nginx server twice. Not too bad!

I have not released that project at PyPI - but if you think it's useful to you and if you want some more features in it, let me know!


[logilab] Nazca notebooks

[cubicweb] CubicWeb roadmap meeting on July 3rd, 2014

$
0
0

The Logilab team holds a roadmap meeting every two months to plan its CubicWeb development effort. The previous roadmap meeting was in May 2014.

Here is the report about the July 3rd, 2014 meeting. Christophe de Vienne (Unlish) and Dimitri Papadopoulos (CEA) joined us to express their concerns and discuss the future of CubicWeb.

Versions

Version 3.17

This version is stable but old and maintainance will continue only as long as some customers will be willing to pay for it (current is 3.17.15 with 3.17.16 in development).

Version 3.18

This version is stable and maintained (current is 3.18.5 with 3.18.6 in development).

Version 3.19

This version was published at the end of April and has now been tested on our internal servers. It includes support for Cross Origin Resource Sharing (CORS) and a heavy refactoring that modifies sessions and sources to lay the path for CubicWeb 4.

For details read the release notes or the list of tickets for CubicWeb 3.19.0. Current is 3.19.2

Version 3.20

This version is under development. It will try to reduce as much as possible the stock of patches in the state "reviewed", "awaiting review" and "in progress". If you have had something in the works that has not been accepted yet, please ready it for 3.20 and get it merged.

It should still include the work done for CWEP-002 (computed attributes and relations.

For details read list of tickets for CubicWeb 3.20.0.

Version 3.21 (or maybe 4.0?)

Removal of the dbapi, merging of Connection and ClientConnection, CWEP-003 (adding a FROM clause to RQL).

Cubes

Cubes published over the past two months

New cubes

  • cubicweb-frbr: Cube providing a schema based on FRBR entities
  • cubicweb-clinipath
  • cubicweb-fastimport

CWEPs

Here is the status of open CubicWeb Evolution Proposals:

CWEP-0002 only missing a bit of migration support, to be finished soon for inclusion in 3.20.

CWEP-0003 has been reviewed and is waiting for a bit of reshaping that should occurs soon. It's targeted for 3.21.

New CWEPs are expected to be written for clarifying the API of the _cw object, supporting persistent sessions and improving the performance of massive imports.

Work in progress

Design

The new logo is now published in the 3.19 line. David showed us his experimentation that modernize a forge's ui with a bit of CSS. There is still a bit of pressure on the bootstrap side though, as it still rely on heavy monkey-patching in the cubicweb-bootstrap cube.

Data import

Also, Dimitry expressed is concerns with the lack of proper data import API. We should soon have some feedback from Aurelien's cubicweb-fastimport experimentation, which may be an answer to Dimitry's need. In the end, we somewhat agreed that there were different needs (eg massive-no-consistency import vs not-so-big-but-still-safe), that cubicweb.dataimport was an attempt to answer them all and then cubicweb-dataio and cubicweb-fastimport were more specific responses. In the end we may reasonably hope that an API will emerge.

Removals

On his way to persistent sessions, Aurélien made a huge progress toward silence of warnings in the 3.19 tests. dbapi has been removed, ClientConnection / Connection merged. We decided to take some time to think about the recurring task management as it is related to other tricky topics (application / instance configuration) and it's not directly related to persistent session.

Rebasing on Pyramid

Last but not least, Christophe demonstrated that CubicWeb could basically live with Pyramid. This experimentation will be pursued as it sounds very promising to get the good parts from the two framework.

Agenda

Logilab's next roadmap meeting will be held at the beginning of september 2014 and Christophe and Dimitri were invited.

[logilab] PyLint sprint during EuroPython in Berlin

$
0
0

The three main maintainers/developpers of Pylint/astroid (Claudiu, Torsten and I) will meet together in Berlin during EuroPython 2014. While this is not an "official" EP sprint but it's still worth announcing it since it's a good opportunity to meet and enhance Pylint. We should find place and time to work on Pylint between wednesday 23 and friday 25.

If you're interested, don't hesitate to contact me (sylvain.thenault@logilab.fr / @sythenault).

[gvaroquaux] Scikit-learn 0.15 release: lots of speedups!

$
0
0
We have just released the 0.15 version of scikit-learn. Hurray!! Thanks to all involved. A long development stretch It’s been a while since the last release of scikit-learn. So a lot has happened. Exactly 2611 commits according my count. Quite clearly, we have more and more existing code, more and more features to support. This means that when [...]

[Biologeek] Lecture et joie

$
0
0

Je ne milite en aucun cas pour l’abolition de l’école. Je crois, effectivement, qu’il serait catastrophique, en l’état actuel des choses, de la supprimer. Je pense, effectivement, qu’il y a de nombreux parents qui ne pourraient, ne voudraient ou ne sauraient en aucun cas assumer cette nouvelle condition ; je pense, effectivement, que la situation actuelle de nombreuses personnes rendrait irréaliste, voire périlleuse, la non-scolarisation de leurs enfants.

Mais quid de tous ceux qui en ont la possibilité et, peut-être, le désir, mais qui l’ignorent ? C’est à eux que mon témoignage offre, je l’espère, l’inspiration pour une pensée nouvelle. Respecter ses convictions, faire, en toute conscience, des choix personnels, honorer son originalité, être l’artisan de son propre devenir : tout cela, bien davantage que l’endoctrinement des masses, contribue à la progression du monde et à l’apparition de nouveaux paradigmes.

… et je ne suis jamais allé à l’école, André Stern

J’ai terminé de lire … et je ne suis jamais allé à l’école à mon fils. Quelques pages quasiment chaque jour depuis 3 mois. La joie qu’il a manifesté lorsque j’ai sorti le livre les dernières fois m’a vraiment ému. Identifier le livre comme un moment de bonheur. Comme une vaine tentative de le dévorer littéralement aussi :-).

Au fil des pages, je me suis rendu compte à quel point ma lecture à voix haute a changée, une meilleure continuité, une meilleure gestion des liaisons, des différentes voix des personnages. Essayer de rendre la forme aussi intéressante que le fond a été un exercice quotidien, pour une lecture plus lente, plus détaillée, plus profonde. La prose d’André Stern se prête bien à une telle lecture et le propos est lucide et sensé, ça donne des idées…

Prochaine lecture : Libres enfants de Summerhill.

Viewing all 3409 articles
Browse latest View live