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 npOpé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');
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');
# 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');
# ou encore l'opérateur ~
# qui fait un not bitwise
anti_rayures = ~rayures
plt.imshow(anti_rayures,
cmap='gray');
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');
# ou garder l'autre moitié
plt.imshow(image*anti_rayures, cmap='gray');
imagearray([[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 23Pour 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');
small_circle = np.isclose(image, 32 **2, 10/100)
plt.imshow(small_circle, cmap='gray');
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');
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');