6.3. Making arrays¶
Every example on the rest of these pages starts with an ndarray already in hand. This page is the catalogue of how that array comes to be. There are four families of constructor:
From a Python iterable – the usual literal / list / tuple form.
Pre-filled at a given shape – zeros, ones, a constant value, an identity matrix.
Generated as a sequence – ranged or evenly spaced values.
Wrapping a buffer already in RAM – the peripheral case.
Every constructor takes a dtype= keyword and defaults to float. Sensor data almost always wants a smaller dtype than the default.
Every example below starts with:
from ulab import numpy as np
6.3.1. From a Python iterable¶
array() builds an ndarray from any iterable of numbers:
a = np.array([1, 2, 3, 4])
print(a)
Output:
array([1.0, 2.0, 3.0, 4.0], dtype=float)
Nested iterables produce multi-dimensional arrays. The inner iterables must all have the same length, or ValueError is raised:
m = np.array([[1, 2, 3],
[4, 5, 6]], dtype=np.uint8)
A pre-existing ndarray is also a valid input; array() always copies. To avoid the copy when one is not needed, use asarray():
b = np.asarray(a, dtype=np.float) # same dtype -> no copy
6.3.2. Pre-filled at a given shape¶
When the target shape is known but the contents are not yet, allocate the buffer up front and write into it later:
zeros()– filled with zeros.ones()– filled with ones.full()– filled with a given value.empty()– alias forzeros()(ulabdoes not leave the buffer uninitialised).eye()– identity-likeN-by-Mmatrix with ones on thek-th diagonal.diag()– a diagonal matrix from a vector, or the diagonal of a 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
The shape argument is either a single integer (for a 1-D array) or a tuple.
6.3.3. Generated as a sequence¶
arange()– evenly spaced values like the built-inrange(), but always returning anndarray:np.arange(0, 10, 2) # array([0, 2, 4, 6, 8])linspace()–numevenly spaced points between two limits, with the upper bound included whenendpoint=True:np.linspace(0, 1, num=11) # 0.0, 0.1, ..., 1.0logspace()– geometrically spaced points.startandstopare exponents, not endpoints; the result runs frombase ** starttobase ** stop:np.logspace(0, 3, num=4) # 1.0, 10.0, 100.0, 1000.0meshgrid()– builds two coordinate matrices from two 1-D arrays so a per-pixel functionf(x, y)can be evaluated over a whole grid in one vectorised call. Given an x-vector of lengthWand a y-vector of lengthH,meshgridreturns twoH-by-Wmatrices:Xis the x-vector repeated down every row,Yis the y-vector repeated across every column, soX[i, j]is the x-coordinate andY[i, j]is the y-coordinate of the cell at rowiand columnj: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)then evaluates the function at every cell of the grid in one expression. A distance-from-centre map over a(H, W)frame, for example, isnp.sqrt((X - cx)**2 + (Y - cy)**2)against the matricesmeshgrid()returned.
6.3.4. Joining¶
concatenate() joins a tuple of arrays along an existing axis:
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)
All inputs must share the same dtype and ndim, and match on every axis other than the joining one. concatenate() allocates a fresh array big enough to hold every input and copies the data in, so it is the right tool for one-shot joining of arrays that already exist; it is the wrong tool inside a streaming loop, where pre-allocating the destination once and writing into it through slice assignment is the pattern.
6.3.5. Wrapping an existing buffer¶
The most useful constructor on a camera is frombuffer(). It re-interprets an existing bytes-like buffer as a 1-D ndarray without copying a single byte:
buf = bytearray(8)
audio = np.frombuffer(buf, dtype=np.int16)
# 4 int16 samples, sharing memory with buf
Writes through audio are visible in buf and vice versa. The chosen dtype must evenly divide the buffer length.
offset= skips a header at the start of the buffer; count= limits how many elements are read:
np.frombuffer(buf, dtype=np.uint8, offset=2, count=4)
This is the right constructor whenever a peripheral hands the application a raw buffer – ADC samples in a bytearray, a payload pulled from SPI. The bytes the peripheral wrote are the array.
When a peripheral writes multi-byte values in a byte order the camera’s CPU does not natively read, byteswap() reverses the byte order of each element so the values read correctly. It returns a new array by default; passing inplace=True modifies the source in place.
frombuffer() only handles the dtypes numpy itself defines. For peripherals that produce 32-bit integer samples, from_int32_buffer() and friends convert to float in one pass.
6.3.6. Print truncation¶
Printing a large array shows only its first and last few elements, with ... in the middle, so the IDE terminal does not fill with thousands of values:
>>> print(np.arange(1000, dtype=np.uint16))
array([0, 1, 2, ..., 997, 998, 999], dtype=uint16)
set_printoptions() overrides the thresholds when debugging needs the whole buffer:
np.set_printoptions(threshold=2000) # print up to 2000 elements in full
np.set_printoptions(edgeitems=10) # 10 items at each end, not 3
get_printoptions() reads the current settings back as a dict.