Jump to content

Programming Reference:SignalSharing Python Demo: Difference between revisions

From BCI2000 Wiki
Mellinger (talk | contribs)
No edit summary
No edit summary
Line 9: Line 9:
The ''SignalSharing Python Demo'' creates a basic visualization in Python using the data collected in BCI2000. Since this is done outside of the BCI2000 processing loop, rendering visualizations can take as long as needed. Using Python also allows for the use of the numerous packages that are available to create complex figures.
The ''SignalSharing Python Demo'' creates a basic visualization in Python using the data collected in BCI2000. Since this is done outside of the BCI2000 processing loop, rendering visualizations can take as long as needed. Using Python also allows for the use of the numerous packages that are available to create complex figures.


This Demo has two parts, one which shares the data as a BCI2000 SignalProcessing Filter, the other which visualizes the data:
This Demo has two parts, a source and a client. The source is the BCI2000 SignalProcessing Filter, and the client is the Python app that visualizes the data:
# [[Programming Reference:SignalSharingDemo Signal Processing|SignalSharing Signal Processing Filter]]: This is the same filter as what is used for the [[Programming Reference:SignalSharingDemo Signal Processing|SignalSharingDemo with the C++ application]]. Please refer to that page for details on the BCI2000 Filter aspect.
# [[Programming Reference:SignalSharingDemo Signal Processing|SignalSharing Signal Processing Filter]]: This is the same filter as what is used for the [[Programming Reference:SignalSharingDemo Signal Processing|SignalSharingDemo with the C++ application]]. Please refer to that page for details on the BCI2000 Filter aspect.
# Python visualization app: A simple Python script that connects with the port that is being used to synchronize the data transfer, and updates the visualization as data is being streamed.
# Python visualization app: A simple Python script that connects with the port that is being used to synchronize the data transfer, and updates the visualization as data is being streamed.


==Source vs Client==
The BCI2000 data is sent according to the format specified in [[Technical_Reference:BCI2000_Messages#Descriptor_Supplement=1:_Signal_Data|BCI2000 Messages Wiki page]], as '''Descriptor=4: Visualization and Brain Signal Data Format''', then '''Descriptor Supplement=1: Signal Data''', then data type 2.


The BCI2000 data is decoded according to the format specified in [[Technical_Reference:BCI2000_Messages#Descriptor_Supplement=1:_Signal_Data|BCI2000 Messages Wiki page]], as '''Descriptor=4: Visualization and Brain Signal Data Format''', then '''Descriptor Supplement=1: Signal Data''', then data type 2. The Python script assumes the data is in this format, and that the data stream is only sending the name of the shared memory. This happens if the Python script is running on the same machine as BCI2000, otherwise the whole signal will be sent in the stream. The Python script will most likely not work if this happens, but one can easily edit it to handle the data accordingly.  
With this demo, '''BCI2000 and the Python script must be run on the same computer'''. The script expects the data stream to be sending the name of the shared memory, which will only happen if they are both on the same computer. It is possible to grab BCI2000 data from another computer, and is implemented in the [[Programming Reference:SignalSharingDemo Signal Processing|C++ SignalSharing Demo]]. If on separate computers, the actual data points are being shared instead of the memory name. Implementing this would require a simple extension of this demo.


==How to run==
==How to run==
Line 39: Line 41:
#initialize axes
#initialize axes
setProps = False
setProps = False
gotMemoryName = False
chNames = []
chNames = []
figure, ax = plt.subplots(figsize=(10, 8))
figure, ax = plt.subplots(figsize=(10, 8))
Line 58: Line 61:
             while True: #go until we manually stop program
             while True: #go until we manually stop program
                 while setProps is False: #get properties
                 while setProps is False: #get properties
                     b = conn.recv(1)
                    b = conn.recv(128) #block until we get first property (source identifier)
                    b = conn.recv(1) # space
                     b = conn.recv(1) # either start of channel names or number of channels
                     if b==b'{': #start of channel names
                     if b==b'{': #start of channel names
                         name = ""
                         name = ""
Line 69: Line 74:
                                 name = ""
                                 name = ""
                         CHANNELS = len(chNames) #we have all the channels
                         CHANNELS = len(chNames) #we have all the channels
                    else:
                        CHANNELS = int(str(b, encoding='utf-8'))
                        chNames = range(1,CHANNELS+1)
                          
                          
                        #element size is next
                    #element size is next
                        elementString = ""
                    elementString = ""
                        while len(elementString) == 0 or b!= b' ':
                    while len(elementString) == 0 or b!= b' ':
                            b = conn.recv(1)
                        b = conn.recv(1)
                            if b != b' ':
                        if b != b' ':
                                elementString += str(b, encoding='utf-8')
                            elementString += str(b, encoding='utf-8')


                        #initialize variables once we have channels and elements
                    #initialize variables once we have channels and elements
                        ELEMENTS = int(elementString)
                    ELEMENTS = int(elementString)
                        phi = np.zeros((CHANNELS, ELEMENTS))
                    phi = np.zeros((CHANNELS, ELEMENTS))
                        bla = np.zeros((ELEMENTS,1))
                    bla = np.zeros((ELEMENTS,1))
                        lineArr = list(range(CHANNELS))
                    lineArr = list(range(CHANNELS))
                        for i in range(0,CHANNELS):
                    for i in range(0,CHANNELS):
                            lineArr[i], = ax.plot(bla, bla)
                        lineArr[i], = ax.plot(bla, bla)


                        print("Properties: Channels: %i, Elements: %i" %(CHANNELS, ELEMENTS))
                    print("Properties: Channels: %i, Elements: %i" %(CHANNELS, ELEMENTS))
                        print("Visualizing data...")
                    setProps= True
                        setProps= True


                 #continuously update stream and get data!
                 #block until we get data
                 stream = conn.recv(128)
                 stream = conn.recv(128)
                 for b in stream:
                 if not gotMemoryName:
                    if b==47: #47=b'\', right before memory name
                    for b in stream:
                        #get memory name
                        if b==47: #47=b'\', right before memory name
                        streamName = stream.split(b'/')[1] #mem name right after
                            #get memory name
                        byteName = streamName.split(b'\x00')[0]
                            streamName = stream.split(b'/')[1] #mem name right after
                        mName = str(byteName, encoding='utf-8')
                            byteName = streamName.split(b'\x00')[0]
                        mem = shared_memory.SharedMemory(mName)
                            mName = str(byteName, encoding='utf-8')
 
                            mem = shared_memory.SharedMemory(mName)
                        #we got data
                            gotMemoryName = True
                        data = np.ndarray((CHANNELS,ELEMENTS),dtype=np.double, buffer=mem.buf)
                            print("Connected to shared memory")
                        for ch in range(0,CHANNELS):
                            print("Visualizing data...")
                            for el in range(0, ELEMENTS):
                           
                                phi[ch, el] = el*2*np.pi/(ELEMENTS-1)
                else:
                    #update visualization with new data
                    data = np.ndarray((CHANNELS,ELEMENTS),dtype=np.double, buffer=mem.buf)
                    for ch in range(0,CHANNELS):
                        for el in range(0, ELEMENTS):
                            phi[ch, el] = el*2*np.pi/(ELEMENTS-1)


                            xdata = np.multiply(1+0.003*data[ch,:],np.cos(phi[ch,:]))
                        xdata = np.multiply(1+0.003*data[ch,:],np.cos(phi[ch,:]))
                            ydata = np.multiply(1+0.003*data[ch,:],np.sin(phi[ch,:]))
                        ydata = np.multiply(1+0.003*data[ch,:],np.sin(phi[ch,:]))
                            #update plots
                        #update plots
                            lineArr[ch].set_xdata(xdata)
                        lineArr[ch].set_xdata(xdata)
                            lineArr[ch].set_ydata(ydata)
                        lineArr[ch].set_ydata(ydata)


                        figure.canvas.draw()
                    #update figure
                        plt.pause(0.01) #render update                   
                    figure.canvas.draw()
                    plt.pause(0.01) #render update                   
         except:
         except:
             print('exception')
             print('exception')
Line 126: Line 140:


==See also==
==See also==
[[Programming Reference:GenericSignal Class]][[Technical Reference:BCI2000 Messages]][[Programming Reference:SignalSharingDemo Signal Processing]]
[[Programming Reference:SignalSharingDemo Signal Processing]], [[Technical Reference:BCI2000 Messages]][[User Tutorial:BCI2000Remote]]


[[Category:Howto]][[Category:Development]]
[[Category:Howto]][[Category:Development]]

Revision as of 19:52, 31 January 2024

Demo example of the flexibility of visualizations with Python

Location

src/core/SignalProcessing/SignalSharingDemo/PythonClientApp

Synopsis

The SignalSharing Python Demo demonstrates how to make a complex real-time visualization in Python using data parallelly collected in BCI2000.

Function

The SignalSharing Python Demo creates a basic visualization in Python using the data collected in BCI2000. Since this is done outside of the BCI2000 processing loop, rendering visualizations can take as long as needed. Using Python also allows for the use of the numerous packages that are available to create complex figures.

This Demo has two parts, a source and a client. The source is the BCI2000 SignalProcessing Filter, and the client is the Python app that visualizes the data:

  1. SignalSharing Signal Processing Filter: This is the same filter as what is used for the SignalSharingDemo with the C++ application. Please refer to that page for details on the BCI2000 Filter aspect.
  2. Python visualization app: A simple Python script that connects with the port that is being used to synchronize the data transfer, and updates the visualization as data is being streamed.

Source vs Client

The BCI2000 data is sent according to the format specified in BCI2000 Messages Wiki page, as Descriptor=4: Visualization and Brain Signal Data Format, then Descriptor Supplement=1: Signal Data, then data type 2.

With this demo, BCI2000 and the Python script must be run on the same computer. The script expects the data stream to be sending the name of the shared memory, which will only happen if they are both on the same computer. It is possible to grab BCI2000 data from another computer, and is implemented in the C++ SignalSharing Demo. If on separate computers, the actual data points are being shared instead of the memory name. Implementing this would require a simple extension of this demo.

How to run

  1. Build BCI2000 as you would, make sure to check BUILD_DEMOS
  2. Make sure the SignalSharingDemo works first
  3. Navigate to src\core\SignalProcessing\SignalSharingDemo\PythonClientApp, where there is a batch file and a Python file. Copy the batch file to your BCI2000 batch folder
  4. Run the Python file. For example from the command line, navigate to the folder and run python SignalSharingPythonDemo.py
  5. Run the batch file, and press "Start Run" (it already sets the configuration, it won't work it you press it multiple times)
    • If you change the Parameter SignalSharingDemoClientAddress, make sure to also change it in the Python script

Python Script

Here is the same code that is on the SVN

#import libraries
import socket
from multiprocessing import shared_memory
import matplotlib.pyplot as plt
import numpy as np

#user input
HOST, PORT = "localhost", 1879
print("Waiting for BCI2000 on %s at port %i" %(HOST, PORT))

#initialize axes
setProps = False
gotMemoryName = False
chNames = []
figure, ax = plt.subplots(figsize=(10, 8))
ax.set_xlim(-2,2)
ax.set_ylim(-2,2)
figure.set_facecolor((0,0,0,1))
ax.set_axis_off()
ax.set_frame_on(0)
figure.canvas.draw()

#attempt connection to specified port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen(1)
    conn, addr = s.accept()
    with conn:
        print('Connected by', addr)
        try:
            while True: #go until we manually stop program
                while setProps is False: #get properties
                    b = conn.recv(128) #block until we get first property (source identifier)
                    b = conn.recv(1) # space
                    b = conn.recv(1) # either start of channel names or number of channels
                    if b==b'{': #start of channel names
                        name = ""
                        while b != b'}': #end of channel names
                            b = conn.recv(1)
                            if b != b' ':
                                name += str(b, encoding='utf-8') #gather whole ch name
                            elif name != "":
                                chNames = np.append(chNames, name) #move to next name
                                name = ""
                        CHANNELS = len(chNames) #we have all the channels
                    else:
                        CHANNELS = int(str(b, encoding='utf-8'))
                        chNames = range(1,CHANNELS+1)

                        
                    #element size is next
                    elementString = ""
                    while len(elementString) == 0 or b!= b' ':
                        b = conn.recv(1)
                        if b != b' ':
                            elementString += str(b, encoding='utf-8')

                    #initialize variables once we have channels and elements
                    ELEMENTS = int(elementString)
                    phi = np.zeros((CHANNELS, ELEMENTS))
                    bla = np.zeros((ELEMENTS,1))
                    lineArr = list(range(CHANNELS))
                    for i in range(0,CHANNELS):
                        lineArr[i], = ax.plot(bla, bla)

                    print("Properties: Channels: %i, Elements: %i" %(CHANNELS, ELEMENTS))
                    setProps= True

                #block until we get data
                stream = conn.recv(128)
                if not gotMemoryName:
                    for b in stream:
                        if b==47: #47=b'\', right before memory name
                            #get memory name
                            streamName = stream.split(b'/')[1] #mem name right after
                            byteName = streamName.split(b'\x00')[0]
                            mName = str(byteName, encoding='utf-8')
                            mem = shared_memory.SharedMemory(mName)
                            gotMemoryName = True
                            print("Connected to shared memory")
                            print("Visualizing data...")
                            
                else:
                    #update visualization with new data
                    data = np.ndarray((CHANNELS,ELEMENTS),dtype=np.double, buffer=mem.buf)
                    for ch in range(0,CHANNELS):
                        for el in range(0, ELEMENTS):
                            phi[ch, el] = el*2*np.pi/(ELEMENTS-1)

                        xdata = np.multiply(1+0.003*data[ch,:],np.cos(phi[ch,:]))
                        ydata = np.multiply(1+0.003*data[ch,:],np.sin(phi[ch,:]))
                        #update plots
                        lineArr[ch].set_xdata(xdata)
                        lineArr[ch].set_ydata(ydata)

                    #update figure
                    figure.canvas.draw()
                    plt.pause(0.01) #render update                   
        except:
            print('exception')
            conn.close() #close connection to client
        finally:
            print('disconnected')
            conn.close() #close connection to client

Conclusion

This demo shows how to grab data from BCI2000 and plot it in Python! The main advantage of this demo shows how to access the data in Python. Once this is done, Python's extensive library can be used to create complex, real-time visualizations and calculations!


See also

Programming Reference:SignalSharingDemo Signal Processing, Technical Reference:BCI2000 Messages, User Tutorial:BCI2000Remote