13.1.9. La vista Canali#

Il frame buffer mostra ciò che la camera vede; la vista Canali mostra ciò che lo script misura e decide. Uno script pubblica valori con nome tramite il protocollo di debug – una temperatura, un conteggio di rilevamenti, la forma d’onda di un microfono, una mappa di profondità time-of-flight, o un controllo che vuole che l’utente possa modificare – e l’IDE visualizza ciascuno in tempo reale come widget, grafico o mappa nel pannello sotto il frame buffer. Nulla deve essere disegnato sull’immagine o estratto dal terminale. Sul lato camera sono poche righe con protocol.CBORChannel; sul lato IDE è la voce Canali del selettore del pannello.

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

I controlli dello script di una camera termica: due letture di temperatura, interruttori per le modalità di misura e cursori per l’intervallo di temperatura. Modificarne uno qualsiasi invia il nuovo valore allo script.#

La vista è organizzata per canale. Ogni canale che lo script registra diventa una sezione con il nome del canale come intestazione, e ogni record del canale diventa una riga sotto di essa, nell’ordine in cui lo script li ha aggiunti. Quando lo script registra un canale o ne cambia la forma, la vista si ricostruisce; quando lo script si ferma, gli ultimi valori restano sullo schermo, con i controlli disabilitati, finché non parte lo script successivo.

13.1.9.1. Widget#

Letture e controlli assumono le forme ovvie. Una label è un valore di sola lettura con la sua unità; un record text è un blocco di rich text statico per titoli e spiegazioni. I tipi scrivibili rimandano il nuovo valore allo script nel momento in cui lo modifichi, dove il callback on_write dello script riceve il nome e il valore del record: un toggle è un interruttore, uno slider e uno spinbox impostano un numero entro l’intervallo e il passo dello script (la casella numerica per l’inserimento preciso), i pulsanti radio e un select scelgono una delle opzioni dello script, un lineedit è un campo di testo libero, e un pushbutton attiva un’azione momentanea. La vista mantiene la tua modifica sullo schermo invece di riportarla all’ultimo valore pubblicato dalla camera mentre la scrittura è in corso.

Sul lato script l’intero insieme è un protocol.CBORChannel con un add per widget e un callback per le scritture:

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)

Eseguilo e passa il pannello a Canali: compare una sezione chiamata controls con i cinque widget, e muovendo il cursore si stampa qualcosa nel terminale. Gli altri tipi di widget – spinbox, radio, lineedit, text – sono un add in più ciascuno; il capitolo sul protocollo host li illustra tutti.

13.1.9.2. Grafici di forme d’onda#

Un record waveform è un blocco di campioni interlacciati con una frequenza di campionamento – audio, gli assi di un accelerometro, una traccia ADC – e la vista lo traccia come un grafico a scorrimento, una serie per asse, con i nomi dati dallo script. I controlli del grafico sono nella sua barra degli strumenti e sul grafico stesso:

  • Trascina per fare una panoramica e scorri per lo zoom; fai doppio clic per tornare a seguire il bordo in tempo reale. Cronologia imposta quanti dati passati vengono conservati, così puoi tornare indietro con la panoramica.

  • Scala automatica adatta l’asse verticale ai dati; disattivata, l’asse è l’intervallo dichiarato dallo script.

  • Fai clic con il tasto destro sul grafico per posizionare un marcatore e leggere il tempo e il valore esatti di un campione, o per impostare un livello di trigger. Con un Trigger armato il grafico resta fermo finché il segnale non attraversa il livello su un fronte di salita o di discesa – il modo in cui un oscilloscopio cattura un transitorio – e Riarma attende il successivo.

  • Spettro trasforma il grafico in un grafico in frequenza degli stessi dati, con una scelta di finestra (Hann, Hamming, Blackman, rettangolare), Media e Mantieni picco tra i frame, e un asse a Frequenza logaritmica. La lettura sotto il grafico indica la frequenza di picco e il suo livello.

  • Pausa congela il grafico senza influire su una registrazione; Stat. aggiunge una riga con minimo / massimo / RMS; e il menu del tasto destro salva il grafico come immagine.

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

Due record waveform che scorrono in tempo reale: una traccia di un microfono e una traccia IMU a tre serie.#

Un campo waveform prende la frequenza di campionamento e, per più segnali insieme, il numero di series interlacciate e i loro nomi; ogni aggiornamento è costituito dai byte grezzi di un array di campioni nel typecode del campo:

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. Mappe di profondità#

Un record depth è una griglia di distanze – i frame prodotti da un sensore time-of-flight – e la vista lo disegna come una mappa in falsi colori ingrandita all’altezza del grafico. La barra degli strumenti sceglie la resa: la palette (le palette Depth, Rainbow e Ironbow proprie della camera, più una palette chiara e una scura per le camere a eventi), il filtro di scalatura che ingrandisce la piccola griglia del sensore (bicubica per una superficie liscia, bilineare, o più vicino per un blocco per ogni pixel del sensore) e la mappatura dei toni che trasforma la distanza in colore: Intervallo script usa l’intervallo dichiarato dallo script, Intervallo auto distende i colori sulle distanze effettivamente presenti in ogni frame (ignorando la piccola percentuale di valori estremi) ed Equalizza li distribuisce sulla distribuzione del frame, così una scena piatta mostra comunque i suoi dettagli. L’intestazione riporta la dimensione della mappa, l’intervallo dichiarato e il minimo, il massimo e la media del frame; passando il mouse sulla mappa si legge la distanza sotto il cursore, e Salva immagine scrive la mappa renderizzata in un 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

Una mappa di profondità time-of-flight di una mano davanti al sensore, con i selettori di palette, scalatura e mappatura dei toni nella barra degli strumenti.#

Un campo depth dichiara la dimensione della griglia e l’intervallo; ogni aggiornamento è la griglia come float a 32 bit impacchettati, che è ciò che produce il lettore del modulo 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. Registrazione#

Ogni grafico e mappa ha un pulsante Registra, e un canale con più di uno di essi ha una barra Registra tutto che li avvia insieme. La registrazione scrive i dati man mano che arrivano – non la resa grafica – in un file il cui formato segue l’estensione che scegli nella finestra di salvataggio: CSV per fogli di calcolo e strumenti di grafici, WAV per forme d’onda audio a canale singolo, array NumPy .npy per Python, ed Edge Impulse JSON o CBOR per il caricamento diretto in un progetto Edge Impulse come dati di addestramento, con un’etichetta opzionale che dà il nome ai file. Vengono offerti solo i formati che possono contenere i dati di un record; una mappa di profondità, per esempio, non può essere un file WAV. Un’acquisizione lunga viene suddivisa in parti numerate così che nessun singolo file diventi ingombrante, e una registrazione sopravvive alla ri-registrazione dei canali da parte dello script. La lettura accanto al pulsante tiene traccia di quanti campioni sono stati scritti e della dimensione del file.

L’intervallo Polling dei canali del pannello, nella sezione del protocollo delle preferenze, imposta quanto spesso l’IDE chiede alla camera nuovi dati dei canali; il valore predefinito va bene per gli script che pubblicano poche volte al secondo, e uno script che trasmette audio beneficia di un intervallo più breve.

Vedi anche

protocol.CBORChannel per il lato script – i tipi di record, i loro argomenti e il callback on_write – e gli esempi 12-Protocol in File → Examples per script funzionanti che pubblicano letture, controlli e una mappa di profondità.