Microblog: A very long article Wikipedia article on the orientation of toilet paper [Jun 7th, 22:52] [R]

Wednesday, April 20th, 2022

Nerdle Solver

Categories: [ IT ]

There was Mastermind_(board_game) where you have to guess the correct colors in the correct order. Then came Wordle, where you have to guess the correct letters in the correct order to make an actual word. And then came Nerdle where you have to guess the correct arithmetic identity, using digits from 0 to 9, the four usual operations and the “equal” sign.

That was fun the couple of first times, but then came the itch to write a solver for it (like I did a long time ago with the Sudoku: same kind of repetitive puzzle, same geek reflex).

$ python3 nerdle.py
([c]orrect [i]ncorrect [a]bsent
8+7*6=50 > iiaaaiai
10+2-4=8 > cccacaci
10+1-8=3 > cccccccc

The user interface is trivial: the solver prompts a solution, type it in the Nerdle web interface, then type the colored hints back into the solver: green is “c” for Correct, purple is “i” for Incorrect and black is “a” for Absent (as indicated by the solver when you start it).

It always starts with the same first attempt, that is composed of 8 of the 15 possible symbols. Then based on the hints, it computes possible next solutions and ranks them based on the diversity of the symbols it contains and the amount of yet-unused ones (assuming that the actual solution contains as wide a variety of symbols as possible, and that unused symbols need to be tried).

One thing I liked when coding this little tool is that I got to use sets a lot, and implement a lot of the logic using set operations.

[ Posted on April 20th, 2022 at 14:01 | no comment | ]

Monday, April 11th, 2022

Pâte à pizza

Translation: [ Google | Babelfish ]

Categories: [ Cooking ]

Proportions de base pour la pâte à pizza:

  • 250 g farine
  • 150 g eau à 43 °C
  • 4 g sel
  • 1,5 c. à café levure sèche
  • 3 c. à soupe huile d'olive

Ce qui fait 440g de pâte en tout. D'après tonton-pizza il faut un pâton de 230 g pour une pizza de 29 cm, ça tombe bien c'est juste adapté à la taille de la pelle à pizza. Donc on peut faire 2 pizza (à 4% près) avec cette quantité de pâte.

Mais comme je ne sais apparemment pas calculer la quantité de pâte pour faire 5 pizzas (je me suis retrouvé samedi dernier avec exactement la quantité nécessaire pour en faire 3), voilà une liste des quantités en fonction du nombre de pizzas.

1 pizza

  • 125 g farine
  • 75 g eau
  • 2 g sel
  • 3/4 c. à café levure sèche
  • 1,5 c. à soupe huile d'olive

2 pizzas

  • 250 g farine
  • 150 g eau
  • 4 g sel
  • 1,5 c. à café levure sèche
  • 3 c. à soupe huile d'olive

3 pizzas

  • 375 g farine
  • 225 g eau
  • 6 g sel
  • 2,25 c. à café levure sèche
  • 4,5 c. à soupe huile d'olive

4 pizzas

  • 500 g farine
  • 300 g eau
  • 8 g sel
  • 3 c. à café levure sèche
  • 6 c. à soupe huile d'olive

5 pizzas

  • 625 g farine
  • 375 g eau
  • 10 g sel
  • 3,75 c. à café levure sèche
  • 7,5 c. à soupe huile d'olive

6 pizzas

  • 750 g farine
  • 450 g eau
  • 12 g sel
  • 4,5 c. à café levure sèche
  • 9 c. à soupe huile d'olive

7 pizzas

  • 875 g farine
  • 525 g eau
  • 14 g sel
  • 5,25 c. à café levure sèche
  • 10,5 c. à soupe huile d'olive

[ Posted on April 11th, 2022 at 11:20 | no comment | ]

Sunday, November 14th, 2021

New Leffakone Infrared Receiver

Categories: [ DIY/Arduino | TV/Leffakone ]

Several months ago, the leffakone infrared receiver started to misbehave. There were a lot of errors in syslog about spikes in the signal, and the problem seemed to come from the serial port on the motherboard rather than from the homebrew IR receiver, connected to the serial port, that I had built in 2002 or 2003 and that I had been using with lirc ever since. One of the symptoms was that unloading the lirc-serial kernel module caused the computer to freeze, while testing the receiver with an oscilloscope seemed to show that it was working correctly. For many months, I was too lazy to do something about it, as using a keyboard with a long enough cord was enough to control leffakone. During the last autumn vacation, I tried to test the receiver with the serial port on minikone, but the latter seems to deliver only 1.2V signals, when the receiver expects at least 7V to power its onboard voltage regulator. So that was not very conclusive.


At the same time, I had the idea of building a CO2 monitor using a Jeenode I had lying around, and somehow I wondered if the IR receiver module would not just fit into one of the Jeenode's ports. Guess what? It fits perfectly, allowing to use the IRQ pin as the input, which is exactly what the Arduino-IRremote library suggests to use. Writing the software was a bit of a headache, because the library assumes that the compiler would run with the -flto option (and the compilation ends with an error if it is not set), but my custom Makefile somehow fails to compile the code correctly if I enable that option. Thankfully, you can get around the problem with #define SUPPRESS_ERROR_MESSAGE_FOR_BEGIN. After that, the program is quite straightforward: configure it for the RC5 protocol (as this is what my remote control produces), read a code and write it to the serial port if it matches the RC5 address. I also added a new feature: if the code is the power on button, it would set a pin to HIGH for a short while, allowing to switch the computer on. I used the Jeenode USB as it has an on-board USB-to-serial adatpter, which makes it perfect to connect to a modern computer. I had one reed relay left from the timer and despite being rated for a 5V control voltage, it works with the Jeenode's 3.3V signal. The Jeenode is connected to the computer with a USB cable where I have replaced the USB Type A connector with a Molex connector so that I can use one of USB headers on the motherboard. Crimping the very small contacts was difficult as I don't have a crimping tool, but the connections seem to be working despite having done quite a poor job of it.

Yesterday, I installed the extra reed relay and the Jeenode onto the PCB that holds the relays of the timer, and now it's inside leffakone and working well. And since I forgot to take a picture, there is no image of what it looks like. In addition, I'm quite happy I have been able to do this project by using only bits and pieces I already had (the Jeenode, the headers, the reed relay, the IR receiver module, the USB cable, the Molex contacts and housing).

[ Posted on November 14th, 2021 at 11:56 | no comment | ]

Tuesday, November 2nd, 2021

CO2 Meter

Categories: [ DIY/Arduino ]


The last issue of Make: magazine had an article about building a CO2 monitor. The concentration of CO2 is apparently a good estimator for the concentration of SARS-CoV-2 in the air, which is correlated to the risk of transmission. The article suggests keeping the concentration of CO2 under 800 ppm when people are wearing masks.

The Make: article proposes a green-yellow-red light indicator, where the light is green when the concentration is below 1000 ppm, yellow above that level and red when it goes above 2000 ppm. These values are rather about indoors air quality and not directly related to limiting the transmission of SARS-CoV-2.

An article from NIST however indicates that the 1000 ppm limit has no basis whatsoever so I decided to use the 800 ppm limit instead and a rather arbitrary limit of 1300 ppm based on looking at Figure 2 in this article which seems to indicate that some cognitive abilities drop around that concentration.



The device itself is quite simple: it has an on/off switch and a single two-color LED indicator, red and green. The yellow color is obtained by turning on both green and red colors at the same time. It also has a small hole where a paperclip can be inserted for triggering the calibration procedure. The holes next to the power switch expose the CO2 sensor and its temperature/humidity sensor.

Inside the box there is an Arduino-like Jeenode I had lying around with an AA Power board (I have no idea if these are still sold, I've had them around for over ten years). The AA Power board is meant for a single AA battery, but it cannot provide enough current, so I removed the battery clips and connected it to a 2-AA battery holder, via the switch. It seems to be working well with two NiMH rechargeable batteries.

The CO2 sensor is a Sensirion SCD30. It is quite expensive (about 50 EUR), but has an easy to use I2C interface and is the most accurate of the sensors presented in the Make: article. The software is quite trivial (if you except the calibration procedure, see below), and available here. The device automatically makes a measurement ever two seconds, the program reads it and updates the LED accordingly. That's it.


There is an automatic calibration procedure that requires to keep the sensor powered for at least 7 days and put it in fresh (outdoors) air at least an hour per day. This is not very practical given that I've estimated that the batteries would last about 25 hours (20 mA for the ATmega, 17 mA for the SCD30, 10 mA for the LED at 3.3 V, with maybe a 80% efficiency for the power board with two 1900 mAh batteries at 2.4 V). There is however also the possibility to expose the sensor to air with a known CO2 concentration and tell it the actual value it is measuring. It then uses this value as a reference point for subsequent measurements. The device is apparently sensitive to changes in shape (e.g. when subjected to mechanical stress during transport) so as a portable device it probably needs to be regularly re-calibrated.

The calibration procedure is simple: place the device outdoors, wait about 2 minutes for it to settle, then introduce a paperclip into the hole and press the button underneath it; the LED will be flashing red. The device will take repeated measurements every 2 seconds, and when a succession of 10 measurements is considered stable enough (i.e. the absolute value of the difference between the first and last of those is at most 1, and the sum of the absolute values of each measurement with its previous one is at most 10), it sets the calibration value to 416 ppm (which seems to be about the average value in 2021). As the device is accurate to +/- 30 ppm, the exact value does not matter so much. The device then returns to its normal operation mode, showing a green, yellow or red light.

What next?

Put a sticker on it with some indications about the LED colors and the reset button hole.

[ Posted on November 2nd, 2021 at 23:22 | 2 comments | ]

Thursday, September 30th, 2021

Hot Sauce

Categories: [ Cooking ]

As I cannot find any Blair's Original Death Sauce in shops, I decided to reverse-engineer it, from memory. This is the result.


  • 25 g carrot
  • 11 g freshly grated garlic
  • 3.5 g fresh coriander leaves
  • 5 g mineral salt (Seltin)
  • 1.5 g powdered paprika
  • 0.2 g smoked paprika
  • 4 g sugar
  • 1.5 g powdered onion
  • 0.5 g dried parsley
  • 10 g tomato concentrate
  • 15 mL lemon juice
  • 150 mL Poppamies Habanero sauce (1 bottle)
  • water


  • 1 very small saucepan and matching kitchen stove
  • 1 0.1 g-precision scale
  • assorted measuring spoons (15 mL , 50 mL)
  • 1 clean and empty habanero sauce bottle (or another container for the leftover sauce).
  • 1 clean small funnel that fits into the bottle
  • 1 electric kettle


Chop the carrot finely, rinse and chop the coriander leaves.

In a very small saucepan, boil 1/2 dL water and add the carrots. Let it boil, then add the garlic, coriander, salt, paprika, smoked paprika, sugar, powdered onion and dried parsley. Let boil until the carrots are soft, stirring from time to time (keep an eye on it, it takes only a few minutes). If the water evaporates too quickly, just add more water and let it boil again. Then add the tomato concentrate, lemon juice and habanero sauce and boil once more.

Blend the preparation with an immersion blender until smooth and set aside.

Rinse the empty habanero sauce bottle, and preferably get a second bottle, as there will be more than 150 mL sauce.

Boil water in a kettle and use it immediately to fill the bottles and the screw caps to sterilize them. Also pour some of that water in and outside of a small funnel. Estimate the thickness of the sauce, add boiled water and stir to make it thinner. I added 30 mL water, maybe 20 mL would have been enough? Then empty the bottles (careful, they are hot) and pour the sauce into them with the funnel. Put the screw caps and store in the refrigerator. I use on such batch over several months, it seems to be keeping well.

[ Posted on September 30th, 2021 at 22:47 | no comment | ]

Sunday, April 11th, 2021

OpenARC with Postfix on Debian 10 (buster)

Categories: [ IT ]

Gmail started to complain recently that the messages forwarded by the trivial mailing-list hosted on my server did not pass the ARC validation. As Google already considers e-mail coming from my domain as spam (but strangely, not the emails forwarded by the mailing list on the same domain), I did not want to risk to see my e-mail treated as even less worthy to be delivered to the Valued Customers of Gmail (i.e., probably half the world) than it is now. And I wanted to look into ARC anyway. But the installation is not trivial, there are no official Debian packages, and no clear tutorial on the Web, so here's what I did. It may work for you or not.

This tutorial assumes you already have configured postfix with opendkim, and they are running on a single computer on a Debian 10. The configuration example expects postfix to run as chroot and uses the private key configured for opendkim.

All the following commands need to be run as root or through sudo.

Install the package

Add an apt source by creating the file /etc/apt/sources.list.d/openarc.list and write:
deb https://download.opensuse.org/repositories/home%3A/andreasschulze/Debian_10 /
Then run:
curl https://download.opensuse.org/repositories/home:/andreasschulze/Debian_10/Release.key | apt-key add -
apt update
apt install openarc
If you don't want to run curl as root, you can run that command instead:
curl https://download.opensuse.org/repositories/home:/andreasschulze/Debian_10/Release.key | sudo apt-key add -
This will install openarc 1.0.0 beta3-3, but the package's post-installation script has a bug, so the installation fails. To fix it, edit /var/lib/dpkg/info/openarc.postinst and comment-out line 62 which contains
  ln -s ../../var/lib/supervise/openarc-milter /etc/service/

Then run as root apt install openarc again.

Configure OpenArc

Create /etc/openarc/keys and copy the key from opendkim (e.g., /etc/opendkim/keys/example.private) into /etc/openarc/keys/. Then copy /etc/opendkim/TrustedHosts into /etc/openarc/.

Create /etc/openarc.conf (or create one such file based on /usr/share/doc/openarc/openarc.conf.sample.gz) and modify the following directives (without the quotes around the values, of course):

  • AuthservID: the name of the server (e.g., “server.example.com”)
  • Canonicalization: the value “relaxed/simple”
  • Domain: the domain namd (e.g., “weber.fi.eu.org”)
  • FinalReceiver: the value “no”
  • InternalHosts: the value “/etc/openarc/TrustedHosts” (if there is a such a file)
  • KeyFile: the path to the private key (e.g., “/etc/openarc/keys/example.private”)
  • OversignHeaders: the value “From”
  • PidFile: the value “/var/run/openarc/openarc.pid”
  • Selector: the value of the opendkim selector (see Selector in opendkim.conf)
  • Socket: the value “local:/var/spool/postfix/var/run/openarc/openarc.sock”
  • Syslog: the value “Yes”
Create /var/spool/postfix/var/run/openarc that will contain the socket:
mkdir /var/spool/postfix/var/run/openarc
chown openarc:openarc /var/spool/postfix/var/run/openarc
chmod 750 /var/spool/postfix/var/run/openarc
Add the postfix user to the openarc group so that postfix can access the socket (run as root):
usermod -a -G openarc postfix
Create a systemd service file /etc/systemd/system/openarc.service with the following content:
Description=OpenARC Authenticated Received Chain (ARC) Milter
Documentation=man:openarc(8) man:openarc.conf(5) https://openarc.org/
After=network.target nss-lookup.target·
ExecStart=/usr/sbin/openarc -c /etc/openarc.conf

The UMask directive is especially important, so that /var/spool/postfix/var/run/openarc/openarc.sock is readable and writable by the members of the openarc group (i.e., postfix). OpenDKIM has a UMask directive, but OpenARC does not.

You can now start the service with
systemctl start openarc

Configure postfix

Edit /etc/postfix/main.cf and add the socket to the smtpd_milters and non_smtpd_milters lists (you may already have other milters configured, such as OpenDKIM):
smtpd_milters = unix:/var/run/opendkim/opendkim.sock,
non_smtpd_milters = unix:/var/run/opendkim/opendkim.sock,
Finally, restart postfix:
systemctl restart postfix

You can now test your OpenARC setup with the tools provided by openarc.org.

[ Posted on April 11th, 2021 at 20:23 | 1 comment | ]

Tuesday, December 1st, 2020

Unpopular Ratings

Categories: [ Science ]

For quite a while, I've been wondering what to do with movies or games which have been rated with a small number of people, such as the ones found on IMDb or BoardGameGeek: users can give a 0 – 10 rating to items, and the users' average is then shown. Is an obscure movie rated 8 by 42 people actually worth an 8?

Putting aside the actual usefulness of those ratings, I wanted to know how much uncertainty there is on those values, knowing only the arithmetic mean and the number of people who have voted.

Given population (i.e., all the users who have seen the film or played the game and who could vote), when we take a sample (i.e., the users who have rated the item) of size n, we get a sample mean m. But if another sample had been taken, we would have gotten a slightly different sample mean. How much much different would these two values be? In other words, how close is this value from the value we would get if the whole population would have rated the item?

The standard error of the mean indicates how much the sample mean m can vary by calculating its standard deviation sm = s/√n. The problem is that it is based on the standard deviation s of the population, which is unknown. Given that we do not know the users' individual ratings, we cannot either calculate the standard deviation of the sample.

All is not lost, however: since the ratings are always between 0 and 10, we can estimate the maximum value of the standard deviation. My best guess is that this value is maximal when half the ratings are 0 and the other half are 10, leading to a standard deviation of 5.

Given that the means of 96% of the samples of size n falls within ±2sm of the population's mean, we know that the mean of the population is very likely to be between m−10√n (but no less than 0) and m+10√n (but no more than 10). So this obscure movie rated 8 by 42 users is worth between 6.5 and 9.5. Given that on IMDb movies rated above about 7 are reasonably good, the obscure movie may be worth watching.

[ Posted on December 1st, 2020 at 22:59 | no comment | ]

Tuesday, October 13th, 2020

Noursella v3

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Choco Noursy ]

Mise à jour du Noursella v2 avec de la vanille et adaptée à de nouveaux ingrédients, disponibles en Finlande.


  • 56g chocolat noir (Pirkka luomu reilun kaupan tumma suklaa 72%)
  • 250g pâte de noisette (100% noisette, Rapunzel Haselnussmus 100% Haselnüsse)
  • 70g cacao en poudre (Callebaut Cocoa Powder CP-E0-776, 22/24% matière grasse)
  • 110g sucre glace (Dan Sukker tomusokeri)
  • 30g sucre vanillé (à base de sucre glace, Dan Sukker Vaniljasokeri)
  • 20-30g huile végétale neutre (Apetit rypsiöljy)


Faire fondre le chocolat (cassé en petits morceaux, 2 min au micro-ondes), y mélanger la pâte de noisette (d'abord l'huile qui surnage, puis la partie solide) jusqu'à obtenir un appareil homogène. Placer le cacao, le sucre glace et le sucre vanillé dans un tamis à farine, et ajouter en plusieurs fois dans le mélange chocolat-noisette, en mélangeant bien à chaque étape. Ajouter finalement de l'huile en fonction de la texture souhaitée: 20g d'huile donne une pâte plus difficile à tartiner sur une crêpe, 30g d'huile donne un pâte plus liquide mais une petite partie de l'huile va remonter à la surface au bout d'une semaine.


Dans un pot en verre (propre…), plusieurs semaines dans un placard à température ambiante.

Des chiffres

Masse totale: 540g

  • Lipides: 42g pour 100g
  • Glucides: 31g pour 100g (sucres: 30g pour 100g)
  • Protéines: 10g pour 100g
  • Fibres: 5.5g pour 100g

Prix: 16,42 EUR (30,41 EUR/kg)

[ Posted on October 13th, 2020 at 15:29 | no comment | ]

Tuesday, July 21st, 2020

Roll through the Dungeon

Categories: [ Games ]

A couple of months ago, I discovered D100 Dungeon, a free print-and-play (at least up to version 2.2) dungeon crawling game where most aspects of the game are randomly generated by rolling dice. While it is yet-another game where you walk into a room, kill the monster and loot the treasure, I was especially enthusiastic about generating the dungeon's map randomly (look at the pictures on BoardGameGeek, some people have taken the time to even make very nice looking maps) I've played a few games before feeling the itch to design such a game myself (game design is something that has been itching every now and then, but without much result until now).

Roll through the Dungeon (RttD) is the result of scratching that itch. The structure of the game is very much inspired by D100 Dungeon, but I changed some of the mechanisms that I didn't like with what I consider better ones.

Inspired by the James Bond 007 role-playing game I played as a teenager in the early '90s, skill tests have an associated success grade, indicating how well your character succeeds in doing a given action. This success grade then determines how much time is spent on doing the action, and in case of fights, how much damage is inflicted to the opponent. This contrasts with D100 Dungeon's two-step fights, where the skill test tells you whether you have hit the opponent, but an second, unrelated roll determines the damage. I find it more intellectually satisfactory that the damage depends on how successful the action has been, rather than being an independent event, but I am aware it is nothing more than a design decision and it does not model reality any better than the two-step model. I'm particularly proud of the nomogram I have designed for computing the success grade, but I must admit that it is actually not very practical (the table is much faster to use, as it does not require manipulating a ruler). But I did it for fun, and that is the kind of geeky stuff I consider fun :)

Another important difference between D100 Dungeon and RttD is the construction of the dungeon's map. The map is a grid of adjacent squares rooms, and in the former game, a single D100 roll determines most of the characteristics in a square: the three exits from the room (corridor, door or wall) and what you find in it (a feature, a monster or nothing). This leads to paradoxical maps, where on one side of a room is a door, but the same side seen from the adjacent room is a solid wall. RttD solves this at the cost of more dice rolls: room sides are determined by independent D10 rolls, and the room's content is yet another roll. That means that you need to roll up to four D10 instead of one D100.

Many design decisions from D100 Dungeon have become apparent when trying to solve the same problems, and have led to the same solutions, reinforcing the similarity between the two games: roll tables are ordered, with the not-go-good stuff at the beginning of the table, and the best stuff at its end. This way, it is possible to get better stuff in some circumstances by offsetting the dice roll (i.e., adding 10 to the D100 roll). This is especially important when searching the rooms: feature rooms may contain a trap, so when searching a feature room, you add 5 to your roll to make it impossible to find a second trap in the same room, as well as increasing the chances to find better stuff.

The hardest thing for me in working on this game has been (and still is) to invent thematic elements, such as monsters, quests, room features, treasures. I tried not to simply copy D100 Dungeon (partly because it's copyrighted, and partly because I wanted to prove myself that I could do it), but it has been really hard. Most monsters and features are coming straight from the NetHack wiki, monster HP and damage are generated by a script to create a progression in the difficulty, but the hardest has been (and still is) quests. Some are more or less direct adaptations from the quests in D100 Dungeon, a few are from my own imagination (especially in the “Ridicule does not kill” they say chapter), but the list of quests is still incomplete (actually, it's the only thing that justifies not releasing a version 1.0 of the game).

I have now played about 25 games of RttD to test the game, fix the problems and try and improve it. I'm a bit sad to say that the novelty is wearing off and that the game feels quite repetitive (it is, by design). What has been the most fun until now has been to use LaTeX again to produce the PDF booklet and to develop macros for generating some parts of the tables and making cross-references. It is nothing fancy, but as a software developer, it's the kind of things I enjoy doing.

I feel like running out of steam about RttD so I don't know how long I will continue working on it; certainly I won't anymore spend several hours daily as I have in the beginning. But as the game is released under Creative Commons BY – SA, anyone can work on it, write changes, and generally improve it. The PDF booklet has a section about developing the game, if you are interested. Among the things that are not implemented yet are curses and blessings (which D100 Dungeon already has), I was considering making them more like NetHacks curses i.e., items you find may be cursed and detrimental to your character if worn or used.

[ Posted on July 21st, 2020 at 12:40 | no comment | ]

Saturday, December 14th, 2019

Battery Capacity Meter

Categories: [ Science ]

I have over forty Ni-MH low self-discharge rechargeable batteries (mostly Eneloops, and a few Vartas and GPs) and I have noticed that some of them tend to not keep the charge very well. In order to find out which ones are worth throwing away to be recycled, I needed a device that would measure their capacity.

The principle is very simple: recharge the battery, discharge it through a resistor with a known value, measure the current while it discharges, and integrate the current over time. This will give you the battery's capacity. A few things need to be taken into account: how do you know that a battery is depleted and what discharge current should be used? From what I could gather by reading a few Web forums, the battery is considered to be depleted when its voltage goes below 1.0 V or 0.9 V (I settled on 1.0 V), and the current should be such that the battery discharges in about five hours (for a 2000 mAh battery, that would be about 400 mA). If the current is too high, the battery's voltage while it is discharging will be very close to 1.0 V already at the beginning of the process and the capacity measurement will probably be biased. I noticed this in the first iteration of the device where I had a 1 Ω resistor and a 1 A current, and decided to change for a 3.3 Ω resistor after reading about the “discharge in 5 hours” rule.

The device


The prototype I built is very simple. The battery discharges through a 3.3 Ω ±1% 0.6 W resistor, and the voltage is measured using the Arduino's ADC (more on that below). A MOSFET (STMicroelectronics VNP35NV04-E, quite expensive but it turns fully On with only 2.5 VGS and its On resistance is only 13 mΩ, which is below the tolerance of the main resistor and the accuracy of what the Arduino can measure, so it can simply be ignored) controls the discharge and cuts the current once the “depleted” threshold has been reached to prevent damaging the battery. A 10 kΩ resistor acts as a pull-down for the MOSFET's gate, and a 110 Ω limits the gate current (whether it's really needed or not seems to depend whom you ask on Web forums, but since I had some lying around I decided to use them). There are two instances of the circuit on the prototype board, so I can test two batteries in parallel.

Accuracy of the Arduino's ADC

The Arduino's ADC has a maximum precision of 10 bits, meaning that the value read from e.g., A0 is translated to a value between 0 and 1023 included, by comparing A0 to a reference voltage that by default is the Arduino's supply voltage. It is however possible to get (reasonably) accurate voltage measurement with the Arduino (see this Web post titled Secret Arduino Voltmeter) by measuring first the supply voltage accurately against the ATMega328's internal band-gap reference. To do so, you need to first measure the band-gap reference's correct value (it's about 1.1 V, it's very stable over time and temperature, but it's exact value differs from one ATMega328 to the next).

The process therefore goes like this.

  1. With a trusted voltmeter, measure the Arduino's supply voltage VCC as accurately as possible.
  2. Using the code snippet in the blog post, measure the band-gap's value with the ADC; that gives you a number Nref. For better accuracy, compute the arithmetic mean of 1000 measurements to get rid of the random fluctuations in the measurement.
  3. Calculate Vref = Nref × Vcc / 1024 (and not 1023 as stated in the blog post). In my case, Vref = 1.089 V ±0.001V.
  4. Hardcode the Vref you have found in your Arduino program.

Then when you want to actually measure a voltage V with the Arduino's ADC, do as follows.

  1. Measure Nref with the ADC and compute the value of Vcc = Vref × 1024 / Nref (again, take the average of 1000 measurements).
  2. Measure N with the ADC and compute the value V = N × Vcc / 1024 (once again, average over 1000 measurements).

If my uncertainty calculations are correct, I get a 0.12% uncertainty on the value of Vcc (compounding the uncertainty of the voltmeter and the ADC), which is less than the 1% uncertainty on the discharge resistor value. I tried to accurately measure the 3.3 Ω resistor value, but my amperemeter is not accurate enough for currents above 200 mA, so that's the best I can hope to achieve.

Take a look at the code if you want to know the details of the implementation (especially computing in millivolts in order to use only integers instead of floats).

What about those batteries, then?

I measured the capacity of 27 batteries so far: 2 GPs, 3 Vartas and 22 Eneloops (6 of generation 1, 7 of generation 2, 2 of generation 3 and 7 of generation 4). All have a nominal capacity of 1900 mAh.

Both GPs have a capacity of 1 mAh, so they are useful only as paperweights. The Vartas have capacities of 870, 450 and 250 mAh, so they are only marginally more useful than the GPs.

One Eneloop is in really bad shape (200 mAh), but the others have capacities between 1660 and 1900 mAh, with a mean of 1790 mAh.


This is a typical battery, in good shape (1890 mAh). The voltage quickly drops to around 1.2 V and remains stable for a while before dropping again.


This one is definitely good for nothing (200 mAh). I remember accidentally dropping batteries on the floor a couple of times in the past, maybe this one suffered from it?


This one is a first generation eneloop, bought probably around 2011. It still has a capacity of 1660 mAh, but the shape of the curve is different from the typical one, maybe due to old age?

[ Posted on December 14th, 2019 at 17:08 | no comment | ]

Thursday, November 21st, 2019

Bed Reading Lamp

Categories: [ DIY ]

For quite a while I have been dreaming about a lamp for reading in bed that would be equally usable when lying on my belly or sitting with my back on the wall. It would be wide enough to uniformly light an open comic book (slightly larger than an A3 paper sheet) without causing a glare on the paper, meaning that it should light from above. Finally it must be made of a strip of warm white LED, its brightness must be adjustable and must consume no power when it's off.

And because making things is fun, I wanted to make it myself. There it is.


It's made of 60 cm of LED strip glued on the inside of a 15×15 mm aluminium profile, and it's powered by a 12 V, 500 mA power supply, via a dimmer (that has also silly features like strobe effect, blinking and pulsating, but that's no good for reading). The power supply is connected to the mains via a wire equipped with a switch (that effectively cuts the power to the power supply when it's off).

Reading_lamp_wiring Reading_lamp_wiring_inside

To meet the requirement of reading in two different positions, the angle of the aluminium profile can be changed by rocking it. I had to solder thinner wires to the LED strip to prevent it from acting like a spring and holding the lamp back when rocking it.

Reading_lamp_position_A Reading_lamp_position_B

[ Posted on November 21st, 2019 at 17:42 | no comment | ]

Monday, September 30th, 2019

Week numbers

Categories: [ Science ]

Given a week number, it's not easy to know when it is located in the calendar. And to add to the confusion, there are three definitions of the week number (ISO-8601; Middle Eastern; North-American and Islamic). The EU and most European countries use the ISO-8601 definition, according to Wikipedia.

The following method is quite approximative so the actual definition does not matter so much, but it should help placing a week number in the calendar without having to look it up:

  1. Divide by 4
  2. Subtract 10%
  3. Add 1

The integer part (before the decimal separator) of the result is the number of the month, and the fractional part (after the decimal separator) times three tells you near what day of the month it is located.

For example:

  • Week 1: 1/4 is 0.25, minus 10% is about 0.2, plus 1 is 1.2; 2 times 3 is 6: it's around January 6th (Jan. 1-5, 2019 actually).
  • Week 7: 7/4 is 1.75, minus 10% is about 1.6, plus 1 is 2.6; 6 times 3 is 18: it's around February 18th (Feb. 10-16, 2019 actually).
  • Week 14: 14/4 is 3.5, minus 10% is about 3.2, plus 1 is 4.2; 2 times 3 is 6: it's around April 6th (Mar. 31-Apr. 6, 2019 actually).
  • Week 42: 42/4 is 10.5, minus 10% is about 9.5, plus 1 is 10.5; 5 times 3 is 15: it's around October 15th (Oct. 13-19, 2019 actually).
  • Week 35: 35/4 is 8.75, minus 10% is about 7.9, plus 1 is 8.9; 9 times 3 is 27: it's around August 27th (Aug. 25-31, 2019 actually).
  • Week 52: 52/4 is 13, minus 10% is about 11.7, plus 1 is 12.7; 7 times 3 is 21: it's around December 21st (Dec. 22-28, 2019 actually).

[ Posted on September 30th, 2019 at 18:13 | no comment | ]

Optique photo 7 : conclusion

Translation: [ Google | Babelfish ]

Categories: [ Science ]

Suite à des discussion sur la distance focale équivalente d'un objectif monté sur des appareil photo numériques équipés de capteurs de différentes tailles et sur des notions exotiques comme l'ouverture équivalent et la sensibilité équivalente évoquées par Richard Butler sur dpreview.com, je me suis penché sur la question de la pertinence de ces concepts. Les articles suivants publiés précédemment sur mon blog présentent les résultats de mes réflexions.

  1. L'angle de champ
  2. La profondeur de champ
  3. La perspective
  4. La sensibilité
  5. Le temps de pose
  6. L'affichage d'une photo

Je ne m'intéresse à la photo que sous l'angle de l'optique, et comme je ne suis pas photographe pour deux sous, j'ai peut-être écrit des énormités ; si c'est le cas, on peut poster des commentaires sous les articles et tenter de me convaincre que je me suis trompé.

Caractériser une photo

Une photo est souvent caractérisée par le modèle d'appareil ayant servi, ainsi que par la distance focale de l'objectif utilisé, le nombre d'ouverture et le temps de pose. Étant donné la confusion créée par la notion de distance focale équivalente, je me suis demandé si la distance focale était vraiment pertinente pour caractériser une photo. Mais caractériser quoi, exactement ? Et caractériser dans quel but ?

Je fais l'hypothèse que caractériser une photo sert à reproduire la photo, autant que possible à l'identique, avec le même appareil ou avec un autre appareil. Ce qu'on peut chercher alors à reproduire c'est l'angle de champ, les distances limites de la profondeur de champ (c'est à dire la distance du premier plan net et du dernier plan net) et le cas échéant un effet voulu de sur- ou sous-exposition, ou de flou de mouvement. On peut décrire ces caractéristiques par des paramètres ayant davantage de sens que le trio focale, diaphragme et temps de pose ?

L'angle de champ est une combinaison de la distance focale et la taille du capteur, donc indiquer seulement la distance focale en laissant le soin au lecteur de deviner la taille du capteur en fonction du modèle de l'appareil est possible, mais on peut faire mieux en indiquant directement la valeur de l'angle de champ au moment de la prise de vue. Il est ensuite possible de recréer cet angle de champ avec n'importe quelle combinaison judicieuse de capteur et d'objectif.

De même, la profondeur de champ dépend de la distance focale, de l'ouverture du diaphragme, de la taille des pixels du capteur et de la distance de mise au point. Au lieu de détailler les trois premières valeurs, on peut les combiner sous la forme de la distance hyperfocale. Les distances des premier et dernier plans nets sont alors deux fonctions simples de la distance de mise au point et de la distance hyperfocale. Reproduire la profondeur de champ devient alors simplement une question de prendre la même distance hyperfocale et de faire la mise au point sur la distance donnée.

Si le photographe ne cherche pas d'effet d'exposition particulier, la sensibilité du capteur est une fonction des autres paramètres pour une valeur d'exposition idéale, standard. En revanche si la photo a été volontairement sur- ou sous-exposée, il faut indiquer la sensibilité utilisée au moment de la prise de vue.

Le flou de mouvement enfin est particulier car il dépend du temps, et le temps de pose est le seul paramètre qui en dépende également. Dans de cas d'une exposition standard, le temps de pose a une valeur minimale qui dépend de la distance focale, et on peut supposer que cette valeur sera choisie car elle permet d'utiliser la sensibilité la plus faible possible et ainsi réduire le bruit. En revanche si un flou de mouvement est voulu par le photographe, ou au contraire si l'objet se déplace rapidement et que le but est de le « figer », il faut alors indiquer le temps de pose.

En conclusion, au lieu de caractériser une photo par le modèle d'appareil, la distance focale, le nombre d'ouverture et éventuellement le temps de pose et la sensibilité, on peut caractériser une image de manière plus générique et descriptive en indiquant l'angle de champ, la distance hyperfocale, la distance de mise au point et éventuellement la sensibilité et le temps de pose. Bien que ces nouvelles caractéristiques soient moins connues que celles utilisées jusqu'à présent, calculer leurs valeurs ne pose pas de problème aux appareils photos numériques et pourraient donc être incluses dans les métadonnées des images. Et je suis certain que la signification des caractéristiques « classiques » n'est pas mieux comprise par la plupart des photographes que les caractéristiques proposées ici, remplacer en ensemble de valeurs hermétique par un autre ensemble de valeurs hermétiques ne devrait donc pas poser de problème une fois vaincue la résistance naturelle de l'être humain au changement.

Applications numériques

Pour se faire une idée des valeurs numériques des caractéristiques décrites plus haut, voici des applications numériques de diverses formules pour trois appareils photos de référence : un compact, un boitier APS-C et un plein format avec 3 objectifs.

Appareils photo de référence
Appareil Pixels Capteur Objectifs
A 21,1 Mpix 6,17×4,55 mm
4,3-172,0 mm, f/3,3-f/6,9
B 25,0 Mpix 23,5×15,6 mm
16 mm, f/2,8 ;
18-135 mm, f3,5-5,6
C 27,1 Mpix 36×24 mm
(plein format)
16-35 mm, f/4 ;
35-70mm, f/2,8;
70-200mm, f/2,8
Dimensions d'un pixel
Appareil ε Surface Densité
A 1,15 μm 1,33 pm2 752 000 px/mm2
B 3,83 μm 14,7 pm2 68 200 px/mm2
C 5,65 μm 31,9 pm2 31 400 px/mm2
Angles de champ
Appareil θ (rad) θ (°)
A 1,45-0,446 83-2,55
B 1,45
C 1,86-1,11
Distances hyperfocales
Appareil F (m) f/N (mm)
A 2,01-3730 4,3/8-172/6,9
B 3,04-23,9
C 2,06-54,2

En admettant que les capteurs des appareils A, B et C ne diffèrent que par la taille de leurs pixels (ce qui est loin d'être évident) et en fixant un niveau de bruit à ne pas dépasser dans l'image obtenue, on peut comparer leur « sensibilités » relatives.

  • A est 11 fois moins sensible que B et 24 fois moins sensible que C.
  • B est 11 fois plus sensible que B et 2,2 fois moins sensible que C.
  • C est 24 fois plus sensible que A et 2,2 fois plus sensible que B.

On peut aussi comparer, à distance focale et temps de pose égaux, de combien de crans il faut ouvrir ou fermer le diaphragme pour obtenir la même exposition et le même niveau de bruit.

  • A doit ouvrir de 3,5 crans par rapport à B et de 4,6 crans par rapport à C.
  • B doit fermer de 3,5 crans par rapport A et ouvrir de 1,1 crans par rapport à C.
  • C doit fermer de 4,6 crans par rapport à A et de 1,1 crans par rapport à B.
  • Moniteur 16:10 24" FullHD : 2,30 Mpx ; taille d'un pixel : 405 μm
  • Moniteur 16:9 32" 4k : 8,29 Mpx ; taille d'un pixel : 265 μm
  • Moniteur 16:9 27" 5k : 14,8 Mpx ; taille d'un pixel : 167 μm
  • Télévision 16:9 40" FullHD : 2,07 Mpx ; taille d'un pixel : 661 μm
  • Télévision 16:9 55" 4k : 8,29 Mpx ; taille d'un pixel : 455 μm
  • Télévision 16:9 85" 8k ; 33,2 Mpx ; taille d'un pixel : 351 μm
  • Impression papier à 300 dpi :taille d'un pixel : 84,7 μm
  • Impression à 300 dpi en 10×13 cm : 1,81 Mpix
  • Impression à 300 dpi en 10×15 cm : 2,09 Mpix
  • Impression à 300 dpi en 20×30 cm : 8,37 Mpix
  • Impression à 300 dpi en 40×60 cm : 33,5 Mpix
  • Impression à 300 dpi en 76×115 cm : 122 Mpix

[ Posted on September 30th, 2019 at 08:30 | no comment | ]

Friday, September 20th, 2019

Optique photo 6 : l'affichage

Translation: [ Google | Babelfish ]

Categories: [ Science ]

Taille d'un point d'affichage

La notion de flou qui est importante pour la profondeur de champ et le flou de bougé ne dépend pas tant de la taille des pixels du capteur que de la taille des pixels au moment de l'affichage, que ce soit sur un écran ou sur un support physique comme le papier. En effet, utiliser la taille d'un pixel comme taille maximale ε d'une tache de flou donne une garantie que le flou n'est pas perçu par le capteur et donc n'est pas enregistré dans l'image, mais puisque l'image est destinée à être regardée par un humain, ce qui importe in fine c'est la capacité de l'humain à percevoir le flou. On peut donc définir une nouvelle valeur de ε utilisé dans les calculs de profondeur de champ et de bougé en fonction de la manière d'afficher l'image.

Pour un affichage sur écran, on peut calculer la taille d'un pixel ε (en mètres) en fonction de la diagonale D (en pouces), du format R (par exemple 16/9) et du nombre de pixels par ligne n:

ε = 0,0254 × D√(1 + 1 / R) / n

Pour un affichage sur papier, on peut calculer la taille d'un point ε (en mètres) en fonction de la définition de l'impression d (en points par pouce, dpi):

ε = 0.0254 / d

Agrandissement maximal

Pour éviter les effets de crénelage lors de l'affichage, il faut aussi veiller à ne pas afficher l'image à une trop grande taille par rapport à la résolution de l'image numérique. En d'autres termes, le nombre de pixels N de l'affichage ne doit pas être plus grand que le nombre de pixels de l'image (c'est à dire du capteur si on ne retaille pas l'image). On suppose ici que l'intégralité de l'image est affichée sur l'écran ou le papier, c'est à dire qu'on ne va pas zoomer dans l'image.

Pour un écran, le nombre de pixels N vaut

N = n2 / R

Pour un tirage papier de densité d points par pouce, N vaut

N = 0,155 × l2d2 / R

où l est la largeur du papier et R est le rapport largeur sur hauteur.

On peut aussi calculer N en fonction de la largeur l et la hauteur h du papier :

N = 0,155 × lhd2

[ Posted on September 20th, 2019 at 13:06 | 1 comment | ]

Monday, September 16th, 2019

Optique photo 5 : le temps de pose

Translation: [ Google | Babelfish ]

Categories: [ Science ]

Exposition et temps de pose

Du point de vue pratique, l'exposition d'une photo dépend du nombre d'ouverture du diaphragme, de la sensibilité du capteur et du temps de pose. On peut comprendre cette exposition comme la quantité de lumière nécessaire pour que chaque pixel du capteur CCD reçoive suffisamment de photons pour produire une valeur « raisonnable » de ce pixel. On a alors le choix d'exposer ces pixels à un flot de lumière important pendant un temps court, ou au contraire un flot de lumière plus faible pendant un temps plus long pour recevoir le même nombre de photons, et donc obtenir le même résultat.

Plus formellement, l'exposition lumineuse H est définie par H = Et, où E est l'éclairement lumineux c'est à dire le flot de lumière et t est le temps de pose.


En faisant l'hypothèse d'un système idéal d'une lentille mince parfaitement transparente uniformément éclairée par l'objet photographié, on peut définir l'éclairement lumineux comme E = Lπ / (4(N / (1 - f / l))2) où L est la luminance de l'objet, f est la distance focale de l'objectif, l est la distance de l'objet et N est le nombre d'ouverture du diaphragme. Dans le cas courant où la distance focale est négligeable par rapport à la distance à l'objet, on peut simplifier cette définition en E = Lπ / (4N2).

Par ailleurs, la sensibilité est définie par S = H0 / H où H0 est une valeur d'exposition de référence, constante. En considérant que la luminance de l'objet est elle aussi constante, on obtient la relation

St / N2 = 4H0 / (Lπ)

qui indique bien que si l'un des trois paramètres N, S ou t varie alors l'un au moins des deux autres paramètres doit varier pour que l'égalité soit toujours vérifiée.

Flou et temps de pose

Si le sujet ou l'appareil photo se déplacent pendant la prise de vue, on comprend aisément qu'un point de l'objet donne une image qui se déplace sur le capteur, qui enregistre alors plusieurs points contigus.

Parmi tous les mouvements possibles de l'appareil photo qui conduisent à un flou de bougé, un mouvement simple à modéliser est une rotation de l'appareil photo autour du centre optique O. On considère que durant la prise de vue, le point A s'est déplacé en B, et que le temps de pose est assez long pour que le capteur enregistre toutes les positions prises par ce point entre A' et B'. L'image du point, au lieu d'être un point, est alors un trait de longueur h'. On peut exprimer h' en fonction de θ

h' = tan(θ)lf/(l-f)

Lorsque la distance focale f est négligeable devant l et que θ est inférieur à 0.5 radians (soit 29°, ce qui fait que l'erreur d'approximation de tan(θ) est inférieure à 10%), on peut simplifier en

h' = fθ

En considérant que l'angle θ est le résultat d'une rotation de vitesse angulaire ω durant un temps t, c'est-à-dire θ = ωt on peut écrire

h' = fωt

En notant ε la hauteur d'un pixel et en reprenant l'hypothèse que le flou est invisible si la taille h' de la « tache » de flou est plus petite qu'un pixel, on peut écrire que le flou de bougé est invisible si

t < ε/(fω)

On retrouve ici l'approximation dite « de l'inverse de la focale », à savoir que pour éviter le flou de bouger,

« le temps de pose minimum a la même valeur numérique que l'inverse de la distance focale exprimée en millimètres. »

Cette approximation suppose que ε/ω = 1/1000 m·s·rad-1. On peut supposer que pour un photographe moyen, la vitesse ω de ses mouvements involontaires est constante, mais on voit que cette approximation dépend directement de la taille ε d'un pixel qui peut beaucoup varier d'un capteur à l'autre.

[ Posted on September 16th, 2019 at 21:22 | 2 comments | ]

Sunday, September 15th, 2019

Optique photo 4 : la sensibilité

Translation: [ Google | Babelfish ]

Categories: [ Science ]

Le bruit

Chaque pixel d'un capteur CCD d'appareil photo numérique est un transducteur qui transforme une quantité de lumière reçue Q en une différence de potentiel électrique u0 qui est proportionnelle à Q selon un facteur f :

u0 = fQ

Dans un situation idéale, une valeur donnée de Q devrait donner systématiquement la même valeur nominale u0. En réalité, à cause du bruit, des mesures répétées d'une même valeur d'exposition donneront des valeurs différentes de u

u = u0 + ε

où ε est une erreur de mesure aléatoire. Cela signifie que chaque mesure répétée de u aura une valeur différente de ε, mais aussi que deux pixels voisins qui sont exposés de la même manière donneront des valeurs de u différentes.

Si on considère qui le bruit est un bruit blanc (ce qui est probablement faux mais suffisamment similaire à la réalité pour être utile à cette explication), alors les valeurs de ε sont couramment proches de zéro (et donc u est proche de u0), mais ε peut parfois, plus rarement, être nettement plus grand que zéro et donc la valeur de u est nettement différente de u0.

La sensibilité d'un capteur numérique

Les valeurs typiques de u sont très petites et il est donc nécessaire de les amplifier avant de les numériser. Ainsi une valeur v de pixel (typiquement entre 0 pour la valeur la plus sombre et 255 pour la valeur la plus lumineuse) est obtenue en effectuant

v = Au si Au ≤ vmax
v = vmax si Au > vmax

où A est le facteur d'amplification. Comme la valeur de v ne peut dépasser vmax, on comprend que si l'amplification A choisie est trop élevé par rapport à u, l'intervalle de valeurs possibles pour un pixel n'est pas suffisant pour représenter la valeur correcte de ce pixel, et on arrive à la saturation.

En faisant varier A, on peut faire varier la « sensibilité » du capteur et obtenir des valeurs élevées de v (donc un pixel très lumineux) à partir d'une valeur faible de u (par exemple en photographiant dans une situation de faible luminosité).

Cependant, comme v contient aussi le bruit ε, ce dernier est amplifié de la même manière :

 v = Au0 + Aε

Ainsi, plus la « sensibilité » du capteur est élevée, c'est à dire plus le facteur d'amplification A est élevé, plus le bruit est élevé et devient perceptible pour l'observateur.

Par exemple pour une image d'un objet noir, on s'attend à ce que v soit proche de zéro pour tous les pixels même pour une grande valeur de A parce que u0 est justement proche de zéro. Mais il peut arriver que ε soit bien plus grand que u0, ce qui conduit à ce que la valeur v du pixel soit essentiellement égale à Aε. Ceci se traduit par des pixels brillants au milieu de pixels sombres, typiques du bruit des photos prises en faible lumière avec une sensibilité élevée.

Comparaison de capteurs

Dans des conditions d'éclairage uniforme dans l'espace et le temps d'une surface s pendant un temps t (le temps de pose d'une photo), on peut définir les grandeurs suivantes :

où Q est la quantité de lumière c'est à dire approximativement le nombre de photons qui arrivent sur la surface s. En combinant ces trois définitions, on obtient

Q = Hs

c'est à dire que la quantité de lumière qui arrive sur un pixel est proportionnelle à la surface de ce pixel. Autrement dit, à exposition H égale, un pixel plus grand reçoit une plus grande quantité de lumière qu'un pixel plus petit.

En reprenant définition de u0 plus haut, on a

u0 = fHs

et donc, en ignorant pour le moment le bruit ε on a

v = AfHs

Ainsi, pour obtenir la même valeur v avec deux capteurs (capteur 1 et capteur 2) dont les pixels ont respectivement des surfaces s1 et s2, on a besoin d'un facteur d'amplification A1 sur le capteur 1 et A2 sur le capteur 2 tels que

A2 = A1s1 / s2

On a vu plus haut que le bruit est amplifié. Cela se traduit par

A2ε = A1εs1 / s2

c'est à dire que le bruit dans l'image obtenue par le second capteur est s1 / s2 fois plus élevé que le bruit dans l'image obtenue par le premier capteur.

Qualitativement, cela signifie que pour obtenir deux photos exposées de manière identiques avec deux appareils différents, l'un muni d'un capteur à grands pixels et le second muni d'un appareil à petits pixels, celui dont les pixels sont petits a besoin d'un facteur d'amplification plus élevé et produit donc une image plus bruitée.


La sensibilité est définie par S = H0 / H où H0 est une valeur d'exposition de référence.

Un photographe s'attend à ce que lorsqu'on prend la même photo avec deux appareils (dont les capteurs sont équivalents à l'exception de la taille des pixels, et donc de leur nombre), l'image obtenue est exposée de la même façon (en supposant que la distance focale, le nombre d'ouverture, le temps de pose et la sensibilité sont les mêmes). Une conséquence est que les facteurs d'amplification des deux appareils doivent être différents puisque les tailles des pixels sont différentes, et que l'image de l'appareil dont les pixels sont plus petits contiendra donc plus de bruit.

Réciproquement, si on cherche à produire avec les deux appareils des images contenant une quantité de bruit identique, il faut changer les paramètres d'exposition de l'appareil produisant le plus de bruit de sorte à

  • diminuer la sensibilité S d'un facteur s1 / s2 afin d'utiliser le même facteur d'amplification dans les deux appareils, et
  • augmenter l'exposition H d'un facteur s1 / s2 afin de compenser la diminution de la sensibilité.

On peut parvenir à ce dernier point en augmentant le temps de pose ou en augmentant la surface de la pupille du diaphragme d'un facteur s1 / s2.

Selon le modèle simplifié utilisé ici, à tailles de capteurs égales, un capteur de plus haute définition (donc comportant un plus grand nombre de pixels) produira donc des images contenant plus de bruit.

Si on décide de considérer qu'avec une exposition de référence Href la sensibilité Sref maximale d'un capteur de référence dont les pixels ont une surface sref représente une quantité de bruit de référence, on peut considérer qu'à quantité de bruit identique, un capteur de plus haute définition dont les pixels ont une surface s aura donc une « sensibilité équivalente » Seq plus faible nécessitant une exposition Heq. En effet Sref = H0 / Href et Heq = sref / sHref, donc

Seq = Srefs/sref

Il faut noter que la sensibilité équivalente, qui dépend de la surface des pixels, n'a rien à voir avec la focale équivalente qui dépend des dimensions du capteur et non de celles de ses pixels.

[ Posted on September 15th, 2019 at 17:22 | 2 comments | ]

Saturday, September 14th, 2019

Optique photo 3 : la perspective

Translation: [ Google | Babelfish ]

Categories: [ Science ]

Le grandissement


La hauteur h' de l'image d'un objet de hauteur h situé à une distance l d'un objectif de distance focale f est donnée par h' = hf / l. Le rapport des tailles entre l'objet et l'image s'appelle le grandissement γ, et en supposant que f est négligeable devant l, γ est défini par

γ = h' / h = f / l

Le grandissement dépend donc uniquement de la distance à l'objet et de la distance focale de l'objectif.

La perspective

On peut donner une valeur chiffrée à la perspective en comparant les tailles des images de deux objets de même taille mais situés à des distances différentes. En supposant que la profondeur de champ est suffisante pour que les images des deux objets soient nettes, on peut noter h'1 et h'2 les hauteurs de ces images et l1 et l2 les distances des objets, puis calculer le rapport h'1 / h'2

h'1 / h'2 = l2 / l1

On constate que le rapport des hauteurs des images dépend seulement du rapport des distances des deux objets. En particulier, il ne dépend pas de la distance focale de l'objectif (si cette dernière est négligeable devant les distances aux objets).

Une conséquence de ce constat est qu'un objectif à longue focale n'« écrase » pas davantage les perspectives qu'un objectif à courte focale (si on ignore les déformations sur les bords de l'image dûs aux courtes focale). Cette conclusion est cohérente avec le fait qu'un objectif à courte distance focale associée à un capteur de petite taille donnera la même image (avec la même perspective) qu'un objectif de plus longue distance focale associé à un capteur plus grand (lorsque le rapport des distance focales de ces deux objectifs est égal au crop factor des deux capteurs).

[ Posted on September 14th, 2019 at 14:25 | 1 comment | ]

Friday, September 13th, 2019

Optique photo 2 : la profondeur de champ

Translation: [ Google | Babelfish ]

Categories: [ Science ]

Le flou


Lorsque la mise au point d'un système optique (simplifié) a été effectuée, l'image d'un objet ponctuel A situé à une distance l de l'objectif est un point A'. La mise au point est cependant imparfaite pour un objet B situé à une distance L plus (ou moins) loin que l : les rayons lumineux convergent en B' au lieu de A', continuent leur course et vont s'étaler autour de A' dans une zone de largeur ε. Sur le capteur d'un appareil photo, l'image B' de B est donc une tache circulaire dont le diamètre ε dépend de L, de la distance de mise au point l, de la distance focale f de la lentille et du diamètre d de la pupille, c'est-à-dire le trou circulaire par lequel la lumière entre dans la lentille, délimitée par le diaphragme. On considère ici que la pupille est suffisamment grande pour négliger les effets dûs à la diffraction.

On considère que l'image d'un point est net lorsque l'observateur (humain, en général) est incapable de faire la différence entre un « vrai » point et une tache. Le diamètre ε maximal d'une telle tache est lié au pouvoir de séparation de l'½il de l'observateur, qui est lié non seulement à la taille de la tache mais aussi à la distance à laquelle se situe l'observateur : plus l'observateur est éloigné, plus il est difficile de distinguer une petite tache d'un « vrai » point. La valeur de cet ε maximal n'est pas universelle, car elle dépend in fine de l'affichage de la photo (sur écran ou en tirage papier) et de la manière dont on regarde cette photo : il sera par exemple plus facile de remarquer un flou en regardant de près une image tirée en grand format qu'en regardant de loin une image tirée en petit format.

La profondeur de champ

D'après l'article de Wikipedia sur la Profondeur de champ, les limites de distance où les objets donneront une image considérée comme nette, une fois choisie un valeur ε maximale sont

L = l / (1 ± εl/df)

Les objets dont la distance est située entre ces limites auront une image considérée comme nette, car la tache de diamètre ε est trop petite pour être distinguée d'un « vrai » point net. On remarque que pour photographier un objet donné situé à une distance l en utilisant un objectif de distance focale f donnée, ces deux valeurs limites ne dépendent que du diamètre de la pupille, et pas du tout de la taille du capteur de l'appareil photo. Plus précisément, si les pixels du capteur sont plus grands que ε il n'y aura aucun flou car la tache circulaire sera contenue dans un seul pixel. Si les pixels sont plus petits que ε mais que les pixels à l'affichage de la photo (sur écran ou sur papier) sont plus grands que la tache circulaire, alors il n'y aura aucun flou à l'affichage, mais on pourra voir le flou en zoomant dans l'image (ce qui revient à dire que les pixels d'affichage deviennent plus petits que la tache circulaire).

Le nombre d'ouverture et la focale équivalente

La taille d de la pupille est exprimée dans la pratique non pas en millimètres, mais en fraction 1/N de la distance focale de l'objectif : d = f / N. N est le nombre d'ouverture et vaut donc N = f / d.

Si considère qu'en utilisant un objectif de distance focale f sur un capteur de taille différente on a une distance focale équivalente feq = kf, alors on peut écrire

N = feq / kd

où k est appelé « crop factor », soit

kN = feq / d

En introduisant la notion de nombre d'ouverture équivalent Neq défini par Neq = kN, la formule précédente devient

Neq = feq / d

qui est a la même forme que la définition du nombre d'ouverture, mais en utilisant le nombre d'ouverture équivalent et la focale équivalente.


On peut définir F = df/ε. En remplaçant le diamètre de la pupille d par son expression utilisant la distance focale et le nombre d'ouverture, on obtient

F = f2/εN

Les distances limites de la profondeur de champ deviennent alors

L = lF / (F ± l)

La distance hyperfocale F est la distance l minimale pour laquelle tous les objets entre F/2 et l'infini sont nets. Un corrolaire est que lors d'une mise au point à l'infini, tous les objets situés au delà d'une distance F sont nets.

Si on utilise la distance hyperfocale comme unité de distance de mise au point photographié, on peut représenter, de manière générique pour n'importe quelle combinaison de capteur, distance focale et nombre d'ouverture, les distances limites entre lesquelles les objets forment des images nettes (en supposant toujours que la distance focale est négligeable par rapport à la distance de mise au point).

En notant l = qF, on peut exprimer L en fonction de q et F:

L = qF / (1 ± q)

Le diagramme ci-contre indique les limites minimum et maximum de netteté pour une mise au point égale à une fraction de la distance hyperfocale F. On remarque que vers 0,9F, la limite maximum de netteté est déjà à 10F, et tend vers l'infini lorsqu'on se rapproche de 1F. Si on fait la mise au point à une valeur supérieure à F, la limite maximum de netteté reste à l'infini alors que la limite minimale n'augmente comparativement que très peu.


Le diagramme ci-contre représente les limites de netteté pour des distances de mise au point inférieures à 0,5F. On remarque que lors d'une mise au point à 0,5F, les objets situés au delà de F ne sont plus nets.

[ Posted on September 13th, 2019 at 18:54 | 4 comments | ]

Tuesday, September 10th, 2019

Optique photo 1: l'angle de champ

Translation: [ Google | Babelfish ]

Categories: [ Science ]

Le modèle simplifié


On peut simplifier un objectif d'appareil photo en le considérant comme une lentille mince de centre O et de distance focale f. L'axe optique de la lentille passe par O et est perpendiculaire à cette dernière. Les points F et F', situés de part et d'autre de la lentille à une distance f sont les points focaux de cette dernière. L'objet AB, situé à une distance l de la lentille donne alors une image A'B' sur le capteur de l'appareil photo. La hauteur de l'objet est h et la hauteur de l'image est h'.

Les règles de l'optique géométrique sont simples :

  • les rayons lumineux passant par le centre O de la lentille ne sont pas déviés, donc AA' et BB' sont des lignes droites ;
  • les rayons lumineux qui arrivent sur la lentille parallèlement à l'axe optique ressortent de la lentille en passant par le point focal F', tel le rayon BCB' ;
  • les rayons lumineux qui arrivent sur la lentille en passant par le point focal F ressortent parallèlement à l'axe optique, tel le rayon BDB'.

L'angle de champ

L'angle sous lequel l'objectif voit l'objet AB est l'angle θ. On a tan θ = h / l et tan α = h / (l - f) =  h' / f. On en déduit que tan θ = (l - f)h' / lf, que l'on peut simplifier (lorsque f est négligeable devant l) en tan θ = h' / f.

Si on considère que l'image A'B' et son symétrique (qui n'est pas représenté sur le diagramme) sont ensemble suffisamment grands pour couvrir l'intégralité de la diagonale du capteur, on sait que la hauteur h' de l'image représente la moitié de la diagonale d du capteur. Cela signifie que l'angle θ est la moitié de l'angle de champ du système objectif-capteur. On a alors

tan(angle de champ / 2) = d / 2f

Ceci montre que l'angle de champ dépend seulement de la distance focale de l'objectif et de la taille du capteur (lorsque f est négligeable devant l, soit en pratique lorsque la distance à l'objet est au moins dix fois plus grande que la distance focale).

Angle de champ et taille de capteur

Pour deux capteurs de tailles différents et avec un objectif donné, le rapport des tangentes des demi-angles de champ est égale au rapport des tailles des capteurs. Pour des angles de champ de moins de 53° (c.-à-d. lorsque la valeur de la tangente est proche de la valeur de l'angle, en radians), c'est à dire lorsque la distance focale est plus grande que la diagonale du capteur, on peut faire l'approximation que le rapport des angles de champ est égal au rapport des tailles des capteurs, avec une erreur de moins de 10%, soit

angle1 / angle2 = diagonale1 / diagonale2

Ainsi par exemple un capteur deux fois plus grand qu'un autre capteur donnera un angle de champ deux fois plus grand lorsque ces capteurs sont munis d'objectifs de même distance focale.

Si on veut une approximation qui fonctionne aussi pour de grands angles, il devient nécessaire de comparer les tangentes des demi-angles de champ, par exemple

tan(angle1 / 2) / tan(angle2 / 2) = diagonale1 / diagonale2

ou de faire intervenir les arctangentes, par exemple

angle1 / angle2 = arctan(diagonale1 / 2f) / arctan(diagonale2 / 2f)

ce qui est nettement moins pratique à évaluer de tête.

La distance focale équivalente

Supposons que l'on a un capteur de référence dont la diagonale est dref (par exemple un capteur 24×36 mm) et un objectif de distance focale f.

Lorsqu'on utilise cet objectif avec un autre capteur dont la diagonale est d, on a un angle de champ θ défini par

tan(θ / 2) = d / f

On veut alors savoir quelle serait la distance focale équivalente feq d'un objectif fictif donnant le même angle de champ θ si on utilisait cet objectif fictif avec le capteur de référence. On a

tan(θ / 2) = d / f =  dref / feq

et donc

feq = f × dref / d

(cette valeur est correcte si f est négligeable par rapport à l et par rapport à ld / dref).

Comparée à l'angle de champ, le calcul de la distance focale équivalente n'est pas limitée à des angles de champ suffisamment petits. La distance focale équivalente fait cependant appel à un facteur caché, la taille du capteur de référence.

[ Posted on September 10th, 2019 at 22:47 | 3 comments | ]

Wednesday, September 4th, 2019


Categories: [ Blog ]

For years, the comments were systematically rejected on the blog because most of them were spam, and I didn't have a good way of filtering them out. A CAPTCHA would have been a solution, but I read that the ones based on warped text are easily defeated, and I didn't want to bother with images anyway.

I recently rediscovered the idea of a CAPTCHA based on arithmetics, which I now have implemented. The poster of a comment must do a simple arithmetics operation involving two single digit numbers and one operator. It should not be difficult to defeat, but arithmetics CAPTCHAs are apparently uncommon, so it is likely that most bots don't implement such solvers. It has already repelled a couple of spam comments today.

[ Posted on September 4th, 2019 at 22:34 | no comment | ]

Monday, August 12th, 2019

Roulette et Martingale

Translation: [ Google | Babelfish ]

Categories: [ Science ]

Supposons qu'on joue à la roulette dans un casino. La roulette est honnête, donc tous les chiffres ont la même probabilité de sortir. Cette roulette comporte 18 numéros rouges, 18 numéros numéros noirs et un zéro qui n'est ni rouge ni noir. Les paris se font par tranche de 1 Euro, et sont limités à 100 Euros. On entre au casino avec 127 Euros en poche, et on parie toujours sur le rouge et on ne réinvestit pas ce qu'on a gagné, c'est à dire qu'on arrête de jouer une fois qu'on a perdu les 127 Euros de départ.

À chaque jeu on a donc p = 18/37 chances de gagner (soit un peu moins d'une chance sur deux).

Si on fait n = 127 paris de 1 Euro, la probabilité de gagner k fois (0 <= k <= n) est de P(X = k) = Comb( n, k) p k (1 - p) n - k (il s'agit d'une Loi binomiale). Les gains après k victoires sur n parties sont de 2 k - n. On peut donc calculer la somme des P(X = k) pour tous les k tels que 2 k - n > 0, ce qui donne la probabilité de quitter le casino avec en poche plus d'Euros que lorsqu'on y est entré. Cette probabilité est de 0,38, c'est à dire qu'on a 38% de chance de quitter le casino avec au moins 128 Euros et au plus 254 Euros. Cela signifie aussi qu'on a 62% de chance de n'avoir rien gagné, voire perdu tout ou partie des 127 Euros initiaux. En fait, on peut s'attendre, en moyenne, à perdre 3,43 Euros.

Jouer une martingale consiste à doubler la mise si on perd, afin que le gain couvre les pertes passées. La table de roulette ayant une limite de 100 Euros, on peut donc miser au plus m = 7 fois de suite en doublant la mise à chaque jeu, soit 1 + 2 + 4 + 8 + 16 + 32 + 64 = 127 Euros (ça tombe bien, c'est justement la somme qu'on avait en entrant au casino). La probabilité de gagner au bout de i jeux (donc de perdre i - 1 fois puis de gagner une fois) est P(X = i) = (1 - p) i - 1 p (il s'agit d'une Loi géométrique). On peut donc calculer la somme de ces probabilités pour i = 1 … m, qui représente la probabilité de gagner 1 Euro en utilisant la martingale. Cette probabilité est de 1 - (1 - p) m = 0,99. Cela signifie qu'on a 99% de chance de gagner 1 Euro, et donc 1% de chance de perdre 127 Euros. On peut donc s'attendre à perdre en moyenne 0,21 Euro.

Si on joue la martingale plusieurs fois de suite aussi longtemps que l'on ne perd pas, on peut espérer gagner à répétition, mais la probabilité de ne jamais perdre diminue à mesure que l'on joue (il s'agit encore une fois d'une loi géométrique, cette fois avec avec p = 0,99). Si on parvient à gagner en jouant la martingale au moins 128 fois de suite, alors on est certain qu'au moment où la chance tourne et que l'on perd, on ne perd que 127 Euros, et donc qu'on quitte le casino avec en poche au moins 1 euro de plus que lorsqu'on y est entré. Cette probabilité est de 0,298. Cela signifie qu'on a 29,8% de chance de sortir du casino avec au moins 128 Euros en poche. Cela signifie aussi qu'on a 70,2% de chance de perdre entre 0 et 127 Euros. En fait, on peut s'attendre à perdre en moyenne 21,8 Euros. Si on compare cette méthode avec la précédente qui consiste à miser 127 fois 1 Euro sur le rouge, on voit que le chances de ne pas perdre d'argent sont plus élevées si on n'utilise pas la martingale, et que les gains moyens sont moins mauvais lorsqu'on n'utilise pas la martingale (ils sont cependant toujours négatifs, c'est à dire qu'on y perd toujours de l'argent, en moyenne).

Jouer la martingale a cependant une utilité : la probabilité de gain élevé est plus grande avec la martingale que sans. Par exemple la probabilité de gagner au moins 10 Euros est de 11,6% sans martingale et 27,1% avec la martingale. Pour 20 Euros ou plus, la probabilité est d'à peine 1.9% sans martingale et de 24,7% avec la martingale, et on a encore environ 10% de chances de gagner au moins 115 Euros avec la martingale alors que cette probabilité est de moins d'une sur cent milliards sans martingale.

[ Posted on August 12th, 2019 at 07:31 | no comment | ]

Saturday, June 22nd, 2019

Nouvelle table

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]


[ Posted on June 22nd, 2019 at 13:02 | no comment | ]

Friday, September 7th, 2018

Jour 8

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]

nouvelle_cuisine_2018-09-07_1 nouvelle_cuisine_2018-09-07_2 nouvelle_cuisine_2018-09-07_3

[ Posted on September 7th, 2018 at 18:16 | no comment | ]

Thursday, September 6th, 2018

Jour 7

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]

nouvelle_cuisine_2018-09-06_1 nouvelle_cuisine_2018-09-06_2

[ Posted on September 6th, 2018 at 18:07 | no comment | ]

Wednesday, September 5th, 2018

Jour 6

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]

nouvelle_cuisine_2018-09-05_1 nouvelle_cuisine_2018-09-05_2

[ Posted on September 5th, 2018 at 18:14 | no comment | ]

Tuesday, September 4th, 2018

Jour 5

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]

nouvelle_cuisine_2018-09-04_1 nouvelle_cuisine_2018-09-04_2

[ Posted on September 4th, 2018 at 17:42 | no comment | ]

Monday, September 3rd, 2018

Jour 4

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]

nouvelle_cuisine_2018-09-03_1 nouvelle_cuisine_2018-09-03_2

[ Posted on September 3rd, 2018 at 17:06 | no comment | ]

Friday, August 31st, 2018

Jour 3

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]

nouvelle_cuisine_2018-08-31_1 nouvelle_cuisine_2018-08-31_2

[ Posted on August 31st, 2018 at 23:45 | no comment | ]

Thursday, August 30th, 2018

Jour 2

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]


[ Posted on August 30th, 2018 at 23:45 | no comment | ]

Wednesday, August 29th, 2018

Jour 1

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]

nouvelle_cuisine_2018-08-29_1 nouvelle_cuisine_2018-08-29_2

[ Posted on August 29th, 2018 at 23:45 | no comment | ]

Saturday, August 25th, 2018

Ancienne cuisine

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]

nouvelle_cuisine_2018-08-25_1 nouvelle_cuisine_2018-08-25_2

[ Posted on August 25th, 2018 at 23:45 | no comment | ]

Thursday, August 23rd, 2018

Tas de meubles

Translation: [ Google | Babelfish ]

Categories: [ Cooking/Nouvelle cuisine ]

nouvelle_cuisine_2018-08-23_1 nouvelle_cuisine_2018-08-23_2

[ Posted on August 23rd, 2018 at 23:45 | no comment | ]

Wednesday, July 18th, 2018

Advertisement on the Web

Categories: [ Grumbling ]

The economic value of a Youtube video

The Youtube platform has been designed for the purpose of making all of its videos are available to anyone at anytime. Given its large amount of content, its suggestion algorithm and the ability to automatically play the next suggested video, a user of Youtube can consider that the stream of videos he wants to watch is limitless. This makes the stream a nonscarce resource i.e., a free good which, from an economics point of view has no value.

Asking users to pay for watching videos would indeed be a bad business move for Youtube, as many user would refuse to pay for watching content they currently get at no cost. Even the Youtube Red subscription, according to an Ars Technica article and related user comments, seems to be valuable not for it exclusive content, but for the extra features provided by the mobile player (download for offline viewing and background playing). Interestingly, these features are already provided by non-standard software, which is freely available. The exclusive content being apparently of little interest according to the article, the subscription seems to rather be “tax” on people who don't know they could legitimately get the same service for free.

Youtube being a business, it needs to get revenue, at least to support the cost of its large IT infrastructure. Since users are not charged any money, Youtube's revenue comes (exclusively?) from advertising (I guess that the collection of user behavioural data and their profiling is also sold to advertisers). Youtube's content therefore has value only insofar as it attracts potential consumers and exposes them to advertisement. The content itself does not matter to the platform, as long as users are drawn to it.

This is thus the foundation of the Web platforms and the Attention Economy, where the users' attention is the actual product being sold by the platforms. User profiling and Big Data is only a tool used for maximising the amount of attention being captured.

Advertisers are gamblers, you don't need to let them win

In 2004, Patrick Le Lay, then CEO of the French TV channel TF1 said “what we sell to Coca-Cola is available human brain-time.” One aspect of this is that TV channels sell advertisers the opportunity to reach the channel's viewers and attempt to influence them into buying the advertised products.

From the point of view of the advertisers, it is a gamble: they bet money (the cost of producing the TV commercial and the price paid to the TV channel to show the commercial) and hope to gain from it (when viewers buy their products because they have seen the commercial and have been influenced by it). This gamble is two-fold: the viewers may or may not see the commercial, and they may or may not be receptive to the influence techniques used in the commercial.

In social media, advertisement can be considered from the same angle: the website sells advertisers display space on its pages, and the advertisers gamble that users of the website will see the commercial and be influenced by it.

While everybody agrees that you don't have a moral obligation to buy a product after you have seen a commercial, it seems less obvious that you don't either have any moral obligation to view the commercial. For example, you could close your eyes and plug your ears to ignore the commercial; you could even use tools that automatically hide the commercial from you.

Putting the advertisers' money to uses I approve of

As I want to protect myself from the influence of advertisers, I normally use automated tools that prevent me from seeing advertisement. I record television programs and skip the commercials (automatically when the tool works as intended, otherwise manually), and I use ad blockers in my Web browser to prevent commercials from being displayed on my screen.

The difference between TV and the Web is that the TV channel gets paid for broadcasting the commercials and cannot control whether or not I skip them while advertisers on the Web pay the websites only if the commercial is being fetched i.e., only if I allow the Web browser under my control to display the commercial. In that case, my preferred solution would be to fetch the commercial as if it would be displayed, without actually displaying it. And since advertisers not only display commercials but also track the users across websites, it is necessary to isolate each commercial so that the tracking is not possible.

I would not normally care about websites not being paid by their advertisers, but in the particular case of Youtube, I use tools that allow me to watch content without having to view any commercial, meaning that the content's creators cannot hope to get payment from Youtube. I therefore dream of a tool that would allow me to channel advertisers' money to the content creators without having to view any commercial, thus letting the advertisers gamble, but strongly shifting the odds of this gamble in my favor. I consider this to be retribution for the advertisers' attempt at influencing me for their profit.

[ Posted on July 18th, 2018 at 16:34 | no comment | ]

Tuesday, May 22nd, 2018

ANSI colors and Solarized

Categories: [ IT ]

The Solarized color scheme redefines some of the standard basic ANSI colors, making some color combinations unsuitable for display. In particular, bright green, bright yellow, bright blue and bright cyan are tones of grey instead of the expected colors.

Also, some terminals interpret bold text as bright colors, turning e.g, bold green into a shade of grey instead of the expected green. At least in URxvt, setting intensityStyles: False will prevent bold text from being displayed in bright colors (but will still be displayed in a bold font).

When redefining color schemes for terminal applications using ANSI colors, these are possible combinations, using the usual ANSI color names. Note that bright colors are usually not available as background colors.

Solarized Light default background (ANSI white)

Normal: black red green yellow blue magenta cyan (light)grey

Bright: black/grey red green yellow blue magenta cyan white

ANSI (Light)Grey background

Normal: black red green yellow blue magenta cyan (light)grey

Bright: black/grey red green yellow blue magenta cyan white

Solarized Dark default background (ANSI bright black/grey)

Normal: black red green yellow blue magenta cyan (light)grey

Bright: black/grey red green yellow blue magenta cyan white

ANSI Black background

Normal: black red green yellow blue magenta cyan (light)grey

Bright: black/grey red green yellow blue magenta cyan white

[ Posted on May 22nd, 2018 at 23:21 | no comment | ]

Sunday, February 18th, 2018

From How Far to Watch TV?

Categories: [ Science ]

The distance l from the TV depends on the desired horizontal viewing angle a, the screen's diagonal d and the number N of pixels on a row. Additionally, we will assume the screen's aspect ratio r to be 16:9 and the human eye's smallest angle that can be seen e to be 31.5 arcseconds.

Let R be the ratio between the diagonal and the width of the screen:

R = √(1 + 1/r2)

We can then write a relationship between a, N and e:

tan(a / 2) = NR tan(e / 2)    (1)

From (1) we can deduce that for any given e, there is a maximum horizontal viewing angle amax above which pixels can theoretically be distinguished.

For N = 1920 (FullHD), amax = 19.1°. With a 4K screen, amax = 37.2°.

We can also write a relationship between horizontal viewing angle, screen diagonal and distance:

d / l = 2 R tan(a / 2)    (2)

The ideal value or a is a matter of debate, but THX defines a horizontal viewing angle of at least 36° (the screen viewed from the rear seat of a THX theatre), while SMPTE suggests 30°. A value of 20° is also mentioned.

With a 4K screen, amax = 37.2° and (2), we draw that the ideal distance is 1.30 times the screen's diagonal. For example:

  • 132 cm for a 40" screen
  • 165 cm for a 50" screen
  • 197 cm for a 60" screen

With a = 30°, the ideal screen distance is 1.63 times the screen's diagonal. For example:

  • 132 cm for a 32" screen
  • 166 cm for a 40" screen
  • 207 cm for a 50" screen
  • 248 cm for a 60" screen

With a FullHD screen and a compromise angle a = 20°, the ideal distance is 2.47 times the screen diagonal. For example:

  • 201 cm for a 32" screen
  • 251 cm for a 40" screen
  • 314 cm for a 50" screen

EDIT: The value of e is valid for a high contrast between two pixels. Most images do not have such a high contrast, and therefore a value of e = 1 arcminute is a reasonnable assumption in practice.

From this follows that for N = 1920 (FullHD), amax = 35.5° (1.36 times the screen diagonal). With a 4K screen, amax = 65.3° (0.68 times the screen diagonal). This also gives a reasonnable value for standard definition PAL TV with N = 1024, amax = 19.4° (2.55 times the screen diagonal).

That would allow for larger horizonat viewing angles, such as 45° (1.05 times the screen diagonal) or 60° (0.75 times the screen diagonal) when viewing a 4K screen. At such short distances one must however take into account the possible lack of comfort due to the physical closeness of smaller screens.

[ Posted on February 18th, 2018 at 18:41 | no comment | ]

Saturday, January 27th, 2018

Petits pains pour hamburgers

Translation: [ Google | Babelfish ]

Categories: [ Cooking ]

Recette adaptée de la recette de pain de mie du blog Pique-assiette.


  • 300 g farine
  • 3 g levure sèche
  • 1 c. café poudre à lever
  • 1 c. soupe sucre
  • 200 mL lait tiède
  • 5 g sel
  • 30 g beurre mou
  • huile
  • 9 cercles métalliques de 9 cm de diamètre, huilés à l'intérieur
  • plaque à four et papier cuisson
  • lèche-frite (profonde si possible)
  • pinceau


  • Mélanger tous les ingrédients secs, ajouter le lait et mélanger.
  • Ajouter le beurre et pétrir 5 min
  • Laisser reposer 1 h
  • Former 9 boules (de 61 g chacune), les étirer en disque et placer chaque disque sur la plaque couverte du papier cuisson, au centre d'un cercle
  • Passer le desssus des disque à l'eau à l'aide du pinceau
  • Laisser reposer 2 h
  • Préchauffer le four à 200 °C. Une fois chaud, y placer sur le fond la lèche-frite avec 1 L d'eau bouillante
  • Cuire les petits pains pendant 15 min
  • Les petits pains se conservent plusieurs jours dans une boite hermétique

[ Posted on January 27th, 2018 at 23:54 | no comment | ]

Wednesday, June 7th, 2017


Categories: [ IT ]

Third part of my DNS setup notes: changing the DNSSEC config from NSEC to NSEC3. This has be on my TODO list for over a year now, and despite the tutorial at the ISC Knowledge Base, the ride was a bit bumpy.

Generating new keys

The previous keys were using the default RSASHA1 algorithm (number 5), and we need new keys using RSASHA256 (number 8).

Generating those keys was easy. On a machine with enough available entropy in /dev/random (such as a Raspberry Pi with its hardware random number generator) run:
dnssec-keygen -a RSASHA256 -b 2048 -3 example.com
dnssec-keygen -a RSASHA256 -b 2048 -3 -fk example.com

Transfer the keys to the server where Bind is running, into the directory where Bind is looking for them.

Loading the keys

The documentation says to load the keys with
rndc loadkeys example.net
but that ended with a cryptic message in the logs:
NSEC only DNSKEYs and NSEC3 chains not allowed

Apparently, the algorithm of the old keys does not allow to use NSEC3 (which I knew) so Bind refuses to load these keys (which I didn't anticipate). I eventually resorted to stopping Bind completely, moving away the old keys, deleting the *.signed and *.signed.jnl files in /var/cache/bind/ and restarting Bind. The new keys got then automatically loaded, and the zone was re-signed using NSEC.

NSEC3 at last

I could then resume with the tutorial.

First, generate a random salt:

openssl rand -hex 4
(let's assume the result of that operation was “d8add234”).

Then tell Bind the parameters it needs to create NSEC3 records:
rndc signing -nsec3param 1 0 10 d8add234 example.com.
Then check that the zone is signed with
rndc signing -list example.com

Linking the zones

Since the keys have changed, you need to update your domain's DS record in your parent domains DNS, using the tool provided to you by your registrar. This step is the same as in the “Linking the zones” of the previous part of this tutorial.

[ Posted on June 7th, 2017 at 23:15 | no comment | ]

Sunday, April 2nd, 2017

Gâteau arc-en-ciel

Translation: [ Google | Babelfish ]

Categories: [ Cooking ]

Préparer une recette de gâteau au yaourt simple. J'ai ajouté 12g de sucre vanillé pour le goût. À la place du moule à cake, utiliser un moule à manqué beurré et fariné.


Séparer la pâte en six portions, et colorer chaque portion avec un colorant alimentaire (rouge, jaune plus rouge pour l'orange, jaune, vert, bleu et rouge plus bleu pour le violet)


Verser la pâte rouge au centre du moule. Par dessus, verser lentement la pâte orange en la faisant tomber d'une hauteur la plus faible possible. Si possible, tourner le moule d'un quart de tour au cours du versement pour que la pâte forme un disque plutôt qu'une ellipse.


Verser de la même manière le jaune, puis le vert, le bleu et le violet.


Cuire 20-25 min au four à 200 °C. Une aiguille enfoncée dans le gâteau doit ressortir propre.


Déguster avec les yeux avant de manger :)

[ Posted on April 2nd, 2017 at 18:35 | no comment | ]

Sunday, February 19th, 2017

Cookies aux pépites de chocolat

Translation: [ Google | Babelfish ]

Categories: [ Cooking ]

Une recette adaptée de Cookwise avec des ingrédients disponibles en Finlande.


Pour environ 32 cookies.

  • 140g + 20g beurre doux, mou
  • 125g noix de pécan
  • 200g fariinisokeri
  • 1 c. à soupe extrait de vanille liquide Dr. Oetker
  • 1 petit oeuf (53g)
  • 200g Kinnusen myllyn luomu vehnäjauho (10.4 % de protéines)
  • 1,5 c. à café poudre à lever
  • 1 pincée de sel
  • 80g chocolat noir 72% Pirkka luomu
  • 100g chocolat au lait Panda


  • Préchauffer le four à 180 °C, étaler les noix de pécan sur une plaque à biscuits.
  • Mélanger (30s au mixeur) la farine, la levure chimique et le sel.
  • Battre (au batteur éléctrique équipé d'un fouet) 140g de beurre avec le sucre et l'extrait de vanille pour obtenir un mélange homogène. Ajouter l'oeuf et continuer à battre.
  • Faire griller les noix de pécan au four pendant 8 min.
  • Incorporer peu à peu le mélange de farine à l'appareil.
  • Lorsque les noix sont grillées, les mélanger à 20g de beurre et laisser refroidir un peu.
  • Hacher le chocolat au couteau en morceaux d'au plus 5mm et mélanger brièvement à l'appareil.
  • Hacher les noix au couteau en morceaux d'au plus 5mm et mélanger brièvement à l'appareil.
  • Laisser reposer 2h30 au réfrigérateur.
  • Préchauffer le four à 180 °C.
  • Placer 8 ou 9 boules de pâte grossièrement formées d'environ 35 – 40mm de diamètre sur une plaque en aluminium recouverte d'une feuille de cuisson réutilisable. Replacer le reste de pâte au réfrigérateur.
  • Cuire pendant 10 min au four à chaleur tournante.
  • Laisser refroidir 5 min puis déplacer les cookies à l'aide d'une spatule sur une grille.
  • Répéter les trois étapes précédentes avec le reste de la pâte.
  • Conserver dans une boite à biscuits en métal.

[ Posted on February 19th, 2017 at 11:45 | 1 comment | ]

Sunday, February 5th, 2017

SSH access to a Buffalo LS210 NAS

Categories: [ IT ]

My old NAS that I use for backups is now over 10 years old, and while it still works and faithfully backs-up my files every night, it has an always increasing probability to fail.

I decided to replace it with a Buffalo Linkstation 210, that offers 2 TB of space for 140 EUR, making it cheaper than building my own device, at the risk of not being able to use it the way I want it, being a commercial device that wasn't designed with my needs in mind.

The way I want to use the NAS is that it boots automatically at a given time, after which the backup script on the desktop starts, transfers the needed files, and puts the NAS to sleep mode again. That last feature was available on my previous device, but not anymore on the LS210. Hence the need to make it do my bidding.

Moreover, the Web UI for administrating the LS210 is horribly slow on my desktop due to bad Javascript code, so the less I have to use it, the better.

The device

The way to gain SSH access seems to vary depending on the exact version of the device and the firmware. Mine is precisely a LS210D0201-EU device with firmware version 1.63-0.04, bought in January 2017.

Initial setup

I found instructions on the nas-central.com forum. It relies on a Java tool called ACP_COMMANDER that apparently uses a backdoor of the device that is used for firmware updates and whatnots, but can apparently be used for running any kind of shell command on the device, as root, using the device's admin user's password.

Let's assume $IP is the IP address of the device and "password" is the password of the admin user on the device (it's the default password).

You can test that ACP_COMMANDER works with the following command that runs uname -a on the device:
java -jar acp_commander.jar -t $IP -ip $IP -pw password -c "uname -a"
It will output some amount of information (including a weird message about changing the IP and a wrong password ), but if you find the following in the middle of it, it means that it worked:
>uname -a
Linux LS210D 3.3.4 #1 Thu Sep 17 22:55:58 JST 2015 armv7l GNU/Linux

Starting the SSH server is then a matter of

  • enabling the SSH feature (which, on this cheap model, is disabled by default),
  • starting the SSH server,
  • changing root's password to "root" so that we can login (the password can be changed to something more secure later).
This is achieved through the following commands:
java -jar acp_commander.jar -t $IP -ip $IP -pw password -c "sed -i 's/SUPPORT_SFTP=0/SUPPORT_SFTP=1/g' /etc/nas_feature"
java -jar acp_commander.jar -t $IP -ip $IP -pw password -c "/etc/init.d/sshd.sh start"
java -jar acp_commander.jar -t $IP -ip $IP -pw password -c "(echo root;echo root)|passwd"

On some older version of the firmware, root login was disabled in SSH, and needed to be allowed with

java -jar acp_commander.jar -t $IP -ip $IP -pw password -c "sed -i 's/#PermitRootLogin/PermitRootLogin/g' /etc/sshd_config"
but that is not the case on my device.

Once this is done, I can run
ssh root@$IP

and login with password "root" (which was set earlier).

One nasty feature of the device is that the /etc/nas_feature file gets rewritten on each boot through the initrd. One last step is then to edit /etc/init.d/sshd.sh and to comment out near the beginning of the file the few lines that check for the SSH/SFTP support and exit in case SSH is not supported:
 #if [ "${SUPPORT_SFTP}" = "0" ] ; then
 #        echo "Not support sftp on this model." > /dev/console
 #        exit 0                                               

According to a comment on the nas-central forum,

“The /etc/nas_feature is restored on each reboot, so sshd does not run on boot. Even if you change the init script.”

but I found this not to be true.

I checked that this setup really resists reboots, by logging in as root and typing reboot. SSH access was still possible after the device had restarted.

Going further

It was then possible to setup SSH access using keys; RSA and ECDSA are supported but not ED25519.

One missing feature is the sudo command, but I can live without it I guess.

I have then setup the device to wake up at a given time (through the “Sleep timer” feature in the administration Web UI). It is then possible to put the device to sleep by running as root
PowerSave.sh standby
The command is located in /usr/local/sbin, and this path is not available for non-interactive logins, so I wrote the following wrapper script to shutdown the device:
ssh root@$IP 'bash -l -c "PowerSave.sh standby"'

After having been put into standby, the device will then start automatically on the set time, or when the “function” button on the back is pressed.

[ Posted on February 5th, 2017 at 12:52 | no comment | ]