6.3. Создание массивов

Каждый пример на остальных этих страницах начинается с уже имеющегося в наличии ndarray. Эта страница – каталог того, как этот массив появляется на свет. Существует четыре семейства конструкторов:

  • Из итерируемого объекта Python – обычная форма литерала / списка / кортежа.

  • Предзаполненный заданной формой – нули, единицы, постоянное значение, единичная матрица.

  • Сгенерированный как последовательность – значения в диапазоне или равномерно распределённые.

  • Оборачивание буфера, уже находящегося в RAM – случай периферийных устройств.

Каждый конструктор принимает ключевое слово dtype= и по умолчанию использует float. Данным датчика почти всегда нужен dtype меньше, чем используемый по умолчанию.

Каждый пример ниже начинается с:

from ulab import numpy as np

6.3.1. Из итерируемого объекта Python

array() строит ndarray из любого итерируемого объекта чисел:

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

Вывод:

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

Вложенные итерируемые объекты создают многомерные массивы. Все внутренние итерируемые объекты должны иметь одинаковую длину, иначе возбуждается ValueError:

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

Уже существующий ndarray также является допустимым входом; array() всегда копирует. Чтобы избежать копирования, когда оно не нужно, используйте asarray():

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

6.3.2. Предзаполненный заданной формой

Когда целевая форма известна, но содержимое ещё нет, выделите буфер заранее и записывайте в него позже:

  • zeros() – заполненный нулями.

  • ones() – заполненный единицами.

  • full() – заполненный заданным значением.

  • empty() – псевдоним для zeros() (ulab не оставляет буфер неинициализированным).

  • eye() – матрица N-на-M, похожая на единичную, с единицами на k-й диагонали.

  • diag() – диагональная матрица из вектора или диагональ матрицы.

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

Аргумент shape – это либо одно целое число (для одномерного массива), либо кортеж.

6.3.3. Сгенерированный как последовательность

  • arange() – равномерно распределённые значения, как у встроенной range(), но всегда возвращающие ndarray:

    np.arange(0, 10, 2)            # array([0, 2, 4, 6, 8])
    
  • linspace()num равномерно распределённых точек между двумя пределами, причём верхняя граница включается, когда endpoint=True:

    np.linspace(0, 1, num=11)      # 0.0, 0.1, ..., 1.0
    
  • logspace() – геометрически распределённые точки. start и stop являются экспонентами, а не конечными точками; результат идёт от base ** start до base ** stop:

    np.logspace(0, 3, num=4)       # 1.0, 10.0, 100.0, 1000.0
    
  • meshgrid() – строит две координатные матрицы из двух одномерных массивов, чтобы попиксельная функция f(x, y) могла быть вычислена по всей сетке за один векторизованный вызов. Дан x-вектор длины W и y-вектор длины H, meshgrid возвращает две матрицы H-на-W: X – это x-вектор, повторённый по каждой строке, Y – это y-вектор, повторённый по каждому столбцу, так что X[i, j] – это x-координата, а Y[i, j] – y-координата ячейки в строке i и столбце 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) затем вычисляет функцию в каждой ячейке сетки одним выражением. Карта расстояния от центра по кадру (H, W), например, – это np.sqrt((X - cx)**2 + (Y - cy)**2) по матрицам, которые вернула meshgrid().

6.3.4. Соединение

concatenate() соединяет кортеж массивов вдоль существующей оси:

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)

Все входы должны иметь одинаковый dtype и ndim и совпадать по каждой оси, кроме соединяемой. concatenate() выделяет новый массив, достаточно большой, чтобы вместить каждый вход, и копирует данные в него, поэтому это правильный инструмент для однократного соединения уже существующих массивов; это неправильный инструмент внутри потокового цикла, где паттерном является однократное предварительное выделение приёмника и запись в него через присваивание срезу.

6.3.5. Оборачивание существующего буфера

Наиболее полезным конструктором на камере является frombuffer(). Он переинтерпретирует существующий байтоподобный буфер как одномерный ndarray без копирования ни единого байта:

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

Записи через audio видны в buf и наоборот. Выбранный dtype должен делить длину буфера нацело.

offset= пропускает заголовок в начале буфера; count= ограничивает количество считываемых элементов:

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

Это правильный конструктор всякий раз, когда периферийное устройство передаёт приложению необработанный буфер – отсчёты ADC в bytearray, полезную нагрузку, полученную из SPI. Байты, которые записало периферийное устройство, и есть массив.

Когда периферийное устройство записывает многобайтовые значения в порядке байт, который CPU камеры не читает нативно, byteswap() обращает порядок байт каждого элемента, чтобы значения считывались правильно. По умолчанию он возвращает новый массив; передача inplace=True изменяет источник на месте.

frombuffer() обрабатывает только dtype, которые определяет сам numpy. Для периферийных устройств, производящих 32-битные целочисленные отсчёты, from_int32_buffer() и подобные ей преобразуют в float за один проход.