6.3. การสร้างอาร์เรย์

ทุกตัวอย่างในหน้าถัดไปเริ่มต้นด้วย ndarray ที่พร้อมใช้งานอยู่แล้ว หน้านี้คือรายการวิธีการสร้างอาร์เรย์นั้น มี constructor สี่กลุ่ม:

  • จาก Python iterable -- รูปแบบ literal / list / tuple ทั่วไป

  • กรอกข้อมูลล่วงหน้าตามรูปร่างที่กำหนด -- ศูนย์ หนึ่ง ค่าคงที่ หรือเมทริกซ์เอกลักษณ์

  • สร้างเป็นลำดับ -- ค่าที่เป็นช่วงหรือเว้นระยะเท่ากัน

  • ห่อบัฟเฟอร์ที่อยู่ใน RAM แล้ว -- กรณีอุปกรณ์ต่อพ่วง

ทุก constructor รับคีย์เวิร์ด dtype= และใช้ค่าเริ่มต้นเป็น float ข้อมูลจาก sensor มักต้องการ dtype ที่เล็กกว่าค่าเริ่มต้น

ทุกตัวอย่างด้านล่างเริ่มต้นด้วย:

from ulab import numpy as np

6.3.1. จาก Python iterable

array() สร้าง ndarray จาก iterable ของตัวเลขใดก็ได้:

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

ผลลัพธ์:

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

iterable ที่ซ้อนกันจะสร้างอาร์เรย์หลายมิติ iterable ภายในต้องมีความยาวเท่ากันทั้งหมด มิฉะนั้นจะเกิด 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 เป็นได้ทั้งจำนวนเต็มเดียว (สำหรับอาร์เรย์ 1 มิติ) หรือ tuple

6.3.3. สร้างเป็นลำดับ

  • arange() -- ค่าที่เว้นระยะเท่ากันคล้ายกับ range() ที่มีอยู่ใน Python แต่จะคืนค่าเป็น 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() -- สร้างเมทริกซ์พิกัดสองตัวจากอาร์เรย์ 1 มิติสองตัว เพื่อให้ฟังก์ชันต่อพิกเซล f(x, y) สามารถประเมินผลทั่วทั้งกริดในการเรียกแบบเวกเตอร์เดียว จาก x-vector ความยาว W และ y-vector ความยาว H meshgrid จะคืนเมทริกซ์สองตัวขนาด H-คูณ-W: X คือ x-vector ที่ซ้ำตลอดทุกแถว Y คือ y-vector ที่ซ้ำตลอดทุกคอลัมน์ ดังนั้น 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() รวม tuple ของอาร์เรย์ตามแกนที่มีอยู่:

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() จัดสรรอาร์เรย์ใหม่ที่ใหญ่พอจะเก็บอินพุตทุกตัวและคัดลอกข้อมูลเข้าไป จึงเหมาะสำหรับการรวมอาร์เรย์ที่มีอยู่แล้วครั้งเดียว แต่ไม่เหมาะสำหรับ streaming loop ซึ่งควรจัดสรรปลายทางล่วงหน้าครั้งเดียวและเขียนผ่าน slice assignment แทน

6.3.5. การห่อบัฟเฟอร์ที่มีอยู่แล้ว

constructor ที่มีประโยชน์ที่สุดบนกล้องคือ frombuffer() ซึ่งตีความบัฟเฟอร์ bytes-like ที่มีอยู่แล้วใหม่เป็น ndarray 1 มิติ โดยไม่คัดลอก ไบต์แม้แต่ไบต์เดียว:

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)

นี่คือ constructor ที่เหมาะสมเมื่ออุปกรณ์ต่อพ่วงส่งบัฟเฟอร์ดิบให้แอปพลิเคชัน เช่น ตัวอย่าง ADC ใน bytearray หรือ payload ที่ดึงจาก SPI ไบต์ที่อุปกรณ์ต่อพ่วงเขียนคืออาร์เรย์

เมื่ออุปกรณ์ต่อพ่วงเขียนค่าหลายไบต์ในลำดับไบต์ที่ CPU ของกล้องอ่านไม่ถูกต้องโดยธรรมชาติ byteswap() จะสลับลำดับไบต์ของแต่ละองค์ประกอบเพื่อให้ค่าอ่านถูกต้อง โดยค่าเริ่มต้นจะคืนอาร์เรย์ใหม่ การส่ง inplace=True จะแก้ไขต้นทางในที่เดิม

frombuffer() รองรับเฉพาะ dtype ที่ numpy กำหนดเอง สำหรับอุปกรณ์ต่อพ่วงที่ผลิตตัวอย่างจำนวนเต็ม 32 บิต from_int32_buffer() และฟังก์ชันที่เกี่ยวข้องจะแปลงเป็น float ในการผ่านเดียว