6.3. Criação de arrays

Todos os exemplos nas restantes páginas começam com um ndarray já disponível. Esta página é o catálogo de como esse array é criado. Existem quatro famílias de construtores:

  • A partir de um iterável Python – a forma habitual de literal / lista / tuplo.

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

  • Gerado como uma sequência – valores em intervalo ou com espaçamento uniforme.

  • A encapsular um buffer já em RAM – o caso periférico.

Todos os construtores aceitam a palavra-chave dtype= e assumem float por defeito. Os dados de sensor quase sempre necessitam de um dtype menor do que o predefinido.

Todos os exemplos abaixo começam 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 ter todos o mesmo comprimento, caso contrário é levantada ValueError

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

Um ndarray já existente é também um input válido; array() copia sempre. Para evitar a cópia quando não é necessária, use asarray()

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

6.3.2. Pré-preenchido com uma determinada forma

Quando a forma pretendida é conhecida mas o conteúdo ainda não, aloque o buffer antecipadamente e escreva nele mais tarde:

  • zeros() – preenchido com zeros.

  • ones() – preenchido com uns.

  • full() – preenchido com um dado valor.

  • empty() – alias de zeros() (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 inteiro simples (para um array 1-D) ou um tuplo.

6.3.3. Gerado como uma sequência

  • arange() – valores com espaçamento uniforme semelhante ao range() integrado, mas devolvendo sempre um ndarray

    np.arange(0, 10, 2)            # array([0, 2, 4, 6, 8])
    
  • linspace()num pontos igualmente 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 com espaçamento geométrico. start e stop são expoentes, não extremidades; 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 de modo a que uma função por pixel f(x, y) possa ser avaliada sobre toda uma grelha numa única chamada vetorizada. Dado um vetor-x de comprimento W e um vetor-y de comprimento H, meshgrid devolve duas matrizes H-por-W: X é o vetor-x repetido em todas as linhas, Y é o vetor-y repetido em todas as colunas, pelo 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) avalia então a função em cada célula da grelha numa única expressão. Um mapa de distância ao centro sobre um fotograma (H, W), por exemplo, é np.sqrt((X - cx)**2 + (Y - cy)**2) aplicado sobre as matrizes devolvidas por meshgrid().

6.3.4. Concatenação

concatenate() junta um tuplo 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)

Todos os inputs devem partilhar o mesmo dtype e ndim, e coincidir em todos os eixos exceto o de concatenação. concatenate() aloca um novo array suficientemente grande para conter todos os inputs e copia os dados, sendo portanto a ferramenta certa para junção pontual de arrays já existentes; é a ferramenta errada dentro de um ciclo de streaming, onde o padrão correto é pré-alocar o destino uma vez e escrever nele através de atribuição por fatia.

6.3.5. A encapsular um buffer existente

O construtor mais útil numa câmara é frombuffer(). Reinterpreta um buffer do tipo bytes existente 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

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

offset= salta um cabeçalho no início do buffer; count= limita o número de elementos lidos:

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

Este é o construtor correto sempre que um periférico entrega à aplicação um buffer em bruto – amostras de ADC num bytearray, um payload obtido de SPI. Os bytes escritos pelo periférico são o array.

Quando um periférico escreve valores de múltiplos bytes numa ordem de bytes que o CPU da câmara não lê nativamente, byteswap() inverte a ordem dos bytes de cada elemento para que os valores sejam lidos corretamente. Por defeito devolve um novo array; passando inplace=True modifica o original no lugar.

frombuffer() trata apenas os dtypes definidos pelo próprio numpy. Para periféricos que produzem amostras inteiras de 32 bits, from_int32_buffer() e funções semelhantes convertem para float numa única passagem.