6.3. Créer des tableaux

Chaque exemple sur le reste de ces pages commence avec un ndarray déjà disponible. Cette page est le catalogue des façons dont ce tableau voit le jour. Il existe quatre familles de constructeurs :

  • À partir d’un itérable Python – la forme habituelle littéral / liste / tuple.

  • Pré-rempli à une forme donnée – des zéros, des uns, une valeur constante, une matrice identité.

  • Généré comme une séquence – des valeurs sur une plage ou régulièrement espacées.

  • Enveloppant un tampon déjà en RAM – le cas des périphériques.

Chaque constructeur prend un mot-clé dtype= et utilise par défaut float. Les données de capteur veulent presque toujours un dtype plus petit que la valeur par défaut.

Chaque exemple ci-dessous commence par

from ulab import numpy as np

6.3.1. À partir d’un itérable Python

array() construit un ndarray à partir de n’importe quel itérable de nombres

a = np.array([1, 2, 3, 4])
print(a)

Sortie

array([1.0, 2.0, 3.0, 4.0], dtype=float)

Les itérables imbriqués produisent des tableaux multidimensionnels. Les itérables internes doivent tous avoir la même longueur, sinon une ValueError est levée

m = np.array([[1, 2, 3],
              [4, 5, 6]], dtype=np.uint8)

Un ndarray préexistant est aussi une entrée valide ; array() copie toujours. Pour éviter la copie lorsqu’elle n’est pas nécessaire, utilisez asarray()

b = np.asarray(a, dtype=np.float)   # same dtype -> no copy

6.3.2. Pré-rempli à une forme donnée

Lorsque la forme cible est connue mais que le contenu ne l’est pas encore, allouez le tampon à l’avance et écrivez-y plus tard :

  • zeros() – rempli de zéros.

  • ones() – rempli de uns.

  • full() – rempli d’une valeur donnée.

  • empty() – alias de zeros() (ulab ne laisse pas le tampon non initialisé).

  • eye() – matrice de type identité N-par-M avec des uns sur la k-ième diagonale.

  • diag() – une matrice diagonale à partir d’un vecteur, ou la diagonale d’une matrice.

np.zeros((3, 3))                   # 3x3 of zeros
np.ones(5, dtype=np.uint8)         # length-5 vector of ones
np.full((2, 3), 7, dtype=np.int8)  # 2x3, all 7
np.eye(4)                          # 4x4 identity
np.diag([1, 2, 3])                 # 3x3, [1, 2, 3] on the diagonal

L’argument shape est soit un entier unique (pour un tableau 1-D), soit un tuple.

6.3.3. Généré comme une séquence

  • arange() – des valeurs régulièrement espacées comme la fonction intégrée range(), mais renvoyant toujours un ndarray

    np.arange(0, 10, 2)            # array([0, 2, 4, 6, 8])
    
  • linspace()num points régulièrement espacés entre deux limites, la borne supérieure étant incluse lorsque endpoint=True

    np.linspace(0, 1, num=11)      # 0.0, 0.1, ..., 1.0
    
  • logspace() – des points espacés géométriquement. start et stop sont des exposants, non des bornes ; le résultat va de base ** start à base ** stop

    np.logspace(0, 3, num=4)       # 1.0, 10.0, 100.0, 1000.0
    
  • meshgrid() – construit deux matrices de coordonnées à partir de deux tableaux 1-D afin qu’une fonction par pixel f(x, y) puisse être évaluée sur toute une grille en un seul appel vectorisé. Étant donné un vecteur x de longueur W et un vecteur y de longueur H, meshgrid renvoie deux matrices H-par-W : X est le vecteur x répété sur chaque ligne, Y est le vecteur y répété sur chaque colonne, de sorte que X[i, j] est la coordonnée x et Y[i, j] la coordonnée y de la cellule à la ligne i et à la colonne j

    x = np.arange(4)            # [0, 1, 2, 3]
    y = np.arange(3)            # [0, 1, 2]
    X, Y = np.meshgrid(x, y)
    # X = [[0, 1, 2, 3],
    #      [0, 1, 2, 3],
    #      [0, 1, 2, 3]]
    # Y = [[0, 0, 0, 0],
    #      [1, 1, 1, 1],
    #      [2, 2, 2, 2]]
    

    f(X, Y) évalue ensuite la fonction sur chaque cellule de la grille en une seule expression. Une carte de distance par rapport au centre sur une trame (H, W), par exemple, s’écrit np.sqrt((X - cx)**2 + (Y - cy)**2) appliquée aux matrices renvoyées par meshgrid().

6.3.4. Assemblage

concatenate() joint un tuple de tableaux le long d’un axe existant

a = np.array([[1, 2], [3, 4]], dtype=np.uint8)
b = np.array([[5, 6]],         dtype=np.uint8)
np.concatenate((a, b), axis=0)
# array([[1, 2], [3, 4], [5, 6]], dtype=uint8)

Toutes les entrées doivent partager le même dtype et le même ndim, et correspondre sur chaque axe autre que celui de la jonction. concatenate() alloue un tableau neuf assez grand pour contenir toutes les entrées et y copie les données ; c’est donc l’outil adapté pour une jonction ponctuelle de tableaux qui existent déjà ; c’est le mauvais outil à l’intérieur d’une boucle de diffusion en continu, où le motif consiste à préallouer la destination une fois et à y écrire par affectation de tranches.

6.3.5. Envelopper un tampon existant

Le constructeur le plus utile sur une caméra est frombuffer(). Il réinterprète un tampon de type bytes existant comme un ndarray 1-D sans copier un seul octet

buf = bytearray(8)
audio = np.frombuffer(buf, dtype=np.int16)
# 4 int16 samples, sharing memory with buf

Les écritures via audio sont visibles dans buf et vice versa. Le dtype choisi doit diviser exactement la longueur du tampon.

offset= saute un en-tête au début du tampon ; count= limite le nombre d’éléments lus

np.frombuffer(buf, dtype=np.uint8, offset=2, count=4)

C’est le bon constructeur chaque fois qu’un périphérique remet à l’application un tampon brut – des échantillons ADC dans un bytearray, une charge utile extraite d’un SPI. Les octets que le périphérique a écrits sont le tableau.

Lorsqu’un périphérique écrit des valeurs multi-octets dans un ordre d’octets que le CPU de la caméra ne lit pas nativement, byteswap() inverse l’ordre des octets de chaque élément afin que les valeurs soient lues correctement. Il renvoie un nouveau tableau par défaut ; passer inplace=True modifie la source sur place.

frombuffer() ne gère que les dtypes que numpy lui-même définit. Pour les périphériques qui produisent des échantillons entiers 32 bits, from_int32_buffer() et consorts convertissent en float en une seule passe.