Trial judgement and present unequal fillers

PennController for IBEX Forums Support Trial judgement and present unequal fillers

Viewing 7 posts - 1 through 7 (of 7 total)
  • Author
    Posts
  • #9573
    xkyan
    Participant

    Hi Jeremy,

    I’m implementing an experiment and was stuck there in designing a template. There are three points unsolved and I appreciate it a lot if you can give some suggestions.

    1. I have two kinds of trials as my target, one is keyresponding to the arrow’s direction and the other one is a self-paced reading of a sentence. I hope they can show as (pic, spr) pair and each pair is regarded as a target. Currently I implemented them in NewTrial. But I think this way is not flexible. I’m wondering if there’s anything like a function so that I can put the right actions in a function for each kind?

    2. The other question is about unequal number of fillers. I got 20 chances to present 40 fillers. I’m curious if it is possible to generate a list of 20 numbers between 1 to 3 that sum up to 40 and present corresponding number of fillers every chance?

    3. Is there a good way to control the order of presentation in target, filler, target, filler, …?

    This is the basic part I have finished (https://farm.pcibex.net/r/IqekEL/). All suggestions about solving the mentioned problems are welcome. Thank you in advance.

    #9575
    Jeremy
    Keymaster

    Hi,

    1. I’m not sure I understand: what’s wrong with the way you’re currently coding the design? (other than that you might want to remove the Canvas element instead of just the Image element, so that the Controller elements do not appear so far down on the page)

    If you find it easier to code the two tasks that constitute a trial separately, you can always create two functions that each return an array of PennController commands, and call those functions inside the newTrial so the commands are executing sequentially, eg:

    const pic = row => [
        newImage("pic", row.pic_name).size(720, 405).print()
        ,
        newCanvas("canvas_pic",720,405)
            .add( 0, 0, getImage("pic"))
            .center()
            .print()
        ,
        newTimer("displaytime", 2000).start()
        ,
        newKey("resp-flanker", "FJ")
            .log()
            .callback( getTimer("displaytime").stop() )
        ,
        getTimer("displaytime").wait()
        ,
        getCanvas("canvas_pic").remove()
    ];
    
    const spr = row => [
        newController("DashedSentence", {s: row.sent_cont})
            .print()
            .log()
            .wait()
            .remove()
        ,
        newController("Question", {q: "Does it make sense?", as: ["Yes", "不合理"]})
            .print()
            .log()
            .wait()
            .remove()
    ];
    
    Template("items.csv", row =>
        newTrial("experimental-trial",
            ...pic(row)
            ,
            ...spr(row)
        )
    )

    2. Do you mean that between each target trial (ie. pic+spr pair) you want to have 1-to-3 filler trials? And that you would have 20 occurrences of those filler breaks, and you want the total of filler trials to sum up to 40?

    3. This will depend on the answer to question 2. If you have, say, 40 trials labeled “target” and 40 trials labeled “filler”, then rshuffle("target","filler") would give you just that: a series of 80 trials alternating between trials labeled “target” and trials labeled “filler”

    On the other hand, if you have 20 target trials and 40 filler trials, and want to insert 1-to-3 filler trials after each target trial, which is how I understand your second point, then you could define this function to generate an array of random integers that sum up to a set value:

    const nRandomIntsToSum = (length,minInt,maxInt,targetSum) => {
      const a = [];
      for (let i=0; i<length; i++) {
        let min = minInt, max = maxInt, remainder = targetSum-(a.reduce((x,y)=>x+y,0)+min*(length-(i+1)));
        if (remainder < maxInt) max = remainder;
        a.push( min+Math.round(Math.random()*(max-min)) );
      }
      fisherYates(a);
      return a;
    }

    Then you can use the custom pick function in 20 iterations, like this:

    targets = randomize("target")
    fillers = randomize("filler")
    repetitions = nRandomIntsToSum(20,1,3,40)
    
    Sequence(
      "instructions", 
      ...repetitions.map(n=>[ pick(targets,1),pick(fillers,n) ]).flat()
    )

    Jeremy

    #9578
    xkyan
    Participant

    Hi Jeremy,

    Thank you a lot for the time and patience helping me figure out.

    For 1, yes there’s no problem with the current version, and the easier one you posted is exactly the one I’m looking for!

    For 2 and 3, sorry I didn’t explain it clearly but yes you understood it right. Those two functions really help a lot and did the things I want them to do.

    Again, thank you so much for the help!

    #9590
    xkyan
    Participant

    Hi Jeremy,

    Sorry it’s me again. When I tried to test the presentation of fillers, I encountered a problem that “Please wait while the resources are preloading. This may take up to 1min.” But I’ve waited for more than 15 mins and it still didn’t finish. I’m wondering if there’s sth wrong with my codes, or it was caused by some other problems (e.g. network connection, etc.).

    Basically I just want to test if two kinds of fillers can be detected and presented in the right format. Here’s my code:

    
    Sequence(randomize("fillers"))
    // Define the template for filler trials
    Template("items-fillers.csv", row =>
    
        newTrial("fillers",
            // newVar("RT").global().set("0"),
            // newVar("TFPress").global().set(false),
            newVar("kind", row.condition)
            ,
            // pic(row)
            getVar("kind")
                .test.is("flanker")
                .success(
                    pic(row)
                )
                .failure(
                    spr(row)
                )
        )
    )
    

    Thanks a lot in advance!

    #9597
    Jeremy
    Keymaster

    Hi,

    You cannot use PennController’s test commands to conditionally include an Image element: you are effectively creating an Image element for your filler items too, which don’t have any value for flan_file, resulting in invalid Image elements

    In the script you currently have in your project, your sentence and flanker filler trials actually have nothing in common (as things stand, they do not define pic-spr pairs: there is no pic-spr sequence of commands, unlike in my code above for example; your code currently either includes pic commands or spr commands in a trial) so if that’s the kind of design you are implementing, I would just split the CSV file in two tables and correspondingly use two Template commands to generate the filler trials (and also one Template for your target trials, since they all seem to be SPR’s):

    Template("flanker-fillers.csv", newTrial("fillers", ...pic(row) )
    Template("sentence-fillers.csv", newTrial("fillers", ...spr(row) )
    Template("items-target.csv", newTrial("targets", ...spr(row) )

    Jeremy

    #9737
    xkyan
    Participant

    Hi Jeremy,

    Thanks for the previous answers! Here’s a follow-up:

    I tried to reorganize my materials and correct my codes as following. But the Sequence controller did not work (my guess) so I can’t present trials in the order I want.

    
    PennController.ResetPrefix(null)
    // DebugOff()
    
    // function ----------------------------------
    const nRandomIntsToSum = (length,minInt,maxInt,targetSum) => {
        const a = [];
        for (let i=0; i<length; i++) {
          let min = minInt, max = maxInt, remainder = targetSum-(a.reduce((x,y)=>x+y,0)+min*(length-(i+1)));
          if (remainder < maxInt) max = remainder;
          a.push( min+Math.round(Math.random()*(max-min)) );
        }
        fisherYates(a);
        return a;
    }
    
    const pic = row => [
        // set a fixation
        newText("fixation1", "+")
            .css("font-size","50px")
            .print("center at 50%" , "center at 50%")
            .log()
        ,
        newTimer("fixtime1",500).start().wait()
        ,
        getText("fixation1").remove() 
        ,
        // set the picture stimuli
        newImage("pic", row.flan_file).size(720, 405).print()
        ,
        newCanvas("canvas_pic",720,405)
            .add( 0, 0, getImage("pic"))
            .center().print()
        ,
        // start recording RT
        getVar("RT1").global().set( v => Date.now() )
        ,
        // set key response
        newKey("flan_RespKey", "FJ")
            .log("flan_RespKey", getKey("flan_RespKey"))
            .wait()
            .callback(
                getVar("RT1").set( v => Date.now() - v )
                    .log( "flanRT" , getVar("RT1") )
            )
        ,
        // record RT
        getVar("RT1").set( v => Date.now() - v )
            .log( "flanRT" , getVar("RT1") )
        ,
        // clear the screen
        getCanvas("canvas_pic").remove()
    ];
    
    const spr = row => [
        // set a fixation
        newText("fixation2", "+")
            .css("font-size","50px")
            .print("center at 50%" , "center at 50%")
            .log()
        ,
        newTimer("fixtime2",500).start().wait()
        ,
        getText("fixation2").remove() 
        ,
        // set the spr stimuli
        newController("DashedSentence", {s: row.sent_cont})
            .print()
            .log()
            .wait()
            .remove()
        ,
        // start recording RT
        getVar("RT2").global().set( v => Date.now() )
        ,
        newController("Question", {q: "这句话合理吗?", as: [["F","合理"], ["J","不合理"]], hasCorrect: parseInt(row.sentCorr), randomOrder: false})
            .print()
            .log()
            .wait()
            .remove()
        ,
        // record RT
        getVar("RT2").set( v => Date.now() - v )
            .log( "sentRT" , getVar("RT2") )
    ];
    
    // Main --------------------------
    
      
    // Instructions
    newTrial("instructions",
        defaultText
            .cssContainer({"margin-bottom":"1em"})
            .center()
            .print()
        ,
        newText("instructions-1", "Welcome!")
        ,
        newText("instructions-2", "In this experiment, you will hear and read a sentence, and see two images.")
        ,
        newText("instructions-3", "<b>Select the image that better matches the sentence:</b>")
        ,
        newText("instructions-4", "Press the <b>F</b> key to select the image on the left.<br>Press the <b>J</b> key to select the image on the right.<br>You can also click on an image to select it.")
        ,
        newTextInput("input_ID")
            .cssContainer({"margin-bottom":"1em"})
            .center()
            .print()
        ,
        newButton("wait", "Click to start the experiment")
            .center()
            .print()
            .wait()
        ,
        newVar("ID")
            .global()
            .set(getTextInput("input_ID"))
    )
    
    // Define the template for target trials (完成,没问题)
    Template("items-targets.csv", row =>
        newTrial("targets",
            newVar("RT1").global().set("0"),
            newVar("RT2").global().set("0"),
    
            pic(row)
            ,
            spr(row)
        )
    )
    
    // Define the template for filler trials
    Template("items-fillers-flan.csv", row =>
        newTrial("fillers",
            pic(row)
        )
    )
    Template("items-fillers-spr.csv", row =>
        newTrial("fillers",
            spr(row)
        )
    )
    
    targets = randomize("targets")
    fillers = randomize("fillers")
    repetitions = nRandomIntsToSum(60,1,3,120)
    
    Sequence(
        "instructions",
        repetitions.map(n=>[ pick(targets,1),pick(fillers,n) ]).flat()
      )
    
    // Sequence("instructions", randomize("targets"))
    // Sequence("instructions", "targets")
    

    However, I found that when I ran it, it would go over with the Template order I coded (i.e. all targets in the sequential order as in the csv file, then the filler-flan, then the filler-spr) and the Sequence controller seems not work at all. Here are some other observations I hope to share with you while attempting to debug:

    1. I tried to change the order of Template and the presented order during experiment changed correspondingly, which proved that the Sequence controller indeed did not work and it went with the order of the Template. It makes sense after checking the doc because I found that the function of Template is generating trials so the trials are presented once they are generated.
    2. I tried to change the Sequence to see which part of Sequence can work. For example, I tried Sequence("instructions", "fillers", randomize("targets")) and it worked! So I guess there might be something wrong with my current Sequence design so it failed to work and therefore the experiment just went with the sequential order of Templates. But after checking .map and .flat, I still didn’t find where the bug is in this line.

    Could you please help me figure out why it fails? I’ve been stuck here for quite a long time which is frustrating 🙁 I really appreciate your help! Thanks in advance.

    #9749
    Jeremy
    Keymaster

    Hi,

    The project at https://farm.pcibex.net/r/IqekEL/ does not define the function pick. Also, you forgot ... before repetitions in Sequence

    Once I fix both issues, the experiment runs with a mix of target and filler trials, as intended

    Jeremy

Viewing 7 posts - 1 through 7 (of 7 total)
  • You must be logged in to reply to this topic.