Loading...

Streaming Data

Streaming Data

When a Stream Out or Stream In node is configured within a Synapse device, it opens a UDP socket to send or receive data, respectively. The corresponding NodeSocket is reported with every Status reply message from the control plane API as well as the DeviceInfo message. You can use the same Stream Out node instance you created when configuring the device to then read data from the socket, via its read method. The same is true for Stream In, via its write method.

Connect

Configuring a device to stream data out or in first requires connecting to the device.

import synapse as syn

# Connect to a device
device = syn.Device(uri)

# Get the device's info
info = device.info()
assert info is not None, "Couldn't get device info"
print(f"Device info: {info}")

Stream Out

Next, you'll need to configure the recording parameters on the device, add nodes to record and stream out, connect those nodes, and then start the device.

import synapse as syn

device = syn.Device("127.0.0.1:647")
info = device.info()

print("Device info: ", device.info())

stream_out = syn.StreamOut(label="my broadband", multicast_group="239.0.0.1")
e_broadband = syn.ElectricalBroadband(
    peripheral_id=2,
    channels=[syn.Channel(id=c, electrode_id=c * 2, reference_id=c * 2 + 1) for c in range(32)],
    sample_rate=30000,
    bit_width=12,
    gain=20.0,
    low_cutoff_hz=500.0,
    high_cutoff_hz=6000.0,
)

config = syn.Config()
config.add_node(stream_out)
config.add_node(e_broadband)
config.connect(e_broadband, stream_out)

device.configure(config)

# Start the device
assert device.start()

# Receive data
try:
    while True:
	# The data type will depend on the configuration of the device -- on the node connected to the StreamOut node
        data: ElectricalBroadbandData = stream_out.read()

	print(f"timestamp 0: {t0}")
	print(f"n channels: {len(data.channels)}")
	for c in data.channels:
		print(f" - [{c.channel_id}]: {c.channel_data}")

# Stop the device
assert device.stop()

Stream In

import random
import time

import synapse as syn

# Configure the device
o_stim = syn.OpticalStimulation()
stream_out = syn.StreamIn()

config = syn.Config()
config.add_node(stream_in)
config.add_node(o_stim)
config.connect(stream_in, o_stim)

device.configure(config)

# Start the device
assert device.start()

# Send data
try:
    while True:
	payload = SpiketrainData(
		t0=time.time_ns() / 1000.0,
		spike_counts=[random.randint(0, 3) for _ in range(100)]
	)

        stream_in.write(payload)

# Stop the device
assert device.stop()

Neural Data Transport Protocol

The Synapse protocol uses Neural Data Transport Protocol (NDTP) to structure the data packets for transmission between nodes over UDP. NDTP structures the message into a 96-bit header, a payload that varies in size according to the originating node, and a 16-bit crc. The Steam Out node handles the packing and unpacking of messages for you.

StructMessage
Field.header .payload .crc16
Width (bits)96 variable 16
Width (bytes)12 variable 2

Example of a Broadband Payload

StructPayload Repeated Channel Data
Field.signed .bit_width ch_count sample_rate .ch_id .sample_count .ch_data ... .ch_data
Width (bits)1 7 24 16 24 16 bit_width ... bit_width
Width (bytes)1 3 2 3 2 bit_width / 8 ... bit_width