Random select of image stimuli without repeat in nested loop

psychopy version: 2020.2.10
Hello,

I want to display 8 images across two blocks in multiple nested loops. 4 images consist of a event, re: the criteria to display image is the same. I designed multiple nested loops because I need the condition of the image selection based on block number and event position in the given block. I found out even though I selected loopType as random, the images are still displayed in repeats across blocks and between events. Below is the scheme of the design:


I have two image criteria: red: Emotional and blue: Neutral.

My psychopy nested loop is:


image
This is my event order condition file:
image

With the outer “twoblocks” loop, the “eventcond_1” loops gets which block condition is being selected and opens the .csv file that contain the order of images in each events. Then the “Imageselect_1” loop open either “emotion.csv” or “neutral.csv” files based on the event_1 condition. The “emotion.csv” and “neutral.csv” contains the list of names of the images. My custom code component only allows the Imageselect_1 loop to run 4 times, so that 4 images will be displayed in one event. Since, I set random as loopType, only the images within an event is randomly displayed without repeats.

Would anyone help me with dislpay all 16 images (8images/block x2) without repeats?

Thanks in advance!

Hi,

Do you mean that you might get the same picture repeated twice, if an event has the same condition as the previous one?

Hello Filiabel,

Thank you for helping again.

Yes it is. It happens when the events are the same condition between blocks. I also tried three events in a block (my final goal is 8 events). If the events within a block was the same condition, the image can have repeats. Since the condition of the image was randomly pick up images in either neutral pool (neutral.csv) or emotional pool (emotion.csv). Even though I set the Imageselect_1 loop as random, it only gaurantees image within an event is not repeated.

Okay, I have a suggestion. Probably not the best solution, but it doesn’t require you to change the current experiment structure.

In the inner loop, remove ImgS from the condition file. We will load list of image files ourselves.
Add a code component in your event routine:

### Begin experiment
import pandas as pd

# Read csv, access first col, make Python list
# Do this only once at beginning of exp. No need to read from file over and over again.
neutral = pd.readcsv('Neutral.csv').iloc[:,0].tolist() # Read csv, choose first col, and convert to list
emotion = pd.readcsv('Emotion.csv').iloc[:,0].tolist() 

# Placeholder variables
prev_imgs = ''
last_idx = -1

# Dispatcher for lists of image filenames
imgs_select = {'Emotion.csv': emotion, 'Neutral.csv': neutral}


### Begin routine
n = currentLoop.thisN

if n == 0: # Only on first iteration of each event
    all_images = imgs_select[ImgS] 

    image_idxs = np.arange(len(all_images))

    # Exclude idx of last picture if same condition
    if ImgS == prev_imgs:
        image_idxs = np.setdiff1d(image_idxs, last_idx)

    event_idx = randchoice(image_idxs, size=4) # Choose four

    # Get our four random pictures
    event_images = all_images[event_idx]
    
    # Remember this for next event intialization
    prev_imgs = ImgS
    last_idx = event_idx[-1]

# Assign image
event_image = event_images[n]

Have a try at this. Just a rather quick solution from me, and as I said probably not the best.

Hello Filiabel,

Thanks a lot. I have tried it out. But I kept encountering the error: event_images = all_images[event_idx] TypeError: only integer scalar arrays can be converted to a scalar index. I have tried to make the event_idx array into integer using a for loop or np.int() function. But it still exist. Do you have any thougts?

Your idea of importing the .csv files inspired me. I am now trying import the list of emotion.csv and neutral.csv, shuffle them in the begining of the experiment. Get the image display in each event based on the current condition, re: if the current imageselect_1 trial is emotion, draw images from the emotional shuffled list. The images have to draw based on the block and event number, re: if it is first block of first event, draw images from shuffled list 1-4 position.

My question is how can I find the current trial condition, re: whether it is emotion or neutral (defined by ImgS). And how I can find my current block number and event number? eventcont_1.thisN for event number and twoblocks.thisN for block number?

A million thanks!!

Oops, my bad. all_images is a list and cannot be sliced like a numpy array.
Try changing the csv imports from tolist() to to_numpy().

I am sorry to borther you again. I got this error message:

Then I have tried

But the same error occured.

No worries. But I would advise to always Googling the error message :slight_smile: You’ll often end up on Stack Overflow.

It might be due to Psychopy using an old Pandas version. Seems like this is a method that works in all versions:

neutral = np.array(pd.readcsv('Neutral.csv').iloc[:,0])

Hello Filiabel,

Thanks for the instruction.

Now I have the error: emotion not defined.

I am very confused, your orginal code already have the line imgs_select = {'Emotion.csv': emotion, 'Neutral.csv': neutral}. I also tried match the cases in the condition file to the code and change Emotion.csv into Emotion in my condition file. All end up with this error.

You need lower case for the variable names in the dictionary. Python variables are case sensitive.

1 Like

I have tried both lower and upper cases. It kept saying “emotion” not defined. :cry:

Can you send your whole code component as it is now?

Have you changed the variable name to neutral_list and emotion_list perhaps?

You are totally right. I have created another version for testing and did not change it back.

Now the error become:

15.2008     ERROR     Couldn't find image t; check path? (tried: C:\Users\cat\Desktop\emotion-boundary interaction paradigm\Psychopy\t)
Traceback (most recent call last):
  File "C:\Users\cat\Desktop\emotion-boundary interaction paradigm\Psychopy\EmotionBoundary_trial_custom_Fil_lastrun.py", line 247, in <module>
    image_1.setImage(event_images)
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\visual\image.py", line 305, in setImage
    setAttribute(self, 'image', value, log)
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\tools\attributetools.py", line 141, in setAttribute
    setattr(self, attrib, value)
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\tools\attributetools.py", line 32, in __set__
    newValue = self.func(obj, value)
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\visual\image.py", line 292, in image
    wrapping=False)
  File "C:\Program Files\PsychoPy3\lib\site-packages\psychopy\visual\basevisual.py", line 823, in _createTexture
    raise IOError(msg % (tex, os.path.abspath(tex)))
OSError: Couldn't find image t; check path? (tried: C:\Users\cat\Desktop\emotion-boundary interaction paradigm\Psychopy\t)

This is very weird, I never have anytime name “t” in my image list. Below is the snip of my image list. They are all ends with “.bmp”.

This error happened after successfully showing one picture.

Since I need to show four images in an event, I set the nReps$ =4. I also tried 1, the program proceed with no problem. But only showed 1 image per event.

Below is my code (not much differ from your original one):

### Begin experiment
import pandas as pd

# Read csv, access first col, make Python list
# Do this only once at beginning of exp. No need to read from file over and over again.
neutral = np.array(pd.read_csv('neutral.csv').iloc[:,0]) # Read csv, choose first col, and convert to list
emotion = np.array(pd.read_csv('emotion.csv').iloc[:,0])

# Placeholder variables
prev_imgs = ''
last_idx = -1

# Dispatcher for lists of image filenames
imgs_select = {'emotion.csv' : emotion, 'neutral.csv' : neutral}

## Begin routine
n = currentLoop.thisN

if n == 0: # Only on first iteration of each event
    all_images = imgs_select[ImgS] 

    image_idxs = np.arange(len(all_images))
    


    # Exclude idx of last picture if same condition
    if ImgS == prev_imgs:
        image_idxs = np.setdiff1d(image_idxs, last_idx)

    event_idx = np.random.choice(image_idxs, size=4) # Choose four

    # Get our four random pictures
    event_images = all_images[event_idx]
    
    # Remember this for next event intialization
    prev_imgs = ImgS
    last_idx = event_idx[-1]
    
#Imageselect_1.addData(prev_imgs)

# Assign image
event_images = event_images[n]

Hello Filiabel,

I very appreciate your help. Since I can’t make the error go away, I modified from your code and came up with an simple version.

This is my current code that worked:

### Begin experiment
import pandas as pd

# Read csv, access first col, make Python list
# Do this only once at beginning of exp. No need to read from file over and over again.
neutral = np.array(pd.read_csv('neutral.csv').iloc[:,0]) # Read csv, choose first col, and convert to list
emotion = np.array(pd.read_csv('emotion.csv').iloc[:,0])

#shuffle their order
shuffle(neutral)
shuffle(emotion)

# Dispatcher for lists of image filenames
imgs_select = {'emotion.csv': emotion, 'neutral.csv': neutral}

### Begin routine
n = currentLoop.thisN

if n == 0: # Only on first iteration of each event
    currentlist = imgs_select[ImgS] 

n_imageselect_1 = Imageselect_1.thisN
n_eventcond_1 = eventcond_1.thisN
n_blocks = blocks.thisN

trialnow = n_blocks*8 +n_eventcond_1*4 + n_imageselect_1-1

event_images = currentlist[trialnow]

I am afraid that all my trial and block counter won’t work online. Do you have any suggestion for coding it so that it is compatiable online? I know the trick for counting current trial number in the inner-most loop. I have not figured how to count the outer loop, except: using “.thisN”.

Another question I have is how I can write the shuffled neutral and emotional list into my final data file? I have tried .adddata, but failed.

Do you mean you tried thisExp.addData ? (which will work online if you consult my crib sheet)

Yes, I have tried it using thisExp.addData(event_images), and I got this error.
image

thisExp.addData('Desired column name in your data file',valueToSave)

How about implementing a simple counter variable that is initiated to zero at beginning of experiment, and then add 1 at the end of each event routine?

### Begin experiment
trialNow = 0

### End routine
trialNow += 1

Just a tip:
When you get “readable” and straight forward errors like this, always check the documentation for the given function/method first :grinning:
Docs for addData clearly states its parameters (name, value) and includes usage examples.