6.3. Creación de arrays

Cada ejemplo del resto de estas páginas comienza con un ndarray ya disponible. Esta página es el catálogo de cómo se crea ese array. Hay cuatro familias de constructores:

  • A partir de un iterable de Python – la habitual forma literal / lista / tupla.

  • Rellenado previamente con una forma dada – ceros, unos, un valor constante, una matriz identidad.

  • Generado como una secuencia – valores en un rango o espaciados uniformemente.

  • Envolviendo un búfer ya presente en RAM – el caso de los periféricos.

Cada constructor acepta una palabra clave dtype= y su valor predeterminado es float. Los datos de sensores casi siempre requieren un dtype más pequeño que el predeterminado.

Cada ejemplo de abajo comienza con:

from ulab import numpy as np

6.3.1. A partir de un iterable de Python

array() construye un ndarray a partir de cualquier iterable de números:

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

Salida:

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

Los iterables anidados producen arrays multidimensionales. Los iterables internos deben tener todos la misma longitud, o se lanza ValueError:

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

Un ndarray ya existente también es una entrada válida; array() siempre copia. Para evitar la copia cuando no es necesaria, usa asarray():

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

6.3.2. Rellenado previamente con una forma dada

Cuando se conoce la forma de destino pero aún no el contenido, asigna el búfer por adelantado y escribe en él más tarde:

  • zeros() – relleno de ceros.

  • ones() – relleno de unos.

  • full() – relleno con un valor dado.

  • empty() – alias de zeros() (ulab no deja el búfer sin inicializar).

  • eye() – matriz de N por M similar a la identidad, con unos en la k-ésima diagonal.

  • diag() – una matriz diagonal a partir de un vector, o la diagonal de una matriz.

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

El argumento shape es o bien un único entero (para un array de 1-D) o una tupla.

6.3.3. Generado como una secuencia

  • arange() – valores espaciados uniformemente como el range() integrado, pero devolviendo siempre un ndarray:

    np.arange(0, 10, 2)            # array([0, 2, 4, 6, 8])
    
  • linspace()num puntos espaciados uniformemente entre dos límites, con el límite superior incluido cuando endpoint=True:

    np.linspace(0, 1, num=11)      # 0.0, 0.1, ..., 1.0
    
  • logspace() – puntos espaciados geométricamente. start y stop son exponentes, no extremos; el resultado va desde base ** start hasta base ** stop:

    np.logspace(0, 3, num=4)       # 1.0, 10.0, 100.0, 1000.0
    
  • meshgrid() – construye dos matrices de coordenadas a partir de dos arrays de 1-D para que una función por píxel f(x, y) pueda evaluarse sobre toda una cuadrícula en una sola llamada vectorizada. Dado un vector x de longitud W y un vector y de longitud H, meshgrid devuelve dos matrices de H por W: X es el vector x repetido en cada fila, Y es el vector y repetido en cada columna, de modo que X[i, j] es la coordenada x e Y[i, j] es la coordenada y de la celda en la fila i y la columna 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) evalúa entonces la función en cada celda de la cuadrícula en una sola expresión. Un mapa de distancia al centro sobre un fotograma de (H, W), por ejemplo, es np.sqrt((X - cx)**2 + (Y - cy)**2) aplicado a las matrices que devolvió meshgrid().

6.3.4. Unión

concatenate() une una tupla de arrays a lo largo de un eje existente:

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)

Todas las entradas deben compartir el mismo dtype y ndim, y coincidir en todos los ejes salvo el de unión. concatenate() asigna un array nuevo lo bastante grande para contener todas las entradas y copia los datos en él, por lo que es la herramienta adecuada para unir de una sola vez arrays que ya existen; es la herramienta equivocada dentro de un bucle de streaming, donde el patrón es preasignar el destino una vez y escribir en él mediante asignación por rebanadas.

6.3.5. Envolver un búfer existente

El constructor más útil en una cámara es frombuffer(). Reinterpreta un búfer existente de tipo bytes como un ndarray de 1-D sin copiar un solo byte:

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

Las escrituras a través de audio son visibles en buf y viceversa. El dtype elegido debe dividir exactamente la longitud del búfer.

offset= omite una cabecera al principio del búfer; count= limita cuántos elementos se leen:

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

Este es el constructor adecuado siempre que un periférico entrega a la aplicación un búfer en bruto – muestras de un ADC en un bytearray, una carga útil extraída de SPI. Los bytes que el periférico escribió son el array.

Cuando un periférico escribe valores de varios bytes en un orden de bytes que la CPU de la cámara no lee de forma nativa, byteswap() invierte el orden de bytes de cada elemento para que los valores se lean correctamente. Por defecto devuelve un array nuevo; pasar inplace=True modifica el origen en el sitio.

frombuffer() solo maneja los dtypes que numpy define por sí mismo. Para periféricos que producen muestras enteras de 32 bits, from_int32_buffer() y similares convierten a float en una sola pasada.