psychopy.org | Reference | Downloads | Github

Creating Box Task in builder? (some grey "boxes" - chosen at random - change color when "opened" with a click)

online
pavlovia
randomization
#1

Hi, I need some help in building a study! I am familiar with the very basics of creating an experiment in Builder view (I use PsychoPy in an online lab) but I am not sure about how to go about creating this specific task. Could you please give me some advice on how to get started?

I would like to create the “Box Task” in Builder, with the aim of uploading it to Pavlovia and then running the study on M-Turk.

The Box Task (a variant of the more famous Beads - or Urn - Task) is used to gauge probability estimations. In the Box Task the display shows a number of boxes (grey rectangles) - let’s say 24. Once the participant “opens” the boxes (by clicking on them) they reveal their “true” color, that may be either red or blue. On the top of the screen the participant can see two numbers, for example 80/20, that shows the relative proportions of the two colors (without specifying which is which.)

The goal of the game is to guess which color is more frequent and which one is less frequent. The IV is the number of clicks made by the participant before making a choice.

So what I need is:

  • create 24 grey rectangles
  • associate a color (blue or red) to each one of them, possibly at random but according to a certain proportion (let’s say 80/20)
  • when clicked on, the rectangles reveal their color
  • the participant guesses which color is more frequent by pressing r (for red) or b (for blue)
  • the display gives feedback
  • the display moves on to the next trial

Consider that:

  • the number of rectangles may change
  • the relative proportion of colors may change

The program should record reaction times before each click and number of clicks before moving to the next screen.

If you could give me some suggestions on how to get started I would really appreciate it!

#2

Hi @seredes, I have a solution in Python, but have not had a chance to make a JS version, but perhaps you could attempt to translate the idea into JavaScript. The following code needs to be added to a code component, to the relevant tabs. Apologies for the large amounts of code. You could do this with 24 rectangle components in Builder with minimal code, and whilst that seems unwieldy, it might be your best bet if this code is of no help. Here is the actual demo task, with one iteration of the setup coloredRects.psyexp (7.8 KB)

Define the grid in the Begin Experiment tab of a code component

The plan is to create a grid. The grid shape can change according to the number of rectangles - however, in this setup they must be in multiples of 4, because of the number of rows I have used to create each grid (so perhaps that could be fixed if needed).


rectSize = .075  # set global rect size for experiment

def defineGrid(nRects):
    # grid will be four rows
    gridRows = 4
    # And a number of columns determined by number of rects
    gridCols = nRects / gridRows
    # Height and width of grid according to rectSize
    height = gridRows * (rectSize)
    width = (gridCols - 1) * (rectSize)
        
    # Center rows and cols around zero
    inc = height / gridRows  # inc means increment
    gridRow = np.arange(start=-height/2, stop=height/2, step=inc)
    inc = width / gridCols
    gridCol = np.arange(start=-width/2, stop=width/2, step=inc)
    
    # Create grid
    grid = []
    for row in gridCol:
        tempRow = []
        for col in gridRow:
            tempRow.append([round(row, 3), round(col, 3)])
        grid.append(tempRow)
    return grid

Define random colors of rects in the Begin Experiment tab

We now create random colors in the specified proportion, ready for coloring the rectangles when they are clicked.

def defineColors(nRects, blueProportion):
    colors = []
    blueRange = int((nRects/100)*blueProportion)
    for rect in range(nRects):
        if rect < blueRange:
            colors.append('blue')
        else:
            colors.append('red')
    shuffle(colors)  # randomize color order
    return colors

Prepare the visual rectangle stimuli for drawing in the Begin Routine tab

This is where we call the functions we just designed. The functions will return our grid of rectangle positions (defineGrid) and a list of random colors for the rectangles (defineColors). We use the positions in the grid to position the rectangle objects that we now create, ready for drawing on screen. We add all the newly created, correctly positioned rectangles to a list.

# number of rects 
nRects = 20  # this can be any number (in mutiples of 4) from a conditions file - so can change on every trial

grid = defineGrid(nRects)  # create the grid based on number of rects
rectColors = defineColors(nRects, 80)  # create the color list for number of rects, based on relative proportions

rects = []  # Make a container for all of your visual rects, ready for drawing
for row in grid:
    for pos in row:
        rects.append(visual.Rect(win, size=rectSize, pos=pos, fillColor='white'))  # Add rects to list

Draw your rects, and color them on each mouse click

Here, we draw the rectangles. If the mouse is pressed in a rectangle, we remove one of the random colors from the list, and use it to color the rectangle. The rectangles can only be colored if they are white, otherwise clicking will have no effect.

for rec in rects:
    rec.draw()  # draw rectangle
    if mouse.isPressedIn(rec) and rec.fillColor == 'white':  # If clicked, color rectangle if it is white
        rec.color = rectColors.pop()
        

1 Like
#3

@dvbridges, thanks a lot for taking the time to write down this extensive explanation! I have downloaded the file and it works very well. What worries me is that if I go this route, then I will need to write more extensive code to finish up the experiment, and then, on top of that, I will need to translate it in JavaScript. I am familiar with the basics of coding and I have seen that online one can find “Python to JavaScript translators” but I wonder if this will be feasible in a short time frame. So I think that, in my case, it may be just easier to use the Builder.

You write: “You could do this with 24 rectangle components in Builder with minimal code”

Let me see if I got the logic down.
First I create a routine and add the 24 shapes to the builder, one by one.
Then I need them to change color with one mouse click. For this I will need a code snippet, right? At that point I just need a keyboard input so that the participant can choose which color is predominant the he/she is ready to do so.

Actually I found this code snippet (that needed to be fixed) in the old forum:

if mouse.getPressed()[0] and Stim01.contains(mouse):
Stim01.colorSpace = ‘rgb255’
Stim01.color = (128, 128, 128)

I created one rectangle in the builder, called it Stim01 and then tried this code under the “Each Frame” tab but nothing happened. Well, the code needed to be fixed to start with, but I wonder if, conceptually, that’s the right way to go.

So I thought, let’s go back to the basics: let me try to just change the color through code. Now I just typed:

Stim01.color = (128, 128, 128)

But again nothing happened. I guess because the rectangle’s color, at this moment, is determined by the Builder’s shape settings. How can I have the color determined by the code snippet?

#4

No problem, it is a lot of code to translate. Here is another example, using mainly Builder, although a bit of code is unavoidable, given your task. I can explain how it works, and that will hopefully get you on your way.colRects.psyexp (14.8 KB)
conditions.xlsx (8.2 KB)

In this example, there are 4 polygon rectangle components, positioned in a shape. There is a keyboard for responding ‘r’ or ‘b’, and a mouse. In the trials loop, there is a conditions file. In the conditions file we have several variables. Each shape has an opacity rating of 1 or 0 under p1Show, p2Show…etc. This determines how many rectangles are presented, because opacity of 0 makes a shape invisible - this way you can change the number of shapes from an Excel file. There is a column called blueProp, which is the blue proportion of colors. Then, the number of rects is defined in the nRects column.

Control shape presentation in Builder

Look at the Opacity param in each shape. The opacity param for each shape is set to the corresponding variable from the conditions file e.g., shape p1 opacity is set to p1Show. This controls whether a shape is presented.

Using code components to define color proportions

Again, we have to use code, but we use the proportions from the conditions file. We have to also reset the color of each shape at the start of every trial - explained in the code snippet

# Calculate color proportions
nBlue = int((nRects / 100) * blueProp)  # calculate number of blues
nRed = nRects - nBlue  # Calculate number of redds
# Add those colors to a list for use later
cols = []
[cols.append('blue') for n in range(nBlue)]
[cols.append('red') for n in range(nRed)]
# Randomise colors
shuffle(cols)

# Create container of your shapes from Builder
rects = [p1, p2, p3, p4]
# Reset color of shapes
for rect in rects:
    rect.setColor('white')

Check for clicked shape, and change its color

As before, we have to loop through the shapes to ssee if they have been clicked, and then change their color accordingly. ‘r’ or ‘b’ ends the trial.

for rect in rects:
    if mouse.isPressedIn(rect):
        if rect.fillColor == 'white':
            rect.setColor(cols.pop())
#5

You could actually avoid all coding related to color list creation, if you create the color lists in your conditions file - but this would mean manually entering all the colors. E.g., conditions.xlsx (8.2 KB)
colRects.psyexp (14.7 KB).

This version has minimal coding. To finish the task probably does not require anymore code that what is in this file. E.g.,

shuffle(myCol)
# Create container of your shapes from Builder
rects = [p1, p2, p3, p4]
# Reset color of shapes
for rect in rects:
    rect.setColor('white')
for rect in rects:
    if mouse.isPressedIn(rect):
        if rect.fillColor == 'white':
            rect.setColor(myCol.pop())
#6

Ok I have made a working version online - this version varies number of shapes between 2 and 4 and
the color proportions also vary - just take these files and create a new project on Pavlovia.

colRects.psyexp (16.2 KB)
conditionsJS.xlsx (8.4 KB)

You no longer need any JavaScript coding, as it is already done in this example ( I ignored Python code, as you are running this online). All you will need to do, is add more shapes in Builder and link them to the conditions files. When done, add the correct settings in the conditions file - including setting the opacity to determine the number of shapes presented, and setting the color proportions, which is simply a list of red or blue words in an Excel cell. The rest of the task will need finishing, including instructions, and you may want to check to make sure the correct data is being logged. If you get stuck with that, just come back with a question.

1 Like
#7

Thank you @dvbridges! I am working on it.

What I have done, I have created a routine based on trial and I am looping linking to the excel file:

Sometimes the routine runs, but sometimes I get this error:

Error when generating experiment script:
local variable ‘clockStr’ referenced before assignment

#8

@seredes, which version of PsychoPy are you using? Also, does this happen when you run the version I created from Pavlovia?

#9

@dvbridges, thank you for your suggestion to look at the PsychoPy version. Indeed, the problem above (file not running) was due to the fact that I was opening the file with PsychoPy2.

I have made some progress in building the box task. In the task I have white “boxes” and each one may “contain” a different color (red or blue).
Right now, upon running the file in attachment, I have four white squares; if I click on one of the squares, the color “below” is revealed.

The problem is that if I click on a second square nothing happens; the change of color works only with the first square I click on - instead this should work for each square till all the colors are potentially “revealed”.

I attached the builder file and the condition file.
In my code I set the following code snippet under “Each Frame”, adapted from this page (I also set “click_on = False” under “Begin Routine”):

if mouse.isPressedIn(p1):
    if not click_on:
        click_on = True
        click_on = t # current time
        p1.fillColor = 'red'
    else:
        click_on = t 
if click_on and t - click_on > 10:
    click_on = False
    p1.fillColor = 'white'
    
if mouse.isPressedIn(p2):
    if not click_on:
        click_on = True
        click_on = t # current time
        p2.fillColor = 'red'
    else:
        click_on = t 
if click_on and t - click_on > 10:
    click_on = False
    p2.fillColor = 'white'
    
if mouse.isPressedIn(p3):
    if not click_on:
        click_on = True
        click_on = t # current time
        p3.fillColor = 'red'
    else:
        click_on = t 
if click_on and t - click_on > 10:
    click_on = False
    p3.fillColor = 'white'

if mouse.isPressedIn(p4):
    if not click_on:
        click_on = True
        click_on = t # current time
        p4.fillColor = 'blue'
    else:
        click_on = t 
if click_on and t - click_on > 10:
    click_on = False
    p4.fillColor = 'white'

This code goes with the following simplified condition file:

Again, the problem with this file is that I can unveil only one box, the first one I click on.

I have tried playing with the if statement, for example using ifelse, but the result was the same. I also realize that this code is inelegant and a bit cumbersome especially when I will use, let’s say, 24 boxes (but I am willing to go there if necessary!)

Please let me know how I could modify the code to have the possibility to “unveil” each “box” (the white squares).

conditionsJS.xlsx (8.5 KB)
colRects.psyexp (17.8 KB)

NOTE:

I have tried to use your code to build a loop:

rects = [p1, p2, p3, p4]
for rect in rects:
    if mouse.isPressedIn(rect):
        if not click_on:
            click_on = True
            click_on = t # current time
            rect.fillColor = 'red'
        else:
            click_on = t 
    if click_on and t - click_on > 10:
        click_on = False
        rect.fillColor = 'white'

This code gives me the same result as before though, and now all the squares are set to be changed to red (while before I had p4 set manually to blue.)

I know that your suggestion was slightly different; but when I try to implement your code in full…

shuffle(myCol)
# Create container of your shapes from Builder
rects = [p1, p2, p3, p4]
# Reset color of shapes
for rect in rects:
    rect.setColor('white')
for rect in rects:
    if mouse.isPressedIn(rect):
        if rect.fillColor == 'white':
            rect.setColor(myCol.pop())

…using this conditions file:

I then get the following error:

#10

I think your best bet, because you want to run the study online, is to use my online example I provided. It has all the JavaScript code already, so all you need to do is update the number of image components, and adapt the conditions file. Currently, you are writing your code in Python, which is good to understand the logic of what is required, but you still need to translate it to JavaScript, because this has to be done manually. If you have any problems with the example I provided, let me know and we can fix it.

#11

@dvbridges, thank you for the feedback. I have tried running your file but I get the following error:

Running: /Users/serena/Dropbox/SPRING 2019/Students’assignments/trial2/colRects_lastrun.py
/Applications/PsychoPy3.app/Contents/MacOS/python: can’t open file ‘/Users/serena/Dropbox/SPRING 2019/Studentsassignments/trial2/colRects_lastrun.py’: [Errno 2] No such file or directory

#12

As this is an online study, you do not need to run the Python code locally. You will need to create a new project on Pavlovia, and run the experiment from your chosen browser (I would use Chrome or Firefox).

Here is an example of your task running in a browser:

https://pavlovia.org/run/dvbridges/coloredrects/html/

#13

@dvbridges, thank you, it ran great in Firefox! I was confused because I am not used to PsychoPy3’s features. I will let you know if I have other questions as I fix the task.

#14

@dvbridges I have started building the task; my first step is to have 24 rectangles.

I decided to get started with eight rectangles, so I added four rectangles to your script, that originally had four. I made adjustments, but I get this mistake once I try to run the experiment on Firefox.

This is the console:

And the debugger:

These are the adjustments I had made because of the added shapes:

  1. I had added the four additional shapes to the condition file, and added colors (by the way, is adding colors right?):

  1. I also relinked the file from the loop:

image

  1. And added the shapes on the javascript file:

image

I guess there is still something that needs to be done!
I add the files in attachment. Thank you in advance for your help.

conditionsJS.xlsx (8.9 KB)
colRects.psyexp (25.4 KB)

#15

Hi @seredes, it is mostly ok, but I think there was just an issue with how the components were ordered in Builder. I also added the correct number of colors for those rects that are showing. There need to be as many colors and rects with opacity of 1. The balance of colors is not correct for what I added though.

conditionsJS.xlsx (8.7 KB)
colRects.psyexp (25.7 KB)

#16

Thanks a lot @dvbridges . I have completed the basic task with 24 rectangles. I attach the files.

I’d like to ask you: is there any way the output could also show how many rectangles have been clicked on before coming to the final decision?

Right now the output gives me the final decision as key press and the time needed to arrive to such decision, but that’s about it. If I could also know how many rectangles have been unveiled, and specifically, how many of them were red and how many blue, I could have more data to build my model.

Thank you
colRects.psyexp (54.6 KB)
conditionsJS.xlsx (8.9 KB)

#17

Thats good, nearly done then. Yes, you could have the number of rectangles presented on screen using a text component. First thing you need to do is create a JS object for holding your red and blue clicks. First, create a text component called “selected”, and leave the text blank, and position the text to appear under the blocks (or anywhere you wish). In the “Begin Routine” tab, under your other code add

selectedN = {'red': 0, 'blue': 0}; // This will be your container for colors clicked

Now you need to start counting your responses. In the each frame tab, add the following to the if statement that changes your rect colors

selectedN[col] += 1;

This adds a 1 to the red or blue field in the selectedN JS object. After the if statement, add

selected.text = "You have selected: " + (selectedN['red'] + selectedN['blue']);

This last line updates the text component on each frame with the summed number of clicked rects. Finally, to add the number of clicks for red and blue rects, add the following to the “End Routine” tab:

psychoJS.experiment.addData('redClicks', selectedN['red'])
psychoJS.experiment.addData('blueClicks', selectedN['blue'])