6.3. 配列の作成

これ以降のページにあるすべての例は、すでに手元にある ndarray から始まります。このページは、その配列がどのように生まれるかのカタログです。コンストラクタには4つのファミリーがあります。

  • Python のイテラブルから -- 通常のリテラル / リスト / タプルの形式です。

  • 指定した形状であらかじめ埋める -- ゼロ、1、定数値、単位行列です。

  • シーケンスとして生成する -- 範囲指定や等間隔の値です。

  • すでに 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() -- 1で埋めます。

  • full() -- 指定した値で埋めます。

  • empty() -- zeros() のエイリアスです(ulab はバッファを未初期化のままにしません)。

  • eye() -- k 番目の対角に1を持つ、単位行列のような N × M 行列です。

  • 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 引数は、(1次元配列の場合は)単一の整数か、タプルのいずれかです。

6.3.3. シーケンスとして生成する

  • arange() -- 組み込みの range() のように等間隔の値ですが、常に ndarray を返します:

    np.arange(0, 10, 2)            # array([0, 2, 4, 6, 8])
    
  • linspace() -- 2つの限界値の間の num 個の等間隔な点で、endpoint=True のときは上限も含まれます:

    np.linspace(0, 1, num=11)      # 0.0, 0.1, ..., 1.0
    
  • logspace() -- 等比間隔の点です。startstop は端点ではなく 指数 であり、結果は base ** start から base ** stop まで続きます:

    np.logspace(0, 3, num=4)       # 1.0, 10.0, 100.0, 1000.0
    
  • meshgrid() -- 2つの1次元配列から2つの座標行列を構築し、ピクセルごとの関数 f(x, y) をグリッド全体に対して1回のベクトル化呼び出しで評価できるようにします。長さ W の x ベクトルと長さ H の y ベクトルが与えられると、meshgrid は2つの H × W 行列を返します。X はすべての行に繰り返された x ベクトル、Y はすべての列に繰り返された y ベクトルです。したがって X[i, j] は行 i、列 j のセルの x 座標であり、Y[i, j] はその y 座標です:

    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) は、グリッドのすべてのセルで関数を1つの式で評価します。たとえば (H, W) フレーム上の中心からの距離マップは、meshgrid() が返した行列に対する np.sqrt((X - cx)**2 + (Y - cy)**2) です。

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() です。これは、既存のバイト列のようなバッファを、1バイトもコピーせずに 1次元の 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)

これは、ペリフェラルがアプリケーションに生のバッファを渡すときに常に適したコンストラクタです -- bytearray に入った ADC サンプルや、SPI から取り出したペイロードなどです。ペリフェラルが書き込んだバイトがそのまま配列になります。

ペリフェラルが、カメラの CPU がネイティブに読み取らないバイトオーダーで複数バイトの値を書き込む場合、byteswap() は各要素のバイトオーダーを反転し、値が正しく読み取られるようにします。デフォルトでは新しい配列を返します。inplace=True を渡すと、ソースをその場で変更します。

frombuffer() は、numpy 自体が定義する dtype のみを扱います。32ビット整数のサンプルを生成するペリフェラルには、from_int32_buffer() などが1回のパスで float に変換します。