Ensuring unique item selection in a longitudinal image recognition task

Hello everyone,

I’m currently developing an image recognition task as part of a longitudinal study.
In this setup, each participant is asked to complete the task almost daily over a fairly long period (around 30 weeks).
This means each participant will perform the task approximately 150 times.

I now have a very large pool of items (over 150,000), and my goal is to ensure that a participant never sees the same item twice across their sessions.

My main question is:
Is it possible, based on certain input parameters (e.g., participant ID or session number), to automatically select specific CSV files for each task? Ideally, I’d like to handle this through variables to prevent any duplicates.

My experiment is currently structured with a double loop:

  • On each trial, I select an Excel file,

  • That file points to a set of sub-files containing the items that make up the trial set.

I was wondering if it’s possible to control which file gets loaded (for example: if my files have a numeric suffix, could that number correspond to the trial/session number?). This would help me make sure participants don’t encounter repeated items.

Thanks a lot in advance for any advice

And also, a big thank you for all the help I’ve already received — I wouldn’t have made it this far without your help!

I have a couple of online demos which address this issue: Multi-session Randomisation and Presentation Cap, though I think they could be improved on. My recommendation would be to have a shelf entry of the trials seen for each participant and then randomly select rows excluding those trials to create a useRows list for the loop.

You would also need to load resources on the fly.

Further information on the shelf.

For example, if exclude is a list of indices from the shelf then you could pick 150 rows using something like:

useRows = []
while len(useRows) < 150:
     randIdx = randint(150000)
     if randIdx not in useRows and randIdx not in exclude:
          useRows.append(randIdx) # edited

This method might be faster than starting with a list of numbers 0 - 149999, shuffling them and then cycling though a similar loop. I don’t know how long it would take to shuffle a list that long.

Hello

 if randIdx not in useRows

isn’t that a linear search which could become rather slow?

Isn’t it

useRows.append(randIdx)

in the last row instead of

useRows.append(randint)

What about instead?

exclude = {10, 20, 30}  # read from shelf
population = [x for x in range(150000) if x not in exclude]
useRows = random.sample(population, 150)

I guess there are a lot of different ways possible to program this. Given the size of the random sampling it is probably worth running some benchmarks.

Best wishes Jens

I’ve corrected my error about randIdx/randInt. Thanks for the spot.

Unfortunately, your suggestion won’t translate to JavaScript. My usual solution random.sample would be to shuffle and then take the first n values.

exclude = {10, 20, 30}  # read from shelf
population = []
for x in range(150000):
     if x not in exclude:
          population.append(x)
shuffle(population) # this could be slow
useRows = population[:150]
1 Like

The other approach using a random seed might just store the session number on the shelf.

If the seed for the loop is int(expInfo['participant']) then you could use routine settings to skip routines where trials.thisN < sessionNum * 150 (where sessionNum is retrieved from the shelf – or even inputted from expInfo (as int(expInfo['session'])

1 Like

Thanks for your help. I will try all these methods and get back to explain what I did and whether it worked.

Hello,

I’m trying to implement the Pavlovia shelf feature to store which sets of stimuli each participant has seen. My approach is based on the participant number.

I was able to successfully write to the shelf, but when I run the experiment again with the same participant number, the previous data is overwritten. What I would like is to store all the combinations of files a participant has seen, so that I can later prevent presenting those same combinations again.

Here’s a simplified version of my code:

Mon code ressemble à cela

let a = expInfo["participant"];
let exclude_trial = nom_fichier;  // récupère la valeur de la colonne "nom_fichier"

let database = psychoJS.shelf.getDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    defaultValue: {}
});

if (!("exclusionlv4" in database)) {
    database["exclusionlv4"] = [];
}

database["exclusionlv4"].push(exclude_trial);

psychoJS.shelf.setDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    fieldValue: database
});

If anyone has an idea of how I can prevent previously stored values from being overwritten (and instead keep adding to them), I’d really appreciate your help!

Thanks in advance!

This bit is confusing me

if (!("exclusionlv4" in database)) {
    database["exclusionlv4"] = [];
}

database["exclusionlv4"].push(exclude_trial);

database is assigned the value of “a” in the shelf dictionary. I would expect it to be a list of excluded trials for participant a. I would therefore expect to see something like:

let a = expInfo["participant"];
let exclude_trial = nom_fichier;  // récupère la valeur de la colonne "nom_fichier"

let database = psychoJS.shelf.getDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    defaultValue: {}
});

if (!("exclusionlv4" in database)) {
    database = []; // This resets the exclusios if it doesn't already include exclusionlv4, which seems odd
}

database.push(exclude_trial);

psychoJS.shelf.setDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    fieldValue: database
});

I used your code, but there’s still an issue. I added logs to check what’s happening in my console, and here’s what the code outputs

let a = expInfo["participant"];
let exclude_trial = nom_fichier;  // récupère la valeur de la colonne "nom_fichier"

let database = psychoJS.shelf.getDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    defaultValue: {}
});
console.log("contenue de la liste tirer du serveur ",a, ":", database);
if (!("exclusionlv4" in database)) {
    database = []; // This resets the exclusios if it doesn't already include exclusionlv4, which seems odd
}
console.log("contenue de la liste de ",a, ":", database);

database.push(exclude_trial);

console.log("contenue de la liste de ",a, ":", database);
psychoJS.shelf.setDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    fieldValue: database
}); 

jI think the returned object is being misinterpreted by the script, and it keeps appearing constantly without the exclusionlv4 exclusion.

I just noticed that you haven’t included “await” (which is in my tip)

Talking to the shelf takes time so you need to include await to pause execution until a value has been returned.

You also probably don’t need “let” but I don’t think that’s the issue.

You’re right — I forgot to include it.However, by modifying this part of the code

a = expInfo["participant"];
exclude_trial = nom_fichier;  // récupère la valeur de la colonne "nom_fichier"

database = await psychoJS.shelf.getDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    defaultValue: {}
});
console.log("contenue de la liste tirer du serveur ",a, ":", database);
if (!("exclusionlv4" in database)) {
    database = []; // This resets the exclusios if it doesn't already include exclusionlv4, which seems odd
}
console.log("contenue de la liste de ",a, ":", database);

database.push(exclude_trial);

console.log("contenue de la liste de ",a, ":", database);
await psychoJS.shelf.setDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    fieldValue: database
});

the server does return the list. The issue is that I can’t figure out how to edit it to add new items.

My goal is to keep track of the test sets I’ve used during a session so I can exclude them later (and possibly replace a loop with a simple random draw).

Maybe there’s a better strategy for this, but this seemed like the most straightforward solution based on the sources you provided. I thought it could be interesting to do it locally and create a csv in the gitlab and edit it during the experiment. I don’t know if it’s possible

Add a console log of exclude_trial.

I would remove the reset. As far as I can tell you are getting what you are asking for because you are emptying the list before pushing to it.

Yeah it was this !! You was right thank you very much!! I have to find a way to create new table on the shelf.

the final code is this one :

a = expInfo["participant"];
exclude_trial = nom_fichier;  // récupère la valeur de la colonne "nom_fichier"

database = await psychoJS.shelf.getDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    defaultValue: {}
});
console.log("contenue de la liste tirer du serveur ",a, ":", database);
if (!Array.isArray (database)) {
      database = []; // This resets the exclusios if it doesn't already include exclusionlv4, which seems odd
  }

console.log("contenue de la liste de ",a, ":", database);

database.push(exclude_trial);

console.log("contenue de la liste de ",a, ":", database);
await psychoJS.shelf.setDictionaryFieldValue({
    key: ["Liste_info"],
    fieldName: a,
    fieldValue: database
});