Example #1
0
async def emit(outlet: pylsl.StreamOutlet,
               data_stream: Generator[Dict[str, float], None,
                                      None], stream: dfs.StreamInfo):
    # get sampling frequency
    f = stream.frequency
    # calculate wait time (assign random wait time if frequency not set)
    dt = (1. / f) if f > 0 else (random.randrange(0, 10) / 10.0)
    # time when last sample was sent
    prev_ts = None
    # time taken between last two samples (ideally dt, but in practice > dt)
    prev_dt = dt
    while True:
        # graceful shutdown
        if SHUTDOWN_FLAG.is_set():
            logger.debug(f'Shutdown initiated: stopped data stream')
            data_stream.close()
            break
        # check if any consumers are available
        if outlet.have_consumers():
            # get relevant slice of data from sample
            data = next(data_stream, None)
            # end of data stream
            if data is None:
                logger.info(f'Reached end of data stream')
                break
            index = float(data[next(iter(
                stream.index))])  # assuming single-level indexes
            sample = [
                float(data[ch]) if data[ch] else np.nan
                for ch in stream.channels
            ]
            if not all([i == np.nan for i in sample]):
                ts = time_ns() * 1e-9
                # push sample if consumers are available, and data exists
                outlet.push_sample(sample, index)
                # update prev_dt
                if prev_ts is not None:
                    prev_dt = ts - prev_ts
                # update prev_ts
                prev_ts = ts
        # sleep for dt + correction to maintain stream frequency
        await asyncio.sleep(dt + (dt - prev_dt))
async def emit(outlet: StreamOutlet, data_stream: Generator[Dict[str, float],
                                                            None, None],
               stream: StreamInfo):
    # get sampling frequency
    f = stream.frequency
    while True:
        t1 = time_ns() * 10e-9
        # graceful shutdown
        if SHUTDOWN_FLAG.is_set():
            logger.debug(f'Shutdown initiated: stopped data stream')
            data_stream.close()
            break
        # calculate wait time (assign random wait time if frequency not set)
        dt = (1. / f) if f > 0 else (random.randrange(0, 10) / 10.0)
        # check if any consumers are available
        if outlet.have_consumers():
            # get relevant slice of data from sample
            data = next(data_stream, None)
            # end of data stream
            if data is None:
                logger.info(f'Reached end of data stream')
                break
            index = float(data[next(iter(
                stream.index))])  # assuming single-level indexes
            sample = [
                float(data[ch]) if data[ch] else np.nan
                for ch in stream.channels
            ]
            if not all([i == np.nan for i in sample]):
                # push sample if consumers are available, and data exists
                outlet.push_sample(sample, index)
        # offset dt to account for cpu time
        t2 = time_ns() * 10e-9
        dt -= (t2 - t1)
        # sleep for dt to maintain stream frequency
        await asyncio.sleep(dt)
def virtual_cognionics(channels=8,
                       srate=500,
                       chunk_size=1,
                       buffer_size=360,
                       stype="random"):
    """ 
    Here we create a data stream output so that we can test the rest of the networking
    properties without having a proper output, like the one from Cognionics DAQ software.
    This function will output random data to a number of channels given as an argument
    (8 by default) at a given frequency in Hertz (500 by default).

    For the purpose of being close to the actual cognionics signal, the data sent will be
    formatted with metada as if it came from the actual cognionics headset. 5 more channels
    are added normally appart from the channels for the sensors.

    There are no required arguments for the function to work, but some keyword arguments
    with default values exist in order for the function to be flexible.

    INPUT:
        channels: The number of sensor that the supposed Cognionics EEG would have working
        srate: The amount of samples sent per second
        chunk_size: If the samples are going to be sent in chunks of data, then change this
            number to how many samples per chunk
        buffer_size: The size of the buffer (in x100 samples) that will hold the data
        stype: "random", "sinusoid" or "noisy_sin" to choose which type of data will be 
            sent (random is the default)

    OUTPUT: There's no output.

    IMPORTANT NOTE: When retrieving information from this stream remember that the data is pushed
    just when there is a client, so there is no data immediately after a client connects, which will
    in some cases, return an empty tuple if the client collects just after connecting.

    SECOND IMPORTANT NOTE: time.sleep() can only do so much. Apparently for periods below 0.002s (roughly
    500 Hz) the function starts behaving very wrong.
    """
    # Here we define some metadata of the stream (Name, type, number of channels,
    # sample rate, data type and serial number/unique identifier).
    stream_info = StreamInfo("Virtual Cognionics Quick-20", "EEG",
                             channels + 5, srate, "float32", "myuid000000")

    # Attach some extra meta-data (accordance with XDF format)
    channels_handle = stream_info.desc().append_child("channels")
    channels_labels = [
        "P8", "P7", "Pz", "P4", "P3", "O1", "O2", "A2", "ACC8", "ACC9",
        "ACC10", "Packet Counter", "TRIGGER"
    ]
    for label in channels_labels:
        ch = channels_handle.append_child("channel")
        ch.append_child_value("label", label)
        ch.append_child_value("unit", "microvolts")
        ch.append_child_value("type", "EEG")

    # Here we define some metadata for the IMPEDANCE stream
    imp_stream_info = StreamInfo("Virtual Cognionics Quick-20 Impedance",
                                 "Impeadance", channels + 5, srate, "float32",
                                 "myuid000001")
    imp_ch_handle = imp_stream_info.desc().append_child("channels")

    for label in channels_labels:
        ch = imp_ch_handle.append_child("channel")
        ch.append_child_value("label", label + "-Z")
        ch.append_child_value("unit", "kohms")
        ch.append_child_value("type", "Impedance")

    # Here we create an outlet with our information, sending information in chunks of
    # 1 sample and the outgoing buffer size being 360 seconds (max.)
    outlet = StreamOutlet(stream_info, chunk_size, buffer_size)
    imp_outlet = StreamOutlet(imp_stream_info, chunk_size, buffer_size)

    # Now here we create the samples and push them to the network
    print("Now sending data...")
    t0, step = local_clock(), 0  # Used for the stamps and the sample signals
    interval = 1 / srate
    while True:
        # Only work if client connected
        if outlet.have_consumers():
            # Get the timestamp with t0 as a reference for initial time
            stamp = local_clock() - t0

            # Here we create the sample with random data
            if stype == "random":
                sample = list(np.random.rand(channels + 5))

            elif stype == "sinusoid":  # Frequency of 10 Hz
                sample = [np.sin(10 * 2 * np.pi * step)] * (channels + 5)

            elif stype == "noisy_sin":  # Frequencies of 5 and 15 Hz
                sample = [np.sin(5 * 2 * np.pi * step) + 0.2 *
                          np.sin(15 * 2 * np.pi * step) + 0.2 * np.random.rand()] * \
                    (channels + 5)
            else:
                raise TypeError(
                    "Wrong signal type. Please check documentation")

            # Update the step
            step += interval

            # Send
            outlet.push_sample(sample, stamp)
            imp_outlet.push_sample(sample, stamp)

        # Wait for next cycle
        time.sleep(interval)