6.3. Criando arrays

Todo exemplo no restante destas páginas começa com um ndarray já em mãos. Esta página é o catálogo de como esse array passa a existir. Há quatro famílias de construtor:

  • A partir de um iterável Python – a forma usual de literal / list / tuple.

  • Pré-preenchido com uma dada forma – zeros, uns, um valor constante, uma matriz identidade.

  • Gerado como uma sequência – valores em faixa ou uniformemente espaçados.

  • Encapsulando um buffer já na RAM – o caso de periférico.

Todo construtor aceita uma palavra-chave dtype= e usa por padrão float. Dados de sensor quase sempre querem um dtype menor que o padrão.

Todo exemplo abaixo começa com:

from ulab import numpy as np

6.3.1. A partir de um iterável Python

array() constrói um ndarray a partir de qualquer iterável de números:

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

Saída:

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

Iteráveis aninhados produzem arrays multidimensionais. Os iteráveis internos devem todos ter o mesmo comprimento, ou ValueError é levantado:

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

Um ndarray pré-existente também é uma entrada válida; array() sempre copia. Para evitar a cópia quando ela não é necessária, use asarray()

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

6.3.2. Pré-preenchido com uma dada forma

Quando a forma de destino é conhecida mas o conteúdo ainda não, aloque o buffer antecipadamente e escreva nele depois:

  • zeros() – preenchido com zeros.

  • ones() – preenchido com uns.

  • full() – preenchido com um dado valor.

  • empty() – alias para zeros() (o ulab não deixa o buffer não inicializado).

  • eye() – matriz N-por-M semelhante à identidade com uns na k-ésima diagonal.

  • diag() – uma matriz diagonal a partir de um vetor, ou a diagonal de uma 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

O argumento shape é um único inteiro (para um array 1-D) ou uma tupla.

6.3.3. Gerado como uma sequência

  • arange() – valores uniformemente espaçados como o built-in range(), mas sempre retornando um ndarray

    np.arange(0, 10, 2)            # array([0, 2, 4, 6, 8])
    
  • linspace()num pontos uniformemente espaçados entre dois limites, com o limite superior incluído quando endpoint=True

    np.linspace(0, 1, num=11)      # 0.0, 0.1, ..., 1.0
    
  • logspace() – pontos geometricamente espaçados. start e stop são expoentes, não extremos; o resultado vai de base ** start a base ** stop

    np.logspace(0, 3, num=4)       # 1.0, 10.0, 100.0, 1000.0
    
  • meshgrid() – constrói duas matrizes de coordenadas a partir de dois arrays 1-D para que uma função por pixel f(x, y) possa ser avaliada sobre uma grade inteira em uma única chamada vetorizada. Dado um vetor x de comprimento W e um vetor y de comprimento H, meshgrid retorna duas matrizes H-por-W: X é o vetor x repetido em cada linha, Y é o vetor y repetido em cada coluna, de modo que X[i, j] é a coordenada x e Y[i, j] é a coordenada y da célula na linha i e coluna 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) então avalia a função em cada célula da grade em uma única expressão. Um mapa de distância-do-centro sobre um quadro (H, W), por exemplo, é np.sqrt((X - cx)**2 + (Y - cy)**2) contra as matrizes que meshgrid() retornou.

6.3.4. Juntando

concatenate() junta uma tupla de arrays ao longo de um eixo 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 as entradas devem compartilhar o mesmo dtype e ndim, e coincidir em todos os eixos exceto o de junção. concatenate() aloca um array novo grande o suficiente para conter todas as entradas e copia os dados para dentro, então é a ferramenta certa para junção única de arrays que já existem; é a ferramenta errada dentro de um laço de streaming, onde pré-alocar o destino uma vez e escrever nele por atribuição de fatia é o padrão.

6.3.5. Encapsulando um buffer existente

O construtor mais útil em uma câmera é frombuffer(). Ele reinterpreta um buffer existente do tipo bytes como um ndarray 1-D sem copiar um único byte:

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

Escritas através de audio são visíveis em buf e vice-versa. O dtype escolhido deve dividir uniformemente o comprimento do buffer.

offset= pula um cabeçalho no início do buffer; count= limita quantos elementos são lidos:

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

Este é o construtor certo sempre que um periférico entrega à aplicação um buffer bruto – amostras de ADC em um bytearray, um payload extraído de SPI. Os bytes que o periférico escreveu são o array.

Quando um periférico escreve valores de múltiplos bytes em uma ordem de bytes que a CPU da câmera não lê nativamente, byteswap() inverte a ordem de bytes de cada elemento para que os valores sejam lidos corretamente. Por padrão retorna um novo array; passar inplace=True modifica a origem no lugar.

frombuffer() só lida com os dtypes que o próprio numpy define. Para periféricos que produzem amostras de inteiros de 32 bits, from_int32_buffer() e funções relacionadas convertem para float em uma única passagem.