Skip to content

Biofeedback

See how physiological data streaming in from BIOPAC acqknowledge can control an object in the scene. In this case a ball will change in size and color based on the physiological signal.  You may need to adjust the position of the ball down if using 360 media.

import vizshape, vizinfo, viztask
import sightlab_utils.sightlab as sl
from sightlab_utils.settings import *
from sightlab_utils import biopacndt
import sys
import struct
import time
sightlab = sl.SightLab(biopac=True)
sightlab.setTrialCount(1)
# Load the object that we shall regulate
ball = vizshape.addSphere(radius=0.2)
ball.setScale(1.2,1.2,1.2)
ball.alpha(0.5)
ball.color(viz.BLUE)
ball.setPosition(0, 1.5, 2)
sightlab.addSceneObject('ball', ball)
allchannels = [] # allchannels will hold all the data
BUFFER_LENGTH = 2 # length of the buffer in samples
UPDATE_FREQUENCY = 0.1 # in seconds, how frequenty we will update
# the onscreen information. If the value is 0.1, this means 10 times a second
SERVER = 0 # Keep track of whether the data server has been started. It can 
# Define thresholds and baseline
UPPER_THRESHOLD = 1.4  # Adjust based on typical amplitude data for deep breaths
LOWER_THRESHOLD = 1.0  # Adjust based on typical amplitude data for shallow breaths
BASELINE_Y = 1.5
MOVEMENT_SCALE = 0.5

def outputToScreen(index, frame, channelsInSlice):
    global allchannels, BUFFER_LENGTH
#    print(f"Received frame: {frame}")
    i = 0

    for each in allchannels:
        each.append(float(frame[i]))
#        movement_scale = 0.3  # Adjust this scale as needed
        # Adjust the ball position based on data deviation
        move = (frame[i] - 0.5) * MOVEMENT_SCALE
        new_y = BASELINE_Y + move
        ball.setPosition((0, new_y, 2))
        # Change color based on breath depth

        if new_y > UPPER_THRESHOLD:
            ball.color(viz.GREEN)  # Good deep breath
        elif new_y < LOWER_THRESHOLD:
            ball.color(viz.RED)    # Too shallow breath
        else:
            ball.color(viz.BLUE)   # Normal breath
#        print(f"Ball Y position: {new_y}")  # Print new Y position for debugging

        if len(each) > BUFFER_LENGTH:
            each.pop(0)
        i += 1
acqServer = biopacndt.AcqNdtQuickConnect()

if acqServer.getDataConnectionMethod() != "single":
 acqServer.changeDataConnectionMethod("single")
 print("Data Connection Method Changed to: single")
enabledChannels = acqServer.DeliverAllEnabledChannels() 
singleConnectPort = acqServer.getSingleConnectionModePort()
dataServer = biopacndt.AcqNdtDataServer(singleConnectPort, enabledChannels )
dataServer.RegisterCallback("OutputToScreen",outputToScreen)

for each in enabledChannels:
 # Here we define the data buffer and fill it up with zeros up to the
 # BUFFER_LENGTH so that we have some data to start with
 temp_buffer = []

 for k in range (0,BUFFER_LENGTH):
  temp_buffer.append(0)
 allchannels.append(temp_buffer)
print(allchannels)

def sightLabExperiment():
    global SERVER
    yield viztask.waitKeyDown(' ')
    yield sightlab.startTrial()

    if SERVER == 0:
        dataServer.Start()
        SERVER = 1
        print("Data server started.")
    yield viztask.waitKeyDown(' ')
    yield sightlab.endTrial()
    dataServer.Stop()
    SERVER = 0
    print("Data server stopped.")
viztask.schedule(sightlab.runExperiment)
viztask.schedule(sightLabExperiment)