13.1.9. チャンネルビュー#

フレームバッファはカメラが見ているものを表示し、チャンネルビューはスクリプトが測定し判断したものを表示します。スクリプトはデバッグプロトコル経由で名前付きの値(温度、検出数、マイクの波形、Time-of-Flight 深度マップ、あるいはユーザーに変更させたいコントロール)を公開し、IDE はそれぞれをフレームバッファの下のペインにウィジェット、グラフ、またはマップとしてライブで描画します。画像に描画したりターミナルから解析したりする必要はありません。カメラ側では protocol.CBORChannel を使った数行で済み、IDE 側ではペインセレクタの チャンネル 項目です。

The Channels view showing a thermal camera script's controls channel: FPA and AUX temperature readouts with their unit, Measurement and High Temp toggles, Temp Min and Temp Max sliders, and H-Mirror and V-Flip toggles

サーマルカメラスクリプトのコントロール。2つの温度の読み出し、測定モードのトグル、温度範囲のスライダー。どれかを変更すると新しい値がスクリプトに送られます。#

ビューはチャンネルごとに整理されます。スクリプトが登録した各チャンネルは、チャンネル名を見出しとするセクションになり、チャンネル内の各レコードは、スクリプトが追加した順にその下の行になります。スクリプトがチャンネルを登録したり形を変えたりするとビューは再構築され、スクリプトが停止すると、次のスクリプトが始まるまで、コントロールが無効化された状態で最後の値が画面に残ります。

13.1.9.1. ウィジェット#

読み出しとコントロールは、見たままの形をとります。label は単位付きの読み取り専用の値で、text レコードは見出しや説明のための静的なリッチテキストのブロックです。書き込み可能な種類は、変更した瞬間に新しい値をスクリプトに送り返し、スクリプトの on_write コールバックがレコード名と値を受け取ります。toggle はスイッチ、slider と spinbox はスクリプトの範囲とステップの中で数値を設定し(精密な入力にはスピンボックス)、radio ボタンと select はスクリプトの選択肢から1つを選び、lineedit は自由入力のテキストフィールド、pushbutton は瞬間的なアクションを発火します。書き込みが処理中の間、ビューはカメラが最後に公開した値へ戻すのではなく、あなたの編集を画面に保ちます。

スクリプト側では、全体は1つの protocol.CBORChannel で、ウィジェットごとに1つの add と、書き込み用のコールバックがあります:

import time
import protocol
from protocol import CBORChannel

def on_write(ch, name, value):
    print(name, "=", value)      # every change lands here

ch = CBORChannel(on_write=on_write)
ch.add("Status", type="label", value="starting")
ch.add("Enable", type="toggle", value=True)
ch.add("Threshold", type="slider", min=0, max=100,
       step=1, value=50, unit="%")
ch.add("Mode", type="select",
       options=["Idle", "Track", "Record"], value="Idle")
ch.add("Calibrate", type="pushbutton")
protocol.register(name="controls", backend=ch)

while True:
    if ch["Enable"]:                   # read a control back
        ch["Status"] = "threshold %d%%" % ch["Threshold"]
    time.sleep_ms(100)

実行してペインを チャンネル に切り替えると、controls という名前のセクションが5つのウィジェットとともに現れ、スライダーを動かすとターミナルに出力されます。他のウィジェット型(spinbox、radio、lineedit、text)はそれぞれ add を1つ追加するだけです。ホストプロトコルの章がそれらすべてを順に説明します。

13.1.9.2. 波形グラフ#

waveform レコードはサンプルレート付きのインターリーブされたサンプルのブロック(音声、加速度計の軸、ADC のトレース)で、ビューはこれをスクロールするグラフとしてプロットします。軸ごとに1本の系列があり、名前はスクリプトが付けます。グラフのコントロールはそのツールバーとプロット自体にあります。

  • ドラッグでパン、スクロールでズームします。ダブルクリックするとライブの先端を追従する状態に戻ります。履歴 は過去のデータをどれだけ保持するかを設定し、その分だけパンして遡れます。

  • 自動スケール は縦軸をデータに合わせます。オフの場合、軸はスクリプトが宣言した範囲になります。

  • プロットを右クリックすると、マーカー を置いてサンプルの正確な時刻と値を読み取ったり、トリガーレベル を設定したりできます。トリガー をアームすると、信号が立ち上がりまたは立ち下がりエッジでそのレベルを横切るまでグラフは静止します。オシロスコープ式に過渡現象を捉える方法です。再アーム は次の発生を待ちます。

  • スペクトル はグラフを同じデータの周波数プロットに切り替えます。窓(Hann、Hamming、Blackman、矩形)の選択、フレーム間の 平均化 と ピークホールド、対数周波数 軸を備えています。プロットの下の読み出しにはピーク周波数とそのレベルが表示されます。

  • 一時停止 は記録に影響を与えずにグラフを固定し、統計 は最小 / 最大 / RMS の行を追加します。右クリックメニューからプロットを画像として保存できます。

The Channels view with two waveform records scrolling live: a 16 kHz microphone trace and a three-series accelerometer trace with its X, Y, and Z legend, each with its Record, Spectrum, Pause, and Stats buttons

ライブでスクロールする2つの waveform レコード。マイクのトレースと3系列の IMU のトレース。#

waveform フィールドはサンプルレートと、複数の信号を同時に扱う場合はインターリーブされた series の数とその名前を取ります。各更新は、フィールドの typecode によるサンプルの array の生バイトです:

import math
import time
import protocol
from array import array
from protocol import CBORChannel

RATE, CHUNK = 1000, 100            # 1 kHz, 100 ms per update

ch = CBORChannel()
ch.add("IMU", type="waveform", sample_rate=RATE, series=3,
       typecode="f", min=-2.0, max=2.0,
       options=["X", "Y", "Z"], unit="g")
protocol.register(name="signals", backend=ch)

t = 0
while True:
    block = array("f")
    for i in range(CHUNK):
        phase = 2 * math.pi * 2 * (t + i) / RATE
        block.extend((math.sin(phase), math.cos(phase), 1.0))
    ch["IMU"] = bytes(block)        # interleaved X, Y, Z
    t += CHUNK
    time.sleep_ms(100)

13.1.9.3. 深度マップ#

depth レコードは距離のグリッド(Time-of-Flight センサーが生成するフレーム)で、ビューはこれをグラフの高さまで拡大した疑似カラーのマップとして描画します。ツールバーで描画方法を選びます。パレット(カメラ自身の Depth、Rainbow、Ironbow の各パレットに加え、イベントカメラ用のライトとダークのパレット)、小さなセンサーグリッドを拡大する スケーリングフィルタ(滑らかな面にはバイキュービック、バイリニア、またはセンサーピクセルごとに1ブロックの最近傍)、そして距離を色に変換する トーンマッピング です。スクリプトレンジ はスクリプトが宣言した範囲を使い、自動レンジ は各フレームに実際に存在する距離(外れ値の数パーセントは無視)に色を引き伸ばし、平坦化 はフレームの分布に沿って色を広げるため、平坦なシーンでも細部が見えます。ヘッダーにはマップのサイズ、宣言された範囲、フレームの最小値、最大値、平均値が表示されます。マップにカーソルを合わせるとその位置の距離が読み出され、画像を保存する は描画されたマップを PNG に書き出します。

The Channels view showing an 8 x 8 time-of-flight depth map scaled up smoothly with the Depth palette, the palette, scaling, and tone-mapping selectors in its toolbar, and the header reporting the size, declared range, and the frame's minimum, maximum, and mean

センサーの前にかざした手の Time-of-Flight 深度マップ。ツールバーにパレット、スケーリング、トーンマッピングのセレクタがあります。#

depth フィールドはグリッドのサイズと範囲を宣言します。各更新はパックされた 32 ビット浮動小数点のグリッドで、これは tof モジュールのリーダーが生成するものです:

import struct
import time
import tof
import protocol
from protocol import CBORChannel

tof.init()
ch = CBORChannel()
ch.add("depth", type="depth", width=tof.width(),
       height=tof.height(), min=0, max=1000)
protocol.register(name="ToF", backend=ch)

while True:
    try:
        d, dmin, dmax = tof.read_depth(vflip=True, hmirror=True)
    except RuntimeError:
        continue
    ch["depth"] = struct.pack("<%df" % len(d), *d)
    time.sleep_ms(50)

13.1.9.4. 記録#

すべてのグラフとマップには 記録 ボタンがあり、複数のグラフやマップを持つチャンネルには、それらをまとめて開始する すべて記録 バーがあります。記録は、描画結果ではなく到着したままのデータを、保存ダイアログで選んだ拡張子に従った形式のファイルに書き込みます。表計算やプロットツール向けの CSV、単一チャンネルの音声波形向けの WAV、Python 向けの NumPy .npy 配列、そして Edge Impulse プロジェクトに学習データとして直接アップロードするための Edge Impulse JSON または CBOR(ファイル名に使われる任意のラベル付き)です。レコードのデータを保持できる形式だけが提示されます。たとえば深度マップは WAV ファイルにはできません。長時間のキャプチャは番号付きのパートに分割されるため、1つのファイルが扱いにくいほど大きくなることはなく、スクリプトがチャンネルを再登録しても記録は継続します。ボタンの横の読み出しには、書き込まれたサンプル数とファイルサイズが表示されます。

設定のプロトコルセクションにあるペインの Channel Polling 間隔は、IDE がカメラに新しいチャンネルデータを問い合わせる頻度を設定します。デフォルトは毎秒数回公開するスクリプトに適しており、音声をストリーミングするスクリプトはより短い間隔の恩恵を受けます。

参考

スクリプト側については protocol.CBORChannel(レコードの種類、その引数、on_write コールバック)を、読み出し、コントロール、深度マップを公開する実際に動くスクリプトについては File → Examples 内の 12-Protocol サンプルを参照してください。