Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Licence CC BY-NC-ND Thierry Parmentelat & Arnaud Legout Inria - UCA

Complément - niveau basique

Même si les tableaux contiennent habituellement des nombres, on peut être amenés à faire des opérations logiques et du coup à manipuler des tableaux de booléens. Nous allons voir quelques éléments à ce sujet.

import numpy as np

Opérations logiques

On peut faire des opérations logiques entre tableaux exactement comme on fait des opérations arithmétiques.

On va partir de deux tableaux presque identiques. J’en profite pour vous signaler qu’on peut copier un tableau avec, tout simplement, np.copy :

a = np.arange(25).reshape(5, 5)
print(a)
[[ 0  1  2  3  4]
 [ 5  6  7  8  9]
 [10 11 12 13 14]
 [15 16 17 18 19]
 [20 21 22 23 24]]
b = np.copy(a)
b[2, 2] = 1000
print(b)
[[   0    1    2    3    4]
 [   5    6    7    8    9]
 [  10   11 1000   13   14]
 [  15   16   17   18   19]
 [  20   21   22   23   24]]

Dans la lignée de ce qu’on a vu jusqu’ici en matière de programmation vectorielle, une opération logique va ici aussi nous retourner un tableau de la même taille :

# la comparaison par == ne nous
# retourne pas directement un booléen
# mais un tableau de la même taille que a et b
print(a == b)
[[ True  True  True  True  True]
 [ True  True  True  True  True]
 [ True  True False  True  True]
 [ True  True  True  True  True]
 [ True  True  True  True  True]]

all et any

Si votre intention est de vérifier que les deux tableaux sont entièrement identiques, utilisez np.all - et non pas le built-in natif all de Python - qui va vérifier que tous les éléments du tableau sont vrais :

# oui
np.all(a == a)
np.True_
# oui
np.all(a == b)
np.False_
# oui
# on peut faire aussi bien
#   np.all(x)
# ou
#   x.all()
(a == a).all()
np.True_
# par contre : non !
# ceci n'est pas conseillé
# même si ça peut parfois fonctionner
try:
    all(a == a)
except Exception as e:
    print(f'OOPS {type(e)} {e}')
OOPS <class 'ValueError'> The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

C’est bien sûr la même chose pour any qui va vérifier qu’il y a au moins un élément vrai. Comme en Python natif, un nombre qui est nul est considéré comme faux :

np.zeros(5).any()
np.False_
np.ones(5).any()
np.True_

Masques

Mais en général, c’est rare qu’on ait besoin de consolider de la sorte un booléen sur tout un tableau, on utilise plutôt les tableaux logiques comme des masques, pour faire ou non des opérations sur un autre tableau.

J’en profite pour introduire une fonction de matplotlib qui s’appelle imshow et qui permet d’afficher une image :

import matplotlib.pyplot as plt
%matplotlib inline
plt.ion()
<contextlib.ExitStack at 0x7fd503a74b90>
# construisons un disque centré au milieu de l'image

width = 128
center = width / 2

ix, iy = np.indices((width, width))
image = (ix-center)**2 + (iy-center)**2
# pour afficher l'image en niveaux de gris
plt.imshow(image, cmap='gray');
<Figure size 640x480 with 1 Axes>

Maintenant je peux créer un masque qui produise des rayures en diagonale, donc selon la valeur de (i+j). Par exemple :

# pour faire des rayures
# de 6 pixels de large
rayures = (ix + iy) % 8 <= 5
plt.imshow(rayures, cmap='gray');
<Figure size 640x480 with 1 Axes>
# en fait c'est bien sûr
# un tableau de booléens
print(rayures)
[[ True  True  True ...  True False False]
 [ True  True  True ... False False  True]
 [ True  True  True ... False  True  True]
 ...
 [ True False False ...  True  True  True]
 [False False  True ...  True  True  True]
 [False  True  True ...  True  True False]]

je vous montre aussi comment inverser un masque parce que c’est un peu abscons :

# on ne peut pas faire 
try:
    anti_rayures = not rayures
except Exception as e:
    print(f"OOPS - {type(e)} - {e}")
OOPS - <class 'ValueError'> - The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

on ne peut pas non plus faire rayures.not(), parce not est un mot clé

# on a le choix entre utiliser
#   rayures.logical_not() 

anti_rayures = np.logical_not(rayures)
plt.imshow(anti_rayures, 
           cmap='gray');
<Figure size 640x480 with 1 Axes>
# ou encore l'opérateur ~ 
# qui fait un not bitwise

anti_rayures = ~rayures
plt.imshow(anti_rayures,
           cmap='gray');
<Figure size 640x480 with 1 Axes>

Maintenant je peux utiliser le masque rayures pour faire des choses sur l’image. Par exemple simplement :

# pour effacer les rayures
plt.imshow(image*rayures, cmap='gray');
<Figure size 640x480 with 1 Axes>
# ou garder l'autre moitié
plt.imshow(image*anti_rayures, cmap='gray');
<Figure size 640x480 with 1 Axes>
image
array([[8192., 8065., 7940., ..., 7817., 7940., 8065.], [8065., 7938., 7813., ..., 7690., 7813., 7938.], [7940., 7813., 7688., ..., 7565., 7688., 7813.], ..., [7817., 7690., 7565., ..., 7442., 7565., 7690.], [7940., 7813., 7688., ..., 7565., 7688., 7813.], [8065., 7938., 7813., ..., 7690., 7813., 7938.]], shape=(128, 128))
np.logical_not(image)
array([[False, False, False, ..., False, False, False], [False, False, False, ..., False, False, False], [False, False, False, ..., False, False, False], ..., [False, False, False, ..., False, False, False], [False, False, False, ..., False, False, False], [False, False, False, ..., False, False, False]], shape=(128, 128))

Expression conditionnelle et np.where

Nous avons vu en Python natif l’expression conditionnelle :

3 if True else 2
3

Pour reproduire cette construction en numpy vous avez à votre disposition np.where. Pour l’illustrer nous allons construire deux images facilement discernables. Et, pour cela, on va utiliser np.isclose, qui est très utile pour comparer que deux nombres sont suffisamment proches, surtout pour les calculs flottants en fait, mais ça nous convient très bien ici aussi :

np.isclose?

Pour élaborer une image qui contient un grand cercle, je vais dire que la distance au centre (je rappelle que c’est le contenu de image) est suffisamment proche de 642, ce que vaut image au milieu de chaque bord :

big_circle = np.isclose(image, 64 **2, 10/100)
plt.imshow(big_circle, cmap='gray');
<Figure size 640x480 with 1 Axes>
small_circle = np.isclose(image, 32 **2, 10/100)
plt.imshow(small_circle, cmap='gray');
<Figure size 640x480 with 1 Axes>

En utilisant np.where, je peux simuler quelque chose comme ceci :

mixed = big_circle if rayures else small_circle
# sauf que ça se présente en fait comme ceci :
mixed = np.where(rayures, big_circle, small_circle)
plt.imshow(mixed, cmap='gray');
<Figure size 640x480 with 1 Axes>

Remarquez enfin qu’on peut aussi faire la même chose en tirant profit que True == 1 et False == 0 :

mixed2 = rayures * big_circle + (1-rayures) * small_circle
plt.imshow(mixed2, cmap='gray');
<Figure size 640x480 with 1 Axes>