6.3. Creare array

Ogni esempio nel resto di queste pagine parte da un ndarray già pronto. Questa pagina è il catalogo di come quell’array prende vita. Esistono quattro famiglie di costruttori:

  • Da un iterabile Python – la consueta forma con letterale / lista / tupla.

  • Pre-riempito a una forma data – zeri, uni, un valore costante, una matrice identità.

  • Generato come sequenza – valori in intervallo o equispaziati.

  • Avvolgendo un buffer già in RAM – il caso delle periferiche.

Ogni costruttore accetta una parola chiave dtype= e ha come valore predefinito float. I dati dei sensori vogliono quasi sempre un dtype più piccolo di quello predefinito.

Ogni esempio qui sotto parte da:

from ulab import numpy as np

6.3.1. Da un iterabile Python

array() costruisce un ndarray da qualsiasi iterabile di numeri:

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

Output:

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

Gli iterabili annidati producono array multidimensionali. Gli iterabili interni devono avere tutti la stessa lunghezza, altrimenti viene sollevata ValueError

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

Anche un ndarray preesistente è un input valido; array() copia sempre. Per evitare la copia quando non è necessaria, usa asarray()

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

6.3.2. Pre-riempito a una forma data

Quando la forma di destinazione è nota ma il contenuto non lo è ancora, alloca il buffer in anticipo e scrivici dentro in seguito:

  • zeros() – riempito con zeri.

  • ones() – riempito con uni.

  • full() – riempito con un valore dato.

  • empty() – alias di zeros() (ulab non lascia il buffer non inizializzato).

  • eye() – matrice N-per-M simile all’identità con uni sulla k-esima diagonale.

  • diag() – una matrice diagonale da un vettore, oppure la diagonale di una 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’argomento shape è un singolo intero (per un array monodimensionale) oppure una tupla.

6.3.3. Generato come sequenza

  • arange() – valori equispaziati come il built-in range(), ma restituendo sempre un ndarray

    np.arange(0, 10, 2)            # array([0, 2, 4, 6, 8])
    
  • linspace()num punti equispaziati tra due limiti, con il limite superiore incluso quando endpoint=True

    np.linspace(0, 1, num=11)      # 0.0, 0.1, ..., 1.0
    
  • logspace() – punti spaziati geometricamente. start e stop sono esponenti, non estremi; il risultato va da base ** start a base ** stop

    np.logspace(0, 3, num=4)       # 1.0, 10.0, 100.0, 1000.0
    
  • meshgrid() – costruisce due matrici di coordinate da due array monodimensionali in modo che una funzione per pixel f(x, y) possa essere valutata su un’intera griglia in una sola chiamata vettorizzata. Dato un vettore x di lunghezza W e un vettore y di lunghezza H, meshgrid restituisce due matrici H-per-W: X è il vettore x ripetuto lungo ogni riga, Y è il vettore y ripetuto lungo ogni colonna, così X[i, j] è la coordinata x e Y[i, j] è la coordinata y della cella alla riga i e colonna 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) valuta quindi la funzione in ogni cella della griglia in un’unica espressione. Una mappa della distanza dal centro su un frame (H, W), per esempio, è np.sqrt((X - cx)**2 + (Y - cy)**2) calcolata sulle matrici restituite da meshgrid().

6.3.4. Unire

concatenate() unisce una tupla di array lungo un asse esistente:

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)

Tutti gli input devono condividere lo stesso dtype e ndim e coincidere su ogni asse diverso da quello di unione. concatenate() alloca un nuovo array abbastanza grande da contenere ogni input e copia i dati al suo interno, quindi è lo strumento giusto per l’unione una tantum di array già esistenti; è lo strumento sbagliato all’interno di un ciclo di streaming, dove il pattern corretto è pre-allocare la destinazione una sola volta e scriverci dentro tramite assegnazione su slice.

6.3.5. Avvolgere un buffer esistente

Il costruttore più utile su una camera è frombuffer(). Reinterpreta un buffer di tipo bytes esistente come un ndarray monodimensionale senza copiare un solo byte:

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

Le scritture tramite audio sono visibili in buf e viceversa. Il dtype scelto deve dividere esattamente la lunghezza del buffer.

offset= salta un’intestazione all’inizio del buffer; count= limita quanti elementi vengono letti:

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

Questo è il costruttore giusto ogni volta che una periferica consegna all’applicazione un buffer grezzo – campioni ADC in un bytearray, un payload estratto da SPI. I byte che la periferica ha scritto sono l’array.

Quando una periferica scrive valori multi-byte in un ordine dei byte che la CPU della camera non legge nativamente, byteswap() inverte l’ordine dei byte di ogni elemento in modo che i valori vengano letti correttamente. Per impostazione predefinita restituisce un nuovo array; passando inplace=True modifica la sorgente sul posto.

frombuffer() gestisce solo i dtype che numpy stesso definisce. Per le periferiche che producono campioni interi a 32 bit, from_int32_buffer() e simili convertono in float in una sola passata.