13.1.9. 채널 뷰#
프레임 버퍼는 카메라가 보는 것을 보여주고, 채널 뷰는 스크립트가 측정하고 판단하는 것을 보여줍니다. 스크립트는 디버그 프로토콜을 통해 이름 붙은 값 – 온도, 감지 횟수, 마이크 파형, 비행 시간 깊이 맵, 또는 사용자가 바꿀 수 있기를 바라는 컨트롤 – 을 게시하고, IDE는 각각을 프레임 버퍼 아래 창에 위젯, 그래프, 맵으로 실시간 렌더링합니다. 이미지에 그리거나 터미널에서 파싱할 필요가 없습니다. 카메라 측에서는 protocol.CBORChannel로 몇 줄이면 되고, IDE 측에서는 창 선택기의 채널 항목입니다.

열화상 카메라 스크립트의 컨트롤: 두 개의 온도 판독값, 측정 모드 토글, 온도 범위 슬라이더. 어느 것을 바꾸든 새 값이 스크립트로 전송됩니다.#
뷰는 채널별로 구성됩니다. 스크립트가 등록하는 각 채널은 채널 이름을 제목으로 하는 섹션이 되고, 채널의 각 레코드는 스크립트가 추가한 순서대로 그 아래의 행이 됩니다. 스크립트가 채널을 등록하거나 채널의 형태를 바꾸면 뷰가 다시 구성되고, 스크립트가 멈추면 다음 스크립트가 시작될 때까지 마지막 값이 컨트롤이 비활성화된 채 화면에 남아 있습니다.
13.1.9.1. 위젯#
판독값과 컨트롤은 자연스러운 형태를 취합니다. label은 단위가 붙은 읽기 전용 값이고, text 레코드는 제목과 설명을 위한 정적 서식 있는 텍스트 블록입니다. 쓰기 가능한 종류는 값을 바꾸는 즉시 새 값을 스크립트로 되돌려 보내며, 스크립트의 on_write 콜백이 레코드 이름과 값을 받습니다. toggle은 스위치이고, slider와 spinbox는 스크립트의 범위와 단계 안에서 숫자를 설정하며(스핀 박스는 정밀한 입력용), radio 버튼과 select는 스크립트의 옵션 중 하나를 고르고, lineedit는 자유 텍스트 필드이며, pushbutton은 순간적인 동작을 발생시킵니다. 뷰는 쓰기가 진행 중인 동안 여러분이 편집한 값을 카메라가 마지막으로 게시한 값으로 되돌리지 않고 화면에 유지합니다.
스크립트 측에서는 전체 세트가 위젯마다 add 하나와 쓰기를 위한 콜백 하나를 가진 protocol.CBORChannel입니다:
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라는 섹션이 나타나고, 슬라이더를 움직이면 터미널에 출력됩니다. 다른 위젯 유형 – spinbox, radio, lineedit, text – 은 각각 add 하나만 더하면 됩니다. 호스트 프로토콜 장이 이들 모두를 살펴봅니다.
13.1.9.2. 파형 그래프#
파형 레코드는 샘플 레이트를 가진 인터리브된 샘플 블록 – 오디오, 가속도계 축, ADC 트레이스 – 이며, 뷰는 이를 축마다 하나의 시리즈로, 스크립트가 지정한 이름으로 스크롤 그래프에 그립니다. 그래프 컨트롤은 도구 모음과 플롯 자체에 있습니다:
드래그하여 패닝하고 스크롤하여 줌합니다. 더블 클릭하면 다시 실시간 끝을 따라갑니다. 히스토리는 과거 데이터를 얼마나 보관할지 설정하므로, 그만큼 뒤로 패닝할 수 있습니다.
자동 스케일은 세로축을 데이터에 맞춥니다. 끄면 축은 스크립트가 선언한 범위가 됩니다.
플롯을 오른쪽 클릭하여 마커를 놓고 샘플의 정확한 시간과 값을 읽거나, 트리거 레벨을 설정합니다. 트리거가 준비되면 신호가 상승 또는 하강 에지에서 레벨을 넘을 때까지 그래프가 정지합니다 – 오실로스코프가 과도 현상을 잡는 방식입니다 – 그리고 다시 준비는 다음 것을 기다립니다.
스펙트럼은 그래프를 같은 데이터의 주파수 플롯으로 전환하며, 창 함수(Hann, Hamming, Blackman, 직사각형) 선택, 프레임 간 평균화와 피크 홀드, 로그 주파수 축을 제공합니다. 플롯 아래의 판독은 피크 주파수와 그 레벨을 알려줍니다.
일시 정지는 기록에 영향을 주지 않고 그래프를 멈추고, 통계는 최소 / 최대 / RMS 줄을 추가하며, 오른쪽 클릭 메뉴는 플롯을 이미지로 저장합니다.

실시간으로 스크롤되는 두 개의 파형 레코드: 마이크 트레이스와 3시리즈 IMU 트레이스.#
파형 필드는 샘플 레이트와, 여러 신호를 한 번에 보낼 때는 인터리브된 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. 깊이 맵#
깊이 레코드는 거리의 격자 – 비행 시간(time-of-flight) 센서가 생성하는 프레임 – 이며, 뷰는 이를 그래프 높이에 맞게 확대한 가색상 맵으로 그립니다. 도구 모음에서 렌더링 방식을 선택합니다. 팔레트(카메라 자체의 Depth, Rainbow, Ironbow 팔레트와 이벤트 카메라용 밝은 팔레트 및 어두운 팔레트), 작은 센서 격자를 확대하는 스케일링 필터(매끄러운 표면을 위한 쌍삼차, 쌍선형, 또는 센서 픽셀당 한 블록을 위한 최근접), 그리고 거리를 색으로 바꾸는 톤 매핑입니다. 스크립트 범위는 스크립트가 선언한 범위를 사용하고, 자동 범위는 각 프레임에 실제로 존재하는 거리(가장자리의 몇 퍼센트는 무시) 위로 색을 펼치며, 평활화는 프레임의 분포 위로 색을 퍼뜨려 평평한 장면에서도 세부가 드러나게 합니다. 헤더는 맵의 크기, 선언된 범위, 프레임의 최솟값, 최댓값, 평균을 보고합니다. 맵 위에 마우스를 올리면 커서 아래의 거리를 읽을 수 있고, 이미지 저장은 렌더링된 맵을 PNG로 씁니다.

센서 앞의 손을 찍은 비행 시간 깊이 맵과, 도구 모음의 팔레트, 스케일링, 톤 매핑 선택기.#
깊이 필드는 격자 크기와 범위를 선언하며, 각 업데이트는 패킹된 32비트 float로 된 격자로, 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 파일이 될 수 없습니다. 긴 캡처는 번호가 매겨진 파트로 분할되어 한 파일이 지나치게 커지지 않으며, 기록은 스크립트가 채널을 다시 등록해도 유지됩니다. 버튼 옆의 판독은 기록된 샘플 수와 파일 크기를 추적합니다.
환경 설정의 프로토콜 섹션에 있는 창의 채널 폴링 간격은 IDE가 카메라에 새 채널 데이터를 얼마나 자주 요청할지 정합니다. 기본값은 초당 몇 번 게시하는 스크립트에 알맞고, 오디오를 스트리밍하는 스크립트는 더 짧은 간격이 유리합니다.
더 보기
스크립트 측에 대해서는 protocol.CBORChannel – 레코드 유형, 그 인수, on_write 콜백 – 을, 판독값, 컨트롤, 깊이 맵을 게시하는 동작하는 스크립트에 대해서는 File → Examples 아래의 12-Protocol 예제를 참조하세요.