6.3. Arrays maken

Elk voorbeeld op de rest van deze pagina’s begint met een ndarray die al in de hand is. Deze pagina is de catalogus van hoe die array tot stand komt. Er zijn vier families van constructors:

  • Vanuit een Python-iterable – de gebruikelijke literal / lijst / tuple-vorm.

  • Vooraf gevuld met een gegeven vorm – nullen, enen, een constante waarde, een eenheidsmatrix.

  • Gegenereerd als een reeks – waarden met een bereik of gelijkmatig verdeeld.

  • Een buffer omhullen die al in RAM staat – het randapparaatgeval.

Elke constructor neemt een dtype=-trefwoord aan en heeft standaard float. Sensordata wil vrijwel altijd een kleinere dtype dan de standaard.

Elk voorbeeld hieronder begint met:

from ulab import numpy as np

6.3.1. Vanuit een Python-iterable

array() bouwt een ndarray uit elke iterable van getallen:

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

Uitvoer:

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

Geneste iterables produceren multidimensionale arrays. De binnenste iterables moeten allemaal dezelfde lengte hebben, anders wordt ValueError opgeworpen:

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

Een reeds bestaande ndarray is ook een geldige invoer; array() kopieert altijd. Gebruik asarray() om de kopie te vermijden wanneer er geen nodig is:

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

6.3.2. Vooraf gevuld met een gegeven vorm

Wanneer de doelvorm bekend is maar de inhoud nog niet, alloceer dan de buffer vooraf en schrijf er later in:

  • zeros() – gevuld met nullen.

  • ones() – gevuld met enen.

  • full() – gevuld met een gegeven waarde.

  • empty() – alias voor zeros() (ulab laat de buffer niet ongeïnitialiseerd).

  • eye() – eenheidsmatrix-achtige N-bij-M-matrix met enen op de k-de diagonaal.

  • diag() – een diagonaalmatrix uit een vector, of de diagonaal van een matrix.

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

Het shape-argument is ofwel een enkel geheel getal (voor een 1-D-array) of een tuple.

6.3.3. Gegenereerd als een reeks

  • arange() – gelijkmatig verdeelde waarden zoals de ingebouwde range(), maar geeft altijd een ndarray terug:

    np.arange(0, 10, 2)            # array([0, 2, 4, 6, 8])
    
  • linspace()num gelijkmatig verdeelde punten tussen twee grenzen, waarbij de bovengrens is inbegrepen wanneer endpoint=True

    np.linspace(0, 1, num=11)      # 0.0, 0.1, ..., 1.0
    
  • logspace() – geometrisch verdeelde punten. start en stop zijn exponenten, geen eindpunten; het resultaat loopt van base ** start tot base ** stop

    np.logspace(0, 3, num=4)       # 1.0, 10.0, 100.0, 1000.0
    
  • meshgrid() – bouwt twee coördinaatmatrices uit twee 1-D-arrays zodat een functie per pixel f(x, y) over een heel raster kan worden geëvalueerd in één gevectoriseerde aanroep. Gegeven een x-vector van lengte W en een y-vector van lengte H, geeft meshgrid twee H-bij-W-matrices terug: X is de x-vector herhaald langs elke rij, Y is de y-vector herhaald over elke kolom, zodat X[i, j] de x-coördinaat en Y[i, j] de y-coördinaat is van de cel op rij i en kolom 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) evalueert vervolgens de functie op elke cel van het raster in één expressie. Een afstand-tot-het-midden-kaart over een (H, W)-frame is bijvoorbeeld np.sqrt((X - cx)**2 + (Y - cy)**2) toegepast op de matrices die meshgrid() teruggaf.

6.3.4. Samenvoegen

concatenate() voegt een tuple van arrays samen langs een bestaande as:

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)

Alle invoeren moeten dezelfde dtype en ndim delen, en overeenkomen op elke as behalve de samenvoegende. concatenate() alloceert een nieuwe array die groot genoeg is om elke invoer te bevatten en kopieert de data erin, dus het is het juiste gereedschap voor het in één keer samenvoegen van arrays die al bestaan; het is het verkeerde gereedschap binnen een streaming-lus, waar het vooraf eenmalig alloceren van de bestemming en daarin schrijven via slice-toewijzing het patroon is.

6.3.5. Een bestaande buffer omhullen

De nuttigste constructor op een camera is frombuffer(). Het herinterpreteert een bestaande bytes-achtige buffer als een 1-D ndarray zonder ook maar één byte te kopiëren

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

Schrijfacties via audio zijn zichtbaar in buf en vice versa. De gekozen dtype moet de bufferlengte gelijkmatig delen.

offset= slaat een header aan het begin van de buffer over; count= beperkt hoeveel elementen worden gelezen:

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

Dit is de juiste constructor wanneer een randapparaat de applicatie een ruwe buffer overhandigt – ADC-samples in een bytearray, een payload opgehaald uit SPI. De bytes die het randapparaat schreef zijn de array.

Wanneer een randapparaat multi-byte-waarden schrijft in een byte-volgorde die de CPU van de camera niet van nature leest, keert byteswap() de byte-volgorde van elk element om zodat de waarden correct worden gelezen. Het geeft standaard een nieuwe array terug; het meegeven van inplace=True wijzigt de bron ter plekke.

frombuffer() verwerkt alleen de dtypes die numpy zelf definieert. Voor randapparaten die 32-bits integer-samples produceren, converteren from_int32_buffer() en verwanten in één doorloop naar float.