event.getKeys() not picking up keypress during print to terminal

Hi
I’m having an issue with using event.getKeys() to listen for a specific keypress (a ‘5’) during a terminal print command, in macOS 10.15.7, using python3.

I am trying to debug some stimulation code for an arduino. Part of my code generates a serial byte code, that is sent to the arduino, but for now I’m ignoring this bit, and just getting the code to make that serial code, and print to terminal.

There is an issue with lag, so to make sure of this, I want the code to pick up triggers sent to the keyboard, so that it can readjust/restart a stimulation cycle with trigger-based timings, rather than just relying on the clock.

I’m using event.getKeys(), but it doesn’t seem to be picking anything up when my loop (generate serial byte code, print to terminal) is running.

Any ideas?
Thanks
Michael

Here is part of the code:

    for run in range(number_of_runs):
		Run_clock = core.Clock()
		logging.setDefaultClock(Run_clock)
		start_run=Run_clock.getTime()
		logging.exp("Start of Experimental Run: "+str(run))
		#count_trigger=1

		while True:
			response = event.getKeys()
			if '5' in response:
				print('GEttinga trig')
		    # if len(event.getKeys(['5']))>0:
		    # count_trigger+=1
		    # print('Trigger %d is received'%count_trigger)

			print('In Run %d \n' %run)
			for digit in digit_ids[digit_order]:
				stim_id=[digit]
				activate_stimulators_debug(frequency, amplitude, stim_id, ON_duration)
			#press_callback('5')
			#time.sleep(OFF_duration)
				core.wait(OFF_duration,hogCPUperiod=2)
				for ii in range(0,7): # for 4s intermitent stimulation (8 bursts)
					activate_stimulators_debug(frequency, amplitude,stim_id, ON_duration)
				#press_callback('5')
				#time.sleep(OFF_duration)
					core.wait(OFF_duration,hogCPUperiod=2)

			print('this cycle %.8f seconds'%Run_clock.getTime())

			# if count_trigger==(ON_duration2*Digits/TR)+1:
			# 	print('\nTrigger is received to start a new Trial at %f'%Run_clock.getTime())
			# 	logging.flush()
			# 	break

			# if len(event.getKeys(["escape"]))>0:
			# 	win.close()
			# 	core.quit()
		thisRun = thisRun+Run_clock.getTime()
		print(thisRun)

The activate_stimulators_debug() loop is in a separate file and goes like:

def activate_stimulators_debug(frequency, amplitude, stimulator_ids, duration):


	freq=str(frequency).zfill(3)
	amp=str(amplitude).zfill(3)
	activation_code='';
	stop_code='';
	for ids in stimulator_ids:
		activation_code=activation_code+freq+ids+str(amp)
		stop_code=stop_code+freq+ids+'000'
		print(activation_code)
		print(stop_code)
	#serial_port.write(activation_code)
	#time.sleep(duration)
	core.wait(duration,hogCPUperiod=2)

Hi Michael,

If you are using the event module, can I please suggest using the logic from the demo in coder view> demos>input>what_key.py:

k = ['']
count = 0
while k[0] not in ['escape', 'esc'] and count < 5:
    k = event.waitKeys()
    print(k)

Alternatively, for better timing, I suggest using the Keyboard class instead of event. For a demo of logic see coder view>demos>input>keyboardNEW.py there are also some relevant materials for using the keyboard class here Improvements — Workshops for PsychoPy 2020 2020.

Hope this helps,
Becca

1 Like

Thanks Becca
I’ve had a go at this, and went down some crazy rabbit holes.
Using the keyboard class as per your advice, I’m still not gettting anything picked up. >>

		timer = core.Clock()
		#logging.setDefaultClock(timer)
		start_run=timer.getTime()
		#logging.exp("Start of Experimental Run: "+str(run))

		count_trigger=1


		while True:
			kb.clock.reset()
			keys = kb.getKeys(keyList = ['5'])

			for key in keys:
				print(key.name, key.rt, key.duration)
			print('In Run %d \n' %run)
			for digit in digit_ids[digit_order]:
				stim_id=[digit]
				activate_stimulators_debug(frequency, amplitude, stim_id, ON_duration)
			#press_callback('5')
			#time.sleep(OFF_duration)
				core.wait(OFF_duration,hogCPUperiod=2)
				for ii in range(0,7): # for 4s intermitent stimulation (8 bursts)
					activate_stimulators_debug(frequency, amplitude,stim_id, ON_duration)
				#press_callback('5')
				#time.sleep(OFF_duration)
					core.wait(OFF_duration,hogCPUperiod=2)

Because I want my timings exact, I actually decided to set up my mac to dual boot Ubuntu, and now debugging this script from there, where keyboard class works

Hi Michael,

Can I confirm that you now have this running? (do the demos in code view work for you?)

Thanks,
Becca

Hi Becca
So the demos work in code view, but copying that bit of code into my own script doesn’t work. I suspect I’m being really dense here - does psychopy always require a window to be opened? I suspect if I do that then it will capture my keypresses. The problem is that I don’t think I can simultanaeously run psychopy in a window, plus send commands to the arduino via terminal. Does that make sense?

Thanks
M

Hi M,

Hmm ok I see, is there a reason you are sending commands to the arduino via terminal? Previously I have sent commands to an arduino from psychopy itself. I am afraid I don’t have the scripts to hand but I can see some previous discussions here that could be helpful Question: connecting arduino with psychopy

Becca

Not too sure what’s going on here.

If you are using events you need a graphics window open. I’m not sure if that’s the case for the keyboard library.

1 Like

Hi Becca

Yes, so I’m using serial_port.write() to send a byte code to the arduino. I suppose I might have a look at using psychopy to do that…

So far I’ve managed to fix it up a bit. Opening a window helps, unsurprisingly!

#!/usr/bin/env python
from __future__ import absolute_import, division, print_function

import time, sys, os
from stimulator_interface.interface2 import *
from psychopy import core, data, event, logging, clock, monitors, visual

import ctypes
xlib = ctypes.cdll.LoadLibrary("libX11.so")
xlib.XInitThreads()

from psychopy.hardware import keyboard
kb = keyboard.Keyboard()

win = visual.Window([400, 400])
msg = visual.TextStim(win, text='Waiting for scan trigger\n < 5 >')
msg.draw()
win.flip()

port_number='/dev/tty.usbmodem14201'
baud_rate=9600
frequency=31
amplitude=70
digit_order=sys.argv[1]

ON_duration=.4  #seconds
ON_duration2=4.0
TR=2.0
Digits=5
OFF_duration=.1 #seconds, for intermitent stimulation seconds
number_of_runs=8

# Wait for a key keypress event ( max wait of 5 seconds )
kb.clearEvents()
kb.clock.reset()
maxWait=60
got_keypress = False
while not got_keypress and kb.clock.getTime() < maxWait:
    keys =  kb.getKeys(keyList = ['5'])
    #if 'escape' in keys:
    #    core.quit()
    if len(keys)>0:
        print(keys[0].name, keys[0].rt, keys[0].duration)
        rt = keys[0].rt
        got_keypress = True

#start trial
            for run in range(number_of_runs):
		timer = core.Clock()
		start_run=timer.getTime()
		count_trigger=1
		while True:

			kb.clock.reset()
			keys = kb.getKeys(keyList = ['5'])

			for key in keys:
				print(key.name, key.rt, key.duration)

				if len(keys)>0:
					count_trigger+=1
					print('Got a trigger Michael')


					if count_trigger==(ON_duration2*Digits/TR)+1:
						print('\nTrigger is received to start a new Trial at %f'%timer.getTime())
						logging.flush()
						break

			print('In Run %d \n' %run)
			for digit in digit_ids[digit_order]:
				stim_id=[digit]
				activate_stimulators_debug(frequency, amplitude, stim_id, ON_duration)
				core.wait(OFF_duration,hogCPUperiod=2)
				for ii in range(0,7): # for 4s intermitent stimulation (8 bursts)
					activate_stimulators_debug(frequency, amplitude,stim_id, ON_duration)
					core.wait(OFF_duration,hogCPUperiod=2)

I’m not sure whether this is actually working, at least, for what I want it to do. But the issue where it wasn’t picking up my keypresses is fixed once I opened up a graphic window.

(I’m trying to get triggers off an MRI scanner, so that my trials stay in sync with that, rather than relying on the precision of core.wait() etc.)

whilst I can’t speak to MRI studies explicitly, I can say that using methods other than core.wait() will give you better precision (e.g. timing by frames) - these methods are used by default in builder view though! so you can always use code components in there :slight_smile:

Becca

1 Like

Thanks Becca.

I’ve been reading up about non-slip timing and found some old threads. Possibly what I’m trying to do may not be necessary - i.e. setting the timing based on each scanner trigger. From what I’ve read, if I just capture one trigger, then make sure the timings are super accurate, it should be fine, in terms of lag.

So, this might be worth making a new issue, and do let me know, but I’m now trying to implement a countdown timer at the start of my trial, such that if there is a lag, the trial will just stick to the exact, say 20 seconds, and jump into the next trial - eliminating lag.

	trial_len = 4
	Run_clock = core.Clock()
	timer = core.CountdownTimer()
	timer.reset()

	oneFrame = 1/60.0

	for run in range(number_of_runs):
		print(timer.getTime())

		#while True:
		while timer.getTime()>oneFrame/2:		
			print('In Run %d \n' %run)
			for digit in digit_ids[digit_order]:
				stim_id=[digit]
				activate_stimulators_debug(frequency, amplitude, stim_id, ON_duration)
				core.wait(OFF_duration,hogCPUperiod=2)
				for ii in range(0,7): # for 4s intermitent stimulation (8 bursts)
					activate_stimulators_debug(frequency, amplitude,stim_id, ON_duration)
					core.wait(OFF_duration,hogCPUperiod=2)
		print('this cycle %.8f seconds'%Run_clock.getTime())
		thisRun = thisRun+Run_clock.getTime()
		print('this run %.8f'%thisRun)

This doesn’t work however, what happens is that one trial goes fine, then it takes a little over the trial length (4 seconds), and instead of breaking and starting the next trial, the whole experiment ends.

facepalm it works fine, just turns out I had my number of runs set to 1, no wonder it broke.
Thank you for your help

M