6.3. Створення масивів

Кожен приклад на решті цих сторінок починається із вже готового ndarray. Ця сторінка — каталог того, як такий масив з’являється. Існують чотири сімейства конструкторів:

  • З ітерованого об’єкта Python — звичайна форма літерала / списку / кортежу.

  • Попередньо заповнений заданої форми — нулі, одиниці, константне значення, одинична матриця.

  • Згенерований як послідовність — значення в діапазоні або рівномірно розташовані.

  • Обгортання буфера, що вже є в RAM — периферійний варіант.

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

Кожен приклад нижче починається з:

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)

Усі вхідні масиви повинні мати однаковий тип і 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() обробляє лише типи, що визначає сам numpy. Для периферійних пристроїв, що виробляють 32-бітні цілочисельні зразки, from_int32_buffer() та подібні функції перетворюють їх у float за один прохід.