Trialhandler and time measuring in psychopy - python

For a go-NoGo Task I want to organize pictures with the data.TrialHandler class from psychopy:
trials = data.TrialHandler(ImageList, nReps=1, method='random')
Now I want to code a loop in which psychopy is going into the dictionary, is presenting the first set of pictures (e.g. A_n) and afterwards is going to the second set until the sixth set. I tried the following:
import glob, os, random, sys, time
import numpy.random as rnd
from psychopy import visual, core, event, monitors, gui, logging, data
im_a = glob.glob('./a*') # upload pictures of the a-type, gives out a List of .jpg-files
im_n = glob.glob('./n*') # n-type
im_e = glob.glob('./e*') # e-type
# combining Lists of Pictures
A_n = im_a + im_n
N_a = im_n + im_a
A_e = im_a + im_e
E_a = im_e + im_a
E_n = im_e + im_n
N_e = im_n + im_e
# making a Dictionary of Pictures and Conditions
PicList = [A_n, N_a, A_e, E_a, E_n, N_e] # just the six Combinations
CondList = [im_a,im_n,im_a,im_e,im_e,im_n] # images that are in the GO-Condition
ImageList = []
for imagelist, condition in zip(PicList, CondList):
ImageList.append({'imagelist':imagelist,'condition':condition}) # to associate the picturelist with the GO Conditionlist
for the header I ask an extra question: Combining and associating multiple dictionaries
# Set Experiment
win = visual.Window(color='white',units='pix', fullscr=False)
fixCross=visual.TextStim(win,text='+',color='black',pos=(0.0,0.0), height=40)
corrFb = visual.TextStim(win,text='O',height=40,color='green',pos=[0,0])
incorrFb = visual.TextStim(win,text='X',height=40, color='red',pos=[0,0])
# Start Experiement
trials = data.TrialHandler(ImageList, nReps=1, method='random')
rt_clock = core.Clock()
bitmap = visual.ImageStim(win)
for liste in ImageList[0:5]: # to loop through all 6 conditions
keys = []
for i,Pictures in enumerate(liste): # to loop through all pictures in each condition
bitmap.setImage(Pictures) # attribute error occurs, not if I use Pictures[0][0], even though in this case every pictures is the same
bitmap.draw()
win.flip()
rt_clock.reset()
resp = False
while rt_clock.getTime() < 2.0: # timelimit is defined 2 s
if not resp:
resp = event.getKeys(keyList=['space'])
rt = rt_clock.getTime()
if bool(resp) is (Pictures in CondList): # at this point python should have access to the Dictionary in which the associated GO Pictures are saved
corrFb.draw()
accu=1 # doesn't work yet
else:
incorrFb.draw()
accu=0
win.flip()
core.wait(0.5)
trials.addData('rt_'+str(i), rt) # is working well when loop: if trial in trials: ...; in this case trialHAndler is not used, therefor trials.addData is not working
trials.addData('accu_'+str(i), accu)
trials.saveAsExcel(datanames)
core.quit()
There are a few problems in this code: first it only presents one pictuere for six times, but not six different pictures [1]
and secondly a totally different problem [2] ist the time measuring and the saving of the accuracy which the trialhandler is doing, but for each trial. So it adds up all the RT's for each trial. I want to get the RT's for each image. I tried a few things like an extra stimulus.trialhandler for the stimuli and an extraloop in the end which gives me the last RT but not each. --> is answered below!!!
for stimuli in stimulus: stimulus.addData('rt', rt)
I know these four questions are a lot for one question, but maybe somebody can give me some good ideas of how I can solve these... Thanks everybody!

The reason for your problem labelled [1] is that you set the image to PicList[0][0] which never changes. As Mike is suggesting above you need::
for i,thisPic in enumerate(PicList):
bitmap.setImage(thisPic) #not PicList[0][0]
But maybe you need to go back to basics so that you actually use the trial handler to handle your trials ;-)
Create a single list of dictionaries where one dictionary represents one trial, and then run through those in order (tell the TrialHandler to use the list 'sequential' rather than 'random'). So the loops that you're currently using should just be to create your list of condition dicts, not to run the trials. Then pass that one list to the trial handler::
trials = TrialHandler(trialTypes = myCondsListInOrder, nReps=1, method='sequential')
for thisTrial in trials:
pic = thisTrial['pic']
stim.setImage(pic)
...
trials.addData('rt', rt)
trials.addData('acc',acc)
Also, I would output your data not using the excel format, but the 'long wide' format::
trials.saveAsWideText('mydataFile.csv')
best wishes,
Jon

(A) This isn't relevant to your question but will improve performance.
The line:
bitmap = visual.ImageStim(win)
Shouldn't occur within the loop. i.e. you should initialise each stimulus only once, then within a loop you just update that the properties of that stimulus, e.g. with bitmap.setImage(…). So shift this initialisation line to the top, where you create the TextStims.
(B) [deleted: I hadn't paid attention to the first code block.]
(C)
bitmap.draw(pictures)
This line doesn't take any arguments. It should just be bitmap.draw(). And anyway, it isn't clear what 'pictures' refers to. Remember that Python is case sensitive. This isn't the same thing as 'Pictures' defined in the loop above. I'm guessing that you want to update what picture is being shown? In that case, then you need to be doing the bitmap.setImage(…) line within this loop, not above, where you will always be drawing a fixed picture as that is the only one that gets set on each trial.
(D) Re the RTs, you are saving this only once per trial (check the indentation). If you want to save one per image, you need to indent these lines again. Also, you only get one line per trial in the data output. If you want to record multiple RTs per trial, you will need to give them unique names, e.g. rt_1, rt_2, …, rt_6 so they each appear in a separate column. e.g. you could use an enumerator for this loop:
for i, picture in enumerate(Piclist)
# lots of code
# then save data:
trials.addData('rt_'+str(i), rt)

Related

My shuffle algorithm crashes when more trials than objects

I am trying to make an experiment where a folder is scanned for images. For each trial, a target is shown and some (7) distractor images. Afterward, in half the trials people are shown the target image and in the other half, they are shown an image that wasn't in the previous display.
My current code sort of works, but only if there are fewer trials than objects:
repeats = 20
# Scan dir for images
jpgs = []
for path, dirs, files in os.walk(directory):
for f in files:
if f.endswith('.jpg'):
jpgs.append(f)
# Shuffle up jpgs
np.random.shuffle(jpgs)
# Create list with target and probe object, Half random, half identical
display = []
question = []
sameobject = []
position = np.repeat([0,1,2,3,4,5,6,7], repeats)
for x in range(1,(repeats*8)+1):
display.append(jpgs[x])
if x % 2 == 0:
question.append(jpgs[-x])
sameobject.append(0)
else:
question.append(jpgs[x])
sameobject.append(1)
# Concatonate objects together
together = np.c_[display,question,position,sameobject]
np.random.shuffle(together)
for x in together:
# Shuffle and set image
np.random.shuffle(jpgs)
myList = [i for i in jpgs if i != together[trial,0]]
myList = [i for i in myList if i != together[trial,1]]
# Set correct image for target
myList[int(together[trial,2])] = together[trial,0]
First of all, I am aware that this is horrible code. But it gets the job done coarsely. With 200 jpgs and a repeat of 20, it works. If repeat is set to 30 it crashes.
Here is an example with repeat too high:
File "H:\Code\Stims\BetaObjectPosition.py", line 214, in <module>
display.append(jpgs[x])
IndexError: list index out of range
Is there a way to update my code in a way that allows more trials while all objects are used as evenly as possible (one object should not be displayed 3 times while another is displayed 0) over an entire experiment?
Full, reproducible example
Bonus points if anyone can see an obvious way to balance the way the 7 distractor images are selected too.
Thanks for taking your time to read this. I hope you can help me onwards.
The solution that changes your code the least should be to change each call of jpgs[x] to jpgs[x % len(jpgs)]1. This should get rid of the IndexError; it basically wraps the list index "around the edges", making sure it's never to large. Although I'm not sure how it will interact with the jpgs[-x] call.
An alternative would be to implement a class that produces a longer sequence of objects from a shorter one.
Example:
from random import shuffle
class InfiniteRepeatingSequence(object):
def __init__(self, source_list):
self._source = source_list
self._current = []
def next(self):
if len(self._current) == 0:
# copy the source
self._current = self._source[:]
shuffle(self._current)
# get and remove an item from a list
return self._current.pop()
This class repeats the list indefinitely. It makes sure to use each element once before re-using the list.
It can easily be turned into an iterator (try changing next to __next__). But be careful since the class above produces an infinite sequence of elements.
1 See "How does % work in Python?" for an explanation about the modulo operator.
Edit: Added link to modulo question.

Python - Comparing each item in array into another ( Dynamo )

I'm new to programming and only started writing my first lines of code last week.
I'm writing a script in a program called dynamo, this is to be used in my project. After some research, it appears like I need to use python.
What I need to script to do is look at a bunch of lines ( In a program called Revit), pick up the geometry of this line and then detect if any other line has a start point or end point that is in contact with that geometry. I then want to Split that line at that point, this can be done byCurve.SplitByPoints but I need some kind of way to compare ALL lines to ALL start/end points then the output be in a way that the output can be used to split the curve by the point. I can have the line and the point in which to cut in.
I tried to explain that the best I could...
code :
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
dataEnteringNode = IN
Line = IN[0] #Line
LPS = IN[1] # Line Point Start
LPE = IN[2] # Line Point End
LPC = IN[3] # Line Point Combined // Maybe not needed
T = 100 # Tolerance of Intersection
INT1 = [] # Blank Variable for First Loop Results
INT2 = [] # Blank Variable for First Loop Results
result1 = [] # Blank Variable for Second Loop Results
result2 =[] # Blank Variable for Second Loop Results
for i in range (0,len(LPS)):
distance = Curve.DistanceTo(LPS[i],Line[i])
INT1.append(distance)
for i in range (0,len(LPE)):
distance = Curve.DistanceTo(LPE[i],Line[i])
INT2.append(distance)
for i in range (0,len(INT1)):
if INT1 > T:
result1.append('T1')
else:
result1.append('F1')
for i in range (0,len(INT2)):
if INT2 > T:
result2.append('T2')
else:
result2.append('F2')
Assign your output to the OUT variable.
OUT = result1, result2
EDIT:
Sorry, I knew explaining this would be tricky for me.
I'll attempt to simplify it.
I want something like:
if curve intersect with StartPoint or EndPoint
Curve.split points(Curve,Intersecting_Point)
So im hoping something similar will have it so, when a start or end point intersects a curve, the curve will be split into 2 curves at that point.
So I want to above for to work on a range of lines. I drew a diagram and attempted to upload, but for some reason, it now says I need 10 rep to post an image. meaning I cant upload a new diagram and had to remove the ones I had in?
Thanks for the help! I'm sorry for my explaination skills

psychopy ratingScale with numeric keypad

In my task, I've been allowing responses using the numeric keypad (e.g 'num_1') as well as the regular numbers at the top of the keyboard (e.g. '1'). When I later ask for ratings using ratingScale, I'd like both options to be available as well, but I don't know how to achieve this.
As is, ratingScale does not accept responses with the numeric keypad. I can change this with respKeys, but I would have to provide "A list of keys to use for selecting choices, in the desired order". This means I can't allow both '1' and 'num_1' to select the first rating (e.g. with respKeys = ['1','num_1, '2', 'num_2', ...] '1' would select the first rating, 'num_1' the second, etc.).
Am I really stuck with either respKeys = ['1','2','3','4','5'] or respKeys = ['num_1','num_2','num_3','num_4','num_5']?
Thanks for any help!
I don't think that there's any built-in ways for visual.RatingScale to take multiple keyboard keys for the same scale location.
If you're using coder, a hack could be to use event.getKeys() and visual.RatingScale.setMarkerPos(). So for a simple case with a rating scale with three positions:
# Initiate psychopy stuff
from psychopy import visual, event
win = visual.Window()
scale = visual.RatingScale(win, low=1, high=3)
# A list of lists, each sublist being the keys which represents this location
keysLocation = [['1', 'num_1'], ['2', 'num_2'], ['3', 'num_3']]
respKeys = [key for keysLoc in keysLocation for key in keysLoc] # looks complicated but it simply flattens the list to be non-nested
# Present the ratingscale and wait for a response
currentPos = 1
while scale.noResponse:
response = event.getKeys(keyList=respKeys) # only accept respKeys
if response and response[0] in respKeys:
# Then loop through sublists and update currentPos to where the match is
for i, loc in enumerate(keysLocation):
if response[0] in loc:
currentPos = i
# Set the marker position and draw
scale.setMarkerPos(currentPos)
scale.draw()
win.flip()
My solution looks complicated but most of it is just about handling the keysLocationlist and searching it for matches. Sorry for the bad/ambiguous variable names but I couldn't come up with anything better right now.
This solution could probably also work in Builder. Just remove the "initiate psychopy" stuff, change scale to the name of your RatingScale, and paste the code into a code component just above the RatingScale.

How to display an Image with python, without pyglet or similar?

So, what I am trying to do in detail:
I have a device that acts as a display, although is technically not one (that's not really important, it can be handled like a normal display) and I want to display a series of images on it. When each image is displayed i call a function that takes readings from another device. So far I have used pyglet for that and I can get it to display the images in the correct order and where I want them, but the readings taken seem not to correspond to the right image.
The simplest way to explain what I want to do is:
for a in range(10):
display('image%d'%a)
values(a) = measure()
Imagine values being initiated in the correct form.
What I tried so far was:
import pyglet
import numpy as np
size = 64
no = 512/size
a,b,c = (0,0,0)
values = np.zeros((no,no,3))
display = pyglet.window.get_platform().get_default_display()
screens = []
for screen in display.get_screens():
screens.append(screen)
window = pyglet.window.Window(screen = screens[1], fullscreen=1)
#here i choose the mentioned device, since it is connected to my computer via display port
#window.event
def update(dt):
global a,b,c
if a == no/2. and b == no/2.:
values[a,b,c] = 0
window.clear()
else:
image = pyglet.image.load('folder_%d/grating_%d_%d_%d.bmp' % (size,a,b,c))
window.clear()
print (a,b,c)
image.blit(0,0)
values[a,b,c] = measure()
c+=1
if c == 3:
b += 1
c = 0
if b == no:
a += 1
b = 0
if a == no:
pyglet.app.exit()
pyglet.clock.schedule_interval(update, .2)
pyglet.app.run()
where measure() is my own function. "no" is an index for my images, they range from (0,0,0),(0,0,1),(0,0,2),(0,1,0)... to (no,no,2) and are called to be called one by one. Also the case of a = b = no/2 is a special case that is of no special importance to my problem, I think.
first of all: I am pretty sure this is neither elegant nor efficient but I am not creating software that anyone else is ever likely to use. I am also aware that using global variables is bad practice but their use is due to me not knowing how to use eventloops correctly.
I am not happy with this solution, because the readings i take always seem to correspond to the previous image.
I guess I misuse the eventloop badly, but the pyglet documentation does not really help me here.
Also I feel like I am building a whole truck just to carry a bag across the street...
I have already looked into pyplot as an alternative, since the imshow() function works in the way I want, but the plotting libraries seem to display images in random sizes, which I cannot figure out how to control properly.
I appreciate any help on using pyglet correctly as well as alternative libraries that can help.
Thank you already,
Mopsi
An extra-long comment, that requires formatted code
From your example, you don't need a,b,c outside of the update function and all the global stuff is about having values that stay alive across invocations. If I'm correct this is better suited by a closure, like
...
def make_update(no, window):
from itertools import product
abcs = product(range(no),range(no),range(3))
#window.event
def _update(...):
try:
a, b, c = next(abcs)
except StopIteration:
... wind up ...
...
return _update
update = make_update(no, window)
...
Alright, I did not actually solve the problem but found a workaround:
I just flatten my image nomenclature, e.g
0_0_0 -> 000
0_0_1 -> 001
0_0_2 -> 002
0_1_0 -> 003
etc.
With values now being an array with dimensions [no*no*3,1]
And since for the n-th iteration and therefore n-th measurement I see the (n-1)th image, I simply add a dummy image and assign the n-th measured value to values[n-1].
The first measurement being useless is no problem here, since values[-1] is the last element, which gets overwritten with a meaningful measurement in the end.
Since the number of images is known and predetermined I can reshape my array of values afterwards.
If anyone cares to explain why no the displayed image is shifted by one iteration (with no image at all being displayed at the first iteration) please go ahead.

Python - How to track (add/remove) lots of class instances over mulitple iterations?

I am building a dynamic map of earthquakes, using the vtk library.
I've already made a static one, (see here: https://github.com/yacobuk/QuakeCloud and here: http://www.youtube.com/watch?v=4HVdTcI_ozI) so I know the basic idea works, but now I want to try and show the quakes over time.
I have some code examples that show me how to update the frame, and how to add / remove objects, but I'm stuck on figuring out how to spin up an instance, track it for a few periods, then remove it.
The basic add/ remove code looks like this:
for point_and_mag in pm.points_mag:
time.sleep(0.5)
mag = point_and_mag[1]
point = point_and_mag[0]
if mag > 2:
pointCloud = VtkPointCloud(pm)
pointCloud.addPoint(point, math.log(mag)*10)
renderer.AddActor(pointCloud.vtkActor)
renderer.ResetCamera()
renderWindow.Render()
time.sleep(0.3)
renderer.RemoveActor(pointCloud.vtkActor)
renderer.ResetCamera()
renderWindow.Render()
But of course, this only allows one object at a time (an instance of pointCloud.vtkActor via renderer.AddActor(pointCloud.vtkActor) waits a while, then removes it with renderer.RemoveActor(pointCloud.vtkActor)
How can I add a number of actors (I'm going to use 10 min interval, and there was as many as 5 quakes in that time), tag it with a counter, increment the counter at every loop iteration, and when it reaches 5 iterations, remove the actor?
There is some more context to this question here: Python/vtk - set each point size individually in a vtkPolyData object?
A possible(untested) solution might be:
from collections import deque
# The number 5 indicates for how many iterations the actors should be rendered.
rendered_actors = deque([None] * 5, maxlen=5)
for point_and_mag in pm.points_mag:
if rendered_actors[-1] is not None:
renderer.removeActor(rendered_actors[-1])
renderer.ResetCamera()
renderWindow.Render()
time.sleep(0.5)
mag = point_and_mag[1]
point = point_and_mag[0]
if mag > 2:
pointCloud = VtkPointCloud(pm)
pointCloud.addPoint(point, math.log(mag)*10)
rendered_actors.appendleft(pointcloud.vtkActor)
renderer.AddActor(pointCloud.vtkActor)
renderer.ResetCamera()
renderWindow.Render()
else:
rendered_actors.appendleft(None)
This code creates a deque(which is a double-linked list) of length 5. The actors are inserted at the left of this deque and at each iteration the rightmost value, if it is an "actor", it is removed from the scene and the scene is re-rendered.
Note that I don't have vtk so I cannot test this code.
A small style note: this is really unpythonic code-style:
for point_and_mag in pm.points_mag:
mag = point_and_mag[1]
point = point_and_mag[0]
Use tuple-unpacking:
for point, mag in pm.points_mag:
# ...
if mag > 2:
# ...

Categories